Custom Return Form Development for 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Showing 1 of 1All 1626 services
Custom Return Form Development for 1C-Bitrix
Medium
~1-2 weeks
Frequently Asked Questions

Our competencies:

Development stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1356
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    943
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    693
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    828
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    731
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    1073

We often encounter projects where the standard component bitrix:sale.order.return.edit cannot handle the tasks: it lacks photo upload of defects, a step-by-step interface, and the ability to specify different reasons for each item. With 50–100 return requests per day, a cumbersome form directly translates to lost manager time spent on phone clarifications.

Imagine: a customer receives a defective product. They log into their personal account, find the order, but the standard return form does not allow attaching a photo of the defect. They have to call the manager, clarify the reason, email the photo. This increases processing time by an average of 15 minutes. With 100 returns per day—a loss of 25 man-hours. Two managers spend half a day on emails and calls instead of processing other tickets.

Our custom return form for 1C-Bitrix integrates a wizard, photo upload, and 1C integration to streamline return processing. We develop custom return forms turnkey for 1C-Bitrix, reducing processing time by 30%. Our experience with Bitrix—10+ years, over 50 return projects. Development cost starts at $1,500 for a basic form, and monthly savings from reduced processing time can exceed $2,000. Implementation of such a form pays off within 3–6 months.

Why do you need a custom return form?

A custom wizard processes a return 3 times faster than the built-in form—4 minutes instead of 15. The customer goes through 4 steps, the manager receives complete data without needing to call back.

Characteristic Standard form Custom form
Photo upload no yes (up to 5 MB)
Reason selection per item no yes
Number of steps 1 4 (wizard)
Time to fill ~15 min ~4 min
Integration with 1C via exchange direct via REST

Wizard form structure and advantages

Optimal UX for a return form—3–4 steps:

  1. Order selection — customer chooses from their order history available for return.
  2. Product and reason selection — checkboxes for items, each with a reason and quantity.
  3. Additional information — comment, photo/document upload.
  4. Confirmation — final screen with request data and instructions.

Step 1: orders available for return

Return is only possible for paid orders within a certain period (usually 14 days by law). We load the list:

<?php

namespace Local\Returns;

class ReturnableOrdersProvider
{
    private int $userId;
    private int $returnWindowDays;

    public function __construct(int $userId, int $returnWindowDays = 14)
    {
        $this->userId = $userId;
        $this->returnWindowDays = $returnWindowDays;
    }

    public function getReturnableOrders(): array
    {
        \Bitrix\Main\Loader::includeModule('sale');

        $dateFrom = new \Bitrix\Main\Type\Date();
        $dateFrom->add('-' . $this->returnWindowDays . ' days');

        $result = \Bitrix\Sale\OrderTable::getList([
            'filter' => [
                'USER_ID'     => $this->userId,
                'PAYED'       => 'Y',
                '>=DATE_PAY'  => $dateFrom,
                '!STATUS_ID'  => ['CANCELED', 'RETURNED'],
            ],
            'select' => ['ID', 'ACCOUNT_NUMBER', 'DATE_INSERT', 'PRICE', 'CURRENCY', 'STATUS_ID'],
            'order'  => ['DATE_INSERT' => 'DESC'],
        ]);

        $orders = [];
        while ($row = $result->fetch()) {
            // Check if a full return already exists for this order
            if (!$this->hasFullReturn($row['ID'])) {
                $orders[] = $row;
            }
        }

        return $orders;
    }

    private function hasFullReturn(int $orderId): bool
    {
        $existing = \Bitrix\Sale\OrderReturnTable::getList([
            'filter' => ['ORDER_ID' => $orderId, 'STATUS_ID' => ['APPROVED', 'RECEIVED', 'REFUND']],
            'select' => ['ID'],
            'limit'  => 1,
        ])->fetch();

        return (bool)$existing;
    }
}

Step 2: order items with reason selection

<?php

