Общая информация

В системе появилась возможность принимать платежи только через одну копию биллинга, проверять и зачислять баланс на вторую и последующие копии системы.

Включение

$conf{MULTI_BILLING_SYNC} = 1;

После включения появляются новые меню:

Настройка>Финансы>Мульти-биллинг

Используется для настройки сторонних биллингов, где ищем абонента или дублируем платёж

ПолеОписание
НазваниеНазвание стороннего сервиса, куда отправляем копию платежей и/или ищем абонента
Ссылка на сторонний биллингУказывам ссылку, по которой принимаются платежи в сторонней системе, в случае с "АСР Казна-30" - стандартная ссылка приёма платежей
СинхронизацияДублирование платежи на копию биллинга (использовать только для полной копии системы)
Проверка пользователя (Тсходящая)Поиск пользователя в сторонней системе, если оне не найден в основной
ФискализацияФискализировать данный платёж на основном биллинге
АктивноСостояние сторонней системы, если стоит галочка "Активно", то использовать эту систему в поиске, иначе пропустить
ГруппыГруппы абонентов, по которым будет происходить синхронизация с полной копией системы
Признак поиска поискаПо какому признаку искать абонента в сторонней системе (LOGIN, CONTRACT_ID, UID)
ПриоритетПриоритет поиска, во время проведения платежа

Отчёт>Финансы>Отчет по кросс-биллингу

Отчёт по финансам, выводят все платежи, которые были отправлены в другую биллинговую систему, или в любую другую систему, которая поддерживает протокол OSMP с определёнными параметрами

 

Настройки модуля для приема платежей на второй и последующих системах АСР "Казна-39"

Дублирование и перенос платежей делается с помощью модуля Osmp.pm.

  1. Добавить в платёжных системах модуль Synchron.pm - "Синхронизация"
  2. Указать IP адрес основного биллинга, с которого будут приходить платежи
  3. Указать тип платежа
  4. Добавить контрагентов, нужно изменить параметр PAYSYS_SYNCHRON_ACCOUNT_KEY, идентификатор нужно сделать такой же как в платежных системах основного биллинга.
  5. Обязательно включить добавленную систему в группу default



Эти действия надо производить НА ВТОРОМ И ПОСЛЕДУЮЩЕМ БИЛЛИНГЕ, НА ОСНОВНОМ ДЕЛАТЬ НЕ НАДО!

Описание взаимосвязи с "АСР Казна-39"

Полная инструкция по интеграции с платёжным шлюзом (Протокол Synchron)

Данная инструкция описывает протокол взаимодействия внешней платёжной системы с биллинговой системой через модуль `Synchron`. Реализация данного протокола позволит вам создать платёжный модуль на любом языке программирования (PHP, Python, Go, Java и др.), который будет восприниматься биллинговой системой как нативный.

1. Общие сведения

  • Протокол передачи данных: HTTP/HTTPS
  • Метод передачи параметров: GET или POST
  • Формат ответа: XML
  • Кодировка: UTF-8

Внешняя система отправляет HTTP-запросы на URL обработчика (обычно `.../paysys_check.cgi`).
Все ответы возвращаются в формате XML с обязательным полем `<result>`, указывающим код результата.

Структура XML-ответа

<?xml version="1.0" encoding="UTF-8"?>
<response>
    <result>КОД_РЕЗУЛЬТАТА</result>
    <comment>ОПИСАНИЕ_РЕЗУЛЬТАТА</comment>
    <!-- Дополнительные поля -->
</response>

2. Коды возврата (Result Codes)

В поле `<result>` возвращается числовой код состояния операции.

КодОписание (Русский)Описание (English)Примечание
0УспешноSuccessОперация выполнена успешно
1Временная ошибка БДTemporary DB errorПовторите запрос позже
4Неверный формат идентификатораWrong client identifierОшибка валидации ID абонента
5Пользователь не найденUser not existАбонент с таким ID не существует
6Неизвестный терминалUnknown terminalОшибка идентификации терминала
7Прием платежей запрещенPayments denyПлатежи для данного абонента запрещены
8Дубликат запросаDouble requestТранзакция с таким ID уже обрабатывается
9Ошибка проверки подписи/ключаKey Info mismatchНеверный пароль или подпись (если используется)
79Счёт абонента не активенAccount not activeАбонент отключен или удален
300Неизвестная ошибкаUnknown errorВнутренняя ошибка сервера

