android中的应用内购买实现

android中的应用内购买实现,android,in-app-purchase,in-app-billing,Android,In App Purchase,In App Billing,我正在教程中的示例应用程序中执行应用内购买功能。 我也从以下链接中了解到了这一点 我已从发布者帐户设置了自己的公钥 当我开始应用它时,它工作得很好,并且成功地连接到Android市场。 但我按了“接受并购买”选项。我得到以下错误 12-29 12:50:27.694: ERROR/BillingService(3741): Signature verification failed. 12-29 12:50:27.698: WARN/BillingService(3741): signatu

我正在教程中的示例应用程序中执行应用内购买功能。 我也从以下链接中了解到了这一点

我已从发布者帐户设置了自己的公钥

当我开始应用它时,它工作得很好,并且成功地连接到Android市场。 但我按了“接受并购买”选项。我得到以下错误

12-29 12:50:27.694: ERROR/BillingService(3741): Signature verification failed.
12-29 12:50:27.698: WARN/BillingService(3741): signature does not match data.
12-29 12:50:27.706: DEBUG/AndroidRuntime(3741): Shutting down VM
12-29 12:50:27.706: WARN/dalvikvm(3741): threadid=1: thread exiting with uncaught exception (group=0x4001d7d0)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741): FATAL EXCEPTION: main
12-29 12:50:27.722: ERROR/AndroidRuntime(3741): java.lang.RuntimeException: Unable to start receiver com.blundell.test.BillingReceiver: java.lang.NullPointerException
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at android.app.ActivityThread.handleReceiver(ActivityThread.java:2821)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at android.app.ActivityThread.access$3200(ActivityThread.java:125)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2083)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at android.os.Handler.dispatchMessage(Handler.java:99)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at android.os.Looper.loop(Looper.java:123)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at android.app.ActivityThread.main(ActivityThread.java:4627)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at java.lang.reflect.Method.invokeNative(Native Method)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at java.lang.reflect.Method.invoke(Method.java:521)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:858)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at dalvik.system.NativeStart.main(Native Method)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741): Caused by: java.lang.NullPointerException
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at com.blundell.test.BillingHelper.verifyPurchase(BillingHelper.java:249)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at com.blundell.test.BillingReceiver.purchaseStateChanged(BillingReceiver.java:46)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at com.blundell.test.BillingReceiver.onReceive(BillingReceiver.java:29)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     at android.app.ActivityThread.handleReceiver(ActivityThread.java:2810)
12-29 12:50:27.722: ERROR/AndroidRuntime(3741):     ... 10 more
下面是相同的代码:

import java.util.ArrayList;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import com.android.vending.billing.IMarketBillingService;
import com.blundell.test.BillingSecurity.VerifiedPurchase;
import com.blundell.test.C.ResponseCode;


public class BillingHelper {



    private static final String TAG = "BillingService";



    private static IMarketBillingService mService;

    private static Context mContext;

    private static Handler mCompletedHandler;



    protected static VerifiedPurchase latestPurchase;



    protected static void instantiateHelper(Context context, IMarketBillingService service) {

        mService = service;

        mContext = context;

    }



    protected static void setCompletedHandler(Handler handler){

        mCompletedHandler = handler;

    }



    protected static boolean isBillingSupported() {

        if (amIDead()) {

            return false;

        }

        Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");

        if (mService != null) {

            try {

                Bundle response = mService.sendBillingRequest(request);

                ResponseCode code = ResponseCode.valueOf((Integer) response.get("RESPONSE_CODE"));

                Log.i(TAG, "isBillingSupported response was: " + code.toString());

                if (ResponseCode.RESULT_OK.equals(code)) {

                    return true;

                } else {

                    return false;

                }

            } catch (RemoteException e) {

                Log.e(TAG, "isBillingSupported response was: RemoteException", e);

                return false;

            }

        } else {

            Log.i(TAG, "isBillingSupported response was: BillingService.mService = null");

            return false;

        }

    }



    /**

     * A REQUEST_PURCHASE request also triggers two asynchronous responses (broadcast intents). 

     * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides error information about the request. (which I ignore)

     * Next, if the request was successful, the Android Market application sends an IN_APP_NOTIFY broadcast intent. 

     * This message contains a notification ID, which you can use to retrieve the transaction details for the REQUEST_PURCHASE

     * @param activityContext

     * @param itemId

     */

