12. Unity에서 원스토어 인앱결제 (SDK V21) 사용하기

개요

원스토어 결제 플러그인은 Unity 환경에서 에셋을 확장하여 게임에서 원스토어 결제 라이브러리의 최신 기능을 제공합니다. 이 가이드에서는 프로젝트를 설정하여 플러그인을 사용하는 방법과 원스토어 결제 라이브러리 기능을 구현하는 방법을 설명합니다.

개발 버전

Unity

2020.3.48f1

Java SDK (Java 11)

Purchase: v21.01.00

App License Checker: v2.1.0

원스토어 결제 플러그인 설정

플러그인 다운로드 및 가져오기

  1. GitHub의 Release 페이지에서 Unity 용 원스토어 인앱결제 플러그인 최신 버전을 다운로드합니다.

  2. Unity 메뉴 바에서 Assets > Import Package > Custom Package를 클릭합니다.

  3. 다운로드한 위치를 찾아 .unitypackage 파일을 선택합니다.

  4. Import Unity Package 대화상자에서 모든 에셋을 선택한 상태로 두고 Import를 클릭합니다.

패키지를 가져오면 폴더가 추가됩니다. 이 폴더에 원스토어 결제 라이브러리가 포함되어 있습니다.

  • Assets/OneStoreCorpPlugins

    • /Common

    • /Authentication

    • /Purchase

    • /AppLicenseChecker

EDM4U(External Dependency Manager for Unity)가 필수로 같이 배포됩니다. 만약, 이미 사용하고 있다면 Import Package 단계에서 ExternalDependencyManager를 체크해지하고 적용하면 됩니다.

외부 종속성 포함

프로젝트에 레파지토리 및 종속성이 포함되도록 하려면 다음 단계를 따릅니다.

Project Settings > Player > Publishing Settings > Build

아래의 두 가지를 체크합니다.

  • Custom Main Manifest

  • Custom Main Gradle Template

Assets > External Dependency Manager > Android Resolver > Force Resolve 를 선택합니다.

mainTemplete.gradle 파일에 아래와 같이 적용되는 것을 확인할 수 있습니다.

  • In-app Purchase 종속성은 다음에 나열되어 있습니다. Assets/OneStoreCorpPlugins/Purchase/Editor/PurchaseDependencies.xml

  • App License Checker 종속성은 다음에 나열되어 있습니다. Assets/OneStoreCorpPlugins/AppLicenseChecker/Editor/AppLicenseCheckerDependencies.xml

<queries> 설정

AndroidManifest.xml 파일에 <queries>를 설정 해야합니다. 자세한 내용은 공지사항을 참조하세요.

<queries> 태그를 설정하지 않으면 SDK에서 원스토어 서비스를 찾을 수 없습니다.

<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.co.kr/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>

글로벌 스토어 배포를 위한 테스트 옵션 설정

SDK가 강제로 글로벌 스토어로 연동되도록 하는 옵션입니다. 이 기능은 SDK v21.01.00 버전부터 적용되었습니다.

<manifest>
    ...
    <application>
        <activity>
            ...
        </activity>
        ...
        
        <!-- Options for in-app testing on your global store -->
        <meta-data android:name="onestore:dev_option" android:value="global" />
    </application>
</manifest>

배포 빌드 버전에서는 이 옵션을 반드시 삭제해야 합니다.

게임에서 원스토어 인앱 결제 라이브러리 적용하기

로그레벨 설정

개발 단계에서 로그 레벨을 설정하여 SDK의 데이터의 흐름을 좀 더 자세히 노출할 수 있습니다. android.util.Log에 정의된 값을 기반으로 동작합니다.

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

배포 빌드 버전에서는 보안에 취약할 수 있으니 이 옵션을 삭제해야 합니다.

원스토어 인앱 결제 초기화

인앱결제를 요청하기 위해 원스토어 개발자 센터에서 제공하는 라이선스 키를 사용하여 PurchaseClientImpl 객체를 초기화를 합니다. Initialize() 함수를 호출하여 기본적인 세팅을 마무리합니다. 이는 원스토어 서비스에 연결하기 위한 선행 작업입니다.

using OneStore.Purchasing;

class YourCallback: IPurchaseCallback
{
    // IPurchaseCallback implementations.
}

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

상품 정보 조회하기

PurchaseClientImpl 객체를 초기화 완료 후 QueryProductDetails()를 통해 상품 정보를 요청하면 IPurchaseCallback.OnProductDetailsSucceeded()로 응답을 받습니다.

