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

# Mode C 直充接口与结果通知

> 说明 Waffo Point Topup 的 Mode C 直充 API：最小接入范围、签名跳转链接的参数与签名算法、直充与履约查询接口、履约结果通知及重试规则，以及 Waffo 履约查询接口。

本页说明 Mode C（直充 API）：用户付款后，Waffo Point Topup 调用你的接口直接给用户账号充值，用户不需要接触点卡。涵盖签名跳转链接怎么生成、直充与履约查询接口、履约结果通知（Webhook）以及你可以反查的 Waffo 履约查询接口。适合能直接给用户账号充值的供应商，例如有账号充值系统的游戏发行商、会员与订阅类服务。

## Mode C 的流程是怎样的？

1. 你和 Waffo Point Topup 互换 RSA 公钥。
2. 你把 `SECRET_KEY`、API Key 和 API 文档提供给 Waffo Point Topup。
3. 你在自己的后端实现 URL 签名逻辑。
4. 双方联调：URL 验签、直充接口、查询接口（可选）、履约结果通知（可选）。
5. 你把签名跳转链接接入自己的网站。
6. 上线。

用户购买时：你的网站生成签名链接 → 用户跳转到 Waffo 页面并付款 → Waffo 调用你的直充接口 → 用户回跳到你的页面。

| 优点 | 适用场景 |
| - | - |
| 用户体验顺畅，不需要点卡 | 有账号充值系统的游戏发行商 |
| 即时到账 | 会员与订阅类服务 |
| 转化率更高 | |

## 接入前要准备什么？

| 材料 | 说明 |
| - | - |
| RSA 公钥 | 用于接口验签，2048 位 |
| `SECRET_KEY` | 用于对跳转链接签名 |
| 接口地址 | 直充接口与查询接口的生产与测试环境地址 |
| 供应商 ID | `supplierId`，由 Waffo 分配的唯一标识 |
| 支持的站点代码列表 | `siteCode`，例如 `US`、`JP`、`HK` |
| 结算信息 | 银行账户、结算周期 |
| Webhook 地址 | 接收履约状态通知的地址 |

## Mode C 有哪些接口？最少要接哪些？

| 序号 | 接口 | 说明 | 提供方 | 是否必须 |
| - | - | - | - | - |
| 1 | URL 签名与跳转 | 带签名的 URL 鉴权 | Waffo | **必须** |
| 2 | 直充接口 | 创建直充订单 | 你 | 可选集成（示例规范） |
| 3 | 履约查询接口 | 查询直充订单。如果你的直充请求本身幂等，则不需要：Waffo 会用同一订单号重试，由你保证幂等 | 你 | 可选集成（示例规范） |
| 4 | 履约结果通知（Webhook） | Waffo 把最终履约状态推送给你 | Waffo 发送 | 可选 |
| 5 | Waffo 履约查询接口 | 你向 Waffo 反查履约结果 | Waffo | 可选 |

最小接入只需要第 1 项和第 4 项：签名跳转加履约结果通知，不需要提供自己的履约接口。这种情况下通知只上报 `PAY_SUCCESS` 和 `PAYMENT_FAILED`，你在收到 `PAY_SUCCESS` 后走自己的发货流程。

## 签名跳转链接怎么生成？

用户从你的网站进入 Waffo 时，通过带签名的 URL 参数传递用户信息，保证参数完整。签名必须在**你的后端**生成，绝不能把 `SECRET_KEY` 暴露给前端。

链接格式：

```
https://{supplier}.waffoplay.com/redirect?amount={amount}&currency={currency}&faceValue={faceValue}&notifyUrl={notifyUrl}&productId={productId}&returnUrl={returnUrl}&salesOrderId={salesOrderId}&siteCode={siteCode}&supplierId={supplierId}&supplierUserAccount={supplierUserAccount}&theme={theme}&timestamp={timestamp}&signature={signature}
```

`{supplier}` 是接入时 Waffo 分配给你的子域名。你也可以改用自己拥有的域名，只要把该域名指向 Waffo 的接入端点；此时签名串里的基础 URL 必须用这个自定义域名，因为签名覆盖基础 URL，域名不一致会验签失败。

### 有哪些跳转参数？

