2012년 4월 30일 월요일

[모바일 하이브리드 웹 과정]SudoKuApp

Abort.java


package kr.sgdata.android.sudoku1;

import android.app.Activity;
import android.os.Bundle;

public class About extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.about);
}
}

Prefs.java


package kr.sgdata.android.sudoku1;

import android.os.Bundle;
import android.preference.PreferenceActivity;

public class Prefs extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
}
}

Sudoku1Activity.java


package kr.sgdata.android.sudoku1;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.MenuInflater;
import android.view.View.OnClickListener;

public class Sudoku1Activity extends Activity implements OnClickListener{
    /** Called when the activity is first created. */
private static final String TAG="Sudoku";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
     
        View continueButton = findViewById(R.id.continue_button);
        continueButton.setOnClickListener(this);
     
        View newButton = findViewById(R.id.new_button);
        newButton.setOnClickListener(this);
     
        View aboutButton = findViewById(R.id.about_button);
        aboutButton.setOnClickListener(this);
     
        View exitButton = findViewById(R.id.exit_button);
        exitButton.setOnClickListener(this);
    }

public void onClick(View v) {
// TODO Auto-generated method stub
switch(v.getId()){
case R.id.continue_button:break;
case R.id.about_button:
Intent i = new Intent(this, About.class);
startActivity(i);
break;
case R.id.new_button:
openNewGameDialog();
break;
case R.id.exit_button:
finish();
break;
}
}


@Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
switch(item.getItemId()){
case R.id.settings:
startActivity(new Intent(this, Prefs.class));
return true;
}
return false;
}

private void openNewGameDialog(){
new AlertDialog.Builder(this)
.setTitle(R.string.new_game_title)
.setItems(R.array.difficulty,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialoginterface, int i) {
// TODO Auto-generated method stub
startGame(i);
}
})
.show();

}

private void startGame(int i){
Log.d(TAG, "clicked on "+i);
}
}

AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="kr.sgdata.android.sudoku1"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="10" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:debuggable="true">
        <activity
            android:name=".Sudoku1Activity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".About"
            android:label="@string/about_title"
            android:theme="@android:style/Theme.Dialog">
        </activity>
        <activity android:name=".Prefs"
            android:label="@string/settings_title">
        </activity>
    </application>

</manifest>

res/layout/main.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal"
    android:padding="30dip"
    android:background="@color/background" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="center" >

        <TextView
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/main_title"
            android:layout_marginBottom="25dip"
            android:textSize="24.5sp" />

        <Button
            android:id="@+id/continue_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/continue_label" />

        <Button
            android:id="@+id/new_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/new_game_label" />
     
        <Button
            android:id="@+id/about_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/about_label" />
     
        <Button
            android:id="@+id/exit_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/exit_label" />
    </LinearLayout>

</LinearLayout>

res/layout-land/main.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal"
    android:padding="15dip"
    android:background="@color/background" >
    <LinearLayout
        android:paddingLeft="20dip"
        android:paddingRight="20dip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="center" >
        <TextView
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/main_title"
            android:layout_marginBottom="20dip"
            android:textSize="24.5sp" />
        <TableLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:stretchColumns="*" >
            <TableRow>
                <Button
                    android:id="@+id/continue_button"
                    android:text="@string/continue_label" />
                <Button
                    android:id="@+id/new_button"
                    android:text="@string/new_game_label" />
            </TableRow>
            <TableRow>
                <Button
                    android:id="@+id/about_button"
                    android:text="@string/about_label" />
                <Button
                    android:id="@+id/exit_button"
                    android:text="@string/exit_label" />
            </TableRow>
        </TableLayout>
    </LinearLayout>
</LinearLayout>

res/values/strings.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="hello">Hello World, Sudoku1Activity!</string>
    <string name="app_name">수도쿠</string>
    <string name="main_title">안드로이드 수도쿠</string>
    <string name="continue_label">게임 계속</string>
    <string name="new_game_label">신규 게임</string>
    <string name="about_label">게임 정보</string>
    <string name="exit_label">게임 종료</string>
 
    <string name="about_title">게임 정보</string>
    <string name="about_text">\
    스도쿠는 가로9개 세로9개, 모두 81개의 네모 칸에
    숫자를 체워가는데 각행과열3x3개의 네모칸으로
    이뤄진 9개의 상자에 1부터9까지 숫자가 딱
    한번씩만 들어가야한다.
    </string>
 
    <string name="settings_label">설정...</string>
    <string name="settings_title">수도쿠 설정</string>
    <string name="settings_shortcut">s</string>
    <string name="music_title">음악</string>
    <string name="music_summary">배경음악재생</string>
    <string name="hints_title">힌트</string>
    <string name="hints_summary">게임동안 힌트보이기</string>
 
    <string name="new_game_title">게임난이도</string>
    <string name="easy_label">쉬움</string>
    <string name="medium_label">중간</string>
    <string name="hard_label">어려움</string>

