2012년 5월 11일 금요일

[In-app Billing_3]In-app Billing 테스트

# 인앱 결제를 테스트 해보자

- 결제 테스트는 앱이 퍼블리싱 되지 않고 마켓에 등록만 되어 있으면 가능하다. 테스트 디바이스 OS는 1.6 이상이여야 하고, 구글 플레이 버전은 최신 버전을 유지해야 한다.


@ Testing in-app purchases with static responses 

 - 우리는 당신이 테스트 요청과 응답을 통해 먼저 결제를 구현 테스트 해보길 추천한다. 이 테스트 요청과 응답은 충분히 결제 요청과 응답에 대한 테스트를 할수 있다.

- 테스트 요청을 하기 위해서는 reserved item이 필요하다. 이는 돈이 인출되지 않을 뿐 아니라   특정(specific static)응답을 구글 플레이서 준다. 

- reserved product를 계정에서 등록 할 필요가 없다. 구글 플레이는 이미 그것을 알고 있다. 

- 아래는 static 인앱 결제 응답에 관한 내용이다.

  •  android.test.purchased : product id를 가지고 결제 요청을 하면 구글 플레이는 성공적으로 결제가 된 것처럼 응답한다. 이 응답은 JSON string을 포함한다. 이 정보는 가짜다.  경우에 따라서는 응답에  결제를 식별하기 위한 signature도 포함한다. 
  •  android.test.canceled :  product id를 가지고 결제 요청을 하면 구글 플레이는 결제가 취소 된 것 처럼 응답한다. 이는 신용카드가 올바르지 않거나 사용자가 대금 지불전에 취소 했을 때 일반적을 일어난다.
  • android.text.refunded : product id를 가지고 결제 요청을 하면 구글 플레이는 결제가 환불 된 것 처럼 응답한다. 환불은 구글 플레이가 자의적으로 할 수 없다. 판매자가 판매자 페이지에서 해야 응답 될 수 있다. 
  • android.test.item_unavailable : product id를 가지고 결제 요청을 하면, 구글 플레이는 구매할 아이템을 리스트 할 수 없다고 알려준다.

 - reserved item이 signature를 포함 할수 있다고 했는데, 아래와 같이 상황에서 signature를 포함한다.




Table 1. Conditions under which static responses are signed.
Application ever been published?Draft application uploaded and unpublished?User who is running the applicationStatic response signature
NoNoAnyUnsigned
NoNoDeveloperSigned
YesNoAnyUnsigned
YesNoDeveloperSigned
YesNoTest accountSigned
YesYesAnySigned


 - reserved product ID를 가지고 요청을 할때는 일반적인 REQUEST_PURCHASE를 만들면 된다. 실제 product ID 를 가지고 만들 필요 없다.

 - reserved product ID을 사용하는 절차는 다음과 같다.

1. 앱을 기기에 설치한다. (애뮬레이터에서는 사용이 불가하다.)

2. 당신의 디바이스에서 개발자 계정을 등록한다. (reserved product ID만 사용할때는 테스트 계정이 필요하지 않다.)

3. 구글 플레이가 결제를 지원하지는지 확인한다.

4. reserved product ID로 결제를 요청한다.

# 구글 플레이 시스템을 오버라이드해서 (reserved product ID)  in-app billing requests 요청을 만든다.  이 요청은 실제 상품 환경과 비교 할수 없습니다.


@ Testing In-app Purchases Using Your Own Product IDs

 # 정적인 응답 테스트가 끝나면 실제 product id로 end-to-end 결제를 테스트 할 수 있다. 구글 플레이에서 응답해서 결제 및 지불까지 하는 테스트이다. 이때도 역시 애플리케이션을 퍼블리싱 할 필요가 없고 드래프트 상태로 올려놓기만 하면 된다.

- end-to-end 테스트를 하기 위해서는 테스트 계정을 등록해야 합니다. 테스트 계정은 판매자 사이트에서 설정할 수 있습니다. 또한 판매자 화면의 테스트 하고자 하는 앱에서 실제 product id를 생성해서 등록 시켜야 합니다.

