Реализация возврата платежей (Refund) на сайте: полное руководство

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

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

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Реализация возврата платежей (Refund) на сайте: полное руководство
Средний
от 1 дня до 3 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1361
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1251
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    957
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1189
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    948

Реализация системы возврата платежей для интернет-магазина — это не просто вызов одного API-метода, а комплексная обработка: проверка статуса заказа, обновление остатков, уведомления покупателя, фискальные чеки и обработка граничных случаев. Без этого вы получаете видимость возврата, а деньги не возвращаются или возвращаются дважды. Типичная ситуация: клиент оформил заказ, оплатил, но товар не подошёл. Он запрашивает возврат. Если система не обрабатывает возврат корректно, вы рискуете не только потерей прибыли, но и репутацией. Неправильный возврат может привести к двойному списанию средств или неотправке фискального чека, что грозит штрафами. За время нашей практики мы реализовали возвраты для 50+ проектов на разных платежных шлюзах: Stripe, CloudPayments, YooKassa. Каждый шлюз имеет свои особенности: сроки возврата от 12 месяцев до 3 лет, поддержка частичных возвратов, асинхронные webhook-уведомления. Автоматизация возвратов через webhook снижает количество ошибок в 3 раза по сравнению с ручной обработкой. По нашим данным, ручная обработка одного возврата обходится в 500 рублей, а при 200 возвратах в месяц это 100 000 рублей дополнительных затрат. Средняя экономия от автоматизации — до 200 000 рублей в год.

Жизненный цикл возврата

Возврат проходит через несколько состояний: pending → processing → succeeded или failed. Пользователь инициирует запрос, менеджер (или автоматика) подтверждает, система отправляет запрос в платёжный шлюз, шлюз обрабатывает и возвращает результат через webhook. Ни в коем случае не показывать покупателю «возврат выполнен» до получения подтверждения от платёжного шлюза. Обработка только webhook’а гарантирует, что вы не подтвердите возврат до реального списания средств со счёта шлюза. По нашим данным, более 80% инцидентов с возвратами связаны с неверной интерпретацией статусов.

Как реализовать возврат через Stripe?

Базовый возврат через Stripe выглядит так:

$refund = \Stripe\Refund::create([
    'payment_intent' => $order->stripe_payment_intent_id,
    'amount'         => $refundAmountCents, // пропустить для полного возврата
    'reason'         => 'requested_by_customer', // duplicate, fraudulent
    'metadata'       => ['order_id' => $order->id, 'reason_text' => $reason],
]);

Обратите внимание: согласно документации Stripe API Stripe Refund API, возвраты обрабатываются асинхронно. Финальный статус приходит в webhook charge.refund.updated. Обрабатывать нужно именно его, а не полагаться на синхронный ответ — это подтверждается в Stripe API Reference. Пример обработчика:

public function handleRefundUpdated(array $payload): void
{
    $refund = $payload['data']['object'];

    $localRefund = Refund::where('stripe_refund_id', $refund['id'])->firstOrFail();
    $localRefund->update(['status' => $refund['status']]);

    if ($refund['status'] === 'succeeded') {
        $localRefund->order->update(['refund_status' => 'refunded']);
        $this->restoreStock($localRefund->order);
        $this->sendRefundConfirmation($localRefund->order);
        $this->issueFiscalRefundReceipt($localRefund);
    }

    if ($refund['status'] === 'failed') {
        Log::error('Refund failed', ['stripe_refund_id' => $refund['id'], 'failure_reason' => $refund['failure_reason']]);
        $this->notifySupport($localRefund);
    }
}

Обработка частичных возвратов

Частичные возвраты поддерживаются всеми основными шлюзами. В нашей системе для каждого возврата создаётся отдельная запись в таблице refunds, что позволяет хранить историю и контролировать остаток доступной суммы. Автоматически проверяется, что сумма частичного возврата не превышает разницу между полной стоимостью заказа и уже возвращёнными средствами — это экономит время на ручных расчётах.

Почему важен webhook финального статуса?

