Refunds are an operation that often causes errors in Bitrix: stuck statuses, duplicate receipts, violations of 54-FZ. Especially when the acquirer's deadlines have passed, but the client demands their money back.
Over 10 years, we have carried out more than 50 acquirer integrations and encountered all the typical problems: an incorrect token, the wrong amount format, a missing refund receipt. Developers often forget about API limits — this leads to blocking.
In this material, we will look at how to set up automated refunds without manual work and fines, using Tinkoff, YooKassa, and Sberbank as examples.
We design the logic taking into account all edge cases: partial refunds, canceling a payment before confirmation, refunding the acquirer's commission. Our goal is for the refund to be completed in one click for the manager and without errors on the payment system's side. Get a consultation on refund automation.
Time limits on refunds
Different acquirers have different timeframes within which a refund is technically possible:
| Acquirer | Refund period | Method after the period |
|---|---|---|
| Tinkoff | Up to 365 days | Manual refund via the bank |
| YooKassa | Up to 365 days | Via YooKassa support |
| Sberbank | Up to 3 years | Via the personal account |
| CloudPayments | Up to 365 days | Request to support |
If the period has passed, the refund is made manually by bank transfer. The payment system is not involved here.
Typical mistakes when setting up a refund:
- Incorrect token or request signature.
- Missing refund receipt (violation of 54-FZ).
- Exceeding the refund amount limit.
- Stuck order status due to missing response handling.
Step-by-step return setup instructions
- Identify the acquirer and its API. Each has its own protocol and requirements for request signing.
- Develop a single wrapper function. It will call the required API by the gateway key.
- Configure limit checks. At Tinkoff, for example, the maximum refund amount cannot exceed the original amount.
- Connect fiscalization. Generate a refund receipt with the same items as in the original receipt.
- Test all scenarios: full refund, partial refund, cancellation before confirmation, and error handling.
Automation reduces refund processing time from 2 hours per day (for 100 refunds) to 5 minutes — saving up to 96% of the manager's time.
How to set up a full refund via API?
Instead of three separate scripts, we use a wrapper that calls the required API depending on the acquirer. This makes the code easier to maintain and extend.
function processRefund(string $gateway, int $paymentId, string $externalId, ?float $amount = null): void {
switch ($gateway) {
case 'tinkoff':
$params = [
'TerminalKey' => TINKOFF_TERMINAL,
'PaymentId' => $externalId,
];
$params['Token'] = tinkoffSign($params, TINKOFF_SECRET);
$result = tinkoffPost('/v2/Cancel', $params);
if ($result['Status'] === 'REFUNDED') {
setPaymentRefunded($paymentId);
}
break;
case 'yookassa':
$refund = $client->createRefund([
'payment_id' => $externalId,
'amount' => [
'value' => number_format($amount, 2, '.', ''),
'currency' => 'USD',
],
'receipt' => buildRefundReceipt($order),
], uniqid('', true));
if ($refund->getStatus() === 'succeeded') {
setPaymentRefunded($paymentId);
}
break;
case 'sberbank':
$params = [
'userName' => SBER_LOGIN,
'password' => SBER_PASSWORD,
'orderId' => $externalId,
'amount' => (int)($amount * 100),
];
$response = file_get_contents(
'https://securepayments.sberbank.ru/payment/rest/refund.do?' . http_build_query($params)
);
$result = json_decode($response, true);
if ($result['errorCode'] === '0') {
setPaymentRefunded($paymentId);
}
break;
}
}
Why is it important to update the order status in Bitrix?
Without a correct status update, the order will remain paid. This leads to a repeat shipment or incorrect accounting. We use the standard API Bitrix\Sale\Payment:
function setPaymentRefunded(int $paymentId): void {
$payment = Bitrix\Sale\Payment::loadById($paymentId);
if (!$payment) return;
$payment->setPaid('N');
$payment->setField('PS_STATUS', 'Y');
$payment->setField('PS_STATUS_CODE', 'refunded');
$payment->setField('PS_STATUS_MESSAGE', 'Refund from ' . date('d.m.Y H:i'));
$payment->save();
$order = $payment->getCollection()->getOrder();
// Optional: move the order to the "Refund" status
$order->setField('STATUS_ID', 'RF');
$order->save();
sendRefundNotification($order, $payment->getSum());
}
What is included in the refund setup?
When ordering refund setup for 1C-Bitrix, we provide:
- Analysis of the current acquirer integration and payment scheme.
- Development of a unified API wrapper for all supported payment systems.
- Setup of refund receipt generation in accordance with Federal Law No. 54-FZ.
- Integration with online cash registers (ATOL, Shtrikh-M, etc.) for automatic receipt sending.
- Updating order statuses and sending notifications to customers.
- Optionally — adding a "Refund payment" button to the administrative panel.
- Testing of all scenarios, including partial refunds and error handling.
- Documentation for the new functionality.
Timeline — from 0.5 to 3 days turnkey. We will assess your project and get back with a cost calculated after audit. Order turnkey refund setup.
How to correctly generate a refund receipt?
If the cash register is connected, the API will reject the request without a refund receipt. The receipt mirrors the original one. More details on the requirements of Federal Law No. 54-FZ.
function buildRefundReceipt(Bitrix\Sale\Order $order): array {
$receipt = [
'customer' => ['email' => getBuyerEmail($order)],
'items' => []
];
foreach ($order->getBasket() as $item) {
$receipt['items'][] = [
'description' => $item->getField('NAME'),
'quantity' => $item->getQuantity(),
'amount' => [
'value' => number_format($item->getPrice(), 2, '.', ''),
'currency' => 'USD',
],
'vat_code' => getItemVatCode($item),
'payment_subject' => 'commodity',
'payment_mode' => 'full_payment',
];
}
// Delivery as a separate line item
$deliveryPrice = $order->getDeliveryPrice();
if ($deliveryPrice > 0) {
$receipt['items'][] = [
'description' => 'Delivery',
'quantity' => 1,
'amount' => [
'value' => number_format($deliveryPrice, 2, '.', ''),
'currency' => 'USD'
],
'vat_code' => 1,
'payment_subject' => 'service',
'payment_mode' => 'full_payment',
];
}
return $receipt;
}
Partial refunds and error handling
Partial refunds are a common need when canceling multiple items. For YooKassa, this is a standard feature: the refund amount is passed in the amount field. For Tinkoff, only a full refund of a confirmed payment is possible. With partial refunds, it is important to check limits and adjust the remaining amount. Error handling: each API returns different codes. In our function, we log every response and, in case of failure, send a notification to the administrator. Caching API requests by payment key prevents duplicate charges. We also add a check for an existing refund to avoid double operations.
Manager Interface
The standard Bitrix interface allows initiating a refund through the order card. If not — we add a "Refund payment" button via a custom action in the administrative part:
// local/templates/admin/sale_order_detail/refund_button.php
if ($order->isPaid() && $order->getField('STATUS_ID') !== 'RF') {
echo '<a href="/local/admin/refund.php?order_id=' . $order->getId() . ' class="adm-btn adm-btn-red">Refund payment</a>';
}
Setup Timeline
| Task | Timeline |
|---|---|
| Full refund without fiscalization | 0.5–1 day |
| Full refund + refund receipt (54-FZ) | 1–3 days |
| Interface in the admin panel | 0.5–1 day |
Automating refunds via API is 10 times faster than manually entering data in your bank's personal account. Contact us to discuss setting up refunds for your project. Get a consultation on integrating payment acquirers with automatic fiscalization. Our experience — over 10 years of working with Bitrix and over 50 integrations with payment systems. We guarantee compliance with 54-FZ and full automation of the process. Order automatic refund setup for your Bitrix project. Our engineers will integrate any acquirer with full fiscalization.

