> For the complete documentation index, see [llms.txt](https://developers-apps-in-toss.toss.im/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-zh/common/monetization/iap/in-app-purchase.md).

# 应用内支付

用于一次性购买即完成的商品，如消耗品、非消耗品的单次支付 SDK。服务介绍和控制台设置方法请见 [应用内支付介绍文档](https://developers-apps-in-toss.toss.im/guide/monetization/in-app-payment)。

{% hint style="info" %}
**BaseURL**

`https://apps-in-toss-api.toss.im`
{% endhint %}

{% hint style="info" %}
**服务器间通信需要 mTLS 证书**

应用内支付的订单状态查询 API 是从合作伙伴服务器调用到 App in Toss 服务器的服务器间通信。为保障安全，请先在服务器上设置 mTLS 证书后再调用。证书签发方法请参考 mTLS 证书签发方法。
{% endhint %}

请按以下顺序进行对接流程。

1. [获取商品列表](#getproductitemlist) — `getProductItemList`
2. [发起支付请求](#createonetimepurchaseorder) — `createOneTimePurchaseOrder`
3. [恢复未完成订单](#getpendingorders) — `getPendingOrders`, `completeProductGrant`
4. [查询订单状态](#getcompletedorrefundedorders) — `getCompletedOrRefundedOrders` 或 [订单状态查询 API](#주문-상태-조회-api)

{% hint style="info" %}
**请注意**

* SDK **1.1.3 版本及以上**请使用。
  * 从 SDK 1.1.3 版本开始， **商品发放完成流程**新增了该流程，因此函数接口发生了变更。
* SDK **1.2.2 版本**起 **购买恢复功能**已新增。
* **即使用户设备变更，也请务必对接，以便应用内支付商品可以保持发放状态。**
  * 请使用原生存储功能。
  * 请使用 Toss 登录联动和应用内支付状态查询 API。
* 使用应用内支付状态查询 API 之前，请务必先进行 Toss 登录联动。
  {% endhint %}

***

### IAP 对象

`IAP`是一个汇集应用内支付相关函数的对象。

{% hint style="info" %}
**请注意**

从 Toss App 5.219.0 版本开始支持。在不支持应用内支付的版本中， `undefined`会返回。
{% endhint %}

**签名**

```typescript
IAP {
  getProductItemList: typeof getProductItemList;
  createOneTimePurchaseOrder: typeof createOneTimePurchaseOrder;
  getPendingOrders: typeof getPendingOrders;
  getCompletedOrRefundedOrders: typeof getCompletedOrRefundedOrders;
  completeProductGrant: typeof completeProductGrant;
}
```

**属性**

* getProductItemListtypeof getProductItemList

  这是一个用于获取可通过应用内支付购买的商品列表的函数。详情请见 [getProductItemList](#getproductitemlist)。
* createOneTimePurchaseOrdertypeof createOneTimePurchaseOrder

  这是一个用于发起应用内支付的函数。详情请见 [createOneTimePurchaseOrder](#createonetimepurchaseorder)。
* getPendingOrderstypeof getPendingOrders

  获取待处理订单列表。详情请见 [getPendingOrders](#getpendingorders) 文档。
* getCompletedOrRefundedOrderstypeof getCompletedOrRefundedOrders

  获取通过应用内支付购买或退款的订单列表。详情请见 [getCompletedOrRefundedOrders](#getcompletedorrefundedorders) 文档。
* completeProductGranttypeof completeProductGrant

  向应用传递商品发放处理已完成的消息。详情请见 [completeProductGrant](#completeproductgrant) 文档。

### 查看商品列表 <a href="#getproductitemlist" id="getproductitemlist"></a>

**SDK 函数：** `getProductItemList`

`getProductItemList` 是一个包含可通过应用内支付购买的商品列表的函数。用于在界面上展示商品列表时。

**签名**

```typescript
function getProductItemList(): Promise<{ products: IapProductListItem[] } | undefined>;
```

**返回值**

* `Promise<{ products: IapProductListItem\[] } | undefined>`

  返回包含商品列表的对象。如果应用版本低于最低支持版本(5.219.0)，则 `undefined`会返回。

**属性**

```typescript
interface IapProductListItem {
  sku: string;
  displayAmount: string;
  displayName: string;
  iconUrl: string;
  description: string;
}
```

* IapProductListItem

  这是一个包含可通过应用内支付购买的单个商品信息的对象。用于在界面上展示商品列表时。
* **sku** · 必需 · `string`

  商品的唯一 ID。 [IAP.createOneTimePurchaseOrder](#createonetimepurchaseorder)时使用的 `productId`相同的值。

**示例**

获取可购买的应用内支付商品列表

{% tabs %}
{% tab title="js" %}

```js
import { IAP } from '@apps-in-toss/web-framework';

async function handleGetProductItemList() {
  const response = await IAP.getProductItemList();

  return response?.products ?? [];
}
```

{% endtab %}

{% tab title="React" %}

```tsx
import { IAP, IapProductListItem } from '@apps-in-toss/web-framework';
import { Button, List, ListRow } from '@toss/tds-mobile';
import { useEffect, useState, useCallback } from 'react';

function IapProductList() {
  const [products, setProducts] = useState<IapProductListItem[]>([]);

  const handleBuy = useCallback(() => {
    const cleanup = IAP.createOneTimePurchaseOrder({
      options: {
        sku,
        processProductGrant: ({ orderId }) => {
          // 请编写商品发放逻辑。
          return true; // 返回商品是否发放。
        },
      },
      onEvent: (event) => {
        console.log(event);

        if (event.type === 'success') {
          cleanup();
        }
      },
      onError: (error) => {
        console.error(error);
        cleanup();
      },
    });
  }, [sku]);

  useEffect(() => {
    async function fetchProducts() {
      try {
        const response = await IAP.getProductItemList();
        setProducts(response?.products ?? []);
      } catch (error) {
        console.error('获取商品列表失败：', error);
      }
    }

    fetchProducts();
  }, []);

  return (
    <List>
      {products.map((product) => (
        <ListRow
          key={product.sku}
          left={<ListRow.Image type="square" src={product.iconUrl} />}
          contents={
            <ListRow.Texts
              type="3RowTypeA"
              top={product.displayName}
              middle={product.description}
              bottom={product.displayAmount}
            />
          }
          right={
            <Button size="medium" onClick={() => handleBuy(product.sku)}>
              购买
            </Button>
          }
        />
      ))}
    </List>
  );
}
```

{% endtab %}

{% tab title="React Native" %}

```tsx
import { IAP, IapProductListItem } from '@apps-in-toss/framework';
import { Button, List, ListRow } from '@toss/tds-react-native';
import { useEffect, useState, useCallback } from 'react';

function IapProductList() {
  const [products, setProducts] = useState<IapProductListItem[]>([]);

  const handleBuy = useCallback(() => {
    const cleanup = IAP.createOneTimePurchaseOrder({
      options: {
        sku,
        processProductGrant: ({ orderId }) => {
          // 请编写商品发放逻辑。
          return true; // 返回商品是否发放。
        },
      },
      onEvent: (event) => {
        console.log(event);

        if (event.type === 'success') {
          cleanup();
        }
      },
      onError: (error) => {
        console.error(error);
        cleanup();
      },
    });
  }, [sku]);

  useEffect(() => {
    async function fetchProducts() {
      try {
        const response = await IAP.getProductItemList();
        setProducts(response?.products ?? []);
      } catch (error) {
        console.error('获取商品列表失败：', error);
      }
    }

    fetchProducts();
  }, []);

  return (
    <List>
      {products.map((product) => (
        <ListRow
          key={product.sku}
          left={<ListRow.Image type="square" source={{ uri: product.iconUrl }} />}
          right={
            <Button size="medium" onPress={() => handleBuy(product.sku)}>
              购买
            </Button>
          }
          contents={
            <ListRow.Texts
              type="3RowTypeA"
              top={product.displayName}
              middle={product.description}
              bottom={product.displayAmount}
            />
          }
        />
      ))}
    </List>
  );
}
```

{% endtab %}
{% endtabs %}

**示例响应**

```json
{
  "products": [
    {
      "sku": "sku1",
      "displayName": "去除广告",
      "displayAmount": "4,900韩元",
      "iconUrl": "https://cdn.example.com/icons/premium-monthly.png",
      "description": "去除广告并提供高级功能"
    },
    {
      "sku": "sku2",
      "displayName": "100个金币",
      "displayAmount": "9,900韩元",
      "iconUrl": "https://cdn.example.com/icons/coin-100.png",
      "description": "可在应用内使用的100个金币"
    }
  ]
}
```

**体验示例应用**

[apps-in-toss-examples](https://github.com/toss/apps-in-toss-examples) 在仓库中 [with-in-app-purchase](https://github.com/toss/apps-in-toss-examples/tree/main/with-in-app-purchase) 下载代码并体验吧。

### 发起一次性支付请求 <a href="#createonetimepurchaseorder" id="createonetimepurchaseorder"></a>

**SDK 函数：** `createOneTimePurchaseOrder`

`createOneTimePurchaseOrder` 函数会弹出应用内支付支付窗口，用户将进行支付。如果支付过程中发生错误，会根据错误类型跳转到错误页面。

{% hint style="info" %}
**请参考**

支付成功后 30 秒内 `processProductGrant` 如果未调用回调或该回调结果不是 true， `{appName} 出现问题了。请申请退款` 页面可能会展示。
{% endhint %}

**签名**

```typescript
function createOneTimePurchaseOrder(params: IapCreateOneTimePurchaseOrderOptions): () => void;
```

**参数**

```typescript
interface IapCreateOneTimePurchaseOrderOptions {
  options: { sku: string; processProductGrant: (params: { orderId: string }) => boolean | Promise<boolean> };
  onEvent: (event: SuccessEvent) => void | Promise<void>;
  onError: (error: unknown) => void | Promise<void>;
}

interface IapCreateOneTimePurchaseOrderResult {
  orderId: string;
  displayName: string;
  displayAmount: string;
  amount: number;
  currency: string;
  fraction: number;
  miniAppIconUrl: string | null;
}

interface SuccessEvent {
  type: 'success';
  data: IapCreateOneTimePurchaseOrderResult;
}
```

* **options** · 必填

  是应用内支付所需的选项。

  * **params.sku** · 必需 · `string`

    是要下单的商品 ID。
  * **params.processProductGrant** · 必需 · `(params: { orderId: string }) => boolean | Promise<boolean>`

    在订单创建后实际发放商品时调用。 `orderId`后，返回发放成功与否 `true` 或 `Promise<true>`。如果发放失败，则返回 `false`会返回。
* **onEvent** · 必需 · `(event: SuccessEvent) => void | Promise<void>`

  在支付成功时调用。

  * **event.type** · 必需 · `"success"`

    是事件的类型。 `"success"`会返回。
  * **event.data** · 必需 · `IapCreateOneTimePurchaseOrderResult`

    应用内支付完成后会返回包含支付明细和商品信息的结果。可使用返回的信息在界面上展示已购买商品的信息。

    * **event.data.orderId** · 必需 · `string`

      支付订单 ID。支付完成后在查询支付状态时使用。 [查询支付状态](https://developers-apps-in-toss.toss.im/api/getIapOrderStatus.html)时使用。
    * **event.data.displayName** · 必需 · `string`

      要在界面上显示的商品名称。
    * **event.data.displayAmount** · 必需 · `string`

      包含货币单位的价格信息。
    * **event.data.amount** · 必需 · `number`

      商品价格的数值。
    * **event.data.currency** · 必需 · `string`

      商品价格的货币单位。
    * **event.data.fraction** · 必需 · `number`

      用于决定价格显示时保留到小数点后几位的值。
    * **event.data.miniAppIconUrl** · `string | null`

      迷你应用图标图片的 URL。
* **onError** · 必需 · `(error: unknown) => void | Promise<void>`

  在支付过程中发生错误时调用。可接收错误对象并进行日志记录或执行恢复流程。

**错误代码**

* INVALID\_PRODUCT\_ID : 商品 ID 无效，或该商品不存在。请确认商品 ID。

  在商品 ID 无效或该商品不存在时发生。

**返回值**

* () => void

  返回 app bridge cleanup 函数。应用内支付功能结束后，务必调用此函数释放资源。

**示例**

跳转到特定应用内支付订单页

{% tabs %}
{% tab title="js" %}

```js
import { IAP } from '@apps-in-toss/web-framework';

let cleanup;

function handleBuyProduct(sku) {
  cleanup = IAP.createOneTimePurchaseOrder({
    options: {
      sku,
      processProductGrant: ({ orderId }) => {
        console.log('执行商品发放逻辑：', orderId);
        return true;
      },
    },
    onEvent: (event) => {
      console.log('事件：', event);
      cleanup?.();
    },
    onError: (error) => {
      console.error('应用内支付失败：', error);
      cleanup?.();
    },
  });
}

window.addEventListener('pagehide', () => {
  cleanup?.();
});
```

{% endtab %}

{% tab title="React" %}

```tsx
import { IAP } from '@apps-in-toss/web-framework';
import { Button } from '@toss/tds-mobile';
import { useCallback } from 'react';

interface Props {
  sku: string;
}

function IapCreateOneTimePurchaseOrderButton({ sku }: Props) {
  const handleBuy = useCallback(() => {
    const cleanup = IAP.createOneTimePurchaseOrder({
      options: {
        sku,
        processProductGrant: ({ orderId }) => {
          // 请编写商品发放逻辑。
          return true; // 返回商品是否发放。
        },
      },
      onEvent: (event) => {
        console.log(event);
        cleanup();
      },
      onError: (error) => {
        console.error(error);
        cleanup();
      },
    });
  }, [sku]);

  return <Button onClick={handleBuy}>购买</Button>;
}
```

{% endtab %}

{% tab title="React Native" %}

```tsx
import { IAP } from '@apps-in-toss/framework';
import { Button } from '@toss/tds-react-native';
import { useCallback } from 'react';

interface Props {
  sku: string;
}

function IapCreateOneTimePurchaseOrderButton({ sku }: Props) {
  const handleClick = useCallback(() => {
    const cleanup = IAP.createOneTimePurchaseOrder({
      options: {
        sku,
        processProductGrant: ({ orderId }) => {
          // 请编写商品发放逻辑。
          return true; // 返回商品是否发放。
        },
      },
      onEvent: (event) => {
        console.log(event);
        cleanup();
      },
      onError: (error) => {
        console.error(error);
        cleanup();
      },
    });
  }, []);

  return <Button onPress={handleClick}>购买</Button>;
}
```

{% endtab %}
{% endtabs %}

**体验示例应用**

[apps-in-toss-examples](https://github.com/toss/apps-in-toss-examples) 在仓库中 [with-in-app-purchase](https://github.com/toss/apps-in-toss-examples/tree/main/with-in-app-purchase) 下载代码并体验吧。

### 查看未完成订单 <a href="#getpendingorders" id="getpendingorders"></a>

**SDK 函数：** `getPendingOrders`

`getPendingOrders` 是 **已完成支付但商品尚未发放的订单列表**用于获取该订单的函数。请确认查询到的订单信息并向用户发放商品。 `createOneTimePurchaseOrder` 即使函数调用后未收到结果，也可以查询该订单。

如果应用版本低于最低支持版本（Android 5.234.0、iOS 5.231.0），则 `undefined`会返回。

**签名**

```typescript
function getPendingOrders(): Promise<{ orders: Order[] } | undefined>;
```

**返回值**

* `Promise<{ orders: Order\[] } | undefined>`

  返回包含待处理订单列表(orders)的对象。如果应用版本低于最低支持版本（Android 5.234.0、iOS 5.231.0），则 `undefined`会返回。

**返回对象属性**

```tsx
interface Order {
  orderId: string;
  sku: string;
  paymentCompletedDate?: string;
}
```

* **orders** · 必需 · `Order\[]`

  这是待处理订单的数组。若没有待处理订单，则返回空数组。
* **orders\[].orderId** · 必需 · `string`

  订单的唯一 ID。
* **orders\[].sku** · 必需 · `string`

  订单商品的唯一 ID。
* **orders\[].paymentCompletedDate** · 必需 · `string`

  表示支付完成的时间点。

{% hint style="info" %}
**字段更新说明**

* **SDK 1.4.2**: `sku` 新增了该字段。该字段仅在 **Android 5.234.0 及以上、iOS 5.231.0 及以上**返回。
* **SDK 1.4.8**: `paymentCompletedDate` 新增了该字段。可确认支付完成时间。
  {% endhint %}

**示例**

{% tabs %}
{% tab title="js" %}

```js
import { IAP } from '@apps-in-toss/web-framework';

async function fetchOrders() {
  try {
    const pendingOrders = await IAP.getPendingOrders();
    return pendingOrders;
  } catch (error) {
    console.error(error);
  }
}
```

{% endtab %}

{% tab title="React" %}

```tsx
import { IAP } from '@apps-in-toss/web-framework';

async function fetchOrders() {
  try {
    const pendingOrders = await IAP.getPendingOrders();
    return pendingOrders;
  } catch (error) {
    console.error(error);
  }
}
```

{% endtab %}

{% tab title="React Native" %}

```tsx
import { IAP } from '@apps-in-toss/framework';

async function fetchOrders() {
  try {
    const pendingOrders = await IAP.getPendingOrders();
    return pendingOrders;
  } catch (error) {
    console.error(error);
  }
}
```

{% endtab %}
{% endtabs %}

### 处理商品发放完成 <a href="#completeproductgrant" id="completeproductgrant"></a>

**SDK 函数：** `completeProductGrant`

`completeProductGrant` 该函数 **用于完成待处理订单商品发放的函数**。向用户发放商品后， `completeProductGrant` 请通过该函数将商品发放标记为完成。

如果应用版本低于最低支持版本（Android 5.231.0、iOS 5.231.0），则 `undefined`会返回。

**签名**

```typescript
function completeProductGrant(params: {
  params: {
    orderId: string;
  };
}): Promise<boolean | undefined>;
```

**参数**

* { params: { orderId: string } }

  这是包含支付完成订单信息的对象。

  * **params.order** · `Id string`

    订单的唯一 ID。用于指定要完成商品发放的订单。

**返回值**

* `Promise<boolean | undefined>`

  返回商品发放是否完成。如果应用版本低于最低支持版本（Android 5.233.0、iOS 5.233.0），则 `undefined`会返回。

**示例**

{% tabs %}
{% tab title="js" %}

```js
import { IAP } from '@apps-in-toss/web-framework';

async function handleCompleteProductGrant(orderId) {
  try {
    await IAP.completeProductGrant({ params: { orderId } });
  } catch (error) {
    console.error(error);
  }
}
```

{% endtab %}

{% tab title="React" %}

```tsx
import { IAP } from '@apps-in-toss/web-framework';

async function handleCompleteProductGrant(orderId: string) {
  try {
    await IAP.completeProductGrant({ params: { orderId } });
  } catch (error) {
    console.error(error);
  }
}
```

{% endtab %}

{% tab title="React Native" %}

```tsx
import { IAP } from '@apps-in-toss/framework';

async function handleCompleteProductGrant(orderId: string) {
  try {
    await IAP.completeProductGrant({ params: { orderId } });
  } catch (error) {
    console.error(error);
  }
}
```

{% endtab %}
{% endtabs %}

### 查询已完成·退款订单 <a href="#getcompletedorrefundedorders" id="getcompletedorrefundedorders"></a>

**SDK 函数：** `getCompletedOrRefundedOrders`

`getCompletedOrRefundedOrders` 获取通过应用内支付购买和退款的订单列表。可查询应用内支付支付及商品发放已完成的订单和已退款的订单。

已完成支付但商品尚未发放的订单不会被查询。 [`getPendingOrders`](#getpendingorders)通过 `orderId`查询 [`completeProductGrant`](#completeproductgrant)请在通过该函数为用户发放商品后，

如果应用版本低于最低支持版本（Android 5.231.0、iOS 5.231.0），则 `undefined`会返回。

{% hint style="info" %}
**分页**

* **每页最多 50 个** 订单会返回。
* 当存在下一页时， `hasNext`为 `true`，且响应中的 `nextKey`作为下一次调用的参数传递，以继续查询。 `key` 参数传递，以继续查询。
  {% endhint %}

**签名**

```typescript
function getCompletedOrRefundedOrders(params?: {
  key?: string | null;
}): Promise<CompletedOrRefundedOrdersResult | undefined>;
```

**返回值**

* `Promise<{ CompletedOrRefundedOrdersResult } | undefined>`

  返回包含分页订单列表的对象。如果应用版本低于最低支持版本（Android 5.231.0、iOS 5.231.0），则 `undefined`会返回。

**返回对象属性**

```tsx
interface CompletedOrRefundedOrdersResult {
  hasNext: boolean;
  nextKey?: string | null;
  orders: {
    orderId: string;
    sku: string;
    status: 'COMPLETED' | 'REFUNDED';
    date: string;
  }[];
}
```

* **hasNext** · 必需 · `boolean`

  是否有下一页。 `` `true` ``则还有更多订单。
* **nextKey（可选）** · `string | null · null`

  用于查询下一页的游标键。上一响应的 `nextKey` 值。首次调用时可省略，或者 `null`传入。
* **orders** · 必需 · `Array`

  包含订单信息的数组。每个元素表示一笔订单。
* **orders\[].orderId** · 必需 · `string`

  订单的唯一 ID。
* **示例**
*
*
* ### 订单状态查询 API
* 可以在服务器上通过 API 直接查询应用内支付订单状态。即使未收到批准或退款响应时也可使用。
* <div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>请参考</strong></p><p>使用支付状态查询 API 前，请先完成 Toss 登录联动。</p></div>
* Content-type: `application/json`
* Method: `POST`
* URL: `/api-partner/v1/apps-in-toss/order/get-order-status`
* **请求头**
* 如果不包含该头部，则会返回所有订单。
* 在头部中 `x-toss-user-key` 包含该值时，仅返回对应 userKey 的订单。
* **请求参数**
* ```json
  {
    "orderId": "13c9a1ff-2baa-4495-bbfa-a0826ba8c7c0"
  }
  ```
* **响应**
* **status (enum)**
* **响应示例**
* ```json
  {
    "resultType": "SUCCESS",
    "success": {
      "orderId": "13c9a1ff-2baa-4495-bbfa-a0826ba8c7c0",
      "sku": "ait.0000010000.af647449.3bd55cfd00.0000000475",
      "statusDeterminedAt": "2025-09-12T16:57:12",
      "status": "PAYMENT_COMPLETED",
      "reason": "付款已完成。"
    }
  }
  ```
* ```json
  {
    "resultType": "SUCCESS",
    "success": {
      "orderId": "13c9a1ff-2baa-4495-bbfa-0000000000",
      "sku": "ait.0000010000.af647449.00000000000.0000000475",
      "statusDeterminedAt": "2025-09-12T16:57:12",
      "status": "PURCHASED",
      "reason": "这是已完成的订单。"
    }
  }
  ```
*
* ### 沙盒测试
* 在发布前务必 **沙盒应用环境**请测试应用内支付是否正常运行。在沙盒中不会发生真实扣款，所有支付都会按测试场景处理。
* <div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>请参考</strong></p><p>目前沙盒测试仅 <strong>一次性支付</strong>支持。当前不支持订阅支付的沙盒测试。</p></div>
* **1. 在沙盒中查询商品列表时的行为**
* 在沙盒应用中 `getProductItemList()`调用后，控制台中注册的应用内支付商品里 **展示状态为 ON**的商品才会被查询到。
* 实际在控制台注册的商品列表会原样返回。
* 在控制台中 **展示 OFF**的商品在沙盒应用中也不可见。
* **2. 必测测试场景**
* 在沙盒中，以下 3 种测试必须分别执行。请确认应用是否能正确响应每个场景。
* **① 支付成功测试**
* 成功回调（`event.type: success`）是否正常传递。
* 不会发生真实支付（扣款）。
* 在 SDK 1.1.3 及以上版本中，合作方的 **商品发放逻辑也必须成功，才算最终成功**会被处理为最终成功。
* <div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>需要确认的项目</strong></p><ul><li><code>orderId</code>, <code>amount</code> 等 <code>event.data</code> 是否正常返回</li><li>内部发放逻辑是否正常运行</li><li>发放完成后画面/UI 更新</li></ul></div>
* \[동영상 보기]\(../../../../resources/development/iap/iap\_sandbox\_test\_1.mp4)
* **② 支付成功（服务器失败）测试**
* 必须测试支付成功但合作方服务器的发放逻辑失败的情况。
* 应用需要支持以下处理：
* 发放完成后 `completeProductGrant` 调用
* 应用重新启动时 `getPendingOrders`恢复未结订单
* 向用户提示发放失败
* 这是在正式服务中也完全可能发生的场景，因此务必测试。
* \[동영상 보기]\(../../../../resources/development/iap/iap\_sandbox\_test\_2.mp4)
* **③ 错误测试**
* 请提前模拟支付过程中发生错误的各种情况。
* <div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>需要测试的典型情况</strong></p><ul><li>网络错误</li><li>用户取消支付</li><li>内部错误</li><li>合作方商品发放失败</li></ul></div>
* \[동영상 보기]\(../../../../resources/development/iap/iap\_sandbox\_test\_3.mp4)
* **3. 测试检查清单**
*
* ### 常见问题
*
*
*
*

| 测试项目                 | 必填 | 确认要点                       |
| -------------------- | -- | -------------------------- |
| 商品列表展示               | ✔️ | 控制台中注册的商品是否正常返回            |
| 支付成功测试               | ✔️ | `event.data` 处理、发放逻辑、UI 处理 |
| 支付成功 + 服务器发放失败（订单恢复） | ✔️ | 未结订单恢复及重新发放处理              |
| 错误测试                 | ✔️ | 错误 UI、错误处理、重试流程            |
| 订单状态查询 API           | 建议 | 服务器验证及一致性确认                |

| 状态                  | 说明    | 详细说明                               |
| ------------------- | ----- | ---------------------------------- |
| PURCHASED           | 订单完成  | 应用内支付和商品发放都已完成的状态                  |
| PAYMENT\_COMPLETED  | 支付完成  | 在 SDK 1.1.3 及以上版本中，支付已完成但商品发放失败的状态 |
| FAILED              | 订单失败  | 支付失败的情况                            |
| REFUNDED            | 订单已退款 | 退款完成的情况                            |
| ORDER\_IN\_PROGRESS | 订单进行中 | 订单已创建，但支付/发放处理尚未完成的情况              |
| NOT\_FOUND          | 无订单   | 找不到对应订单号的情况                        |
| MINIAPP\_MISMATCH   | 商品不一致 | 所订购的商品不是该应用的商品的情况                  |
| ERROR               | 内部错误  | 发生系统内部错误时                          |

| 名称                 | 类型     | 说明                                                                       |
| ------------------ | ------ | ------------------------------------------------------------------------ |
| orderId            | String | 请求的订单号                                                                   |
| sku                | String | 所订购的商品 ID                                                                |
| statusDeterminedAt | String | 订单完成时间（yyyy-MM-dd'T'HH:flag\_mm:ss，固定为 KST） `status`为 `REFUNDED`时为退款完成时间 |
| status             | String | 订单状态（enum）                                                               |
| reason             | String | 状态说明                                                                     |

| 名称      | 类型     | 必填 | 说明                   |
| ------- | ------ | -- | -------------------- |
| orderId | String | Y  | 支付创建后获取的订单号（uuid v7） |

| 名称              | 类型     | 是否必填 | 说明                      |
| --------------- | ------ | ---- | ----------------------- |
| x-toss-user-key | string | N    | 通过 Toss 登录获取的 userKey 值 |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-zh/common/monetization/iap/in-app-purchase.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
