跳转到正文
图奕科技 开发文档 平台登录
接入指南 / 支付与退款下载本章 Markdown ↓

本实例 Issuer https://platform.too1.net

统一支付与收银台接入

图奕统一认证只处理渠道接入、支付状态、退款、回调和对账。产品仍是商品、价格、业务订单和权益的唯一所有者;amount_fen 必须由产品服务端根据自己的业务订单生成,不能接受浏览器传入金额后原样转发。

管理员先验证支付账户

在「支付中心 → 支付账户」保存证书后,点击对应账户右侧的「0.01 元测试」。无需先绑定产品,也不需要先打开正式收款。

  1. 完成最近十分钟管理员二次验证,输入原因、账户代码,并勾选真实商户测试确认。
  2. 平台只创建固定 1 分 CNY 的测试订单。微信展示 Native 二维码;支付宝提供电脑网站支付链接。必须由管理员手动核对商户与金额后付款。
  3. 详情页分别显示支付状态、成功回调验签与金额核对、退款状态。同步回跳和主动查询不会将“回调验收”标为通过。
  4. 付款后点击「原路退款 0.01 元」,再次确认测试 ID 和原因;未知结果只查询,禁止重复退款。渠道明确拒绝后可排除故障并手动重试,使用原退款编号。
  5. 查看有效支付回调与退款成功结果,再单独验证产品下单、Webhook 和权益交付。测试不会自动开启任何账户或事故开关。

此入口在 disabled 模式下仍允许管理员显式确认的真实测试,且 staging 下支付宝也使用正式商户网关,不是沙箱。它不开放给产品服务端或普通用户,不产生产品业务订单、业务权益或产品 Webhook。单账户同时只允许一笔未完成测试(已付款须退款成功),每分钟最多一次、滚动 24 小时最多二十次。表单重复提交返回原记录;请求超时、连接中断或结果保存失败后,必须通过「查看最近一次测试」核对,不会再次调用下单。

测试记录在 payment_account_tests,金额由数据库约束为 1 分;管理员操作与审计同事务提交。测试支付与退款使用独立回调 /callbacks/payments/{channel}/{account_code}/account-test,不带查询参数,并核验已存在的测试订单、账户、签名、金额与币种。普通产品订单仍使用原回调地址,不会走测试结算路径。历史版本带 account_test=1 的通知继续接收以处理在途订单,但新订单不再生成这种地址。原始回调继续加密保存并遵循原有留存策略。

若页面显示「下单失败」,说明未取得可展示的二维码;先排除页面给出的错误,再点击「修复后新建测试」。失败记录保留供审计,不会自动重新下单。「结果未知」则不同:必须先查已有订单,不能新建或重复付款。微信要求 notify_url 为不带参数的完整 HTTPS 回调地址,详见官方填写规则。

后台每分钟查询未知结果并尝试关闭过期的未付订单,不自动付款、退款或重新下单。退款失败或长期未知时保留记录,需核对渠道商户后台。测试过程中不要轮换账户证书;若确需轮换,先处理全部在途订单与回调。

账户测试记录不混入产品交易统计;本次只验证账户付款、成功回调和退款,不代表产品 Webhook、账单文件对账或正式上线门禁通过。真实测试产生的流水仍需在商户账单中核对。

创建支付会话

以下所有接口调用方都是产品服务端,使用服务 Token。创建/关闭需要 payments:create,支付和退款查询需要 payments:read,退款申请需要 refunds:create。不能把用户 OIDC Token 当服务 Token,也不能让浏览器直接持有 Service Secret。

前置准备:产品已启用,登记精确返回地址,绑定通过验收的渠道账户,启用相应支付能力;产品自己先建立待付款业务订单。

POST /api/v1/payment-sessions
Authorization: Bearer SERVICE_TOKEN
Idempotency-Key: wyc-order-20260902-10086
Content-Type: application/json

{
  "external_order_id": "WYC-20260902-10086",
  "amount_fen": 9900,
  "subject": "网云穿专业版一年",
  "return_url": "https://www.example.com/pay/result",
  "user_sub": "0199..."
}

user_sub 和 external_user_id 必须二选一。迁移期使用 external_user_id 时,该 ID 必须已有当前产品映射。return_url 必须与产品后台登记值完全一致,不支持通配符。

