支付控制台工作原理

这个网站把一个较长的 GoPay 支付链路拆成三步:初始化账单、验证 OTP、验证 PIN。前端只保存当前阶段需要的少量字段,真正的协议编排都在本地 Go 后端完成。

返回控制台

整体结构

浏览器前端

收集 token、手机号、WhatsApp OTP、PIN。初始化成功后才允许输入 OTP,OTP 成功后才允许输入 PIN,避免用户跳步骤。

本地 Go 后端

提供 /api/checkout/start/api/gopay/otp/api/gopay/pin,并负责组装外部请求、重试和错误透传。

配置文件

config.json 控制 checkout 地址、mock/正式基础地址、Midtrans header、代理测试地址;环境变量可以覆盖配置。

学习交流

作者与声明

作者微信:cian0318。本文档和工具仅供学习交流,不可用于其他用途。遇到流程问题可扫码添加微信;觉得有帮助可以扫码赞助。

转载请保留作者信息;如果删除作者信息,愿你以后写的代码全是 BUG。

仅供学习 请勿商用 理性赞助
微信加好友二维码
添加微信 交流与反馈

协议流程图

本地接口主流程 1 初始化账单 /api/checkout/start 等待 WhatsApp OTP 返回 reference_id 2 验证 OTP /api/gopay/otp 3 验证 PIN /api/gopay/pin PIN 阶段内部链路 这一段由 /api/gopay/pin 在后端连续完成,前端只等待最终状态。 PIN token 绑定阶段 validate PIN linking charge Midtrans validate payment confirm challenge process success status settlement

上半部分是用户可见的三步操作;下半部分是 PIN 提交后后端自动执行的支付确认链路。

本地后端接口

接口 前端发送 后端返回 用途
POST /api/checkout/start 访问令牌、GoPay 手机号、国家码、代理、税务地区;邮箱由前端随机生成 Gmail。 reference_idgopay_guidnext_action: enter_otp、Stripe/Midtrans/GoPay 阶段响应。 创建 checkout session,初始化 Stripe,打开 Midtrans GoPay linking,并停在 OTP 前。
POST /api/gopay/otp reference_id 和用户输入的 otp challenge_idnext_action: enter_pin、完整 gopay_otp 验证 WhatsApp OTP,拿到后续 PIN 验证需要的 challenge。
POST /api/gopay/pin reference_idchallenge_idgopay_guid 和用户输入的 pin ok: truestage: gopay_completemidtrans_status、各阶段原始响应。 完成绑定 PIN 验证、发起 charge、完成支付 PIN 验证并查询最终状态。

初始化账单阶段访问了什么

1

Checkout 与 Stripe 初始化

后端先把前端参数转发到配置里的 checkout 接口,取得 checkout_session_idpublishable_key。随后调用 Stripe 页面初始化、更新账单信息、创建 gopay 类型 payment method,再确认 session。

2

审批与跳转信息

后端调用 checkout approve 接口,随后读取 Stripe payment details,检查 setup_intent.next_actionpayment_intent.next_action 中的跳转地址。

3

Midtrans GoPay linking

后端跟随 GoPay redirect 得到 gopay_guid,再请求 POST /snap/v3/accounts/{gopay_guid}/linking,发送手机号数据并从 activation_link_url 提取 reference_id

{
  "type": "gopay",
  "country_code": "86",
  "phone_number": "1888888888"
}
4

Reference 与用户授权检查

后端继续调用 POST /v1/linking/validate-referencePOST /v1/linking/user-consent,都使用 reference_id。成功后返回给前端,按钮进入 OTP 阶段。

OTP 与 PIN 阶段访问了什么

OTP 验证

本地 /api/gopay/otp 调用 GoPay POST /v1/linking/validate-otp。请求体只包含 reference 和 OTP。当前实现只覆盖 WhatsApp 验证码,不包含 SMS 发送接口;如果需要短信通道,需要自行补充发送 SMS 的接口和前端入口。

{
  "reference_id": "前一步返回的 reference_id",
  "otp": "用户输入的 OTP"
}
绑定 PIN token

本地 /api/gopay/pin 先调用 POST /api/v1/users/pin/tokens/nb,把 OTP 阶段返回的 challenge 和 PIN 换成 pin_token

{
  "challenge_id": "OTP 返回的 challenge_id",
  "client_id": "51b5f09a-3813-11ee-be56-0242ac120002-MGUPA",
  "pin": "用户输入的 PIN"
}

PIN 后的支付完成链路

顺序 外部接口 发送/提取的数据 成功条件
A POST /v1/linking/validate-pin 发送 reference_id 和上一步的 pin_token success: true,并进入 linking 成功状态。
B POST /snap/v2/transactions/{gopay_guid}/charge 发送 payment_type: gopaytokenization: truepromo_details: null 响应里必须有 gopay_verification_link_url,后端从中提取新的支付 reference_id
C GET /v1/payment/validate?reference_id=... 查询 GoPay 支付页数据,包括商户、金额、支付工具 token。 success: truenext_action: payment-details
D POST /v1/payment/confirm?reference_id=... 发送 {"payment_instructions":[]} 返回 payment-validate-pin,并给出支付 PIN 的 challenge_idclient_id
E POST /api/v1/users/pin/tokens/nb 发送支付 confirm 返回的 challenge_idclient_id 和同一个 PIN。 返回新的 data.token
F POST /v1/payment/process?reference_id=... 发送 GOPAY_PIN_CHALLENGE 和支付 PIN token。 success: truenext_action: payment-success
G GET /snap/v1/transactions/{transaction_id}/status 从 process 的 redirect_url 提取交易 id,再查询 Midtrans 状态。 页面展示 transaction_status,例如 settlement

关键字段如何流转

字段 来源 后续用途
gopay_guid Stripe next_action 跳转到 Midtrans 后,由后端解析 redirect 得到。 用于 Midtrans linking 和 charge 的 URL 路径。
reference_id Midtrans linking 返回的 activation_link_url 用于 reference 校验、用户授权、OTP、绑定 PIN。
challenge_id GoPay OTP 返回的 data.challenge.action.value.challenge_id 用于换取绑定阶段 PIN token。
payment_reference_id Midtrans charge 返回的 gopay_verification_link_url 用于 payment validate、confirm、process。
midtrans_status 最后的 Midtrans transaction status 接口。 作为页面最终结果展示,并保留完整 JSON 方便排查。

配置与 mock

checkout_mode 可设置为 upstreamlocallocal 会跳过 ChatGPT checkout 初始化并使用本地模拟流程(适合排查联调);upstream 走真实上游。其余 local_mock_base_urlmidtrans_mock_base_urlgopay_gwa_mock_base_urlgopay_customer_mock_base_url 决定后端访问哪里。默认可指向 http://localhost:8282 进行本地 mock;替换成正式域名会访问真实外部链路。

错误如何定位

每个阶段失败时,后端都会返回 stage 和对应阶段的原始响应,例如 gopay_payment_confirmgopay_payment_processmidtrans_status。排查时先看 stage,再看该阶段对象里的 endpointstatusstatus_message 或 GoPay errors