Google In-App Billing 实现(内含Unity 实现经验)

实现内购计费

傻逼目录

  1. Adding the AIDL file
  2. Updating Your Manifest
  3. Creating a ServiceConnection
  4. Making In-app Billing Requests
    1. Querying Items Available for Purchase
    2. Purchasing an Item
    3. Querying Purchased Items
    4. Consuming a Purchase
    5. Implementing Subscriptions
  5. Securing Your App

二逼参考

  1. In-app Billing Reference (V3)

根本无法执行的例子

  1. Sample Application (V3)

也看

  1. Selling In-app Products

 Google Play 的 In-app Billing 提供了一种直(hui)观(se)简(nan)单(dong)的接口, 提供向GooglePlay发送内购请求和管理GooglePlay内购交易的功能。这都基于V3版本的API

注意: 完整的植入和测试你的程序的教程,参见  Selling In-app Products 训练课程. 教程提供了完整的内购示例。

包含方便的类库,用于从google play设置你的连接,发送账单,购买响应,以及管理后台线程让你可以在主线程调用内购。

在此之前,确保你阅读了 In-app Billing Overview 以了解内购的基本概念,让你更容易实现内购。

实现内购,你需要

  1. IInAppBillingService库文件植入你的项目
  2. 更新你的 AndroidManifest.xml 文件
  3. 创建一个 ServiceConnection 并绑定到 IInAppBillingService.
  4. 从你的APP发送内购请求到IInAppBillingService.
  5. 处理从GooglePlay返回的内购回复。

添加 AIDL 文件到你的工程


IInAppBillingService.aidl 是一个 Android Interface Definition Language (AIDL) 文件他定义了V3版内购服务的接口,你可以使用这些接口,通过IPC方法的方式调用函式,创建账单请求

获取ADIL:

  1. 打开 Android SDK Manager.
  2. 展开 Extras 段
  3. 选择Google Play Billing Library.
  4. 点击Install packages 完成下载

 IInAppBillingService.aidl 文件在sdk/extras/google/play_billing/.

具体来说:

  1. 首先,下载 Google Play Billing Library到你的安卓工程
    1. 选择 Tools > Android > SDK Manager.(2.0版AS-File->Settings)
    2. Appearance & Behavior > System Settings > Android SDK, 选择SDK Tools 子页下载Google Play Billing Library.
  2. 下一步,拷贝 IInAppBillingService.aidl 文件到你的工
    1. 原文都是垃圾,直接创建src/com/android/vending/billing/
    2. 把文件考进去,拉倒。
    3. 原文说需要Gradle编译,没有也一样,直接打个APK试试,对应文件就会自己生成!
  3. 编译你的工程.编译出IInAppBillingService.java,用F3找吧,垃圾文件不在源文档说的那个位置!
    1. 需要用gradle编译一下,但是已经编好的也可以直接拿过来用(别听他的!)。(重要的事情说3遍,Unity用户请不要用AS,用Eclipse)X3。

更新你的 App的 Manifest


不翻译了,废话那么多,直接加上这句拉倒

android:name="com.android.vending.BILLING"/>

创建一个 ServiceConnection


你的APP必须有一个 ServiceConnection 来协助你的APP和GooglePlay之间的消息传递,至少你应该:

  • 绑定到 IInAppBillingService.
  • 发送账单 (作为 IPC 方法调用) 到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 训练课程,和附件例子。

创建In-app Billing请求


一旦你的应用程序连接到 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字符串和你之前随购买请求发送的令牌相互匹配。 

查询已购商品

要从应用程序检索用户的购买的信息,调用V3 API的   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,他会保护全部的激活的用户订阅信息。一旦订阅过期,并且没有被续订,他将不会出现在返回列表中。

保护你的APP


为了帮助确保发送到您的应用程序的事务信息的完整性,狗狗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.

















你可能感兴趣的:(Unity)