# \[Unity 플러그인] 원스토어 인앱결제 업그레이드 하기

### **Import Unity Package 비교** <a href="#id-unity-importunitypackage" id="id-unity-importunitypackage"></a>

| <p><br></p>                    | SDK V17                         | SDK V19               |
| ------------------------------ | ------------------------------- | --------------------- |
| <p>PURCHASE<br><br></p>        | Onestore\_IapCallbackManager.cs | GaaCallbackManager.cs |
| Onestore\_IapCallManager.cs    | GaaCallManager.cs               |                       |
| Onestore\_IapResultListener.cs | GaaIapResultListener.cs         |                       |
| Onestore\_PurchaseResponse.cs  | GaaPurchaseResponse.cs          |                       |
| UTIL                           | AndroidNative.cs                | AndroidNative.cs      |
| UI                             | Onestore\_IapUi.cs              | MainUIScript.cs       |
| <p><br></p>                    | LogScript.cs                    |                       |
| <p><br></p>                    | LoadingScript.cs                |                       |
| JSON                           | <p><br></p>                     | global-appstores.json |

<br>

### **IapCallManager API 비교** <a href="#id-unity-iapcallmanagerapi" id="id-unity-iapcallmanagerapi"></a>

| <p><br></p>                            | <p>v17<br>Onestore\_IapCallManager.cs</p> | <p>v19<br>GaaIapCallManager.cs</p> |
| -------------------------------------- | ----------------------------------------- | ---------------------------------- |
| <p>결제모듈과의 연결<br><br></p>               | connect                                   | StartConnection                    |
| 결제모듈과의 연결해제                            | terminate                                 | EndConnection                      |
| 지원여부 확인                                | isBillingSupported                        | x                                  |
| 인앱 상품 구매                               | launchPurchaseFlowAsync                   | LaunchPurchaseFlow                 |
| 인앱 상품 소비                               | consume                                   | Consume                            |
| 인앱 상품 확인                               | x                                         | Acknowledge                        |
| <p>소비되지 않은 상품의 구매내역<br>(월 자동결제 포함)</p> | getPurchases                              | QueryPurchases                     |
| 인앱 상품 상세정보                             | getProductDetails                         | QueryProductDetails                |
| 월 자동결제 상태 변경                           | manageRecurringAuto                       | ManageRecurringProduct             |
| 결제 모듈 업데이트 또는 설치                       | x                                         | LaunchUpdateOrInstallFlow          |
| 원스토어 로그인 호출                            | login                                     | LaunchLoginFlow                    |
| 마켓 구분 코드 확인                            | x                                         | GetStoreInfo                       |

<br>

### **인앱결제 초기화 및 연결** <a href="#id-unity" id="id-unity"></a>

v19 SDK 부터는 IapResult.code 의 값으로 성공, 실패 여부를 판단할 수 있습니다.

결제 모듈과 연결하는 방식은 똑같습니다.

**Before**

```
// Onestore_IapUi.cs (C#)
public void connectService ()
{
    var base64EncodedPublicKey = "MIGfMA0GCSqGSIb3D....";
    IapCallManager.connectService (base64EncodedPublicKey);
}
```

**After**

```
// MainUIScript.cs
public void StartConnection()
{
    if (GaaIapCallManager.IsServiceAvailable() == false)
    {
        GaaIapCallManager.StartConnection( /* your public key */ );
    }
}
```

연결 성공, 실패에 대한 응답은 많이 달라졌습니다.

SDK V17에서는 연결 성공 이후에 isBillingSupported()를 통해 지원 가능여부를 확인해야 했습니다.

하지만, SDK V19부터는 더 이상 지원 가능여부 체크를 하지 않아도 됩니다. SDK 내부에서 자동으로 지원 가능여부를 체크합니다. &#x20;

IapResult.code 의 값을 확인하려면 PurchaseResponse.ResponseCode 를 참조하세요.

**Before**