3. Описание команд (Commands)

Тип операции определяется параметром `command`.

3.1. Проверка абонента (CHECK)

Используется для проверки существования абонента перед проведением платежа.

Параметры запроса:

Параметр Обязательность Описание
commandДаЗначение: `check`
accountДаИдентификатор абонента (UID, Логин или Договор, в зависимости от настроек)
sumНетСумма платежа (для проверки ограничений по сумме)
txn_idДаУникальный ID транзакции во внешней системе
testНетФлаг тестового режима (1 - тест)

Пример запроса:

https://billing.example.com/paysys_check.cgi?command=check&account=user123&txn_id=10001&sum=100

Пример ответа (Успех):

<?xml version="1.0" encoding="UTF-8"?>
<response>
    <result>0</result>
    <fio>Иванов Иван Иванович</fio>
    <balance>150.00</balance>
    <address>ул. Ленина, д. 10, кв. 5</address>
    <phone>+79001234567</phone>
    <txn_id>10001</txn_id>
    <comment>Success</comment>
</response>

Пример ответа (Ошибка - Абонент не найден):

<?xml version="1.0" encoding="UTF-8"?>
<response>
    <result>5</result>
    <comment>User not exist</comment>
</response>
3.2. Проведение платежа (PAY)

Используется для зачисления средств на счет абонента.

Параметры запроса:

Параметр Обязательность Описание
commandДаЗначение: `pay`
accountДаИдентификатор абонента
sumДаСумма платежа (разделитель - точка)
txn_idДаУникальный ID транзакции во внешней системе
txn_dateНетДата транзакции в формате `YYYYMMDDHHMMSS` (например `20231025143000`)
servicetypeНетТип услуги (1=Домофон, 2=Интернет, 3=Телефон, 4=ТВ). Используется для описания платежа.
testНетФлаг тестового режима (1 - тест)

Пример запроса:

https://billing.example.com/paysys_check.cgi?command=pay&account=user123&txn_id=10001&sum=100.00

Пример ответа (Успех):

<?xml version="1.0" encoding="UTF-8"?>
<response>
    <result>0</result>
    <txn_id>10001</txn_id>
    <prv_txn>555888</prv_txn> <!-- Внутренний ID платежа в биллинге -->
    <sum>100.00</sum>
    <comment>Success</comment>
</response>
Если платеж с таким `txn_id` уже был успешно проведен ранее, система вернет код `0` (Успех) и параметры существующего платежа (идемпотентность).
3.3. Отмена платежа (CANCEL)

Используется для отмены ранее проведенного платежа.

Параметры запроса:

Параметр Обязательность Описание
commandДаЗначение: `cancel`
prv_txnДаВнутренний ID платежа в биллинге (полученный в ответе на `pay`)
txn_idНетID транзакции внешней системы (для логов)

Пример запроса:

https://billing.example.com/paysys_check.cgi?command=cancel&prv_txn=555888

Пример ответа (Успех):

<?xml version="1.0" encoding="UTF-8"?>
<response>
    <result>0</result>
    <prv_txn>555888</prv_txn>
    <comment>Success</comment>
</response>
3.4. Проверка статуса транзакции (STATUS)

Используется для проверки, прошел ли платеж с указанным ID.

Параметры запроса:

Параметр Обязательность Описание
commandДаЗначение: `status`
txn_idДаID транзакции внешней системы

Пример запроса:

https://billing.example.com/paysys_check.cgi?command=status&txn_id=10001

Пример ответа (Найден):

<?xml version="1.0" encoding="UTF-8"?>
<response>
    <result>0</result>
    <txn_id>10001</txn_id>
    <comment>Success</comment>
</response>

Пример ответа (Не найден/Ошибка):

<?xml version="1.0" encoding="UTF-8"?>
<response>
    <result>1</result>
    <txn_id>10001</txn_id>
    <comment>Temporary DB error</comment> <!-- Или другой текст ошибки -->
