> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clinkbill.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 上线检查

> 切生产前要验证的场景，要改的配置，以及出问题时怎么查。

沙盒跑通不等于可以上线。这一页列出切生产前要过的东西。

## 先完成开户认证

生产环境要真正收款和提现，得先通过开户认证。

在**系统设置**里提交开户资料，用于审核你的收款主体和结算资质。状态有未开始、审核中、审核未通过、已通过四种。

对走上线引导的商户，企业认证（KYB）或个人认证（KYC）通过后，系统设置里的其余功能就会解锁，不需要等创建产品或完成首笔支付。

<Note>
  部分生产账户的后台只显示「完成开户认证」一步，没有后面两步。那种情况下这一步用于开通提现资格。
</Note>

认证和技术接入可以并行——生产资料在审的时候，你照样能在沙盒写代码调试。沙盒有自己的开户认证步骤，是自动通过的，不受生产审核状态影响。

## 分四层验证

按这个顺序验，前一层没过就别急着做下一层。

<Steps>
  <Step title="本地逻辑">
    不接真实支付，先验金额计算、商品快照、幂等键、状态流转和发货去重。这一层用单元测试就能覆盖。
  </Step>

  <Step title="模拟 Webhook">
    自己构造带签名的请求打给你的 Webhook 接口，验证：原始请求体读取正确、HMAC 算得对、重复事件不会重复发货、乱序事件不会覆盖新状态、错误签名会被拒。
  </Step>

  <Step title="真实沙盒 Session">
    由后端创建真实的沙盒 Checkout Session，验证前端能正常跳转或挂载。
  </Step>

  <Step title="真实沙盒付款">
    用测试卡完整走一遍。确认本地订单变成 paid，而且发货、充值或开通权益也真的执行了。
  </Step>
</Steps>

<Warning>
  第四层的验收标准是"业务真的完成了"，不是"Webhook 返回了 200"。检查你自己数据库里的发货状态，不要只看日志。
</Warning>

## 场景清单

| 场景                | 要确认的事                                                                                      |
| ----------------- | ------------------------------------------------------------------------------------------ |
| 商品模式              | 注册商品的 `productId`/`priceId` 用对了；临时商品的 `priceDataList` 金额和币种跟总额对得上                          |
| 前端承载              | Session 创建、跳转或挂载都正常；浏览器里拿到的只有 Publishable Key 和 Session 相关字段                               |
| 各支付方式             | 你已启用的每种方式都单独测一遍，包括二维码和需要额外验证的流程                                                            |
| 多币种               | 固定多币种价格或自动换算的结果正确；实际支付币种和金额能跟 Order 对上                                                     |
| 成功                | 订单变 paid，发货执行，且只执行一次                                                                       |
| 失败                | 记下了 `failureCode`，客户能重试                                                                    |
| `pending`         | 显示"确认中"，不会自动重复扣款                                                                           |
| `requires_action` | 能引导客户完成 3DS 或其他验证                                                                          |
| Webhook 重复        | 同一个事件推三遍，只发一次货                                                                             |
| Webhook 乱序        | 先收到 `session.expired` 再收到 `order.succeeded`，最终状态仍是成功                                       |
| Session 过期后成功     | 入口过期但付款后来成功，不能误判成失败                                                                        |
| 退款                | 同一个 `refundMerchantOrderId` 重复提交不会退两次；退款状态能同步                                              |
| 订阅                | 首次付款、试用、续费、`past_due`、客户门户、取消，各自的权益变化都对                                                    |
| 争议                | `dispute.created`、`dispute.updated`、`dispute.won`、`dispute.lost`、`dispute.closed` 能进人工处理流程 |

### 做了订阅还要验这些

| 场景         | 要确认的事                                                                |
| ---------- | -------------------------------------------------------------------- |
| 首次订阅       | 周期价格能通过 Hosted Checkout 完成首次订阅，Subscription、Invoice、Order 三类 ID 都落库了 |
| 免费试用       | 权益只在 `subscription.trialing` 之后开通；试用结束后的首笔扣款不会被重复发起                  |
| `past_due` | 不会被当成已支付，也不会触发第二笔并发扣款                                                |
| 取消         | 到期取消和立即取消的权益结束时间不同；立即取消没有被误写成自动退款                                    |
| 续费幂等       | 按 `invoiceId` 幂等，乱序和重复的 `invoice.paid` 不会重复延长权益                      |

### 做了优惠还要验这些