```
// Onestore_IapCallbackManager.cs (C#)
public static event Action<string> serviceConnectionEvent;
public void ServiceConnectionListener (string callback)
{
    if (callback.Contains (preDefinedStrings [CBType.Connected])) {
        serviceAvailableEvent ();
    }
    serviceConnectionEvent (callback);
}
 
// Onestore_IapResultListener.cs (C#)
Onestore_IapCallbackManager.serviceConnectionEvent += serviceConnectionResult;
void serviceConnectionResult (string result)
{
    AndroidNative.showMessage ("serivce connect", result, "ok");
}
```

**After**

```
// GaaIapResultListener.cs
GaaIapCallbackManager.PurchaseClientStateEvent += PurchaseClientStateEvent;
void PurchaseClientStateEvent(IapResult iapResult)
{
    if (iapResult.IsSuccess())
    {
        ...
    }
    else if (iapResult.code == ResponseCode.RESULT_NEED_UPDATE)
    {
        ...
    }
    else if (iapResult.code == ResponseCode.RESULT_NEED_LOGIN)
    {
        ...
    }
}
```

### **상품정보 조회하기**  <a href="#id-unity" id="id-unity"></a>

**Before**

```
// Onestore_IapUi.cs (C#)
public void getProductDetails ()
{
    var inapp_products = new string[] {inapp_pId1, inapp_pId2, inapp_pId3};
    var auto_products = new string[] {auto_pId1};
  
    IapCallManager.getProductDetails (inapp_products, inapp_type);
    IapCallManager.getProductDetails (auto_products, auto_type);
}
```

인앱상품 아이디(productId) 배열값과 그에 해당하는 상품타입을 지정합니다. 인앱상품 유형으로는 관리형 상품(ProductType.INAPP), 월정액 상품(ProductType,AUTO)가 있으며, 두 유형을 모두 조회하려면 ProductType.ALL을 입력하면 됩니다.<br>

**After**

```
// MainUIScript.cs
// 상품 상세 정보를 조회한다.
string[] all_products = { inapp_p5000, inapp_p10000, inapp_p50000, auto_a100000 };
GaaIapCallManager.QueryProductDetails(all_products, ProductType.ALL);
 
or
 
string[] inapp_products = new string[] {inapp_p5000, inapp_p10000, inapp_p50000};
string[] auto_products = new string[] {auto_a100000};
 
GaaIapCallManager.QueryProductDetails(inapp_products, ProductType.INAPP);
GaaIapCallManager.QueryProductDetails(auto_products, ProductType.AUTO);
```

상품정보 조회의 응답 결과값은 GaaIapResultListener의 ProductDetailsSuccessEvent(), ProductDetailsErrorEvent()를 통해 전달 받을 수 있습니다.

Andorid와 Unity와의 통신은 문자열 통신이고 한번에 전달할 수 있는 문자열의 길이가 제한되어 한 번에 한 상품씩 전달됩니다.

그래서, ProductDetailsSuccessEvent(..., ..., int count, int totalCount) 값이 중요합니다.

ex) 상품이 1개인 경우: count = 1, totalCount = 1\
상품이 10개인 경우: count = 1, totalCount = 10 ... count = 10, totalCount = 10

**Beforede**

```

// Onestore_IapCallbackManager.cs (C#)
public static event Action<ProductDetail> queryProductsSuccessEvent;
public static event Action<string> queryProductsErrorEvent;
  
public void QueryProductsListener (string callback)
{
    string data = findStringAfterCBType (callback, CBType.Success);
    if (data.Length > 0) {
        ProductDetail productDetail = JsonUtility.FromJson<ProductDetail> (data);
        queryProductsSuccessEvent (productDetail);
    } else {
        string errorData = findStringAfterCBType (callback, CBType.Error);
        queryProductsErrorEvent (errorData);
    }
}
 
 
// Onestore_IapResultListener.cs (C#)
void queryProductsSuccessEvent (ProductDetail result)
{
    AndroidNative.showMessage ("queryProducts success", result.ToString (), "ok");
}
  
void queryProductsErrorEvent (string result) {
    AndroidNative.showMessage ("queryProducts error", result, "ok");
}
```