</response>

4. Пример реализации (псевдокод)

Ниже приведен пример того, как ваша система должна формировать запросы.

4.1. Python
python
import requests
import xml.etree.ElementTree as ET
BILLING_URL = "https://billing.example.com/paysys_check.cgi"
def send_request(params):
    response = requests.get(BILLING_URL, params=params)
    response.encoding = 'utf-8'
    print(f"Request: {response.url}")
    print(f"Response: {response.text}")
    
    # Парсинг XML
    root = ET.fromstring(response.text)
    result_code = root.find('result').text
    return int(result_code), root
# 1. Проверка пользователя
params_check = {
    'command': 'check',
    'account': 'user123',
    'sum': '100.00',
    'txn_id': 'TXN_001'
}
code, xml = send_request(params_check)
if code == 0:
    print("Пользователь найден:", xml.find('fio').text)
    
    # 2. Проведение платежа
    params_pay = {
        'command': 'pay',
        'account': 'user123',
        'sum': '100.00',
        'txn_id': 'TXN_001',
        'txn_date': '20231025120000'
    }
    code, xml = send_request(params_pay)
    
    if code == 0:
        print("Платеж успешен. ID в биллинге:", xml.find('prv_txn').text)
    else:
        print("Ошибка платежа:", code)
else:
    print("Ошибка проверки пользователя:", code)
4.2. PHP
php
<?php

$billing_url = "https://billing.example.com/paysys_check.cgi";

function send_request($url, $params) {
    $query = http_build_query($params);
    $full_url = $url . '?' . $query;
    
    // Инициализация cURL
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $full_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Для тестов, в продакшене лучше включить
    
    $response = curl_exec($ch);
    
    if (curl_errno($ch)) {
        echo 'Error:' . curl_error($ch);
        return [300, null];
    }
    
    curl_close($ch);
    
    echo "Request: $full_url\n";
    echo "Response: $response\n";
    
    // Парсинг XML
    try {
        $xml = new SimpleXMLElement($response);
        return [(int)$xml->result, $xml];
    } catch (Exception $e) {
        echo "Error parsing XML\n";
        return [300, null];
    }
}

// 1. Проверка пользователя
$params_check = [
    'command' => 'check',
    'account' => 'user123',
    'sum'     => '100.00',
    'txn_id'  => 'TXN_001'
];

list($code, $xml) = send_request($billing_url, $params_check);

if ($code === 0) {
    echo "Пользователь найден: " . (string)$xml->fio . "\n";
    
    // 2. Проведение платежа
    $params_pay = [
        'command'  => 'pay',
        'account'  => 'user123',
        'sum'      => '100.00',
        'txn_id'   => 'TXN_001',
        'txn_date' => date('YmdHis')
    ];
    
    list($code_pay, $xml_pay) = send_request($billing_url, $params_pay);
    
    if ($code_pay === 0) {
        echo "Платеж успешен. ID в биллинге: " . (string)$xml_pay->prv_txn . "\n";
    } else {
        echo "Ошибка платежа: $code_pay\n";
    }
} else {
    echo "Ошибка проверки пользователя: $code\n";
}
?>


5. Обработка ошибок

  • Если вы получили код `0`, транзакция успешна.
  • Если вы получили код `1` (Временная ошибка), рекомендуется повторить запрос через некоторое время (например, через 1-5 минут).
  • Если вы получили коды `4`, `5`, `6`, `7`, `9`, `79`, повтор запроса **не требуется**, так как ошибка является постоянной (неверные данные или запрет).
  • Код `8` означает, что платеж уже обрабатывается или обработан. Следует проверить статус через команду `status`.

6. Важные замечания

  1. Уникальность `txn_id`: Каждая транзакция должна иметь уникальный ID в рамках вашей системы. Биллинг использует пару `SYSTEM_ID` + `txn_id` для предотвращения дублей.
  2. Формат суммы: Используйте точку как разделитель дробной части (например, `10.50`).
  3. Безопасность: Рекомендуется использовать HTTPS и ограничить доступ к скрипту `paysys_check.cgi` по IP-адресам ваших серверов.


