# 通过 SDK 完成购买

> 获取购买信息并确定玩家购买的商品的购买状态。

> **Important:**
>
> 对于付款提供商，必须在调用`UnityIAPServices`或`StoreController`时指定商店名称 (`PaymentProvider.Name`)。`PaymentProvider.Name` 指定要使用直接到消费者 (D2C) 提供商集成，并且不返回提供商的显示名称。

Unity IAP 从商店获取购买信息，以便您的应用程序可以识别和满足玩家的购买需求。这样可以确保您的游戏可以根据用户的购买历史记录向用户交付内容或授权，即使用户在应用外部或其他设备上购买了物品也是如此。

购买表示为[`Order`](https://docs.unity3d.com/Packages/com.unity.purchasing@latest?subfolder=/api/UnityEngine.Purchasing.Order.html)对象。该`Order`包含有关购买的所有相关详细信息，并提供在商店中进行跟踪和管理所需的信息。

## 获取购买

可以从商店中检索用户进行的购买。但是，消耗品在使用后必须由应用程序进行跟踪，因为商店不会退回已用完的消耗品。对于非消耗品和订阅，商店会在您调用 [`FetchPurchases`](https://docs.unity3d.com/Packages/com.unity.purchasing@latest?subfolder=/api/UnityEngine.Purchasing.IPurchaseService.html#UnityEngine_Purchasing_IPurchaseService_FetchPurchases) 或 [`CheckEntitlement`](https://docs.unity3d.com/Packages/com.unity.purchasing@latest?subfolder=/api/UnityEngine.Purchasing.IPurchaseService.html#UnityEngine_Purchasing_IPurchaseService_CheckEntitlement_UnityEngine_Purchasing_Product_) 时准确返回这些购买。

## 确定购买状态

您可以通过两种方式确定购买状态：

* 使用 [`Order`](https://docs.unity3d.com/Packages/com.unity.purchasing@latest?subfolder=/api/UnityEngine.Purchasing.Orders.html) 确定`Order`是`PendingOrder`、`ConfirmedOrder`、`DeferredOrder`还是`FailedOrder`。
* 使用 `CheckEntitlement` 接收[`EntitlementStatus`](https://docs.unity3d.com/Packages/com.unity.purchasing@latest?subfolder=/api/UnityEngine.Purchasing.Entitlement.html#UnityEngine_Purchasing_Entitlement_Status)，返回 `EntitledButNotFinished`、`EntitledUntilConsumed`、`FullyEntitled`、`NotEntitled` 或 `Unknown`。

### 购买属性

| 属性              | 描述              |
| --------------- | --------------- |
| `transactionId` | 购买的唯一标识符。       |
| `product`       | 购买的商品。          |
| `quantity`      | 购买的商品数量。        |
| `receipt`       | 用于向商店验证购买的收据数据。 |

### 购买状态

| State       | 描述          |
| ----------- | ----------- |
| `Pending`   | 购买已付款但尚未完成。 |
| `Confirmed` | 购买已完成并确认。   |
| `Failed`    | 由于错误，购买失败。  |
| `Deferred`  | 购买正在等待付款。   |

## 处理购买

进行购买并等待实现时调用 [`OnPurchasePending`](https://docs.unity3d.com/Packages/com.unity.purchasing@latest?subfolder=/api/UnityEngine.Purchasing.IPurchaseService.html#UnityEngine_Purchasing_IPurchaseService_OnPurchasePending) 回调。此时，应用程序应该会完成购买，例如解锁本地内容或将购买收据发送到服务器以更新服务器端游戏模型。

请注意，在成功初始化后的任何时间都可以调用 `OnPurchasePending`。如果应用程序在执行`OnPurchasePending`处理程序期间崩溃，则在 Unity IAP 下次初始化时再次调用。考虑实现您自己的重复数据消除逻辑。

> **Note:**
>
> 如果未确认购买，商店会退回购买，有些商店甚至会自动退款以保护用户。

```cs
// Handle restore on initialization
private async void Start()
{
    // Setup, e.g. add listeners to your StoreController...
    m_StoreController.OnPurchasePending += OnPurchasePending;
    m_StoreController.OnPurchasesFetch += OnPurchasesFetch;
    await m_StoreController.Connect();

    // Fetch previous purchases (includes confirmed orders)
    m_StoreController.FetchPurchases();
}

// Handle new purchases and pending transactions
private void OnPurchasePending(PendingOrder order)
{
    ProcessPurchase(order);
}

// Handle fetched purchases (includes previously confirmed orders)
private void OnPurchasesFetch(Orders orders)
{
    foreach（在 orders.ConfirmedOrders 中更改 confirmedOrder）
    {
        if (confirmedOrder.CartOrdered.Items().FirstOrDefault()?.Product.definition.type != ProductType.Consumable)
        {
            // Mark non-consumable and subscription products as entitled on fetch, as they only need to be granted once
            MarkAsEntitled(confirmedOrder.CartOrdered.Items().FirstOrDefault().Product);
        }
    }
}

// Your ProcessPurchase logic
private void ProcessPurchase(PendingOrder order)
{
    foreach（var product in order.CartOrdered.Items()）
    {
        // Grant product
        GrantProduct(product);
    }
    // Confirm the order to finalize the transaction
    m_StoreController.ConfirmPurchase(order);
}
```

## 购买确认和可靠性

Unity IAP 要求您显式确认购买，以确保可靠地完成购买，即使在网络中断或应用程序崩溃期间也是如此。如果购买已付款但未兑现，Unity IAP 会在应用程序下次初始化时将购买内容传递给应用程序。此过程可防止购买流程中断时或在应用程序离线时完成购买时丢失购买。

成功执行购买后，请致电具有相关`PendingOrder`的[`ConfirmPurchase`](https://docs.unity3d.com/Packages/com.unity.purchasing@latest?subfolder=/api/UnityEngine.Purchasing.IPurchaseService.html#UnityEngine_Purchasing_IPurchaseService_ConfirmPurchase_UnityEngine_Purchasing_PendingOrder_)以向商店确认购买。

> **Warning:**
>
> 始终确认购买。否则，可能会出现以下情况：
>
> * Unity IAP 在每次后续`FetchPurchases`调用或会话开始时重新调用订单的`OnPurchasePending`，直到您确认为止。如果没有您自己的重复数据消除逻辑，则可以多次授予同一项目。
> * 某些商店会自动反转购买。例如，Google Play 会在三天后退回未确认的购买，Apple App Store 会在较不严格的时间轴上应用类似的保护逻辑。
> * 对于玩家来说，购买看起来会成功，然后消失，这看起来像是失败的购买。

> **Note:**
>
> 对于消耗品，一旦您确认购买，商店不会再次退回。始终远程保存消耗品奖励。如果在本地存储消耗品奖励，可能会丢失无法恢复的数据。

## 确认购买已保留到云端

如果要将消耗品购买保存到云端，必须在成功保存购买后调用 [`ConfirmPurchase`](https://docs.unity3d.com/Packages/com.unity.purchasing@latest?subfolder=/api/UnityEngine.Purchasing.IPurchaseService.html#UnityEngine_Purchasing_IPurchaseService_ConfirmPurchase_UnityEngine_Purchasing_PendingOrder_)。

返回 `Pending` 时，Unity IAP 会保持底层商店上的交易处于打开状态，直到确认为已处理。这样可确保即使用户在消耗品等待期间重新安装了应用程序，也不会丢失消耗品购买。

## 恢复购买##restore-purchases

允许用户在重新安装应用或切换设备时重新访问以前拥有的商品和订阅。了解 IAP 如何检索权利记录并授予访问权限：

* 用户重新安装应用程序时，Unity IAP 会在第一次`StoreController.FetchPurchases()`调用时恢复拥有的商品。
* IAP 使用包括所有购买（所有状态）的 `Orders` 对象来调用 `OnPurchasesFetched` 监听器。
* 如果 `PurchaseService.ProcessPendingOrdersOnPurchasesFetched` 设置为 `true`，则 IAP 会为每个未实现的购买调用 `OnPurchasePending` 监听器。
* 同一会话中的后续`FetchPurchases()`调用不会触发已在同一会话中看到的订单的`OnPurchasePending`。

## 将服务器端验证与 SDK 结合使用##use-server-side-validation-alongside-the-sdk

您可以在使用 Unity 应用内购 (IAP) SDK 时调用后端 API。这种混合方法允许您的服务器在您奖励玩家之前直接根据 Unity 的记录验证交易，从而充当授权者。与标准后端 API 方法不同，此方法不需要公开 Webhook 的公共终端，这样可以减少攻击面并简化服务器基础架构。

### 客户端实现##client-side-implementation

发起购买时，SDK 会返回一个 `PendingOrder` 对象。提取`OrderInfo.TransactionId`并将其传递给后端。

### 后端身份验证##backend-authentication

> **Important:**
>
> 要对 Unity IAP API 进行调用，后端必须进行 [Service Account](/cloud/accounts/create-service-account.md) 身份验证。

使用服务帐户中的 Key ID 和 Secret Key 执行令牌交换以接收无状态持有者令牌。有关更多信息，请参阅如何[使用无状态令牌验证 API](https://services.docs.unity.com/docs/service-account-auth/#authenticate-an-api-using-a-stateless-token)。

请参阅以下令牌交换终端：

```http
POST https://services.api.unity.com/auth/v2/token-exchange?projectId={projectId}&environmentId={envId}
```

### 验证订单##validate-the-order

后端拥有持有者令牌后，请使用以下请求查询 Unity IAP Order 服务：

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

`orderId`与上一步中的`TransactionId`相同。
请参阅以下重要的响应字段：

* 在授予物品之前，`status` 必须是 `paid`。
* `paymentProviderResourceId` 包含底层 Stripe Checkout Session ID（如果需要在 Stripe Dashboard（条带控制面板）中进行交叉引用）。

### 完成并完成订单##fulfill-and-complete-the-order

请参阅以下工作流程以使用服务器权限完成订单：

1. 验证状态是否已支付，并且 productSku 与预期商品匹配。
2. 使用新授权更新玩家的数据库。
3. 后端向客户端返回成功代码后，请使用以下方法之一在 Unity IAP 系统中完成交易：
   * [直接使用 API 更新订单](https://staging.docs.unity.com/en-us/iap/payment-providers/implement-backend.md#update-an-order)
   * 使客户端调用 `m_StoreController.ConfirmPurchase(order)`。

有关更多信息，请参阅有关如何[标记订单通过 API](./implement-backend.md#mark-orders-as-fulfilled-via-api) 完成的部分。

## 后续步骤##next-steps

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

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