\
**After**

```
// GaaIapResultListener.cs
List<ProductDetail> products = new List<ProductDetail>();
void ProductDetailsSuccessEvent(ProductDetail productDetail, int count, int totalCount)
{
    if (count == 1)
    {
        products.Clear();
    }
      
    products.Add(productDetail);
  
    if (count == totalCount)
    {
        // send ProductDetail List to UI
    }
}
  
void ProductDetailsErrorEvent(IapResult iapResult)
{
    if (iapResult.code == ResponseCode.RESULT_NEED_UPDATE)
    {
        ...
    }
    else if (iapResult.code == ResponseCode.RESULT_NEED_LOGIN)
    {
        ...
    }
}
```

### **구매 요청하기** <a href="#id-unity" id="id-unity"></a>

인앱 상품 구매를 위해 QueryProductDetails로 얻어온 productId, productType 등을 PurchaseFlowParams 객체에 담아 전달 하도록 변경 되었습니다.

**Before**

```
// Onestore_IapUi.cs (C#)
public void buyProductInapp ()
{
    //developPayload는 최대 사이즈 100byte까지만 허용됩니다.
    IapCallManager.buyProduct (inapp_pId1,  inapp_type, devPayload);
}
```

\
**After**

```
// MainUIScript.cs
void BuyProduct(string productId, string type)
{
    PurchaseFlowParams param = new PurchaseFlowParams();
    param.productId = productId;
    param.productType = type;
    //param.productName = "";
    //param.devPayload = "your Developer Payload"; // 최대 사이즈 200byte까지만 허용됩니다.
    //param.gameUserId = "";
    //param.promotionApplicable = false;
 
    GaaIapCallManager.LaunchPurchaseFlow(param);
}
```

LaunchPurchaseFlow() 메서드를 호출하면 원스토어 결제화면이 나타납니다.

<figure><img src="https://dev.onestore.co.kr/wiki/ko/doc/files/8291149/8291148/1/1596526261000/image2020-8-4_16-5-51.png" alt=""><figcaption></figcaption></figure>

**그림1.** 원스토어 상품 결제 화면

결제에 대한 응답 결과를 받는 부분에 '상품정보 조회하기'와 같은 이유로 count, totalCount가 존재 합니다.

**Before**

```
// Onestore_IapCallbackManager.cs (C#)
public static event Action<PurchaseData> getPurchaseIntentSuccessEvent;
public static event Action<string> getPurchaseIntentErrorEvent;
  
public void PurchaseFlowListener (string callback)
{
    string data = findStringAfterCBType (callback, CBType.Success);
  
    if (data.Length > 0) {
        try {
            PurchaseResponse purchaseRes = JsonUtility.FromJson<PurchaseResponse> (data);
            PurchaseData purchaseData = JsonUtility.FromJson<PurchaseData>  (purchaseRes.purchaseData);
            getPurchaseIntentSuccessEvent (purchaseData);
        } catch (System.Exception ex) {
            Debug.Log ("PurchaseFlowListener Exception " + ex.Message);
        }
    } else {
        //onError만 뒤에 추가데이터 IapResult 가 있으므로 추가 데이터만 전달해주고 나머지 에러들은 추가 데이터가 없으므로 callback 그대로만 전달해준다.
        string errorData = findStringAfterCBType (callback, CBType.Error);
        if (errorData.Length > 0) {
            getPurchaseIntentErrorEvent (errorData);
        } else {
            getPurchaseIntentErrorEvent (callback);
        }
    }
}
 
 
// Onestore_IapResultListener.cs (C#)
void getPurchaseIntentSuccessResult (PurchaseData result)
{
    AndroidNative.showMessage ("getPurchaseIntent sucess",  result.ToString (), "ok");
    PlayerPrefs.SetString (result.productId, JsonUtility.ToJson  (result));
}
  
void getPurchaseIntentErrorResult (string result)
{
    AndroidNative.showMessage ("getPurchaseIntent error", result, "ok");
}
```