</resources>

res/values/colors.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="background">#3500ffff</color>
</resources>

res/values/arrays.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <array name="difficulty">
        <item>@string/easy_label</item>
        <item>@string/medium_label</item>
        <item>@string/hard_label</item>
    </array>
</resources>


res/xml/settings.xml


<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">
    <CheckBoxPreference
        android:key="music"
        android:title="@string/music_title"
        android:summary="@string/music_summary"
        android:defaultValue="true"/>
    <CheckBoxPreference
        android:key="hints"
        android:title="@string/hints_title"
        android:summary="@string/hints_summary"
        android:defaultValue="true"/>    
</PreferenceScreen>

res/menu/menu.xml


<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/settings"
        android:title="@string/settings_label"
        android:alphabeticShortcut="@string/settings_shortcut" />
</menu>











2012년 4월 21일 토요일

[In-app Billing_0]Overview 2

@ Messaging sequence 1 [PURCHASE]


 - 아래는 메시지 시퀀스의 전형적인 요청을 보여준다.
 (sendBillingResquest()는 Bold, broadcast는 italic)


==> 결제 요청


1-1.   (발신_IPC) 사용자 애플리케이션은 구글 플레이에 구매 요청(REQUEST_PURCHASE)를 한다. 이때 PRODUCT_ID와 다른 파라미터를 함께 보낸다.

1-2.   (수신_IPC) 구글 플레이에서 번들 데이터(RESPONSE_CODE, PURCHASE_INTENT, REQUEST_ID)를 요청한 앱에 보낸다. PURCHASE_INTENT는 결제 UI를 위한 PendingIntent를 제공한다.

1-3.   (발신_UI호출)사용자 애플리케이션은 PendingIntent로 결제 UI를 실행 시킨다.
  - 단, 이때 application context가 아니라 activity context로 부터 pending intent를 실행시킨다.

==> 결제 종료 및 결제 내역 정보 요청


2.  (수신_Broadcast)결제가 끝나면(상품을 성공적으로 구매했거나 결제를 취소한 것을 의미) 구글 플레이에서 사용자 애플리케이션으로 알림 메시지(an IN_APP_NOTIFY broadcast intent)를 보낸다. 알림 메시지는 notification ID(transation과 관련된)를 포함한다.

3-1.  (발신_IPC)사용자 애플리케이션은 GET_PURCHASE_STATE_CHANGED 요청을 보내서 관련된 결제정보를 요청한다. 이때 4번에서 얻은 notification ID를 함께 보낸다.

3-2. (수신_IPC) 구글 플레이는 번들 형태(RESPONSE_CODE key, REQUEST_ID key)로 해당 결제 정보를 보내준다.

==> 결제 내역 확인 


3-3.  (수신_Broadcast)구글 플레이 TRANSATION 정보를 사용자 애플리케이션에 보낸다.(PURCHASE_STATE_CHANGED broadcast intent)

4-1. (발신_IPC)사용자 애플리케이션은  NOTIFICATION ID로 수신된 결제 정보를 확인한다. 확인후 CONFIRM_NOTIFICATIONS 를 보낸다.

4-2. (수신_IPC) 구글 플레이에서 사용자 애플리케이션에 번들(RESPONSE_CODE , REQUEST_ID를 포함한)을 보낸다.


#  당신은 결제 내역 확인 후 확인 메시지를  구글 플레이에 보내야 한다.[4-1 CONFIRM_NOTIFICATIONS] 그렇지 않으면 구글 플레이는 IN_APP_NOTIFY message를 확인 하지 않은 것으로 간주한다. 이것으로 개발자는 상품을 사용자에게 전달하기 전까지 확인 메시지를 보내지 않도록 함으로 결제의 안전성(중간에 애플리케이션이 종료되거나, 상품이 전달 되지 못하는 경우)을 높일 수 있다. 즉, 결제가 시작되고 물건이 사용자에게 전달되면, CONFIRM_NOTIFICATIONS를 구글 플레이에 보내 결제를 마무리 하도록 할 수 있다. 또한 여러 건의 구매에 경우 이러한 확인 응답은 매우 중요한 역할을 한다.(중간에 여러가지 일들이 있을수 있으므로)



@ Messaging sequence 2 [RESTORE]


