集成 Android IAP API


集成 Android IAP API

本页介绍如何将 IAP API 集成到您的 Android 应用中,并提供了用例和代码段来补充 IAP v2.0 API 参考文档。请注意,本文档中的许多代码段都可以在消费品 IAP 示例应用中找到。

IAP 电子书 详细描述了创建支持 IAP 的 Android 应用所需的步骤。

关于 Android IAP 程序包

com.amazon.device.iap 程序包提供了用于在 Android 应用中实现应用内购买的类和界面。

此程序包包含以下接口和类:

  • ResponseReceiver:​ 从 Amazon Appstore 接收广播意图的类。
  • PurchasingService:​ 通过 Amazon Appstore 发出请求的类。
  • PurchasingListener​: 接收 PurchasingService 发出的请求的异步响应的接口。

下表显示了 PurchasingService 的请求方法和相关联的 PurchasingListener 响应回调。这些是您在实现 IAP API 时最常用的方法、回调和响应对象:

PurchasingService 方法 PurchasingListener 回调 响应对象
getUserData() onUserDataResponse() UserDataResponse
getPurchaseUpdates() onPurchaseUpdatesResponse() PurchaseUpdatesResponse
getProductData() onProductDataResponse() ProductDataResponse
purchase() onPurchaseResponse() PurchaseResponse
notifyFulfillment()

有关这些类和方法的更多信息,请参阅 IAP v2.0 API 参考文档

ResponseReceiver

应用内购买 API 以异步方式执行其所有活动。您的应用需要通过 ResponseReceiver 类从 Amazon Appstore 接收广播意图。此类从不在您的应用中直接使用,但要使您的应用接收意图,您必须将 ResponseReceiver 条目添加到 AndroidManifest.xml 文件中。以下代码段显示了如何将 ResponseReceiver 添加到 IAP v2.0 的 AndroidManifest.xml 文件中:

 <application>
  ...
  <receiver android:name = "com.amazon.device.iap.ResponseReceiver"
      android:permission = "com.amazon.inapp.purchasing.Permission.NOTIFY" >
    <intent-filter>
      <action android:name = "com.amazon.inapp.purchasing.NOTIFY" />
    </intent-filter>
  </receiver>
  ...
 </application>

PurchasingService

使用 PurchasingService 类来检索各类信息,执行购买,并将购买的履行情况通知亚马逊。PurchasingService 实现以下方法:

  • registerListener(PurchasingListener purchasingListener): 在 PurchasingService 类中调用其他方法之前先调用此方法。
  • getUserData():​ 调用此方法来检索当前登录的用户的特定于应用的 ID 和市场。例如,如果用户切换账户或者多个用户在同一设备上访问您的应用,则此调用将帮助您确保您检索的收据针对当前用户账户。
  • getPurchaseUpdates(boolean reset):​ GetPurchaseUpdates 跨所有设备检索所有订阅和权利购买。只能从在其中购买消费品的设备检索消费品购买。GetPurchaseUpdates 只检索未履行的和已取消的消费品购买。亚马逊建议您保留返回的 PurchaseUpdatesResponse 数据并仅向系统查询更新。响应会分页。
  • getProductData(java.util.Set skus):​ 调用此方法来检索一组 SKU 的项目数据以显示在您的应用中。
  • purchase(java.lang.String sku):​ 调用此方法以发起特定 SKU 的购买。
  • notifyFulfillment(java.lang.String receiptId, FulfillmentResult fulfillmentResult): 调用此方法以发送指定 receiptIdFulfillmentResultFulfillmentResult 的可能值为 FULFILLED 或 UNAVAILABLE。

PurchasingListener

