as

Settings
Sign out
Notifications
Alexa
亚马逊应用商店
AWS
文档
Support
Contact Us
My Cases
开发
测试
应用发布
盈利
用户参与
设备规格
资源

实现Appstore SDK IAP

实现Appstore SDK IAP

为了更好地了解Appstore SDK应用内购买 (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()

清单要求

如果您想使用应用内购买API,并且您的应用以Android API级别30或更高级别为目标,则必须在AndroidManifest.xml文件中定义应用需要查询的程序包列表。要想能够查询Amazon App Tester和亚马逊应用商店,请将以下代码添加到您的清单文件中。

<manifest>
...
  <queries>
    <package android:name="com.amazon.sdktestclient" />
    <package android:name="com.amazon.venezia" />
  </queries>
</manifest>

请务必同时更新您的清单以接收来自ResponseReceiver类的意图。有关详细信息,请参阅ResponseReceiver

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. 创建占位符方法

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

在应用主要活动的onCreate() 方法中调用registerListener()。在应用的主页活动(应用使用的主要活动)的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:注册PurchasingListener");

  PurchasingService.registerListener(this.getApplicationContext(), purchasingListener);
  PurchasingService.enablePendingPurchases(); // 启用待定购买

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

3. 获取用户信息

通过在onResume() 中实现getUserData() 来检索当前用户相关信息(用户ID、市场和居住的国家/地区代码)。

// ...
private String currentUserId = null;
private String currentMarketplace = null;
private String currentCountryCode = 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();
      currentCountryCode = response.getUserData().getCountryCode();
      break;

   case FAILED:
      // 客户未在设备上登录亚马逊,或者
      // 应用和亚马逊应用商店之间存在连接问题。
      // 在此设备上暂时对用户禁用应用内购买,
      // 直到成功检索到有关用户和产品的详细信息。
      // 当getProductData方法成功检索到ProductData时,
      // 会重新启用购买。
      iapManager.disableAllPurchases();
      break;
   case NOT_SUPPORTED:
      // 设备不支持IAP功能。
      // 在此设备上对用户禁用应用内购买。
      iapManager.disableAllPurchases();
      break;
  }
}

请注意,此示例还会将用户ID和市场持久存储到内存中,以供应用日后使用。

4. 实现getPurchaseUpdates方法

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

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

  • false - 返回自上次调用getPurchaseUpdates() 之后的购买记录的分页响应。检索用户的未履行消费品、权利和订阅购买的收据。亚马逊应用商店建议在大部分情况下使用此方法。

  • true - 检索用户的完整购买记录。需要将数据存储在某个位置(例如服务器端数据缓存),或将所有数据保留在内存中。如果您需要用户进行的购买的完整列表,例如当客户想要恢复购买时,或者您检测到您的应用与亚马逊应用商店之间存在一致性问题时,这时请使用true

关于设备缓存的注意事项

getPurchaseUpdates() 方法的行为取决于客户端(设备)缓存中的可用数据。客户在新设备上登录或清除当前设备的缓存后,第一次getPurchaseUpdates() 调用会返回所有收据,而不考虑reset的值。reset设置为false的后续调用仅返回自上次调用以来的最新收据。

某些场景(例如用户在其他设备上登录或用户重置设备)可能会清除客户端设备上的数据。在这些场景中,可能会多次返回同一收据。您的应用必须能够识别新收据和旧收据并正确处理每个收据。

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);
         }
         iapManager.refresh();
         break;
       case FAILED:
         // 如果存在用户数据,FAILED表示亚马逊应用商店端存在问题。
         // 请在一段时间后重试。
         // 对此设备暂时禁用应用内购买,
         // 直到成功检索到有关用户和产品的详细信息。
         // 当getProductData方法成功检索到ProductData时,会重新启用购买。
         iapManager.disableAllPurchases();
         break;
       case NOT_SUPPORTED:
         // 设备不支持IAP功能。
         // 对此设备禁用应用内购买。
         iapManager.disableAllPurchases();
     }
   }
   //...
}

处理收据

处理收据。调用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,  "正在通过亚马逊验证SKU" );
   }

调用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 字符串 产品类型。有效值: CONSUMABLEENTITLEDSUBSCRIPTION
coinsRewardAmount 整数 不再支持。亚马逊硬币于2025年8月20日停用。
freeTrialPeriod 字符串 订阅期限的免费试用期。仅当配置了免费试用且客户符合条件时才返回。
subscriptionPeriod 字符串 SKU的订阅期限。仅针对有期限的SKU返回。有效值: WeeklyBiWeeklyMonthlyBiMonthlyQuarterlySemiAnnuallyAnnually
promotions List<Promotion> 客户有资格享受的促销的详细信息。仅针对有期限的SKU返回。有关Promotion对象的详细信息,请参见下表。有关如何设置促销定价的信息,请参阅设置促销定价
price 字符串 产品的本地化价格(针对订阅商品的子SKU)。

Promotion对象包含以下字段:

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

PromotionalPlan对象包含以下字段:

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