-  아래는 이전에 했던 결제 내용을 확인 하는 메시지 시퀀스입니다.
(sendBillingResquest()는 Boldbroadcast는 italic)



- 이 메시지 시퀀스의 경우 한번의 요청으로 3가지 응답을 받습니다.

 1.(발신_IPC) RESTORE_TRANSATIONS 요청을 구글 플레이에 보냅니다.

 2.(수신_IPC) 구글 플레이는 RESPONSE_CODE와 REQUEST_ID를 포함한 번들을 사용자 애플리케이션에 보냅니다.

 3. (수신_Broadcast) 구글 플레이는 RESPONSE_CODE broadcast intent를 통해 요청 정보 상태에 대한 응답을 보냅니다.(정보 상태, 에러 정보등) 이때 메시지는 특정 request ID를 참조하고 있습니다.

 4. (수신_Broadcast)  구글 플레이는 또한 PURCHASE_STATE_CHANGED broadcast intent를 보냅니다. 이 응답은 요청한 결제 정보를 포함하고 있습니다. (이때 CONFIRM_NOTIFICATIONS 메시지를 보낼 필요가 없다)

# RESTORE_TRANSACTIONS 요청은 처음 사용자 애플리케이션이 설치될때나 삭제되고 다시 설치 될때 사용해야 한다.



@ Messaging sequence 3 [CHECKING]


- 아래는 결제 가능한 상태인지 알아보는  메시지 시퀀스 입니다.
(sendBillingResquest()는 Bold)




1. 사용자 애플리케이션은 구글 플레이에  CHECK_BILLING_SUPPORTED를 요청한다.


2. 구글 플레이는 번들 데이터로 사용자 애플리케이션에 응답을 한다.
 - RESULT_OK  :  결제가능 상태
 - RESULT_BILLING_UNAVAILABLE : 결제 불가 상태 (사용자가 있는 곳이 부분 결제가 되지 않는 경우..)
 - SERVER_ERROR : 구글 플레이 서버 오류




@ Handling IN_APP_NOTIFY messages

 - 아래는 IN_APP_NOTIFY 메시지의 특별한 케이스를 다룬다. 

1. Handling multiple IN_APP_NOTIFY message

 -  구글 플레이는 늘 CONFIRM_NOTIFICATIONS 메시지(PURCHASE_STATES_CHANGED에 해당하는)를 받았을 때 PURCHASE_STATES_CHANGED 메시지를 보내지 않는다. 하지만 때때로 사용자 애플리케이션이 CONFIRM_NOTIFICATIONS 메시지를 보내도 반복적으로 PURCHASE_STATES_CHANGED 메시지를 멈추지 않고 보낸다. 

  이와 같은 현상은,
  첫째로, CONFIRM_NOTIFICATION 메시지 전송중 네트워크 커낵션을 잃어버리면 일어난다.
  둘째로, 여러개의 IN_APP_NOTIFY 메시지를 보낸 경우 해당 확인 (CONFIRM_NOTIFICATIONS) 메시지를 받지 못했을 때 일어난다. 

그러므로 사용자 애플리케이션은 각 결제에 대한 IN_APP_NOTIFY를 식별해서 해당 확인 메시지를 보내야 한다. 이를 위해 JSON string에 있는 orderid를 사용하면 된다. (orderid는 모든 결제에 대해 중복되지 않는 고유한 것 아이디이다.)


2. Handling refunds and other unsolicited IN_APP_NOTIFY messages
 [ 환불 메시지 , 요청하지 않은 알림 메시지]

- REQUEST_PURCHASE 메시지를 보내지 않고 IN_APP_NOTIFY broadcast intents를 받을 수 있다.



1. 두 개의 디바이스에 사용자 애플리케이션이 설치되어 한 디바이스에서 결제 요청을 보낼때, 요청하지 않은 알림 메시지를 받을 수 있다. 이 경우 전형적인 메시지 시퀀스 요청으로 처리 할 수 있다. 단, 'managed per user account' 타입 아이템에만 지원된다.

2. 구글 체크 아웃에서 환불로 메시지를 보낼 때 요청하지 않은 알림 메시지를 받을 수 있다. 이 경우도 위와 같이 전형적인 메시지 시퀀스 요청으로 PURCHASE_STATES_CHANGED 메시지를 받을 수 있다. 여기서 포함된 JSON string을 통해 환불 상태를 확인 할 수 있다. (purchaseState ->2)

# 중요!!  : Google Checkout API를 통해 환불 요청하거나 부분 유료 결제를 취소할 수 없다. 단, 판매자 계정에서 취소시 환불 될 수 있다. Google Checkout API는 결제에 대한 정보만 검색할 수 있다.