字段 类型 限制
external_order_id string 非空、最多 64 字节;同产品订单唯一
amount_fen int64 1–10,000,000,000 整数分;不能传小数或金额字符串
subject string 非空,最多 128 字符;不放手机号等敏感内容
return_url string 精确登记的 HTTPS 地址;不能传任意客户端 URL
user_sub string 已认证平台用户 UUID,与 external_user_id 二选一
external_user_id string 本产品已映射的旧用户 ID,保留前导零

不接收自定义 currency 字段,当前固定 CNY。产品仍须核对请求用户拥有这笔业务订单,不能只因为传来的 sub 语法正确就付款给其他账号。

创建返回 HTTP 201,例如:

{
  "data": {
    "payment_id": "00000000-0000-4000-8000-000000000020",
    "external_order_id": "WYC-20260902-10086",
    "amount_fen": 9900,
    "currency": "CNY",
    "subject": "网云穿专业版一年",
    "status": "pending",
    "paid_amount_fen": 0,
    "expires_at": "2026-09-03T00:15:00Z",
    "checkout_url": "https://platform.too1.net/pay/ONE_TIME_PUBLIC_TOKEN"
  }
}

示例省略部分时间/用户字段;必须保存 payment_id 与业务订单的关系。收银台 URL 是持有即访问的敏感链接,不记到普通访问日志、统计平台或第三方 Referer。当前没有按 external_order_id 的公共查询接口;丢失 payment_id 且原幂等回执不可领取时,应由运维核查,不能新建相同业务订单。

成功返回 payment_id、状态和一个 256 位随机收银台 URL。URL 默认 15 分钟有效且无需再次登录。桌面端提供微信 Native 二维码和支付宝电脑网站支付;移动端使用微信 H5 或支付宝手机网站支付。

同步回跳只用于用户体验,绝不作为支付成功依据。产品必须以签名 Webhook payment.succeeded 为主要依据,并可用查询接口补偿:

GET /api/v1/payments/{payment_id}
Authorization: Bearer SERVICE_TOKEN

支付状态固定为 pending、paid、closed、expired、error。

查询响应仍是 {"data":{...支付对象...}},通常不再返回 checkout_url;退款不会把支付对象的 paid 改成未支付,退款状态单独查询。浏览器倒计时结束仅是展示过期,不是资金事实,以平台服务端确认的状态为准。用户返回后由产品服务端查平台状态,前端 ?success=1 等字段完全不能作为交付依据。

幂等及异常恢复

每次业务操作使用独立 Key,例如 pay:WYC-001、close:PAYMENT_UUID、refund:WYC-R-001;同一 Client 的 Key 跨路由共享。原始 JSON 序列化字节需保持一致,详见服务 API 幂等规则。

duplicate_external_order 表示业务单已存在,idempotency_in_progress 表示请求未完成,均不能理解为可以重新下单。发生网络超时或 idempotency_storage_error 时,订单可能已经创建/到达渠道,先查询已保存 ID 或领取原回执;无法判断则人工核查,不自动换新 Key。

关闭订单

POST /api/v1/payments/{payment_id}/close
Authorization: Bearer SERVICE_TOKEN
Idempotency-Key: close-WYC-20260902-10086
Content-Type: application/json

{}

平台只有在渠道明确确认关闭后才将本地订单改为 closed。渠道超时、网络中断或错误时保持原状态,并返回安全错误;查询 Worker 会继续核对未知状态。

关闭成功返回 HTTP 200 与 data 支付对象;渠道未知可能返回 HTTP 202 但正文仍是 error 对象,错误码 channel_result_unknown。必须解析正文与状态,不要仅以“HTTP 2xx”标记关闭成功。

退款

POST /api/v1/payments/{payment_id}/refunds
Authorization: Bearer SERVICE_TOKEN
Idempotency-Key: refund-WYC-20260902-1
Content-Type: application/json

{
  "external_refund_id": "WYC-R-20260902-1",
  "amount_fen": 500,
  "reason": "用户重复购买退款"
}
  • 允许部分退款,累计申请金额不得超过实付金额。
  • 默认退款窗口为支付成功后 90 天。
  • 单笔不超过 100 元且产品当天自动退款累计不超过 500 元时,在事故开关允许的前提下自动发起。
  • 超过任一阈值时创建 pending_approval,必须由最近 10 分钟完成短信二次验证的管理员输入退款 ID 和原因后批准或拒绝。
  • 渠道结果未知时保持 processing 并由 Worker 查询,禁止盲目重复申请。