- 실제 product id를 가지고 테스트를 하는 절차는 아래와 같다.

 1. 애플리케이션을 마켓에 올려놓는다. (draft상태로)
 - 애플리케이션 릴리즈는 signed해서 만든다. 설치한 앱 버전과 업로드한 버전은 일치시켜야 한다.

 2. 상품을 마켓에 등록한다.

 3. 기기에 애플리케이션을 설치한다.(No 에뮬레이터)

 4. 기기에 테스트 계정을 상위 계정(primary account)으로 설정한다.

 5. 구글 플레이 버전을 확인한다. (자세한건 이전 내용 참조)

 6. 결제를 실행 한다.

2012년 5월 10일 목요일

[In-app Billing_2]In-app Billing 보안 및 설계

 # 보안과 사용자 애플리케이션 디자인을 살펴보자

@ Security Best Practices

 1. 사인(signature)의 식별 작업을 서비스 서버에서 한다. 

- 이는 애플리케이션 디컴파일 되어서 해킹이 될 위험으로 벗어나게 해준다. 이를 위한 전제는 서비스 서버를 두고 이를 위한 통신 모듈(보안)이 개발되어야 한다.

 2. 잠금 해제된 컨텐츠를 보호해야 한다. 

- 결제를 통해 획득한 컨텐츠가 유포되지 않도록 보호 해야 한다. real-time 서비스를 이용하면 매번 컨텐츠 구매 여부를 확인 할수있다. 그리고 서비스 서버가 존재하면 매번 컨텐츠를 가져다 줄수 있다. 만약 컨텐츠가 가져와서 저장될 수 있다면 이 역시 암호화 해야 한다.

3. 소스 코드를 암호화 한다. 

- 예를 들어 안드로이드에서 제공하는 프로가드를 통해 코드를 암호화할 수 있다.
# 프로가드로 암호화 할시에는 아래 명령어를 써서 IPC 패키지는 유지하도록 한다.
  ==> keep class com.android.vending.billing.**

 4. 샘플 코드를 수정해서 사용한다. 

 - 샘플 코드를 그래도 사용한다면 이를 알고 있는 해커에게 쉽게 해킹이 될 수 있다.

 5. 보안 nonces를 랜덤하게 생성해서 사용한다.

 - nonces가 예상되어 지거나 계속적으로 하나만 사용하면 해킹의 대상이 될수 있다. 항상 암호화된 랜덤 숫자를 생성해서 사용한다.
 - 서버에서 nonce를 생성하는 것도 괜찮은 방법이다.


 6. 컨텐츠와 저작권 도용에 대해 구글 플레이에 신고를 한다.


 7. 구글 플레이 공개 키를 암호화해서 노출 되지 않도록 한다. 

- 소스 코드에 그대로 박으면 안된다.






[In-app Billing_1]In-app Billing 구현 2


# In-App Billing을 구현해 보자.

- 아래는 전반적인 구현 순서이다.


1. in-app billing 샘플 애플리케이션을 다운 받는다.

2. IMarketBillingService.aidl 파일을 당신의 프로젝트에 추가한다.

3. Manifest.xml을 업데이트한다.

4. Service를 만들어 MarketBillingService 와 묶는다. 이는 구글 플레이 요청과 응답을 받기 위함이다.

5.  broadcast intents와 관련된 응답을 처리하기 위한 BroadcastReceiver를 만든다.

6.  in-app billing이 지원하는 애플리케이션 코드를 수정한다.



-  전 게시물에서 서비스를 만들어 다루는 법(4번)을 설명을 드렸습니다. 오늘은 BroadcastReceiver를 만드는 것으로 시작하겠습니다.


@ Downloading the Sample Application

- 구글 플레이는 사용자 애플리케이션에 비동기적으로 결제 정보를 보냅니다. 이를 받기 위해서는 BroadcastReceiver가 있어야 합니다. 이 Receiver 다루는 응답은 아래와 같습니다.

  • com.android.vending.billing.RESPONSE_CODE :  요청이 잘 들어 갔는지에 대한 응답이다. 요청이 완료되었다는 의미는 아니다. 단순히 전달이 잘되었는지만 알려준다.
