2017년 10월 30일 월요일

Android IAP references, 개요, 아이템 구매

Android IAP가 궁금해서 대충 요약 해 봄.
: https://developer.android.com/google/play/billing/index.html?hl=ko

대상 : 표준 인앱 상품(일회성 결제), 구독(반복 자동 결제)
- IAP를 사용하여 아이템 판매 시 Google Play가 모든 결제 정보를 처리하여 App은 결재 transaction 관련 처리가 필요 없음.
- Google Play에서 제공하는 결재 서비스를 사용하므로 모든 app들에 대해 일관적인 경험 제공
- Google Play를 통해 세기되는 App 대상



(개요)

[In-app Billing API]
[Google Play Developer Console]

: https://developer.android.com/google/play/billing/billing_overview.html?hl=ko#api

- Google Play 앱에 의해 노출 되는 API를 통해 결제 서비스 사용

- Google Play 앱이 App<->Google Play server 사이 결제 요청, 응답 전달
- App<->Google Play 앱은 IPC를 사용하여 결제 요청, 응답을 받음.
- 결재는 Google Play 서버를 통해서 진행하므로 Google Play app이 서버와 통신할 수 있어야 함.
- Android In-app billing version은 현재 v3가 최신 버전임.
 . App은 google Play의 API를 통해 상품 세부 정보 요청, 상품 주문, 소유 상품 복원 가능.
 . API는 구매 완료 시 주문 정보를 동기적으로 기기로 전파.
 . 모든 구매가 Google Play를 통해 관리됨. 
 . 구매한 상품은 소비 가능하며 소비 시 소유하지 않은 상태로 변경됨.
 . 구독 서비스를 지원함.

- IAP 사용 App을 게시하고 IAP로 판매할 상품을 관리(구매/구독, 상품 목록 생성)함.

- 제품에 대한 다음 정보들을 정의함.
 . 상품 ID(SKU), 상품 유형, 가격, 설명, 구매에 대한 처리 및 추적?

https://developer.android.com/google/play/billing/billing_reference.html?hl=ko#getSkuDetails

표 2. getSkuDetails 요청에서 반환되는 상품 아이템 세부정보를 포함한 JSON 필드에 대한 설명.
설명
productId상품의 ID입니다.
type이 키의 값은 인앱 상품의 경우 “inapp”, 구독의 경우 "subs"여야 합니다.
price아이템의 형식 지정된 가격(통화 기호 포함)으로, 세금을 제외한 가격입니다.
price_amount_micros마이크로 단위의 가격으로, 1,000,000 마이크로 단위가 1 통화 단위와 같습니다. 예를 들어, price가 "€7.99"이면 price_amount_micros는 "7990000"입니다. 이 값은 특정 통화에 대해 현지화된 반올림 가격을 나타냅니다.
price_currency_codeprice에 대한 ISO 4217 통화 코드입니다. 예를 들어, price가 영국 파운드 단위로 지정되어 있는 경우 price_currency_code는 "GBP"입니다.
title상품의 제목입니다.
description상품에 대한 설명입니다.

[Google Play 구매 흐름]

1. App >- IAP 상품 결재 요청 -> Google Play
2. Google Play 결재 양식 요청, 유효성 검사, 구매 transaction 처리
3. Google Play >- 구매 세부 정보(주문 번호, 날짜, 시간, 가격 등) -> Google Play


(In-app Billing API)

https://developer.android.com/google/play/billing/api.html?hl=ko#producttypes
: https://developer.android.com/google/play/billing/billing_integrate.html?hl=ko
  * 구현방법이 좀 다름(확인 필요). https://developer.android.com/training/in-app-billing/preparing-iab-app.html?hl=ko

[아이템 구매]

구매 물품 확인
Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);
int response = ownedItems.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList<String> ownedSkus =
      ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
   ArrayList<String>  purchaseDataList =
      ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
   ArrayList<String>  signatureList =
      ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
   String continuationToken =
      ownedItems.getString("INAPP_CONTINUATION_TOKEN");

   for (int i = 0; i < purchaseDataList.size(); ++i) {
      String purchaseData = purchaseDataList.get(i);
      String signature = signatureList.get(i);
      String sku = ownedSkus.get(i);

      // do something with this purchase information
      // e.g. display the updated list of products owned by user
   }

   // if continuationToken != null, call getPurchases again
   // and pass in the token to retrieve more items
}

인앱 상품 세부정보 확인
Bundle skuDetails = mService.getSkuDetails(3,
   getPackageName(), "inapp", querySkus);
int response = skuDetails.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList<String> responseList
      = skuDetails.getStringArrayList("DETAILS_LIST");

   for (String thisResponse : responseList) {
      JSONObject object = new JSONObject(thisResponse);
      String sku = object.getString("productId");
      String price = object.getString("price");
      if (sku.equals("premiumUpgrade")) mPremiumUpgradePrice = price;
      else if (sku.equals("gas")) mGasPrice = price;
   }
}

인앱 상품 구매
(요청)
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(),
   sku, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
startIntentSenderForResult(pendingIntent.getIntentSender(),
   1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
   Integer.valueOf(0));
(응답)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   if (requestCode == 1001) {
      int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
      String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
      String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");

      if (resultCode == RESULT_OK) {
         try {
            JSONObject jo = new JSONObject(purchaseData);
            String sku = jo.getString("productId");
            alert("You have bought the " + sku + ". Excellent choice,
               adventurer!");
          }
          catch (JSONException e) {
             alert("Failed to parse purchase data.");
             e.printStackTrace();
          }
      }
   }
}
(구매 정보)
'{
   "orderId":"GPA.1234-5678-9012-34567",
   "packageName":"com.example.app",
   "productId":"exampleSku",
   "purchaseTime":1345678900000,
   "purchaseState":0,
   "developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
   "purchaseToken":"opaque-token-up-to-1000-characters"
 }'

* 구독에 대한 구매 흐름은 유사하지만 상품 유형이 subs로 설정해야 함.
(요청)
Bundle bundle = mService.getBuyIntent(3, "com.example.myapp",
   MY_SKU, "subs", developerPayload);
PendingIntent pendingIntent = bundle.getParcelable(RESPONSE_BUY_INTENT);
if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) {
   // Start purchase flow (this brings up the Google Play UI).
   // Result will be delivered through onActivityResult().
   startIntentSenderForResult(pendingIntent, RC_BUY, new Intent(),
       Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
}
(구매 확인)
Bundle activeSubs = mService.getPurchases(3, "com.example.myapp",
                   "subs", continueToken);

* Transaction 무결성을 보장하기 위해 Google Play는 구매 응답 데이터를 포함한 JSON 문자열에 서명함. 


[인앱 상품 소비]

구매한 상품은 소유된 상태로 간주되고 소유된 상태는 Google Play에서 다시 구매할 수 없음. 하지만 소유한 상품을 소비 하면 구매 데이터가 삭제 되고 소유되지 않는 상태로 변경됨.


int response = mService.consumePurchase(3, getPackageName(), token);


[구독]

[홍보]

[보안 및 디자인]

[테스트]

[관리]

댓글 1개:

  1. 와...진짜 감사합니다... 제가 원하던 정보가 그대로 있었네요...
    초보개발하는데 엄청 도움되었습니다. 감사합니다

    답글삭제