ProductType은 아래와 같습니다.

ProductEnum

관리형 상품

ProductType.INAPP

정기 결제 상품 (구독 상품)

ProductType.SUBS

월 정액 상품

ProductType.AUTO (이 상품은 향후 지원되지 않을 예정입니다.)

위의 모든 유형의 데이터를 한 번에 조회하고 싶을 경우는 ProductType.ALL 설정하면 됩니다.

ProductType.ALL상품 정보 조회하기에서만 사용할 수 있으며, 구매 요청하기, 구매 내역 조회하기에서는 사용할 수 없습니다.

using OneStore.Purchasing;
  
List items = ...
purchaseClient.QueryProductDetails(items.AsReadOnly(), ProductType.INAPP);

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

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

인앱 상품 ID 목록은 개발사의 보안 백엔드 서버에서 관리해야 합니다.

구매 요청하기

앱에서 구매 요청을 하기 위해서는 기본 스레드에서 Purchase() 함수를 호출합니다.

QueryProductDetails() API를 호출해서 얻은 ProductDetail 객체의 값을 토대로 PurchaseFlowParams 객체를 생성합니다. PurchaseFlowParams 객체를 생성하려면 PurchaseFlowParams.Builder 클래스를 사용합니다.

SetDeveloperPayload()는 개발사에서 임의로 입력한 값으로 최대 200byte입니다. 이 값은 결제 후에 데이터의 정합성과 부가 데이터를 확인하기 위해 사용할 수 있습니다. SetProductName()은 상품 이름을 결제 시 변경하여 노출하고 싶을 때 사용됩니다. SetQuantity()는 관리형 인앱 상품에만 적용되며 한 상품을 여러 개 구매할 때 사용됩니다.

원스토어는 사용자에게 할인 쿠폰, 캐시백 등의 다양한 혜택 프로모션을 진행하고 있습니다. 개발사는 구매 요청 시에 gameUserId, promotionApplicable 파라미터를 이용하여 앱을 사용하는 유저의 프로모션 참여를 제한하거나 허용할 수 있습니다. 개발사는 앱의 고유한 유저 식별 번호 및 프로모션 참여 여부를 선택하여 전달하고, 원스토어는 이 값을 토대로 사용자의 프로모션 혜택을 적용하게 됩니다.

gameUserId, promotionApplicable 파라미터는 옵션 값으로 원스토어 사업 부서 담당자와 프로모션에 대해 사전협의가 된 상태에서만 사용하여야 하며, 일반적인 경우에는 값을 보내지 않습니다. 또한, 사전협의가 되어 값을 보낼 경우에도 개인 정보보호를 위해 gameUserIdhash된 고유한 값으로 전송하여야 합니다.

  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]은 관리형 상품 구매 화면을 나타냅니다.

구매에 성공하면 IPurchaseCallback.OnPurchaseSucceeded() 함수에 결과를 전송합니다. 실패 시에는 IPurchaseCallback.OnPurchaseFailed() 함수가 호출됩니다.

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

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

구매가 성공하면 구매 데이터에는 사용자 및 상품 ID를 나타내는 고유 식별자인 구매 토큰도 생성됩니다. 구매 토큰을 앱 내에 저장할 수 있지만 구매를 인증하고 사기로부터 보호할 수 있는 백엔드 서버로 토큰을 전달하는 것이 좋습니다.

관리형 상품과 정기 결제 상품의 구매 토큰은 결제가 일어날 때마다 구매 토큰이 발행됩니다. (월 정액 상품의 경우 자동 결제가 갱신되는 동안 구매 토큰은 동일하게 유지됩니다.)

또한 사용자는 영수증 번호가 포함된 거래 영수증을 이메일로 받습니다. 관리형 상품은 구매할 때마다 이메일을 받으며, 월 정액 상품과 정기 결제 상품은 처음 구매 시 그리고 이후 갱신될 때마다 이메일을 받습니다.

정기 결제

