SDK接入(2)之Android Google Play内支付(in-app Billing)接入

继上篇SDK接入(1)之Android Facebook SDK接入整理完Facebook接入流程之后,再来整理下Google Play in-app Billing支付的接入流程。众所周知,Google Play是Google Android官方的应用商店,也是将应用发布到世界各地一个重要的渠道。支付作为盈利的一个重要手段,可以借助Google in-app Billing api来达到目的。

Google Play in-app Billing的接入过程需要阅读接入文档。

(1)Google Play开发者控制台

Google Play开发者控制台是管理应用,配置商品和发布的后台。

使用步骤:

1. 注册Google Play开发者帐号



2. 配置应用商品,获取客户端接入参数,步骤简述如下:

在Google Play后台创建应用,设置游戏名称。按照要求填写商品详情。上传APK,注意上传的APK必须是release签名的正式包,同时要匹配好包名和版本号,然后再配置商品品项。获取Google Play的接入公钥(Base64编码的RSA公共密钥)。


受管理的商品:每个帐号只能购买一次,不能重复购买的商品。例如:激活码、解锁关卡等。



不受管理的商品:该商品可重复购买。包含支付完须消费,没消费的不可能重复购买,这步对消费者是透明的。例如:金币、钻石等。



订阅:通过按月或按年结算的方式在应用内向用户销售内容、服务或功能。


3. 获取服务端接入参数

支付完成之后,需要在服务端校验支付的票据是否合法。同样,也需要获取服务端参数。步骤如下:

(1)使用创建应用的开发者帐号,进入Google API管理后台。

(2)确认是否启用了Google Play Android Developer API。若未启用,则点启用。

(3)配置OAuth,填写邮件地址和产品名称,并保存。

(4)创建客户端OAuth id。

(5)在新界面中,选择网页应用。并配置授权地址和重定向地址,并点击创建。

(6)获取客户端id和客户端密钥。

(7)回到在Google Play后台启用API。确保一个关联和一个启用状态。

(8)用客户端id获取code。

在浏览器中访问,如下地址(注意XXX替换实际的客户端id):

​​

会得到如下地址,至此便获取code的值:

​​

(9)再利用获取到的code,获取refresh_token参数。

用post请求如下地址,注意参数:​​code=XXX&client_id=XXX&client_secret=XXX&redirect_uri=http://www.example.com/oauth2callback&grant_type=authorization_code​​,其中grant_type为固定值。


得到json:


{  
"access_token" : "ya29.dAFAF9xX89iR4s0Li4_faSmtGFonWQz67HvfXZFkPWsY_tHI_q1c7fc6WTS9EqKMi7_wonxhp4Q2FA",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "XXX"
}


这样便获得了refresh_token参数。

服务器校验的时候,便可以通过refresh_token,来获取access_token等。

(2)Google Play in-app Billing购买流程

这里我就直接从Google Play官方文档摘取过来购买流程的内容,方便查找。


1. 购买流程

先熟悉下V3版本的购买流程,知道大体的支付步骤和实现逻辑。如图:

(1) 您的应用向 Google Play 发送 ​​isBillingSupported​​ 请求,以确定您当前使用的应用内结算 API 目标版本是否受支持。

(2) 当您的应用启动或用户登录时,最好向 Google Play 进行查询,确定该用户拥有哪些商品。要查询用户的应用内购买,请发送 ​​getPurchases​​​ 请求。如果该请求成功,Google Play 会返回一个 ​​Bundle​​,其中包含所购商品的商品 ID 列表、各项购买详情的列表以及购买签名的列表。

(3) 通常情况下,您需要通知用户商品是否可供购买。要查询您在 Google Play 中定义的应用内商品的详细信息,应用可以发送 ​​getSkuDetails​​​ 请求。您必须在该查询请求中指定商品 ID 列表。如果该请求成功,Google Play 会返回一个包含产品详情(包括商品的价格、标题、说明和购买类型)的 ​​Bundle​​。

(4) 如果该用户还未拥有应用内商品,您可以提示购买。为了发起购买请求,您的应用会发送 ​​getBuyIntent​​ 请求,指定要购买商品的商品 ID 以及其他参数。当您在开发者控制台中创建新的应用内商品时,应记录其商品 ID。

Google Play 返回的 ​​Bundle​​​ 中包含 ​​PendingIntent​​,您的应用可用它来启动购买结帐界面。

您的应用通过调用 ​​startIntentSenderForResult​​​ 方法来启动 ​​PendingIntent​​。

当结帐流程结束后(即用户成功购买商品或取消购买),Google Play 会向您的 ​​onActivityResult​​​ 方法发送响应 Intent。​​onActivityResult​​​ 的结果代码中有一个代码将用于表明用户是完成了购买还是取消了购买。响应 ​​Intent​​​ 中会包含所购商品的相关信息,其中包括 Google Play 为了唯一识此次购买交易而生成的 ​​purchaseToken​​​ 字符串。​​Intent​​ 中还包含使用您的私人开发者密钥签署的购买签名。

