开发者控制台

实现Android IAP API

实现Android IAP API

为了更好地了解应用内购买(IAP) API,请阅读以下描述,了解Android IAP程序包中包含的类。要了解如何将IAP API集成到Android应用中,请遵循本页面提供的用例和代码示例。在Appstore SDK中包含的消费品IAP示例应用中,可以找到其中许多代码示例。

新手入门,请观看视频教程(附带中文字幕)。有关如何在应用中实现IAP的更多详细信息,请参阅后续章节。

关于Android IAP程序包

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

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

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

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

ResponseReceiver

应用内购买API以异步方式执行其所有活动。应用需要通过ResponseReceiver类从亚马逊应用商店接收广播意图。应用不能直接使用此类,但要让应用能够接收意图,就必须在清单中添加ResponseReceiver条目。以下代码示例展示了如何在Appstore SDK的AndroidManifest.xml文件中添加ResponseReceiver:如果您的应用以Android 12或更高版本为目标,则必须在MainActivityResponseReceiver中显式地将android:exported设置为true

 <application>
 ...
    <activity android:name="com.amazon.sample.iap.entitlement.MainActivity"
              android:label="@string/app_name" android:exported="true" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <receiver android:name="com.amazon.device.iap.ResponseReceiver" android:exported="true"
              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会实现以下方法。必须实现每个方法,回调才能正常运行:

PurchasingListener

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

响应对象

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

要查看Kotlin应用程序中的响应对象的示例,请在此处从GitHub克隆或下载IAP Kotlin示例应用程序:

将IAP API与应用集成

现在您已经详细了解了实现IAP所需的门类,接下来就可以开始在应用中编写IAP代码了。

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

1.创建占位符方法

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

调用从应用的主要活动中调用API的onCreate ()onResume ()方法。这四个调用属于PurchasingService类的一部分,为执行应用内购买奠定了基础。后面的步骤会更详细地介绍如何实现这些调用并提供可用于为您的代码建模的示例代码段。

2.实现并注册PurchasingListener

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

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

  • (必需)注册PurchasingListener

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

  • (可选)检查应用是否在沙盒模式下运行。如果使用Appstore SDK,并且已经为DRM实现了LicensingListener,请使用LicensingService类中的getAppstoreSDKMode方法。如果使用IAP v2.0,请借助PurchasingService.IS_SANDBOX_MODE标记检查应用是否在沙盒模式下运行。此标记在应用的开发过程中非常有用,而且您将使用App Tester在本地进行应用测试。

  • (可选)启用待定购买。此功能允许Amazon Kids中的儿童请求应用内购买,并由家长予以批准或拒绝。在等待家长的响应时,购买将处于挂起状态。有关如何设置待定购买的说明,请参阅实现待定购买

private SampleIapManager sampleIapManager; // 商店购买收据数据(可选)
protected void onCreate(final Bundle savedInstanceState) // 在onCreate中实现PurchasingListener
{
  super.onCreate(savedInstanceState);
  // setupApplicationSpecificOnCreate();
  
  // 使用AppstoreSDK注册ApplicationContext,并启动检索DRM许可证的请求
  // 使用LicensingListener的实现(此处命名为LicensingCallback)
  LicensingService.verifyLicense(this.getApplicationContext(), new LicensingCallback());
  
  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);
  PurchasingService.enablePendingPurchases(); //启用待定购买

  Log.d(TAG, "Appstore SDK Mode: " + LicensingService.getAppstoreSDKMode()); //检查应用是否处于测试模式
}

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()响应。在下列情况下会发送响应:

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

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

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

//...

  PurchasingService.getUserData();

//...

  PurchasingService.getPurchaseUpdates(false);
}

处理getPurchaseUpdates的响应

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

PurchasingListener.onPurchaseUpdatesResponse()回调后,请检查PurchaseUpdatesResponse.getRequestStatus()返回的请求状态。如果RequestStatusSUCCESSFUL,则处理每个收据。您可以使用getReceipts()方法检索关于收据的详细信息。

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

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

   public void onPurchaseUpdatesResponse( final PurchaseUpdatesResponse response) {
     //...
     // 处理收据
     switch (response.getRequestStatus()) {
       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); // 触发PurchasingListener.onProductDataResponse()

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

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

请求成功

如果RequestStatusSUCCESSFUL,则检索以应用中显示的SKU为键的产品数据图。如果RequestStatusSUCCESSFUL,但存在不可用的SKU,则调用ProductDataResponse.getUnavailableSkus(),以检索无效SKU的产品数据并阻止用户购买这些产品。

如果希望在应用内显示IAP图标,则必须编辑AndroidManifest.xml文件,以添加android.permission.INTERNET权限。

产品数据图包含以下值:

字段 数据类型 描述
sku 字符串 产品的库存单位(SKU)。
title 字符串 产品的本地化标题。
description 字符串 产品的本地化描述。
smallIconUrl 字符串 产品的小图标URL。
productType 字符串 产品类型。有效值: 消费品权利订阅
coinsRewardAmount int 客户购买本产品后可获得的奖励亚马逊硬币数量。
freeTrialPeriod 字符串 订阅期限的免费试用期。仅当配置了免费试用且客户符合条件时才返回。
subscriptionPeriod 字符串 SKU的订阅期限。仅针对有期限的SKU返回。有效值: WeeklyBiWeeklyMonthlyBiMonthlyQuarterlySemiAnnuallyAnnually
promotions List<Promotion> 客户有资格享受的促销的详细信息。仅针对有期限的SKU返回。有关Promotion对象的详细信息,请参见下表。有关如何设置促销定价的信息,请参阅设置促销定价
price 字符串 产品的本地化价格(针对订阅商品的子SKU)。

Promotion对象包含以下字段:

字段 数据类型 描述
promotionType 字符串 促销类型。有效值: 试销
promotionPlans List<PromotionalPlan> 有关促销的价格和计费周期的详细信息。有关PromotionalPlan对象的详细信息,请参见下表。

PromotionalPlan对象包含以下字段:

字段 数据类型 描述
promotionPrice 字符串 以本地化格式显示的促销期间有期限SKU的价格。
promotionPricePeriod 字符串 促销的每个计费周期的持续时间。有效值: WeeklyBiWeeklyMonthlyBiMonthlyQuarterlySemiAnnuallyAnnually
promotionPriceCycles int 计费周期数目。

要查看不同订阅用例的产品数据图示例,请单击以下按钮。示例为JSON格式。

请求失败

如果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:

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

Last updated: 2023年12月6日