实现Appstore SDK IAP
为了更好地了解Appstore SDK应用内购买 (IAP) API,请阅读以下描述,了解Android IAP程序包中包含的类。要了解如何将IAP API集成到Android应用中,请遵循本页面提供的用例和代码示例。在Appstore SDK中包含的消费品IAP示例应用中,可以找到其中许多代码示例。
新手入门,请观看视频教程。有关如何在应用中实现IAP的更多详细信息,请参阅后续章节。
关于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() |
无 | 无 |
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或更高版本为目标,则必须在MainActivity
和ResponseReceiver
中显式地将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
会实现以下方法。必须实现每个方法,回调才能正常运行:
registerListener(PurchasingListener purchasingListener)
: 此方法是发生回调时采用的机制。在PurchasingService
类中,先调用registerListener()
,然后再调用其他方法。通过PurchasingListener
对象,应用可以侦听并处理由ResponseReceiver
触发的回调。在应用的主UI线程中,在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的购买。notifyFulfillment(java.lang.String receiptId, FulfillmentResult fulfillmentResult)
: 调用此方法以发送指定receiptId
的FulfillmentResult
。FulfillmentResult
可能的值为FULFILLED
或UNAVAILABLE
。enablePendingPurchases()
: 调用此方法可在您的应用中为孩子和家长启用“待定购买”功能。当PurchaseResponse.RequestStatus
值为PENDING
时,购买尚未得到批准。如果状态为待定,请勿向客户授予权利。如果购买得到批准,则在onResume()
方法中调用getPurchaseUpdates()
时会以通常的方式进行处理。有关更多详细信息,请参阅实现待定购买。
PurchasingListener
实现PurchasingListener
接口以处理异步回调。因为UI线程会调用这些回调,所以不要在UI线程中处理长时间运行的任务。PurchasingListener
实例应实现以下所需的方法:
onUserDataResponse(UserDataResponse userDataResponse)
: 在调用getUserData()
后引用。确定当前登录用户的用户ID和市场。还提供用户对于快速订阅的同意状态。onPurchaseUpdatesResponse(PurchaseUpdatesResponse purchaseUpdatesResponse)
: 在调用getPurchaseUpdates()
后引用。检索购买记录。亚马逊建议保留返回的PurchaseUpdatesResponse
数据并仅向系统查询更新。onProductDataResponse(ProductDataResponse productDataResponse)
: 在调用getProductDataRequest()
后引用。检索有关您想要在应用中销售的SKU的信息。使用来自ProductDataResponse
对象的有效SKU。onPurchaseResponse(PurchaseResponse purchaseResponse)
: 在调用purchase()
后引用。用于确定购买状态。
响应对象
每个通过PurchasingService
发出的调用,PurchasingListener
都会收到相应响应。其中每个响应都使用响应对象:
UserDataResponse
: 提供当前登录用户特定于应用的用户ID和市场。还提供用户对于快速订阅的同意状态。PurchaseUpdatesResponse
: 提供收据的分页列表。收据是无序的。ProductDataResponse
: 提供以SKU为键的商品数据。getUnavailableSkus()
方法会列出所有不可用的SKU。PurchaseResponse
: 提供在应用内发起的购买的状态。请注意,PurchaseResponse.RequestStatus
结果为FAILED
可能只是表示用户在完成之前取消了购买。
要查看Kotlin应用程序中的响应对象的示例,请在此处从GitHub克隆或下载IAP Kotlin示例应用程序:
将IAP API与应用集成
现在您已经详细了解了实现IAP所需的门类,接下来就可以开始在应用中编写IAP代码了。
本节中的代码段来自SDK附带的Consumable IAP(消费品IAP)示例应用。
1. 创建占位符方法
要整理您的代码,请使用占位符或无存根代码段在以下位置调用相应方法:
- 在
onCreate()
方法中调用registerListener()
。 - 在
onResume()
方法中调用getUserData()
。 - 在
onResume()
方法中调用getPurchaseUpdates()
。 - 在
onResume()
方法中调用getProductData()
。
在应用主要活动的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;
// ...
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:
// 客户未在设备上登录亚马逊,或者
// 应用和亚马逊应用商店之间存在连接问题。
// 在此设备上暂时对用户禁用应用内购买,
// 直到成功检索到有关用户和产品的详细信息。
// 当getProductData方法成功检索到ProductData时,
// 会重新启用购买。
iapManager.disableAllPurchases();
break;
case NOT_SUPPORTED:
// 设备不支持IAP功能。
// 在此设备上对用户禁用应用内购买。
iapManager.disableAllPurchases();
break;
}
}
请注意,此示例还会将用户ID和市场保留到内存中,以供应用日后使用。
4. 实现getPurchaseUpdates方法
getPurchaseUpdates()
方法会检索自上次调用该方法之后用户完成的所有购买交易。在onResume()
方法中调用getPurchaseUpdates()
,以确保获取最新的更新。
getPurchaseUpdates()
才能让应用同步亚马逊应用商店中的购买。如果不执行此必要的步骤,则应用可能无法向客户授予其购买的商品。不要根据任何业务标准来限制此调用,例如客户是登录还是注销,或者客户的订阅状态为何。
该方法会接受名为reset
的布尔值参数。根据要检索的信息量,将该值设为true
或false
:
-
false
- 返回自上次调用getPurchaseUpdates()
之后的购买记录的分页响应。检索用户的未履行消费品、权利和订阅购买的收据。亚马逊应用商店建议在大部分情况下使用此方法。注意: 如果您的应用启用了待定购买,则亚马逊应用商店会在每次getPurchaseUpdates(false)
调用中为用户返回所有未履行的消费品和权利收据,直到您的应用调用notifyFulfillment()
为止。如果不启用“待定购买”,则在每次getPurchaseUpdates(false)
调用中只返回未履行的消费品收据。 -
true
- 检索用户的完整购买记录。需要将数据存储在某个位置(例如服务器端数据缓存),或将所有数据保留在内存中。如果您需要用户进行的购买的完整列表,例如当客户想要恢复购买时,或者您检测到您的应用与亚马逊应用商店之间存在一致性问题时,这时请使用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()
返回的请求状态。如果RequestStatus
为SUCCESSFUL
,则处理每个收据。您可以使用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" );
}
productSkus
以验证getProductData()
。您需要包含子SKU,因为价格信息与每个子SKU关联。父SKU没有价格,因为价格因订阅时长而异。有关更多信息,请参阅订阅商品常见问题解答。调用PurchasingService.getProductData()
方法会触发PurchasingListener.onProductDataResponse()
回调。检查ProductDataResponse.getRequestStatus()
中返回的请求状态,并且仅销售经过此调用验证的商品或SKU。
请求成功
如果RequestStatus
为SUCCESSFUL
,则检索以应用中显示的SKU为键的产品数据图。如果RequestStatus
为SUCCESSFUL
,但存在不可用的SKU,则调用ProductDataResponse.getUnavailableSkus()
,以检索无效SKU的产品数据并阻止用户购买这些产品。
如果希望在应用内显示IAP图标,则必须编辑AndroidManifest.xml文件,以添加android.permission.INTERNET
权限。
产品数据图包含以下值:
字段 | 数据类型 | 描述 |
---|---|---|
sku |
字符串 | 产品的库存单位(SKU)。 |
title |
字符串 | 产品的本地化标题。 |
description |
字符串 | 产品的本地化描述。 |
smallIconUrl |
字符串 | 产品的小图标URL。 |
productType |
字符串 | 产品类型。有效值: CONSUMABLE , ENTITLED , SUBSCRIPTION . |
coinsRewardAmount |
整数 | 客户购买本产品后可获得的奖励亚马逊硬币数量。 |
freeTrialPeriod |
字符串 | 订阅期限的免费试用期。仅当配置了免费试用且客户符合条件时才返回。 |
subscriptionPeriod |
字符串 | SKU的订阅期限。仅针对有期限的SKU返回。有效值: Weekly 、BiWeekly 、Monthly 、BiMonthly 、Quarterly 、SemiAnnually 、Annually 。 |
promotions |
List<Promotion> | 客户有资格享受的促销的详细信息。仅针对有期限的SKU返回。有关Promotion 对象的详细信息,请参见下表。有关如何设置促销定价的信息,请参阅设置促销定价。 |
price |
字符串 | 产品的本地化价格(针对订阅商品的子SKU)。 |
Promotion
对象包含以下字段:
字段 | 数据类型 | 描述 |
---|---|---|
promotionType |
字符串 | 促销类型。有效值: Introductory
|
promotionPlans |
[列表]<PromotionalPlan> | 有关促销的价格和计费周期的详细信息。有关PromotionalPlan 对象的详细信息,请参见下表。 |
PromotionalPlan
对象包含以下字段:
字段 | 数据类型 | 描述 |
---|---|---|
promotionPrice |
字符串 | 以本地化格式显示的促销期间有期限SKU的价格。 |
promotionPricePeriod |
字符串 | 促销的每个计费周期的持续时间。有效值: Weekly 、BiWeekly 、Monthly 、BiMonthly 、Quarterly 、SemiAnnually 、Annually 。 |
promotionPriceCycles |
整数 | 计费周期数目。 |
要查看不同订阅用例的产品数据图示例,请单击以下按钮。示例为JSON格式。
请求失败
如果RequestStatus
为FAILED
,则禁用应用中的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生产终端节点。
cancelDate
,以防范退款欺诈。如果不验证取消日期,客户可以取消购买并继续接收服务。有关如何检查cancelDate
字段的更多信息,请参阅IAP最佳实践。在以下示例中,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.FULFILLED
调用notifyFulfillment()
。完成此步骤后,亚马逊应用商店将不再尝试向该应用发送购买收据。 - 如果客户已经拥有现有账户和您服务的订阅,或者客户没有资格注册您服务的账户,则以
FulfillmentResult.UNAVAILABLE
调用notifyFulfillment()
。
有关发送哪个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: 2024年11月6日