원스토어 인앱결제 SDK 업그레이드 안내

개요

이 문서에서는 원스토어 인앱결제 라이브러리 V5(SDK V17)와 원스토어 인앱결제 라이브러리 V6(SDK V19)의 변경사항을 설명하고, 버전 업그레이드를 위한 절차를 안내합니다.

Before, After 예제를 참고하면, 좀 더 쉽게 변경 사항을 적용 할 수 있습니다.

PurchaseClient API 비교

V5 (SDK V17)

V6(SDK V19)

결제모듈과의 연결

connect

startConnection

결제모듈과의 연결해제

terminate

endConnection

지원여부 확인

isBillingSupportedAsync

X

인앱 상품 구매

launchPurchaseFlowAsync

launchPurchaseFlow

인앱 상품 소비

consumeAsync

consumeAsync

인앱 상품 구매 확인

X

acknowledgeAsync

소비되지 않은 상품의 구매내역(월 자동결제 포함)

queryPurchasesAsync

queryPurchasesAsync

인앱 상품 상세정보

queryProductsAsync

queryProductDetailsAsync

월 자동결제 상태 변경

manageRecurringProductAsync

manageRecurringProductAsync

결제 모듈 업데이트 또는 설치

launchUpdateOrInstallFlow

launchUpdateOrInstallFlow

원스토어 로그인 호출

launchLoginFlowAsync

launchLoginFlowAsync

스토어 코드 확인

X

getStoreInfoAsync

원스토어 인앱결제 버전 업그레이드 하기

meta-data 삭제

이제 앱의 매니페스트에서 인앱 API 버전을 추가할 필요가 없습니다. 인앱결제 SDK V19에 API Version meta-data가 추가됩니다.

기존 앱의 매니페스트에서 <meta-data android:name="iap:api_version" android:value="5"/> 어플리케이션 결제 API Version을 삭제합니다.

<application>
    ...
    <meta-data android:name="iap:api_version" android:value="5"/>
    ...
</application>

인앱 V19 SDK 라이브러리 추가

  1. 인앱결제 SDK 라이브러리 파일을 다운로드 한 후 해당 파일을 개발사 앱 프로젝트 내 'libs' 디렉토리 안에 복사합니다. InApp SDK 다운로드

  2. global-appstores.json 파일을 assets 폴더안에 복사합니다.

  3. 앱의 build.gradle 파일의 종속성 섹션에 다음 행을 추가합니다.

dependencies {
    ...
    implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}

인앱결제 초기화 및 연결하기

PurchaseClient 를 Builder 를 통해 객체를 생성하고 Payment module 과 연결 할 때 응답 결과를 처리하려면 PurchaseClientStateListener 를 구현합니다. 이전에는 에러에 맞는 함수 호출형식이지만 v19 SDK에서는 IapResult.getResponseCode() 를 통해 응답 결과를 판단할 수 있습니다.

Before

Java

private PurchaseClient mPurchaseClient;
...
PurchaseClient.ServiceConnectionListener mServiceConnectionListener = new PurchaseClient.ServiceConnectionListener() {
    @Override
    public void onConnected() {
        Log.d(TAG, "Service connected");
    }
 
    @Override
    public void onDisconnected() {
        Log.d(TAG, "Service disconnected");
    }
 
    @Override
    public void onErrorNeedUpdateException() {
        Log.e(TAG, "connect onError, The payment module update is required.");
        PurchaseClient.launchUpdateOrInstallFlow(this);
    }
};
...
mPurchaseClient = new PurchaseClient(this, /*your public key*/);
mPurchaseClient.connect(mServiceConnectionListener);

After

Kotlin

private lateinit var mPurchaseClient: PurchaseClient
...
mPurchaseClient = PurchaseClient.newBuilder(activity)
                       .setListener(this)
                       .setBase64PublicKey(/*your public key*/) // optional
                       .build()