정기 결제는 취소될 때까지 자동으로 갱신됩니다. 정기 결제는 다음 상태를 가질 수 있습니다.

  • 활성: 사용자가 콘텐츠 사용에 문제가 없는 양호한 상태이며 정기 결제에 접근할 수 있습니다.

  • 일시 중지 예약: 사용자가 정기 결제를 이용 중 일시 중지를 하고 싶을 때 선택할 수 있습니다.

    • 주간 정기 결제: 1~3주 단위로 일시 중지할 수 있습니다.

    • 월간 정기 결제: 1~3개월 단위로 일시 중지할 수 있습니다.

    • 연간 정기 결제: 일시 중지를 지원하지 않습니다.

  • 해지 예약: 사용자가 정기 결제를 이용 중이지만 취소하고 싶을 때 선택할 수 있습니다. 다음 결제일에 결제가 되지 않습니다.

  • 유예, 보류: 사용자에게 결제 문제가 발생하면 다음 결제일에 결제가 되지 않습니다. 취소 예약을 할 수 없으며 즉시 "구독 해지"를 할 수 있습니다.

사용자가 정기 결제를 업그레이드, 다운그레이드 또는 변경할 수 있도록 허용

사용자가 정기 결제를 업그레이드하거나 다운그레이드 하려면 구매 시 비례 배분 모드를 설정하거나 변경사항이 정기 결제 사용자에게 영향을 주는 방식을 설정할 수 있습니다. 다음 표에는 사용 가능한 비례 배분 모드(OneStoreProrationMode)가 나와 있습니다.

비례 배분 모드

설명

IMMEDIATE_WITH_TIME_PRORATION

정기 결제의 교체가 즉시 이루어지며, 남은 시간은 가격 차이를 기반으로 조정되어 입금되거나 청구됩니다. (이것은 기본 동작입니다.)

IMMEDIATE_AND_CHARGE_PRORATED_PRICE

정기 결제의 교체가 즉시 이루어지며, 청구 주기는 동일하게 유지됩니다. 나머지 기간에 대한 가격이 청구됩니다. (이 옵션은 업그레이드 에서만 사용할 수 있습니다.)

IMMEDIATE_WITHOUT_PRORATION

정기 결제의 교체가 즉시 이루어지며, 다음 결제일에 새로운 가격이 청구됩니다. 청구 주기는 동일하게 적용됩니다.

DEFERRED

기존 요금제가 만료되면 교체가 적용되며 새 요금이 동시에 청구됩니다.

업그레이드 또는 다운그레이드

정기 결제는 구매 요청하기와 동일한 API를 사용하여 사용자에게 업그레이드 또는 다운그레이드를 제공할 수 있습니다. 다만, 정기 결제의 업그레이드 다운그레이드를 적용하기 위해선 기존 정기 결제 구매 토큰과 비례 배분 모드 값이 필수로 필요합니다. 다음 예와 같이 현재 정기 결제, 향후(업그레이드 또는 다운그레이드) 정기 결제 및 비례 배분 모드에 관한 정보를 제공해야 합니다.

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);
}

업그레이드 또는 다운그레이드의 경우도 구매 요청하기 로직을 수행하기 때문에 응답은 똑같이 수신합니다. 또한 구매 내역 조회하기에서도 요청 시 응답을 받을 수 있습니다. 비례 배분 모드로 구매했을 때도 일반 구매와 동일하게 PurchaseClientImpl.AcknowledgePurchase()를 사용하여 구매 후 처리를 해야 합니다.

구매 후 처리

사용자가 구매를 완료하면 앱에서 구매를 처리해야 합니다. 대부분의 경우 앱은 OnPurchaseSucceeded()를 통해 구매 알림을 받습니다. 또는 구매 내역 조회하기에서 설명된 것처럼 앱이 PurchaseClientImpl.QueryPurchases() 함수를 호출하여 처리하는 경우가 있습니다.

구매 소비하기 (consume)

관리형 상품은 소비를 하기 전까지는 재 구매할 수 없습니다.

상품을 소비하기 위해서는 ConsumePurchase()를 호출합니다. 또한 소비 작업 결과는 IPurchaseCallback..OnConsumeSucceeded() 호출 됩니다.

관리형 상품을 소비하지 않으면 영구성 형태의 상품 타입처럼 활용할 수 있으며, 구매 후 즉시 소비하면 소비성 형태의 상품으로도 활용됩니다. 또한 특정 기간 이후에 소비하면 기간제 형태의 상품으로 활용할 수 있습니다.

using OneStore.Purchasing;

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

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

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

소비 요청이 때로 실패할 수 있으므로 보안 백엔드 서버를 확인하여 각 구매 토큰이 사용되지 않았는지 확인해야 합니다. 그래야 앱이 동일한 구매에 대해 여러 번 자격을 부여하지 않습니다. 또는 자격을 부여하기 전에 성공적인 소비 응답을 받을 때까지 기다릴 수 있습니다.