| 参数 | 是否必填 | 说明 |
| - | - | - |
| `supplierId` | 必填 | Waffo 分配给你的供应商 ID |
| `supplierUserAccount` | 必填 | 充值账号，例如你的会员 ID |
| `faceValue` | 必填 | 面额，例如你的点数 |
| `salesOrderId` | 必填 | 你的订单 ID。Waffo 会在履约请求、通知和查询中原样回传，方便你对账 |
| `siteCode` | 必填 | 目标站点代码，ISO 3166-1 alpha-2 国家代码，例如 `JP`、`US` |
| `timestamp` | 必填 | 毫秒级 Unix 时间戳；签名有效期由它控制，超过 2 小时的请求会被拒绝 |
| `signature` | 必填 | 对签名串计算的大写十六进制 HMAC-SHA256 签名 |
| `supplierUserEmail` | 选填 | 你的会员邮箱，Waffo Point Topup 用它把发票发给用户 |
| `amount` | 选填 | 订单实际应付金额，精度需与币种一致。由你自己定点数价格时，`amount` 与 `currency` 一起传 |
| `currency` | 选填 | 订单定价币种（ISO 4217）。你可以只用一种币种定价，由 Waffo 处理定价币种与用户实际支付币种之间的换算 |
| `returnUrl` | 选填 | 用户完成收银台后回跳的你的前端地址，Waffo 会在其后附加 `salesOrderId`；取值必须做 URL 编码 |
| `theme` | 选填 | 收银台页面主题，`light` 或 `dark`，默认 `dark` |
| `productId` | 选填 | Waffo 定义的产品 ID。供应商有多个产品类型或多个来源网站时，Waffo 为每一项分配一个产品 ID，用于对应各自的支付渠道配置；多产品供应商必填，单产品供应商选填 |
| `notifyUrl` | 选填 | 接收该订单履约结果通知的地址 |