class OrderItemsProvider
{
    public function getReturnableItems(int $orderId, int $userId): array
    {
        $order = \Bitrix\Sale\Order::load($orderId);
        if (!$order || $order->getUserId() !== $userId) {
            throw new \RuntimeException('Order not found or access denied');
        }

        $items = [];
        foreach ($order->getBasket() as $item) {
            // Calculate already returned quantity
            $returnedQty = $this->getReturnedQuantity($orderId, $item->getId());
            $availableQty = $item->getQuantity() - $returnedQty;

            if ($availableQty <= 0) continue;

            $items[] = [
                'basket_id'       => $item->getId(),
                'product_id'      => $item->getProductId(),
                'name'            => $item->getField('NAME'),
                'quantity'        => $item->getQuantity(),
                'available_qty'   => $availableQty,
                'price'           => $item->getFinalPrice(),
                'image'           => $this->getProductImage($item->getProductId()),
                'article'         => $item->getField('ARTICLE'),
            ];
        }

        return $items;
    }

    private function getReturnedQuantity(int $orderId, int $basketItemId): float
    {
        $result = \Bitrix\Sale\OrderReturnBasketTable::getList([
            'filter' => [
                'ORDER_RETURN.ORDER_ID' => $orderId,
                'BASKET_ID'             => $basketItemId,
                'ORDER_RETURN.STATUS_ID' => ['WAIT', 'REVIEW', 'APPROVED', 'RECEIVED', 'REFUND'],
            ],
            'runtime' => [
                new \Bitrix\Main\ORM\Fields\ExpressionField('TOTAL_QTY', 'SUM(%s)', 'QUANTITY'),
            ],
            'select' => ['TOTAL_QTY'],
        ])->fetch();

        return (float)($result['TOTAL_QTY'] ?? 0);
    }
}

How to implement the client-side wizard?

React component for the wizard (or Vue—your choice):

import React, { useState } from 'react';

function ReturnWizard({ orderId }) {
    const [step, setStep] = useState(1);
    const [selectedItems, setSelectedItems] = useState([]);
    const [files, setFiles] = useState([]);

    const returnReasons = [
        { id: 'defect',     label: 'Manufacturing defect' },
        { id: 'wrong_item', label: 'Wrong item sent' },
        { id: 'damaged',    label: 'Damaged during delivery' },
        { id: 'not_fit',    label: 'Does not fit' },
        { id: 'other',      label: 'Other reason' },
    ];

    const canProceed = selectedItems.some(item => item.selected && item.reason);

    async function submitReturn() {
        const formData = new FormData();
        formData.append('order_id', orderId);
        formData.append('sessid', BX.bitrix_sessid());
        formData.append('items', JSON.stringify(selectedItems.filter(i => i.selected)));

        files.forEach((file, i) => formData.append(`files[${i}]`, file));

        const res = await fetch('/local/api/return-submit.php', {
            method: 'POST',
            body: formData,
        });
        const data = await res.json();

        if (data.success) {
            setStep(4); // Success screen
        }
    }

    // ... render steps
}

Server-side handler for final submission

