Crypto Payments 1C-Bitrix Integration

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Crypto Payments 1C-Bitrix Integration
Medium
~2-3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1217
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1046
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Crypto Payment Integration in 1C-Bitrix

1C-Bitrix is not the most convenient platform for custom payment integrations. The system of handlers is documented partially, events in CEvent trigger unpredictably, and the table structure changed between versions. Nevertheless, it's a real use case: Bitrix shops whose audience pays in crypto are not rare, especially in certain niches.

If you're in one of these use cases — this document is about doing it right.

Payment Handler Architecture

In Bitrix, payment systems are implemented via a class inheriting from \Bitrix\Sale\PaySystem\ServiceHandler. This is the correct approach — not a hack via awkward redirects, but official API.

Module structure:

/local/modules/mypay.crypto/
├── install/
│   ├── index.php          # Module installer
│   └── handler/
│       └── crypto.php     # Handler for Bitrix
├── lib/
│   ├── CryptoGateway.php  # Business logic
│   └── WebhookHandler.php # Callback processing
├── include.php
└── .settings.php

Handler Class

<?php
namespace MyCrypto\CryptoPay;

use Bitrix\Sale\PaySystem\ServiceHandler;
use Bitrix\Sale\Payment;
use Bitrix\Main\Request;

class Handler extends ServiceHandler
{
    // Handler type: REDIRECT — user goes to external URL
    const RETURN_URL = true;

    public function initiatePay(Payment $payment, Request $request = null)
    {
        $orderId = $payment->getOrderId();
        $amount  = $payment->getSum();
        $currency = $payment->getCurrencyCode(); // usually RUB or USD

        // Convert to crypto via rate
        $cryptoAmount = $this->convertToCrypto($amount, $currency, 'USDT');

        // Create payment in external system
        $paymentData = $this->gateway->createInvoice([
            'order_id'    => $orderId,
            'amount'      => $cryptoAmount,
            'currency'    => 'USDT',
            'callback_url'=> $this->getCallbackUrl($payment),
            'return_url'  => $this->getSuccessUrl($payment),
        ]);

        // Save external ID for reconciliation
        $this->setExtraParams([
            'crypto_invoice_id' => $paymentData['invoice_id'],
        ]);

        $this->setInitiatePayRedirect($paymentData['payment_url']);
        return ServiceResult::createSuccess();
    }

    protected function getCallbackUrl(Payment $payment): string
    {
        return \Bitrix\Main\Engine\UrlManager::getInstance()->getHostUrl()
            . '/bitrix/tools/sale_ps_interact.php?'
            . http_build_query([
                'TYPE'   => 'BACK_URL_NOTIFY',
                'PAYMENT_ID' => $payment->getId(),
            ]);
    }
}

Webhook Processing

sale_ps_interact.php is the standard Bitrix endpoint for payment notifications. The handler receives control via processRequest:

public function processRequest(Payment $payment, Request $request)
{
    $invoiceId = $request->get('invoice_id');
    $status    = $request->get('status');
    $signature = $request->get('signature');

    // 1. Verify signature
    if (!$this->verifyWebhookSignature($request->toArray(), $signature)) {
        $logger->error('Invalid webhook signature', ['invoice' => $invoiceId]);
        return ServiceResult::createError('INVALID_SIGNATURE');
    }

    // 2. Check amount via API (don't trust webhook data)
    $invoiceData = $this->gateway->getInvoice($invoiceId);
    if ($invoiceData['status'] !== 'confirmed') {
        return ServiceResult::createSuccess(); // wait
    }

    // 3. Verify amount matches
    $expectedSum = $payment->getSum();
    if (!$this->isAmountSufficient($invoiceData['received_amount'], $expectedSum)) {
        return ServiceResult::createError('UNDERPAYMENT');
    }

    // 4. Confirm payment in Bitrix
    $result = $payment->setField('PAID', 'Y');
    if ($result->isSuccess()) {
        $payment->save();
        // Trigger event — send email, update order status
        \Bitrix\Sale\Order::load($payment->getOrderId())->save();
    }

    return ServiceResult::createSuccess();
}

Exchange Rates