Руководство по разработке сервера-эмулятора протокола Synchron

Данное руководство предназначено для разработчиков, желающих создать собственную платёжную систему или шлюз, который будет принимать запросы от основной биллинговой системы (ACP), имитируя поведение модуля `Synchron`.

В этом сценарии:

  • Основная биллинговая система (Client) отправляет HTTP/HTTPS запросы на ваш сервер.
  • Ваша система (Server) обрабатывает эти запросы и возвращает ответ в формате XML.

1. Общие требования

Протокол:** HTTP/HTTPS

  • Метод: GET (основной) или POST
  • Формат ответа: XML
  • Кодировка: UTF-8

Биллинг будет обращаться к вашему скрипту (например, `https://your-system.com/api/callback`), передавая параметры в строке запроса.


2. Обработка запросов (Server Side)

Ваш сервер должен обрабатывать параметр `command`, который определяет тип операции.

2.1. Проверка пользователя (command=check)

Биллинг запрашивает информацию о пользователе перед платежом.

Входящие параметры (GET):

  • `command`: `check`
  • `account`: Идентификатор пользователя (Логин, UID, Договор и т.д.)
  • `check_field`: (Опционально) Тип идентификатора (например, `UID`, `LOGIN`)
  • `sum`: Сумма платежа (для проверки лимитов)
  • `txn_id`: ID транзакции

Ожидаемый ответ (XML):

Успех (Пользователь найден):

<?xml version="1.0" encoding="UTF-8"?>
<response>
    <result>0</result>
    <fio>Иванов Иван Иванович</fio> <!-- ФИО абонента -->
    <balance>100.00</balance>      <!-- Текущий баланс -->
    <comment>OK</comment>
</response>

Ошибка (Пользователь не найден):

<response>
    <result>5</result>
    <comment>User not found</comment>
</response>
2.2. Проведение платежа (command=pay)

Биллинг уведомляет о поступлении платежа.

Входящие параметры (GET):

  • `command`: `pay`
  • `account`: Идентификатор пользователя
  • `sum`: Сумма платежа (например, `10.50`)
  • `txn_id`: ID транзакции в биллинге (External ID)
  • `txn_date`: Дата транзакции (опционально)

Ожидаемый ответ (XML):

Успех:

<?xml version="1.0" encoding="UTF-8"?>
<response>
    <result>0</result>
    <prv_txn>123456</prv_txn> <!-- ID платежа в ВАШЕЙ системе -->
    <sum>10.50</sum>
    <comment>Payment successful</comment>
</response>


Если платеж с таким `txn_id` уже был обработан, нужно вернуть `result=0` и данные старого платежа (идемпотентность).
2.3. Отмена платежа (command=cancel)

Входящие параметры:

  • `command`: `cancel`
  • `txn_id`: ID транзакции

Ожидаемый ответ:

<response>
    <result>0</result>
    <comment>Cancelled</comment>
</response>
2.4. Проверка статуса (command=status)

Входящие параметры:

  • `command`: `status`
  • `txn_id`: ID транзакции

Ожидаемый ответ:

<response>
    <result>0</result> <!-- 0 - проведен, другие коды - ошибки -->
    <txn_id>...</txn_id>
    <comment>OK</comment>
</response>

3. Коды результатов (Result Codes)

Ваша система должна возвращать следующие коды в теге `<result>`:

Параметры запроса:

Код Значение Описание
0OkУспешная операция / Пользователь найден
1Temp ErrorВременная ошибка (БД недоступна и т.п.)
5User Not FoundПользователь не найден
7ForbiddenПрием платежей запрещен
300Unknown ErrorПрочие ошибки

4. Примеры реализации сервера

Ниже приведены примеры скриптов, которые принимают запросы от биллинга и отвечают в нужном формате.

4.1. PHP (index.php)

Разместите этот скрипт на веб-сервере (например, Nginx/Apache).

