Integration of 1C-Bitrix with the Payme payment system (Uzbekistan)

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.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1173
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • 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
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    745
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

1C-Bitrix Integration with the Payme Payment System (Uzbekistan)

Payme is Uzbekistan's largest payment aggregator with more than 10 million active users. For an online store serving a Uzbek audience, the absence of Payme from the list of payment methods noticeably hurts conversion — mobile shoppers in Uzbekistan are accustomed to paying through this app. There is no official ready-made module for 1C-Bitrix; integration is implemented via a custom JSON-RPC handler using the Subscribe API protocol.

How the Subscribe API Works

Payme does not redirect the buyer to a third-party site — instead, the Payme server calls JSON-RPC methods on your server. The store implements a server accepting 6 methods:

Method Purpose
CheckPerformTransaction Verify the order exists and the amount is correct
CreateTransaction Record the start of a payment
PerformTransaction Confirm successful payment
CancelTransaction Handle a cancellation
CheckTransaction Return the transaction status
GetStatement Transaction reconciliation for a period

Server Implementation in Bitrix

The entry point is a dedicated PHP file local/api/payme.php:

<?php
define('NO_KEEP_STATISTIC', true);
define('NOT_CHECK_PERMISSIONS', true);
require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

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

// Basic Auth — the password must match the key from the Payme merchant cabinet
$auth = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
preg_match('/Basic (.+)/', $auth, $m);
[, $password] = explode(':', base64_decode($m[1] ?? ''), 2);
if (!hash_equals(PAYME_CASHIER_KEY, $password)) {
    echo json_encode(['error' => ['code' => -32504, 'message' => 'Auth failed']]);
    exit;
}

$body   = json_decode(file_get_contents('php://input'), true);
$method = $body['method'] ?? '';
$params = $body['params'] ?? [];
$id     = $body['id'] ?? null;

if ($method === 'CheckPerformTransaction') {
    $orderId = (int)($params['account']['order_id'] ?? 0);
    $amount  = (int)($params['amount'] ?? 0); // in tiyin

    $order = Bitrix\Sale\Order::load($orderId);
    if (!$order) {
        echo json_encode(['error' => ['code' => -31050, 'message' => ['ru' => 'Order not found']], 'id' => $id]);
        exit;
    }

    // Compare amount (store prices in UZS multiplied by 100)
    $expected = (int)round($order->getPrice() * 100);
    if ($expected !== $amount) {
        echo json_encode(['error' => ['code' => -31001, 'message' => ['ru' => 'Amount mismatch']], 'id' => $id]);
        exit;
    }

    echo json_encode(['result' => ['allow' => true], 'id' => $id]);
    exit;
}

if ($method === 'PerformTransaction') {
    $paymeId = $params['id'];
    // Find the payment by payme_id and confirm
    $payment = findPaymentByPaymeId($paymeId);
    if ($payment && !$payment->isPaid()) {
        $payment->setPaid('Y');
        $payment->save();
    }
    echo json_encode(['result' => [
        'transaction'  => $paymeId,
        'perform_time' => time() * 1000,
        'state'        => 2,
    ], 'id' => $id]);
    exit;
}

Transaction Storage

Payme requires idempotency: a repeated CreateTransaction with the same ID must return the existing transaction. A dedicated table is needed:

CREATE TABLE b_payme_transactions (
    payme_id     VARCHAR(64) PRIMARY KEY,
    order_id     INT          NOT NULL,
    amount       BIGINT       NOT NULL,
    state        TINYINT      DEFAULT 1,
    create_time  BIGINT,
    perform_time BIGINT       DEFAULT 0,
    cancel_time  BIGINT       DEFAULT 0,
    reason       TINYINT      DEFAULT NULL
);

Transaction states: 1 — created, 2 — completed, -1/-2 — cancelled at different stages.

Currency and Conversion

Payme only accepts Uzbek soums. Pass the amount in tiyin (1 UZS = 100 tiyin). If store prices are in USD or EUR, conversion at the CBU exchange rate is required (recommended to cache for 1 hour):

$amountTiyin = (int)round($orderPriceUsd * $uzsPerUsd * 100);

Testing

Test environment: https://checkout.test.paycom.uz/. Test keys are issued separately from live keys in the developer cabinet. Test all 6 methods, especially CancelTransaction at different stages — depending on whether PerformTransaction has already been executed, different state values (-1 or -2) are returned.

Timeline

Task Duration
JSON-RPC server development (all 6 methods) 2–3 days
Integration with the Bitrix Sale module 1–2 days
Testing and submission to Payme for review 1–2 days
Store registration in Payme 3–10 business days