external_refund_id 最多 64 字节,reason 为 4–200 字符,amount_fen 为正整数。审批中/处理中/成功的退款都占用可退款额度。产品应先保存自己的业务退款单、生成唯一号,再发请求;审批未通过不能向用户展示“已到账”。申请响应 data 为退款对象,包含 refund_id、payment_id、external_refund_id、amount_fen、status 以及可选渠道信息;以该 status 继续查询。

查询退款:

GET /api/v1/refunds/{refund_id}
Authorization: Bearer SERVICE_TOKEN

退款状态固定为 pending_approval、processing、succeeded、failed。

平台事件的验签与可靠消费是必做项。产品退款成功后是否撤销套餐、缩短有效期或保留已用权益,由产品自己的业务规则决定,平台不会替产品改权益。

渠道回调

  • 微信:https://platform.too1.net/callbacks/payments/wechat/{account_code}
  • 支付宝:https://platform.too1.net/callbacks/payments/alipay/{account_code}

平台使用官方微信支付 Go SDK完成签名验证和 AES-GCM 解密;支付宝适配器执行 RSA2 验签。验签后还会核对 AppID、商户号、平台订单号、渠道订单号、金额和币种。通知 ID 唯一约束、数据库行锁、状态更新和 Outbox 事件保证重复通知安全。

若同一平台订单已支付后又收到不同渠道订单号的有效付款通知,平台不会把第二笔资金静默当成普通重试:它会创建 duplicate_payment_suspected 资金异常、在后台置顶,并投递 payment.duplicate_suspected 事件,等待人工核对和退款处置。

已验签的原始通知以 AES-GCM 加密保存 180 天。支付凭据采用证书模式并由超管后台上传:微信包含商户 API 证书及配对私钥、微信支付平台证书和 APIv3 Key;支付宝包含应用公钥证书及配对应用私钥、支付宝根证书和支付宝公钥证书。平台保存前校验证书格式、有效期和公私钥配对。支付数据密钥通过 HKDF-SHA256 从现有 SSO_TOKEN_HASH_SECRET 进行用途隔离派生,不需要额外文件;后台、日志和审计均不回显凭据原文。

证书轮换时使用同一账户代码重新上传完整材料。账户的 AppID、商户号和渠道创建后不可原地修改,更换商户主体必须创建新的账户代码,避免历史订单与延迟回调被错误路由。新证书版本与账户元数据、审计在同一事务中生效,旧版本标记为 retired;运行中的请求可完成,后续请求自动使用新版本,无需重启平台。SSO_TOKEN_HASH_SECRET 已是平台必须长期保留的核心 Secret;上传支付证书后不可直接更换,如确需轮换必须先执行支付密文迁移。

对账与异常

后台 Worker 会主动查询超时未知订单、查询处理中的退款、关闭过期订单。当前版本的每日自动对账采用渠道鉴权的逐单查询,差异只进入后台,不自动改写资金状态。

微信/支付宝账单文件下载与逐笔文件对账需要真实商户凭据、账单权限和实际账单样本完成联调验收;在完成该验收前,后台会明确按“逐单渠道核对”展示,不能将其视为账单文件已对账。生产上线仍必须在渠道商户后台核对首批交易和退款。

事故开关

暂停新支付、微信支付、支付宝支付或自动退款不会关闭以下能力:

  • 已有支付/退款回调接收;
  • 订单与退款查询 Worker;
  • 过期订单关闭与对账;
  • 管理员登录和事故处置。

一旦平台产生真实交易,回滚只能暂停新支付并保留当前版本继续处理回调与查询,不能直接换回不认识新交易表的旧二进制。

运维验收命令

# 纯内存模拟,不写数据库、不调用真实渠道
./ssoctl platform payment simulate --channel wechat
./ssoctl platform payment simulate --channel alipay

# 检查配置、事故开关、账户和产品绑定
./ssoctl platform payment check

# 执行昨日逐单渠道核对;真实渠道必须显式双确认
./ssoctl platform payment reconcile --live --confirm RUN-RECONCILIATION

模拟命令覆盖渠道创建、查询、关闭和退款接口,但不等同于 HTTP API/数据库闭环。完整闭环由集成测试和 staging 收银台验收承担。真实扣款必须通过平台中已持久化的支付会话进行,CLI 不提供绕过平台订单直接向渠道扣款的命令。