实现应用内购买结算

Google Play 上的应用内购买结算提供了一个直接、简单的界面,让您可以使用 Google Play 发送应用内购买结算请求和管理应用内购买结算交易。 下面的信息涵盖了如何使用 API 版本 3 从您的应用调用应用内购买结算服务的基本知识。

:要查看完整实现并了解如何测试您的应用,请参阅出售应用内商品培训课程。 培训课程提供了一个完整的示例应用内购买结算应用,包括多种工具类,便于处理关键任务(例如设置您的连接、发送购买结算请求和处理来自 Google Play 的响应以及管理后台线程),这样您就可以从主 Activity 调用应用内购买结算。

开始前,请务必阅读应用内购买结算概览,以便熟悉一些概念,使您能够轻松实现应用内购买结算。

要在您的应用中实现应用内购买结算,您需要执行以下操作:

  1. 将应用内购买结算库添加到您的项目中。
  2. 更新您的 AndroidManifest.xml 文件。
  3. 创建 ServiceConnection 并将其绑定到 IInAppBillingService
  4. 从您的应用发送应用内购买结算请求至IInAppBillingService
  5. 处理来自 Google Play 的应用内购买结算请求响应。

将 AIDL 文件添加到您的项目中

IInAppBillingService.aidl 是一种定义应用内购买结算版本 3 服务接口的 Android 接口定义语言 (AIDL) 文件。 您可以使用此接口通过调用 IPC 方法调用来发送结算请求。

要获取 AIDL 文件,请执行以下操作:

  1. 打开 Android SDK 管理器
  2. 在 SDK 管理器中,展开 Extras 部分。
  3. 选择 Google Play Billing Library
  4. 点击 Install packages 完成下载。

IInAppBillingService.aidl 文件将安装到 <sdk>/extras/google/play_billing/

要将 AIDL 添加到您的项目,请执行以下操作:

  1. 首先,下载 Google Play Billing Library 到您的 Android 项目:
    1. 选择 Tools > Android > SDK Manager
    2. Appearance & Behavior > System Settings > Android SDK 下面,选择 SDK Tools 标签以选择并下载 Google Play Billing Library
  2. 接下来,复制 IInAppBillingService.aidl 文件到您的项目。
    • 如果您使用的是 Android Studio,请执行以下操作:
      1. 导航至 Project 工具窗口中的 src/main
      2. 选择 File > New > Directory,然后在 New Directory 窗口中输入 aidl,再选择 OK
      3. 选择 File > New > Package,然后在 New Package 窗口中输入 com.android.vending.billing,再选择 OK
      4. 使用您的操作系统文件资源管理器,导航至 <sdk>/extras/google/play_billing/,复制 IInAppBillingService.aidl 文件,然后将其粘贴到项目中的 com.android.vending.billing 软件包。
    • 如果您在非 Android Studio 环境中开发,请执行以下操作:创建目录 /src/com/android/vending/billing,并将 IInAppBillingService.aidl 文件复制到此目录。 将 AIDL 文件添加到您的项目中并使用 Gradle 工具构建项目,从而生成 IInAppBillingService.java 文件。
  3. 开发您的应用。您会在项目的 /gen 目录中看到名为 IInAppBillingService.java 的生成文件。

更新您的应用清单

应用内购买结算依赖于 Google Play 应用,后者将处理应用与 Google Play 服务器之间的所有通信。 要使用 Google Play 应用,您的应用必须请求适当的权限。 您可以通过将 com.android.vending.BILLING 权限添加到 AndroidManifest.xml 文件执行此操作。 如果您的应用未声明应用内购买结算权限,但试图发送结算请求,Google Play 将拒绝请求并使用错误响应。

要为您的应用授予必要的权限,请在 AndroidManifest.xml 文件中添加以下代码行:

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

创建 ServiceConnection

您的应用必须拥有 ServiceConnection 才能实现应用与 Google Play 之间的通信。 您的应用至少需要执行以下操作:

  • 绑定到 IInAppBillingService
  • 发送结算请求(作为 IPC 方法调用)至 Google Play 应用。
  • 处理每个结算请求返回的同步响应消息。

