Setting Up Automatic Return Processing in 1C-Bitrix
Return automation is needed where the volume exceeds 20–30 requests per day and the manager spends significant time on routine operations: checking order payment, creating a return in 1C, processing the refund, sending an email to the customer.
Each of these actions can be triggered automatically when the request status changes — through the sale module event handlers and Bitrix agents.
Our team, over 8 years of working with the platform, has implemented more than 40 return automations for online stores of various scales.
We provide full integration with 1C and payment gateways, which reduces the average processing time of a single request from 15 minutes to 2 minutes.
The savings for a store with a flow of 50 returns per day amount to about 10 hours of manager work per week — this frees up resources for more important tasks. Automation also eliminates human errors in refund processing and working with 1C.
Return Event Model
The sale module generates events when working with returns. Key points for automation:
OnSaleOrderReturnSaved— creating or updating a requestOnSaleOrderReturnStatusChange— status changeOnSalePaymentCollectionReturnAdd— adding a payment return
Register handlers in /local/php_interface/init.php:
$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->addEventHandler('sale', 'OnSaleOrderReturnSaved', [\Local\Returns\AutoProcessor::class, 'onReturnSaved']);
$eventManager->addEventHandler('sale', 'OnSaleOrderReturnStatusChange', [\Local\Returns\AutoProcessor::class, 'onStatusChange']);
Automatic actions when creating a return request
namespace Local\Returns;
class AutoProcessor
{
public static function onReturnSaved(\Bitrix\Main\Event $event): void
{
$return = $event->getParameter('ENTITY');
if (!$return instanceof \Bitrix\Sale\OrderReturn) return;
// Only on creation (not on update) if (!$event->getParameter('IS_NEW')) return; $returnId = $return->getId(); // 1. Assign a responsible person by rotation self::assignResponsible($returnId);
// 2. Send confirmation to the customer
Notifications::sendToCustomer($returnId, 'RETURN_CREATED');
// 3. Notify the manager Notifications::sendToManager($returnId, 'NEW_RETURN'); // 4. If the return amount is small — auto-approve self::tryAutoApprove($return); }
public static function onStatusChange(\Bitrix\Main\Event $event): void
{
$returnId = $event->getParameter('RETURN_ID');
$newStatus = $event->getParameter('NEW_STATUS_ID');
$oldStatus = $event->getParameter('OLD_STATUS_ID');
$handler = new self();
$handler->handleStatusTransition($returnId, $oldStatus, $newStatus);
}
private function handleStatusTransition(int $returnId, string $from, string $to): void
{
match ($to) {
'APPROVED' => $this->onApproved($returnId),
'RECEIVED' => $this->onReceived($returnId),
'REJECTED' => $this->onRejected($returnId),
'REFUND' => $this->onRefunded($returnId),
default => null,
};
}
}
Auto-approval of small returns
Returns below a certain amount (configurable threshold) do not require manual review:
private static function tryAutoApprove(\Bitrix\Sale\OrderReturn $return): void
{
$autoApproveLimit = (float)\Bitrix\Main\Config\Option::get(
'local.returns',
'auto_approve_limit',
500
);
$returnAmount = (float)$return->getField('REFUND_AMOUNT');
if ($returnAmount <= 0 || $returnAmount > $autoApproveLimit) {
return;
}
// Check: customer has no history of disputed returns
$userId = $return->getOrder()->getUserId();
if (self::hasDisputeHistory($userId)) {
return;
}
// Automatically change status to APPROVED
$return->setField('STATUS_ID', 'APPROVED');
$return->setField('MANAGER_COMMENT', 'Auto-approved: amount up to ' . $autoApproveLimit . ' RUB');
$result = $return->save();
if ($result->isSuccess()) {
\CEventLog::Add([
'SEVERITY' => 'INFO',
'AUDIT_TYPE_ID' => 'RETURN_AUTO_APPROVED',
'MODULE_ID' => 'local.returns',
'DESCRIPTION' => "Return #{$return->getId()} auto-approved, amount: {$returnAmount}",
]);
}
}
private static function hasDisputeHistory(int $userId): bool
{
// Count rejected returns over the last 6 months
$dateFrom = new \Bitrix\Main\Type\Date();
$dateFrom->add('-180 days');
$result = \Bitrix\Sale\OrderReturnTable::getList([
'filter' => [
'=ORDER.USER_ID' => $userId,
'STATUS_ID' => 'REJECTED',
'>=DATE_INSERT' => $dateFrom,
],
'select' => ['ID'],
]);
return (bool)$result->fetch();
}
Automatic refund upon approval
When transitioning to the "Approved" status — we automatically initiate a refund via the payment system API:
private function onApproved(int $returnId): void {
$return = \Bitrix\Sale\OrderReturn::loadById($returnId);
if (!$return) return;
$order = $return->getOrder(); $payments = $order->getPaymentCollection(); foreach ($payments as $payment) { if (!$payment->isPaid()) continue;
$psHandler = \Bitrix\Sale\PaySystem\Manager::getObjectById(
$payment->getPaymentSystemId()
);
if (!$psHandler) continue;
$supportsRefund = method_exists($psHandler, 'refund');
if (!$supportsRefund) {
// The payment system does not support auto refund — queue it for manual processing
$this->flagForManualRefund($returnId, 'PS does not support auto refund');
continue;
}
$refundAmount = (float)$return->getField('REFUND_AMOUNT');
$result = $psHandler->refund($payment, $refundAmount);
if ($result->isSuccess()) {
$return->setField('STATUS_ID', 'REFUND');
$return->setField('REFUND_DATE', new \Bitrix\Main\Type\DateTime());
$return->save();
Notifications::sendToCustomer($returnId, 'RETURN_REFUNDED');
} else {
$this->flagForManualRefund($returnId, implode('; ', $result->getErrorMessages()));
}
}
}
Automatic verification of goods receipt via tracking
If the buyer sends the item back using a tracking number, delivery can be tracked automatically:
class TrackingWatcher { // Agent launched every 2 hours
public static function checkPendingReturns(): string {
$returns = \Bitrix\Sale\OrderReturnTable::getList([
'filter' => [
'STATUS_ID' => 'APPROVED',
'UF_TRACKING' => ['!=', '', false], // requests with a tracking number
],
'select' => ['ID', 'UF_TRACKING', 'UF_CARRIER'],
]);
$checker = new \Local\Delivery\TrackingChecker();
while ($row = $returns->fetch()) {
$status = $checker->getStatus($row['UF_TRACKING'], $row['UF_CARRIER'] ?? 'pochta');
if ($status === 'delivered') {
$return = \Bitrix\Sale\OrderReturn::loadById($row['ID']);
$return->setField('STATUS_ID', 'RECEIVED');
$return->save();
}
}
return 'checkPendingReturns();'; // restart the agent
}
}
Escalation of overdue requests
Agent for return SLA monitoring:
class SlaWatcher {
private const SLA_HOURS = [
'WAIT' => 24, // review within 24 hours
'REVIEW' => 48, // make a decision within 48 hours
];
public static function checkOverdue(): string {
foreach (self::SLA_HOURS as $statusId => $maxHours) {
$deadline = new \Bitrix\Main\Type\DateTime();
$deadline->add('-' . $maxHours . ' hours');
$overdueReturns = \Bitrix\Sale\OrderReturnTable::getList([
'filter' => [
'STATUS_ID' => $statusId,
'<=DATE_STATUS' => $deadline,
],
'select' => ['ID', 'RESPONSIBLE_ID', 'ORDER_ID'],
]);
while ($row = $overdueReturns->fetch()) {
Notifications::escalate($row['ID'], $row['RESPONSIBLE_ID'], $statusId, $maxHours);
}
}
return 'checkOverdue();';
}
}
Scope of work
- Event handlers:
OnSaleOrderReturnSaved,OnSaleOrderReturnStatusChange - Auto-approval logic based on amount and customer history
- Automatic refunds via payment system APIs (YuKassa, Tinkoff, etc.)
- Inbound parcel tracking agent (Russian Post, CDEK, Boxberry)
- SLA monitoring agent with escalation of overdue requests
- Email automation: templates for each status
Timelines: basic automation (notifications + automatic refunds) — 2–3 weeks. Full stack with SLA monitoring and tracking — 4–6 weeks.
Detailed process description
Integration requires careful planning and analysis of the current processes in your company. We audit existing systems, identify integration points and possible conflicts.
At every stage, we provide detailed documentation and perform testing in a staging environment before launching in production. Our solutions are designed with industry best practices in mind and can significantly reduce operational costs.
Our quality guarantee
We take full responsibility for the quality of the work performed. Within 12 months after project completion, we fix any issues identified completely free of charge. Technical support includes consultations, training for your team, and assistance with any questions that arise.
Every project concludes with a final report describing the implemented functionality, optimization recommendations, and a system development plan.
Examples of successful projects
Over the course of our work, we have implemented integrations for companies of various sizes: from small online stores to large wholesale distributors.
Each project required an individual approach, and we are proud of the result, which significantly improved the operational efficiency of our clients.
Advantages of our approach
We provide a comprehensive solution, not isolated fixes. Every project includes documentation, testing, and team training.
Our clients appreciate that we don't just do the work, but also explain every step of the process, enabling your team to maintain the system independently in the future. For our part, we guarantee support for a year after the project is completed.
Contacts and next steps
If your company is facing the described problem, get in touch with us. We will analyze your system and propose the optimal solution.
The project cost depends on the complexity and scope of work, but for typical solutions we always provide an accurate estimate based on a preliminary requirements analysis.
Technologies and tools
In our work we use a modern stack: D7 event handlers, Bitrix ORM for database operations, caching via the main module, integrations with external APIs via the Guzzle HTTP client.
For payment gateways we use official SDKs from banks and payment systems — this guarantees the stability and security of refund operations. Testing is carried out on a staging environment with a full copy of production data before rolling out changes to the live environment.

