Реализация Split-платежей (расщепление оплаты) на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Split-платежей (расщепление оплаты) на сайте
Сложная
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Реализация Split-платежей (расщепление оплаты) на сайте

Split-платёж — это разбивка одной транзакции покупателя на несколько получателей одновременно. Типичные сценарии: маркетплейс, где часть суммы идёт продавцу, часть — платформе; booking-сервис с комиссией агрегатора; подписка с revenue share между партнёрами. Без правильной архитектуры это превращается в ручную бухгалтерию с регулярными ошибками и спорами.

Реализация занимает от 5 до 14 рабочих дней в зависимости от количества получателей, логики распределения и используемого платёжного провайдера.

Модели расщепления

Есть два принципиально разных подхода, и выбор между ними определяет всё остальное.

Charge + Transfer (Stripe) — деньги приходят на мастер-аккаунт платформы, затем вручную (через API) переводятся на connected accounts. Платформа несёт ответственность за KYC продавцов, комплаенс и возможные возвраты.

Direct Charge — клиент платит напрямую продавцу, платформа получает application fee. Продавец сам проходит KYC. Меньше ответственности, но меньше контроля.

Для большинства маркетплейсов на ранней стадии проще Charge + Transfer — меньше юридической сложности при онбординге продавцов.

Stripe: реализация Charge + Transfer

Stripe Connect — де-факто стандарт для split-платежей. Сначала создаём PaymentIntent на полную сумму:

$paymentIntent = \Stripe\PaymentIntent::create([
    'amount' => $order->total_cents,
    'currency' => 'eur',
    'payment_method_types' => ['card'],
    'metadata' => [
        'order_id' => $order->id,
        'split_recipients' => json_encode($order->recipients),
    ],
]);

После успешного платежа — событие payment_intent.succeeded в webhook. В обработчике выполняем трансферы:

public function handlePaymentSucceeded(array $payload): void
{
    $intent = $payload['data']['object'];
    $recipients = json_decode($intent['metadata']['split_recipients'], true);

    foreach ($recipients as $recipient) {
        \Stripe\Transfer::create([
            'amount' => $recipient['amount_cents'],
            'currency' => $intent['currency'],
            'destination' => $recipient['stripe_account_id'],
            'transfer_group' => $intent['transfer_group'],
            'source_transaction' => $intent['charges']['data'][0]['id'],
        ]);
    }
}

transfer_group связывает все трансферы с исходным платежом — это критично для корректного рефанда. source_transaction гарантирует, что трансфер выполняется только из средств конкретного платежа, а не из общего баланса.

Хранение конфигурации расщепления

Правила split хранятся в БД, не в коде — иначе каждое изменение комиссии требует деплоя:

CREATE TABLE split_rules (
    id          bigserial PRIMARY KEY,
    entity_type varchar(50)    NOT NULL, -- 'seller', 'partner', 'platform'
    entity_id   bigint         NOT NULL,
    rule_type   varchar(20)    NOT NULL, -- 'percentage', 'fixed', 'remainder'
    value       numeric(10, 4) NOT NULL,
    priority    int            NOT NULL DEFAULT 0,
    currency    char(3),
    active      boolean        NOT NULL DEFAULT true,
    created_at  timestamptz    NOT NULL DEFAULT now()
);

Расчёт долей перед созданием трансферов:

class SplitCalculator
{
    public function calculate(int $totalCents, array $rules): array
    {
        $allocated = 0;
        $result = [];

        // Сначала фиксированные суммы
        foreach ($rules as $rule) {
            if ($rule['rule_type'] === 'fixed') {
                $result[] = ['recipient' => $rule['entity_id'], 'amount' => $rule['value']];
                $allocated += $rule['value'];
            }
        }

        // Затем процентные
        foreach ($rules as $rule) {
            if ($rule['rule_type'] === 'percentage') {
                $amount = (int) round($totalCents * $rule['value'] / 100);
                $result[] = ['recipient' => $rule['entity_id'], 'amount' => $amount];
                $allocated += $amount;
            }
        }

        // Остаток — платформе или last-in-line получателю
        $remainder = $totalCents - $allocated;
        foreach ($rules as $rule) {
            if ($rule['rule_type'] === 'remainder') {
                $result[] = ['recipient' => $rule['entity_id'], 'amount' => $remainder];
                break;
            }
        }

        return $result;
    }
}

Правило remainder всегда должно быть ровно одно — это защита от округления и накопленных ошибок. Сумма долей обязана совпадать с total до копейки.

Возвраты при split

Возврат при расщеплённом платеже — самое болезненное место. Stripe автоматически не отзывает трансферы при рефанде на PaymentIntent — это нужно делать явно:

public function refund(Order $order, int $refundCents): void
{
    // 1. Рефанд основного платежа
    \Stripe\Refund::create([
        'payment_intent' => $order->stripe_payment_intent_id,
        'amount' => $refundCents,
        'refund_application_fee' => true,
    ]);

    // 2. Реверс трансферов пропорционально
    $ratio = $refundCents / $order->total_cents;
    foreach ($order->transfers as $transfer) {
        $reverseAmount = (int) round($transfer->amount_cents * $ratio);
        \Stripe\Transfer::createReversal($transfer->stripe_transfer_id, [
            'amount' => $reverseAmount,
            'refund_application_fee' => true,
        ]);
    }
}

Если на аккаунте получателя недостаточно средств для реверса (например, он уже вывел деньги), Stripe вернёт ошибку. В этом случае нужна логика дебетования — отдельный сценарий с manual intervention.

Альтернативы Stripe

CloudPayments (для СНГ) поддерживает split через механизм Receipt с несколькими получателями, но API менее гибкий. YooKassa имеет встроенный split для маркетплейсов через Deal API — создаётся deal, к нему привязываются выплаты. Fondy и LiqPay предлагают split через партнёрские договоры, конфигурация на стороне провайдера, не через API.

Мониторинг и согласование

Каждый день запускается reconciliation job — сравнение сумм трансферов в БД с реальными трансферами в Stripe через API:

$stripeTransfers = \Stripe\Transfer::all([
    'created' => ['gte' => $yesterday->timestamp, 'lt' => $today->timestamp],
    'limit' => 100,
]);

$dbTransfers = Transfer::whereDate('created_at', $yesterday)->get()->keyBy('stripe_id');

foreach ($stripeTransfers->autoPagingIterator() as $transfer) {
    if (!isset($dbTransfers[$transfer->id])) {
        Log::critical('Untracked transfer', ['stripe_id' => $transfer->id, 'amount' => $transfer->amount]);
    }
}

Расхождения уходят в алерт. Это не паранойя — webhook-и иногда теряются, особенно при деплоях в момент транзакции.

Налоговые и юридические аспекты

Расщепление платежа не освобождает платформу от фискальных обязательств — в большинстве юрисдикций платформа является налоговым агентом. В России это означает передачу данных о выплатах в ФНС, в ЕС — DAC7 reporting. Это нужно учитывать при проектировании схемы split ещё на старте — переделывать потом дороже.