# 通过后端 API 完成购买

> 实现您自己的后端，监听购买事件并跟踪玩家的权利。

如果您的游戏使用服务器授权，请实现一个执行以下操作的后端系统：

1. [验证 Webhook 事件 (JWT)](#validate-webhook-events-\(jwt\))。
2. [解析事件](#webhook-event-shape)。
3. 根据[事件类型](#event-types)更新系统中的授权或清单。
4. [将订单标记为已完成](#mark-orders-as-fulfilled-via-api)。
5. [返回事件响应](#return-an-event-response)。

这样可以确保您的系统与 Unity IAP 保持同步。

> **Important:**
>
> 即使后端授予玩家权限，也必须将每个已付款订单标记为已完成。无论哪种方式，玩家都会保留其授权，但保持`paid`状态的订单会使 Unity IAP 订单跟踪和分析不完整。在混合 SDK 和 Webhook 的混合设置中，未确认的订单也可能导致系统之间的订单状态漂移。

## 验证 Webhook 事件 (JWT)##validate-webhook-events-(jwt)

为了启用验证，Unity IAP 使用 JSON Web Token (JWT) 对 Webhook 请求进行签名。在后端验证此令牌的以下信息以确保事件真实可靠：

* Authorization 标头
* "signature":使用私钥签名。获取并缓存 \*\*JWKS（公钥）\*\*以验证：[`https://services.api.unity.com/webhooks/.well-known/jwks.json`](https://services.api.unity.com/webhooks/.well-known/jwks.json)
* **发行商**：`https://services.api.unity.com/webhooks/`
* **受众声明（数组）**：`upid` (Unity Project Id)、`envId` (Environment Id)
* **令牌到期**：确保`exp`（到期）未通过。

大[多数语言的库](https://www.jwt.io/libraries?programming_language)可用于验证 JWT。

## Webhook 事件形状##webhook-event-shape

Unity IAP 将来自所有受支持的支付提供商的事件标准化为这种一致的格式。请参阅以下示例文件：

```json
{
  "id": "018d5e5e-5e5e-7e5e-5e5e-5e5e5e5e5e5e",
  "version": "1.0.0",
  "eventType": "order.paid",
  "time":"2024-01-15T14:30:00Z",
  "projectId":"018d5e5e-1111-7e5e-5e5e-111111111111",
  "environmentId":"018d5e5e-2222-7e5e-5e5e-222222222222",
  "dataType": "order",
  "data": {
    "id": "018d5e5e-3333-7e5e-5e5e-333333333333",
    "playerId": "player_12345",
    "paymentProvider": "stripe",
    "paymentProviderResourceId": "cs_test_a1b2c3d4e5f6",
    "url": "https://checkout.stripe.com/pay/cs_test_a1b2c3d4e5f6",
    "lineItems": [
      {
        "sku": "com.game.coins_100",
        "productType":“消耗品”，
        "price": {
          "amountMicros":"25270591"
          "currency":"USD"
        }
      }
    ],
    "total": {
      "amountMicros":"25270591"
      "currency":"USD",
      "refundedAmountMicros":0
    },
    "status": "paid",
    "customReferenceId": "order_xyz_789",
    "metadata": {
      "campaign": "summer_sale",
      "platform": "iOS"
    },
    "createdAt":"2024-01-15T14:25:00Z",
    "updatedAt":"2024-01-15T14:30:00Z",
    "paidAt":"2024-01-15T14:30:00Z",
    "fulfilledAt": null
  }
}
```

请参阅 Webhook 事件中的以下字段：

**id** (string, required): Webhook 事件的唯一标识符。**version** (string, required): Webhook 事件架构的版本。**eventType** (string, required): 事件的类型。请参阅[事件类型](#event-types)以了解可能的值。**time** (string, required): 生成事件的时间（ISO 8601 格式）。**projectId** (string, required): 此订单所属的 Unity 项目的 ID。**environmentId** (string, required): 发生此顺序的 Unity 环境的 ID。**dataType** (string, required): 事件中的数据类型。**data** (object, required): 包含购买详细信息的订单数据对象。**id** (string, required): 订单的唯一标识符。**playerId** (string, required): Unity 身份验证玩家 ID。**paymentProvider** (string, required): 处理订单的付款提供商，例如`stripe`或`coda`。**paymentProviderResourceId** (string, required): 付款提供商分配给资源的唯一 ID。**url** (string, required): 订单的签出 URL。**lineItems** (object array, required): 订单中购买的商品列表。**sku** (string, required): 商品的唯一标识符。**productType** (string, required): 商品类型，例如 `Consumable`。**price** (object, required): 商品的价格。**amountMicros** (integer, required): 以微米为单位的价格（1,000,000 micros 等于 $1.00）。这些值是向用户显示的值。**currency** (string, required): 三个字母的 ISO 4217 货币代码。**total** (object, required): 总订单金额。**amountMicros** (integer, required): 总订单金额（以微秒为单位）。**currency** (string, required): 三个字母的 ISO 4217 货币代码。**refundedAmountMicros** (integer, required): 退款总额（以微克为单位）。在部分退款中，此金额可能小于`amountMicros`。**status** (string, required): 订单的状态。可能的值有：`created`、`paid`、`failed`、`fulfilled`、`revoked`。**customReferenceId** (string): 订单的可选自定义参考 ID。**metadata** (object): 一个键值对象，包含购买流程期间传递的任何自定义元数据。**createdAt** (string, required): 创建订单时的时间戳（ISO 8601 格式）。**updatedAt** (string, required): 上次更新订单的时间戳（ISO 8601 格式）。**paidAt** (string): 支付订单的时间戳（ISO 8601 格式）。**fulfilledAt** (string): 完成订单的时间戳，如果尚未完成，则`null`。**revokedAt** (string): 撤销订单时的时间戳，如果未撤销，则`null`。

## 事件类型##event-types

Unity IAP 发送以下 Webhook 事件类型：

### order.paid##order.paid

当玩家成功完成购买时，Unity IAP 会发送 `order.paid` 事件。收到此事件后，请向玩家授予他们购买的权利或货币，并将[订单标记为已执行](#mark-orders-as-fulfilled-via-api)。

### order.updated##order.updated

Unity IAP 会在每次更新订单时发送 `order.updated` 事件。这包括以下更改：

* 标记为已完成的顺序。
* 正在处理的退款。
* 其他顺序修改。

`data.total` 中的 `refundedAmountMicros` 字段反映了当前退款总额。

退款由开发者发起。例如，您可以通过付款提供商的后台向玩家发放退款。

Unity IAP 在发生退款时不会自动撤销授权。如果要在退款时撤销授权，可以监听此事件并在您自己的授权系统中进行处理。

### order.revoked##order.revoked

当订单状态更改为 `revoked` 时，Unity IAP 会发送 `order.revoked` 事件。收到此事件后，撤销玩家在最初购买时获得的权利或货币。

按存储容量使用计费由玩家发起。例如，玩家可能会通过信用卡公司或银行对收费提出异议。

当按存储容量使用计费争议结束并且玩家赢得争议时，Unity IAP 会自动撤销订单并发送此事件。与退款不同，Unity IAP 会撤销退款授权。

## 通过 API 将订单标记为已完成##mark-orders-as-fulfilled-via-api

在后端授予授权后，使用 Orders API 将订单标记为已完成。这会更新`fulfilledAt`时间戳并确保正确跟踪订单状态。

### 身份验证##authentication

要调用这些终端，需要使用以下方法之一进行身份验证：

* 服务帐户在 Unity Dashboard（Unity 后台）中创建一个服务帐户，并用它来验证后端服务器。有关更多信息，请参阅[服务帐户身份验证](https://services.docs.unity.com/docs/service-account-auth/)。
* **Cloud Code**从 Cloud Code 脚本或模块调用终端，该脚本或模块可以代表项目进行身份验证。有关更多信息，请参阅[使用 Cloud Code 模块实现购买](./cloud-code-fulfillment.md)。

### 获取顺序##get-order

调用 [`GET` 终端](/oas-iap-client/1.0.0/.md#get-order-status)以检索订单。

```http
GET https://iap.services.api.unity.com/v1/projects/{projectId}/environments/{environmentId}/orders/{orderId}
```

#### 订单状态##order-statuses

| **状态**      | **描述**                                             |
| ----------- | -------------------------------------------------- |
| `created`   | 订单已创建，但尚未完成付款。可以转换到 `paid`、`failed` 或 `cancelled`。 |
| `paid`      | 付款已收到并由付款提供商确认。可以转换到 `fulfilled` 或 `revoked`。      |
| `fulfilled` | 订单已完成，玩家已获得奖励。`可以转换到`revoked\`                     |
| `failed`    | 订单失败。这是终端状态。                                       |
| `revoked`   | 订单已撤销。这是终端状态。                                      |
| `cancelled` | 订单在付款前已取消。这是终端状态。                                  |

#### 响应正文示例##example-response-body

`GET` 和 `PATCH` 终端都返回完整顺序对象：

```json
{
  "id": "018d5e5e-3333-7e5e-5e5e-333333333333",
  "projectId":"018d5e5e-1111-7e5e-5e5e-111111111111",
  "environmentId":"018d5e5e-2222-7e5e-5e5e-222222222222",
  "playerId": "player_12345",
  "paymentProvider": "stripe",
  "paymentProviderResourceId": "cs_test_12345",
  "url": "https://checkout.stripe.com/pay/cs_test_12345",
  "lineItems": [
    {
      "sku": "com.game.coins_100",
      "productType":“消耗品”
    }
  ],
  "status": "paid",
  "fulfilledAt": null,
  "revokedAt": null,
  "customReferenceId": "order_xyz_789",
  "metadata": {
    "campaign": "summer_sale"
  },
  "createdAt":"2024-01-15T14:25:00Z",
  "updatedAt":"2024-01-15T14:30:00Z"
}
```

### 更新订单##update-an-order

调用 [`PATCH` 终端](/oas-iap-client/1.0.0/.md#update-an-order)以更新订单的状态：

```http
PATCH https://iap.services.api.unity.com/v1/projects/{projectId}/environments/{environmentId}/orders/{orderId}
```

#### 请求正文##request-body

```json
{
  "status": "fulfilled"
}
```

**status** (string, required): 要为订单设置的新状态。允许的值：。`fulfilled`在授予玩家权利后设置。只能根据`paid`订单进行设置。
设置为撤销玩家的权利（例如，退款后）。只能根据`paid`或`fulfilled`订单进行设置。
设置为取消订单。只能在`created`订单上设置（付款前）。一旦订单为 `revoked` 或 `cancelled`，就无法更改其状态。

请参阅 IAP Client API 文档以了解 [HTTP 响应状态代码](/oas-iap-client/1.0.0/.md#http-response-status-codes-for-update-an-order:)。

## 返回事件响应##return-an-event-response

您需要返回事件响应来指示处理事件的成功或失败。

* "response": 指示事件处理成功。
* **非`2xx`响应**：指示失败。Unity IAP 将根据重播策略[重播事件](#event-replay-behavior)。

### 事件重放行为##event-replay-behavior

当您的 Webhook 终端返回非 `2xx` 响应时，Unity IAP 会自动重试事件的传递。确保 Webhook 处理程序幂等，以免重放的事件导致重复授权。

## 后续步骤##next-steps

本页面是使用 IAP 设置直接到消费者 (D2C) 付款提供商的工作流程的一部分。要继续此工作流程，请选择以下选项之一：

[Integrate D2C payment providers](./workflow.md#fulfill-purchases-through-the-backend-api): 返回到 Integration D2C payment providers with IAP workflow（将 D2C 支付提供商与 IAP 工作流程集成）页面。
[Test your integration](./test-integration.md): 继续工作流程中的下一步以设置 D2C 支付提供商。