mPurchaseClient.startConnection(object : PurchaseClientStateListener {
    override fun onSetupFinished(iapResult: IapResult) {
        if (iapResult.isSuccess) {
            // The PurchaseClient is ready. You can query purchases here.
        } else if (iapResult.responseCode == ResponseCode.NEED_LOGIN) {
            // The connection is completed successfully but login is required. You must write your login code here.
        } else if (iapResult.responseCode == ResponseCode.NEED_UPDATE) {
            // You need the required version of the ONE store service in the SDK.
        } else {
            // Other error codes.
        }
    }
 
    override fun onServiceDisconnected() {
        // Try to restart the connection on the next request to
        // ONE store service by calling the startConnection() method.
    }
})

Java

private PurchaseClient mPurchaseClient;
...
mPurchaseClient = PurchaseClient.newBuilder(activity)
                       .setListener(this)
                       .setBase64PublicKey(/*your public key*/) // optional
                       .build();
mPurchaseClient.startConnection(new PurchaseClientStateListener() {
    @Override
    public void onSetupFinished(IapResult iapResult) {
        if (iapResult.isSuccess()) {
            // The PurchaseClient is ready. You can query purchases here.
        } else if (iapResult.getResponseCode() == ResponseCode.NEED_LOGIN) {
            // The connection is completed successfully but login is required. You must write your login code here.
        } else if (iapResult.getResponseCode() == ResponseCode.NEED_UPDATE) {
            // You need the required version of the ONE store service in the SDK.
        } else {
            // Other error codes.
        }
    }
 
    @Override
    public void onServiceDisconnected() {
        // Try to restart the connection on the next request to
        // ONE store service by calling the startConnection() method.
    }
});

Activity 가 종료 될 때 onDestroy() 에서 PurchaseClient를 종료하는 코드를 넣습니다. 연결해제를 하지 않으면 생성된 연결이 계속 유지되면서 어플리케이션 성능에 문제가 생길 수 있습니다.

Before

Java

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mPurchaseClient != null) {
        mPurchaseClient.terminate();
    }
}

After

Kotlin

override fun onDestory() {
    super.onDestory()
    mPurchaseClient.endConnection()
}

Java

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mPurchaseClient != null) {
        mPurchaseClient.endConnection();
        mPurchaseClient = null;
    }
}

지원여부 조회하기

v17 SDK 에서 결제 모듈과 연결 후 제일 먼저 호출해야 했던 isBillingSupportedAsync는 없어졌습니다.

v19 SDK는 개발사에서 더 이상 지원여부를 조회하지 않아도 됩니다. PurchaseClient에서 Payment module과의 연결 성공 후 지원여부를 실행합니다.

PurchaseClientStateListener.onSetupFinished() 를 통해 응답을 확인할 수 있습니다.

상품정보 조회하기

상품 정보를 조회하기 위해 PurchaseClient.queryProductDetailsAsync()를 호출 합니다.

이 메서드를 호출할 때는 ProductDetailsParams의 인스턴스를 만들고 setProductIdList()에 인앱상품 ID들을 ArrayList 형태로 지정합니다. 또한, 상품의 정보를 조회하기 위해서는 상품 타입을 입력해야 합니다.. 인앱상품의 유형으로는 관리형 상품(INAPP), 월정액 상품(AUTO)이 있으며, 두 유형을 모두 조회하려면 ProductType.ALL을 입력하면 됩니다.

상품정보 조회의 결과를 처리 하려면 ProductDetailsListener 인터페이스를 구현해야 합니다. 성공적으로 인앱상품의 정보를 받아왔을 경우 IapResult.getResponseCode()는 ResponseCode.RESULT_OK이며 ProductDetail 리스트 형태로 응답을 내려줍니다.

Before

Java

PurchaseClient.QueryProductsListener mQueryProductsListener = new PurchaseClient.QueryProductsListener() {
    @Override
    public void onSuccess(List<ProductDetail> productDetails) {
        Log.d(TAG, "queryProductsAsync onSuccess, " + productDetails.toString());
    }
  
    @Override
    public void onErrorRemoteException() {
        Log.e(TAG, "queryProductsAsync onError, 원스토어 서비스와 연결을 할 수 없습니다");
    }
  
    @Override
    public void onErrorSecurityException() {
        Log.e(TAG, "queryProductsAsync onError, 비정상 앱에서 결제가 요청되었습니다");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        Log.e(TAG, "queryProductsAsync onError, 원스토어 서비스앱의 업데이트가 필요합니다");
    }
  
    @Override
    public void onError(IapResult result) {
        Log.e(TAG, "queryProductsAsync onError, " + result.toString());
    }
};
  
