# 在Unity中使用ONE store In-App支付

## 概要

ONE store支付插件在Unity環境拓展asset，提供在遊戲中one store支付程序庫的最新功能。本指南對設置project使用插件的方法，以及展現ONE store支付程序庫功能的方法進行說明。

### 研發版本

<table data-view="cards"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><strong>Unity</strong></td><td>2022.3.62f3</td></tr><tr><td><strong>Java SDK (Java 11)</strong></td><td><p>Purchase: v21.04.00</p><p>App License Checker: v2.2.1</p></td></tr></tbody></table>

## ONE store支付插件設置

### 插件下載及導入（import）

1. 可以在GitHub的 Release 頁面下載Unity用ONE store app内支付插件的最新版本。
2. 在Unity選單欄點擊**Assets > Import Package > Custom Package**\
   ![](/files/Fw2jp1RvvJr38f5pBTy4)
3. 找到下載位置選擇.unitypackage文件。
4. **Import Unity Package**對話框内所有Assets都選上，點擊**Import**。

導入Package生成新文件夾。這個文件夾裡包括ONE store支付程序庫。

* Assets/OneStoreCorpPlugins
  * /Common
  * /Authentication
  * /Purchase
  * /AppLicenseChecker

[EDM4U(External Dependency Manager for Unity)](https://github.com/googlesamples/unity-jar-resolver)必須一起發行。\
如果已經在使用，在Import Package階段解除檢測ExternalDependencyManager適用即可。

### 包含外部從屬性

要在Project包含倉庫及從屬性，需要遵循如下階段。

**`Project Settings > Player > Publishing Settings > Build`**

檢測以下兩方面

* Custom Main Manifest
* Custom Main Gradle Template\
  \
  ![](/files/bjT4Cdbm6MKoXFhBdFVk)

選擇 **`Assets > External Dependency Manager > Android Resolver > Force Resolve`**

可以在`mainTemplete.gradle`文件確認適用如下内容。<br>

<figure><img src="/files/xnFcATb2gtk8KCTGlrC2" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}

* **In-app Purchase**從屬性列出如下。\
  Assets/OneStoreCorpPlugins/Purchase/Editor/PurchaseDependencies.xml
* **App License Checker** 從屬性列出如下。\
  Assets/OneStoreCorpPlugins/AppLicenseChecker/Editor/AppLicenseCheckerDependencies.xml
  {% endhint %}

### \<queries> 設置

需要在`AndroidManifest.xml`文件設置。詳細内容請參考公告事項。

{% hint style="danger" %}
如果不設&#x7F6E;**\<queries>，**&#x5C31;无=無法在SDK找到ONE store服務。.
{% endhint %}

```xml
<manifest>
    <!-- 
        if your binary use ONE store's In-app SDK,
        Please make sure to declare the following query on Androidmanifest.xml. 
        Refer to the notice for more information.
        https://dev.onestore.net/devpoc/support/news/noticeView.omp?noticeId=32968
     -->
    <queries>
        <intent>
            <action android:name="com.onestore.ipc.iap.IapService.ACTION" />
        </intent>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="onestore" />
        </intent>
    </queries>
    ...
    <application>
        ...
    </application>
</manifest>
```

### 發行全球store的測試選項設置

#### v21.04.00更新 - ONE Billing Lab 選擇功能 <a href="#id04.sdk-zhui-jia-v21.03.00-geng-xin-onebillinglab-xuan-ze-gong-neng" id="id04.sdk-zhui-jia-v21.03.00-geng-xin-onebillinglab-xuan-ze-gong-neng"></a>

可以通過如下設置 onestore:dev\_option 的 android:value 值來指定與 SDK 連動的商店應用。\
應位於 `<application>` 標籤的直屬子級，並應添加以下元素。

```xml
<manifest>
    <application>
        <meta-data android:name="onestore:dev_option" android:value="onestore_01" />
    </application>
</manifest>
```

| android:value | 適用國家/地區           |
| ------------- | ----------------- |
| `onestore_00` | South Korea       |
| `onestore_01` | Singapore, Taiwan |
| `onestore_02` | United States     |
| `onestore_03` | ONE Billing Lab   |

{% hint style="info" %}
**Version History**

* **v21.04.00** : ONE Billing Lab 選擇功能
* **v21.02.00** : 使用 `android:value` 可設定韓國、新加坡/台灣及美國。
* **v21.01.00** : 使用 `android:value` 僅能設定 global，且僅能指定新加坡/台灣商店應用程式。
  {% endhint %}