绑定到 InAppBillingService

要在 Google Play 上与应用内购买结算服务建立连接,请实现 ServiceConnection,以便将您的 Activity 绑定到 IInAppBillingService。 建立连接后,重写 onServiceDisconnectedonServiceConnected 方法以获取 IInAppBillingService 实例的引用。

IInAppBillingService mService;

ServiceConnection mServiceConn = new ServiceConnection() {
   @Override
   public void onServiceDisconnected(ComponentName name) {
       mService = null;
   }

   @Override
   public void onServiceConnected(ComponentName name,
      IBinder service) {
       mService = IInAppBillingService.Stub.asInterface(service);
   }
};

在您 Activity 的 onCreate 方法中,通过调用 bindService 方法执行绑定。 向方法传递引用应用内购买结算服务的 Intent 和您创建的一个 ServiceConnection 实例,并明确地将 Intent 的目标软件包名称设置为 com.android.vending — Google Play 应用的软件包名称。

注意:要保护结算交易的安全性,请始终确保使用下面示例中所示的 setPackage() 明确地将 Intent 的目标软件包名称设置为 com.android.vending。 明确地设置软件包名称能够确保只有 Google Play 应用可以处理来自您的应用的结算请求,从而防止其他应用拦截这些请求。

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Intent serviceIntent =
      new Intent("com.android.vending.billing.InAppBillingService.BIND");
  serviceIntent.setPackage("com.android.vending");
  bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
}

现在,您可以使用 mService 引用来与 Google Play 服务通信。

重要说明:完成您的 Activity 后,请务必与应用内购买结算服务解除绑定。 如果不解除绑定,开启的服务连接会导致您的设备性能下降。 此示例说明了如何通过重写 Activity 的 onDestroy 方法对到应用内购买结算的服务连接 mServiceConn 执行解除绑定操作。

@Override
public void onDestroy() {
    super.onDestroy();
    if (mService != null) {
        unbindService(mServiceConn);
    }
}

如需了解绑定到 IInAppBillingService 的服务连接的完整实现,请参阅出售应用内商品培训课程和相关示例。

发起应用内购买结算请求

将应用连接到 Google Play 后,您可以对应用内商品发送购买请求。 Google Play 为用户进入他们的付款方式提供了一个结账界面,这样您的应用就无需直接处理付款交易。 在商品被用户购买后,Google Play 会识别用户拥有此商品,并在此商品被消耗前阻止用户购买具有相同商品 ID 的另一商品。 您可以控制如何在应用中消耗商品,并通知 Google Play 该商品可供再次购买。 您也可以查询 Google Play,以便快速地检索用户的购买列表。 这样十分有用,例如,非常适合您希望在用户启动应用时恢复用户购买的情况。

查询可供购买的商品

在您的应用中,可以使用 In-app Billing Version 3 API 从 Google Play 查询商品详情。 要将请求传递至应用内购买结算服务,首先需要创建一个包含商品 ID 字符串 ArrayListBundle,该字符串带有键“ITEM_ID_LIST”,每个字符串是可购买商品的商品 ID。

ArrayList<String> skuList = new ArrayList<String> ();
skuList.add("premiumUpgrade");
skuList.add("gas");
Bundle querySkus = new Bundle();
querySkus.putStringArrayList(“ITEM_ID_LIST”, skuList);

要从 Google Play 检索此信息,请在 In-app Billing Version 3 API 上调用 getSkuDetails 方法,然后将 In-app Billing API 版本(“3”)、发起调用的应用的软件包名称、商品类型(“应用内”)以及您创建的 Bundle 传递给方法。

Bundle skuDetails = mService.getSkuDetails(3,
   getPackageName(), "inapp", querySkus);

如果请求成功,返回的 Bundle 将包含响应代码 BILLING_RESPONSE_RESULT_OK (0)。