| 场景      | 要确认的事                                                |
| ------- | ---------------------------------------------------- |
| 各种优惠码路径 | 可见输入、隐藏预应用、无效码、过期码、最低消费不足、商品不匹配、固定金额币种不匹配，逐个都测过      |
| 折扣持续时间  | `once`、`repeating`、`forever` 在有试用和正常续费两种情况下的账期数都符合预期 |
| 金额展示    | 前端只展示后端返回的原价、折扣和实付金额，没有自己重算                          |

<Note>
  目前公开提供的沙盒测试数据只有成功卡 `4242 4242 4242 4242`，没有可公开的 `failed`、3DS、`pending` 测试数据。

  所以上表里这三行暂时没法在沙盒里跑出来，要靠代码审查和单元测试覆盖。`pending` 尤其要认真看一遍——确认你的代码不会把它当成失败然后重新发起扣款，那是造成重复扣款最常见的原因。
</Note>

## 切生产要改什么

| 项目                                       | 从                                     | 改成                                |
| ---------------------------------------- | ------------------------------------- | --------------------------------- |
| 商户后台                                     | `https://uat-dashboard.clinkbill.com` | `https://dashboard.clinkbill.com` |
| API 地址                                   | `https://uat-api.clinkbill.com`       | `https://api.clinkbill.com`       |
| Secret Key                               | `sk_uat_…`                            | `sk_prod_…`                       |
| Publishable Key                          | `pk_uat_…`                            | `pk_prod_…`                       |
| Webhook 地址                               | 内网穿透的临时地址                             | 生产域名的正式地址                         |
| Webhook 签名密钥                             | 沙盒的                                   | 生产端点新生成的                          |
| `successUrl` / `cancelUrl` / `returnUrl` | 本地或沙盒域名                               | 生产域名                              |

上面这些都是环境相关的配置。**账号本身不用换** —— 沙盒和生产用同一套登录账号密码，从沙盒后台右上角的「进入生产环境」切过去就行，不需要重新注册。

<Warning>
  生产密钥要在**生产后台**重新初始化，Webhook 端点也要在生产环境重新注册，签名密钥跟沙盒不是同一个。忘了换的话，生产事件会全部验签失败——而且失败得很安静，你的服务照常返回 401，客户付了钱但永远不发货。
</Warning>

上线后建议先用一笔小额真实交易走完全流程，确认收款、Webhook、发货、对账都正常，再放开流量。

## 出问题怎么查

### 客户说付了钱，但订单没变

按这个顺序查：

1. 后台 **交易** 页面能不能找到这笔交易，状态是什么（注意去对应环境的后台找，沙盒和生产的交易是分开的）
2. 找不到就用 `sessionId` 查 `GET /checkout/session/{id}`，看 `status` 和 `orderId`
3. 有 `orderId` 就查 `GET /order/{id}`，看 Order 的 `status` 是不是 `success`
4. Order 是 `success` 但你的订单没更新，说明问题在 Webhook 那一段，往下看

### 收不到 Webhook

| 检查             | 说明                                      |
| -------------- | --------------------------------------- |
| 地址是不是公网 HTTPS  | localhost、内网 IP、回环地址会被拒绝                |
| 端点是不是 enabled  | 后台 **开发者 > Webhooks** 里看状态              |
| 事件订阅了没有        | 只订阅了 `session.*` 就收不到 `order.succeeded` |
| 你的服务返回了什么      | 非 2xx 会被当作失败并重试                         |
| 有没有防火墙或 WAF 拦截 | 检查服务器访问日志里有没有请求进来                       |

### 验签一直失败

最常见的三个原因：

* 用了解析过的 body 而不是原始请求体（Express 里用了 `express.json()`）
* 签名密钥没换成当前端点的（轮换过，或者从沙盒复制过来的）
* 拼接时少了中间那个 `.`，或者时间戳用了自己生成的而不是请求头里的

### 客户被扣了两次

检查这几处：

* 下单接口有没有防重复提交，同一个购物车重复点会不会建出两条订单
* `pending` 状态有没有被当成失败，然后自动重新发起了扣款
* Webhook 有没有按 `event.id` 去重

`merchantReferenceId` 不是幂等键。用同一个值创建两次 Session，Clink 会给你两个不同的 Session，两笔都能付款。

### 开户认证没通过

在**系统设置**里看审核未通过的具体原因，按提示补充或更正资料后重新提交。技术接入不受影响，可以继续在沙盒调试。

## 上线之后

<CardGroup cols={2}>
  <Card title="余额与结算" icon="circle-dollar-sign" href="/cn/finance/balance">
    钱什么时候能提，费用怎么算。
  </Card>

  <Card title="退款" icon="rotate-ccw" href="/cn/guides/resources/refund">
    退款流程和状态同步。
  </Card>
</CardGroup>