int IAP_API_VERSION = 5;
String productType = IapEnum.ProductType.IN_APP.getType(); // "inapp"
ArrayList<String> productCodes = new ArrayList<>();
productCodes.add("p5000");
productCodes.add("p10000");
  
mPurchaseClient.queryProductsAsync(IAP_API_VERSION, productCodes, productType, mQueryProductsListener);

After

Kotlin

val products = ArrayList<String>().apply {
    add("p5000")
    add("p50000")
}
val params = ProductDetailsParams.newBuilder()
        .setProductIdList(products).setProductType(ProductType.INAPP).build()
mPurchaseClient.queryProductDetailsAsync(params) { iapResult, productDetailList ->
    // Process the result.
}

Java

List<String> products = new ArrayList<>();
products.add("p5000");
products.add("p50000");
 
ProductDetailsParams params = ProductDetailsParams.newBuilder()
        .setProductIdList(products).setProductType(ProductType.INAPP).build();
 
mPurchaseClient.queryProductDetailsAsync(params, new ProductDetailsListener() {
    @Override
    public void onProductDetailsResponse(IapResult iapResult, List<ProductDetail> productDetailList) {
        // Process the result.
    }
});

구매 요청하기

v17 SDK 에서는 구매요청 후 onActivityResult를 통해 PurchaseClient.handlePurchaseData 를 통해 파싱 작업을 요청 해야지만 PurchaseFlowListener 를 통해 가공된 데이터를 받을 수 있었습니다.

구매 요청을 위해 PurchaseFlowParams 객체를 생성하여 필요한 값을 입력하여 전달 하면 됩니다.

v19 SDK 부터는 더 이상 onActivityResult에서 구현했던 작업을 하지 않아도 됩니다.

Before

Java

<script src="https://wiki.onestorecorp.com/download/attachments/26808197/test.js?api=v2"> </script>
<div class="ds-selector-tabs">
  <section><h3 id="java">Java</h3>
      <pre class="prettyprint lang-java" translate="no" dir="ltr">
 
PurchaseClient.PurchaseFlowListener mPurchaseFlowListener = new PurchaseClient.PurchaseFlowListener() {
    @Override
    public void onSuccess(PurchaseData purchaseData) {
        Log.d(TAG, "launchPurchaseFlowAsync onSuccess, " + purchaseData.toString());
        // 구매완료 후 developer payload 검증을 수해한다.
        if (!isValidPayload(purchaseData.getDeveloperPayload())) {
            Log.d(TAG, "launchPurchaseFlowAsync onSuccess, Payload is not valid.");
            return;
        }
  
        // 구매완료 후 signature 검증을 수행한다.
        boolean validPurchase = AppSecurity.isValidPurchase(purchaseData.getPurchaseData(), purchaseData.getSignature());
        if (validPurchase) {
            if (product5000.equals(purchaseData.getProductId())) {{
                // Grant entitlement to the user.
                consumeItem(purchaseData);
            }
        } else {
            Log.d(TAG, "launchPurchaseFlowAsync onSuccess, Signature is not valid.");
            return;
        }
    }
  
    @Override
    public void onError(IapResult result) {
        Log.e(TAG, "launchPurchaseFlowAsync onError, " + result.toString());
    }
  
    @Override
    public void onErrorRemoteException() {
        Log.e(TAG, "launchPurchaseFlowAsync onError, 원스토어 서비스와 연결을 할 수 없습니다");
    }
  
    @Override
    public void onErrorSecurityException() {
        Log.e(TAG, "launchPurchaseFlowAsync onError, 비정상 앱에서 결제가 요청되었습니다");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        Log.e(TAG, "launchPurchaseFlowAsync onError, 원스토어 서비스앱의 업데이트가 필요합니다");
    }
};
  
int IAP_API_VERSION = 5;
int PURCHASE_REQUEST_CODE = 1000; // onActivityResult 로 전달받을 request code
String product5000 = "p5000"; // 구매요청 상품ID
String productName = ""; // "" 일때는 개발자센터에 등록된 상품명 노출
String productType = IapEnum.ProductType.IN_APP.getType(); // "inapp"
String devPayload = AppSecurity.generatePayload();
String gameUserId = ""; // 디폴트 ""
boolean promotionApplicable = false;
  
mPurchaseClient.launchPurchaseFlowAsync(IAP_API_VERSION, activity, PURCHASE_REQUEST_CODE, product5000, productName, productType, devPayload, gameUserId, promotionApplicable, mPurchaseFlowListener)
      </pre>
  </section>
</div>
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.e(TAG, "onActivityResult resultCode " + resultCode);
  
    switch (requestCode) {
        case PURCHASE_REQUEST_CODE:
            /*
             * launchPurchaseFlowAsync API 호출 시 전달받은 intent 데이터를 handlePurchaseData를 통하여 응답값을 파싱합니다.
             * 파싱 이후 응답 결과를 launchPurchaseFlowAsync 호출 시 넘겨준 PurchaseFlowListener 를 통하여 전달합니다.
             */
            if (resultCode == Activity.RESULT_OK) {
                if (mPurchaseClient.handlePurchaseData(data) == false) {
                    Log.e(TAG, "onActivityResult handlePurchaseData false ");
                    // listener is null
                }
            } else {
                Log.e(TAG, "onActivityResult user canceled");
                // user canceled , do nothing..
            }
            break;
        default:
    }
}