警告:请不要在主线程上调用 getSkuDetails 方法。 调用此方法会触发网络请求,进而阻塞主线程。 请创建单独的线程并从该线程内部调用 getSkuDetails 方法。

要从 Google Play 查看所有可能的响应代码,请参阅应用内购买结算参考

查询结果将保存在带有键 DETAILS_LIST 的字符串 ArrayList 中。购买信息存储在 JSON 格式的字符串中。 要查看返回的商品类型详细信息,请参阅应用内购买结算参考

在此示例中,您将从之前代码段返回的 skuDetails Bundle 中检索您的应用内商品的价格。

int response = skuDetails.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList<String> responseList
      = skuDetails.getStringArrayList("DETAILS_LIST");

   for (String thisResponse : responseList) {
      JSONObject object = new JSONObject(thisResponse);
      String sku = object.getString("productId");
      String price = object.getString("price");
      if (sku.equals("premiumUpgrade")) mPremiumUpgradePrice = price;
      else if (sku.equals("gas")) mGasPrice = price;
   }
}

购买商品

要从您的应用发起购买请求,请在应用内购买结算服务上调用 getBuyIntent 方法。 将 In-app Billing API 版本(“3”)、发起调用的应用的软件包名称、要购买商品的商品 ID、商品类型(“应用内”或“订阅”)以及 developerPayload 字符串传递给方法。 developerPayload 字符串用于指定您想要 Google Play 随购买信息一同发送的任何其他参数。

Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(),
   sku, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");

如果请求成功,返回的 Bundle 将包含响应代码 BILLING_RESPONSE_RESULT_OK (0) 和您可以用于开始购买流程的 PendingIntent。 要从 Google Play 查看所有可能的响应代码,请参阅应用内购买结算参考。 接下来,请使用键 BUY_INTENT 从响应 Bundle 中提取 PendingIntent

PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");

要完成购买交易,请调用 startIntentSenderForResult 方法并使用您创建的 PendingIntent。 在此示例中,您将任意值 1001 用于请求代码。

startIntentSenderForResult(pendingIntent.getIntentSender(),
   1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
   Integer.valueOf(0));

Google Play 会将对您 PendingIntent 的响应发送至应用的 onActivityResult 方法。 onActivityResult 方法将获得结果代码 Activity.RESULT_OK (1) 或 Activity.RESULT_CANCELED (0)。要查看响应 Intent 中返回的订单类型信息,请参阅应用内购买结算参考

订单的购买数据是 JSON 格式的字符串,将映射到响应 Intent 中的 INAPP_PURCHASE_DATA 键,例如:

'{
   "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 会为购买生成令牌。此令牌是不透明的字符序列,最长可为 1,000 字符。 将整个令牌传递至其他方法(例如在您消耗购买时,如消耗购买中所述)。 不要省略或者截断此令牌,您必须保存并返回整个令牌。

继续前面的示例,您将从响应 Intent 获得响应代码、购买数据和签名。

@Override
protected void 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 = new JSONObject(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 中包含此令牌。您可以将随机生成的字符串作为令牌。 从 Google Play 接收到购买响应时,请确保检查返回的数据签名、orderIddeveloperPayload 字符串。 为了增强安全性,您应在自己安全的服务器上执行检查。 请确保验证 orderId 为您之前未处理的唯一值,且 developerPayload 字符串与您之前通过购买请求发送的令牌相匹配。

查询已购买商品

要从您的应用检索用户所发起购买的相关信息,请在应用内购买结算版本 3 服务上调用 getPurchases 方法。 将 In-app Billing API 版本(“3”)、发起调用的应用的软件包名称以及商品类型(“应用内”或“订阅”)传递给方法。

Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);

Google Play 服务仅会返回由当前登录设备的用户帐户发起的购买。 如果请求成功,返回的 Bundle 将包含响应代码 0。响应 Bundle 也会包含商品 ID 列表、每个购买的订单详情列表以及每个购买的签名。

