Реалізація часткового повернення платежу на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація часткового повернення платежу на сайті
Середня
від 1 робочого дня до 3 робочих днів
Часті питання

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

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація частинного повернення платежу на веб-сайті

Часткове повернення — окремий сценарій, який часто реалізується небрежно: роблять повне повернення і новий платіж, або просто видають купон. Обидва варіанти створюють проблеми з бухгалтерією і звітністю. Правильна реалізація часткового рефанду працює на рівні рядків замовлення.

Коли потрібне часткове повернення

Покупець повернув один з кількох товарів. Частина замовлення не доставлена. Була застосована невірна скидка — потрібно вернути різницю. Товар виявився з дефектом і клієнт погодився на часткову компенсацію.

API часткового повернення

У Stripe, YooKassa та більшості провайдерів часткове повернення — це той же refund-метод з указанням суми:

// Stripe: часткове повернення конкретної суми
$refund = \Stripe\Refund::create([
    'payment_intent' => $order->stripe_payment_intent_id,
    'amount'         => 150000, // 1500.00 RUB у копійках
]);

// YooKassa
$builder = \YooKassa\Request\Refunds\CreateRefundRequest::builder();
$request = $builder
    ->setPaymentId($order->yookassa_payment_id)
    ->setAmount(new \YooKassa\Model\MonetaryAmount('1500.00', 'RUB'))
    ->setDescription('Повернення за товар #' . $item->id)
    ->build();
$refund = $client->createRefund($request, uniqid('', true));

Ідемпотентний ключ у YooKassa критичний — без нього повторний запит при таймауті створить другий повернення.

Прив'язка повернення до позицій замовлення

Часткове повернення повинно бути прив'язано до конкретних позицій — це потрібно для відновлення залишків і для коректного фіскального чека:

CREATE TABLE refund_items (
    id           bigserial PRIMARY KEY,
    refund_id    bigint NOT NULL REFERENCES refunds(id),
    order_item_id bigint NOT NULL REFERENCES order_items(id),
    quantity     int    NOT NULL,
    amount_cents int    NOT NULL
);

При повертанні часткової кількості (3 з 5 одиниць) залишки відновлюються тільки на повернене кількість:

DB::transaction(function () use ($refund) {
    foreach ($refund->items as $refundItem) {
        $orderItem = $refundItem->orderItem;
        $product   = Product::lockForUpdate()->find($orderItem->product_id);
        $product->increment('stock', $refundItem->quantity);

        $orderItem->increment('refunded_quantity', $refundItem->quantity);
    }

    $totalRefunded = $refund->order->refunds()
        ->where('status', 'succeeded')
        ->sum('amount_cents');

    $status = ($totalRefunded >= $refund->order->total_cents)
        ? 'fully_refunded'
        : 'partially_refunded';

    $refund->order->update(['payment_status' => $status]);
});

Фіскальний чек на часткове повернення

Чек на часткове повернення містить тільки повертаємі позиції з їхніми сумами. Повна сума замовлення в чеку не фігурує:

$receiptItems = $refund->items->map(fn($item) => [
    'name'     => $item->orderItem->product_name,
    'price'    => $item->orderItem->unit_price / 100,
    'quantity' => $item->quantity,
    'sum'      => $item->amount_cents / 100,
    'tax'      => 'vat20',
]);

Обмеження суми часткових повернень

Сума всіх часткових повернень не повинна перевищувати оплачену суму. Цей інваріант потрібно перевіряти на рівні БД:

-- Тригер або CHECK CONSTRAINT через функцію
CREATE OR REPLACE FUNCTION check_refund_total()
RETURNS trigger AS $$
DECLARE
    total_refunded int;
    order_total    int;
BEGIN
    SELECT COALESCE(SUM(amount_cents), 0)
    INTO total_refunded
    FROM refunds
    WHERE order_id = NEW.order_id AND status != 'failed';

    SELECT total_cents INTO order_total
    FROM orders WHERE id = NEW.order_id;

    IF total_refunded + NEW.amount_cents > order_total THEN
        RAISE EXCEPTION 'Сума повернень перевищує суму замовлення';
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Інтерфейс часткового повернення

В admin-панелі потрібна форма, де менеджер вибирає рядки замовлення і кількість для повернення. Автоматично рахується сума. Кнопка «Вернути» блокується до тих пір, поки сума не пересчитана. Після підтвердження — запит йде до платіжного шлюзу, статус оновлюється через webhook. Фінальний статус менеджер бачить без перезагрузки сторінки.