Configuring Automatic Return Processing in 1C-Bitrix
Return automation is needed when the volume exceeds 20–30 requests per day and managers are spending significant time on routine operations: checking order payment, creating a return in 1C, processing the refund, sending a customer email. Each of these actions can be triggered automatically when a request status changes — through sale module event handlers and Bitrix agents.
Return event model
The sale module generates events when returns are processed. Key automation trigger points:
-
OnSaleOrderReturnSaved— return request created or updated -
OnSaleOrderReturnStatusChange— status changed -
OnSalePaymentCollectionReturnAdd— payment return added
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 a request is created
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 manager 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 up to a configurable amount 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 below ' . $autoApproveLimit);
$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 "Approved" status — 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) {
// Payment system does not support auto refund — queue 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 receipt confirmation via tracking
If the customer ships the item back with a tracking number, delivery can be monitored automatically:
class TrackingWatcher
{
// Agent running 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();'; // reschedule agent
}
}
Escalation of overdue requests
Agent for SLA monitoring of returns:
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 refund via payment system APIs (YooKassa, Tinkoff, etc.)
- Incoming parcel tracking agent (Russian Post, CDEK, Boxberry)
- SLA monitoring agent with escalation of overdue requests
- Email automation: templates for each status
Timeline: basic automation (notifications + automatic refund) — 2–3 weeks. Full stack with SLA monitoring and tracking — 4–6 weeks.