php
<?php
header('Content-Type: text/xml; charset=utf-8');
// Получаем параметры
$command = $_GET['command'] ?? '';
$account = $_GET['account'] ?? '';
$txn_id  = $_GET['txn_id'] ?? '';
$sum     = $_GET['sum'] ?? 0;
// Генерация ответа
function response($code, $data = []) {
    echo '<?xml version="1.0" encoding="UTF-8"?>';
    echo '<response>';
    echo "<result>{$code}</result>";
    foreach ($data as $key => $val) {
        echo "<{$key}>" . htmlspecialchars($val) . "</{$key}>";
    }
    echo '</response>';
    exit;
}
// Логика обработки
try {
    switch ($command) {
        case 'check':
            // TODO: Проверка пользователя в вашей БД
            if ($account === 'test_user') {
                response(0, [
                    'fio' => 'Test User',
                    'balance' => '50.00',
                    'comment' => 'Found'
                ]);
            } else {
                response(5, ['comment' => 'User not found']);
            }
            break;
        case 'pay':
            // TODO: Зачисление средств в вашей системе
            // Проверка на дубликат по $txn_id!
            
            $my_payment_id = time(); // Генерируем внутренний ID
            
            response(0, [
                'prv_txn' => $my_payment_id,
                'sum' => $sum,
                'comment' => 'Payment accepted'
            ]);
            break;
            
        case 'status':
            // TODO: Проверка статуса платежа по $txn_id
            response(0, ['comment' => 'Payment exists']);
            break;
            
        case 'cancel':
            response(0, ['comment' => 'Cancelled']);
            break;
        default:
            response(300, ['comment' => 'Unknown command']);
    }
} catch (Exception $e) {
    response(1, ['comment' => 'Internal error']);
}
?>
4.2. Python (Flask)

Для запуска: `pip install flask` и `python server.py`

python
from flask import Flask, request, Response
app = Flask(__name__)
def make_xml(result_code, extra_fields=None):
    xml = '<?xml version="1.0" encoding="UTF-8"?>\n<response>\n'
    xml += f'    <result>{result_code}</result>\n'
    if extra_fields:
        for key, value in extra_fields.items():
            xml += f'    <{key}>{value}</{key}>\n'
    xml += '</response>'
    return Response(xml, mimetype='text/xml')
@app.route('/api/callback', methods=['GET'])
def handle_request():
    command = request.args.get('command')
    account = request.args.get('account')
    txn_id = request.args.get('txn_id')
    
    if command == 'check':
        # Эмуляция проверки пользователя
        if account == 'test_user':
            return make_xml(0, {
                'fio': 'Test User',
                'balance': '100.00',
                'comment': 'OK'
            })
        else:
            return make_xml(5, {'comment': 'User not found'})
    elif command == 'pay':
        # Эмуляция платежа
        # Здесь должна быть запись в БД
        return make_xml(0, {
            'prv_txn': '999999', # ID платежа в вашей системе
            'sum': request.args.get('sum'),
            'comment': 'Success'
        })
        
    elif command == 'status':
        return make_xml(0, {'comment': 'OK'})
        
    return make_xml(300, {'comment': 'Unknown command'})
if __name__ == '__main__':
    app.run(port=8080)
4.3. Node.js (Express)
javascript
const express = require('express');
const app = express();
app.get('/api/callback', (req, res) => {
    const { command, account, sum, txn_id } = req.query;
    
    res.set('Content-Type', 'text/xml');
    
    let responseBody = '';
    
    if (command === 'check') {
        if (account === 'test_user') {
            responseBody = `
                <result>0</result>
                <fio>Test User</fio>
                <balance>100.00</balance>
                <comment>OK</comment>
            `;
        } else {
            responseBody = `<result>5</result><comment>Not found</comment>`;
        }
    } else if (command === 'pay') {
        responseBody = `
            <result>0</result>
            <prv_txn>${Date.now()}</prv_txn>
            <sum>${sum}</sum>
            <comment>Success</comment>
        `;
    } else {
        responseBody = `<result>300</result><comment>Unknown command</comment>`;
    }
    
    res.send(`<?xml version="1.0" encoding="UTF-8"?><response>${responseBody}</response>`);
});
app.listen(3000, () => console.log('Server running on port 3000'));




  • Нет меток