Google Play 的 In-app Billing 提供了一种直(hui)观(se)简(nan)单(dong)的接口, 提供向GooglePlay发送内购请求和管理GooglePlay内购交易的功能。这都基于V3版本的API
注意: 完整的植入和测试你的程序的教程,参见 Selling In-app Products 训练课程. 教程提供了完整的内购示例。
包含方便的类库,用于从google play设置你的连接,发送账单,购买响应,以及管理后台线程让你可以在主线程调用内购。
在此之前,确保你阅读了 In-app Billing Overview 以了解内购的基本概念,让你更容易实现内购。
实现内购,你需要
AndroidManifest.xml
文件ServiceConnection
并绑定到 IInAppBillingService
.IInAppBillingService
. IInAppBillingService.aidl
是一个
Android Interface Definition Language (AIDL) 文件他定义了V3版内购服务的接口,你可以使用这些接口,通过IPC方法的方式调用函式,创建账单请求
获取ADIL:
Extras
段 IInAppBillingService.aidl
文件在sdk/extras/google/play_billing/
.
具体来说:
IInAppBillingService.aidl
文件到你的工
IInAppBillingService.java
,用F3找吧,垃圾文件不在源文档说的那个位置!
不翻译了,废话那么多,直接加上这句拉倒
android:name="com.android.vending.BILLING"/>
你的APP必须有一个 ServiceConnection
来协助你的APP和GooglePlay之间的消息传递,至少你应该:
IInAppBillingService
. 为了和GooglePlay上的内购服务建立连接,你需要实现一个 ServiceConnection
并把你的Activity绑定到IInAppBillingService
.
重写onServiceDisconnected
和 onServiceConnected
方法,在建立连接后获得IInAppBillingService
实例的引用
IInAppBillingService mService;
ServiceConnection mServiceConn =newServiceConnection(){
@Override
publicvoid onServiceDisconnected(ComponentName name){
mService =null;
}
@Override
publicvoid onServiceConnected(ComponentName name,
IBinder service){
mService =IInAppBillingService.Stub.asInterface(service);
}
};
在你的activity里的 onCreate
方法中,调用 bindService
方法绑定. 把 Intent
传给方法,他代表内购服务。再传入一个你刚刚创建的 ServiceConnection
的实例,并且显式的设定你的Intent的包名为com.android.vending
— Google Play app的包名。
注意: 为了保证账单交易的安全性, 使用如下所示的setPackage()
方法时刻确保显示的设置intent的目标包名为com.android.vending
。显示设置暴民够可以保证只有GooglePlay的App可以从你的APP里处理账单请求,阻止其他应用中断你的请求.
@Override
publicvoid onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent serviceIntent =
newIntent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
bindService(serviceIntent, mServiceConn,Context.BIND_AUTO_CREATE);
}
现在你可以使用mService的引用去和GooglePlay Service通信了。
重要: 当你的Activity嗝屁的时候记住对你的mService解绑。不然他会让你的(其实是用户的,不关你屁事)设备变慢。如何解绑看下面:
@Override
publicvoid onDestroy(){
super.onDestroy();
if(mService !=null){
unbindService(mServiceConn);
}
}
(没让你看JJ,看上面的代码块)
全套实现IInAppBillingService
, 参考 Selling In-app Products 训练课程,和附件例子。
一旦你的应用程序连接到 Google Play, 你就可以初始化内购商品了。 GooglePlay提供了校验接口,使用户可以进入他们的支付方法,所以你的应用程序不必自己处理支付事务。
当一个物品被购买,GooglePlay会识别这个用户对那个物品的所有权,并且防止他购买相同id的产品,直到这个物件被消费掉。
你可以在你的APP里面控制物品如何被消耗掉(通常买了就应该马上消耗掉,这样客户才能再买),并通知GooglePlay,那个商品可以被再次购买。
你也可以快速的向GooglePlay查询为用户建立的物品购买清单。这很有用,例如,当你的用户启动APP时,你可能需要为他恢复购买。
在你的APP里,你可以利用V3内购API向GooglePlay查询商品的细节。你需要把一个请求发给In-app Billing 服务。
首先创建一个Bundle
他包含一个记录商品ID的,键值为"ITEM_ID_LIST"的字符串 ArrayList列表
, 每一个ID都是一个可购买商品。
ArrayList<String> skuList =newArrayList<String>();
skuList.add("premiumUpgrade");
skuList.add("gas");
Bundle querySkus =newBundle();
querySkus.putStringArrayList(“ITEM_ID_LIST”, skuList);
为了从Google Play取到这个信息,调用In-app Billing V3API的 getSkuDetails
方法,
并且把你的
API版号,3
你的APP包名:getPackageName()
购买类别“inapp”
以及你创建的bundle传递给方法
Bundle skuDetails = mService.getSkuDetails(3,
getPackageName(),"inapp", querySkus);
如果请求成功,返回的bundle将会有返回值代码 BILLING_RESPONSE_RESULT_OK
(0).
警告: 不要在主线程调用 getSkuDetails
方法. 调用这个方法会触发一个网络请求并阻塞主线程。
作为替代,创建一个单独线程,并在其中调用getSkuDetails
。(不会JAVA,你倒是说说怎么创建线程)
如果你想指定所有的可能的返回代码的意义,去看 In-app Billing Reference.(我不知道,也不想知道!)
查询结果被存在一个key为DETAILS_LIST的字符串ArrayList中。购买信息以字符串形式存于JSON格式中。
想要获取返回的产品细节信息,参照 In-app Billing Reference.
在这个例子中,你从前面代码块里创建的skuDetails 这个bundle里面,取得你的内购商品价格。
int response = skuDetails.getInt("RESPONSE_CODE");
if(response ==0){
ArrayList<String> responseList
= skuDetails.getStringArrayList("DETAILS_LIST");
for(String thisResponse : responseList){
JSONObjectobject=newJSONObject(thisResponse);
String sku =object.getString("productId");
String price =object.getString("price");
if(sku.equals("premiumUpgrade")) mPremiumUpgradePrice = price;
elseif(sku.equals("gas")) mGasPrice = price;
}
}
PS:估计你得自己搞一张CSV表,或其他什么的,存放你的商品,然后对应返回信息,来更新UI,显示给玩家。客户端有而服务器不存在的商品,或被下架的商品,是不应该显示给玩家的。
购买一个商品
调用IaB服务的getBuyIntent方法,从你的APP发起一个购买请求。
将参数:
Iab版号:3
包名
商品ID
购买类型“inapp”
以及一个developerPayload字符串,传入该方法。
这个developerPayLoad字符串用于指定一个你希望GooglePlay随着购买结果返回的特别附加参数(有毛用,你倒是先说明白啊)。
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(),
sku,"inapp","bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
如请求成功,返回的 Bundle
会有返回值代码 BILLING_RESPONSE_RESULT_OK
(0) 和一个 PendingIntent
(这又是啥逼玩意啊!)你可以用他们启动一个购买流程。
接下来,从使用键值BUY_INTENT从返回的Bundle
提取 PendingIntent
.
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
想要完成购买事务,需要调用 startIntentSenderForResult
方法,并使用之前创建的 PendingIntent
。在这个栗子里面,你使用一个随意的值1001作为请求码。
startIntentSenderForResult(pendingIntent.getIntentSender(),
1001,newIntent(),Integer.valueOf(0),Integer.valueOf(0),
Integer.valueOf(0));
Google Play 发送返回消息到 PendingIntent
到你的APP的 onActivityResult
方法中。 onActivityResult
方法会得到一个返回值为Activity.RESULT_OK
(1) 或Activity.RESULT_CANCELED
(0)的结果.获得Intent
中全部返回订单 的信息,访问 In-app Billing Reference.
购买订单以字符串JSON格式,映射于 INAPP_PURCHASE_DATA
键值,在返回 Intent中
,举个栗子:
'{
"orderId":"GPA.1234-5678-9012-34567",
"packageName":"com.example.app",
"productId":"exampleSku",
"purchaseTime":1345678900000,
"purchaseState":0,
"developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
"purchaseToken":"opaque-token-up-to-1000-characters"
}'
注意: Google Play 会为购买生成一个token(令牌). 此令牌是一个不可读的字符序列,最长1000个字符. 将这个令牌整个传入其他方法,比如当你想消耗购买时,正如 Consume a Purchase. 中提到的。不要省略或者改变令牌的大小写,你需要保存整个令牌。
从前面的示例中,您得到响应代码、购买数据和响应Intent
的签名。
@Override
protectedvoid onActivityResult(int requestCode,int resultCode,Intent data){
if(requestCode ==1001){
int responseCode = data.getIntExtra("RESPONSE_CODE",0);
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
if(resultCode == RESULT_OK){
try{
JSONObject jo =newJSONObject(purchaseData);
String sku = jo.getString("productId");
alert("You have bought the "+ sku +". Excellent choice,
adventurer!");
}
catch(JSONException e){
alert("Failed to parse purchase data.");
e.printStackTrace();
}
}
}
}
安全性建议: 当你发送一个购买请求,请创建一个唯一的字符串令牌标记这次购买请求,并且把令牌包含到developerPayload
中,你可以随机的生成一个字符串令牌。
当你从GooglePlay获取购买响应,确保验证返回数据的前面,orderId和developerPayload
字符串。
附加的安全性:你应该在你自己的安全服务器上,检测这些东西。
确保你的orderId是一个你之前没用过的唯一值。并且developerPayload
字符串和你之前随购买请求发送的令牌相互匹配。
getPurchases
方法。参数传入版号3,包名和“inapp”类别(订阅为“subs”)
Bundle ownedItems = mService.getPurchases(3, getPackageName(),"inapp",null);
GooglePlay服务只返回当前设备上,登录到GooglePlay的用户帐户所做的购买。如果请求成功, Bundle
返回代码0.
返回的 Bundle
同时也包含一个产品IDs列表,标记每一个产品的细节和签名。
为了提高表现,内购账单服务最多返回用户拥有的前700个产品,以getPurchase
调用顺序为基准。
如果用户拥有大量产品,GooglePlay的 Bundle
会返回INAPP_CONTINUATION_TOKEN
暗示还有更多的商品可以获得。你可以用这个token作为参数,进行后续的getPurchases
请求。
GooglePlay不断的返回接续令牌,直到返回全部商品。(没鸡巴用,我不可能创建那么多产品)
下面代码演示如何从返回值中获取已购商品信息
int response = ownedItems.getInt("RESPONSE_CODE");
if(response ==0){
ArrayList<String> ownedSkus =
ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
ArrayList<String> purchaseDataList =
ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
ArrayList<String> signatureList =
ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
String continuationToken =
ownedItems.getString("INAPP_CONTINUATION_TOKEN");
for(int i =0; i < purchaseDataList.size();++i){
String purchaseData = purchaseDataList.get(i);
String signature = signatureList.get(i);
String sku = ownedSkus.get(i);
// do something with this purchase information
// e.g. display the updated list of products owned by user
}
// if continuationToken != null, call getPurchases again
// and pass in the token to retrieve more items
}
您可以使用V3API来跟踪在GooglePlay中购买的应用程序产品的所有权。一旦一个内购商品被购买,它被认为是“拥有”,不能从GooglePlay再次购买。你必须向GooglePlay,为你之前被购买的内购商品,发送一个消费请求,使之可以被再次购买。
重要: 只有内购商品可以被消耗,订阅无法被消耗。
你怎样在你的APP中利用消耗机制是你的事.通常,通常消耗机制对应的是消耗品,比如药瓶,金币,或者装备,而不应该是永久效果,比如付费关卡,一旦开启,永久收益。
为了记录一个消耗,发送consumePurchase
方法到 In-app Billing service 并且将标记将要被移除的购买的purchaseToken
作为参数传入。
看上面,购买的时候INAPP_PURCHASE_DATA
返回的字符串包含了这次要用的purchaseToken
,他在token变量里面,这就是为什么上面的说明要求你自己记录token。
int response = mService.consumePurchase(3, getPackageName(), token);
警告: 别在主线程调用这个方法,建立独立的子线程干这个事。否则他会阻塞你的主线程。
把产品交付给玩家,是你的事,比如玩家买了500钻石,你需要更新玩家的库存,GooglePlay不管这事。
安全性建议:你必须先发送消耗请求给GooglePlay,等收到GooglePlay的消耗反馈之后,在更新玩家的库存。(不然玩家可能会作弊么?会多得几次好处么?)
订阅购买流程和产品购买流程相似。唯一不同是产品类别需要标记为“subs”,
Bundle bundle = mService.getBuyIntent(3,"com.example.myapp",
MY_SKU,"subs", developerPayload);
PendingIntent pendingIntent = bundle.getParcelable(RESPONSE_BUY_INTENT);
if(bundle.getInt(RESPONSE_CODE)== BILLING_RESPONSE_RESULT_OK){
// Start purchase flow (this brings up the Google Play UI).
// Result will be delivered through onActivityResult().
startIntentSenderForResult(pendingIntent, RC_BUY,newIntent(),
Integer.valueOf(0),Integer.valueOf(0),Integer.valueOf(0));
}
查询也是一样,传入“subs”参数即可
Bundle activeSubs = mService.getPurchases(3,"com.example.myapp",
"subs", continueToken);
这个调用会返回他Bundle,他会保护全部的激活的用户订阅信息。一旦订阅过期,并且没有被续订,他将不会出现在返回列表中。
为了帮助确保发送到您的应用程序的事务信息的完整性,狗狗Play会为包含购买订单的JSON字符串返回数据进行签名。
狗狗Play使用配置在你的Developer Console的私钥给来创建这个签名,Developer Console为每个应用程序生成一个RSA钥匙对。
注意: 想找到钥匙对的公钥部分,在 Developer Console,打开你的application's details,然后点击Services & APIs,查看字段 Your License Key for This Application.
由狗狗Play生成的 Base64-encoded RSA 公钥使用二进制编码, X.509 subjectPublicKeyInfo DER SEQUENCE 格式. 他与狗狗Play的许可是同一级别的。
当你的应用程序收到了签名的回复之后,你可以使用RSA的公钥部分去验证签名。通过验证签名你可以知晓返回信息是否被篡改或欺骗。
你可以在你的客户端执行验证操作,但是如果你的APP连接到安全的远程主机,我们建议你在服务器上做这个验证。
更多安全性建议,参考 Security and Design.