Платежи
Безопасный приём денег: шлюзы, 3-D Secure, вебхуки, идемпотентность, возвраты и сверка.
За 30 секунд
Платёж — это разговор между вашим магазином, платёжным шлюзом, банком покупателя и иногда телефоном покупателя. Ваша задача — безопасно начать этот разговор, никогда не прикасаться к реальным номерам карт и фиксировать истинный результат, даже когда сообщения приходят с опозданием, дважды или не приходят вовсе. Именно это обеспечивают вебхуки, ключи идемпотентности и задачи сверки.
Почему я это использую
- Я использую эти паттерны, потому что интегрировал множество способов оплаты — банковские карточные шлюзы, кошельки, Apple Pay и Google Pay, Stripe и PayPal, — и каждый из них ломается по-своему.
- Платежи — это место, где маленький баг превращается в реальные потери: деньги списаны с покупателя дважды, заказ отправлен без оплаты, возврат так и не дошёл до банка.
- Хороший платёжный код специально скучный: явные состояния, подписанные колбэки, безопасные повторы и задача, которая всё перепроверяет.
- Если данные карт не попадают на ваши серверы, работа по соответствию PCI сокращается с целого проекта до анкеты.
Как это работает
01Кто участвует и как движутся деньги
Представьте четыре стороны. Покупатель платит. Мерчант (ваш магазин) продаёт. Эквайер — это банк мерчанта, с которым обычно работают через платёжный шлюз или провайдера (Stripe, Adyen, виртуальный POS местного банка). Эмитент — это банк покупателя, который одобряет или отклоняет платёж. Карточные сети, такие как Visa и Mastercard, передают сообщения между ними. У карточного платежа два шага: авторизация (эмитент блокирует деньги) и списание, или capture (деньги действительно забираются). Многие магазины списывают сразу; рестораны или магазины, где наличие товара не гарантировано, могут сначала авторизовать, а списать после подтверждения заказа. Расчёт (settlement) — поступление денег на ваш счёт — происходит позже, часто через день или больше, за вычетом комиссий.
- Авторизация: «может ли эта карта оплатить 50.00?» — средства блокируются.
- Capture: «спиши 50.00 сейчас» — может быть полным или частичным.
- Void: отмена авторизации до списания; refund: возврат денег после списания.
02Способы интеграции: редирект, hosted fields и SDK
Есть три распространённых способа принять данные карты. Редирект (hosted payment page) отправляет покупателя на страницу шлюза, а потом обратно на ваш return URL — это проще всего и даёт минимальную зону ответственности по compliance; так обычно работают банковские шлюзы. Hosted fields или встроенные компоненты (Stripe Payment Element, Adyen Drop-in) рисуют поля карты внутри iframe на вашей странице, поэтому всё выглядит нативно, а данные карты всё равно уходят прямо к провайдеру. Мобильные SDK делают то же самое в приложениях, а приложения-кошельки можно открывать через deep links или нативные модули. Каким бы ни был способ, поток один: ваш сервер создаёт платёжную сессию для заказа с суммой и валютой, покупатель завершает её у провайдера, а ваш сервер узнаёт результат от провайдера — никогда не только от браузера.
// Server: create a payment session for an existing order (Stripe example)
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function startPayment(order: { id: string; total: number; currency: string }) {
const intent = await stripe.paymentIntents.create(
{
amount: order.total, // integer minor units
currency: order.currency.toLowerCase(),
metadata: { orderId: order.id },
automatic_payment_methods: { enabled: true },
},
{ idempotencyKey: `order-${order.id}-attempt-1` },
);
return intent.client_secret; // the client confirms with this; no card data touches us
}
033-D Secure и строгая аутентификация клиента
3-D Secure (3DS) — это дополнительный шаг, на котором эмитент просит покупателя подтвердить платёж, обычно в банковском приложении или SMS-кодом. Текущая версия, EMV 3DS (часто её называют 3DS2), передаёт подробные данные об устройстве и транзакции, поэтому многие платежи проходят «без трения», без дополнительной проверки, и только рискованные получают challenge. В Европе строгая аутентификация клиента (SCA) по PSD2 делает это обязательным для большинства онлайн-платежей картой; в других странах это необязательно, но снижает мошенничество и обычно перекладывает ответственность за мошеннические чарджбэки на эмитента. Для вас 3DS означает, что платёж не мгновенный: он может остановиться на проверке, а потом продолжиться. Ваш поток должен обрабатывать статус вроде «requires action» и покупателя, который закрыл вкладку на полпути.
04Вебхуки, колбэки и идемпотентность
Редирект браузера обратно на ваш сайт — не доказательство оплаты: пользователи закрывают вкладки, сеть пропадает, а кто угодно может вручную ввести return URL. Источник истины — сообщение сервер-сервер: вебхук (провайдер вызывает ваш эндпоинт) или запрос статуса, который вы делаете к провайдеру. Проверяйте подпись каждого вебхука, ищите платёж по вашему собственному ID и обновляйте заказ внутри транзакции. Провайдеры повторяют вебхуки, поэтому одно и то же событие может прийти несколько раз или не по порядку: храните ID обработанных событий и делайте обработчик идемпотентным, то есть таким, чтобы двойной запуск давал тот же эффект, что и одиночный. Используйте ключи идемпотентности и в собственных исходящих вызовах, чтобы повторный «создать платёж» или «вернуть деньги» никогда не списал и не вернул деньги дважды.
// Webhook handler: verify, deduplicate, apply once
app.post("/webhooks/payments", express.raw({ type: "application/json" }), async (req, res) => {
const event = stripe.webhooks.constructEvent(
req.body, req.header("stripe-signature")!, process.env.STRIPE_WEBHOOK_SECRET!,
);
await db.transaction(async (tx) => {
const seen = await tx.processedEvents.insertIgnore({ id: event.id }); // unique key
if (!seen.inserted) return; // duplicate delivery: already handled
if (event.type === "payment_intent.succeeded") {
const orderId = event.data.object.metadata.orderId;
await tx.orders.markPaidIfPending(orderId); // state machine guards the move
}
});
res.sendStatus(200); // answer fast; heavy work goes to a queue
});
05Состояния платежа, «результат неизвестен» и сверка
Держите таблицу платежей отдельно от заказов: у одного заказа может быть несколько попыток. Дайте каждой попытке явные состояния, например: created, pending, requires action, authorised, captured, failed, cancelled, refunded и partially refunded. Добавьте ещё одно, о котором забывают новички: outcome unknown («результат неизвестен»). Оно покрывает таймауты и падения, когда вы отправили запрос, но так и не получили ответ, — деньги могли списаться, а могли и нет. Никогда не гадайте. Пометьте попытку как неизвестную, не повторяйте вслепую новым списанием и позвольте задаче сверки спросить у провайдера реальный статус. Сверка работает по расписанию: находит платежи, застрявшие в pending или unknown, опрашивает шлюз, исправляет локальное состояние и сравнивает ежедневные отчёты о расчётах с вашим леджером, чтобы каждый цент был учтён.
- Повторяйте вызов после таймаута только с тем же ключом идемпотентности или сначала запросите статус.
- Поднимайте алерт по платежам, которые застряли в нефинальном состоянии дольше ожидаемого.
- Сохраняйте сырые ответы провайдера для споров и отладки — без данных карт.
06Возвраты, сохранённые карты, кошельки и зона PCI
Возвраты подчиняются той же дисциплине: создайте запись о возврате, вызовите провайдера с ключом идемпотентности и подтвердите финальный статус по вебхуку или запросу, потому что банковские возвраты могут занимать несколько дней. Поддерживайте частичные возвраты и возвраты по отдельным позициям и никогда не возвращайте больше, чем было списано. Сохранённые карты работают через токенизацию: провайдер хранит карту и выдаёт вам токен или ID клиента, по которому можно списывать позже, а у себя вы храните только бренд, последние четыре цифры и срок действия для отображения. Apple Pay и Google Pay — это кошельки, которые передают провайдеру зашифрованный токен, привязанный к устройству; на вебе им нужна верификация домена и настройка мерчанта, зато они дают быстрый checkout с биометрией. PCI DSS — стандарт безопасности карточной индустрии. Если данные карты касаются только страниц или iframe провайдера, вы подпадаете под самую короткую самооценку (SAQ A), хотя по PCI DSS 4.0.1 всё равно должны защищать страницы, которые загружают платёжные скрипты.
Когда применять
- Любой магазин, сервис бронирования или подписочный продукт, который принимает деньги онлайн.
- Вам нужно несколько способов оплаты — карты, местные кошельки, Apple Pay, Google Pay, наличные или POS — за одним согласованным потоком заказа.
- Заказы должны оставаться корректными, даже когда шлюзы отваливаются по таймауту или присылают дублирующиеся колбэки.
Когда не стоит
- Никогда не делайте собственное хранение или обработку карт; используйте токенизацию провайдера.
- Для совсем маленького магазина хостинговый checkout (Shopify, Stripe Checkout, платёжная ссылка) избавит от большей части этой работы.
- Не пишите собственную логику 3DS; пусть её выполняет SDK провайдера или его hosted-страница.
Альтернативы
- Stripe / Adyen
Глобальные провайдеры с отличными API, hosted fields, кошельками и вебхуками; доступность и комиссии зависят от страны.
- Местные банковские шлюзы
Часто единственный вариант для местных карт и валют; обычно работают через редирект, с более простыми и старыми API, которые требуют аккуратной проверки статусов.
- PayPal
Популярный кошелёк с собственной защитой покупателя; интегрируется через редирект или кнопки, с вебхуками для финального статуса.
- Merchant of record (Paddle, Lemon Squeezy)
Продают от вашего имени и берут на себя налоги и compliance; хорошо для цифровых товаров, хуже — для физических.
Лучшие практики
- Считайте редирект только частью UX; подтверждайте оплату проверенным вебхуком или серверным запросом статуса.
- Делайте каждый обработчик вебхуков идемпотентным и храните ID обработанных событий.
- Используйте ключи идемпотентности в вызовах создания платежа и возврата.
- Моделируйте состояние «результат неизвестен» и разрешайте его задачей сверки, а не слепым повтором.
- Не пускайте данные карт в свои системы: редиректы, hosted fields и токены.
- Отделяйте попытки оплаты от заказов и ведите леджер всех движений денег.
- Логируйте сырые ответы провайдера (без данных карт) и поднимайте алерты по застрявшим платежам.
Частые ошибки
- Помечать заказ оплаченным, потому что пользователь попал на success URL.
- Не проверять подписи вебхуков, позволяя кому угодно помечать заказы оплаченными.
- Повторять списание после таймаута новым запросом и списывать деньги с покупателя дважды.
- Хранить номера карт или CVV «временно» в логах, кэшах или сохраняемой корзине.
- Забывать, что возвраты могут не пройти или висеть в ожидании несколько дней.
Глубже
07Ledger: двойная запись для движения денег
Таблица платежей показывает состояние каждой попытки; ledger (учётная книга) показывает, куда ушёл каждый цент. В двойной записи каждое движение фиксируется дважды — дебетом одного счёта и кредитом другого, поэтому итоги всегда сходятся. Для магазина счета простые: дебиторская задолженность покупателя, баланс у провайдера, комиссии, возвраты и выручка. Списанный платёж на 50.00 с комиссией 1.50 превращается в проводки, которые переносят 50.00 от покупателя на баланс провайдера и 1.50 с баланса провайдера в комиссии. Возвраты и чарджбэки — это новые проводки, а не правки старых. Когда приходит ежедневный settlement, баланс провайдера должен уменьшиться ровно на сумму выплаты. Любое расхождение — это находка сверки с точной датой и референсом, а не загадка в таблице бухгалтера.
CREATE TABLE ledger_entries (
id BIGSERIAL PRIMARY KEY,
journal_id UUID NOT NULL, -- groups the entries of one movement
account TEXT NOT NULL, -- customer, provider_balance, fees, refunds
amount BIGINT NOT NULL, -- positive = debit, negative = credit
currency CHAR(3) NOT NULL,
payment_id BIGINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Every journal must balance to zero
SELECT journal_id FROM ledger_entries
GROUP BY journal_id, currency HAVING SUM(amount) <> 0;
08Семантика доставки webhook-ов на практике
Провайдеры доставляют webhook-и как минимум один раз, а не ровно один раз, и не по порядку. Stripe, например, в live-режиме повторяет неудачные доставки до трёх дней с экспоненциальной задержкой и прямо пишет, что порядок событий не гарантирован. Проектируйте под это. Читайте сырое тело запроса, потому что проверка подписи падает, если фреймворк распарсил и заново сериализовал его. Отклоняйте события со слишком старым подписанным timestamp (библиотеки Stripe по умолчанию используют пять минут), чтобы блокировать replay-атаки. Быстро возвращайте 2xx и отправляйте работу в очередь, поскольку медленные обработчики вызывают таймауты и новые повторы. В воркере не доверяйте порядку событий: либо запрашивайте актуальный объект через API провайдера, либо применяйте переходы через state machine, которая игнорирует движение назад, например запоздавшее событие «processing» после «succeeded».
- Дедуплицируйте по ID события с уникальным ограничением.
- Исключите маршрут webhook-а из CSRF-защиты, но никогда — из проверки подписи.
- Поднимайте алерт, когда эндпоинт отвечает не-2xx, ещё до того, как закончатся повторы.
09Диспуты и чарджбэки
Чарджбэк происходит, когда держатель карты просит свой банк отменить платёж: мошенничество, товар не получен или «не соответствует описанию». Провайдер списывает с вашего баланса спорную сумму плюс комиссию за диспут и открывает диспут со сроком подачи доказательств. Выигрывают конкретные доказательства: подтверждение доставки с адресом, подписанные квитанции, логи с аккаунтом и устройством покупателя, политика возврата, принятая при оформлении, и предыдущая переписка. Аутентификация 3-D Secure обычно перекладывает ответственность за диспуты о мошенничестве на эмитента, но не за диспуты «товар не получен». Платёжные системы следят за долей диспутов у мерчантов, и превышение их порогов ведёт к штрафам или потере возможности принимать карты. Профилактика важнее победы: понятный statement descriptor, быстрые возвраты по реальным жалобам и фрод-скрининг рискованных заказов.
10Защита платёжной страницы по PCI DSS 4.0.1
Даже когда поля карты находятся в iframe провайдера, злоумышленники атакуют страницу вокруг него: скомпрометированный скрипт может подменить iframe поддельной формой или перекрыть его (это часто называют e-skimming или атаками в стиле Magecart). Требование 6.4.3 PCI DSS 4.0.1 требует реестр всех скриптов на платёжных страницах с обоснованием и методом контроля целостности, а 11.6.1 — механизм, обнаруживающий несанкционированные изменения этих страниц и их security-заголовков. В редакции SAQ A от января 2025 года эти два пункта заменили критерием применимости: мерчант должен подтвердить, что его сайт не подвержен атакам через скрипты, а в руководстве Council сказано, что показать это можно именно техниками из 6.4.3 и 11.6.1. На практике: минимум скриптов на checkout, строгая Content Security Policy, Subresource Integrity для статических файлов и мониторинг изменений.
# nginx: strict CSP on checkout pages (adjust to your provider's documented domains)
location /checkout {
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://js.stripe.com; frame-src https://js.stripe.com https://hooks.stripe.com; connect-src 'self' https://api.stripe.com; img-src 'self' data:; report-to csp" always;
add_header Reporting-Endpoints 'csp="https://example.com/csp-reports"' always;
try_files $uri /index.php?$query_string;
}
Реальные ситуации
Заказы оплачены в банке, но в магазине всё ещё ждут оплаты
Проблема
Несколько раз в неделю покупатели присылают скриншоты успешных платежей из банка, а их заказы остаются в статусе «ожидает оплаты» и позже отменяются задачей истечения срока.
Как решить
Webhook либо не доходит, либо молча падает. Проверьте лог доставок у провайдера на не-2xx ответы, затем обычные причины: ошибки подписи из-за распарсенного тела, CSRF middleware, firewall, блокирующий провайдера, или обработчик, который упирается в таймаут. Исправьте эндпоинт, чтобы он проверял сырое тело и возвращал 2xx после постановки в очередь. Затем добавьте страховку: задачу сверки, которая опрашивает провайдера по каждому платежу, висящему в pending дольше нескольких минут; запускайте её до того, как задача истечения срока что-то отменит, и пусть задача истечения пропускает заказы с платежом в неизвестном или processing-состоянии. Настройте алерты на уровень ошибок webhook-ов.
Волна мелких отклонённых платежей в 3 часа ночи
Проблема
Ночью на checkout приходят сотни заказов по 1.00 с разными картами; большинство отклонено. Провайдер предупреждает о высоком уровне отказов на аккаунте.
Как решить
Это card testing: мошенники проверяют украденные карты через ваш checkout. Отвечайте слоями. Сразу поставьте rate limit на попытки оплаты по IP, устройству, аккаунту и корзине и добавьте проверку (CAPTCHA или сервис защиты от ботов) после повторных неудач. Если атака продолжается, требуйте вход или подтверждённый телефон для оформления. Включите фрод-правила провайдера, например блокировку при неудачной проверке CVC или почтового индекса, и задайте минимальную сумму заказа. Отмените заказы, созданные атакой, и верните деньги по успешным, так как они превратятся в чарджбэки. После этого добавьте мониторинг уровня отказов по часам, чтобы следующая атака вызвала алерт в течение нескольких минут.
Подключение второго платёжного провайдера
Проблема
Бизнес хочет рядом со Stripe подключить локальный банковский шлюз для покупателей, чьи карты не проходят международные платежи. Сейчас платёжный код вызывает Stripe напрямую из контроллера оформления.
Как решить
Сначала введите платёжный интерфейс, не привязанный к провайдеру: createSession, getStatus, refund и parseWebhook, которые возвращают ваши собственные состояния платежа, а не специфичные для провайдера. Оберните существующий код Stripe в адаптер за этим интерфейсом и выпустите этот рефакторинг отдельно, без изменения поведения. Затем добавьте адаптер банка — скорее всего, он будет на редиректах и потребует опроса статуса вместо богатых webhook-ов. Храните имя провайдера в каждой попытке оплаты, направляйте возвраты тому провайдеру, который списал деньги, и пусть сверка перебирает провайдеров. Feature flag может направлять процент покупателей или карты определённых стран на новый шлюз, пока вы следите за долей успешных платежей.
Шпаргалка
- Пробросить webhook-и на локальный сервер
stripe listen --forward-to localhost:4242/webhooks/payments- Вызвать тестовое событие
stripe trigger payment_intent.succeeded- Повторно отправить прошлое событие на эндпоинт
stripe events resend evt_123 --webhook-endpoint=we_123- Идемпотентный вызов API (curl)
curl https://api.stripe.com/v1/refunds -u sk_test_...: -H "Idempotency-Key: refund-order-1042-1" -d payment_intent=pi_123- Дедупликация событий webhook-ов
CREATE UNIQUE INDEX processed_events_uq ON processed_events (provider, event_id);- Найти зависшие платежи
SELECT id FROM payments WHERE status IN ('pending','requires_action','unknown') AND updated_at < now() - interval '15 minutes';- Файл верификации домена для Apple Pay
https://example.com/.well-known/apple-developer-merchantid-domain-association- Тестовая карта Stripe с обязательным 3DS
4000 0027 6000 3184 (любой будущий срок действия, любой CVC)
В моих реальных проектах
- На платформе заказов для сети ресторанов я интегрировал оплату картой с сохранёнными картами, Apple Pay, Google Pay, несколько местных кошельков (один — через собственный нативный Android-модуль), POS-терминал и наличные — девять способов оплаты за одним потоком заказа.
- В мультитенантной POS SaaS платёжный модуль включает сессии шлюза, сохранённые карты, леджер, возвраты с состоянием «результат неизвестен», колбэки и задачу сверки, а также ключи идемпотентности для заказов партнёров.
- Я также интегрировал Stripe и PayPal с вебхуками, банковский шлюз с подписанными запросами и сверкой ожидающих платежей, а также возвраты по позициям через другой карточный шлюз.
Вопросы и ответы
Q1Почему нельзя доверять редиректу об успешной оплате?
Редирект происходит в браузере покупателя, который вы не контролируете. Вкладку могут закрыть до того, как он сработает, сеть может упасть, а пользователь может просто открыть success URL вручную. Поэтому редирект только показывает дружелюбную страницу. Настоящее решение приходит от провайдера: подписанный вебхук или серверный запрос статуса по вашему ID платежа. Только это подтверждение переводит заказ из ожидания в оплаченный. Если вебхук запаздывает, страница успеха может опрашивать ваш бэкенд, пока статус не станет финальным.
Q2Что такое идемпотентность и почему она важна в платежах?
Операция идемпотентна, если её двойное выполнение даёт тот же эффект, что и одиночное. В платежах полно повторов: провайдеры пересылают вебхуки, клиенты повторяют запросы при таймаутах, очереди заново доставляют задачи. Без идемпотентности повтор может дважды списать или дважды вернуть деньги. В исходящих вызовах я отправляю ключ идемпотентности, привязанный к заказу и попытке, и провайдер возвращает исходный результат. Для входящих вебхуков я храню ID событий с уникальным ограничением и пропускаю дубликаты, а проверки машины состояний не дают применить один и тот же переход дважды.
Q3Как вы обрабатываете платёжный запрос, который завершился по таймауту?
Таймаут означает, что результат неизвестен: списание могло пройти. Я помечаю попытку как outcome unknown и не создаю новое списание. Если провайдер это поддерживает, я повторяю запрос с тем же ключом идемпотентности, и он безопасно возвращает исходный результат. Иначе я запрашиваю статус платежа по своему идентификатору. Задача сверки тоже подхватывает всё, что осталось неизвестным через несколько минут. Покупатель видит «мы подтверждаем ваш платёж», а не ошибку, которая подталкивает заплатить ещё раз.
Q4Что такое 3-D Secure и как он влияет на ваш поток?
3-D Secure — это шаг аутентификации, который выполняет банк покупателя, обычно через подтверждение в банковском приложении или одноразовый код. EMV 3DS передаёт данные об устройстве и транзакции, поэтому платежи с низким риском проходят без проверки. В Европе строгая аутентификация клиента по PSD2 делает его обязательным для большинства платежей картой; в других странах он снижает мошенничество и обычно перекладывает ответственность за мошеннические чарджбэки на эмитента. В коде это значит, что платёж может перейти в состояние requires action и завершиться асинхронно, поэтому поток должен опираться на вебхуки и обрабатывать брошенные проверки.
Q5Как уменьшить зону PCI DSS?
Следить за тем, чтобы данные карт никогда не касались ваших серверов. Используйте hosted payment page или hosted fields и компоненты SDK, которые рендерятся внутри iframe провайдера, а для сохранённых карт храните только токены. Тогда типичный e-commerce мерчант может использовать SAQ A — самую короткую анкету самооценки. По PCI DSS 4.0.1 вы всё равно должны подтвердить, что сайт защищён от атак через скрипты, которые могут затронуть платёжную страницу, поэтому контролируйте сторонние скрипты, используйте строгую Content Security Policy и обновляйте зависимости.
Q6Что делает задача сверки?
Это страховочная сетка по расписанию, которая приводит ваши записи в соответствие с данными провайдера. Она выбирает платежи, которые находятся в pending, requires action или outcome unknown дольше ожидаемого, запрашивает по каждому статус у шлюза и применяет реальный статус через тот же идемпотентный путь кода, что и вебхуки. Вторая часть сравнивает отчёт провайдера о расчётах или транзакциях с вашим леджером по датам и помечает расхождения: пропавшие платежи, разницу в суммах, возвраты без записей. Всё, что нельзя исправить автоматически, превращается в алерт для человека.
Вопросы уровня senior
S1Как поддерживать согласованность состояния заказа и платёжного провайдера без распределённых транзакций?
Я принимаю, что это две разные системы, и проектирую под eventual consistency с чётким разделением ответственности. Провайдер владеет истиной о деньгах, моя база — истиной о заказах. Каждый исходящий вызов несёт idempotency key, поэтому повторы безопасны. Каждый входящий сигнал — webhook, запрос статуса или результат сверки — проходит через одну идемпотентную функцию, которая сопоставляет состояния провайдера с моими и применяет переходы state machine. Когда я не могу знать результат, я записываю «unknown», а не гадаю. Задача сверки по расписанию замыкает цикл. Цена в том, что система кратковременно несогласованна — секунды или минуты, — поэтому UI должен честно показывать промежуточные состояния вроде «подтверждаем оплату», а не делать вид, что всё происходит мгновенно.
S2Авторизовать и списать позже или списывать сразу?
Немедленное списание проще и подходит магазинам, которые отправляют то, что есть на складе, или продают цифровые товары. Раздельные авторизация и capture подходят, когда итоговая сумма или наличие не определены: весовые товары, рестораны, которые могут отклонить заказ, маркетплейсы, ожидающие продавца, или отели. Списывая только то, что вы реально доставили, вы избегаете возвратов, которые стоят комиссий и идут до покупателя несколько дней, а отмена авторизации быстро снимает холд. Платой становятся сложность и срок действия: авторизации истекают — для онлайн-платежей картой обычно примерно через семь дней, в зависимости от платёжной системы и провайдера, — поэтому нужна задача, которая вовремя списывает или отменяет их, а частичное списание должны поддерживать ваш провайдер и шлюз. Я выбираю под каждый бизнес-сценарий, а не глобально.
S3Как бы вы спроектировали возвраты для системы с акциями, кошельками и несколькими способами оплаты?
Возврат — это сначала бизнес-расчёт, а потом набор денежных движений. Расчёт использует снимки заказа: оплаченную сумму строки после распределённой на неё скидки, её налог и политику по стоимости доставки. Затем сумма делится между исходными способами оплаты в заданном порядке — обычно сначала на карту, потом в кошелёк или на бонусный счёт магазина, — и никогда не больше, чем было списано каждым способом. Каждая часть становится отдельной записью возврата с idempotency key и исполняется у своего провайдера; части кошелька — это проводки в ledger. Кэшбэк, начисленный за заказ, отменяется пропорционально. Частичные сбои ожидаемы, поэтому у возврата есть собственное состояние и сверка. Самое сложное — заранее согласовать бизнес-правила, особенно для скидок, зависящих от суммы заказа.
Глоссарий
- Эквайер
- Банк или процессор мерчанта, который принимает карточные платежи и перечисляет средства мерчанту.
- Эмитент
- Банк покупателя, выпустивший карту; он одобряет или отклоняет каждый платёж.
- Авторизация
- Эмитент резервирует средства под платёж, но пока не переводит их.
- Capture (списание)
- Фактическое списание ранее авторизованных средств; может быть полным или частичным.
- Settlement
- Выплата списанных средств за вычетом комиссий от провайдера на банковский счёт мерчанта.
- Чарджбэк
- Отмена платежа по инициативе банка держателя карты, обычно с комиссией и сроком подачи доказательств.
- Liability shift
- Перенос ответственности за убытки от мошенничества с мерчанта на эмитента, обычно после успешной аутентификации 3-D Secure.
- Токенизация
- Замена данных карты токеном, который только провайдер может превратить обратно в списание.
- Idempotency key
- Уникальное значение, отправляемое с запросом, чтобы повторы возвращали первый результат, а не выполняли действие снова.
- SAQ A
- Самая короткая самооценка PCI для мерчантов, полностью передавших обработку карточных данных сертифицированному провайдеру.
Что изучить дальше
Есть идея продукта?Давайте создадим его.
Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.