开发者控制台

集成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类中,先调用registerListener,然后再调用其他方法。通过PurchasingListener对象,应用可以侦听并处理由ResponseReceiver触发的回调。在onCreate方法中调用registerListener
  • getUserData(): 调用此方法来检索当前登录用户应用特定的ID和市场。例如,如果同一用户切换账户或者多个用户在同一设备上访问您的应用,则此调用将帮助确保您检索的收据是针对当前用户账户的。在onResume方法中调用getUserData
  • getPurchaseUpdates(boolean reset)getPurchaseUpdates可以跨所有设备检索所有订阅和权利购买。消费品购买仅可在购买消费品时使用的设备上检索。getPurchaseUpdates仅可检索未履行和已取消的消费品购买。亚马逊建议保留返回的PurchaseUpdatesResponse数据并仅向系统查询更新。响应会分页。在onResume方法中调用getPurchaseUpdates
  • getProductData(java.util.Set skus):​ 调用此方法可检索一组SKU的商品数据并显示在您的应用中。在onResume方法中调用getProductData
  • purchase(java.lang.String sku):​ 调用此方法可发起特定SKU的购买。在OnResume方法中调用purchase
  • notifyFulfillment(java.lang.String receiptId, FulfillmentResult fulfillmentResult): 调用此方法以发送指定receiptIdFulfillmentResultFulfillmentResult可能的值为“FULFILLED”或“UNAVAILABLE”。在OnResume方法中调用notifyFulfillment

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附带的Consumable IAP(消费品IAP)示例应用。

1.创建占位符方法

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

  • onCreate()方法中调用registerListener()
  • onResume()方法中调用getUserData()
  • onResume()方法中调用getPurchaseUpdates()
  • onResume()方法中调用getProductData()

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

2.实现并注册PurchasingListener

onCreate方法中实现并注册PurchasingListener,您的应用就可以侦听并处理由ResponseReceiver触发的回调。

下列代码片段将执行以下任务:

  • (必需)注册PurchasingListener

  • (可选)创建新的SampleIapManager实例来存储购买收据相关数据。这是可选步骤;但是,您的应用应将购买收据数据存储在您可以访问的位置。您可以选择使用数据库进行储存,或选择将这些数据存储在内存中。

  • (可选)通过检查PurchasingService.IS_SANDBOX_MODE来确认应用是否在沙盒模式下运行。此标记在应用的开发过程中非常有用,而且您将使用App Tester在本地测试应用。

private SampleIapManager sampleIapManager; //商店购买收据数据(可选)
protected void onCreate(final Bundle savedInstanceState) //在onCreate中实现PurchasingListener
{
  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); //检查应用是否处于测试模式
}

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方法

getPurchaseUpdates方法会检索自上次调用该方法之后用户完成的所有购买交易。在onResume方法中调用getPurchaseUpdates,以确保获取最新的更新。

该方法会接受名为reset布尔值参数。根据您想检索的信息量,将该值设为truefalse

  • false - 返回自上次调用getPurchaseUpdates()之后的购买记录的分页响应。检索用户的待定消费品、权利和订阅购买的收据。亚马逊建议在大部分情况下使用此方法。
  • true - 检索用户的完整购买记录。您需要将数据存储在某个位置(例如服务器端数据缓存),或将所有数据保存在内存中。

getPurchaseUpdates响应

在大多数情景中,您都将收到getPurchaseUpdates响应。在下列情况下会发送响应:

  • 订阅和权利: 对于订阅和权利购买,您始终可以收到收据。
  • 消费品​: 如果消费品交易成功,并且您将履行信息记录在亚马逊系统中(例如,调用notifyFullfilment),则您不会收到收据。在所有其他情况下,您都会收到消费品的收据。该方法仅会在极少数情况下返回已履行的消费品购买,例如应用在履行之后但在通知亚马逊之前崩溃,或在履行之后亚马逊端出现问题。在这些情况下,需要删除重复的收据,以免过度履行商品。交付商品后,要做好记录表明您已完成交付。即使再次收到收据,也不要再次交付。
  • 已取消购买: 对于所有类型的已取消购买(订阅、权利或消费品),您都会收到收据。

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

@Override
protected void onResume() //仅调用onResume中的getPurchaseUpdates
{
  super.onResume();

//...

  PurchasingService.getUserData();

//...

  PurchasingService.getPurchaseUpdates(false);
}

处理getPurchaseUpdates的响应

下一步,您需要处理该响应。

触发PurchasingListener.onPurchaseUpdatesResponse()回调后,请检查PurchaseUpdatesResponse.getPurchaseUpdatesRequestStatus()返回的请求状态。如果requestStatus为“SUCCESSFUL”,则处理每个收据。您可以使用getReceiptStatus方法检索关于收据的详细信息。

要处理分页,请获取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 ;
     }
   }
   //...
}

处理收据

处理收据。调用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;
}
// ...
}              

5.实现getProductData方法

onResume()方法中同样调用getProductData()。该方法会验证您的SKU,从而使用户的购买不会因SKU无效而意外失败。

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

protected void onResume() //仅在onResume中验证产品SKU
{
   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); //触发Purchase.Listener.onProductDataResponse()

   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,  "不可用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.处理购买收据并履行购买

现在,您可以处理购买收据,并可在收据已经验证的情况下履行购买。在设计您自己的应用时,请记住:您很可能需要实现某种履行引擎,以便在同一个位置处理所有这些步骤。

在履行商品之前,让后端服务器通过亚马逊的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("无法完成购买,请稍后重试。");
        }
    }

订阅购买准则

如果可购买商品是订阅,请牢记以下有关receiptId值的准则。

  • 如果订阅是连续的,并且从未在任何时候取消过,则对于该订阅/客户,应用仅会收到一个收据。
  • 如果订阅不是连续的,例如客户未自动续订,让订阅期满终止,然后一个月后再次订阅,则应用会收到多个收据。

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());
    }
}