Can't show rate from arbitrary source without fixing. Use official rate with timestamp and store it:

class ExchangeRateService
{
    private const CACHE_TTL = 300; // 5 minutes
    
    public function getRate(string $from, string $to): array
    {
        $cacheKey = "crypto_rate_{$from}_{$to}";
        $cached = \Bitrix\Main\Data\Cache::createInstance();
        
        if ($cached->initCache(self::CACHE_TTL, $cacheKey)) {
            return $cached->getVars();
        }
        
        // CoinGecko API or Binance
        $rate = $this->fetchRateFromAPI($from, $to);
        $data = ['rate' => $rate, 'fetched_at' => time(), 'expires_at' => time() + 900];
        
        $cached->startDataCache();
        $cached->endDataCache($data);
        
        return $data;
    }
}

Fix rate for 15 minutes. If user pays after expiration — create new invoice with current rate.

Saving Transaction Data

Bitrix doesn't have native storage for custom payment data. Options:

\Bitrix\Sale\Internals\PaymentTable — standard table, via setExtraParams you can save arbitrary fields (serialized in PS_PARAMS).

Separate table — preferred for large data and future analytics:

class CryptoTransactionTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName(): string { return 'my_crypto_transactions'; }
    
    public static function getMap(): array
    {
        return [
            new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
            new IntegerField('PAYMENT_ID'),
            new StringField('INVOICE_ID'),
            new StringField('CURRENCY'),       // USDT, BTC, ETH
            new StringField('NETWORK'),        // ethereum, tron, bitcoin
            new FloatField('CRYPTO_AMOUNT'),
            new FloatField('FIAT_AMOUNT'),
            new StringField('EXCHANGE_RATE'),
            new StringField('TX_HASH'),
            new StringField('STATUS'),
            new DatetimeField('CREATED_AT'),
        ];
    }
}

Display Payment Details

Component bitrix:sale.payment.pay renders payment page. For crypto payments you need custom page with QR code and timer:

// In template /local/templates/.../pay.php
$invoiceData = $arResult['PAY_SYSTEM_PARAMS'];
?>
<div class="crypto-payment">
    <p>Transfer <strong><?= $invoiceData['amount'] ?> <?= $invoiceData['currency'] ?></strong></p>
    <div class="qr-code">
        <img src="https://api.qrserver.com/v1/create-qr-code/?data=<?= 
            urlencode($invoiceData['payment_uri']) ?>&size=200x200">
    </div>
    <code class="address"><?= $invoiceData['address'] ?></code>
    <div class="timer" data-expires="<?= $invoiceData['expires_at'] ?>">
        Pay within <span id="countdown"></span>
    </div>
</div>

Payment URI for EVM: ethereum:0xADDRESS/transfer?address=0xTO&uint256=AMOUNT (EIP-681). For BTC: bitcoin:ADDRESS?amount=0.001&label=Order123.

Testing

Bitrix has no sandbox for payment systems. Test scenario: create test payment system, send TYPE=BACK_URL_NOTIFY manually via curl with test data, check statuses.

curl -X POST https://yourshop.ru/bitrix/tools/sale_ps_interact.php \
  -d "TYPE=BACK_URL_NOTIFY&PAYMENT_ID=123&invoice_id=test_inv_001&status=confirmed&signature=..."

Typical Issues

Event order. Bitrix saves PAID=Y only if Order::save() is called correctly — Payment::save() is not enough. Test full flow with real order.

CSRF on webhook URL. sale_ps_interact.php bypasses CSRF protection, but your custom webhook endpoint doesn't. If making separate endpoint, add define('STOP_STATISTICS', true) and define('NO_KEEP_STATISTIC', 'Y') at file beginning.

Timezone. Bitrix stores dates in UTC but displays with site settings. When comparing expires_at use \Bitrix\Main\Type\DateTime not native PHP time().

Workflow

Analyze Bitrix version and current payment architecture → module development → gateway integration (NOWPayments/CoinPayments or custom) → staging testing → production installation.

Timeline 2-3 days: 1 day on module + webhook, 1 day on testing edge cases, 1 day on production deploy and monitoring first transactions.