实现 PurchasingListener 接口来处理异步回调。因为您的 UI 线程调用这些回调,所以不要在 UI 线程中处理长时间运行的任务。您的 PurchasingListener 实例应实现以下方法:

  • onUserDataResponse(UserDataResponse userDataResponse): 在调用 getUserData() 后调用。确定当前登录用户的 UserIdmarketplace
  • onPurchaseUpdatesResponse(PurchaseUpdatesResponse purchaseUpdatesResponse): 在调用 getPurchaseUpdates(boolean reset) 后调用。检索购买历史记录。亚马逊建议您保留返回的 PurchaseUpdatesResponse 数据并仅向系统查询更新。
  • onProductDataResponse(ProductDataResponse productDataResponse): 在调用 getProductDataRequest(java.util.Set skus) 后调用。检索有关要从您的应用销售的 SKU 的信息。在 onPurchaseResponse() 中使用有效的 SKU。
  • onPurchaseResponse(PurchaseResponse purchaseResponse): 在调用 purchase(String sku) 后调用。用于确定购买的状态。

ResponseObjects

您通过 PurchasingService 发出的每个调用都会使 PurchasingListener 收到相应响应。其中每个响应都使用响应对象:

  • UserDataResponse 提供当前登录用户的特定于应用的 UserIdmarketplace
  • PurchaseUpdatesResponse:​ 提供收据的分页列表。收据是无序的。
  • ProductDataResponse:​ 提供以 SKU 为键的项目数据。getUnavailableSkus() 方法列出不可用的任何 SKU。
  • PurchaseResponse:​ 提供在您的应用内发起的购买的状态。请注意,PurchaseResponse.RequestStatus 结果为 FAILED 可能只表示用户在完成之前取消了购买。

将 IAP API 与应用集成

既然您稍微详细地了解了实现 IAP 所需的类,您可以开始在应用中编写 IAP 代码。

本节中的代码段来自 SDK 附带的消费品 IAP 示例应用。

1.创建占位符方法

要整理您的代码,请使用占位符或无存根代码段在以下位置调用以下方法:

  • 在您的 onCreate() 方法中调用 registerListener()。
  • 在您的 onResume() 方法中调用 getUserData()
  • 在您的 onResume() 方法中调用 getPurchaseUpdates()
  • 在您的 onResume() 方法中调用 getProductData()

这四个调用属于 PurchasingService 类的一部分,用于为执行应用内购买提供基础。后面的步骤将更详细地介绍如何实现这些调用并提供可用于为您的代码建模的示例代码段。

2.实现并注册 PurchasingListener

在您的代码中,实现并注册 PurchasingListener,以便您的应用可以侦听并处理由 ResponseReceiver 触发的回调。以下是您在上一步中设置的调用:

private SampleIapManager sampleIapManager;
protected void onCreate(final Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setupApplicationSpecificOnCreate();
  setupIAPOnCreate();
}

private void setupIAPOnCreate() {
  sampleIapManager = new SampleIapManager(this);

  final SamplePurchasingListener purchasingListener = new SamplePurchasingListener(sampleIapManager);
  Log.d(TAG, "onCreate: registering PurchasingListener");

  PurchasingService.registerListener(this.getApplicationContext(), purchasingListener);

  Log.d(TAG, "IS_SANDBOX_MODE:" + PurchasingService.IS_SANDBOX_MODE);
}

除了注册 PurchasingListener 外,此示例代码还执行以下两个可选任务:

  • 创建新的 SampleIapManager 实例来存储与购买收据相关的数据。这是一个可选步骤;但是,您的应用应将购买收据数据存储在您可以在其中访问这些数据的某个位置。是选择使用数据库还是将这些数据存储在内存中由您决定。
  • 通过检查 PurchasingService.IS_SANDBOX_MODE 来检查应用是否在沙盒模式下运行。当应用正在开发中时,此标志很有用,并且您将使用 App Tester 在本地测试您的应用。

3.获取用户信息

通过在 onResume() 中实现 getUserData () 来检索有关当前用户的信息(用户 ID 和市场):

// ...
private String currentUserId =  null ;
private String currentMarketplace =  null ;

// ...

public void onUserDataResponse( final UserDataResponse response) {

 final UserDataResponse.RequestStatus status = response.getRequestStatus();

 switch (status) {
   case SUCCESSFUL:
	 currentUserId = response.getUserData().getUserId();
	 currentMarketplace = response.getUserData().getMarketplace();
	 break ;

   case FAILED:
   case NOT_SUPPORTED:
	 // 正常失败。
	 break ;
 }
}

请注意,此示例还将用户 ID 和市场保存到内存中以供应用可能以后使用。