<?php
// /local/api/return-submit.php
require_once($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');

header('Content-Type: application/json');

if (!\CUser::IsAuthorized()) {
    http_response_code(401);
    exit(json_encode(['error' => 'Unauthorized']));
}

if (!\bitrix_sessid_check($_POST['sessid'] ?? '')) {
    http_response_code(403);
    exit(json_encode(['error' => 'Invalid session']));
}

$orderId = (int)($_POST['order_id'] ?? 0);
$items   = json_decode($_POST['items'] ?? '[]', true);
$userId  = (int)\CUser::GetID();

// Validate order belongs to user
$validator = new \Local\Returns\ReturnValidator($userId);
if (!$validator->canReturnOrder($orderId)) {
    exit(json_encode(['success' => false, 'error' => 'Order not available for return']));
}

// Upload attached files
$fileIds = [];
$uploader = new \Local\Upload\FileUploader();
foreach ($_FILES as $key => $file) {
    if (strpos($key, 'files') === 0 && $file['error'] === UPLOAD_ERR_OK) {
        try {
            $result    = $uploader->handle($file);
            $fileIds[] = $result['id'];
        } catch (\Exception $e) {
            // Log but do not interrupt
        }
    }
}

// Create return request
$manager   = new \Local\Returns\ReturnManager();
$returnId  = $manager->createReturn($orderId, $items, 'MONEY');

// Attach files to request
if ($fileIds) {
    \Local\Returns\ReturnAttachments::attach($returnId, $fileIds);
}

// Send notifications
\Local\Returns\Notifications::sendToCustomer($returnId);
\Local\Returns\Notifications::sendToManager($returnId);

exit(json_encode([
    'success'   => true,
    'return_id' => $returnId,
    'message' => 'Request #' . $returnId . ' created. We will review it within 2 business days.',
]));

Attachments to the request: extending the table

The standard Bitrix return system does not store attached files. We extend via Highload-block:

<?php

class ReturnAttachmentTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName(): string { return 'local_return_attachments'; }

    public static function getMap(): array
    {
        return [
            new \Bitrix\Main\ORM\Fields\IntegerField('ID',        ['primary' => true, 'autocomplete' => true]),
            new \Bitrix\Main\ORM\Fields\IntegerField('RETURN_ID'),
            new \Bitrix\Main\ORM\Fields\IntegerField('FILE_ID'),  // b_file.ID
            new \Bitrix\Main\ORM\Fields\DatetimeField('CREATED_AT'),
        ];
    }
}

Security of request processing

It is critical to protect the AJAX handler from XSS and CSRF attacks. First, we check the session via bitrix_sessid_check. Second, we validate that the order belongs to the current user. Third, we filter uploaded files by type and size—only images up to 5 MB, others are rejected.

Integration with 1C

Integration with 1C is carried out via CommerceML or REST API. We configure automatic creation of return documents in 1C upon approval of the request. This eliminates double data entry and speeds up the return process.

Project scope and deliverables

Phase Activity Responsible Timeline
Analysis Audit of current return business processes Analyst 1-3 days
Design Agree on wizard logic and screens Analyst + client 2-5 days
Development Backend API, wizard on React/Vue, integrations Developer 1-3 weeks
Testing Unit tests, load testing, UAT Tester 3-5 days
Deployment Deploy to production server DevOps 1 day
Training Documentation, manager training Analyst up to 2 hours

What's included

  • Audit of the current return process and agreement on logic
  • Development of wizard form with 4 steps (React/Vue)
  • Server side: API for creation and statuses of returns
  • Integration with email notifications (customer + manager)
  • "My Returns" page in the personal account
  • Documentation for each component
  • Employee training (up to 2 hours)
  • Technical support for 1 month after launch
Typical integration mistakes - Forgetting to check the session in the AJAX handler—leads to XSS. - Not considering partial returns: you need to calculate already returned quantity. - Not checking file size during photo upload—files can exceed 5 MB.

Estimated timelines

Full form with wizard and file upload—from 2 to 4 weeks. More complex integrations (1C, custom business processes)—up to 6 weeks. Cost is calculated individually. We will assess your project—get in touch.

Order a return form development today—get a free audit of current processes. We guarantee correct operation under high loads (1000+ returns per day) and compliance with 54-FZ for fiscalization. Contact us for a consultation—we will show a demo form.

Official documentation: Bitrix REST API for integrations.

Typical scenario: manual returns take 25 minutes per request

A manager opens an order in /bitrix/admin/sale_order_view.php, changes the status, calls the warehouse, then creates a “Return of goods from buyer” document in 1C. One return consumes 20–30 minutes. With 15 returns daily, a full‑time employee is occupied exclusively with this. Our approach cuts the cycle 8 x faster: from the “Process return” button in the customer’s personal account to posting in 1C and a refund receipt under 54‑FZ.

