Shop: платежи
Nexora Shop поставляется без платёжного шлюза. В нём есть способы принимать деньги, которые вовсе не шлюзы (перевод с карты на карту, подтверждённый человеком по квитанции клиента или SMS вашего банка), и одна общая возможность: общий HTTP-шлюз — небольшой JSON-контракт, который можно направить на любого выбранного вами посредника. Какой это посредник и можно ли вам им пользоваться — ваше решение и ваша ответственность.
Всё это настраивается в разделе Способы оплаты админки Shop.
Деньги и кошелёк
Каждая сумма — целое число в наименьшей единице валюты рядом с кодом валюты. Деньги, которые платит клиент, сначала попадают в его кошелёк и уже оттуда оплачивают заказ. Если заплатить немного больше, остаток останется в кошельке, а заказ, который кошелёк уже покрывает, вообще не требует оплаты.
Перевод с карты на карту
Добавьте свои карты (номер, банк, владелец, валюта). Карты одной валюты используются по очереди: каждому платежу даётся карта, которая дольше всех не использовалась.
Каждый платёж картой просит у клиента уникальную сумму: цену плюс несколько единиц, которых не просит ни один другой открытый платёж в этой валюте. Лишние единицы остаются в кошельке клиента. По уникальной сумме перевод сопоставляется со своим платежом.
- По умолчанию платёж открыт 60 минут, но его сумма удерживается сутки, поэтому опоздавший перевод всё равно попадает на свой платёж.
- Клиенту, который снова просит оплатить тот же заказ, показывается уже открытый платёж.
- По умолчанию один клиент открывает не больше 10 платежей картой в день, чтобы брошенные нажатия или скрипт не могли занять все суммы рядом с ценой.
Платёж картой подтверждается одним из двух способов.
Человеком, по квитанции
Клиент отправляет последние четыре цифры карты, с которой платил, и фото или PDF квитанции либо номер отслеживания банка. Квитанция ждёт в разделе Квитанции админки Shop и, когда бот настроен, в теме форума группы ваших администраторов в Telegram с кнопками одобрения и отклонения.
- Решать по квитанции могут только пользователи Telegram, которых вы указали как проверяющих.
- Квитанция, решённая где угодно (в админке, в Telegram или по SMS банка), отмечается в своём сообщении. Двойное одобрение или одобрение из двух мест одновременно оплачивает один раз.
- Уже использованный номер отслеживания отклоняется, как и повторная та же квитанция, даже если первая была отклонена.
- Изображение, которое только похоже на прежнюю квитанцию, пропускается, но помечается платежом, на который оно похоже. У квитанций одного банка общий макет, поэтому сравнивать вам.
По SMS вашего банка
Пересылайте SMS своего банка с телефона в Shop. Сумма зачисления подтверждает тот единственный платёж, который просил ровно эту сумму, без участия человека. Пересыльщик отправляет каждое SMS на https://<shop>/pay/hook/card, подписывая его Секретом, показанным в блоке «SMS вашего банка» раздела Способы оплаты (ниже).
- Тело — JSON с
textSMS или сам текст SMS. Оно может содержатьamount, если пересыльщик сам читает сумму, иcard, если один телефон получает SMS по нескольким картам. - Shop читает зачисление из привычных форматов, включая персидские цифры, и никогда — из баланса, даты, времени или маскированного номера счёта. Списание ничего не подтверждает.
- Единиц в SMS на единицу у карты переводит единицу банка в валюту карты: банки считают в риалах, поэтому для карты в туманах сумма из SMS делится на 10. Поставьте 1, если SMS вашего банка считают в туманах.
- Одно и то же SMS, пересланное дважды, проводит платёж один раз. SMS, чью сумму не просит ни один открытый платёж, сохраняется, чтобы вы на него посмотрели.
- SMS на сумму платежа, чья квитанция была отклонена, ничего не проводит: квитанция возвращается в Квитанции, чтобы её посмотрел человек.
- Если секрет утёк, нажмите Создать новый секрет; старый сразу перестаёт действовать.
SMS без подписи
Пересыльщик, который не умеет подписывать, может отправлять сам секрет, но только после того, как вы включите Принимать также SMS с секретом вместо подписи. Любой, кто увидит такой запрос, может подделать зачисление, поэтому держите это выключенным, если пересыльщику это не нужно.
Что отвечает Shop:
| Статус | Значение |
|---|---|
2xx | Принято. На то же SMS, заново подписанное после проведения, отвечает 200 как на уже полученное. |
401 | Подпись неверна или расходится с часами Shop больше чем на пять минут. Проверьте часы отправителя и подпишите заново. |
409 | Тот же самый подписанный запрос после проведения платежа: считайте его выполненным. |
429 | С этого адреса пришло двадцать отклонённых запросов за десять минут. Подождите. |
503 | Shop не смог сохранить или провести его прямо сейчас. Отправьте снова, подписав текущим временем. |
400 | Тело — не SMS. Повторная отправка ничего не изменит. |
Общий HTTP-шлюз
Шлюз в Shop — это четыре адреса на вашей стороне, обычно небольшой посредник перед выбранным вами платёжным сервисом, и общий секрет:
| Адрес | Обязателен | Что с ним делает Shop |
|---|---|---|
| Создание | да | Запрашивает страницу оплаты. |
| Проверка | Спрашивает, был ли платёж выполнен. | |
| Возврат | Просит вернуть деньги. | |
| Состояние | Проверяет, может ли шлюз принимать деньги. |
У каждого шлюза свои валюты и переключатель вкл./выкл. Если секрет не указан, Shop создаёт его сам. Публичный адрес Shop должен быть задан, потому что шлюз возвращает туда клиента (Shop: портал для клиентов).
Подпись
Каждый запрос в обе стороны и каждый вызов Shop к вашему сервису подписываются одинаково: заголовок X-Nexora-Timestamp (секунды Unix) и X-Nexora-Signature — HMAC-SHA256 в шестнадцатеричном виде от <timestamp>.<raw body> с секретом. Проверяйте подпись Shop в своём посреднике. Shop отклоняет обратный вызов с неверной подписью или с меткой времени, отличающейся от его часов больше чем на пять минут.
TS=$(date +%s)
BODY='{"text":"..."}'
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl -X POST https://shop.example.com/pay/hook/card \
-H "X-Nexora-Timestamp: $TS" -H "X-Nexora-Signature: $SIG" \
-H 'Content-Type: application/json' --data "$BODY"Создание
POST <create address>
{"checkout": "ck_9f2…", "amount": 150000, "currency": "IRT",
"description": "order 12", "language": "en",
"callbackUrl": "https://<shop>/pay/hook/gateway-1",
"returnUrl": "https://<shop>/pay/return/ck_9f2…"}Ответьте 200 с {"payUrl": "https://…", "reference": "<your id>"}. Shop отправляет клиента на payUrl. Когда клиент закончит, верните его на returnUrl; тогда Shop обратится к verify и покажет, что узнал. language — язык самого клиента: fa, en, ru или zh.
Обратный вызов
POST <callbackUrl>
{"checkout": "ck_9f2…", "reference": "<your id>", "status": "paid",
"amount": 150000, "currency": "IRT"}status — paid, pending или failed. Если у шлюза есть адрес проверки, обратный вызов — лишь подсказка: перед проведением Shop обращается к verify. Повтор обратного вызова безопасен; платёж оплачивается один раз. Коды ответа Shop значат то же, что и для SMS банка выше: 409 — платёж уже проведён (перестаньте его отправлять), 503 — отправьте снова, подписав текущим временем, 400 — тело не соответствует контракту, 429 — после двадцати неверно подписанных обратных вызовов за десять минут с одного адреса.
Проверка
POST <verify address>
{"checkout": "ck_9f2…", "reference": "<your id>", "amount": 150000, "currency": "IRT"}Ответьте в том же формате, что и обратный вызов. Shop спрашивает, когда клиент возвращается, когда приходит обратный вызов, каждые несколько минут в течение трёх часов — о платеже, чей обратный вызов так и не пришёл, и каждые четверть часа в течение суток — о платеже, закрытом на стороне Shop, чтобы деньги, всё же оплаченные в шлюзе, дошли до кошелька клиента. Деньги, оплаченные не в той валюте, которую просили, ждут вашего подтверждения.
Возврат
POST <refund address>
Idempotency-Key: shop-refund-7-3f9a…
{"checkout": "ck_9f2…", "reference": "<your id>", "amount": 50000,
"currency": "IRT", "idempotencyKey": "shop-refund-7-3f9a…"}Shop снимает сумму с кошелька клиента до запроса и называет каждый возврат ключом. Ваш посредник делает один возврат на ключ: на повторный запрос с тем же ключом отвечают так же, как в первый раз, и никогда не возвращают деньги дважды.
| Ваш ответ | Что делает Shop |
|---|---|
2xx | Возврат выполнен. |
4xx с {"refused": true, "reason": "…"} | По этому ключу ничего не возвращено и не будет возвращено. Деньги возвращаются в кошелёк, а администратор видит причину. Отправляйте такой ответ, только когда это точно. |
| Что угодно другое | Ничего не известно наверняка. Возврат остаётся в пути, вне кошелька, и администратор может запросить его снова с тем же ключом. |
Без адреса возврата верните деньги клиенту вручную кнопкой Выплатить на его странице в разделе Клиенты.
Состояние
health — это GET, который отвечает 2xx, пока шлюз может принимать деньги. Shop спрашивает раз в минуту; платёж, который не удалось открыть, считается так же. После трёх ошибок подряд шлюз считается неработающим: клиентам предлагаются другие способы оплаты, а группа ваших администраторов в Telegram и обзор получают об этом сообщение. Первый удачный ответ возвращает шлюз. Шлюз без адреса проверки состояния пробуется снова через пятнадцать минут после отказа.
Курсы валют
Курс говорит, сколько единиц одной валюты стоит единица другой. Задаётся в Способы оплаты → Курсы валют. Источник — любой JSON-адрес по вашему выбору (Shop никакой не называет), с путём к числу в его ответе, например data.price, и множителем («Умножить на»). Shop опрашивает источник каждые десять минут. Ручной курс используется, когда источник не ответил в пределах срока действия (по умолчанию 60 минут) или когда вы его выбрали.
Курс делает две вещи:
- Товар с ценой только в другой валюте продаётся в валюте магазина по курсу дня, с округлением вверх до заданного вами шага.
- Шлюз, который принимает только другую валюту, предлагается и для валюты магазина. Платёж запрашивает пересчитанную сумму и зачисляет в кошелёк причитающуюся сумму по курсу, зафиксированному при открытии платежа. Такой платёж возвращается вручную.