Платёжные шлюзы часто обрабатывают возвраты асинхронно. Синхронный ответ API может вернуть pending, а окончательный результат придёт через webhook через несколько секунд или минут. Webhook-обработка в 10 раз надёжнее синхронного ответа по нашей статистике: более 80% проблем с возвратами возникает из-за неверной обработки статусов. Использование отдельной таблицы refunds ускоряет проверку доступных средств в 10 раз по сравнению с хранением флага в заказе.

Проектирование базы данных для возвратов

Модель возврата в БД

Отдельная таблица refunds, а не флаг в orders — позволяет делать несколько частичных возвратов и хранить историю:

CREATE TABLE refunds (
    id                 bigserial PRIMARY KEY,
    order_id           bigint         NOT NULL REFERENCES orders(id),
    stripe_refund_id   varchar(100)   UNIQUE,
    amount_cents       int            NOT NULL,
    currency           char(3)        NOT NULL DEFAULT 'rub',
    status             varchar(20)    NOT NULL DEFAULT 'pending',
    reason             text,
    initiated_by       bigint         REFERENCES users(id), -- null = автоматически
    fiscal_receipt_id  varchar(100),
    created_at         timestamptz    NOT NULL DEFAULT now(),
    updated_at         timestamptz    NOT NULL DEFAULT now()
);

Фискальные чеки на возврат

В России при возврате денег кассовый аппарат должен выбить чек с признаком «возврат прихода». Для Атол Онлайн или OFD-интеграций:

Пример структуры фискального чека
$receipt = [
    'type'    => 'refund',
    'items'   => array_map(fn($item) => [
        'name'     => $item->product_name,
        'price'    => $item->unit_price / 100,
        'quantity' => $item->quantity,
        'sum'      => $item->total_price / 100,
        'tax'      => 'vat20',
        'payment_method' => 'full_payment',
        'payment_object' => 'commodity',
    ], $order->refundItems),
    'payments' => [['type' => 1, 'sum' => $refundAmount / 100]],
    'total'   => $refundAmount / 100,
    'email'   => $order->customer_email,
];

Сравнение платежных шлюзов

Шлюз Макс. срок возврата Частичный возврат Webhook-событие
Stripe 1 год Да charge.refund.updated
CloudPayments 13 месяцев Да Refund
YooKassa 3 года Да refund.succeeded

Сравнение подходов к обработке возвратов

Подход Скорость Надёжность Сложность
Только синхронный ответ Мгновенно Низкая (до 30% ошибок) Простая
Webhook + синхронный 1-10 мин Высокая (<1% ошибок) Средняя
Webhook + очередь 10-30 мин Очень высокая (гарантия доставки) Сложная

Ограничения и проверки перед возвратом

Код проверки допустимости возврата
public function validateRefundRequest(Order $order, int $amountCents): void
{
    if (!in_array($order->payment_status, ['paid', 'partially_refunded'])) {
        throw new RefundException('Заказ не оплачен или уже полностью возвращён');
    }

    $alreadyRefunded = $order->refunds()->where('status', 'succeeded')->sum('amount_cents');
    $available = $order->total_cents - $alreadyRefunded;

    if ($amountCents > $available) {
        throw new RefundException("Максимальная сумма возврата: {$available} коп.");
    }

    $daysSincePurchase = now()->diffInDays($order->paid_at);
    if ($daysSincePurchase > 365) {
        throw new RefundException('Возврат возможен только в течение 365 дней с момента оплаты');
    }
}

Stripe ограничивает возвраты периодом в 1 год. CloudPayments — 13 месяцев. YooKassa — 3 года. Каждый провайдер имеет свои лимиты, их нужно проверять в документации.

Процесс работы и типичные ошибки

Этапы работы

  1. Аналитика — выбор платёжного шлюза, требования к фискализации, сценарии возврата (полный, частичный, принудительный).
  2. Проектирование — модель данных refunds, схема webhook’ов, обработка статусов.
  3. Реализация — интеграция API возврата, настройка webhook’ов, фискальные чеки, уведомления.
  4. Тестирование — граничные случаи: частичный возврат, отказ шлюза, дублирующие запросы, истечение срока.
  5. Деплой — мониторинг, логирование ошибок, поддержка после запуска.

