Google 谷歌play商店店里面有哪些好玩的单机游戏

Google play billing(Google play 内支付) - Sun‘刺眼的博客 - 博客园
随笔 - 732, 文章 - 0, 评论 - 36, 引用 - 0
http://www.bubuko.com/infodetail-930440.html
如billing开发文档所说,要在你的应用中实现In-app&Billing只需要完成以下几步就可以了。&&
第一,把你上篇下载的AIDL文件添加到你的工程里,第二,把&&
&uses-permission android:name="com.android.vending.BILLING" /&
这个权限加到你工程的AndroidManifest.xml文件中,第三,创建一个ServiceConnection,并把它绑定到IInAppBillingService中。完成上面三条后就可以使用支付了。当然这只是一个简单的介绍。其实Google的这个支付,大部分都是你手机上的Google Play来进行处理的,你只需要处理购买请求,处理购买结果就行了。文档写的很好,先把这个文档看完,就知道支付流程了。
1.内购商品相关
针对我的项目而言,我们在Google后台设置的是受管理可消耗的商品("managed per user account"),具体的就是游戏里的水晶,玩家可以多次购买。但是Google后台的这种可重复购买商品(还有一种是只能购买一次的商品"subscription")有个要求,就是你购买成功后需要主动向Google Play请求消耗这个商品,等消耗成功后你才可以再次下单购买。因此,在游戏里的支付会多出一个操作步骤就是请求消耗购买成功的商品。
2.检测设备是否支持Google Play Service
在正式开启支付前,Google billing会检查你的手机是否支持Google billing,这个下面会讲到。为了更好的用户体验,建议在Google billing检测之前,可以先检测一下用户的设备是否支持Google Play Service,其中基本要求就是安装了Google Play应用商店和Google Play Service。如果用户的设备不具备这两个,就可以弹出提示引导用户去安装。这里有两种方式可以用,一种是通过Google Play Service来进行检测,就是上篇下载的那个Service扩展包,一种是自己写代码,遍历设备上安装的应用程序,检查是否有安装Google Play。先说第一种。
(1)Google Play Service
上篇下载的Service包里会有一个库工程
把这个库工程导入你的eclipse,引用到你的工程里就可以用了,具体操作可以参加docs下的文档,so easy!导入成功后,调用其中的一个方法就可以了。
* Check the device to make sure it has the Google Play Services APK.If
* it doesn‘t, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device‘s system settings
private boolean checkPlayServices()
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if(resultCode != ConnectionResult.SUCCESS)
if(GooglePlayServicesUtil.isUserRecoverableError(resultCode))
GooglePlayServicesUtil.getErrorDialog(resultCode, this,
PLAY_SERVICES_RESOLUTION_REQUEST).show();
Log.i(TAG, "This device is not supported");
如果当前设备的Google Service不可用,就会弹出提示,引导用户去设置安装。如果此设备不支持的话,就也不需要检测Google billing是否可用了。多说一句,Google Play Service可以做很多事的,如果觉得只用上面的功能太简单的话,就可以考虑把应用自动更新也加上,当你在Google Play上传了新版程序后,Google Play会帮你提示用户更新程序。还有一个比较好玩的就是如果引入了这个库工程后,就可以加GCM了(Google Cloud Messaging),就是消息推送推送功能,当然这个比较麻烦,有兴趣的可以去加加看。
(2)遍历包名
Google Play的程序包名是"com..vending",运行在设备上的Google Play Service的包名是"com.google.android.gms",可以在程序启动的时候遍历下设备上的包名,如果没有这两个东西就引导用户去安装。
遍历包名方法
//Check Google Play
protected boolean isHaveGooglePlay(Context context, String packageName)
//Get PackageManager
final PackageManager packageManager = context.getPackageManager();
//Get The All Install App Package Name
List&PackageInfo& pInfo = packageManager.getInstalledPackages(0);
//Create Name List
List&String& pName = new ArrayList&String&();
//Add Package Name into Name List
if(pInfo != null){
for(int i=0; i&pInfo.size(); i++){
String pn = pInfo.get(i).packageN
pName.add(pn);
//Log.v("Package Name", "PackAgeName: = " + pn);
return pName.contains(packageName);
提示安装方法
Uri uri = Uri.parse("market://details?id=" + "要安装程序的包名");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
&上面这个方法会打开你手机上的应用商店,定位到要安装的程序。
不过还是推荐用Google Play Service来检测,貌似第二种,即使有的用户装了Google Play(像国内用户),也不支持Google Play Service的。3.添加代码(终于要加支付代码了)
把上篇下载的samples里util的代码全部拷到你的工程里,可以新建一个包,放到里面。
这个说明一下,其实这个例子的代码还是不错的,本着天下代码一大抄和拿来主义,就直接拿来用吧!当然如果你觉得这个代码写的不好,或者不适用你的工程,你就可以依据文档自己写适用的代码。当然文档里说过,为了防止别人破解你的游戏,最好把里面的变量和方法都改下名字,毕竟这里的代码任何人都看得到。我的做法是照搬过来了,只是把IabHelper.改造了下,因为这个是整个支付的关键,其他都是辅助的,可以不管。
把这里的代码拷完,把该import的都import了,你就可以照samples中的代码开写自己的支付了。针对单机游戏,就需要考虑这个代码改造和本地的验证,加密了。针对网络游戏就要简单了。因为我其实对java不太熟悉,所以单机的加密,base验证,混淆什么的就不做介绍了。下面主要说网络游戏。
(1)IabHelper.java
这个是支付的关键代码,其中已经把设置billing,商品查询,商品购买,商品回调,商品验证以及回调方法都写好了,你直接参照samples用就可以了。
01.设置billing
就是开篇所说的绑定ServiceConnection到IInAppBillingService。功能很完善,包括成功和失败都有回调,还有各种异常。在你程序的启动Activity里检测完设备是否Google Play Service后,就可以new一个IabHelper,来调用这个方法,根据不同的回调里做相应的处理。
* Starts the setup process. This will start up the setup process asynchronously.
* You will be notified through the listener when the setup process is complete.
* This method is safe to call from a UI thread.
* @param listener The listener to notify when the setup process is complete.
public void startSetup(final OnIabSetupFinishedListener listener) {
// If already set up, can‘t do it again.
checkNotDisposed();
if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");
// Connection to IAB service
logDebug("Starting in-app billing setup.");
mServiceConn = new ServiceConnection() {
public void onServiceDisconnected(ComponentName name) {
logDebug("Billing service disconnected.");
mService =
public void onServiceConnected(ComponentName name, IBinder service) {
if (mDisposed)
logDebug("Billing service connected.");
mService = IInAppBillingService.Stub.asInterface(service);
String packageName = mContext.getPackageName();
logDebug("Checking for in-app billing 3 support.");
// check for in-app billing v3 support
int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
if (response != BILLING_RESPONSE_RESULT_OK) {
if (listener != null) listener.onIabSetupFinished(new IabResult(response,
"Error checking for billing v3 support."));
// if in-app purchases aren‘t supported, neither are subscriptions.
mSubscriptionsSupported =
logDebug("In-app billing version 3 supported for " + packageName);
// check for v3 subscriptions support
response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Subscriptions AVAILABLE.");
mSubscriptionsSupported =
logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
mSetupDone =
catch (RemoteException e) {
if (listener != null) {
listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
"RemoteException while setting up in-app billing."));
e.printStackTrace();
if (listener != null) {
listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
// service available to handle that Intent
mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
// no service available to handle that Intent
if (listener != null) {
listener.onIabSetupFinished(
new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
"Billing service unavailable on device."));
samples中的代码&&
// Create the helper, passing it our context and the public key to verify signatures with
Log.d(TAG, "Creating IAB helper.");
mHelper = new IabHelper(this, base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true);
// Start setup. This is asynchronous and the specified listener
// will be called once setup completes.
Log.d(TAG, "Starting setup.");
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
if (!result.isSuccess()) {
// Oh noes, there was a problem.
complain("Problem setting up in-app billing: " + result);
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null)
// IAB is fully set up. Now, let‘s get an inventory of stuff we own.
Log.d(TAG, "Setup successful. Querying inventory.");
mHelper.queryInventoryAsync(mGotInventoryListener);
02.查询商品
在setup方法的最后有一个
mHelper.queryInventoryAsync(mGotInventoryListener);
是用来查询你目前拥有的商品的。其中的回调的代码如下
// Listener that‘s called when we finish querying the items and subscriptions we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished.");
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null)
// Is it a failure?
if (result.isFailure()) {
complain("Failed to query inventory: " + result);
Log.d(TAG, "Query inventory was successful.");
* Check for items we own. Notice that for each purchase, we check
* the developer payload to see if it‘s correct! See
* verifyDeveloperPayload().
// Do we have the premium upgrade?
Purchase premiumPurchase = inventory.getPurchase(SKU_PREMIUM);
mIsPremium = (premiumPurchase != null && verifyDeveloperPayload(premiumPurchase));
Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
// Do we have the infinite gas plan?
Purchase infiniteGasPurchase = inventory.getPurchase(SKU_INFINITE_GAS);
mSubscribedToInfiniteGas = (infiniteGasPurchase != null &&
verifyDeveloperPayload(infiniteGasPurchase));
Log.d(TAG, "User " + (mSubscribedToInfiniteGas ? "HAS" : "DOES NOT HAVE")
+ " infinite gas subscription.");
if (mSubscribedToInfiniteGas) mTank = TANK_MAX;
// Check for gas delivery -- if we own gas, we should fill up the tank immediately
Purchase gasPurchase = inventory.getPurchase(SKU_GAS);
if (gasPurchase != null && verifyDeveloperPayload(gasPurchase)) {
Log.d(TAG, "We have gas. Consuming it.");
mHelper.consumeAsync(inventory.getPurchase(SKU_GAS), mConsumeFinishedListener);
updateUi();
setWaitScreen(false);
Log.d(TAG, "Initial inve enabling main UI.");
因为目前我们的内购商品是可重复购买的,所以在成功查询到我们已经购买的商品后进行了消耗商品操作。消耗的代码在这里
// Check for gas delivery -- if we own gas, we should fill up the tank immediately
Purchase gasPurchase = inventory.getPurchase(SKU_GAS);
if (gasPurchase != null && verifyDeveloperPayload(gasPurchase)) {
Log.d(TAG, "We have gas. Consuming it.");
mHelper.consumeAsync(inventory.getPurchase(SKU_GAS), mConsumeFinishedListener);
在讲消耗前,先解释下以上这么操作的原因。在内购商品那里讲过,如果是设置的是可重复商品,当你在成功购买这个商品后是需要主动消耗的,只有消耗成功后才可以再次购买。可能有些人觉得这种设定不好,我的商品本来就是可重复购买的,为什么还要在买成功后通知Google Play消耗掉商品呢(可能本身商品没用消耗掉,这只是一种叫法)?我个人觉得这样设定,第一,可以避免用户重复下单购买,第二,可以保证每笔消费订单的唯一。有了以上两点就可以很好地处理漏单。 so,上面代码在成功设置billing后,第一个操作就是查询拥有的商品,就是做的漏单处理。因为支付过程其实就是你的应用程序-----&Google Play程序(通过Google Play Service)------&Google服务器-------&Google Play程序(通过Google Play Service)------&你的应用程序。这样一个交互过程,还需要网络支持,所以每次支付操作不会保证百分百成功,这样就会产生漏单现象,就是用户付费成功了,但是Google Play在通知你的应用程序支付结果时,因为某些原因断掉了,这样你的程序就不知道支付是否操作成功了,so,只好在下次进游戏时查查有没有已经购买的,但是还没有消耗的商品,有的话就消耗掉,然后再把商品发送给用户。因为这个商品在消耗之前,用户是无法再次购买的,所以单个用户就只会对应单一的漏单,不会有重复的漏单。这些信息都是存到Google服务器上的,直接调代码里的查询代码就可以了。
02.消耗商品
消耗商品会在两个地方出现。一,查询商品中所说的漏单中,二,就是你每次购买成功后的消耗。消耗商品也有一个回调,如下
// Called when consumption is complete
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase, IabResult result) {
Log.d(TAG, "Consumption finished. Purchase: " + purchase + ", result: " + result);
// if we were disposed of in the meantime, quit.
if (mHelper == null)
// We know this is the "gas" sku because it‘s the only one we consume,
// so we don‘t check which sku was consumed. If you have more than one
// sku, you probably should check...
if (result.isSuccess()) {
// successfully consumed, so we apply the effects of the item in our
// game world‘s logic, which in our case means filling the gas tank a bit
Log.d(TAG, "Consumption successful. Provisioning.");
mTank = mTank == TANK_MAX ? TANK_MAX : mTank + 1;
saveData();
alert("You filled 1/4 tank. Your tank is now " + String.valueOf(mTank) + "/4 full!");
complain("Error while consuming: " + result);
updateUi();
setWaitScreen(false);
Log.d(TAG, "End consumption flow.");
代码比较简单,针对自己的游戏逻辑,在里面稍做改动即可。
03.购买商品
按重要程度,购买商品应该排在第一位的,只是按支付流程走的话,购买商品却不是第一位,这里就根据支付流程来走吧。
* Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
* which will involve bringing up the Google Play screen. The calling activity will be paused while
* the user interacts with Google Play, and the result will be delivered via the activity‘s
* {@link android.app.Activity#onActivityResult} method, at which point you must call
* this object‘s {@link #handleActivityResult} method to continue the purchase flow. This method
* MUST be called from the UI thread of the Activity.
* @param act The calling activity.
* @param sku The sku of the item to purchase.
* @param itemType indicates if it‘s a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)
* @param requestCode A request code (to differentiate from other responses --
as in {@link android.app.Activity#startActivityForResult}).
* @param listener The listener to notify when the purchase process finishes
* @param extraData Extra data (developer payload), which will be returned with the purchase data
when the purchase completes. This extra data will be permanently bound to that purchase
and will always be returned when the purchase is queried.
public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode,
OnIabPurchaseFinishedListener listener, String extraData) {
checkNotDisposed();
checkSetupDone("launchPurchaseFlow");
flagStartAsync("launchPurchaseFlow");
if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
"Subscriptions are not available.");
flagEndAsync();
if (listener != null) listener.onIabPurchaseFinished(r, null);
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response));
flagEndAsync();
result = new IabResult(response, "Unable to buy item");
if (listener != null) listener.onIabPurchaseFinished(result, null);
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
mRequestCode = requestC
mPurchaseListener =
mPurchasingItemType = itemT
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(),
Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
catch (SendIntentException e) {
logError("SendIntentException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
if (listener != null) listener.onIabPurchaseFinished(result, null);
catch (RemoteException e) {
logError("RemoteException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
if (listener != null) listener.onIabPurchaseFinished(result, null);
以上是IabHelper中的支付购买代码,其中包括了重复购买商品类型和一次购买商品类型的处理。主要的代码是try里面的这一块
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response));
flagEndAsync();
result = new IabResult(response, "Unable to buy item");
if (listener != null) listener.onIabPurchaseFinished(result, null);
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
mRequestCode = requestC
mPurchaseListener =
mPurchasingItemType = itemT
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(),
Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
第三个参数,商品Id,就是你在Google开发者后台上设置的内购商品的名字。每个商品的名字要唯一。推荐用商品名字加下划线加价格的组合,比如"crystal_0.99",这样你一看名字就知道这个商品的价格是0.99美金,商品是水晶。
第三个参数,订单号。如果本地有支付服务器的话,这个订单号可以由支付服务器生成,然后再传给客户端,这样的话本地服务器也可以记录下订单信息,方便以后的查询和操作。订单号的格式推荐用时间戳加商品名字和价格,这样也可以容易看出订单信息。这个订单号会传给Google,购买成功后Google会原样传给你,所以也可以在其中加个标示信息,可以做下比对。
二,在getBuyIntent成功后,返回的Bundle中会有个BILLING_RESPONSE_RESULT_OK返回码,这就代表成功了。然后再用这个Bundle得到一个PendingIntent.如上面代码演示。
三,进行支付
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(),
Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
&这个方法是Activity中的一个方法,调用这个方法后,回有一个回调来接收结果。除了第一个PengdingIntent参数外,其他的可以按参数类型随便写。
四,支付完成
* Handles an activity result that‘s part of the purchase flow in in-app billing. If you
* are calling {@link #launchPurchaseFlow}, then you must call this method from your
* Activity‘s {@link android.app.Activity@onActivityResult} method. This method
* MUST be called from the UI thread of the Activity.
* @param requestCode The requestCode as you received it.
* @param resultCode The resultCode as you received it.
* @param data The data (Intent) as you received it.
* @return Returns true if the result was related to a purchase
false if the result was not related to a purchase, in which case you should
handle it normally.
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode != mRequestCode)
checkNotDisposed();
checkSetupDone("handleActivityResult");
// end of async purchase operation that started on launchPurchaseFlow
flagEndAsync();
if (data == null) {
logError("Null data in IAB activity result.");
result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
int responseCode = getResponseCodeFromIntent(data);
String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {
logDebug("Successful resultcode from purchase activity.");
logDebug("Purchase data: " + purchaseData);
logDebug("Data signature: " + dataSignature);
logDebug("Extras: " + data.getExtras());
logDebug("Expected item type: " + mPurchasingItemType);
if (purchaseData == null || dataSignature == null) {
logError("BUG: either purchaseData or dataSignature is null.");
logDebug("Extras: " + data.getExtras().toString());
result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
Purchase purchase =
purchase = new Purchase(mPurchasingItemType, purchaseData, dataSignature);
String sku = purchase.getSku();
// Verify signature
if (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {
logError("Purchase signature verification FAILED for sku " + sku);
result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, purchase);
logDebug("Purchase signature successfully verified.");
catch (JSONException e) {
logError("Failed to parse purchase data.");
e.printStackTrace();
result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
if (mPurchaseListener != null) {
mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
else if (resultCode == Activity.RESULT_OK) {
// result code was OK, but in-app billing response was not OK.
logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));
if (mPurchaseListener != null) {
result = new IabResult(responseCode, "Problem purchashing item.");
mPurchaseListener.onIabPurchaseFinished(result, null);
else if (resultCode == Activity.RESULT_CANCELED) {
logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
logError("Purchase failed. Result code: " + Integer.toString(resultCode)
+ ". Response: " + getResponseDesc(responseCode));
result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
public Inventory queryInventory(boolean querySkuDetails, List&String& moreSkus) throws IabException {
return queryInventory(querySkuDetails, moreSkus, null);
支付结果返回后会调用上面这个方法,对于支付失败和其中的错误,代码写的很清楚,可以自行处理。现在来关注支付成功后的结果验证。在上面方法中会从支付结果的数据中取得两个json数据。
int responseCode = getResponseCodeFromIntent(data);
String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
就是purchaseData和dataSignature。验证支付就是需要这两个参数和publicKey,例子里的验证方法是写在Security.java里的。里面写了三个方法来完成支付结果的验证。
对于有本地支付服务器的游戏来说,这个操作就可以放到服务端了,客户端只需要把purchaseData和dataSignature传给支付服务器即可。然后有支付服务器把验证结果传给客户端,再做成功和失败的处理。成功后则要进行消耗商品的操作。对于没有支付服务器的游戏来说,我个人觉得本地的操作要达到安全,还是比较难的。不过对于服务器验证支付结果,也是存在风险的,只是风险要小。
* Verifies that the data was signed with the given signature, and returns
* the verified purchase. The data is in JSON format and signed
* with a private key. The data also contains the {@link PurchaseState}
* and product ID of the purchase.
* @param base64PublicKey the base64-encoded public key to use for verifying.
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
Log.e(TAG, "Purchase verification failed: missing data.");
PublicKey key = Security.generatePublicKey(base64PublicKey);
return Security.verify(key, signedData, signature);
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
* @param encodedPublicKey Base64-encoded public key
* @throws IllegalArgumentException if encodedPublicKey is invalid
public static PublicKey generatePublicKey(String encodedPublicKey) {
byte[] decodedKey = Base64.decode(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
Log.e(TAG, "Invalid key specification.");
throw new IllegalArgumentException(e);
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
throw new IllegalArgumentException(e);
* Verifies that the signature from the server matches the computed
* signature on the data.
Returns true if the data is correctly signed.
* @param publicKey public key associated with the developer account
* @param signedData signed data from server
* @param signature server signature
* @return true if the data and signature match
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) {
Log.e(TAG, "Signature verification failed.");
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
Log.e(TAG, "Signature exception.");
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
PublicKey:
这个PublicKey是用来验证支付结果的,所以这绝对是个Key,不可以让其他人知道的,这个Key放到支付服务器端,本地不存。
samples里的这段代码写的很有意思,能看出笑点不?
单机游戏的话,想办法把这个key存到某个地方加个密什么的,最好不要直接写到代码里。(其实对于单机游戏,如果没有自己的服务器来验证支付结果,本地不管如何操作,都是很容易被破解的,如果游戏比较大卖,推荐自己写个支付服务器端来验证支付结果)。
/* base64EncodedPublicKey should be YOUR APPLICATION‘S PUBLIC KEY
* (that you got from the Google Play developer console). This is not your
* developer public key, it‘s the *app-specific* public key.
* Instead of just storing the entire literal string here embedded in the
* program,
construct the key at runtime from pieces or
* use bit manipulation (for example, XOR with some other string) to hide
* the actual key.
The key itself is not secret information, but we don‘t
* want to make it easy for an attacker to replace the public key with one
* of their own and then fake messages from the server.
String base64EncodedPublicKey = "CONSTRUCT_YOUR_KEY_AND_PLACE_IT_HERE";
// Some sanity checks to see if the developer (that‘s you!) really followed the
// instructions to run this sample (don‘t put these checks on your app!)
if (base64EncodedPublicKey.contains("CONSTRUCT_YOUR")) {
throw new RuntimeException("Please put your app‘s public key in MainActivity.java. See README.");
if (getPackageName().startsWith("com.example")) {
throw new RuntimeException("Please change the sample‘s package name! See README.");
本地服务器验证补充:
关于支付结果的验证,本地服务器除了用publicKey做签名验证外,还可以到Google后台请求下支付结果验证。这个需要本地服务器和Google服务器交互通信。可以参考这个文档。
不过对于国内的开发者而言,在Google日益被封锁加重的情况下,在与Google服务器通信上绝对会有障碍,因为通信阻碍,会导致你验证失败,所以这个功能可选,有兴趣的可以添加上。
如果是直接用samples的代码的话还需要注意几点。第一,把错误提示改成用户友好型的。因为samples的错误提示主要是给开发者看的,所以提示的很详细,但是用户不需要,你只要告诉用户成功,失败以及简单的失败原因就行了。第二,在发布正式版时把打印信息关掉。第三,修改类名和变量名。
如果在支付时遇到一下错误,可做的处理。
1.当前应用程序不支持购买此商品:确定你手机上装的程序包名和签名和后台上传的一致。p.s.上传后台后APK需要等一段时间才能生效。
2.购买的商品不存在 :确保你代码里的商品名字和后台的一致,如果一致,则可能需要等一两个小时再测试,Google后台的问题。
3.loading了很长时间,最后给你提示未知错误:这个不管它,Google后台的问题,等会再测。
最后国内开发者确保是在vpn下进行测试!!!!
写在后面:
以上就是Google In-app Billing的代码添加了,其实就是把samples讲了一下,所以还是推荐去看下官方文档和samples吧,在那里你会学到更多。}

我要回帖

更多关于 谷歌play商店中国官网 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信