游戏不能play商店内购破解是怎么回事,是因为在playstore安装的时候跳过了付款方式吗,怎么解决啊

【参考文献】https://blog.csdn.net/liuweiballack/article/details/https://blog.csdn.net/change_from_now/article/details/一、概述使用应用内结算(In-app Billing)时,我们的应用通过相应的API来访问In-app Billing服务。应用不直接与Google Play服务器通信,而是通过进程间通信(IPC)向Google Play发送结算请求并接收Google Play返回的响应。In-app Billing在使用之前需要注意以下几点:只能用来销售数字内容,不能销售实体商品应用一旦被购买,无法进行退款服务 GooglePlay不提供内容交付,开发者需要自行交付在应用内购买的数字内容一个应用不能购买另一个应用发布的商品目前使用的In-app Billing是第3版,需要运行在Android2.2或更高版本,而且要求设备安装了最新版本的Google Play商店。第三版支持两种商品:托管的应用内商品(Managed product)和订阅(Subscription)。托管的应用商品:由Google Play跟踪和管理其所有权信息的商品,托管的商品在被购买后,必须先向Google Play发送消耗请求进行消耗,然后才能供用户再次购买。 订阅:允许开发者通过按月或按年结算的方式在应用内向用户销售内容、服务或功能,订阅无法消耗。二、Google Play购买流程购买开始时,应用需要针对相应的应用内商品发送结算请求。然后,Google Play 会处理此次交易的所有结帐详情,包括请求和验证付款方式以及处理财务交易。 当结帐流程完成后,Google Play 会向应用发送购买详情,例如订单号、订单日期和时间以及所付价格。应用不需要处理任何财务交易,这些事宜完全Google Play负责。具体流程如下所示: 应用向Google Play发送isBillingSupported 请求,确定当前使用的应用内结算 API 目标版本是否受支持;启动或用户登录时,向Google Play进行查询,确定用户拥有哪些商品,发送getPurchases 请求;通知用户商品是否可供购买,发送getSkuDetails 请求;提示用户购买,发送getBuyIntent 请求。三、消耗托管的应用内商品应用内商品一经售出,就会被视为“被拥有”。处于“被拥有”状态的应用内商品无法再通过 Google Play 购买。必须对“被拥有”的应用内商品发送消耗请求,然后 Google Play 才能再次将其设成可购买状态。消耗应用内商品会将商品切换回“未被拥有”状态并删除之前的购买数据。 通过发送consumePurchase 提出消耗请求。接入之前的准备一、在 Google Play Developer Console 创建应用注册一个开发者账号,按提示绑定信用卡并支付25美金;创建一个新的app,填写应用名称; 点击上图中的Prepare Store Listing,按照提示填写app的基本信息,信息一定要填写完整,否则无法正常发布应用;在Services & APIs中,找到license key记录下来。二、添加In-app Billing库文件(以Android Studio为例)在yourSDKpath/extras/google/market_billing/中找到文件IInAppBillingService.aidl,在src/main中创建文件夹aidl,然后创建package‘com.android.vending.billing’ 如图所示 点击执行rebuild,在下图所示位置看到IInAppBillingService.java文件,即为导入成功 将market_billing/samples/TrivialDrive 中util包整个复制到项目中。三、声明权限在AndroidManifest.xml中声明权限:&uses-permission android:name="com.android.vending.BILLING" /&四、初始化与Google Play的连接1、初始化IabHelperIabHelper mH
public void onCreate(Bundle savedInstanceState) {
String base64EncodedPublicK
// compute your public key and store it in base64EncodedPublicKey
mHelper = new IabHelper(this, base64EncodedPublicKey);
}2、调用startSetup方法mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
Log.e(TAG, "Problem setting up In-app Billing: " + result);
if (mHelper == null) {
});3、在Activity的onDestory()中解绑@Override
protected void onDestroy() {
super.onDestroy();
if (mHelper != null) {
mHelper.dispose();
}创建应用内购买的商品一、上传APK并发布进入APK面板,上传一个带有签名的APK文件,文件可以上传到PRODUCTION、BETA TESTING、ALPHA TESTING中任意一个中,三者区别见附录1。下面的流程以BETA测试为例。进入BETA TESTING页面,上传带有签名的APK文件;进入Content Rating页面,填写内容分级调查问卷,填完进行评估,根据结果接受评估或者重新填写;进入Pricing&Distribution页面,选择应用是付费还是免费,之后选择产品要发布的国家和地区;确保如图所示四个内容都显示为绿色对勾,即可发布应用; 点击Publish app发布,如果按钮显示为灰色,可以点击按钮上方的Why can’t I publish?查看还需要添加的内容,应用发布之后需要等候几个小时。在任一页面可以查看App的发布状态,如左图所示为正在发布,右图显示已经发布成功。
二、选择测试方法(BETA测试为例)待APP发布完成之后,选择一个测试方法,测试方法有三种(详见附录),此处以封闭式Beta测试为例。登录Google Play开发者控制台选择相应应用,点击左侧菜单中的APK选择Beta测试,进入“封闭式测试“创建列表或选择已有列表将Opt-in URL中链接发送给测试人员测试人员需要点击测试连接并选择加入测试注意:测试时可以选择测试账号是否需要真实支付。比如应用在进行大范围测试时,需要用户可以进行真实的付款操作,而内部测试人员可以进行支付测试,但无需真实付款。可以将内部测试的账号配置到Google Play后台。 配置如下:将账号填入下图三、创建商品点击左侧菜单中“In-app Products”,选择”Add new Product“In-app Billing第3版支持类型为’Managed product’和’Subscription’的商品配置product ID时要保证product ID是唯一的,product ID也叫做SKU,product ID在查询商品和购买商品时需要用到带商品信息填写完成,将其状态置为active客户端相关内容实现客户端实现具体内容详见官方Demo,此处只简单举例说明(示例代码不全,参照Demo)。一、查询商品详情通过创建商品时添加的product ID,可以在客户端查询商品的详情,调用queryInventoryAsync(boolean, List, QueryInventoryFinishedListener)boolean:是否返回商品详情(此处应设置为true)List:要查询的product IDs(SKUs)QueryInventoryFinishedListener:监听查询结果如果查询成功,返回结果封装在Inventory 对象中。代码示例:List&String& productNameList = new ArrayList&&();
productNameList.add(PRODUCT_ONE);
productNameList.add(PRODUCT_TWO);
mHelper.queryInventoryAsync(true, productNameList, new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
String price = inventory.getSkuDetails(PRODUCT_ONE).getPrice();
});二、购买托管的商品购买商品时也需要product ID,调用launchPurchaseFlow(Activity, String, int, OnIabPurchaseFinishedListener, String)Activity:调用launchPurchaseFlow的activityString:product ID,确保是ID而不是商品名称Int:request code值,可以是任何正整数,Google Play会将该值随购买的回复一起返回给Activity的onActivityResultOnIabPurchaseFinishedListener:购买请求完成的监听器String:字符串标记,惟一的标示购买请求,可以为空。如果你指定一个字符串值,谷歌将返回此字符串以及购买响应。在实际项目中最好由服务器随机生成一个字符串使用如果购买成功,返回结果封装在Purchase 对象里。 类型为‘Managed product’的商品可以重复购买,但再次购买之前一定要先向Google Play发送商品消耗的请求,否则无法购买。代码示例:mHelper.launchPurchaseFlow(this, productId, REQUEST_CODE, new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase info) {
}, "XXXXXXX");三、购买订阅内容在购买订阅内容时,调用的方法与购买托管的商品不一样,强行调用同一个方法,会在确认购买时接收到Google Play返回的错误信息。 调用launchSubscriptionPurchaseFlow(Activity, String, int,OnIabPurchaseFinishedListener, String), 参数解释参照2中内容。代码示例:mHelper.launchSubscriptionPurchaseFlow(this, productId, REQUEST_CODE, new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase info) {
}, "purchase subscription");四、查询已购买商品查询用户已经购买的商品,调用queryInventoryAsync(QueryInventoryFinishedListener) 注意:查询已购买商品时,查询到的是当前登录到Google Play上的用户已购买到的商品。代码实例:mHelper.queryInventoryAsync(new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inv) {
});五、消耗托管的商品一旦一个商品被购买,它将被认为是“被拥有”状态,处于该状态下的商品无法被同一用户再次购买。因此需要发送根据需求向Google Play发送消耗请求,调用consumeAsync(Purchase, OnConsumeFinishedListener)Purchase:要消耗的商品OnConsumeFinishedListener:消耗完成之后的监听代码示例:mHelper.consumeAsync(purchase, new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase, IabResult result) {
});测试注意事项由于配置比较复杂,在项目测试的时候,如果遇到“应用无法购买”的问题,请检查以下几点是否完成:确保上传了带有签名的APK文件;确保设备上安装了带有签名的APK,而不是调试版;确保在测试中添加了测试人员;确保设备登录的账号属于测试账号;确保测试账号激活了测试链接(最容易忽略);确保设备上的应用版本号和版本名称与Google开发者后台上传的APK一致;确保商品状态为Active。参考文档:
上文只是简单介绍说明,下文会详细说明(1)Google Play Service下载的Google Play Service包里会有一个库工程/**
* 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.android.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.java改造了下,因为这个是整个支付的关键,其他都是辅助的,可以不管。把这里的代码拷完,把该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."));
simple 里面的代码// 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里面的这一块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));
一,调用In-app Billing中的getBuyIntent方法,会传几个参数,第一个参数 3 代表的是当前所用的支付API的版本,第二个参数是你的包名,第三个参数就是你内购商品的ID,第四个参数是这次购买的类型,“inapp”和"subs",我们用的是第一个,第二个是只能购买一次的类型,第五个参数是订单号。需要讲的只有第三个和第五个参数。第三个参数,商品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);
支付结果返回后会调用上面这个方法,对于支付失败和其中的错误,代码写的很清楚,可以自行处理。关于上面的这个方法,这里简单说一下流程,看看这个方法是从哪调用的。首先去Sample里的MainActivity找到那个 onActivityResult 方法protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
if (mHelper == null)
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "onActivityResult handled by IABUtil.");
这个方法会在支付结束,你的程序重新回到前台的时候调用。在这个方法中可以看到!mHelper.handleActivityResult(requestCode, resultCode, data)
这里调用了 IabHelper 里的 handleActivityResult 方法。然后再到此方法里会看到调用 PurchseListener 的地方if (mPurchaseListener != null) {
mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
至此就可以知道 OnIabPurchaseFinishedListener 是从哪调用的了。然后在 handleActivityResult
方法里还可以看到这段代码// 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."); 也是有童鞋在问,我购买结束后,Google Play都提示购买成功了,但是在 OnIabPurchaseFinishedListener
却还是失败的,失败的信息就是Purchase signature verification FAILED for sku xxx这个错误信息就是从这里输出的,至于为何出现这个错误,就是因为 Sample 里的本地验证失败了(其实已经购买成功了)。出现这个比较多的情况就是因为使用Google保留测试ID:因为在 Security.java 中 会验证失败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);
base64PublicKey 为空,又或者是 signature 是空,就会验证失败。解决方法:1.使用本地验证。就去看看 Security 中的方法,然后仔细检查看是哪里出问题了。2.使用服务器验证。就去改造下 IabHelper 中的 handleActivityResult f方法,不再使用本地的 Security 做验证,如何操作可以看下面的内容。Sample里的Security还有一个坑,就是在你成功购买商品后,但是没有消耗,下次再登录游戏进行查询的时候会报个错误Failed to query inventory: IabResult: Error refreshing inventory (querying owned items). (response: -1003:Purchase signature verification failed)
之所以出现这个错误,是因为在查询的时候,也会运行 Security 中的 verifyPurchase 方法。追本溯源,一步一步的查找代码,会在 IabHelper 中的int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {... ...}
此方法中找到Security 调用 verifyPurchase 。如果是服务器做验证的话,就如上图示,把验证注释掉,如果是本地验证的话,就去查找 Security 中的
verifyPurchase 方法,看看哪里出错了,哪里传的值为空。 注意把验证代码注释掉的时候记得把生成 Purchase 的那两行代码提出来,否则你在查询的时候不会返回查询到的商品。仔细看Sample的代码的话,你会发现其实很多回调监听都是从IabHelper中调用的。两行代码如下:Purchase purchase = new Purchase(itemType, purchaseData, signature);
inv.addPurchase(purchase); 根据查询到的数据生成一个 purchase,然后把这个purchase 加入到 Inventory中,这样你就可以在查询成功的时候通过调用 inventory.getPurchase 方法来获取已经购买但是未消耗的商品了。现在来关注支付成功后的结果验证。在上面方法中会从支付结果的数据中取得两个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: base64EncodedPublicKey这个PublicKey是用来验证支付结果的,所以这绝对是个Key,不可以让其他人知道的,这个Key放到支付服务器端,本地不存。如果是拷贝Sample里的代码使用,会发现在 new IabHelper 的时候会在构造方法里传递这个 其实Sample只是用这个
PublicKey 做本地验证的,和初始化无关,你仔细看下 IabHelper 源码就知道了,所以本地不存这个 PublicKey,当
new IabHelper 的时候,可以随便传个字符串,也可把 IabHelper 构造方法改一下,不再传这个值。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服务器交互通信。可以参考这个文档。参考地址:https://developer.android.google.cn/google/play/developer-api.html#publishing_api_overview不过对于国内的开发者而言,在Google日益被封锁加重的情况下,在与Google服务器通信上绝对会有障碍,因为通信阻碍,会导致你验证失败,所以这个功能可选,有兴趣的可以添加上。补充1:如果是直接用samples的代码的话还需要注意几点。第一,把错误提示改成用户友好型的。因为samples的错误提示主要是给开发者看的,所以提示的很详细,但是用户不需要,你只要告诉用户成功,失败以及简单的失败原因就行了。第二,在发布正式版时把打印信息关掉。第三,修改类名和变量名。补充2:如果在测试支付时遇到一下错误,可做的处理。1.当前应用程序不支持购买此商品:确定你手机上装的程序包名和签名和后台上传的一致。p.s.上传后台后APK需要等一段时间才能生效。2.购买的商品不存在 :确保你代码里的商品名字和后台的一致,如果一致,则可能需要等一两个小时再测试,Google后台的问题。3.loading了很长时间,最后给你提示未知错误:这个不管它,Google后台的问题,等会再测。最后国内开发者确保是在vpn下进行测试!!!!写在后面:以上就是Google In-app Billing的代码添加了,其实就是把samples讲了一下,所以还是推荐去看下官方文档和samples吧,在那里你会学到更多。1. 最近有童鞋在问,测试支付时遇到账号未认证的错误,怎么解决。我当时测试的时候没遇见这个错误,个人估计可能是你的应用还没有通过谷歌的审核,上传后台要等半个小时或者一个小时。当然如果不是这个原因的话,请解决了此问题的童鞋私信我,我把它加到博客里,这样可以帮助其他人,先谢过啦。2.最近在搞unity,所以很少上 CSDN了(也很久没更新博客了,惭愧啊惭愧)。留个邮箱吧,有问题的可以发邮件给我
。努力学习,共同进步!关于 Authentication is required. You need to sign into your Google Account. 的解决。1.先前有童鞋问过这个问题,就是支付测试时提示账号未认证,需要登录Google账号。目前已知的解决办法就是在手机上使用测试账号(在上篇介绍过如何设置测试账号),不要用普通的Google账号。P.S.添加使用的测试账号,只要是Gmail账号即可,但是不要用自己的开发者账号,就是说不要在测试机上登陆开发者账号进行测试,切记,切记。否则就会出现"无法购买您要买的商品" 的错误!2.在 android 5.0 上测试时遇见
java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.android.vending.billing.InAppBillingService.BIND } 根据童鞋反应,情况是这样滴。。。在5.0之前通过 Intent 调用 bindService()这个方法时使用 explicit intent 是推荐级别,但是在5.0之后是强制使用了。解决办法:1.升级In-app Billing。使用最新的Billing libraries。我看了下Billing Version已经升到5了。大概看了下,没有新增公共方法。
2.targetSdkVersion降级如果targetSdkVersion用的是 5.0 级别的 API 21,那就把targetSdkVersion降到 19(对应的版本是 4.4.2)。同时记得修改工程属性文件 project.properties 中的 target .最近很多童鞋来问,APK上传后台了,设置为Alpha或者Beta版了,商品也设置好了,也等了一个小时,有的等了一天了。。。等等,都准备好了,但是在测试购买商品的时候还是不能购买QAQ~
后来才发现,他们上传到Google后台的商品虽然设置为Alpha或者Beta版了,但是还是处于草稿(Draft)状态,而不是发布状态,所以无法测试。出现这个的原因主要是Google后台的 APP信息没有填写完整。在把自己的程序上传到后台后,需要填写相关的信息,虽然是测试,但是不要以为就写个App名字就完事了,你需要填写完整的App信息才可以发布,即使不是发布正式版。还有商品也要设置为发布状态,印象中商品也会有草稿状态。所以把App上传到Google后台,等待一段时间后,要记得检查你App的状态,看是不是草稿状态,后台的右上角也有提示“为何此App无法发布?”,点进去看看也会有收获。切记要细心~int responseCode = getResponseCodeFromIntent(data);
String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
android集成GooglePay支付,实现应用内支付,如金币等
发布海外市场,应用内又有支付功能,不可避免要集成GooglePay支付,国内不支持GooglePay,要FQ.
开发文档:集成应用内支付官方文档
2、开发前工具准备...
Google支付的接入详解
Google支付的详细集成过程
Android集成Google支付,以及遇到的坑、坑
Google商店的应用被下架,应用内购买必须走Google支付,还要扣去百分之三十的手续费,而且有些国家还会收一定的销售税最高达27%,其实Google支付只是自己集成了Paypal支付和银行卡支付,...
谷歌支付总结(上)
最近Google貌似又被全面封杀了,幸好在此之前,把Google play billing弄完了,现在写篇 博客来做下记录。这篇博客一是自己做个记录,二是帮助其他有需要的人。因为现在...
接入 Google 支付(In-app-Billing)
工作中接入 Google 支付的流程
由于产品决定需要接入 Google 支付(In-app-Billing)来进行购买商品,但是之前完全没有接触过呀,没办法呀,只能自行百度,但是找到的资料都是千篇...
接入谷歌支付所遇到的坑
最近公司需要把游戏发布到海外,要接入一个谷歌支付。听到消息的我眼泪掉下来,但是工作还是要做的。任务还是要执行的,一步一步来吧。首先得拿到文档,得挂个vpn上个谷歌开发者,拿到文档才行。这里提供谷歌的a...
Google支付(In-app Billing)接入
该文档基于第3版。应用内结算简介一、概述使用应用内结算(In-app Billing)时,我们的应用通过相应的API来访问In-app Billing服务。应用不直接与Google Play服务器通信...
Google in app billing 应用内支付
一 简介Google in app billing 是google play 商店的应用内支付,他是一种应用内的虚拟的道具支付服务,支持应用内支付(inapp)和订阅(subs)两种模式;
一般在中...
java服务端验证谷歌支付Google Pay
翻阅大半个谷歌,对服务器验证账单,讲的少之又少,还TM没有看懂
查阅整个百度,发现几乎所有demo都是用世界上最好的语言php写的,这我
在此坐下记录希望能帮到有需要的人
没有更多推荐了,}

我要回帖

更多关于 play商店内购 的文章

更多推荐

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

点击添加站长微信