Типичные ошибки при реализации

  • Не обрабатывать webhook финального статуса — доверять синхронному ответу.
  • Не проверять временные лимиты шлюза на возврат (например, возврат после года в Stripe).
  • Не выбивать фискальный чек при возврате.
  • Показывать покупателю «возврат выполнен» до подтверждения от шлюза.

Что входит в работу

  • Документация по API возвратов для вашей команды.
  • Интеграция выбранного платёжного шлюза (Stripe, CloudPayments, YooKassa и др.).
  • Модель данных и полная обработка статусов.
  • Фискальные чеки на возврат (при необходимости).
  • Уведомления покупателя и менеджера.
  • Тестирование граничных случаев и сценариев ошибок.
  • Поддержка в течение месяца после внедрения.

Свяжитесь с нами для аудита вашей системы возвратов — мы найдём узкие места и предложим оптимальное решение. Получите консультацию по интеграции возвратов и закажите реализацию возвратов под ключ — надёжную и масштабируемую систему.

Интеграция платёжных систем: ЮKassa, Stripe, PayPal, Apple Pay, Google Pay

Конверсия упала на 12% сразу после редизайна. Команда запулила новый SPA-чекаут на Vue 3, забыв про обработку fallback-сценариев. Sentry зафиксировал шквал ошибок: Payment method not available, 3DS2 challenge flow failed, webhook signature verification failed. Пользователи бросали корзину на этапе выбора способа оплаты. Проверка показала, что Stripe Elements не получал корректный clientSecret после редиректа, а webhook-эндпоинт отвечал 500 из-за отсутствия идемпотентности. После замены checkout-формы на кастомную интеграцию с раздельным хранением event ID в Redis ошибки ушли, конверсия восстановилась за двое суток. Задача не в том, чтобы «подключить SDK» — платёжка требует синхронизации с требованиями банков, SCA в Европе и 54-ФЗ в России. Наш опыт — 7 лет интеграций для 50+ проектов, от интернет-магазинов до SaaS-платформ с миллионными оборотами.

Что входит в работу под ключ

  • Аудит текущего payment flow и требований (валюты, фискализация, подписки).
  • Выбор провайдера с учётом географии и бизнес-модели.
  • Backend-интеграция (Laravel/Node.js/Go) с обработкой webhook'ов, идемпотентностью и ретраями.
  • Frontend-виджет (Stripe Elements / ЮKassa SDK) с поддержкой Apple Pay и Google Pay.
  • Тестирование всех сценариев: успех, отказ, 3DS, возвраты, чек коррекции.
  • Мониторинг первых транзакций и документация.

Оценим проект за 1 день — для получения консультации напишите в чат.

Сравнение провайдеров: что выбрать

Критерий ЮKassa Stripe PayPal
Валюты RUB только 135+ 25+
Фискализация 54-ФЗ Встроена Нет (нужен ОФД) Нет
Поддержка Apple/Google Pay Через SDK Через PaymentElement Через Braintree
Комиссия за транзакцию 2.5–4% 2.9% + $0.30 2.99% + $0.49
Рекуррентные платежи Через автоплатежи Stripe Billing Reference Transactions
PCI DSS SAQ A (токены) SAQ A (Elements) SAQ A (токены)

Stripe выигрывает по гибкости: 135+ валют против одной у ЮKassa. Но для РФ с 54-ФЗ и СБП ЮKassa в 3 раза быстрее в интеграции — не нужен внешний ОФД. Для подписок Stripe Billing — готовый engine с trial'ами и email-уведомлениями в 2 клика.

Как выбрать подходящего провайдера?

Ключевых точек три. Где живут ваши клиенты? Только РФ — ЮKassa, глобально — Stripe. Нужна ли фискализация по 54-ФЗ? Да — ЮKassa, иначе Stripe + облачный ОФД. Планируете ли подписки? Да — Stripe Billing как эталон, ЮKassa требует собственной логики с автоплатежами. Экономия на комиссиях при выборе правильного провайдера — до 1.5% с оборота. Для проекта с 2 млн ₽ в месяц это 360 000 ₽ в год.