Table 1. Summary of response codes returned by Google Play.
Response CodeValueDescription
RESULT_OK0Indicates that the request was sent to the server successfully. When this code is returned in response to aCHECK_BILLING_SUPPORTED request, indicates that billing is supported.
RESULT_USER_CANCELED1Indicates that the user pressed the back button on the checkout page instead of buying the item.
RESULT_SERVICE_UNAVAILABLE2Indicates that the network connection is down.
RESULT_BILLING_UNAVAILABLE3Indicates that in-app billing is not available because the API_VERSION that you specified is not recognized by the Google Play application or the user is ineligible for in-app billing (for example, the user resides in a country that prohibits in-app purchases).
RESULT_ITEM_UNAVAILABLE4Indicates that Google Play cannot find the requested item in the application's product list. This can happen if the product ID is misspelled in your REQUEST_PURCHASE request or if an item is unpublished in the application's product list.
RESULT_DEVELOPER_ERROR5Indicates that an application is trying to make an in-app billing request but the application has not declared the com.android.vending.BILLING permission in its manifest. Can also indicate that an application is not properly signed, or that you sent a malformed request, such as a request with missing Bundle keys or a request that uses an unrecognized request type.
RESULT_ERROR6Indicates an unexpected server error. For example, this error is triggered if you try to purchase an item from yourself, which is not allowed by Google Checkout.

  • com.android.vending.billing.IN_APP_NOTIFY : 요청이 응답되었는지 알려 준다. 이때 notification_id를 포함하는데 이것을 이용해 GET_PURCHASE_INFORMATION 요청을 보내면 아래 응답에서 세부적인 정보를 알수 있다.
  • com.android.vending.billing.PURCHASE_STATE_CHANGED : 요청에 대한 실제적인 응답 정보를 담고 있다. 결제가 성공했는지 실패했는지, 환불되었는지에 대한 정보를 담고 있다. 
Table 1. Description of broadcast intent extras that are sent in response to billing requests.
IntentExtraDescription
com.android.vending.billing.RESPONSE_CODErequest_idlong representing a request ID. A request ID identifies a specific billing request and is returned by Google Play at the time a request is made.
com.android.vending.billing.RESPONSE_CODEresponse_codeAn int representing the actual Google Play server response code.
com.android.vending.billing.IN_APP_NOTIFYnotification_idString representing the notification ID for a given purchase state change. Google Play notifies you when there is a purchase state change and the notification includes a unique notification ID. To get the details of the purchase state change, you send the notification ID with theGET_PURCHASE_INFORMATION request.
com.android.vending.billing.PURCHASE_STATE_CHANGEDinapp_signed_dataString representing the signed JSON string. The JSON string contains information about the billing transaction, such as order number, amount, and the item that was purchased or refunded.
com.android.vending.billing.PURCHASE_STATE_CHANGEDinapp_signatureString representing the signature of the JSON string.

 - 아래의 샘플 코드는 Broadcast intent 응답을 어떻게 받아서 Extra 정보를 추출하는지 보여준다.

public class BillingReceiver extends BroadcastReceiver {

  private static final String TAG = "BillingReceiver";

  // Intent actions that we receive in the BillingReceiver from Google Play.
  // These are defined by Google Play and cannot be changed.
  // The sample application defines these in the Consts.java file.
  public static final String ACTION_NOTIFY = "com.android.vending.billing.IN_APP_NOTIFY";
  public static final String ACTION_RESPONSE_CODE = "com.android.vending.billing.RESPONSE_CODE";
  public static final String ACTION_PURCHASE_STATE_CHANGED =
    "com.android.vending.billing.PURCHASE_STATE_CHANGED";

  // The intent extras that are passed in an intent from Google Play.
  // These are defined by Google Play and cannot be changed.
  // The sample application defines these in the Consts.java file.
  public static final String NOTIFICATION_ID = "notification_id";
  public static final String INAPP_SIGNED_DATA = "inapp_signed_data";
  public static final String INAPP_SIGNATURE = "inapp_signature";
  public static final String INAPP_REQUEST_ID = "request_id";
  public static final String INAPP_RESPONSE_CODE = "response_code";