After

Kotlin

// Retrieve a value for "productDetail" by calling queryProductDetailsAsync().
val params = PurchaseFlowParams.newBuilder()
        .setProductId(productId)    // productDetail.getProductId()
        .setProductName("")
        .setProductType(ProductType.INAPP)
        .setPayload(devPayload)
        .build()
mPurchaseClient.launchPurchaseFlow(activity, params)

Java

// Retrieve a value for "productDetail" by calling queryProductDetailsAsync().
PurchaseFlowParams params = PurchaseFlowParams.newBuilder()
        .setProductId(productId)    // productDetail.getProductId()
        .setProductName("")
        .setProductType(ProductType.INAPP)
        .setPayload(devPayload)
        .build();
 
mPurchaseClient.launchPurchaseFlow(activity, params);

구매에 대한 응답 결과 받기

구매에 대한 응답결과는 PurchaseClient 를 초기화 할 때 setListener() 를 통해 구현된 PurchaseUpdatedListener 인터페이스를 통해 값이 전달됩니다.

Kotlin

private lateinit var mPurchaseClient: PurchaseClient
...
mPurchaseClient = PurchaseClient.newBuilder(activity)
                       .setListener(this)
                       .setBase64PublicKey(/*your public key*/) // optional
                       .build()

Java

private PurchaseClient mPurchaseClient;
...
mPurchaseClient = PurchaseClient.newBuilder(activity)
                       .setListener(this)
                       .setBase64PublicKey(/*your public key*/) // optional
                       .build();

Kotlin

override fun onPurchasesUpdated(iapResult: IapResult, purchaseData: List<PurchaseData>?) {
    if (iapResult.isSuccess && purchaseData != null) {
        for (purchase in purchaseData) {
            if (!isValidPayload(purchase.developerPayload)) {
                // invalid dev payload
                return
            }
            // A signature check is required to secure the purchased product.
            val validPurchase = AppSecurity.verifyPurchase(purchase.originalJson, purchase.signature)
            if (validPurchase) {
                if ("p50000".equals(purchase.productId)) {
                    // This state is managed by the app itself.
                } else {
                    // Grant entitlement to the user.
                    consumeItem(purchase)
                }
            } else {
                // Signature information is invalid.
            }
        }
    } else if (iapResult.responseCode == ResponseCode.NEED_UPDATE) {
        // You need the required version of the ONE store service in the SDK.
    } else {
        // Other error codes.
    }
}

Java