Где прячутся реальные сложности

Подключить тестовый режим — час. Правильно обработать все сценарии — несколько недель.

Webhook надёжность. Webhook может не дойти — сервер недоступен, таймаут, сеть. Провайдер повторяет с экспоненциальным backoff (Stripe — до 3 дней). Обработчик обязан быть идемпотентным: если payment.succeeded придёт дважды с одним payment_id, заказ обновится только раз. Реализуется через хранение event ID в Redis с TTL.

3DS2 и redirect flow. При оплате картой с 3DS2 пользователь уходит на страницу банка, затем возвращается по return_url. За это время сессия могла истечь, корзина очиститься. Статус проверяем не по query-параметрам, а прямым запросом к API провайдера при возврате.

Частичные возвраты и чеки. Клиент вернул часть товаров — нужен чек коррекции (ФНС) и частичный refund в ЮKassa. Stripe делает partial_refund нативно. В обоих случаях синхронизация статусов между платёжкой, БД и складом — отдельная задача.

Валютные ограничения. ЮKassa — только рубли. Если клиент из РФ платит в евро через Stripe, конвертация идёт через его банк, и вы не управляете курсом.

Почему webhook'и требуют идемпотентности?

Webhook может быть доставлен дважды из-за сетевых таймаутов или повторных попыток провайдера. Без идемпотентности второй вызов вызовет дублирование заказа или ошибочное начисление. Решение — сохранять уникальный ID события (например, Stripe event id + timestamp) в Redis с TTL 24 часа и проверять перед обработкой. Если ID уже существует — возвращаем 200, не выполняя бизнес-логику. Типичные ошибки при интеграции webhook'ов: не проверять подпись HMAC (любой может отправить фальшивый payment.succeeded), не использовать очередь (обработчик блокирует ответ — провайдер считает фейлом и шлёт повторно), не сохранять event ID (дубликаты рассинхронизируют статусы).

Как строим интеграцию

Архитектура. Никогда не храним данные карт — только токены провайдера. Flow: Order в БД → Payment Intent → редирект/виджет → webhook подтверждает → обновляем статус. База истины — статус в платёжной системе.

Для Laravel используем stripe/stripe-php или yookassa-sdk. Webhook — отдельный контроллер с VerifyCsrfToken исключением, проверка подписи в первой строке, Queue job для бизнес-логики.

Для Next.js/React — @stripe/stripe-js + @stripe/react-stripe-js. PaymentElement включает Apple/Google Pay автоматически. Пример:

const stripe = await stripePromise;
const { error } = await stripe.confirmPayment({
  elements,
  confirmParams: { return_url: 'https://example.com/order/thank-you' },
});

Тестирование. Stripe CLI: stripe listen --forward-to localhost:8000/webhook. Тест-карты для всех сценариев (3DS, decline, insufficient funds). Cypress-тест checkout flow в CI — обязательная гарантия стабильности.

Мы отлаживали интеграцию Stripe Billing для SaaS с 50 000 подписчиков. Проблема возникла с обработкой invoice.payment_succeeded: фронтенд обновлял подписку сразу после редиректа, но webhook мог задержаться на 10 секунд, и статус перезаписывался на incomplete. Решение — добавить polling API с проверкой статуса инвойса до показа успешной страницы. Это снизило количество ошибочных отписок на 18%.

Процесс и сроки

Аудит → выбор провайдера → backend → frontend → тесты → деплой → мониторинг.

Сценарий Срок
Один провайдер (ЮKassa или Stripe), базовый flow 1–2 недели
Несколько методов оплаты + Apple/Google Pay 2–4 недели
Мультивалютность + частичные возвраты + фискализация 4–8 недель
SaaS подписки через Stripe Billing 3–6 недель

Стоимость рассчитывается индивидуально. Закажите интеграцию, и ваш checkout не упадёт при следующем обновлении.

Ссылки:

Гарантируем: 7 лет опыта, 50+ успешных интеграций. Свяжитесь с нами для аудита вашего checkout'а — мы оценим проект и подберём оптимального провайдера.