{% hint style="danger" %}
&#x20;發行组建版本中，本選項必須刪除。
{% endhint %}

## 遊戲中適用ONE store app内支付程序庫

### 設置 Log level

在研發階段設置Log level，可以更仔细顯示SDK的數據流向。以[android.util.Log](https://developer.android.com/reference/android/util/Log#summary)内定義的值為基礎運作。

```csharp
using OneStore.Common;
/// <summary>
/// Warning! This code must be deleted during release build as it may be vulnerable to security.
/// Use <seealso cref="OneStoreLogger.SetLogLevel(int)"/> only for development.
/// </summary>
OneStoreLogger.SetLogLevel(2);
```

| 常數             | 值 |
| -------------- | - |
| VERBOSE        | 2 |
| DEBUG          | 3 |
| INFO (default) | 4 |
| WARN           | 5 |
| ERROR          | 6 |

{% hint style="danger" %}
發行组建版本網絡安全比較弱，本選項需要刪除。
{% endhint %}

### ONE store app内支付初始化

<figure><img src="/files/fXhT1xzMHELowam1uO01" alt=""><figcaption></figcaption></figure>

為了請求app内支付，需要使用ONE store研發者中心提供的許可證密鑰對`PurchaseClientImpl`對象進行初始化。調用`Initialize()`函數完成基本配置。這是為了連接ONE store服務的先行操作。

```csharp
using OneStore.Purchasing;

class YourCallback: IPurchaseCallback
{
    // IPurchaseCallback implementations.
}

var purchaseClient = new PurchaseClientImpl("your license key");
purchaseClient.Initialize(new YourCallback());
```

### 查詢商品資訊

`PurchaseClientImpl`對象初始化结束後，如果通過`QueryProductDetails()`請求商品資訊，以`IPurchaseCallback.OnProductDetailsSucceeded()`接收回覆。

`ProductType`如下

<table><thead><tr><th width="246">Product</th><th>Enum</th></tr></thead><tbody><tr><td>管理型商品</td><td><code>ProductType.INAPP</code></td></tr><tr><td>定期支付商品 (訂閱商品)</td><td><code>ProductType.SUBS</code></td></tr><tr><td>包月型商品</td><td><del><code>ProductType.AUTO</code></del> (該商品今後本商品將不予支持) </td></tr></tbody></table>

想一次性查詢上述所有類型資訊，請設置`ProductType.ALL`。

{% hint style="warning" %}
`ProductType.ALL`僅可在查詢商品詳細資訊中使用，在請求購買，查詢購買明細中無法使用。
{% endhint %}

```csharp
using OneStore.Purchasing;
  
List items = ...
purchaseClient.QueryProductDetails(items.AsReadOnly(), [ProductType.ALL | ProductType.INAPP | ProductType.SUBS]);

// IPurchaseCallback implementations
public void OnProductDetailsSucceeded(List productDetails)
{
    ...
}

public void OnProductDetailsFailed(IapResult iapResult)
{
    ...
}
```

{% hint style="warning" %}
應用程序內商品ID列表要在研發者的網或安全後端伺服器上管理。
{% endhint %}

### 請求購買

為了在app請求購買，要在基本線程調用`Purchase()`函數。

以調用`QueryProductDetails()`API獲取的`ProductDetail`對象的值為基礎，生成`PurchaseFlowParams` 對象。\
要生成`PurchaseFlowParams`對象，需要使用`PurchaseFlowParams.Builder` class。

`SetDeveloperPayload()`作为研發者任意輸入的值，最多為200byte。此值可以用於確認支付後資訊的整合性和附加資訊。\
`SetProductName()`在支付時想要更改顯示商品名的情况下使用。

`SetQuantity()`僅用於管理型app内商品，在購買數個同一商品时使用。

{% hint style="success" %}
ONEstore正在進行為用戶提供優惠券、現金卡等諸多優惠宣傳活動。

研發公司可以在請求購買時使用`gameUserId`、 `promotionApplicable`參數，限制或允许app用户参与宣传活动。\
研發公司傳送app的固有用户識別號碼及選擇是否參加宣傳活動，one store以此值為基礎，適用用戶的宣傳優惠。
{% endhint %}

{% hint style="warning" %}
`gameUserId`, `promotionApplicable`參數作為選項值，僅在事先與ONE store事業部負責人協商宣傳活動時使用，其他一般情况下不發送值。\
另外，即使達成事先協議並發錯值，為了保護個人資訊，gameUserId也必須以hash的固有值形式傳送。
{% endhint %}

```csharp
  using OneStore.Purchasing;
  
  ProductDetail productDetail = ...
  ProductType productType = ProductType.Get(productDetail.type);

  var purchaseFlowParams = new PurchaseFlowParams.Builder()
          .SetProductId(productId)                // mandatory
          .SetProductType(productType)            // mandatory
          .SetDeveloperPayload(developerPayload)  // optional
          .SetQuantity(quantity)                  // optional
          // .SetProductName(null)                // optional: Change the name of the product to appear on the purchase screen.
          
          // It should be used only in advance consultation with the person in charge of the One Store business, and is not normally used.
          // .SetGameUserId(null)                 // optional: User ID to use for promotion.
          // .SetPromotionApplicable(false)       // optional: Whether to participate in the promotion.
          .Build(); 

  purchaseClient.Purchase(purchaseFlowParams);
```

如果成功調用`Purchase()`，顯示如下界面。【圖片1】顯示显示管理型商品支付購買界面。

<figure><img src="/files/lLXzdRnC54kCp7zfnpRz" alt=""><figcaption></figcaption></figure>

如果購買成功，向`IPurchaseCallback.OnPurchaseSucceeded()`函數傳送结果。

失敗時調用`IPurchaseCallback.OnPurchaseFailed()` 函數。

```csharp
  using OneStore.Purchasing;
  
  // IPurchaseCallback implementations
  public void OnPurchaseSucceeded(List purchases)
  {
      handlePurchase(purchases);
  }

  public void OnPurchaseFailed(IapResult iapResult)
  {
      ...
  }
```

<figure><img src="/files/NKcetywrUIQwA5dhRCGp" alt=""><figcaption></figcaption></figure>

如果購買成功， 購買數據還會生成用戶及商品ID的固有識別符號購買token。購買token雖然可以在應用軟件内儲存，但是最好將token傳送到驗證購買、避免詐騙的後端伺服器。&#x20;

管理型商品和定期支付商品的購買token在每次支付的時候都會發行購買token。（包月型商品自動支付更新期間，購買token將保持不變。）&#x20;

另外，用戶會通過電子郵件接收包含收據號碼的交易收據。管理型商品每次購買都會接收電子郵件，包月型商品和定期支付商品在第一次購買和之後有更新的時候會接收電子郵件。&#x20;

### 定期支付 <a href="#id-12.unity-sdkv21" id="id-12.unity-sdkv21"></a>

定期支付在取消之前一直自動更新。定期支付可能有以下狀態。

* 激活：用戶使用内容過程中没有問題的良好狀態，可以接觸定期支付。
* 預约暫停：用戶使用定期支付期間想要暫停時可以選擇。
  * 周定期支付：以1-3周為單位進行暫停。&#x20;
  * 月定期支付：以1-3個月為單位進行暫停。&#x20;
  * 年定期支付：不予支持暫停。&#x20;
* 預约解除：雖然用戶正在使用定期支付，但想取消時可以選擇。下一個支付日將不會支付。&#x20;
* 延期，保留：如果用戶出現支付問題，則無法在下一個支付日支付。用戶不能取消預约，可以立即“解除訂閱”。&#x20;

#### 允許用戶升级、降级或變更定期支付 <a href="#id-12.unity-sdkv21" id="id-12.unity-sdkv21"></a>

用戶想要升级或降级定期支付時，可以在購買時設置比例分配模式，或者設置變更事項影響到定期支付用戶的方式。&#x20;

下表是可以使用的比例分配模式(`OneStoreProrationMode`)

| 比例分布模式                                  | 說明                                                    |
| --------------------------------------- | ----------------------------------------------------- |
| IMMEDIATE\_WITH\_TIME\_PRORATION        | 定期支付的變更將立即進行，剩餘時間基於差價調整後退款或支付。（這是基本操作）                |
| IMMEDIATE\_AND\_CHARGE\_PRORATED\_PRICE | 定期支付的變更將立即進行，請求支付周期和之前一致。請求用戶支付剩餘時間的價格。（本選項僅是在更新中可使用） |
| IMMEDIATE\_WITHOUT\_PRORATION           | 定期支付的變更將立即進行，下一個支付日支付新的價格。請求支付周期和之前一致。                |
| DEFERRED                                | 現有套餐到期後立即變更，同時發给用戶新的資費。                               |

#### 升级或降级 <a href="#id-12.unity-sdkv21" id="id-12.unity-sdkv21"></a>

定期支付可以使用與請求購買同一個API，向用戶提供升级或降级。但是，為了適用定期支付的升级與降级，必須具備現有定期支付購買token和比例分配模式值。&#x20;

如下例子，需要提供現有定期支付、今後（升级或降级）定期支付以及比例分配模式相關資訊。&#x20;

```csharp
using OneStore.Purchasing;
  
public void UpdateSubscription(ProductDetail productDetail, PurchaseData oldPurchase, OneStoreProrationMode mode, string developerPayload)
{
    ProductType productType = ProductType.Get(productDetail.type);
  
    var purchaseFlowParams = new PurchaseFlowParams.Builder()
             .SetProductId(productId)                        // mandatory
             .SetProductType(productType)                    // mandatory
             .SetOldPurchaseToken(oldPurchase.PurchaseToken) // mandatory
             .SetProrationMode(mode)                         // mandatory
  
             .SetDeveloperPayload(developerPayload)          // optional
             // .SetProductName(null)                        // optional: Change the name of the product to appear on the purchase screen.
             .Build(); 

    purchaseClient.UpdateSubscription(purchaseFlowParams);
}
```

因為升级或降级也會執行請求購買邏輯，所以接收同樣回覆。&#x20;

另外，也會在查詢購買明细中有請求時接收回覆。用比例分配模式購買也和一般購買一樣，需要使用`PurchaseClientImpl.AcknowledgePurchase()`行購買後處理。

### 購買後處理 <a href="#id-12.unity-sdkv21" id="id-12.unity-sdkv21"></a>

如果用戶購買完成，需要在應用軟件進行購買處理。 大部分情况下，應用軟件通過`OnPurchaseSucceeded()`得購買通知。或者如查詢購買明细說明那樣，也有應用軟件調用`PurchaseClientImpl.QueryPurchases()`函數處理的情况。

#### 使用購買商品 (consume) <a href="#id-12.unity-sdkv21-consume" id="id-12.unity-sdkv21-consume"></a>

管理型商品在使用之前不能再次購買。&#x20;

為了使用商品，調用`ConsumePurchase()`。另外，使用操作结果調用為`IPurchaseCallback..OnConsumeSucceeded()`。

{% hint style="info" %}
不使用管理型商品時，可以作為永久性形態的商品使用， 購買後立即使用時，也可以作為使用性形態的商品使用。&#x20;

另外，如果特定時間後使用，還可以作為固定期限形態的商品使用 。&#x20;
{% endhint %}

```csharp
using OneStore.Purchasing;

public void handlePurchase(PurchaseData purchaseData)
{
    purchaseClient.ConsumePurchase(purchaseData);
}

// IPurchaseCallback implementations
public void OnConsumeSucceeded(PurchaseData purchase)
{
    ...
}

public void OnConsumeFailed(IapResult iapResult)
{
    ...
}
```

{% hint style="info" %}
使用請求有時會失敗， 因此必須檢查安全後端伺服器，確認各購買token是否使用。只有這樣應用軟件才不會對同一個購買進行多次授權。或者，在授權之前， 可以等到成功使用回覆為止。
{% endhint %}

{% hint style="warning" %}
3天内不確認購買(acknowledge)或不使用(consume)，判斷為没有為用戶提供商品，自動退款给用戶。
{% endhint %}

#### 驗證購買商品 (acknowledge) <a href="#id-12.unity-sdkv21-acknowledge" id="id-12.unity-sdkv21-acknowledge"></a>

如果驗證非使用型商品，使用`PurchaseClientImpl.AcknowledgePurchase()`函數。管理型商品、包月型商品、訂閱型商品均可使用。

使用`PurchaseData.Acknowledged()` 函數判斷是否已驗證。另外，驗證操作成功後，將調用`IPurchaseCallback.OnAcknowledgeSucceeded()`函數。

```csharp
using OneStore.Purchasing;

public void handlePurchase(PurchaseData purchaseData)
{
    if (!purchaseData.Acknowledged())
    {
         purchaseClient.AcknowledgePurchase(purchaseData);
    }
}

// IPurchaseCallback implementations
public void OnAcknowledgeSucceeded(PurchaseData purchase)
{
    ...
}

public void OnAcknowledgeFailed(IapResult iapResult)
{
    ...
}
```

### 查詢購買明细 <a href="#id-12.unity-sdkv21" id="id-12.unity-sdkv21"></a>

僅僅處理購買，不足以保證應用軟件處理了所有購買。應用軟件可能無法識別用戶購買的全部商品。 應用軟件可能錯過購買跟踪或無法識別購買等幾種情况如下。

* 網絡問題：用戶成功購買，ONE store上也收到確認，但是設備在接收購買通知之前，發生網絡連接斷開的情况。&#x20;
* 多個設備：用戶在一台設備上購買商品後，切換到其他設備時，期待顯示該商品。&#x20;

為了處理這種情况，需要在應用軟件的`Start()`或`OnApplicationPause()`中調用`PurchaseClientImpl.QueryPurchases()`，按照購買後處理說明來確認所有購買是否已經成功處理。

```csharp
using OneStore.Purchasing;
  
// IPurchaseCallback implementations
public void OnPurchaseSucceeded(List purchases)
{
    handlePurchase(purchases);
}

public void OnPurchaseFailed(IapResult iapResult)
{
    ...
}
```

### 更改包月型商品狀態 (Deprecated) <a href="#id-12.unity-sdkv21-deprecated" id="id-12.unity-sdkv21-deprecated"></a>

包月型商品是最初購買後30天更新的商品。包月型商品的狀態可以通過`PurchaseData.RecurringState()`確認。

如果要更改包月型商品的狀態，請使用`PurchaseClientImpl.ManageRecurringProduct()`。輸入購買數據和要更改的`RecurringAction`值 。

```csharp
using OneStore.Purchasing;
  
RecurringAction recurringAction = purchaseData.RecurringState == 0 ? RecurringAction.CANCEL : RecurringAction.REACTIVATE;
purchaseClient.ManageRecurringProduct(purchaseData, recurringAction); 

// IPurchaseCallback implementations
public void OnManageRecurringProduct(IapResult iapResult, PurchaseData purchase, RecurringAction action)
{
     if (iapResult.IsSuccessful())
    {
        ...
    }
    else
    {   
        ...
    }
}
```

### 打開定期支付管理界面 <a href="#id-12.unity-sdkv21" id="id-12.unity-sdkv21"></a>

可以示管理訂閱商品狀態的界面。\
作為參數輸入`PurchaseData`後，確認購買數據，運行相應定期支付商品的管理界面。但是輸入`null`時，運行用戶的定期支付列表界面。下面是顯示定期支付管理界面的方法的示例。

```csharp
using OneStore.Purchasing;
  
PurchaseData purchaseData = ...;
purchaseClient.LaunchManageSubscription(purchaseData); 
```

### 獲取市場分類代碼（code） <a href="#id-12.unity-sdkv21" id="id-12.unity-sdkv21"></a>

從SDK v19版本以上開始，為了使用S2S API需要市場分類代碼。

`PurchaseClientImpl` 對象初始化時，SDK嘗試與支付模塊連接。此時連接成功就會自動導入`StoreCode`。

`PurchaseClientImpl.StoreCode` 變數有配額。

```csharp
using OneStore.Purchasing;
  
// Possible after calling the PurchaseClientImpl.RetrieveProducts()
var storeCode = purchaseClient.StoreCode;
```

### 安裝ONE store服務 <a href="#id-12.unity-sdkv21" id="id-12.unity-sdkv21"></a>

ONE store服務的版本低或者没有時，無法使用app内支付。首次調用API時，首先嘗試與ONE store服務連接。此時，如果發生`RESULT_NEED_UPDATE`，則需要調用`LaunchUpdateOrInstallFlow()` 方法。

```csharp
using OneStore.Purchasing;

purchaseClient.LaunchUpdateOrInstallFlow(); 
```

### 請求登錄ONE store  <a href="#id-12.unity-sdkv21" id="id-12.unity-sdkv21"></a>

ONE store app内SDK只要用戶登錄ONE store才能啟動。内部首先嘗試以登錄token進行登錄。失敗或最初登錄等没有用戶資訊時，會顯示前台登錄畫面，引導用戶登錄。

```csharp
using OneStore.Auth;
  
new OneStoreAuthClientImpl().LaunchSignInFlow({signInResult} => {
    if (signInResult.IsSuccessful())
    {
        ...
    }
});
```


---

# Agent Instructions: 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:

```
GET https://onestore-dev.gitbook.io/dev/cht/tools/billing/v21/unity.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