要查看不同订阅用例的产品数据图示例,请单击以下按钮。示例为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);
         }
         // 客户无法购买SKU。
         // 禁用不可用SKU的购买。
         iapManager.disablePurchaseForSkus(response.getUnavailableSkus());
         // 启用所有可用SKU的购买
         iapManager.enablePurchaseForSkus(response.getProductData());
         iapManager.refresh();

         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" );
         // 如果存在用户数据,FAILED表示亚马逊应用商店端存在问题。
         // 请在一段时间后重试。
         // 对此设备暂时禁用应用内购买,
         // 直到成功检索到有关用户和产品的详细信息。
         // 当getProductData方法成功检索到ProductData时,会重新启用购买。
         iapManager.disableAllPurchases();
         break;
       case NOT_SUPPORTED:
         // 设备不支持IAP功能。
         // 对此设备禁用应用内购买。
         iapManager.disableAllPurchases();
     }
   }

   //  ...
}

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.handlePurchase(receipt, response.getUserData());
      iapManager.refresh();
      break;
    case ALREADY_PURCHASED:
      // 客户已经拥有购买商品的有效权利。
      // 应用和亚马逊应用商店不同步。重新同步亚马逊应用商店中的所有购买。
      PurchasingService.getPurchaseUpdates(true);
      break;
    case INVALID_SKU:
      Log.d(TAG,
            "onPurchaseResponse:无效SKU!onProductDataResponse应已将购买按钮禁用。");
      final Set<String> unavailableSkus = new HashSet<String>();
      unavailableSkus.add(response.getReceipt().getSku());
      iapManager.disablePurchaseForSkus(unavailableSkus);
      break;
    case FAILED:
      // 客户在完成购买旅程之前退出
      Log.d(TAG, "onPurchaseResponse:失败,因此从本地储存中删除购买请求");
      iapManager.showPurchaseFailedMessage(response.getReceipt().getSku());
      break;
    case NOT_SUPPORTED:
      Log.d(TAG, "onPurchaseResponse:失败,因此从本地储存中删除购买请求");
      iapManager.showPurchaseFailedMessage(response.getReceipt().getSku());
      // 设备不支持IAP功能。
      // 对此设备禁用应用内购买。
      iapManager.disableAllPurchases();
      break;
	}
}

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

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

在履行商品之前,让后端服务器通过亚马逊的收据验证服务 (RVS) 验证receiptId,从而验证购买所产生的收据。亚马逊提供了RVS沙盒环境和RVS生产环境。请参阅收据验证服务 (RVS) 文档,以了解如何设置RVS沙盒和您的服务器以使用RVS:

  • 在开发过程中,使用RVS沙盒环境来验证App Tester生成的收据。
  • 在生产中,使用RVS生产终端节点。

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

  • 如果收据已取消,并且商品之前已履行,则调用revokePurchase() 方法来撤销购买。
  • 如果客户因为在亚马逊外部进行了购买而已可访问内容,请调用cancelPurchase() 以通知亚马逊无法履行购买。
  • 如果收据未取消,则使用RVS从您的服务器对收据进行验证,然后调用grantPurchase() 来履行购买。
public void handlePurchase(final Receipt receipt, final UserData userData) {
    try {
        if (receipt.isCanceled()) {
            revokePurchase(receipt, userData);
        } else {
            // 亚马逊强烈建议您在服务器端验证收据
            if (!verifyReceiptFromYourService(receipt.getReceiptId(), userData)) {
                // 如果无法验证购买,
                // 请向客户显示相关的错误消息。
                mainActivity.showMessage("无法验证购买,请稍后重试。");
                return;
            }
            if (itemAlreadyPurcahsed(receipt, userData)) {
                    // 如果已经从其他商店购买商品,请调用
                    // 取消购买并向客户显示相关的错误消息。
                    cancelPurchase(receipt, userData);
                    return;
            }
            if (receiptAlreadyFulfilled(receipt.getReceiptId(), userData)) {
                // 如果之前已履行收据,请通知亚马逊
                // 应用商店收据被再次履行。
                PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
                return;
            }
            grantPurchase(receipt, userData);
        }
        return;
    } catch (final Throwable e) {
        mainActivity.showMessage("无法完成购买,请稍后重试。");
    }
}

订阅购买准则

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

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

8. 将履行结果发送给亚马逊并向用户授予商品

通过发送履行结果,您确保亚马逊可以确认用户是否可以访问其付费购买的内容。务必将履行结果传达给亚马逊。使用notifyFulfillment() 方法发送FulfillmentResult

有关发送哪个FulfillmentResult的简明参考,请参阅调用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());
            // 将履行结果发送给亚马逊应用商店。亚马逊收到
            // 购买的履行结果后,亚马逊应用商店停止
            // 向应用发送购买收据的尝试。
            PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
        } else {
            // 更新SQLite数据库中的购买状态失败。
            // 状态已更改。
            // 这通常表示同一收据已由另一个
            // onPurchaseResponse或onPurchaseUpdatesResponse回调更新。
            // 此代码仅记录错误。
            Log.w(TAG, "未能将购买从PAID更新为FULFILLED,收据ID为:" + receipt.getReceiptId()
                       + ",状态已更改。");
        }

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

调用notifyFulfillment的指南

调用notifyFulfillment() 时,请参考以下指南来确定发送哪个FulfillmentResult

满足以下条件时发送FULFILLED

  • 客户已成功创建其账户,并且可以访问您服务上的内容。

出现以下任一情况时发送UNAVAILABLE

  • 客户已经拥有现有账户并订阅了您的服务。
  • 客户没有资格针对您的服务注册账户。

9. 取消购买

要取消购买商品并退款,请将信息更新到持久存储和您的后端服务器,然后使用状态UNAVAILABLE调用notifyFulfillment(),通知亚马逊您无法完成履行。

private void cancelPurchase(final Receipt receipt, final UserData userData) {
  // 更新应用和服务器中的购买信息以确定
  // 客户是否有重复的购买尝试。
  // 使用UNAVAILABLE履行状态,
  // 通知亚马逊无法履行商品
   updatePurchaseStatus(receipt.getReceiptId(), null, PurchaseStatus.UNAVAILABLE);
   PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.UNAVAILABLE);
   return;
}

Last updated: 2025年8月20日