为了提升性能,第一次调用 getPurchase 时,应用内购买结算服务仅会返回由用户拥有的最多 700 个商品。 如果用户拥有大量商品,Google Play 会在响应 Bundle 中包含映射到键 INAPP_CONTINUATION_TOKEN 的字符串令牌,以表明可以检索更多的商品。 然后,您的应用可以进行后续 getPurchases 调用,并将此令牌作为参数传递。 Google Play 会继续在响应 Bundle 中返回继续令牌,直到用户拥有的所有商品都发送到您的应用。

如需了解有关由 getPurchases 返回的数据的详细信息,请参阅应用内购买结算参考。 下面的示例说明了如何从响应中检索此数据。

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
}

消耗购买

您可以使用 In-app Billing Version 3 API 跟踪在 Google Play 中购买的应用内商品的所有权。 应用内商品一经购买,就会被视为“被拥有”且无法从 Google Play 购买。 您必须对应用内商品发送消耗请求,然后 Google Play 才能允许再次购买。

重要说明:可以消耗托管的应用内商品,但不能消耗订阅。

如何在应用中使用消耗机制取决于您。通常情况下,您可以对用户想要购买多次、能够提供短期效益的应用内商品实现消耗(例如,游戏中使用的货币或设备)。 您通常不必对仅供购买一次和具有永久效应的应用内商品实现消耗(例如,高级版升级)。

要记录购买消耗,请将 consumePurchase 方法发送到应用内购买结算服务并在标识要移除购买的 purchaseToken 字符串值中传递。 purchaseToken 是由购买请求成功后 Google Play 服务在 INAPP_PURCHASE_DATA 字符串中所返回数据的一部分。 在此示例中,您会将使用 purchaseToken 标识的商品的消耗记录在 token 变量中。

int response = mService.consumePurchase(3, getPackageName(), token);

警告:请不要在主线程上调用 consumePurchase 方法。 调用此方法会触发网络请求,进而阻塞主线程。 请创建单独的线程并从该线程内部调用 consumePurchase 方法。

您负责控制和跟踪如何向用户配置应用内商品。 例如,如果用户购买了游戏内货币,您应使用购买的货币金额更新玩家的库存。

安全性建议:您必须首先发送消耗请求,才能向用户配置可消耗的应用内购买商品。 确保已从 Google Play 接收到成功的消耗请求,然后再配置商品。

实现订阅

启动订阅的购买流程与启动商品的购买流程相似,不同之处是商品类型必须设置为“订阅”。 购买结果会传送至您 Activity 的onActivityResult 方法,与应用内商品的情况完全一样。

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, new Intent(),
       Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
}

要查询有效订阅,请使用 getPurchases 方法,并将商品类型参数设置为“订阅”。

Bundle activeSubs = mService.getPurchases(3, "com.example.myapp",
                   "subs", continueToken);

调用会返回 Bundle,其包含由用户拥有的所有有效订阅。 如果订阅到期且不续订,将不会出现在返回的 Bundle 中。

保证您的应用安全

为了确保发送到您应用的交易信息的完整性,Google Play 会签署包含购买订单响应数据的 JSON 字符串。 Google Play 会使用 Developer Console 中与您的应用关联的私钥来创建此签名。 Developer Console 会为每个应用生成一个 RSA 密钥对。

:要找到此密钥对的公钥部分,请在 Developer Console 中打开应用的详细信息,然后点击 Services & APIs,并查看命名为 Your License Key for This Application 的字段。

由 Google Play 生成的以 Base64 编码的 RSA 公钥为二进制编码,格式为 X.509 subjectPublicKeyInfo DER SEQUENCE。 它与 Google Play 许可使用的公钥相同。

当您的应用接收到带签名的响应后,您可以使用 RSA 密钥对的公钥部分验证签名。通过执行签名验证,您可以检测到被篡改或假冒的响应。 您可以在应用中执行此签名验证步骤;不过,如果您的应用是连接到安全的远程服务器,我们建议您在该服务器上执行签名验证。

如需了解有关安全性和设计的最佳做法的详细信息,请参阅安全性和设计