3일 이내에 구매를 확인(acknowledge) 또는 소비(consume)를 하지 않으면 사용자에게 상품이 지급되지 않았다고 판단되어 자동으로 환불됩니다.

구매 확인하기 (acknowledge)

비 소비성 상품을 인증하려면 PurchaseClientImpl.AcknowledgePurchase() 함수를 사용합니다. 관리형 상품, 월 정액 상품, 정기 결제 상품 모두 사용할 수 있습니다.

PurchaseData.Acknowledged() 함수를 사용하여 인증 되었는지를 판단할 수 있습니다. 또한 인증 작업 결과가 성공하면

IPurchaseCallback.OnAcknowledgeSucceeded() 함수가 호출됩니다.

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)
{
    ...
}

구매 내역 조회하기

구매를 처리하는 것만으로는 앱이 모든 구매를 처리하는 것을 보장하기에 충분하지 않습니다. 앱에서 사용자가 구매한 모든 항목을 인식하지 못할 수 있습니다. 앱에서 구매 추적을 놓치거나 구매를 인식하지 못할 수 있는 몇 가지 시나리오는 다음과 같습니다.

  • 구매 중 네트워크 문제: 사용자가 구매를 성공적으로 완료하고 원스토어에서 확인을 받았지만 기기가 구매 알림을 받기 전에 네트워크 연결이 끊어졌을 경우

  • 여러 기기: 사용자는 한 기기에서 항목을 구입한 후 기기를 전환할 때 이 항목이 표시되기를 기대합니다.

이러한 상황을 처리하려면 앱의 Start() 또는 OnApplicationPause()에서 PurchaseClientImpl.QueryPurchases()를 호출하여 구매 후 처리에 설명된 대로 모든 구매가 성공적으로 처리되었는지 확인해야 합니다.

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

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

월 정액 상품 상태 변경하기 (Deprecated)

월 정액 상품은 최초 구매 후 30일 갱신이 이루어지는 상품입니다. 월 정액 상품의 상태는 PurchaseData.RecurringState()를 통해 확인할 수 있습니다.

월 정액 상품의 상태를 변경하려면 PurchaseClientImpl.ManageRecurringProduct()를 사용합니다. 구매 데이터와 변경하려는 RecurringAction 값을 입력합니다.

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
    {   
        ...
    }
}

정기 결제 관리 화면을 열기

구독한 상품의 상태를 관리하는 화면을 띄울 수 있습니다. 매개변수로 PurchaseData를 넣으면 구매 데이터를 확인하여 해당 정기 결제 상품의 관리 화면을 실행합니다. 그러나 null 넣을 경우 사용자의 정기 결제 리스트 화면을 실행합니다. 다음은 정기 결제 관리 화면을 띄우는 방법을 나타내는 예제입니다.

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

마켓 구분 코드 얻기

SDK v19 이상부터 S2S API를 사용하기 위해서는 마켓 구분 코드가 필요합니다.

PurchaseClientImpl 객체를 초기화시 SDK는 결제 모듈과 연결을 시도합니다. 이 때 연결이 성공적으로 완료되면 자동으로 StoreCode를 가져옵니다.

PurchaseClientImpl.StoreCode 변수에 할당되어 있습니다.

using OneStore.Purchasing;
  
// Possible after calling the OneStoreImpl.RetrieveProducts()
var storeCode = oneStoreImpl.StoreCode;

원스토어 서비스 설치하기

원스토어 서비스의 버전이 낮거나 없을 경우 인앱결제를 이용할 수 없습니다. 최초 API 호출 시 우선 원스토어 서비스와 연결 시도를 먼저 합니다. 이때 RESULT_NEED_UPDATE가 발생하면 LaunchUpdateOrInstallFlow() 메서드를 호출해야 합니다.

using OneStore.Purchasing;

purchaseClient.LaunchUpdateOrInstallFlow(); 

원스토어 로그인 요청하기

원스토어 인앱 SDK는 사용자가 원스토어에 로그인이 되어 있어야 동작합니다. 내부적으로 우선 로그인 토큰으로 로그인을 시도합니다. 실패하거나 최초 로그인같이 사용자의 정보가 없을 경우 포그라운드 로그인 화면을 띄워 사용자로 하여금 로그인을 유도합니다.

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

Last updated