PPMK — Подписки
Вычисление MD5-подписи
MD5-подпись вычисляется от строки, полученной конкатенацией значений входных параметров (ключи сортируются в алфавитном порядке) и секретного ключа сервиса.
Пример: ключ k, параметры aaaa=2, zzzz=1 → строка 21k → md5("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"
}
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"
}
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"
}
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"
}
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"
}
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 означает, что возврат принят шлюзом к исполнению.
Итоговый результат зачисления определяется оператором СБП.