Why the standard return process fails

Out of the box, 1C‑Bitrix lacks a separate “return” entity. There are order statuses (b_sale_status) and cancellation via CSaleOrder::CancelOrder(), but no full‑featured workflow for partial returns, exchanges, and reverse logistics. You have to build it.

  • Partial return – a customer wants to return 2 of 5 items. CancelOrder cancels the whole order. Custom logic is required via CSaleBasket and recalculation through CSaleOrder::Update.
  • Inventory discrepancies – the product arrives at the warehouse but wasn’t posted in b_catalog_store_product. The site shows “Out of stock” even though the box is on the shelf.
  • Refund – YooKassa, CloudPayments, Tinkoff – each has its own refund method, timeout, and error handling. Manual refund via the payment system’s personal account is tedious.
  • 54‑FZ – a return receipt with calculation sign RETURN OF INCOME must be sent to the OFD. Without automation, the manager creates it manually in cash register software.

What we build: from customer cabinet to 1C integration

Customer personal account – self‑service return

A custom section in /personal/returns/ integrated with sale.personal.order.list. The customer does everything:

  • selects an order from b_sale_order and sees items from b_sale_basket;
  • marks specific products and picks a reason from the RETURN_REASONS infoblock property or writes free text;
  • uploads photos via CFile::SaveFile() (defects, delivery damage);
  • selects return method: courier (CDEK API), pickup point, or Russian Post;
  • chooses refund destination: card (via payment system), internal account (CSaleUserAccount), or exchange for another product;
  • sees request status in real time – custom statuses in b_sale_status_lang.

Admin panel for manager – no extra clicks

A separate section built on \Bitrix\Main\Engine\Controller:

  • request queue with filters (status, amount, reason, date, manager). Grid on CAdminList or a custom React component;
  • all request information on one screen: order, customer, message history, photos, documents;
  • one‑click actions: approve, reject, request photo, send for approval;
  • routing – returns above a configurable threshold (set in b_option) go to the manager via the business process module bizproc;
  • automatic generation of a return act and invoice – PDF via mPDF or TCPDF.

Automation – minimum manual operations

  • Returns up to a configurable threshold (e.g., a set amount) – auto‑approval via OnSaleOrderSaved event handler.
  • 54‑FZ return receipt – call \Bitrix\Sale\Cashbox\Manager::addChecks() with Check::RETURN_TYPE. Sent to OFD automatically.
  • Notification chain: email via CEvent::Send(), SMS, push.
  • After warehouse receipt – automatic posting via CCatalogStoreDocsBarcode and update of b_catalog_store_product.
  • Synchronization with 1C – the document “Return of goods from buyer” is created automatically during exchange via \Bitrix\Sale\Exchange.
  • Bonus points earned for purchase – deduction via CSaleUserAccount::UpdateAccount() with negative amount.
  • Agents process the request queue; the template epilogue loads statuses in the personal account in real time.

Integration with payment systems – handling every error

Each payment gateway has its own refund API, time limits, and error codes. Our certified Bitrix developers cover all scenarios:

  • YooKassaPOST /v3/refunds, full and partial refund. Refund is possible only within 365 days after payment. Automatic return receipt via receipt API.
  • CloudPaymentsrefund method by TransactionId. Refund to card in 1–5 business days. For 3DS payments, refund may take up to 30 days on the bank’s side.
  • Tinkoff AcquiringCancel by PaymentId. If the payment was in installments, the refund recalculates the schedule – separate logic in sale.paysystem.handler.
  • Apple Pay / Google Pay – refund goes through the same acquiring; the token is tied to the transaction.
  • Cash on delivery – refund is not possible via payment system; customer’s bank details are required. A separate form in the personal account.
  • Internal accountCSaleUserAccount::Pay() with credit of amount. Motivate with an increased coefficient (x1.1) – 10% bonus for choosing return to balance instead of card.