  @Override
  public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();
    if (ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
      String signedData = intent.getStringExtra(INAPP_SIGNED_DATA);
      String signature = intent.getStringExtra(INAPP_SIGNATURE);
      // Do something with the signedData and the signature.
    } else if (ACTION_NOTIFY.equals(action)) {
      String notifyId = intent.getStringExtra(NOTIFICATION_ID);
      // Do something with the notifyId.
    } else if (ACTION_RESPONSE_CODE.equals(action)) {
      long requestId = intent.getLongExtra(INAPP_REQUEST_ID, -1);
      int responseCodeIndex = intent.getIntExtra(INAPP_RESPONSE_CODE,
        ResponseCode.RESULT_ERROR.ordinal());
      // Do something with the requestId and the responseCodeIndex.
    } else {
      Log.w(TAG, "unexpected action: " + action);
    }
  }
  // Perform other processing here, such as forwarding intent messages to your local service.
}

@ Verifying Signatures and Nonces

- 인앱 결제는 두가지 메커니즘으로 결제를 식별한다. nonces와 signatures이다. nonces는 암호화된 보안 번호이다. 사용자 애플리케이션에서 생성해서 GET_PURCHASE_INFORMATION 과 RESTORE_TRANSACTIONS 요청에 함께 보낸다. 이 nonces는 PURCHASE_STATE_CHANGED broadcast intent응답에 함께 와서 요청에 대한 응답임을 식별한다. 그리고 PURCHASE_STATE_CHANGED 응답에는 세부 결제 정보와 signature를 가지고 있다. 이는 결제를 식별하는 또 하나의 인자이다.


  private static final SecureRandom RANDOM = new SecureRandom();
  private static HashSet<Long> sKnownNonces = new HashSet<Long>();

  public static long generateNonce() {
    long nonce = RANDOM.nextLong();
    sKnownNonces.add(nonce);
    return nonce;
  }

  public static void removeNonce(long nonce) {
    sKnownNonces.remove(nonce);
  }

  public static boolean isNonceKnown(long nonce) {
    return sKnownNonces.contains(nonce);
  }

  - 사용자 애플리케이션은 PURCHASE_STATE_CHANGED에 포함되어 있는 signature를 통해 거래를 식별할 수 있다. 이때 구글 public key가 있어야 한다. 이 키는 퍼블리셔 계정 사이트에서 얻을 수 있다.


Figure 2. The Licensing and In-app Billing panel of your account's Edit Profile page lets you see your public key.
# 구글 공개키를 그대로 String타입에 넣어서 코딩 하지 말것을 권한다. 이는 해커나 크래커의 타겟이 될수 있다.


@ Modifying Your Application Code


  * Creating a storage mechanism for storing purchase information

 - 결제를 성공하고 결제 내역을 저장해야 한다. 샘플 애플리케이션의 경우 내부에 저장하지만 이것은 보안적으로 좋지 않다. 서비스 서버를 운영한다면 거기에 저장하는 것이 좋다.

# 만약 결제 내역을 내부에 저장 한다면 암호화를 해서 넣어야 한다. "unmanaged" 결제의 경우 반드시 결제 내역을 저장해서 관리를 해야 한다. 즉, RESTORE_TRANSACTIONS 으로도 결제가 회복되지 않으므로 unmanaged 결제는 백업을 꼭 해야 한다.

  * Creating a user interface for selecting items


- 결제를 위한 UI페이지를 만들어야 한다. 이 페이지는 sendBillingRequest()를 호출하고 다루는 페이지이다.

















2012년 5월 7일 월요일

[In-app Billing_1]In-app Billing 구현 1


# In-App Billing을 구현해 보자.

- 아래는 전반적인 구현 순서이다.


1. in-app billing 샘플 애플리케이션을 다운 받는다.

2. IMarketBillingService.aidl 파일을 당신의 프로젝트에 추가한다.

3. Manifest.xml을 업데이트한다.