\
**After**

```
// GaaIapResultListener.cs
private List<PurchaseData> purchases = new List<PurchaseData>();
private List<string> signatures = new List<string>();
  
  
void PurchaseUpdatedSuccessEvent(PurchaseData purchaseData, string signature, int count, int totalCount)
{
    if (purchaseData != null) {
        if (count == 1)
        {
            purchases.Clear();
            signatures.Clear();
        }
  
        purchases.Add(purchaseData);
        signatures.Add(signature);
  
        if (count == totalCount)
        {
            OnPurchaseUpdatedResponse(purchases, signatures); // send to ui
        }
    }
    else
    {
        // no PurchaseData
    }
}
  
void PurchaseUpdatedErrorEvent(IapResult iapResult)
{
    ...
}
```

### **구매 확인하기**  <a href="#id-unity" id="id-unity"></a>

원스토어 인앱결제 라이브러리 V6(SDK V19)에 새롭게 추가된 API 입니다.

자세한 내용은 '[Unity에서 원스토어 인앱결제 사용하기](https://onestore-dev.gitbook.io/dev/tools/billing/old-version/v19/unity)'에서 확인 할 수 있습니다.&#x20;

### **관리형 상품 소비하기**  <a href="#id-unity" id="id-unity"></a>

**Before**

```
// Onestore_IapUi.cs (C#)
public void consume ()
{
    string inapp_json = PlayerPrefs.GetString  (inapp_pId1);
  
    if (inapp_json.Length > 0) {
        IapCallManager.consume (inapp_json);
    } else {
        AndroidNative.showMessage ("error", "no  data to consume", "ok");
    }
}
```

\
**After**

```
// MainUIScript.cs
PurchaseData purchaseData = ...
GaaIapCallManager.Consume(purchaseData, "your developer payload");
```

### **구매내역 조회하기** <a href="#id-unity" id="id-unity"></a>

구매내역 조회하기의 요청은 큰 변동 사항이 없습니다.&#x20;

**Before**

```
// Onestore_IapUi.cs (C#)
public void getPurchases ()
{
    Onestore_IapCallManager.getPurchases ();
}
 
 
// Onestore_IapCallMnager.cs (C#)
//inapp과 auto 두 가지 상품 타입이 존재하는데 각각 상품에 대해서 각각 따로 호출해야 합니다.  개발사에서 변경 가능한 부분
public static void getPurchases ()
{
    checkServiceAvailable ();
    iapRequestAdapter.Call ("getPurchase", IAP_API_VERSION, "inapp");
    iapRequestAdapter.Call ("getPurchase", IAP_API_VERSION, "auto");
}
```

\
**After**

```
// MainUIScript.cs
GaaIapCallManager.QueryPurchases(ProductType.INAPP);
GaaIapCallManager.QueryPurchases(ProductType.AUTO);
```

다음은 응답 부분이며, '구매 요청하기' 응답과 동일하게 전달 됩니다.

**Before**

