开发人员控制台

集成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:​ 从亚马逊应用商店接收广播意图类。
  • PurchasingService:​ 通过亚马逊应用商店发出请求类。
  • 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类从亚马逊应用商店接收广播意图。不能在您的应用中直接使用此类,但要使应用接收意图,必须将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代码了。

本节中的代码段来自开发工具包附带的Consumable IAP(消费品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,  "向亚马逊验证SKU" );
   }

调用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,  "不可用SKU:" + s);
         }

         final Map <string,>products = response.getProductData();
         for ( final String key : products.keySet()) {
           Product product = products.get(key);
           Log.v(TAG, String.format( "产品:%s\n 类型:%s\n SKU:%s\n 价格:%s\n 描述:%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("无法验证购买,请稍后重试。");
                    return;
                }
                if (receiptAlreadyFulfilled(receipt.getReceiptId(), userData)) {
                    // 如果之前已履行收据,则只需再次通知亚马逊
                    // 应用商店收据已履行。
                    PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
                    return;
                }
    
                grantConsumablePurchase(receipt, userData);
            }
            return;
        } catch (final Throwable e) {
            mainActivity.showMessage("无法完成购买,请稍后重试。");
        }
    }
    

8.向用户授予商品

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

同样要验证SKU:

  • 如果SKU可用,则履行商品并使用状态FULFILLED调用notifyFulfillment。完成此步骤后,亚马逊应用商店将不再尝试向应用发送购买收据。
  • 如果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, "收据中的SKU [" + receipt.getSku() + "]不再有效");
            // 如果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, "已成功将购买从PAID更新为FULFILLED,收据id为:" + receipt.getReceiptId());
            //将状态更新到亚马逊应用商店。在收到购买的
            // Fulfilled状态后,亚马逊不会再尝试将
            // 购买收据发送到应用程序
            PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
        } else {
            //更新SQLite数据库中的购买状态失败 - 状态
            // 已更改。
            // 通常意味着同一收据由另一个
            // onPurchaseResponse或onPurchaseUpdatesResponse回调更新。
            // 只需接受错误并将其记录在本示例代码中即可
            Log.w(TAG, "未能将购买从PAID更新为FULFILLED,收据id为:" + receipt.getReceiptId()
                       + ",状态已更改。");
        }

    } catch (final Throwable e) {
        // 不管出于任何原因,如果该应用无法履行购买,
        // 请在此处添加您自己的错误处理代码。
        // 在您下次调用PurchasingService.getPurchaseUpdates API时,
        // 亚马逊会尝试再次发送消费品购买收据
        Log.e(TAG, "未能授予消费品购买,错误为" + 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;
// ...