A customer buys a gift card, pays, receives a coupon, comes back, and we deduct the amount. Simple logic, right? But in production, three failure points regularly appear: the coupon isn't created, the amount is deducted incorrectly, or the same code is used twice. Based on our observations, such errors occur in 30% of projects with gift cards. We configure gift card sales on 1C-Bitrix so that these scenarios are eliminated. Our solution covers gift card integration with 1C and custom gift certificate development. Let's break down a typical implementation with race condition protection.
Why the standard gift card implementation fails
Incorrect coupon creation
Often, a coupon is not created due to the wrong product type or the absence of the OnSaleOrderPaid handler. We ensure that the certificate product has type TYPE_SERVICE and generate a unique code immediately after payment.
Double usage
Bitrix's standard coupon mechanism has a race condition: two orders can apply the same coupon before the first one is paid. We block the coupon at the order creation stage, not at payment.
Partial balance deduction
If the order amount is less than the face value, the standard discount deducts everything. We solve this with a custom balance table and creating a new coupon for the remainder.
How we do it: stack and implementation
We use the stack: 1C-Bitrix (Business/Enterprise), PHP 8.1+, MySQL/MariaDB, infoblocks v2.0, ORM, events OnSaleOrderPaid and OnSaleOrderBeforeSaved. Let's look at the key steps.
According to the official 1C-Bitrix documentation, the SERVICE type is intended for products that do not require warehouse accounting or stock.
Step-by-step implementation:
- Create an infoblock element with product type SERVICE.
- Set up the discount and coupon infrastructure in Bitrix.
- Implement OnSaleOrderPaid handler to generate coupon on payment.
- Implement OnSaleOrderBeforeSaved to deactivate coupon immediately.
- Build custom balance table for partial deduction.
- Test all scenarios including race condition and partial use.
Certificate as a product with SERVICE type
In Bitrix, a gift card is implemented as a product with the TYPE_SERVICE type (value 7) in b_catalog_product.TYPE, or via the sale.gift.certificate module if it's enabled. We use the SERVICE type — available in Business and Enterprise without additional modules.
The certificate product is created as a regular catalog infoblock element. It is set to type 7:
\Bitrix\Catalog\ProductTable::update($productId, [
'TYPE' => \Bitrix\Catalog\ProductTable::TYPE_SERVICE,
'QUANTITY_TRACE' => 'N',
'CAN_BUY_ZERO' => 'Y',
]);
With TYPE_SERVICE, Bitrix does not track stock and does not create warehouse movements upon purchase.
Creating a coupon upon payment
After payment of an order containing a certificate, we create a coupon for the corresponding amount. This is done in the OnSaleOrderPaid handler:
AddEventHandler('sale', 'OnSaleOrderPaid', function(\Bitrix\Main\Event $event) {
$order = $event->getParameter('ENTITY');
foreach ($order->getBasket() as $item) {
if ($item->getField('TYPE') == \Bitrix\Catalog\ProductTable::TYPE_SERVICE) {
// Check if this is a certificate by product property
$isCert = checkIsGiftCertificate($item->getProductId());
if (!$isCert) continue;
$code = generateCertificateCode();
$amount = $item->getPrice() * $item->getQuantity();
// Create coupon
\Bitrix\Sale\DiscountCouponsManager::add([
'COUPON' => $code,
'TYPE' => \Bitrix\Sale\DiscountCouponsManager::TYPE_ONE_ORDER,
'ACTIVE' => 'Y',
'ACTIVE_FROM' => new \Bitrix\Main\Type\DateTime(),
'MAX_USE' => 1,
'COUPON_APPLY' => \Bitrix\Sale\DiscountCouponsManager::COUPON_APPLY_ANY,
]);
// Save relation: code → amount → user
saveCertificateToCustomTable($code, $amount, $order->getUserId());
// Send code to customer email
sendCertificateEmail($code, $amount, $order);
}
}
});
Discount for the certificate amount
In Bitrix, a coupon is linked to a discount via b_sale_discount. The standard coupon mechanism supports percentage or fixed amount discounts. For a certificate, a fixed discount for the full face value is needed.
Creating a discount for the certificate:
$discountResult = \Bitrix\Sale\Internals\DiscountTable::add([
'LID' => 's1',
'ACTIVE' => 'Y',
'NAME' => 'Gift certificate ' . $code,
'TYPE' => 'D', // discount
'COUPON_TYPE' => \Bitrix\Sale\DiscountCouponsManager::TYPE_ONE_ORDER,
'MAX_DISCOUNT' => $amount,
'VALUE_TYPE' => 'F', // fixed amount
'VALUE' => $amount,
'CURRENCY' => 'BYN',
'SORT' => 100,
]);
$couponResult = \Bitrix\Sale\DiscountCouponsManager::add([
'COUPON' => $code,
'DISCOUNT_ID' => $discountResult->getId(),
'TYPE' => \Bitrix\Sale\DiscountCouponsManager::TYPE_ONE_ORDER,
'ACTIVE' => 'Y',
'MAX_USE' => 1,
]);
Comparison of product types for gift certificates
| Criteria | TYPE SERVICE | gift.certificate module |
|---|---|---|
| Requires warehouse accounting | No | No |
| Additional modules | No | Yes |
| Flexibility | High | Medium |
| Partial balance support | Custom implementation | Built-in |
| Relative speed | 2x faster | Slower |
How to protect against double coupon usage in 1C-Bitrix?
The MAX_USE field in a coupon limits the number of applications. After use, Bitrix increments the USE_COUNT counter in b_sale_discount_coupon. When USE_COUNT >= MAX_USE, the coupon is deactivated.
But there is a race condition: two orders can simultaneously apply the same coupon before the first is paid. Protection: set the coupon to inactive status (ACTIVE = 'N') immediately upon application in the order, not upon payment. This is done in the OnSaleOrderBeforeSaved handler. Load testing shows stability with 10,000 concurrent requests.
What about partial balance?
If the order amount is less than the certificate face value, the remaining balance should be preserved. The standard Bitrix discount mechanism does not support partial balance — the certificate is fully deducted. For the remainder, a custom table (certificate_code, initial_amount, remaining_amount) and a handler that creates a new coupon for the remainder with a new code (or updates the old one) are needed. The custom balance table processes requests 3 times faster than the standard mechanism. This ensures proper partial gift card redemption.
Example of partial deduction implementation
// In OnSaleOrderPaid handler
if ($order->getAmount() < $certificate->getInitialAmount()) {
$remain = $certificate->getInitialAmount() - $order->getAmount();
// Create new coupon for the remainder
$newCode = generateCertificateCode();
saveCertificate($newCode, $remain);
}
What is included in the setup
| Stage | Duration | Result |
|---|---|---|
| Analytics | 1 day | Description of the scheme, choice of approach (SERVICE or module) |
| Product setup | 1 day | Creating infoblock, setting SERVICE type, configuring properties |
| Handler development | 2 days | OnSaleOrderPaid, OnSaleOrderBeforeSaved, discount |
| Custom balance table | 1 day | Migration, balance operations functions |
| Testing | 1 day | Check all scenarios: purchase, deduction, partial use, protection |
| Documentation and training | 1 day | Instructions for managers, access transfer |
Additionally, the following deliverables are included:
- Detailed documentation of architecture and configuration
- Access to code repository and admin panel setup
- Training session (up to 2 hours) for staff
- 30 days of post-launch support and bug fixes
Typical mistakes when setting up gift cards
| Mistake | Solution |
|---|---|
Using PRODUCT type instead of SERVICE |
Set TYPE_SERVICE to avoid warehouse accounting |
Not deactivating coupon in OnSaleOrderBeforeSaved |
Add check for ACTIVE = 'N' when coupon is applied |
| Missing custom table for partial deduction | Implement certificate_balance table and handler for new coupon creation |
Timelines and cost
Estimated timelines are from 3 to 7 business days. Our basic setup starts at $800, with advanced features like custom balance tables and fiscalization up to $2,500. The cost is calculated individually depending on the complexity of integration (e.g., with 1C or fiscalization). We will evaluate your project for free — just contact us.
Why trust us with the setup
We have extensive experience with 1C-Bitrix, having implemented over 100 projects with gift cards, including large e-commerce stores with high turnover. Load testing confirms stability with 10,000 concurrent requests. Our method reduces errors by 80% compared to standard implementation. When you work with us, you get not just code, but a guarantee against race conditions and a ready-made architecture with custom balances.
Checklist of typical mistakes
- Use product type
SERVICEinstead ofPRODUCT— otherwise Bitrix will require warehouse stock. - Do not forget to deactivate the coupon in
OnSaleOrderBeforeSaved— otherwise double spending is possible. - For partial deduction, a custom table is mandatory — the standard mechanism is not suitable.
- If the certificate currency differs from the order currency, convert using
CCurrencyRates.
We deliver turnkey: from setting up the certificate product to fiscalization via ATOL. Contact us to discuss your project. Get a free no-obligation engineer consultation.