```
// Onestore_IapCallbackManager.cs (C#)
public static event Action<PurchaseData> getPurchaseSuccessEvent;
public static event Action<string> getPurchaseErrorEvent;
  
public void QueryPurchaseListener (string callback)
{
    string data = findStringAfterCBType (callback, CBType.Success);
    if (data.Length > 0) {
        try {
            PurchaseResponse purchaseRes = JsonUtility.FromJson<PurchaseResponse> (data);
            PurchaseData purchaseData = JsonUtility.FromJson<PurchaseData> (purchaseRes.purchaseData);
            getPurchaseSuccessEvent (purchaseData);
        } catch (System.Exception ex) {
            Debug.Log ("QueryPurchaseListener Exception " + ex.Message);
            getPurchaseErrorEvent (data); //success but no data
        }
    } else {
        //onError만 뒤에 추가데이터 IapResult 가 있으므로 추가 데이터만 전달해주고 나머지 에러들은 추가 데이터가 없으므로 callback 그대로만 전달해줍니다.
        string errorData = findStringAfterCBType (callback, CBType.Error);
        if (errorData.Length > 0) {
            getPurchaseErrorEvent (errorData);
        } else {
            getPurchaseErrorEvent (callback);
        }
    }
}
```

\
**After**

```
// GaaIapResultListener.cs
void QueryPurchasesSuccessEvent(PurchaseData purchaseData, string signature, int count, int totalCount)
 
void QueryPurchasesErrorEvent(IapResult iapResult)
```

### **월정액 상품 상태 변경하기**  <a href="#id-unity" id="id-unity"></a>

SDK V17에서는 개발사에서 직접 recurringState를 판단하여 command 값을 전달 해야 했지만, SDK V19에서는 PurchaseData를 넣으면 SDK에서 판단하여 command 값을 전달 합니다.

**Before**

```
// Onestore_IapUi.cs (C#)
public void cancelAutoItem ()
{
    string auto_json = PlayerPrefs.GetString (auto_pId1);
    PurchaseData response = JsonUtility.FromJson<PurchaseData> (auto_json);
  
    if (auto_json.Length > 0) {
        string command = "";
        if (response.recurringState == 0) {
            command = "cancel";
        } else if (response.recurringState == 1) {
            command = "reactivate";
        }
        IapCallManager.manageRecurringAuto (auto_json, command);
    } else {
        AndroidNative.showMessage ("Warning!!", "no data for manageRecurringAuto", "ok");
    }
}
```

\
**Aftere**

```
// MainUIScript.cs
PurchaseData purchaseData = ...
string recurringAction;
if (purchaseData.recurringState == RecurringState.RECURRING) {
    recurringAction = RecurringAction.CANCEL;
} else if (purchaseData.recurringState == RecurringState.CANCEL) {
    recurringAction = RecurringAction.REACTIVATE;
}
GaaIapCallManager.ManageRecurringProduct(purchaseData, recurringAction);
```

### **원스토어 로그인 요청하기**  <a href="#id-unity" id="id-unity"></a>

**Before**

```
// Onestore_IapUi.cs (C#)
IapCallManager.login();
 
 
// Onestore_IapResultListener.cs (C#)
void getLoginIntentEvent (string result)
{
    AndroidNative.showMessage ("getLoginIntent ", result.ToString (), "ok");
}
```

\
**After**

```
// MainUIScript.cs
GaaIapCallManager.LaunchLoginFlow();
  
// GaaIapResultListener.cs
void LoginFlowEvent(IapResult iapResult)
{
    if (iapResult.IsSuccess()) {
        // You need to specify the scenario after successful login.
    }
}
```

### **원스토어 서비스 설치하기** <a href="#id-unity" id="id-unity"></a>

원스토어 인앱결제 라이브러리 V6(SDK V19)에 새롭게 추가된 API 입니다.

자세한 내용은 '[Unity에서 원스토어 인앱결제 사용하기](https://onestore-dev.gitbook.io/dev/tools/billing/old-version/v19/unity)'에서 확인 할 수 있습니다.&#x20;

<br>

### **스토어 구분 코드 얻기** <a href="#id-unity" id="id-unity"></a>

원스토어 인앱결제 라이브러리 V6(SDK V19)에 새롭게 추가된 API 입니다.

자세한 내용은 '[Unity에서 원스토어 인앱결제 사용하기](https://onestore-dev.gitbook.io/dev/tools/billing/old-version/v19/unity)'에서 확인 할 수 있습니다.&#x20;

<br>