@Override  
public void onPurchasesUpdated(IapResult iapResult, List<PurchaseData> purchaseData) {
    if (iapResult.isSuccess() && purchaseData != null) {
        for (PurchaseData p : purchaseData) {
            if (!isValidPayload(p.getDeveloperPayload())) {
                // invalid dev payload
                return;
            }
            // A signature check is required to secure the purchased product.
            boolean validPurchase = AppSecurity.verifyPurchase(p.getOriginalJson(), p.getSignature());
            if (validPurchase) {
                if ("p50000".equals(p.getProductId())) {
                    // This state is managed by the app itself.
                } else {
                    // Grant entitlement to the user.
                    consumeItem(p);
                }
            } else {
                // Signature information is invalid.
            }
        }
    } else if (iapResult.getResponseCode() == ResponseCode.NEED_UPDATE) {
        // You need the required version of the ONE store service in the SDK.
    } else {
        // Other error codes.
    }
}

구매 확인하기

원스토어 IAP 라이브러리 v6 이상을 사용하는 경우 3일 이내에 구매 확인을 해야 합니다. 구매 확인이 되지 않으면, 구매 금액이 환불됩니다.

다음 메서드 중 하나를 사용해 구매를 확인할 수 있습니다.

  • 소모성 제품인 경우 PurchaseClient.consumeAsync()를 사용합니다.

  • 소모성 제품이 아니라면 PurchaseClient.acknowledgeAsync()를 사용합니다.

월정액상품의 경우에는 최초 결제에 대해서만 구매를 확인하면 됩니다.

PurchaseData 객체에는 구매가 확인 되었는지를 나타내는 isAcknowledged() 메서드가 포함되어 있습니다. 구매 확인 전에 이러한 메서드를 사용하면 이미 구매가 확인 되었는지 파악할 수 있습니다.

관리형 상품 소비하기

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

Before

Java

PurchaseClient.ConsumeListener mConsumeListener = new PurchaseClient.ConsumeListener() {
    @Override
    public void onSuccess(PurchaseData purchaseData) {
        Log.d(TAG, "consumeAsync onSuccess, " + purchaseData.toString());
        // 상품소비 성공, 이후 시나리오는 각 개발사의 구매완료 시나리오를 진행합니다.
    }
  
    @Override
    public void onErrorRemoteException() {
        Log.e(TAG, "consumeAsync onError, 원스토어 서비스와 연결을 할 수 없습니다");
    }
  
    @Override
    public void onErrorSecurityException() {
        Log.e(TAG, "consumeAsync onError, 비정상 앱에서 결제가 요청되었습니다");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        Log.e(TAG, "consumeAsync onError, 원스토어 서비스앱의 업데이트가 필요합니다");
    }
  
    @Override
    public void onError(IapResult result) {
        Log.e(TAG, "consumeAsync onError, " + result.toString());
    }
};
  
  
int IAP_API_VERSION = 5;
PurchaseData purchaseData; // 구매내역조회 및 구매요청 후 전달받은 PurchaseData
mPurchaseClient.consumeAsync(IAP_API_VERSION, purchaseData, mConsumeListener);

After

Kotlin

// Enter the purchaseData received through onPurchasesUpdated() or onPurchasesResponse().
val params = ConsumeParams.newBuilder().setPurchaseData(purchaseData).build()
mPurchaseClient.consumeAsync(params) { iapResult, purchaseData ->
    if (iapResult.isSuccess) {
        // Process the result.
    } else if (iapResult.responseCode == ResponseCode.NEED_UPDATE) {
        // You need the required version of the ONE store service in the SDK.
    } else {
        // Other error codes.
    }
}

Java

// Enter the purchaseData received through onPurchasesUpdated() or onPurchasesResponse().
ConsumeParams params  = ConsumeParams.newBuilder().setPurchaseData(purchaseData).build();
mPurchaseClient.consumeAsync(params, new ConsumeListener() {
    @Override
    public void onConsumeResponse(IapResult iapResult, PurchaseData purchaseData) {
        if (iapResult.isSuccess()) {
            // Process the result.
        } else if (iapResult.getResponseCode() == ResponseCode.NEED_UPDATE) {
            // You need the required version of the ONE store service in the SDK.
        } else {
            // Other error codes.
        }
    }
});

월정액 상품 상태 변경하기