2. 消耗流程

Google Play的支付分为​​购买-消耗​​两步。如果是在Google Play后台配置的商品品项是受管理类型的商品,则只需要调用购买即可,即不可重复购买。如果配置的是不受管理类型的商品,则在购买成功回调里要手动调用下消耗接口,否则该商品不能重复购买。

在第 3 版中,所有应用内商品都是托管的。也就是说,用户对所购应用内商品的所有权由 Google Play 进行维护,您的应用可以在需要时查询用户的购买信息。当用户成功购买应用内商品后,该次购买就会记录在 Google Play 中。应用内商品一经售出,就会被视为“被拥有”。处于“被拥有”状态的应用内商品无法再通过 Google Play 购买。您必须对“被拥有”的应用内商品发送消耗请求,然后 Google Play 才能再次将其设成可购买状态。消耗应用内商品会将商品切换回“未被拥有”状态并删除之前的购买数据。

为了检索用户所拥有商品的列表,您的应用会向 Google Play 发送 ​​getPurchases​​​ 调用。您的应用可以通过发送 ​​consumePurchase​​​ 调用提出消耗请求。在请求参数中,您必须指定应用内商品独一无二的 ​​purchaseToken​​ 字符串,此字符串是在商品售出时由 Google Play 指定的。Google Play 会返回一个状态代码,指明此次消耗是否已成功记录。

(1)调用 ​​getBuyIntent​​ 启动购买流程。

(2)从 Google Play 接收指示购买是否成功完成的响应 ​​Bundle​​。

(3)如果购买成功,通过调用 ​​consumePurchase​​ 消耗此次购买。

(4)从 Google Play 接收指示消耗是否成功完成的响应代码。

(5)如果消耗成功,在应用中配置商品。

以上是购买到消耗的整个流程,这么复杂的购买流程到代码层该如何实现了。还好我们可以通过取巧的办法,用Google Play给我们提供的Samples中代码“拿来即用”,节省开发周期。

(3)Google Play in-app Billing api接入

1. 下载Google Play Billing Library

打开ANdroid SDK Manager。在extras中勾选Google Play Billing Library和Google Play Service。如图:

下载完的Library路径应该会在​​<android-sdk-root>/extras/google/play_billing​​。目录结构如图:


并导入samples到Eclipse中。用AndroidStudio也类似,这里主要介绍下Eclipse的操作步骤。

2. 将samples对应的代码直接拷贝到自己的项目中。如图:
3. 在AndroidManifest中添加如下权限:


<uses-permission android:name="com.android.vending.BILLING" />


4. 初始化in-app Billing api。直接把以下代码放到合适的地方,一般是主Activity的onCreate中。​​IabHelper​​​是一个封装了购买-消耗的个工具类,​​queryInventoryAsync​​是订单查询方法。注意替换实际的公钥(Google Play后台创建应用里,在服务和API这项可以找到,一串Base64编码的字符串)。


// base64EncodedPublicKey为Base64编码RSA公共密钥
String base64EncodedPublicKey = "CONSTRUCT_YOUR_KEY_AND_PLACE_IT_HERE";
// 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.
return;
}

// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return;

// 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);
}
});


5. 支付。在发起购买请求的时候,调用以下代码。sku参数为对应Google Play后台配置的商品品项id。extraData可用于透传参数


IabHelper.launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener, String extraData)


6. 消耗。一般是在支付成功或查询成功的回调里调用消费方法。


注意:不受管理的商品,在支付完需要调用消费。不然,没法重复购买。测试帐号返回的票据中不带orderId。



IabHelper.consumeAsync(Purchase purchase, OnConsumeFinishedListener listener)


7. 查询。在查询回调里,检测如果存在支付但没消费的商品,继续调用消费。可用于漏单处理。直接放在IapHelper初始化中即可。


IabHelper.queryInventoryAsync(QueryInventoryFinishedListener listener)


8. 销毁。一般是在主Activity的onDestory里执行。


public void onDestroy() {
super.onDestroy();

// very important:
Log.d(TAG, "Destroying helper.");
if (mHelper != null) mHelper.dispose();
mHelper = null;
}


(4)测试Google Play in-app Billing api的接入

1. 测试机上安装Google框架

国内的手机一般需要手动安装Google框架(Google Play商店,Google Play Service等),这里推荐个“谷歌安装器”的app。安装这个app可以自动检测手机内缺少哪些Google服务。不过,安装“谷歌安装器”部分手机需要Root权限。

当然,安装完Google框架还需要VPN,能FQ,正常打开Google Play商店。

2. 安装接入好Google Play in-app Billing的app到手机

前面提到,测试Google Play in-app Billing的接入,须包名,版本好跟上传的一致,而且签名必须是release签名的。这样我们可以将release签名文件修改为默认的签名,方便我们调试。直接连手机运行,省去了每次打包再安装到手机

