Реалізація холдирування платежу на веб-сайті
Холдирування (авторизація без списання) — механізм, при якому гроші блокуються на карті покупця, але не надходять продавцю до явного підтвердження. Використовується там, де окончательна сума або факт виконання замовлення невідомі в момент оплати: доставка з оплатою по факту, бронювання з змінною вартістю, предзамовлення.
Як працює двостадійна оплата
Процес складається з двох операцій: authorize (авторизація, блокування коштів) і capture (захист, фактичне списання). Між ними — довільний проміжок часу, але з обмеженням: більшість банків знімають холд через 7 днів, деякі через 30. Якщо capture не відбувся — холд снімається автоматично, гроші повертаються.
Stripe: authorize + capture
У Stripe двостадійна оплата реалізується через capture_method: manual:
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => $order->total_cents,
'currency' => 'eur',
'capture_method' => 'manual', // не списувати одразу
'payment_method_types' => ['card'],
'metadata' => ['order_id' => $order->id],
]);
Після підтвердження 3DS покупцем PaymentIntent переходить у статус requires_capture. Гроші заблоковані, але не списані. Коли замовлення готове до відправки:
\Stripe\PaymentIntent::capture($order->stripe_payment_intent_id, [
'amount_to_capture' => $finalAmountCents, // можна менше ініціального
]);
Capture можна виконати на суму, меншу заблокованої — наприклад, якщо частина товару виявилася недоступна. На більшу — нельзя.
YooKassa: двостадійна оплата
$payment = $client->createPayment([
'amount' => ['value' => '1500.00', 'currency' => 'RUB'],
'capture' => false, // двостадійна оплата
'payment_method_data' => ['type' => 'bank_card'],
'confirmation' => ['type' => 'redirect', 'return_url' => $returnUrl],
'description' => 'Замовлення #' . $order->id,
], uniqid('', true));
Коли готові списати:
$client->capturePayment(
['amount' => ['value' => '1500.00', 'currency' => 'RUB']],
$order->yookassa_payment_id,
uniqid('', true)
);
Відмена холду (cancel authorization)
Якщо замовлення відмінено до capture — потрібно явно відмінити авторизацію, інакше гроші залишаються заблокованими до istечення строку:
// Stripe
\Stripe\PaymentIntent::cancel($order->stripe_payment_intent_id, [
'cancellation_reason' => 'abandoned',
]);
// YooKassa
$client->cancelPayment($order->yookassa_payment_id, uniqid('', true));
Моніторинг istекаючих холдів
Строк дії авторизації — критичний параметр. Потрібен фоновий процес, який знаходить холди, близькі до istечення:
// Команда, запускається раз на годину
$expiringSoon = Order::where('payment_status', 'authorized')
->where('authorized_at', '<', now()->subDays(6)) // Stripe дає 7 днів
->get();
foreach ($expiringSoon as $order) {
// Сповістити менеджера або автоматично скасувати
Notification::send($order->manager, new AuthorizationExpiring($order));
}
Зберігати authorized_at в таблиці замовлень — обов'язково. Строк Stripe — 7 днів для більшості карт, до 31 дня для деяких. Краще орієнтуватися на 5–6 днів як безопасний ліміт.
Partial capture
Stripe підтримує partial capture — захист суми менше авторизованої. Різниця автоматично розблокується. Це корисно для сценаріїв з змінною вартістю доставки: спочатку авторизуємо максимум, по факту списуємо реальну вартість.
\Stripe\PaymentIntent::capture($intentId, [
'amount_to_capture' => $actualAmountCents,
]);
// Остача ($authorizedAmount - $actualAmount) автоматично розблокується
CloudPayments та більшість російських провайдерів partial capture також підтримують, але синтаксис і поведінка потрібно перевіряти в документації конкретного шлюзу — відмінності є.







