Shop:支付
Nexora Shop 不自带任何支付网关。它自带的是那些根本不算网关的收款方式(卡对卡转账,由人根据客户的凭证确认,或由你银行的短信确认),以及一项通用能力:通用 HTTP 网关,一个小型 JSON 协议,你可以把它接到自己选择的任何中介上。选用哪个中介、是否可以使用它,由你决定,也由你负责。
这些都在 Shop 管理后台的 支付方式 中设置。
金额与钱包
每个金额都是该货币最小单位下的整数,并附带货币代码。客户付的钱先进入其 钱包,再从钱包支付订单。多付的部分留在钱包里,钱包余额已够支付的订单根本不需要再付款。
卡对卡转账
添加你的银行卡(卡号、银行、持卡人、货币)。同一货币的银行卡轮流使用:每笔付款分配给最久未使用的那张卡。
每笔卡对卡付款都会要求客户支付一个 唯一金额:价格加上几个单位,且该货币下没有其他未完成的付款要求同样的金额。多出的单位留在客户的钱包里。唯一金额就是把一笔转账与其付款对应起来的依据。
- 付款默认开启 60 分钟,但其金额会保留一天,因此迟到的转账仍会落在它自己的付款上。
- 客户为同一订单再次请求付款时,会看到已开启的那笔付款。
- 每位客户每天默认最多开启 10 笔卡对卡付款,使放弃的点击或脚本无法占满某个价格附近的所有金额。
卡对卡付款通过以下两种方式之一确认。
由人根据凭证确认
客户发送付款卡号的后四位,以及凭证的照片或 PDF,或银行的交易流水号。凭证会在 Shop 管理后台的 转账凭证 中等待处理;设置好机器人后,还会出现在管理员 Telegram 群组的一个论坛话题中,带有批准和拒绝按钮。
- 只有你列为审批人的 Telegram 用户可以处理凭证。
- 无论在哪里处理的凭证(管理后台、Telegram 还是银行短信),都会在其帖子上标明。同一凭证批准两次,或在两处同时批准,只会付款一次。
- 已经用过的流水号会被拒绝,同一张凭证再次提交也会被拒绝,即使第一次被拒绝过。
- 只是看起来像以前某张凭证的图片会放行,但会标出与之相似的付款。同一家银行的凭证版式相同,所以由你来比对。
由银行短信确认
把银行短信从手机转发给 Shop。入账金额会确认恰好要求该金额的那一笔付款,无需人工。转发器把每条短信发送到 https://<shop>/pay/hook/card,并用 支付方式 下显示的短信密钥签名(见下文)。
- 请求体是带短信
text的 JSON,或者直接是短信文本。如果转发器自己读出了金额,可以带上amount;一部手机接收多张卡的短信时,可以带上card。 - Shop 能从常见格式中读出入账金额(包括波斯语数字),绝不会从余额、日期、时间或掩码账号中读取。支出不会确认任何付款。
- 银行卡的 每单位对应的短信金额单位 把银行的单位换算成银行卡的货币:银行以里亚尔计数,因此以托曼计价的卡会把短信中的金额除以 10。如果你的银行短信以托曼计数,请设为 1。
- 同一条短信转发两次只入账一次。没有任何未完成付款要求其金额的短信会被保留,供你查看。
- 金额对应某笔凭证已被拒绝的付款的短信不会入账:该凭证会回到 转账凭证 中,由人再次查看。
- 密钥泄露时请 生成新密钥;旧密钥立即失效。
未签名的短信
无法签名的转发器可以直接发送密钥本身,但只有在你开启 也接受携带密钥而非签名的短信 之后才行。任何看到这种请求的人都能伪造入账,所以除非转发器需要,请保持关闭。
Shop 的应答:
| 状态码 | 含义 |
|---|---|
2xx | 已接收。入账后重新签名的同一条短信会收到 200,表示已经收到过。 |
401 | 签名错误,或与 Shop 的时钟相差超过五分钟。检查发送方的时钟后重新签名。 |
409 | 付款入账后再次发送的完全相同的签名请求:请视为已完成。 |
429 | 该地址在十分钟内发送了二十次被拒绝的请求。请等待。 |
503 | Shop 此刻未能保存或入账这条短信。请用当前时间重新签名后再次发送。 |
400 | 请求体不是短信。再次发送也不会有任何改变。 |
通用 HTTP 网关
Shop 中的网关就是 你这一侧 的四个地址(通常是放在你所选中介前面的一个小型中转),加上一个共享密钥:
| 地址 | 必填 | Shop 用它做什么 |
|---|---|---|
| Create | 是 | 请求支付页面。 |
| Verify | 询问某笔付款是否已完成。 | |
| Refund | 请求退款。 | |
| Health | 检查网关能否收款。 |
每个网关有自己的货币和开关。不填密钥时由 Shop 生成。必须设置 Shop 的公网地址,因为网关会把客户送回那里(Shop:客户门户)。
签名
双向的每个请求,以及 Shop 对你的服务发出的每次调用,都以同样的方式签名:请求头 X-Nexora-Timestamp(Unix 秒)和 X-Nexora-Signature,即用密钥对 <timestamp>.<raw body> 计算的十六进制 HMAC-SHA256。请在中转中校验 Shop 的签名。签名错误或时间戳与 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"Create
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。
Callback
POST <callbackUrl>
{"checkout": "ck_9f2…", "reference": "<your id>", "status": "paid",
"amount": 150000, "currency": "IRT"}status 为 paid、pending 或 failed。网关设有 verify 地址时,回调只是一个提示:Shop 会先调用 verify 再入账。重复回调是安全的;一笔 checkout 只付款一次。Shop 返回的状态码与上文银行短信的含义相同:付款入账后返回 409(不要再发送);503 表示请用当前时间重新签名后再次发送;400 表示请求体不符合约定;同一地址在十分钟内发送二十次签名错误的回调后返回 429。
Verify
POST <verify address>
{"checkout": "ck_9f2…", "reference": "<your id>", "amount": 150000, "currency": "IRT"}按与回调相同的格式应答。Shop 会在以下时候查询:客户返回时;收到回调时;对一直没有回调的付款,每隔几分钟查询一次,持续三小时;对在 Shop 一侧已关闭的 checkout,在一天内每十五分钟查询一次,使仍在网关付了的钱也能进入客户的钱包。以非要求货币支付的款项会等待你确认。
Refund
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 | 退款完成。 |
带 {"refused": true, "reason": "…"} 的 4xx | 该键下没有也不会有任何退款。款项回到钱包,管理员会看到原因。只有在确定如此时才发送。 |
| 其他任何应答 | 结果不确定。退款保持在途,不在钱包中,管理员可以用同一个键再次请求。 |
没有退款地址时,请在 客户 下该客户的页面上用 支出 手动退款给客户。
Health
health 是一个 GET 请求,网关能收款时返回 2xx。Shop 每分钟请求一次;无法开启的付款也计为一次失败。连续失败三次后网关视为宕机:客户会看到其他付款方式,管理员的 Telegram 群组和概览页会收到通知。第一次正常响应即恢复。没有健康检查地址的网关在宕机十五分钟后会再次尝试。
汇率
汇率 表示一种货币的一个单位等于多少另一种货币。在 支付方式 → 汇率 中设置。它的来源是你选择的任意 JSON 地址(Shop 不指定任何来源),并设置应答中数值的 数值路径(例如 data.price)和 乘数。Shop 每十分钟查询一次。当来源在其有效期(默认 60 分钟)内没有应答,或你选择使用时,使用 手动汇率。
汇率有两个作用:
- 只以另一种货币定价的商品 会按当日汇率以商店货币出售,并向上取整到你设定的单位。
- 只收另一种货币的网关 也可用于商店货币。checkout 要求支付换算后的金额,并把应付金额存入钱包,汇率在付款开启时锁定。此类付款需要手动退款。