4, 实现 getPurchaseUpdates 方法

onResume() 调用中,实现 getPurchaseUpdates() 调用,您可以调用它来检索自上次调用此方法之后用户进行的所有购买交易。

  • reset 设置为 false 可返回自上次调用 getPurchaseUpdates() 之后的购买历史记录的分页响应。此组合检索用户的待定消费品、权利和订阅购买的收据。(亚马逊建议在几乎所有情况下使用此方法。)
  • 仅当您希望检索用户的全部购买历史记录并将其存储在某个位置(如服务器端数据缓存中)或将一切都保存在内存中时,才将 reset 设置为 true

无论 reset 标志的值是什么,请注意 getPurchaseUpdates() 的以下行为:

  • getPurchaseUpdates() 调用始终返回所有未履行的消费品购买。
  • getPurchaseUpdates() 调用仅在极少情况下返回已履行的购买。例如,如果应用在履行之后但在通知亚马逊之前崩溃,或在履行之后亚马逊端出现问题,则 getPurchaseUpdates() 会返回已履行的消费品购买。在这些情况下,您需要删除重复的收据以免过度履行项目。在您交付项目时,记录您已交付的地方,不要再次交付,即使您收到第二个收据也是如此。

getPurchaseUpdates() 返回的响应将触发 PurchasingListener.onPurchaseUpdatesResponse() 回调。

@Override
protected void onResume() {
  super.onResume();

//...

  PurchasingService.getUserData();

//...

  PurchasingService.getPurchaseUpdates(false);
}

现在,处理响应:

当触发 PurchasingListener.onPurchaseUpdatesResponse() 回调时,检查 PurchaseUpdatesResponse.getPurchaseUpdatesRequestStatus() 返回的请求状态。如果 requestStatus 为 SUCCESSFUL,则处理每个收据。

要处理分页,请获取 PurchaseUpdatesResponse.hasMore() 的值。如果 PurchaseUpdatesResponse.hasMore() 返回 true,则对 getPurchaseUpdates() 进行递归调用,如以下示例代码所示:

public class MyPurchasingListener  implements PurchasingListener {
   boolean reset =  false ;
   //...

   public void onPurchaseUpdatesResponse( final PurchaseUpdatesResponse response) {
     //...
     // 处理收据
     switch (response.getPurchaseUpdatesRequestStatus()) {
       case SUCCESSFUL:
         for ( final Receipt receipt : response.getReceipts()) {
           // 处理收据
         }
         if (response.hasMore()) {
           PurchasingService.getPurchaseUpdates(reset);
         }
         break ;
       case FAILED:
         break ;
     }
   }
   //...
}

5.实现 getProductData 方法

还在 onResume() 方法中,调用 getProductData() 来验证您的 SKU,以便用户的购买不会因 SKU 无效而意外失败。

以下示例代码向亚马逊验证应用的消费品、权利和订阅项目的 SKU:

protected void onResume() {
   super.onResume();

   // ...

   final Set <string>productSkus =  new HashSet<string>();
   productSkus.add( "com.amazon.example.iap.consumable" );
   productSkus.add( "com.amazon.example.iap.entitlement" );
   productSkus.add( "com.amazon.example.iap.subscription" );
   PurchasingService.getProductData(productSkus);

   Log.v(TAG,  "Validating SKUs with Amazon" );
   }

调用 PurchasingService.getProductData() 方法会触发 PurchasingListener.onProductDataResponse() 回调。检查 ProductDataResponse.getRequestStatus() 中返回的请求状态,并且仅销售经过此调用验证的项目或 SKU。

请求成功

如果 requestStatusSUCCESSFUL,则检索以应用中显示的 SKU 为键的产品数据图。产品数据图包含以下值:

  • 产品类型
  • 图标 URL
  • 本地化价格(针对订阅项目的子 SKU)
  • 标题
  • 描述
  • SKU

请注意,如果您希望在应用中显示 IAP 图标,则需要编辑 AndroidManifest.xml 文件以包括 android.permission.INTERNET 权限。

此外,如果 requestStatusSUCCESSFUL,但具有不可用的 SKU,则调用 PurchaseUpdatesResponse.getUnavailableSkus() 来检索无效 SKU 的产品数据并阻止应用的用户购买这些产品。

