android中的应用内购买实现
我正在教程中的示例应用程序中执行应用内购买功能。 我也从以下链接中了解到了这一点 我已从发布者帐户设置了自己的公钥 当我开始应用它时,它工作得很好,并且成功地连接到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
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(请求);
//请求