@ Security Controls


 - 구글 플레이 결제 정보 무결성(중복방지)을 위해, PURCHASE_STATE_CHANGED의  JSON string에 사인을 넣고 있다. 이 사인을 만들기 위해 판매자의 private key를 사용한다. 판매자는 RSA key 짝(public keys)을 구글 플레이 사이트에서 생성할 수 있다.

 - 구글 플레이에서 사인한 응답을 내 RSA 키와 짝이 맞는 공개키를 사용해 식별한다. 이는 조작되거나 해킹된 정보를 식별할 수 있다. 이 확인 작업을 사용자 서비스 서버가 있다면 거기서 하기를 권장한다.

- 결제 정보에는 nonces라는 번호가 있다. 이는 유일한 랜덤 번호이다. 이를 통해 결제를 구별해 낼 수 있다. 당신의 애플리케이션은 GET_PURCHASE_INFORMATION 요청과 RESTORE_TRANSACTIONS 요청을 할때 반드시 nonce를 생성해서 보내야 한다. 구글 플레이는 이 요청을 받으면  결제에 관한  JSON  string 정보에 nonce를 넣어 응답한다. 사용자 애플리케이션은 응답을 받았을 때 사인 정보를 식별하는 것과 더불어 nonce를 확인해서 할 필요가 있다.

@in-app Billing Requirements and Limitations


# 인앱 결제를 하기 전에 다음과 같은 사항을 확인해야 한다.

1. 인앱 결제는 반드시 구글 플레이를 통해서 유통되는 애플리케이션에만 구현될 수 있다.
2. 당신은 Google Checkout Merchant account를 가지고 있어야 한다.
3. 당신의 애플리케이션이 Android 3.0 버전에서 구동된다면, in-app billing은 5.0.12 버전 이상을 필요로 하고 그외에는 2.3.4버전 이상을 필요로 한다.
4. 애플리케이션은 Android 1.6 버전 이상에서 인앱 결제를 사용할 수 있다.
5. 당신은 인앱 결제를 통해 전자 컨텐츠만 판매 할 수 있다. 물리적인 재화나 개인적인 서비스등 물리적인 전달이 필요한 어떤 것이든 판매 할수 없다.
6. 구글 플레이는 어떤 컨텐츠 배달 폼을 지원하지 않는다. 컨텐츠 배달, 전달은 사용자 애플리케이션을 판매하는 판매자에게 책임이 있다.
7. 네트워크가 연결되지 않은 상황에서 결제는 일어날수 없다. 결제완료 요청을 하기 위해서는 디바이스는 네트워크를 통해 구글 플레이 서버에 접속해야 한다.










[In-app Billing_0]Overview 1

# In-App Billing


- 구글에서 제공하는 부분 결제 모듈이다. 애플리케이션을 만들면서 수익을 낼 수 있는 방법에는 크게 3가지가 있다. 애플리케이션 단위로 판매해서 수익을 얻는 모델,  무료 애플리케이션에 광고를 넣어 수익을 얻는 모델, 마지막으로 무료 애플리케이션으로 배포하고 애플리케이션 내에서 부분 결제를 유도하는 모델이 있다. In-App Billing은 마지막 모델에서 필수적인 모듈이다.


@ In-App Billing Architecture


- 인앱결제는 실제 유저의 프로그램과 구글 플레이(마켓) 서버와 직접 통신을 하지 않습니다. 그 대신 구글 플레이 애플리케이션 간 통신(IPC)를 통해 구글 플레이(마켓) 서버와 통신을 합니다. 이때 비동기 메시지 루프를  사용합니다.

- 컨텐츠를 제공하거나 거래를 확인하기 위해 개인 서비스 서버를 사용할 수 있습니다. 단 이때도 원격 서버와 직접적으로 구글 플레이(마켓) 서버와 직접적으로 결제를 하지 않습니다.


*대표적인 앱인 결제의 3가지 구현 컴포넌트

- A Service : 유저 애플리케이션에서 구입 메시지 처리.
                   인앱 결제 서비스에 결제 요청을 송신.

- A BroadcastReceiver  : 비동기적으로 구글 플레이 애플리케이션의 결제 응답을 수신.

- A Security componet : 보안과 관련된 작업 수행 ( 서명 확인 이나 난수 키)


* 추가적인 2가지 컴포넌트

- A response Handler  :  구입 알림, 에러, 다른 상태 응답에 대한 처리를 지원한다.
- An observer : 응용 프로그램에 결제 관련 응답(상태) 정보에 따라 콜백을 보낸다.

* 유저 컴포넌트