onRecurringResponse()에서 전달받은 action 값은 현재 적용된 값을 전달합니다. PurchaseClient.RecurringAction에서 확인할 수 있습니다.

또한, 응답 결과로 넘어오는 PurchaseData는 요청할 당시의 데이터 이기 때문에 '구매내역 확인하기'를 통해 변경된 데이터를 다시 받아야 변경된 RecurringState 값을 확인할 수 있습니다.

Before

Java

PurchaseClient.ManageRecurringProductListener mManageRecurringProductListener = new PurchaseClient.ManageRecurringProductListener() {
    @Override
    public void onSuccess(PurchaseData purchaseData, String manageAction) {
        Log.d(TAG, "manageRecurringProductAsync onSuccess, " + manageAction + " " + purchaseData.toString());
    }
  
    @Override
    public void onErrorRemoteException() {
        Log.e(TAG, "manageRecurringProductAsync onError, 원스토어 서비스와 연결을 할 수 없습니다");
    }
  
    @Override
    public void onErrorSecurityException() {
        Log.e(TAG, "manageRecurringProductAsync onError, 비정상 앱에서 결제가 요청되었습니다");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        Log.e(TAG, "manageRecurringProductAsync onError, 원스토어 서비스앱의 업데이트가 필요합니다");
    }
  
    @Override
    public void onError(IapResult result) {
        Log.e(TAG, "manageRecurringProductAsync onError, " + result.toString());
    }
};
  
  
int IAP_API_VERSION = 5;
PurchaseData purchaseData; // 구매내역조회 및 구매요청 후 전달받은 PurchaseData
String action = IapEnum.RecurringAction.CANCEL.getType(); // "cancel"
mPurchaseClient.manageRecurringProductAsync(IAP_API_VERSION, purchaseData, action, mManageRecurringProductListener);

After

Kotlin

// Enter the purchaseData received through onPurchasesUpdated() or onPurchasesResponse().
val params = RecurringProductParams.newBuilder()
                .setPurchaseData(purchaseData)
                .setRecurringAction(recurringAction)
                .build()
mPurchaseClient.manageRecurringProductAsync(params) { iapResult, purchaseData, action ->
    if (iapResult.isSuccess) {
        Log.d(TAG, "manageRecurringProductAsync onRecurringResponse, $action ${purchaseData?.toString()}")
         
        if (RecurringAction.CANCEL.equals(action, true)) {
            ...
        } else {
            ...
        }
 
    }
    ...
}

Java

// Enter the purchaseData received through onPurchasesUpdated() or onPurchasesResponse().
RecurringProductParams params = RecurringProductParams.newBuilder()
                                    .setPurchaseData(purchaseData)
                                    .setRecurringAction(recurringAction)
                                    .build();