4. Service를 만들어 MarketBillingService 와 묶는다. 이는 구글 플레이 요청과 응답을 받기 위함이다.

5.  broadcast intents와 관련된 응답을 처리하기 위한 BroadcastReceiver를 만든다.

6.  in-app billing이 지원하는 애플리케이션 코드를 수정한다.




@ Downloading the Sample Application


- 샘플 애플리케이션의 구현 내용은 다음과 같다.


1. 구글 플레이에 결제 요청을 보낼 수 있다.

2. 동기, 비동기 응답을 처리 할 수 있다.

3. 보안 매커니즘을 통한 결제의 무결성을 식별할 수 있다.

4. 결제 아이템을 위한 UI를 제공한다.


# 샘플 애플리케이션 소스 설명과 다운 로드는 아래 이미지 참고.




 - 만약 샘플 애플리케이션을 실제로 실행해서 결제를 일으키기 위해서는 아래와 같은 작업이 필요하다.


1. 애플리케이션을 구성한 후 빌드한다.
[ src/com/example/dungeons/Security.java 의
  String base64EncodedPublicKey = "your public key here";
  에 판매계정의 공개키를 넣는다. 패키지 네임을 바꾸고 빌드한다.]

2. 구글 플레이에 올린다.
[draft 상태(not publish)로 애플리케이션을 올리고 상품을 등록한다.]

3. 테스트 계정을 세팅하고 샘플 애플리케이션 실행시킨다.
[에뮬레이터에서 테스트가 불가능 하므로 다바이스에 설치해서 실행 시킨다. 이때 테스트 계정을 등록해서 디바이스의 primary 계정으로 넣어서 실행시킨다. 테스트 계정의 경우 환불이 가능하다.]


 Consts.java 파일을 수정해서 디버깅 내용을 볼 수 있다.

@ Adding the AIDL file to your project

- com.android.vending.billing 패키지를 구현하고자 하는 사용자 애플리케이션에 넣는다.


@ Updating Your Application's Manifest

- 인앱 결제의 경우 구글 플레이 애플리케이션과 통신을 하기 때문에 이를 위한 권한이 Manifest에 설정 되어야 한다.


<uses-permission android:name="com.android.vending.BILLING" />


- 비동기 메시지(broadcast intent) 응답를 받기 위해 아래와 같이 BroadcastReceiver를 등록한다.이 때 인텐트 필터를 꼭 설정한다.


<receiver android:name="BillingReceiver">
      <intent-filter>
        <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
        <action android:name="com.android.vending.billing.RESPONSE_CODE" />
        <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
      </intent-filter>
    </receiver>


- IMarketBillingService 와 바인드해서 구글 플레이에 메시지를 보낼 수 있는 서비스를 등록 시킨다.


  <service android:name="BillingService" />



# 샘플 애플리케이션에서 BillingReceiver는 BroadcastReceiver로 broadcast 메시지를 다루고, BillingService는 Service로 요청 메시지를 구글 플레이에 보낸다.


@ Creating a Local Service


- 로컬 Services는 구글 플레이와 메시지(동기) 교환을 위해 아래와 같이 구현을 해서 사용할 수 있다.

1. MarketBillingService를 바인드 시킨다. 서비스이 onCreate()에서 바인드를 시킨다.



try {
  boolean bindResult = mContext.bindService(
    new Intent("com.android.vending.billing.MarketBillingService.BIND"), this,
    Context.BIND_AUTO_CREATE);
  if (bindResult) {
    Log.i(TAG, "Service bind successful.");
  } else {
    Log.e(TAG, "Could not bind to the MarketBillingService.");
  }
} catch (SecurityException e) {
  Log.e(TAG, "Security exception: " + e);
}


- 바인드 시킨 후에는 IMarketBillingService inferface로 billing 요청을 IPC 방식으로 보낼 수 있다. onServiceConnected()는 서비스 연결시 호출되는 콜백 메서드이다.


**
  * The Android system calls this when we are connected to the MarketBillingService.
  */
  public void onServiceConnected(ComponentName name, IBinder service) {
    Log.i(TAG, "MarketBillingService connected.");
    mService = IMarketBillingService.Stub.asInterface(service);
  }