- 추가적으로 구입 아이템에 관한 선택 컴포넌트를 만들 수 있다. 하지만 결제 창에 대한 컴포넌트는 만들 필요가 없다. (구글 플레이에서 제공한다.)



@  인앱 결제 메시지




- 유저가 결제를 시작하면 응용 애플리케이션은 구글 플레이 인앱 결제 서비스에 신호를 보낸다. (IPC) 구글 플레이 애플리케이션은 동기적, 비동기적으로 결제 요청에 대한 응답을 처리한다.

* In-app billing requests


- 응용 애플리케이션은 IMarketBillingService.aidl의 sendBillingRequest() (IPC 메서드)를 사용해서 결제를 요청한다. [이 인터페이스(IMarketBillingService.aidl) 샘플앱에서 얻을 수 있다.]

 interface IMarketBillingService {  
   /** Given the arguments in bundle form, returns a bundle for results. */  
   Bundle sendBillingRequest(in Bundle bundle);  
 }  

- sendBillingRequest()는 하나의 번들 파라미터를 갖는다. 이 번들에 포함 되는 정보는 아래 도표를 참고 하길 바란다.(번들 키)



* BILLING_REQUEST 키 값


- CHECK_BILLING_SUPPORTED : 인앱 결제가 지원하는지 확인, 응용프로그램이 시작될때 보통 요청을 보냄.

- REQUEST_PURCHASE : 응용 프로그램에서 구매 메시지를 전송. 이 요청을 통해 구글 플레이는 결제 화면을 보여 거래를 처리한다.

- GET_PURCHASE_INFORMATION : 구매 상태 변경의 정보 검색 요청. 구매가 성공되었거나, 구매가 취소 되었을때 구매 상태는 변화한다.

- CONFIRM_NOTIFICATIONS : 구매 상태 변경의 정보 검색 요청을 받았음을 알림. 구글 플레이는 이 알림이 올때까지 구매 상태 변경 정보를 보낸다.

- RESTORE_TRANSACTIONS : 제 설치 또는 다른 기기에 처음으로 애플리케이션을 설치 했을 때 관리되는 결제(1한명의 유저가 1번 결제되어 추가로 구매가 되지 않음. managed purchase)가 있는지 검색하는데 사용된다.

* in-app Billing Response


- 요청에 대한 응답은 다음과 같다. 요청의 응답은 동기와 비동기가 있다.



* The synchronous response (동기 응답)


- RESPONSE_CODE : 이 키는 요청에 대한 상태 정보나 응답 정보를 알려 줍니다.

- PURCHASE_INTENT : 이 키는 결제 액티비티의 실행의 PendingIntent를 제공합니다.

- REQUEST_ID : 이 키는 요청 아이디를 제공합니다. 이 아이디를 통해 비동기 응답을 식별할 수 있습니다.

* The asynchronous response(비동기 응답)

: broadcast intents 로서 결제에 대한 정보를 응용 애플리케이션에게 전달한다. 응용 애플리케이션은 a BroadcastReceiver로 해당 정보를 가져와서 사용할 수 있다.


- com.android.vending.billing.RESPONSE_CODE : 이 응답은 구매 요청후 받을 수 있으며, 구글 플레이 서버의 응답 코드를 포함한다. 응답 코드는 구매가 성공적으로 이루어 졌는지, 에러가 발생했는지 표시할 수 있다.

  &Extras

    1. request_id  :  요청을 식별하는 아이디
    2. response_code : 구글 플레이 서버 응답 코드


  &Response Code




- com.android.vending.billing.IN_APP_NOTIFY : 이 응답은 구매 성공, 구매 취소, 환불 같은 결제 상태의 변화를 표시한다. 이때 알림 아이디 (notification ID)를 포함하고 있는데, 이는 각각의 구글 플레이 서버의 메시지와 대응합니다.

 & Extra

  1. notification_id : 결제 세부 상태를 알기 위한 알림 아이디. 이 아이디를 가지고 GET_PURCHASE_INFORMATION 요청을 보내서 관련 정보(트랜잭션 정보)를 알 수 있다.



- com.android.vending.billing.PURCHASE_STATE_CHANGED : 이 응답은 하나 또는 그 이상의 트랜섹션에 대한 세부 정보를 포함한다. 트랜섹션 데이터는 JSON string형태로 포함되어 있다. 이 메시지는 인앱 결제 보안을 보장하기 위해 서명을 포함하고 있다.

 &Extra

   1. inapp_signed_data : JSON 데이터를 나타낸다.
   2. inapp_signature : 문자열 서명을 나타낸다.

 &An example of this JSON string