请求失败

如果 requestStatusFAILED,则禁用应用中的 IAP 功能,如以下示例代码所示:

public class MyPurchasingListener  implements PurchasingListener {
   //  ...

   public void onProductDataResponse( final ProductDataResponse response) {
     switch (response.getRequestStatus()) {
       case SUCCESSFUL:
         for ( final String s : response.getUnavailableSkus()) {
           Log.v(TAG,  "Unavailable SKU:" + s);
         }

         final Map <string,>products = response.getProductData();
         for ( final String key : products.keySet()) {
           Product product = products.get(key);
           Log.v(TAG, String.format( "Product: %s\n Type: %s\n SKU: %s\n Price: %s\n Description: %s\n" , product.getTitle(), product.getProductType(), product.getSku(), product.getPrice(), product.getDescription()));
         }
         break ;

       case FAILED:
         Log.v(TAG,  "ProductDataRequestStatus: FAILED" );
         break ;
     }
   }

   //  ...
}

6.实现代码以进行购买

编写代码来执行购买。虽然此特定示例执行消费品的购买,您应该还能够使用类似代码购买订阅和权利。

来自 MainActivity 的以下代码段调用 PurchasingService.purchase() 来初始化购买。在消费品示例应用中,在应用用户点击 Buy Orange(购买橙子)按钮时执行此方法:

  public void onBuyOrangeClick(final View view) {
     final RequestId requestId = PurchasingService.purchase(MySku.ORANGE.getSku());
     Log.d(TAG, "onBuyOrangeClick: requestId (" + requestId + ")");
  }

接下来,实现 SamplePurchasingListener.onPurchaseResponse() 回调。在此代码段中,SampleIapManager.handleReceipt() 处理实际购买:

public void onPurchaseResponse(final PurchaseResponse response) {
	switch (status) {
		// ...
		case SUCCESSFUL:
			final Receipt receipt = response.getReceipt();
			iapManager.setAmazonUserId(response.getUserData().getUserId(), response.getUserData().getMarketplace());
			Log.d(TAG, "onPurchaseResponse: receipt json:" + receipt.toJSON());
			iapManager.handleReceipt(receipt, response.getUserData());
			iapManager.refreshOranges();
			break;
	}
}

7.履行购买并处理购买收据

履行购买并处理购买收据。在设计您自己的应用时,请记住,您很可能将实现某种履行引擎来在一个地方处理所有这些步骤。

如果可购买项目是订阅,请牢记以下有关 receiptId 的值的指南。

*   如果订阅是连续的且从未在任何时候取消,则应用将仅收到该订阅/客户的一个收据。 

*   如果订阅不是连续的,例如客户未自动续订,让订阅终止,然后一个月后再次订阅,则应用将收到多个收据。

在履行项目之前,让您的后端服务器使用亚马逊的 Receipt Verification Service (RVS) 验证 receiptId 来验证购买收据。亚马逊提供了 RVS 沙盒环境和 RVS 生产环境。请参阅 Receipt Verification Service (RVS) 文档,了解如何将 RVS 沙盒和您的服务器都设置为使用 RVS。* 在开发过程中,请使用 RVS 沙盒环境验证由 App Tester 生成的收据。* 在生产中,请使用 RVS 生产终端节点。

在以下示例中,SampleIapManager 中的 handleConsumablePurchase() 方法检查是否已取消收据。

  • 如果已取消收据,并且以前已履行项目,则调用 revokeConsumablePurchase() 方法来撤销购买。
  • 如果未取消收据,则使用 RVS 从您的服务器验证收据,然后调用 grantConsumablePurchase() 来履行购买。

    public void handleConsumablePurchase(final Receipt receipt, final UserData userData) {
        try {
            if (receipt.isCanceled()) {
                revokeConsumablePurchase(receipt, userData);
            } else {
                // 我们强烈建议您验证收据服务器端
                if (!verifyReceiptFromYourService(receipt.getReceiptId(), userData)) {
                    // 如果无法验证购买,
                    // 请向客户显示相关的错误消息。
                    mainActivity.showMessage("Purchase cannot be verified, please retry later.");
                    return;
                }
                if (receiptAlreadyFulfilled(receipt.getReceiptId(), userData)) {
                    // 如果之前已履行收据,则只需通知 Amazon
                    // Appstore 已再次履行收据。
                    PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
                    return;
                }
    
                grantConsumablePurchase(receipt, userData);
            }
            return;
        } catch (final Throwable e) {
            mainActivity.showMessage("Purchase cannot be completed, please retry");
        }
    }
    