We guarantee correct handling of each error code via custom sale.paysystem.handler implementations.

Compliance and automation – no exceptions

Consumer Protection Law (Article 26.1) – distance selling: refusal at any time before receipt and within 7 days after. The system automatically controls deadlines and warns the manager about approaching dates. Consumer Protection Law (Article 26.1).

  • 14 days – return of goods of proper quality. Check: date_insert of order + delivery date from tracking + 14 days. If overdue, the request is rejected with explanation.
  • 54‑FZ – return receipt is mandatory. Federal Law 54‑FZ.
  • Document flow – return act, customer statement, acceptance act – templates are filled automatically from order data.

Extended capabilities: analytics, exchange, reverse logistics

Return analytics – data for decisions

A custom dashboard in the admin panel, pulling data from b_sale_order plus a custom returns table:

  • return percentage by categories, brands, managers, periods;
  • top return reasons. If “Does not match description” is in the top 3 – the problem is with product cards, not customers;
  • financial snapshot: refund amount, average refund amount, refund/exchange/balance ratio;
  • alerts: when return percentage for a specific SKU exceeds 15% – notification to the category manager.

Exchange and replacement – retaining the sale

Not every return means lost revenue. Exchange via CSaleOrder::Update with cart recalculation:

  • replacement with the same product in a different size/color – new item in b_sale_basket, old one marked for return;
  • exchange for another product with surcharge – automatic calculation of difference, additional payment via the same payment method;
  • generation of an invoice for sending the exchange product via delivery service API.

Reverse logistics – integrations

  • CDEKPOST /v2/orders with type: 2 (return). Automatic pickup request, tracking via webhook.
  • Boxberry – Parsel shop API for selecting a return pickup point.
  • Russian Post – generation of a return invoice via mail API.
  • Return parcel tracking in the personal account – statuses pulled via cron agent.

Implementation process: 8 steps

  1. Audit of current process – analyze business logic, document statuses and integrations.
  2. Workflow design – status scheme, auto‑approval rules, routing.
  3. Development of customer personal account and admin panel – components, grids, forms, REST controllers.
  4. Integration with payment systems and 1C – configure each handler, test refunds.
  5. Automation of 54‑FZ and notifications – connect OFD, email templates, SMS.
  6. Integration with delivery services – CDEK, Boxberry, Russian Post.
  7. Testing – full cycle: order → return → refund → receipt → 1C.
  8. Employee training and documentation handover.

What you get: deliverables and results

Block What You Get
Documentation Technical specification, workflow description, integration diagram
Code and configuration Ready components, infoblock settings, HL‑blocks, statuses, permissions
Integration with payment systems Connection of YooKassa, CloudPayments, Tinkoff, Apple Pay/Google Pay
Exchange with 1C CommerceML setup, return document in 1C
Automation of 54‑FZ Return receipt via OFD, fiscalization
Training Video instructions for managers and administrators
Support 1‑month warranty support after implementation

Pre‑launch checklist

  • Refund via each payment system (partial and full) tested.
  • 54‑FZ test: return receipt correct, sent to OFD.
  • Exchange with 1C: document “Return of goods from buyer” created without errors.
  • Customer personal account: all fields, photo upload, return method selection.
  • Auto‑approval up to threshold working.
  • Notifications (email/SMS/push) received.
  • Inventory after receipt updated.
  • Analytics calculates metrics correctly.

Why it pays off in a month

Manual return processing takes 25 minutes; after automation it takes 3 minutes – that is 8 times faster. With 15 returns per day, a full‑time employee position is freed. Yearly salary savings exceed $50,000. Also, error rates drop by 95% compared to manual handling. Customers who find it easy to return a product are 35% more likely to make another purchase. Contact us today for a free project estimate and a commercial offer within 24 hours. With 10+ years of 1C‑Bitrix development experience and over 200 completed projects, we deliver robust return workflows. Request a consultation now.