- 위의 메서드가 호출되면 개발자는 sendBillingRequest() 메서드를 호출할수 있다. 샘플 애플리케이션에서 서비스 바인드의 구현은 BillingService를 보면 된다.



2. 결제 요청 메시지를 보낸다.


 - Service가 바인드 되면 IMarketBillingService의 sendBillingRequest()메서드(샘플 애플리케이션 기준)를 통해 통신을 한다. 이때 파라미터는 Bundle를 포함한다. 이 요청 메시지는 동기 응답을 주는데(결과 코드를 포함한) 결제 완료를 의미하진 않는다.

# 요청 메시지 타입

 - CHECK_BILLING_SUPPORTED : 구글플레이가 결제를 지원하는지 확인 요청
 - REQUEST_PURCHASE : 아이템 결제를 요청
 - GET_PURCHASE_INFORMATION : 결제(환불) 내역을 요청
 - CONFIRM_NOTIFICATIONS : 결제(환불) 내역 요청을 확인 알림
 - RESTORE_TRANSACTIONS : 관리되는 결제(managed purchase, 한 유저에 한번 결제되는 아이템) 기록을 요청해서 확인함

 - 관련 요청을 하기 위해서는 Bundle 파라미터를 만들어야 한다. 이때 BILLING_REQUEST, API_VERSION, PACKAGE_NAME 을 포함해야 한다.


protected Bundle makeRequestBundle(String method) {
  Bundle request = new Bundle();
  request.putString(BILLING_REQUEST, method);
  request.putInt(API_VERSION, 1);
  request.putString(PACKAGE_NAME, getPackageName());
  return request;


# 인앱 결제 요청은 애플리케이션 메인 스레드에서 실행해야 한다.


* Verifying that in-app billing is supported (CHECK_BILLING_SUPPORTED)



/**
* Request type is CHECK_BILLING_SUPPORTED
*/
  Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
  Bundle response = mService.sendBillingRequest(request);
  // Do something with this response.
}

 - 동기 응답(response) 번들의 RESPONSE_CODE의 값은 다음과 같다.

  • RESULT_OK : 인앱 결제가 지원된다.
  • RESULT_BILLING_UNAVAILABLE : 인앱 결제가 지원되지 않는다.API 버전 때문이거나 제한된 특정 국가의 유저이기 때문이다.
  • RESULT_ERROR : 구글 플레이 애플리케이션의 접속 에러
  • RESULT_DEVELOPER_ERROR : 퍼미션(com.android.vending.BILLING)을 선언하지 않거나 적절하지 않은 사인을 사용하거나 잘못된 요청을 보냈을 때.


-  CHECK_BILLING_SUPPORTED 요청은 어떤 비동기 응답을 위한 요청이 되지 않는다. 즉, 동기 응답만 있음.

- CHECK_BILLING_SUPPORTED 요청시 RemoteException block 통해 예외 사항을 사용자에게 알려 주도록 한다. 주 예외 사항은 구글 플레이 애플리케이션이 업데이트가 필요한 경우이다. 이와같은 오류의 메시지는 샘플 애플리케이션의 DIALOG_CANNOT_CONNECT_ID  in Dungeons.java에서 볼수 있다.

* Making a purchase request (REQUEST_PURCHASE)


1. REQUEST_PURCHASE 요청을 보낸다. (Making the request)

- 앞서 언급한 3가지 키-밸류는 모든 요청에 필수적으로 포함해야 한다.
   [ BILLING_REQUEST, API_VERSION, PACKAGE_NAME ]
이외에 REQUEST_PURCHASE요청에는  ITEM_ID를 키로 하는 productid(필수)와 DEVELOPER_PAYLOAD를 키로 하는 developerPayload(선택)이 넣는다.


/**
* Request type is REQUEST_PURCHASE
*/
  Bundle request = makeRequestBundle("REQUEST_PURCHASE");
  request.putString(ITEM_ID, mProductId);
  // Note that the developer payload is optional.
  if (mDeveloperPayload != null) {
    request.putString(DEVELOPER_PAYLOAD, mDeveloperPayload);
  }
  Bundle response = mService.sendBillingRequest(request);
  // Do something with this response.


