Перейти к содержанию

PPMK — Подписки

Вычисление MD5-подписи

MD5-подпись вычисляется от строки, полученной конкатенацией значений входных параметров (ключи сортируются в алфавитном порядке) и секретного ключа сервиса.

Пример: ключ k, параметры aaaa=2, zzzz=1 → строка 21kmd5("21k").

# Python (как в ppmk/mk/views.py)
import hashlib, collections

def do_sign(data: dict, key: str) -> str:
    od = collections.OrderedDict(sorted(data.items()))
    s = ""
    for v in od.values():
        if v:
            s += str(v)
    return hashlib.md5(s.encode("utf8") + str(key).encode("utf8")).hexdigest()

SERVICE_KEY = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Параметр подписи в запросах называется sign.

API (ТСП → ПП)

Инициализация подписки

POST http://subscribe.dmapi.ru/subscribe/init

Параметр Обяз. Описание
phone да Номер абонента для обычных подписок или произвольный идентификатор (email, логин и т.п.) для СБП
eid нет Внешний ID
operator да mts, beeline, megafon, tele2, bee, mts2, mts_2, mts3, mts4, sbp, yota
service да ID сервиса (integer)
sign да MD5-подпись
custom_url нет Кастомный URL (для отдельных схем)

Ответ (JSON):

{
  "ok": true,
  "id": 12345,
  "eid": "external-id",
  "redirect": null,
  "message": "..."
}

ok: true — запрос принят. Дополнительные поля зависят от оператора и схемы.

Пример (Python):

url = "http://subscribe.dmapi.ru/subscribe/init"
data = dict(
    service=100,
    phone="79031234567",
    operator="sbp",
    eid="order-123",
)
data["sign"] = do_sign(data, SERVICE_KEY)
r = requests.post(url, data)
print(r.status_code, r.text)

Подтверждение подписки кодом

POST http://subscribe.dmapi.ru/subscribe/confirm

Параметр Описание
id ID подписки из ответа init
code Код подтверждения от абонента
sign MD5-подпись

Ответ:

{ "ok": true, "message": "ok message" }

Пример (Python):

url = "http://subscribe.dmapi.ru/subscribe/confirm"
data = dict(id=12345, code="1234")
data["sign"] = do_sign(data, SERVICE_KEY)
r = requests.post(url, data)
print(r.status_code, r.text)

Остановка подписки

POST http://subscribe.dmapi.ru/subscribe/stop

Параметр Описание
service ID сервиса (обязательный)
id ID подписки — не обязательный параметр
phone телефон — не обязательный параметр
eid eid — не обязательный параметр
sign MD5-подпись

Ответ — JSON-массив:

[{ "ok": true, "id": 1, "eid": "..." }]

Пример (Python):

url = "http://subscribe.dmapi.ru/subscribe/stop"
data = dict(service=100, id=12345)
data["sign"] = do_sign(data, SERVICE_KEY)
r = requests.post(url, data)
print(r.status_code, r.text)

Инициализация внеочередного платежа

POST http://subscribe.dmapi.ru/payment/init

Параметр Описание
subscribe ID подписки
sign MD5-подпись

Ответ:

{ "ok": true, "payment_uid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }

Повторный запрос в тот же день вернёт {"ok": false, "error": "dobule payment"}.

Пример (Python):

url = "http://subscribe.dmapi.ru/payment/init"
data = dict(subscribe=12345)
data["sign"] = do_sign(data, SERVICE_KEY)
r = requests.post(url, data)
print(r.status_code, r.text)
# {"ok": true, "payment_uid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}

Возврат платежа (СБП)

POST http://subscribe.dmapi.ru/payment/refund

Алиас: /subscribe/refund. Возврат выполняется для успешного СБП-списания по подписке.

Параметр Обяз. Описание
subscribe да ID подписки (integer)
payment да ID списания: integer PK (pay из нотификации payment_status) или UUID (payment_uid из ответа /payment/init / pay_uid из нотификации)
sign да MD5-подпись

Ответ — успех:

{ "ok": true, "refund_id": 1 }

При ошибке шлюза: {"ok": false, "refund_id": 1, "message": "..."}.

Независимо от синхронного ответа по факту завершения возврата ТСП отправляется калбек refund_status (см. Уведомление о возврате).

Ошибки валидации запроса (HTTP 420):

Ответ Когда
{"ok": false, "message": "no subscribe"} Не передан subscribe
{"ok": false, "message": "no payment"} Не передан payment
{"ok": false, "message": "wrong payment"} Списание не найдено у этой подписки (неверный PK или UUID)
{"ok": false, "message": "no sign"} Не передан sign
{"ok": false, "message": "wrong sign"} Неверная MD5-подпись

Ошибки бизнес-логики (HTTP 200):

Ответ Когда
{"ok": false, "error": "refund not supported"} Оператор подписки не СБП
{"ok": false, "error": "payment not refundable"} Списание ещё не успешно (status != ok)
{"ok": false, "error": "already_refunded"} По этому списанию уже есть возврат

Пример (Python):

url = "http://subscribe.dmapi.ru/payment/refund"

# вариант 1: integer PK списания (поле pay из нотификации)
data = dict(subscribe=12345, payment=67890)
data["sign"] = do_sign(data, SERVICE_KEY)
r = requests.post(url, data)
print(r.status_code, r.text)

# вариант 2: UUID списания (payment_uid / pay_uid)
data = dict(
    subscribe=12345,
    payment="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
)
data["sign"] = do_sign(data, SERVICE_KEY)
r = requests.post(url, data)
print(r.status_code, r.text)

Нотификации (ПП → ТСП)

Уведомления отправляются POST-запросами на URL, указанные при подключении: notification_url / notification_url2 / notification_url3. Нотификация рассылается на все настроенные для сервиса URL; каждая копия получает собственный UUID (используется нотификатором для дедупликации).

Тело запроса — POST form-data (application/x-www-form-urlencoded), не JSON; все значения передаются строками. Для каждой нотификации приведены два примера: JSON — для наглядности, и urlencoded — фактически отправляемое тело.

Уведомление о статусе подписки — subscribe_status

При изменении статуса подписки (new, active, stop) платформа отправляет POST-запрос.

Поле Тип Описание
action string Всегда subscribe_status
state string new — новая, не активирована; active — активная; stop — остановлена
operator string mts, beeline, megafon, tele2, bee, mts2, mts3, mts4, sbp, yota
id int ID подписки
eid string Внешний ID подписки (до 256 символов)
phone string Номер в формате 7xxxxxxxxxx
service_id int ID сервиса
check string(32) MD5-подпись (конкатенация значений полей по алф. порядку + секретный ключ)

Пример:

{
  "action": "subscribe_status",
  "id": "12345",
  "eid": "order-123",
  "service_id": "100",
  "state": "active",
  "operator": "sbp",
  "phone": "79031234567",
  "check": "0123456789abcdef0123456789abcdef"
}
urlencoded (фактически отправляемое тело):

action=subscribe_status&id=12345&eid=order-123&service_id=100&state=active&operator=sbp&phone=79031234567&check=0123456789abcdef0123456789abcdef

Уведомление о списании — payment_status

При успешном или неуспешном списании отправляется POST-уведомление:

Поле Значения Комментарий
action payment_status Тип уведомления
date ISO 8601 Дата по расписанию
date_actual ISO 8601 Дата фактического списания
eid string Внешний ID
sub int ID подписки
service int ID сервиса
pay int ID расписания
pay_uid UUID UUID списания
message string Сообщение от оператора (при ошибке)
status payed / stop / fail payed — успех; stop — подписка остановлена; fail — платёж не прошёл
money_abonent decimal Сумма абонента, руб. (2 знака)
money_partner decimal Сумма ТСП, руб. (2 знака)
operator string Оператор связи
check string(32) MD5-подпись

Пример — успешное списание:

{
  "action": "payment_status",
  "date": "2026-09-15 10:00:00+00:00",
  "date_actual": "2026-09-15T10:00:05.123456+00:00",
  "eid": "order-123",
  "status": "payed",
  "sub": "12345",
  "service": "100",
  "pay": "67890",
  "pay_uid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "message": "",
  "money_abonent": "99.00",
  "money_partner": "85.00",
  "operator": "sbp",
  "check": "0123456789abcdef0123456789abcdef"
}
urlencoded (фактически отправляемое тело):

action=payment_status&date=2026-09-15+10%3A00%3A00%2B00%3A00&date_actual=2026-09-15T10%3A00%3A05.123456%2B00%3A00&eid=order-123&status=payed&sub=12345&service=100&pay=67890&pay_uid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&message=&money_abonent=99.00&money_partner=85.00&operator=sbp&check=0123456789abcdef0123456789abcdef

Пример — неуспешное списание (status: fail, в message — сообщение оператора):

{
  "action": "payment_status",
  "date": "2026-09-15 10:00:00+00:00",
  "date_actual": "2026-09-15T10:00:05.123456+00:00",
  "eid": "order-123",
  "status": "fail",
  "sub": "12345",
  "service": "100",
  "pay": "67890",
  "pay_uid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "message": "insufficient funds",
  "money_abonent": "0",
  "money_partner": "0",
  "operator": "sbp",
  "check": "0123456789abcdef0123456789abcdef"
}
urlencoded (фактически отправляемое тело):

action=payment_status&date=2026-09-15+10%3A00%3A00%2B00%3A00&date_actual=2026-09-15T10%3A00%3A05.123456%2B00%3A00&eid=order-123&status=fail&sub=12345&service=100&pay=67890&pay_uid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&message=insufficient+funds&money_abonent=0&money_partner=0&operator=sbp&check=0123456789abcdef0123456789abcdef

Уведомление о возврате — refund_status

После синхронного ответа шлюза на возврат СБП платформа отправляет POST на notification_url / notification_url2 / notification_url3 (как для payment_status).

Калбек отправляется и при успешном, и при неуспешном возврате — различие только в значении поля status (done / failed).

Когда отправляется:

  • по завершении запроса POST /payment/refund — сразу после синхронного ответа СБП-шлюза start_refund;
  • после возврата, выполненного из интерфейса поддержки ППМК.
Поле Значения Комментарий
action refund_status Тип уведомления
date ISO 8601 Дата исходного списания
date_actual ISO 8601 Время нотификации о возврате
eid string Внешний ID подписки
sub int ID подписки
service int ID сервиса
pay int ID списания
pay_uid UUID UUID списания
refund_id int ID возврата в ППМК
status done / failed done — возврат принят шлюзом; failed — ошибка (шлюз недоступен или отклонил возврат)
message string Всегда пустая строка; текст ошибки шлюза возвращается только в синхронном ответе /payment/refund
money_abonent decimal Сумма возврата (абонент), руб.
money_partner decimal Сумма ТСП, руб.
operator string Оператор (sbp)
check string(32) MD5-подпись

Поле money_abonent — сумма возврата абоненту (равна сумме возвращаемого списания). Подпись check вычисляется так же, как для остальных нотификаций: конкатенация значений полей, отсортированных по ключу в алфавитном порядке (значения None пропускаются), + секретный ключ сервиса, от неё берётся MD5.

Пример — успешный возврат (status=done)

{
  "action": "refund_status",
  "date": "2026-09-15 10:00:00+00:00",
  "date_actual": "2026-09-15T12:30:05.123456+00:00",
  "eid": "order-123",
  "status": "done",
  "sub": "12345",
  "service": "100",
  "pay": "67890",
  "pay_uid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "refund_id": "1",
  "message": "",
  "money_abonent": "99.00",
  "money_partner": "85.00",
  "operator": "sbp",
  "check": "0123456789abcdef0123456789abcdef"
}
urlencoded (фактически отправляемое тело):

action=refund_status&date=2026-09-15+10%3A00%3A00%2B00%3A00&date_actual=2026-09-15T12%3A30%3A05.123456%2B00%3A00&eid=order-123&status=done&sub=12345&service=100&pay=67890&pay_uid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&refund_id=1&message=&money_abonent=99.00&money_partner=85.00&operator=sbp&check=0123456789abcdef0123456789abcdef

Пример — неуспешный возврат (status=failed)

{
  "action": "refund_status",
  "date": "2026-09-15 10:00:00+00:00",
  "date_actual": "2026-09-15T12:30:05.123456+00:00",
  "eid": "order-123",
  "status": "failed",
  "sub": "12345",
  "service": "100",
  "pay": "67890",
  "pay_uid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "refund_id": "1",
  "message": "",
  "money_abonent": "99.00",
  "money_partner": "85.00",
  "operator": "sbp",
  "check": "0123456789abcdef0123456789abcdef"
}
urlencoded (фактически отправляемое тело):

action=refund_status&date=2026-09-15+10%3A00%3A00%2B00%3A00&date_actual=2026-09-15T12%3A30%3A05.123456%2B00%3A00&eid=order-123&status=failed&sub=12345&service=100&pay=67890&pay_uid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&refund_id=1&message=&money_abonent=99.00&money_partner=85.00&operator=sbp&check=0123456789abcdef0123456789abcdef

Внимание!

Нотификация refund_status со статусом done означает, что возврат принят шлюзом к исполнению. Итоговый результат зачисления определяется оператором СБП.