完整的类型和示例见官网 API Reference：签名跳转 ([https://waffo.com/docs/api-reference/point-topup-redirect/signed-redirect-to-checkout.md](https://waffo.com/docs/api-reference/point-topup-redirect/signed-redirect-to-checkout.md))。

### 签名算法是什么？

1. **去掉参数**：排除 `signature` 参数，并**丢弃所有值为空或空白的参数**。
2. **排序**：把剩余参数按 key 的 ASCII 升序排序。
3. **拼接签名串**：格式为 `{baseUrl}?key1=value1&key2=value2&...`，**必须包含基础 URL**（协议 + 主机 + 路径，例如 `https://supplier.waffoplay.com/redirect`）。
4. **计算签名**：以共享的 `SECRET_KEY` 作为 HMAC 密钥，对签名串计算 HMAC-SHA256。
5. **转大写**：十六进制编码后转为大写。

一个容易出错的点：`returnUrl` 在签名串里是**未编码**的，在最终链接里是**URL 编码**后的。签名和传输是两个独立步骤，不要混用两种形式。

官网提供了 Java 实现示例，以及包含 HMAC-SHA256 和 RSA-SHA256 的 Node.js 签名示例包，见官网 Mode C：直充 API ([https://waffo.com/docs/zh/developer-docs/point-topup/mode-c-direct-fulfillment.md](https://waffo.com/docs/zh/developer-docs/point-topup/mode-c-direct-fulfillment.md))。

### 签名示例

假设参数如下：

| 参数 | 值 |
| - | - |
| `supplierUserAccount` | `supplier_user_12345` |
| `supplierUserEmail` | `test@supplier.com` |
| `supplierId` | `WAFFO_POINT_TOPUP_001` |
| `faceValue` | `3000` |
| `amount` | `3000` |
| `currency` | `JPY` |
| `salesOrderId` | `A123456` |
| `siteCode` | `US` |
| `returnUrl` | `https://example.com/return_url_page?salesOrderId=XXX` |
| `theme` | `light` |
| `timestamp` | `1713483091000` |

签名串：

```
https://supplier.waffoplay.com/redirect?amount=3000&currency=JPY&faceValue=3000&returnUrl=https://example.com/return_url_page?salesOrderId=XXX&salesOrderId=A123456&siteCode=US&supplierId=WAFFO_POINT_TOPUP_001&supplierUserAccount=supplier_user_12345&supplierUserEmail=test@supplier.com&theme=light&timestamp=1713483091000
```

`signature = HMAC_SHA256(签名串).toUpperCase()`，最终链接：

```
https://supplier.waffoplay.com/redirect?amount=3000&currency=JPY&faceValue=3000&returnUrl=https%3A%2F%2Fexample.com%2Freturn_url_page%3FsalesOrderId%3DXXX&salesOrderId=A123456&siteCode=US&supplierId=WAFFO_POINT_TOPUP_001&supplierUserAccount=supplier_user_12345&supplierUserEmail=test@supplier.com&theme=light&timestamp=1713483091000&signature=ABC123XYZ456DEF789
```

## 用户点了签名链接之后，Waffo 做了什么？链接有问题会怎样？

Waffo Point Topup 后端会：

1. 接收所有 URL 参数；
2. 校验必填参数齐全：`faceValue`、`salesOrderId`、`supplierId`、`supplierUserAccount`、`siteCode`、`timestamp`、`signature`；
3. 校验 `timestamp` 在 2 小时有效期内；
4. 用同样的算法重新计算签名并与请求中的签名比对；
5. 通过后生成 JWT token 写入用户会话，并跳转到目标 `siteCode` 的购买页。

异常情况：

| 情况 | 结果 |
| - | - |
| 缺少参数 | 跳转到错误页，提示缺少必填参数 |
| 验签失败 | 跳转到错误页，提示签名无效 |
| 时间戳过期 | 跳转到过期页，提示链接已过期 |

## 直充接口长什么样？

用户付款后由 Waffo Point Topup 调用。

| 项目 | 内容 |
| - | - |
| 方法 | `POST` |
| 路径 | 由你定义 |
| 鉴权 | 通用接口签名（SHA256WithRSA），见[供应商接入总览](https://waffo.com/docs/zh/support/point-topup-supplier-integration-overview-faq.md) |

本节规范是**示例**。如果你已有直接充值接口，把你自己的 API 文档交给 Waffo Point Topup，Waffo 会适配你现有的接口。

**幂等要求**：你必须基于 `salesOrderId` + `supplierId` 实现幂等，同一请求重复到达时返回同样的成功响应。

强烈建议：核对同一 `salesOrderId` 的 `faceValue`、`amount`、`currency` 与你系统里的订单记录严格一致；业务校验不通过就拒绝履约。

### 请求字段

| 字段 | 说明 | 类型 | 是否必填 |
| - | - | - | - |
| `supplierId` | Waffo 分配给供应商的唯一标识 | String(64) | 必填 |
| `salesOrderId` | 你在签名链接里提供的订单 ID，与 `supplierId` 一起作为幂等键 | String(64) | 必填 |
| `faceValue` | 面额 | String(32) | 必填 |
| `amount` | 订单实际应付金额。如果同时收到面额和订单金额，建议互相校验 | DECIMAL(20,8) | 选填 |
| `currency` | 订单定价币种 | String(3) | 选填 |
| `requestedAt` | Waffo 侧请求时间，UTC 的 ISO 8601 扩展格式 `YYYY-MM-DDThh:mm:ss.sssZ` | String(32) | 必填 |
| `buyerInfo` | 买家信息，直充场景下需要 | Object | 选填 |
| `buyerInfo.supplierUserAccount` | 充值账号 ID | String(64) | 选填 |

### 响应字段

| 字段 | 说明 | 类型 | 是否必填 |
| - | - | - | - |
| `salesOrderId` | 你在签名链接里提供的订单 ID | String(32) | 必填 |
| `status` | 订单状态：`IN_PROGRESS` 充值处理中，`SUCCESS` 充值成功，`FAILURE` 充值失败 | String(24) | 必填 |

```json theme={null}
{
  "code": "0",
  "msg": "success",
  "data": {
    "salesOrderId": "M202504160311311156635",
    "status": "IN_PROGRESS"
  }
}
```

## 你侧的履约查询接口长什么样？

由 Waffo Point Topup 调用，向你查询履约结果。同样是示例规范，方法 `POST`、路径由你定义、鉴权为 SHA256WithRSA。如果你的直充接口本身幂等，可以不提供这个接口——Waffo 会用同一个 `salesOrderId` 重试，由你保证幂等处理。

请求字段：`salesOrderId`（必填，String(64)）、`supplierId`（必填，String(64)）。

响应字段：

| 字段 | 说明 | 是否必填 |
| - | - | - |
| `salesOrderId`、`supplierId` | 与请求一致 | 必填 |
| `status` | `IN_PROGRESS`、`SUCCESS` 或 `FAILURE` | 必填 |
| `requestedAt` | Waffo 侧请求时间，与直充请求一致 | 必填 |
| `faceValue` | 面额，面额可变的商品需要 | 选填 |
| `buyerInfo` | 买家信息，结构与直充请求一致 | 选填 |
| `completedTime` | 订单完成时间 | 选填 |
| `failureCode`、`failureReason` | 订单失败码与失败原因 | 选填 |

## 履约结果通知（Webhook）会通知什么？我要怎么回应？

Waffo Point Topup 会把最终履约状态 POST 到你在签名链接里通过 `notifyUrl` 传入的地址，覆盖这几种情况：付款成功但履约失败、付款失败、未付款超时。事件名为 `FULFILLMENT_RESULT_NOTIFY`，业务数据在 `data` 里。

你的接口必须基于 `salesOrderId` + `supplierId` 实现幂等：同一通知重复到达时，返回同样的成功响应。

驱动你业务逻辑的字段是 `fulfillmentStatus`：

| 取值 | 含义 |
| - | - |
| `SUCCESS` | 履约成功 |
| `PAY_SUCCESS` | 付款成功。发给没有接入履约 API 的供应商，作为他们启动自己发货流程的触发 |
| `PAY_SUCCESS_SUPPLY_FAILED` | 付款成功但履约失败，需要确认退款或补发 |
| `PAYMENT_FAILED` | 付款失败，包括用户付款超时 |

`failureCode` 取值：

| 对应状态 | `failureCode` | 含义 |
| - | - | - |
| `PAY_SUCCESS_SUPPLY_FAILED` | `SUPPLIER_API_ERROR` | 供应商接口返回错误 |
| `PAY_SUCCESS_SUPPLY_FAILED` | `INVALID_ACCOUNT` | 在供应商处找不到用户账号 |
| `PAYMENT_FAILED` | `PAYMENT_TIMEOUT` | 用户未及时付款 |
| `PAYMENT_FAILED` | `PAYMENT_DECLINED` | 被支付渠道拒绝 |
| `PAYMENT_FAILED` | `PAYMENT_CANCELLED` | 用户取消了支付 |

通知数据里除上述字段外，还包含 `supplierId`、`salesOrderId`、`faceValue`、`amount`、`currency`、`requestedAt`、`completedTime`、`buyerInfo`、`payMethodName`（用户实际使用的支付方式）、`payOrderId`（Waffo Point Topup 生成的支付订单号，用于追踪支付、退款和争议）、`productId`、`failureCode`、`failureReason`。完整字段见官网 API Reference：履约结果通知 ([https://waffo.com/docs/api-reference/point-topup-fulfillment-webhook.md](https://waffo.com/docs/api-reference/point-topup-fulfillment-webhook.md))。

### 收到通知后怎么回应？重试规则是什么？

处理成功后，返回 HTTP `200 OK`，并且响应 body 里包含 `success`，例如：

```json theme={null}
{
  "message": "success"
}
```

否则 Waffo Point Topup 视为通知失败并重试：

| 项目 | 内容 |
| - | - |
| 重试间隔 | 5 秒、30 秒、1 分钟、5 分钟、30 分钟、1 小时、2 小时、4 小时、8 小时、24 小时、24 小时…… |
| 最大重试次数 | 15 次 |
| 达到上限后 | 通知被标记为失败，需要人工处理 |

## 漏收了通知，能主动查履约结果吗？

可以。Waffo 提供履约查询接口，是履约结果通知的**主动拉取版**，常用于对账，或在漏收通知时兜底。响应里的 `data` 结构和通知中的 `data` 一致。

| 项目 | 内容 |
| - | - |
| 方法 | `POST` |
| 路径 | `/api/v1/gamepin/fulfillment-inquiry` |
| 鉴权 | 通用接口签名（SHA256WithRSA），签名放在 `X-SIGNATURE` 请求头 |

查询键：`supplierId` 必填；`salesOrderId` 和 `payOrderId` 至少传一个。订单不存在或不属于调用方供应商时，返回错误，错误码见[供应商接入总览](https://waffo.com/docs/zh/support/point-topup-supplier-integration-overview-faq.md)。

```json theme={null}
{
  "supplierId": "WAFFO_POINT_TOPUP_001",
  "salesOrderId": "A123456"
}
```

完整参数与在线调试见官网 API Reference：履约查询 ([https://waffo.com/docs/api-reference/point-topup-fulfillment-inquiry/fulfillment-inquiry.md](https://waffo.com/docs/api-reference/point-topup-fulfillment-inquiry/fulfillment-inquiry.md))。

## 想了解更多？

* 官网：Mode C：直充 API ([https://waffo.com/docs/zh/developer-docs/point-topup/mode-c-direct-fulfillment.md](https://waffo.com/docs/zh/developer-docs/point-topup/mode-c-direct-fulfillment.md))


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.