- 동기 응답 response 번들의 값은 다음과 같다.


  •  RESPONSE_CODE : 요청 상태 코드
  •  REQUEST_ID : 요청에 대한 식별자(중복 x)
  •  PURCHASE_INTENT : PendingIntent으로 checkout UI 실행시 사용할 수 있다.


2. 리턴 받은 PendingIntent를 실행시킨다. (Using the pending intent)

 - PendingIntent를 실행 시키는 방법은 Android OS버전에 따라 다르다. 1.6의 경우 반드시 사용자 애플리케이션 액티비티에 스택하는 게 아니라 PendingIntent의 구별된 테스크에서 실행 해야 한다. 2.0이상의 경우 사용자 애플리케이션 액티비티에 스택 시켜서 실행 시킬 수 있다.


void startBuyPageActivity(PendingIntent pendingIntent, Intent intent) {
  if (mStartIntentSender != null) {
    // This is on Android 2.0 and beyond.  The in-app checkout page activity
    // will be on the activity stack of the application.
    try {
      // This implements the method call:
      // mActivity.startIntentSender(pendingIntent.getIntentSender(),
      //     intent, 0, 0, 0);
      mStartIntentSenderArgs[0] = pendingIntent.getIntentSender();
      mStartIntentSenderArgs[1] = intent;
      mStartIntentSenderArgs[2] = Integer.valueOf(0);
      mStartIntentSenderArgs[3] = Integer.valueOf(0);
      mStartIntentSenderArgs[4] = Integer.valueOf(0);
      mStartIntentSender.invoke(mActivity, mStartIntentSenderArgs);
    } catch (Exception e) {
      Log.e(TAG, "error starting activity", e);
      }
  } else {
    // This is on Android 1.6. The in-app checkout page activity will be on its
    // own separate activity stack instead of on the activity stack of
    // the application.
    try {
      pendingIntent.send(mActivity, 0 /* code */, intent);
    } catch (CanceledException e) {
      Log.e(TAG, "error starting activity", e);
      }
  }
}


# 개발자는 activity context(not an application context)에서 pending된 intent를 실행시켜야 한다.  또한 singleTop 모드로 실행 시킬 수 없다. 만약 singleTop모드로 실행시키면 intent는 애플리케이션 프로세스에 붙을 수 없다. 즉, 구글 플레이를 상단으로 가져오면 당신의 애플리케이션는 방해를 하게 될 것이다.

3. broadcast intents 응답을 처리한다. (Handling broadcast intents)

- 사용자 애플리케이션이 결제 요청(REQUEST_PURCHASE)를 하면 두개의 비동기 응답이 오게 된다.

 1. 첫째는 RESPONSE_CODE를 포함한 broadcast이다. 이는 결제 요청에 대한 에러 정보를 가진다. 만약 RESULT_OK가 오면 이건 요청이 잘 전달되었다는 신호이다. (결제가 완료 되었다는 건 아니다.)

 2. 둘째는 IN_APP_NOTIFY 브로드캐스트이다. 이는 결제가 체결되었는지를 알려주는 응답이다.

-  결제 과정은 바로 이루어 지지 않는다. 그래서 지연되고 완료되는 과정을 구글 플레이는 알려준다. 이 디스플레이는 60초가 지나면 사라진다. 사용자는 이 디스플레이에 의존할 필요없이 개별의 UI를 통해 진행상황을 알려 줄 수 있다.


# 개발자은 managed item 결제를 할때, 결제가 진행되는 동안 동일한 결제를 막을 필요가 있다. 구글 플레이에서는 사용자가 동일한 아이템을 두번 결제를 진행 시키면 에러를 사용자에게 표시 할것이다. 그리고 당신의 애플리케이션은 두번째 결제에 대한 IN_APP_NOTIFY 메시지를 계속 기다려, 문제가 발생하게 된다.



* Retrieving transaction information for a purchase or refund(GET_PURCHASE_INFORMATION)