8.向用户授予项目

要向用户授予项目,请为购买创建购买记录并将该记录存储在持久位置。

同样验证 SKU:

  • 如果 SKU 可用,则履行项目并调用具有状态 FULFILLED 的 notifyFulfillment。完成此步骤后,Amazon Appstore 将不再尝试向应用发送购买收据。
  • 如果 SKU 不可用,则调用具有状态 UNAVAILABLE 的 notifyFulfillment
private void grantConsumablePurchase(final Receipt receipt, final UserData userData) {
    try {
        // 以下示例代码是一个简单实现,请
        // 实现您自己的授予逻辑,使之具有线程安全、事务性和健壮性
        // 的特点

        // 在您的应用/您的服务器中创建购买信息,
        // 并向客户授予购买 - 在本例中将为客户
        // 提供一个橙子
        createPurchase(receipt.getReceiptId(), userData.getUserId());
        final MySku mySku = MySku.fromSku(receipt.getSku(), userIapData.getAmazonMarketplace());
        // 验证 SKU 是否仍然适用。
        if (mySku == null) {
            Log.w(TAG, "The SKU [" + receipt.getSku() + "] in the receipt is not valid anymore ");
            // 如果 SKU 不再适用,请调用
            // 状态为“UNAVAILABLE”的 PurchasingService.notifyFulfillment
            updatePurchaseStatus(receipt.getReceiptId(), null, PurchaseStatus.UNAVAILABLE);
            PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.UNAVAILABLE);
            return;
        }

        if (updatePurchaseStatus(receipt.getReceiptId(), PurchaseStatus.PAID, PurchaseStatus.FULFILLED)) {
            // 更新 SQLite 数据库中的购买状态成功
            userIapData.setRemainingOranges(userIapData.getRemainingOranges() + 1);
            saveUserIapData();
            Log.i(TAG, "Successfuly update purchase from PAID->FULFILLED for receipt id " + receipt.getReceiptId());
            // 将状态更新到 Amazon Appstore。在收到购买的
            // “已履行”状态后,亚马逊不会再尝试将
            // 购买收据发送到应用程序
            PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
        } else {
            // 更新 SQLite 数据库中的购买状态失败 - 状态
            // 已更改。
            // 通常意味着同一收据由另一个
            // onPurchaseResponse 或 onPurchaseUpdatesResponse 回调更新。
            // 只需接受错误并将其记录在本示例代码中即可
            Log.w(TAG, "Failed to update purchase from PAID->FULFILLED for receipt id " + receipt.getReceiptId()
                       + ", Status already changed.");
        }

    } catch (final Throwable e) {
        // 如果该应用出于任何原因无法履行购买,
        // 请在此处添加您自己的错误处理代码。
        // 在您下次调用 PurchasingService.getPurchaseUpdates API 时,
        // 亚马逊会尝试再次发送消费品购买收据
        Log.e(TAG, "Failed to grant consumable purchase, with error " + e.getMessage());
    }
}

9.处理收据

处理收据。请注意,此步骤是 getPurchaseUpdates() 的结果,不是调用 purchase(SKU) 的结果。调用 SampleIapManager.handleReceipt 方法来处理作为 PurchaseUpdatesResponse 的一部分返回的所有收据。

public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse response) {
// ....
switch (status) {
case SUCCESSFUL:
	iapManager.setAmazonUserId(response.getUserData().getUserId(), response.getUserData().getMarketplace());
	for (final Receipt receipt : response.getReceipts()) {
		iapManager.handleReceipt(receipt, response.getUserData());
	}
	if (response.hasMore()) {
		PurchasingService.getPurchaseUpdates(false);
	}
	iapManager.refreshOranges();
	break;
// ...