    protected static void requestPurchase(Context activityContext, String itemId){

        if (amIDead()) {

            return;

        }

        Log.i(TAG, "requestPurchase()");

        Bundle request = makeRequestBundle("REQUEST_PURCHASE");

        request.putString("ITEM_ID", itemId);

        try {

            Bundle response = mService.sendBillingRequest(request);



            //The RESPONSE_CODE key provides you with the status of the request

            Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");

            //The PURCHASE_INTENT key provides you with a PendingIntent, which you can use to launch the checkout UI

            PendingIntent pendingIntent = (PendingIntent) response.get("PURCHASE_INTENT");

            //The REQUEST_ID key provides you with a unique request identifier for the request

            Long requestIndentifier     = (Long) response.get("REQUEST_ID");

            Log.i(TAG, "current request is:" + requestIndentifier);

            C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);

            Log.i(TAG, "REQUEST_PURCHASE Sync Response code: "+responseCode.toString());



            startBuyPageActivity(pendingIntent, new Intent(), activityContext);

        } catch (RemoteException e) {

            Log.e(TAG, "Failed, internet error maybe", e);

            Log.e(TAG, "Billing supported: "+isBillingSupported());

        }

    }



    /**

     * A GET_PURCHASE_INFORMATION request also triggers two asynchronous responses (broadcast intents). 

     * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides status and error information about the request.  (which I ignore)

     * Next, if the request was successful, the Android Market application sends a PURCHASE_STATE_CHANGED broadcast intent. 

     * This message contains detailed transaction information. 

     * The transaction information is contained in a signed JSON string (unencrypted). 

     * The message includes the signature so you can verify the integrity of the signed string

     * @param notifyIds

     */

    protected static void getPurchaseInformation(String[] notifyIds){

        if (amIDead()) {

            return;

        }

        Log.i(TAG, "getPurchaseInformation()");

        Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");

        // The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate.

        // The Android Market application returns this nonce with the PURCHASE_STATE_CHANGED broadcast intent so you can verify the integrity of the transaction information.

        request.putLong("NONCE", BillingSecurity.generateNonce());

        // The NOTIFY_IDS key contains an array of notification IDs, which you received in the IN_APP_NOTIFY broadcast intent.

        request.putStringArray("NOTIFY_IDS", notifyIds);

        try {

            Bundle response = mService.sendBillingRequest(request);



            //The REQUEST_ID key provides you with a unique request identifier for the request

            Long requestIndentifier     = (Long) response.get("REQUEST_ID");

            Log.i(TAG, "current request is:" + requestIndentifier);

            //The RESPONSE_CODE key provides you with the status of the request

            Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");

            C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);

            Log.i(TAG, "GET_PURCHASE_INFORMATION Sync Response code: "+responseCode.toString());



        } catch (RemoteException e) {

            Log.e(TAG, "Failed, internet error maybe", e);

            Log.e(TAG, "Billing supported: "+isBillingSupported());

        }

    }



    /**

     * To acknowledge that you received transaction information you send a

     * CONFIRM_NOTIFICATIONS request.

     * 

     * A CONFIRM_NOTIFICATIONS request triggers a single asynchronous response�a RESPONSE_CODE broadcast intent. 

     * This broadcast intent provides status and error information about the request.

     * 

     * Note: As a best practice, you should not send a CONFIRM_NOTIFICATIONS request for a purchased item until you have delivered the item to the user. 

     * This way, if your application crashes or something else prevents your application from delivering the product,

     * your application will still receive an IN_APP_NOTIFY broadcast intent from Android Market indicating that you need to deliver the product

     * @param notifyIds

     */

    protected static void confirmTransaction(String[] notifyIds) {

        if (amIDead()) {

            return;

        }

        Log.i(TAG, "confirmTransaction()");

        Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");

        request.putStringArray("NOTIFY_IDS", notifyIds);

        try {

            Bundle response = mService.sendBillingRequest(request);



            //The REQUEST_ID key provides you with a unique request identifier for the request

            Long requestIndentifier     = (Long) response.get("REQUEST_ID");

            Log.i(TAG, "current request is:" + requestIndentifier);



            //The RESPONSE_CODE key provides you with the status of the request

            Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");

            C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);



            Log.i(TAG, "CONFIRM_NOTIFICATIONS Sync Response code: "+responseCode.toString());

        } catch (RemoteException e) {

            Log.e(TAG, "Failed, internet error maybe", e);

            Log.e(TAG, "Billing supported: " + isBillingSupported());

        }

    }



    /**

     * 

     * Can be used for when a user has reinstalled the app to give back prior purchases. 

     * if an item for sale's purchase type is "managed per user account" this means google will have a record ofthis transaction

     * 

     * A RESTORE_TRANSACTIONS request also triggers two asynchronous responses (broadcast intents). 

     * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides status and error information about the request. 

     * Next, if the request was successful, the Android Market application sends a PURCHASE_STATE_CHANGED broadcast intent. 

     * This message contains the detailed transaction information. The transaction information is contained in a signed JSON string (unencrypted).

     * The message includes the signature so you can verify the integrity of the signed string

     * @param nonce

     */

    protected static void restoreTransactionInformation(Long nonce) {

        if (amIDead()) {

            return;

        }

        Log.i(TAG, "confirmTransaction()");

        Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");

        // The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate

        request.putLong("NONCE", nonce);

        try {

            Bundle response = mService.sendBillingRequest(request);



            //The REQUEST_ID key provides you with a unique request identifier for the request

            Long requestIndentifier     = (Long) response.get("REQUEST_ID");

            Log.i(TAG, "current request is:" + requestIndentifier);



            //The RESPONSE_CODE key provides you with the status of the request

            Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");

            C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);

            Log.i(TAG, "RESTORE_TRANSACTIONS Sync Response code: "+responseCode.toString());

        } catch (RemoteException e) {

            Log.e(TAG, "Failed, internet error maybe", e);

            Log.e(TAG, "Billing supported: " + isBillingSupported());

        }

    }



    private static boolean amIDead() {

        if (mService == null || mContext == null) {

            Log.e(TAG, "BillingHelper not fully instantiated");

            return true;

        } else {

            return false;

        }

    }



    private static Bundle makeRequestBundle(String method) {

        Bundle request = new Bundle();

        request.putString("BILLING_REQUEST", method);

        request.putInt("API_VERSION", 1);

        request.putString("PACKAGE_NAME", mContext.getPackageName());

        return request;

    }



    /**

     * 

     * 

     * You must launch the pending intent from an activity context and not an application context

     * You cannot use the singleTop launch mode to launch the pending intent

     * @param pendingIntent

     * @param intent

     * @param context

     */

    private static void startBuyPageActivity(PendingIntent pendingIntent, Intent intent, Context context){

        //TODO add above 2.0 implementation with reflection, for now just using 1.6 implem



        // 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(context, 0, intent);         

        } catch (CanceledException e){

            Log.e(TAG, "startBuyPageActivity CanceledException");

        }

    }



    protected static void verifyPurchase(String signedData, String signature) {

        **ArrayList<VerifiedPurchase> purchases = BillingSecurity.verifyPurchase(signedData, signature);
        latestPurchase = purchases.get(0);**



        confirmTransaction(new String[]{latestPurchase.notificationId});



        if(mCompletedHandler != null){

            mCompletedHandler.sendEmptyMessage(0);

        } else {

            Log.e(TAG, "verifyPurchase error. Handler not instantiated. Have you called setCompletedHandler()?");

        }

    }



    public static void stopService(){

        mContext.stopService(new Intent(mContext, BillingService.class));

        mService = null;

        mContext = null;

        mCompletedHandler = null;

        Log.i(TAG, "Stopping Service");

    }

}
import java.util.ArrayList;
导入android.app.pendingent;
导入android.app.pendingent.CanceledException;
导入android.content.Context;
导入android.content.Intent;
导入android.os.Bundle;
导入android.os.Handler;
导入android.os.RemoteException;
导入android.util.Log;
导入com.android.vending.billing.IMarketBillingService;
导入com.blundell.test.BillingSecurity.VerifiedPurchase;
导入com.blundell.test.C.ResponseCode;
公共级计费帮助器{
私有静态最终字符串标记=“BillingService”;
私有静态IMarketBillingService mService;
私有静态上下文mContext;
私有静态处理程序mccompletedhandler;
受保护的静态验证采购后期采购;
受保护的静态void实例化帮助程序(上下文上下文,IMarketBillingService服务){
mService=服务;
mContext=上下文;
}
受保护的静态void setCompletedHandler(处理程序处理程序){
mccompletedhandler=处理程序;
}
受保护的静态布尔值isBillingSupported(){
if(amIDead()){
返回false;
}
Bundle request=makeRequestBundle(“检查账单是否受支持”);
if(mService!=null){
试一试{
Bundle response=mService.sendBillingRequest(请求);
ResponseCode code=ResponseCode.valueOf((整数)response.get(“response_code”));
Log.i(标记“isBillingSupported response was:”+code.toString());
如果(响应代码结果_确定等于(代码)){
返回true;
}否则{
返回false;
}
}捕获(远程异常){
e(标签“isBillingSupported response was:RemoteException”,e);
返回false;
}
}否则{
Log.i(标记,“isBillingSupported响应为:BillingService.mService=null”);
返回false;
}
}
/**
*请求\u购买请求还触发两个异步响应(广播意图)。
*首先,Android Market应用程序发送一个响应代码广播意图,它提供关于请求的错误信息(我忽略了)
*接下来,如果请求成功,Android Market应用程序将发送IN_APP_NOTIFY广播意图。
*此消息包含一个通知ID,可用于检索购买请求的交易详细信息
*@param activityContext
*@param itemId
*/
受保护的静态void requestPurchase(上下文activityContext,字符串itemId){
if(amIDead()){
返回;
}
Log.i(标记“requestPurchase()”);
Bundle request=makeRequestBundle(“请求购买”);
request.putString(“ITEM_ID”,itemId);
试一试{
Bundle response=mService.sendBillingRequest(请求);
//响应代码键为您提供请求的状态
整数responseCodeIndex=(整数)response.get(“response_CODE”);
//PURCHASE_INTENT键为您提供了一个PendingEvent,您可以使用它启动签出UI
PendingIntent PendingIntent=(PendingIntent)response.get(“购买意图”);
//请求ID键为请求提供唯一的请求标识符
Long RequestIdentifier=(Long)response.get(“请求标识”);
Log.i(标记“当前请求为:”+RequestIdentifier);
C.ResponseCode ResponseCode=C.ResponseCode.valueOf(responseCodeIndex);
Log.i(标记“请求与购买同步响应代码:”+responseCode.toString());
startBuyPageActivity(PendingContent、new Intent()、activityContext);
}捕获(远程异常){
Log.e(标记“失败,可能是internet错误”,e);
Log.e(标记“支持计费:”+isBillingSupported());
}
}
/**
*获取购买信息请求也会触发两个异步响应(广播意图)。
*首先,Android Market应用程序发送一个响应代码广播意图,它提供关于请求的状态和错误信息(我忽略)
*接下来,如果请求成功,Android Market应用程序将发送购买状态更改的广播意图。
*此消息包含详细的事务信息。
*事务信息包含在签名的JSON字符串(未加密)中。
*消息包含签名,因此您可以验证签名字符串的完整性
*@param notifyIds
*/
受保护的静态void getPurchaseInformation(字符串[]notifyIds){
if(amIDead()){
返回;
}
Log.i(标记“getPurchaseInformation()”);
Bundle request=makeRequestBundle(“获取采购信息”);
//请求\u NONCE密钥包含必须生成的加密安全NONCE(使用一次的数字)。
//Android Market应用程序返回此nonce,其中包含购买状态更改的广播意图,以便您可以验证交易信息的完整性。
request.putLong(“NONCE”,BillingSecurity.generateOnce());
//NOTIFY_id键包含一个通知id数组,您在in_APP_NOTIFY广播意图中接收到该数组。
putStringArray(“NOTIFY_id”,notifyid);
试一试{
Bundle response=mService.sendBillingRequest(请求);
//请求