-  IN_APP_NOTIFY 응답은 결제 정보를 알 수 있는 notification id를 포함하고 있다.  결제 정보를 알기 위해 5가지 키를 가지는 번들을 만들어 요청을 보내야 한다. 코드는 아래와 같다.


/**
* Request type is GET_PURCHASE_INFORMATION
*/
  Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");
  request.putLong(REQUEST_NONCE, mNonce);
  request.putStringArray(NOTIFY_IDS, mNotifyIds);
  Bundle response = mService.sendBillingRequest(request);
  // Do something with this response.
}

- 구글 플레이에서 위와 같은 요청으로 RESPONSE_CODE와 REQUEST_ID를 키로하는 값을 가진 번들을 보낸다(동기 응답).

- GET_PURCHASE_INFORMATION 요청은 두가지 비동기 응답을 가진다.(비동기)

1. 구글 플레이에서 RESPONSE_CODE로 요청에 대한 정보를 보낸다. 요청이 제대로 전달되었는지를 알려준다.(결제완료 아님)

2. 둘째로 PURCHASE_STATE_CHANGED 응답을 보낸다. 이는 결제 정보를 가지고 있다. 결제 정보는  a signed JSON string(unencrypted)이다. 메시지는 결제의 무결성을 확인 할수있는 사인(signature)를 포함하고 있다.

* Acknowledging transation information (CONFIRM_NOTIFICATIONS)

 -  결제 정보를 받았으면 반드시, 확인 메시지를 보내야 한다. 이때 네가지 키에 값을 넣은 번들을 보내야 한다.


/**
* Request type is CONFIRM_NOTIFICATIONS
*/
  Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
  request.putStringArray(NOTIFY_IDS, mNotifyIds);
  Bundle response = mService.sendBillingRequest(request);
  // Do something with this response.
}


- 위의 요청으로 동기 응답을 받는다. 응답은 RESPONSE_CODE와 REQUEST_ID를 가진다.

- 또한 위의 요청으로 비동기 응답(RESPONSE_CODE broadcast intent)을 가져온다.  이는 요청의 상태와 에러 정보를 알려준다.

- 확인 요청은 IN_APP_NOTIFY 응답을 받은 후 반드시 보내야 한다. 그렇지 않으면 구글 플레이는 계속적으로 IN_APP_NOTIFY 응답을 보낸다. 또한 다중 주문에 대해서도 처리해야 하기 때문에 확인 응답을 보내야 한다.

# 가장 좋은 예는 사용자가 결제를 통해 관련 서비스를 이용가능하면 확인 메시지를 보내는 것이다.


* Restoring transaction information (RESTORE_TRANSACTIONS)


- 결제 내역을 회복시키 위해서는 4가지 키가 필요하다. 기본 키 3가지와 REQUEST_NONCE키에 암호화 된 nonce값을 필요로 한다. 이 nonce는 PURCHASE_STATE_CHANGED broadcast intent 응답에서 결제를 식별하기 위해 제공한다.

/**
* Request type is RESTORE_TRANSACTIONS
*/
  Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
  request.putLong(REQUEST_NONCE, mNonce);
  Bundle response = mService.sendBillingRequest(request);
  // Do something with this response.
}

- 위의 요청으로 동기 응답을 받는다. RESPONSE_CODE와  REQUEST_ID를 키로 하는 번들이다. RESPONSE_CODE는 요청의 전송에 관한 상태 응답, REQUEST_ID 요청에 대한 식별자 이다.

- 위의 요청으로 또한 두가지 비동기 응답을 받을 수 있다.
1. 첫째로 RESPONSE_CODE broadcast intent로서 요청에 대한 상태 정보를 보내 준다.

2. 둘째로 요청이 성공하면 요청에 대한 정보를 PURCHASE_STATE_CHANGED broadcast intent로 받는다. 이 메시지는 세부적인 결제 정보를 가지고 있다.이 정보는 JSON string(암호화 되지 않음) 타입이다.

# RESTORE_TRANSACTIONS요청은 애플리케이션이 처음 디바이스에서 실행될때 그리고 삭제되고 다시 설치되었을때 보내야한다.