在Android中的App Purchase实现中

时间:2011-12-29 08:44:37

标签: android in-app-purchase in-app-billing

我在this教程的示例应用程序中执行In App Purchase功能。 我也从以下链接中了解了它

http://developer.android.com/guide/market/billing/billing_overview.html

http://developer.android.com/guide/market/billing/billing_integrate.html

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

当我开始使用App时,它运行正常并且已成功连接到Android Market。 但我按下了接受&买期权。我收到以下错误。

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");

    }

}

我在哪里做错了?请分享你的想法??

由于

1 个答案:

答案 0 :(得分:1)

例外情况似乎是在签名验证中引起的。

您是否已按照测试Android应用内结算的所有步骤进行操作?

  • 在发布模式下构建您的应用并签名。

  • 将发布版本上传到Android电子市场,而不是发布,并创建产品列表。

  • 将应用程序安装到您的设备上,并在您的设备上设置主要测试帐户。