--- title: "礼品码" description: "验证并兑换 Gift Codes、发放奖励,以及处理兑换 webhook。" source: "https://docs.tokenz.one/zh-CN/v2/campaigns/gift-codes" api_version: "v2" locale: "zh-CN" version_status: "current" docs_stage: "prod" --- # 礼品码 原名为兑换码。API 端点与事件名称保持不变。 了解如何通过 Tokenz 兑换码 API 验证和兑换活动码。 兑换码 API 帮助您开展玩家输入代码并获得奖励的活动。您的服务器验证代码、确认资格、执行兑换,然后处理 `redemption.completed` Webhook 进行奖励发放。 ## 概述 兑换流程包括: - **验证**:检查代码是否可兑换并返回奖励预览 - **兑换**:为特定玩家消耗代码并创建兑换记录 - **Webhook 发放**:接收 `redemption.completed` 并在您的游戏后端发放奖励 ## 活动设置与管理 活动和兑换码在 Tokenz 商户后台中创建和管理。商户使用后台进行以下操作: - 创建具有活动期间和奖励配置的活动 - 生成或管理兑换码 - 暂停、恢复或结束活动 ## 活动状态 活动可以处于以下状态之一: - `draft`:进行中。兑换不允许。所有字段可编辑 - `scheduled`:已发布并排队。在配置的 `start_at` 时间自动转为 `active` - `active`:活动已上线,兑换可以成功 - `paused`:已手动暂停。兑换将被拒绝。可恢复至 `active` - `ended`:活动已结束,兑换将被拒绝。此状态不可逆 `validate` 和 `redeem` 在活动处于 `active` 状态时会成功;活动处于 `scheduled` 状态且已到达 `startAt` 时同样会成功。从 `endAt` 起会被拒绝。`scheduled` 活动会在 `startAt` 自动转为 `active`,因此这一点只在状态切换前后的短暂时间内有影响。 ## 兑换码状态 兑换码状态根据活动状态和兑换码自身状态计算得出: - `pending`:兑换码有效,但活动当前未激活(例如 draft、scheduled 或 paused)。不允许兑换,但状态为 `scheduled` 且已到达 `startAt` 的活动可以兑换。 - `active`:兑换码可用。需要活动处于 active 状态。可兑换的状态不止这一种:`scheduled` 活动到达 `startAt` 后,`pending` 状态的兑换码同样可以兑换。 - `deactivated`:兑换码已停用。商户手动停用或活动结束时显示此状态。 显示的兑换码状态取决于活动状态: | 活动状态 | 兑换码状态 | | --- | --- | | `active` | `active` | | `draft`、`scheduled`、`paused` | `pending` | | `ended` | `deactivated` | 活动超过 `endAt` 后,即使兑换码状态仍显示为 `active`,也无法兑换。此时 API 将返回 `redemptionCode.campaign-invalid` 错误;尚未开始的活动也会返回同一错误。 如果商户手动停用了兑换码,无论活动状态如何,该兑换码始终显示为 `deactivated`。 此外,当 `currentRedemptions >= maxRedemptions` 时,Tokenz Dashboard会将兑换码显示为**已兑换**,但 API 状态仍为 `active`。这仅是控制台的显示状态,不会由 API 返回。 ## 兑换生命周期 1. 玩家在您的促销活动界面中输入代码 2. 您的服务器调用 `POST /v2/redemption-codes/validate` 3. 您的服务器向玩家显示奖励预览 4. 玩家确认兑换 5. 您的服务器调用 `POST /v2/redemption-codes/redeem` 6. Tokenz 返回成功的兑换响应 7. Tokenz 发送 `redemption.completed` Webhook 8. 您的后端发放奖励,并应使用 `redemptionId` 防止重复发放 ```mermaid sequenceDiagram; autonumber; participant P as 玩家; participant M as 商户服务器; participant T as Tokenz API; participant W as 商户 Webhook 处理器; P->>M: 输入兑换码; M->>T: POST /v2/redemption-codes/validate; T-->>M: 活动 + rewardPreview; P->>M: 确认兑换; M->>T: POST /v2/redemption-codes/redeem; T-->>M: redemptionId + reward; T->>W: webhook: redemption.completed; W->>W: 发放奖励,使用 redemptionId 防止重复发放; ``` ## 验证代码 验证检查活动和代码的资格,并返回活动预览和奖励预览。验证成功需要活动处于 active 状态,或处于 `scheduled` 状态且已到达 `startAt`,并且在 `endAt` 之前。 ```bash curl --request POST \ --url https://api.tokenz.one/v2/redemption-codes/validate \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer secret_test_YOUR_KEY_HERE' \ --data '{ "redemptionCode": "SPRING2026FREE", "playerId": "player_98765" }' ``` ### 请求字段 - `redemptionCode`(必填):玩家输入的代码 - `playerId`(可选):商户玩家标识符。如果提供,还将检查每位玩家的限制。如果省略,则仅验证兑换码和活动的全局限制 ### 成功响应(`200`) ```json { "campaign": { "id": "campaign_1AbCdEfGh12", "name": "Spring Campaign 2026" }, "rewardPreview": { "name": "SSR Reimu Card", "imageUrl": "https://images.example.com/rewards/reimu.png", "quantity": 1 } } ``` ## 兑换代码 兑换为玩家消耗代码并创建兑换记录。兑换成功需要活动处于 active 状态,或处于 `scheduled` 状态且已到达 `startAt`,并且在 `endAt` 之前。 ```bash curl --request POST \ --url https://api.tokenz.one/v2/redemption-codes/redeem \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer secret_test_YOUR_KEY_HERE' \ --data '{ "redemptionCode": "SPRING2026FREE", "playerId": "player_98765" }' ``` ### 请求字段 - `redemptionCode`(必填):玩家输入的代码 - `playerId`(必填):商户玩家标识符 ### 成功响应(`201`) ```json { "redemptionId": "redemption_1XyZaBcDe34", "code": "SPRING2026FREE", "playerId": "player_98765", "campaign": { "id": "campaign_1AbCdEfGh12", "name": "Spring Campaign 2026" }, "reward": { "skuRedemptionReward": { "type": "EXTERNAL_SKU", "sku": "ITEM_SSR_REIMU", "name": "SSR Reimu Card", "quantity": 1 } }, "redeemedAt": "2026-03-25T08:10:00Z", "kind": "CODE_REDEMPTION", "source": "CODE_REDEMPTION" } ``` ## 兑换 Webhook 兑换成功后,Tokenz 将向订阅此事件的 Webhook 端点发送 `redemption.completed`。 **发放奖励前必须先验证签名。** 每次投递都包含 `Tokenz-Signature` 标头。如果跳过验证,任何找到或猜到该端点 URL 的人都可能获得奖励。请针对原始请求体进行验证,然后再解析。具体步骤请参阅[保护你的 webhook 端点](https://docs.tokenz.one/zh-CN/v2/checkout/webhooks-get-started#3.-%E4%BF%9D%E6%8A%A4%E4%BD%A0%E7%9A%84-webhook-%E7%AB%AF%E7%82%B9)。只有在签名验证通过后,下列字段才可信任。 ```json { "id": "f75ff6f0-bdf9-4b25-bb96-968f8733a7e3", "object": "redemption.completed", "createdAt": "2026-03-25T08:10:00Z", "test": false, "eventData": { "type": "redemption", "version": "v2", "data": { "redemption": { "redemptionId": "redemption_1XyZaBcDe34", "campaign": { "id": "campaign_1AbCdEfGh12", "name": "Spring Campaign 2026" }, "code": "SPRING2026FREE", "playerId": "player_98765", "reward": { "skuRedemptionReward": { "type": "EXTERNAL_SKU", "sku": "ITEM_SSR_REIMU", "name": "SSR Reimu Card", "quantity": 1 } }, "redeemedAt": "2026-03-25T08:10:00Z", "kind": "CODE_REDEMPTION", "source": "CODE_REDEMPTION" } } } } ``` 使用顶层事件 `id` 进行 Webhook 投递追踪,使用 `redemptionId` 防止后端中的重复发放。 [免费道具领取](https://docs.tokenz.one/zh-CN/v2/free-items)也会发送同一事件。兑换码的 `kind` 为 `CODE_REDEMPTION`,免费道具为 `FREE_ITEM_CLAIM`。`source` 已弃用,仅为兼容保留;新集成使用 `kind`。较早进入队列的事件可能省略 `kind`。免费道具领取不包含 `code`。 ## 错误处理 兑换端点返回带有结构化错误对象的标准 HTTP 状态码。 ### 常见错误 - `401 Unauthorized`:无效或缺少 API 密钥 - `403 Forbidden`:API 密钥没有所需权限范围 - `404 Not Found`(`entity.not-found`):兑换码未找到 - `422 Unprocessable Entity`(`campaign.limit-reached`):活动已达到其兑换总数限制 - `422 Unprocessable Entity`(`campaign.player-limit-reached`):玩家已达到此活动的玩家兑换限制 - `422 Unprocessable Entity`(`redemptionCode.campaign-invalid`):活动未激活、超出有效时间窗口,或代码已停用。错误的 `message` 字段可区分这些原因 - `422 Unprocessable Entity`(`redemptionCode.limit-reached`):兑换码已达到自身的使用限制 - `422 Unprocessable Entity`(`redemptionCode.player-limit-reached`):玩家已达到此兑换码的每码兑换限制 - `429 Too Many Requests`:超出速率限制 - `500 Server Error`:意外的服务器错误 ## 测试 使用测试模式开发和测试您的兑换集成: 1. 使用测试 API 密钥(以 `test_` 为前缀)来操作测试活动和兑换码 2. 在 Tokenz 商户后台中使用测试模式创建测试活动 3. 测试兑换与正式数据完全隔离 — 测试 API 密钥只能兑换测试码,正式 API 密钥只能兑换正式码 4. 测试实体 ID 包含 `_t` 后缀(例如 `redemption_1XyZaBcDe34_t`) 5. 测试 Webhook 仅发送到您的测试 Webhook 端点,载荷中包含 `"test": true`