(1)先备份正式的签名文件。然后,在Windows命令行窗口中修改keystore密码为android。

keytool -storepasswd -keystore test.keystore

先输入原签名文件的密码,再输入新密码为android。

(2)修改原签名文件的alias,修改为androiddebugkey。

keytool -changealias -keystore test.keystore -alias test -destalias androiddebugkey

test为原签名文件的alias,修改为​​androiddebugkey​

这步会要求输入keystore的密码(即:android)和当前alias的密码

(3)修改alias密码

keytool -keypasswd -keystore my.keystore -alias androiddebugkey

这步会要求输入keystore密码(即:android)和原alias密码,然后在输入新的alias密码,输入android即可。

(4)Eclipse中指定默认的签名

选择Window->Preferences->Android->Build->Browse,选择刚制作的签名文件即可。这样,就可以直接连手机调试运行了。

3. 添加测试帐号

在Google Play后台上传Alpha版测试或Beta测试版里添加测试人员帐号。

(1)创建Google+群

邀请测试人员加入Google+群组的方式,邀请会收到邮件,不过邀请大概会有4小时延迟。

(2)分享测试连接

将形如以下连接分享给测试者,测试者打开连接加入测试。“org.cocos2d.game”为应用对应的包名。


(5)接入常见问题

1. 无法购买您要买的商品。

当前Google Play帐号不是测试帐号

2. 需要验证身份。您需要登录自己的Google帐号。

本地测试的版本号和google上传的版本号要一致

3. Cannot load library: soinfo_relocate(linker.cpp:993): cannot locate symbol "signal"

05-05 19:08:32.148: E/AndroidRuntime(7822): FATAL EXCEPTION: main

05-05 19:08:32.148: E/AndroidRuntime(7822): java.lang.UnsatisfiedLinkError: Cannot load library: soinfo_relocate(linker.cpp:993): cannot locate symbol "signal" referenced by "libcocos2dlua.so"...

05-05 19:08:32.148: E/AndroidRuntime(7822): at java.lang.Runtime.loadLibrary(Runtime.java:372)

05-05 19:08:32.148: E/AndroidRuntime(7822): at java.lang.System.loadLibrary(System.java:514)

05-05 19:08:32.148: E/AndroidRuntime(7822): at org.cocos2dx.lib.Cocos2dxActivity.onLoadNativeLibraries(Cocos2dxActivity.java:113)

05-05 19:08:32.148: E/AndroidRuntime(7822): at org.cocos2dx.lib.Cocos2dxActivity.onCreate(Cocos2dxActivity.java:128)

05-05 19:08:32.148: E/AndroidRuntime(7822): at org.cocos2dx.lua.AppActivity.onCreate(AppActivity.java:90)

05-05 19:08:32.148: E/AndroidRuntime(7822): at android.app.Activity.performCreate(Activity.java:5226)

05-05 19:08:32.148: E/AndroidRuntime(7822): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1151)

05-05 19:08:32.148: E/AndroidRuntime(7822): at com.lbe.security.service.core.client.b.x.callActivityOnCreate(Unknown Source)

05-05 19:08:32.148: E/AndroidRuntime(7822): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2360)

05-05 19:08:32.148: E/AndroidRuntime(7822): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2448)

05-05 19:08:32.148: E/AndroidRuntime(7822): at android.app.ActivityThread.access$600(ActivityThread.java:173)

05-05 19:08:32.148: E/AndroidRuntime(7822): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1392)

05-05 19:08:32.148: E/AndroidRuntime(7822): at android.os.Handler.dispatchMessage(Handler.java:107)

05-05 19:08:32.148: E/AndroidRuntime(7822): at android.os.Looper.loop(Looper.java:194)

05-05 19:08:32.148: E/AndroidRuntime(7822): at android.app.ActivityThread.main(ActivityThread.java:5469)

05-05 19:08:32.148: E/AndroidRuntime(7822): at java.lang.reflect.Method.invokeNative(Native Method)

05-05 19:08:32.148: E/AndroidRuntime(7822): at java.lang.reflect.Method.invoke(Method.java:525)

05-05 19:08:32.148: E/AndroidRuntime(7822): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:857)

05-05 19:08:32.148: E/AndroidRuntime(7822): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:624)

05-05 19:08:32.148: E/AndroidRuntime(7822): at dalvik.system.NativeStart.main(Native Method)

Application.mk添加:APP_PLATFORM := android-17

4. 此版本的应用为配置为通过Google Play结算。有关详情,请访问帮助中心。

检查下打包所用的签名与上传Google Play后台的签名是否一直。

5. In-app billing error: Purchase signature verification FAILED

代码中base64EncodedPublicKey参数不正确,跟Google后台的不一致。

技术交流QQ群:528655025

作者:AlphaGL