mPurchaseClient.manageRecurringProductAsync(params, new RecurringProductListener() {
    @Override
    public void onRecurringResponse(IapResult iapResult, PurchaseData purchaseData, @RecurringAction String action) {
        if (iapResult.isSuccess()) {
            Log.d(TAG, "manageRecurringProductAsync onSuccess, " + action + " " + purchaseData.toString());
 
            if (RecurringAction.CANCEL.equalsIgnoreCase(action)) {
                ...
            } else {
                ...
            }
 
        ...
    }
});

원스토어 로그인 요청하기

v17 SDK에서는 로그인을 요청한 이후 onActivityResult 로 전달된 값을 다시 PurchaseClient.handLoginData() 실행해야 LoginFlowListener 로 값을 전달 받을 수 있었습니다.

v19 SDK 부터는 더 이상 onActivityResult에서 구현했던 작업을 하지 않아도 됩니다.

Before

Java

PurchaseClient.LoginFlowListener mLoginFlowListener = new PurchaseClient.LoginFlowListener() {
    @Override
    public void onSuccess() {
        Log.d(TAG, "launchLoginFlowAsync onSuccess");
        // 개발사에서는 로그인 성공시에 대한 이후 시나리오를 지정하여야 합니다.
    }
  
    @Override
    public void onError(IapResult result) {
        Log.e(TAG, "launchLoginFlowAsync onError, " + result.toString());
    }
  
    @Override
    public void onErrorRemoteException() {
        Log.e(TAG, "launchLoginFlowAsync onError, 원스토어 서비스와 연결을 할 수 없습니다");
    }
  
    @Override
    public void onErrorSecurityException() {
        Log.e(TAG, "launchLoginFlowAsync onError, 비정상 앱에서 결제가 요청되었습니다");
    }
  
    @Override
    public void onErrorNeedUpdateException() {
        Log.e(TAG, "launchLoginFlowAsync onError, 원스토어 서비스앱의 업데이트가 필요합니다");
    }
  
};
  
  
int IAP_API_VERSION = 5;
int LOGIN_REQUEST_CODE = 2000; // onActivityResult 로 전달받을 request code
  
mPurchaseClient.launchLoginFlowAsync(IAP_API_VERSION, "호출Activity".this, LOGIN_REQUEST_CODE, mLoginFlowListener);

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.e(TAG, "onActivityResult resultCode " + resultCode);
  
    switch (requestCode) {
        case LOGIN_REQUEST_CODE:
 
            /*
             * launchLoginFlowAsync API 호출 시 전달받은 intent 데이터를 handleLoginData를 통하여 응답값을 파싱합니다.
             * 파싱 이후 응답 결과를 launchLoginFlowAsync 호출 시 넘겨준 LoginFlowListener 를 통하여 전달합니다.
             */
 
            if (resultCode == Activity.RESULT_OK) {
                if (mPurchaseClient.handleLoginData(data) == false) {
                    Log.e(TAG, "onActivityResult handleLoginData false ");
                    // listener is null
                }
            } else {
                Log.e(TAG, "onActivityResult user canceled");
 
                // user canceled , do nothing..
            }
            break;
        default:
    }
}

After

Kotlin

mPurchaseClient.launchLoginFlowAsync(activity) { iapResult ->
    if (iapResult.isSuccess) {
        // You need to specify the scenario after successful login.
    }
    ...
}

Java

mPurchaseClient.launchLoginFlowAsync(activity, new IapResultListener() {
    @Override
    public void onResponse(IapResult iapResult) {
        if (iapResult.isSuccess()) {
            // You need to specify the scenario after successful login.
        }
        ...
    }
});

원스토어 서비스 설치하기

v17 SDK 에서는 AppInstaller.launchUpdateOrInstallFlow() 를 실행하고 동선이 끊어 졌지만, v19 SDK 부터는 설치가 완료 되었을 때 응답을 받을 수 있습니다.

Before

Java

AppInstaller.launchUpdateOrInstallFlow(activity)

After

Kotlin

mPurchaseClient.launchUpdateOrInstallFlow(activity) { iapResult ->
    if (iapResult.isSuccess) {
        // If the installation is completed successfully,
        // you should try to reconnect with the ONE store service.
        startConnection()
    } else {
        ...
    }
}

Java

mPurchaseClient.launchUpdateOrInstallFlow(activity, new IapResultListener() {
    @Override
    public void onResponse(IapResult iapResult) {
        if (iapResult.isSuccess()) {
        // If the installation is completed successfully,
        // you should try to reconnect with the ONE store service.
            startConnection();
        } else {
            ...
        }
    }
});

마켓 구분 코드 얻기

IAP 라이브러리 V6부터 Server to Server API를 사용하기 위해서는 마켓 구분 코드가 필요합니다.

getStoreInfoAsync()를 통해서 마켓 구분 코드를 획득 할 수 있습니다.

다음은 마켓 구분 코드를 획득 하는 방법을 나타낸 예제입니다.

Before

Kotlin

val client: PurchaseClient = ...
val listener: StoreInfoListener = ...
 
client.getStoreInfoAsync { iapResult, storeCode ->
    // Save storecode and use it in Server to Server API.
}

Java

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mPurchaseClient != null) {
        mPurchaseClient.endConnection();
 PurchaseClient client = ...
 
client.getStoreInfoAsync(new StoreInfoListener() {
    @Override
    public void onStoreInfoResponse(IapResult iapResult, String storeCode) {
        // Save storecode and use it in Server to Server API.
    }
});

Last updated