Skip to content

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 с text SMS или сам текст SMS. Оно может содержать amount, если пересыльщик сам читает сумму, и card, если один телефон получает SMS по нескольким картам.
  • Shop читает зачисление из привычных форматов, включая персидские цифры, и никогда — из баланса, даты, времени или маскированного номера счёта. Списание ничего не подтверждает.
  • Единиц в SMS на единицу у карты переводит единицу банка в валюту карты: банки считают в риалах, поэтому для карты в туманах сумма из SMS делится на 10. Поставьте 1, если SMS вашего банка считают в туманах.
  • Одно и то же SMS, пересланное дважды, проводит платёж один раз. SMS, чью сумму не просит ни один открытый платёж, сохраняется, чтобы вы на него посмотрели.
  • SMS на сумму платежа, чья квитанция была отклонена, ничего не проводит: квитанция возвращается в Квитанции, чтобы её посмотрел человек.
  • Если секрет утёк, нажмите Создать новый секрет; старый сразу перестаёт действовать.

SMS без подписи

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

Что отвечает Shop:

СтатусЗначение
2xxПринято. На то же SMS, заново подписанное после проведения, отвечает 200 как на уже полученное.
401Подпись неверна или расходится с часами Shop больше чем на пять минут. Проверьте часы отправителя и подпишите заново.
409Тот же самый подписанный запрос после проведения платежа: считайте его выполненным.
429С этого адреса пришло двадцать отклонённых запросов за десять минут. Подождите.
503Shop не смог сохранить или провести его прямо сейчас. Отправьте снова, подписав текущим временем.
400Тело — не SMS. Повторная отправка ничего не изменит.

Общий HTTP-шлюз ​

Шлюз в Shop — это четыре адреса на вашей стороне, обычно небольшой посредник перед выбранным вами платёжным сервисом, и общий секрет:

АдресОбязателенЧто с ним делает Shop
СозданиедаЗапрашивает страницу оплаты.
ПроверкаСпрашивает, был ли платёж выполнен.
ВозвратПросит вернуть деньги.
СостояниеПроверяет, может ли шлюз принимать деньги.

У каждого шлюза свои валюты и переключатель вкл./выкл. Если секрет не указан, Shop создаёт его сам. Публичный адрес Shop должен быть задан, потому что шлюз возвращает туда клиента (Shop: портал для клиентов).

Подпись ​

Каждый запрос в обе стороны и каждый вызов Shop к вашему сервису подписываются одинаково: заголовок X-Nexora-Timestamp (секунды Unix) и X-Nexora-Signature — HMAC-SHA256 в шестнадцатеричном виде от <timestamp>.<raw body> с секретом. Проверяйте подпись Shop в своём посреднике. Shop отклоняет обратный вызов с неверной подписью или с меткой времени, отличающейся от его часов больше чем на пять минут.

bash
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"

Создание ​

http
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.

Обратный вызов ​

http
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 — после двадцати неверно подписанных обратных вызовов за десять минут с одного адреса.

Проверка ​

http
POST <verify address>
{"checkout": "ck_9f2…", "reference": "<your id>", "amount": 150000, "currency": "IRT"}

Ответьте в том же формате, что и обратный вызов. Shop спрашивает, когда клиент возвращается, когда приходит обратный вызов, каждые несколько минут в течение трёх часов — о платеже, чей обратный вызов так и не пришёл, и каждые четверть часа в течение суток — о платеже, закрытом на стороне Shop, чтобы деньги, всё же оплаченные в шлюзе, дошли до кошелька клиента. Деньги, оплаченные не в той валюте, которую просили, ждут вашего подтверждения.

Возврат ​

http
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 минут) или когда вы его выбрали.

Курс делает две вещи:

  • Товар с ценой только в другой валюте продаётся в валюте магазина по курсу дня, с округлением вверх до заданного вами шага.
  • Шлюз, который принимает только другую валюту, предлагается и для валюты магазина. Платёж запрашивает пересчитанную сумму и зачисляет в кошелёк причитающуюся сумму по курсу, зафиксированному при открытии платежа. Такой платёж возвращается вручную.

Текст и изображения — CC BY 4.0.