Cocos creatro 3.8.7接入Amazon sdk,内购详细教程(消耗品)
Cocos 构建配置

为了适配16kb,这里用Android-36,也可以使用Android-35 ,把密钥填加后直接构建
Android配置,以下都是在在Android视图完成的,如果切换我会标注
在Android中配置gradle-wrapper.properties修改distributionUrl,修改之后如下
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
再找到build.gradle项目级的修改成如下
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
// jcenter() // keeped as anchor, will be removed soon
}
dependencies {
classpath 'com.android.tools.build:gradle:8.5.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenCentral()
}
}
apply from: NATIVE_DIR +"/build.gradle"
接下来打开build.gradle(Module:CocosGame)的文件引入依赖,Amazon的建议看官网最新的
dependencies {
implementation fileTree(dir: '../libs', include: ['*.jar','*.aar'])
implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
implementation fileTree(dir: "${COCOS_ENGINE_PATH}/cocos/platform/android/java/libs", include: ['*.jar'])
implementation project(':libservice')
implementation project(':libcocos')
//Amazon
implementation 'com.amazon.device:amazon-appstore-sdk:3.+'
if (Boolean.parseBoolean(PROP_ENABLE_INPUTSDK)) {
implementation 'com.google.android.libraries.play.games:inputmapping:1.1.0-beta'
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.10"
}
}
将防止Amazon代码混淆添加到proguard-rules.pro(Proguard Rules for ":libservice")中,添加完成后同步一下(async),鼠标右击项目
# Amazon,chartboost,kochava
-dontwarn com.amazon.**
-keep class com.amazon.** {*;}

找到manifests文件夹下的androidManifest.xml添加清单和权限
//在manifest下添加
<queries>
<package android:name="com.amazon.sdktestclient" />
<package android:name="com.amazon.venezia" />
</queries>
//在application下添加
<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>
完成之后找到如下目录
项目\native\engine\android\app\src
创建一个main文件夹,打开后再创建一个assets文件夹,最后将AppstoreAuthenticationKey.pem和amazon.sdktester.json放入,如下图

接下来是代码,首先在AppActivity中添加JsbBridge.setCallback用来接受Cocos层的消息
//JSB
protected void Jsb_connection() {
JsbBridge.setCallback(new JsbBridge.ICallback() {
@Override
public void onScript(String arg0, String arg1) {
final String a0 = arg0;
final String a1 = arg1;
CocosHelper.runOnGameThread(new Runnable() {
@Override
public void run() {
dispatchScriptMessage(a0, a1);
}
});
}
});
}
//message
private void dispatchScriptMessage(String arg0, String arg1) {
switch (arg0) {
break;
case "buyProduct"://购买
final String sku = arg1;
runOnUiThread(() -> PurchasingService.purchase(sku));
break;
case "checkBuDan"://补单
if (!budanState) {
iapManager.activate();
PurchasingService.getUserData();
PurchasingService.getPurchaseUpdates(false);
budanState = true;
} else {
PurchasingService.getPurchaseUpdates(false);
}
Log.d("Cocos", "dispatchScriptMessage: 补单");
break;
case "dingdanGood"://到账
iapManager.onJsConfirmPurchase(arg1);
Log.d("Cocos", "onScript:到账成功 " + arg1);
break;
}
}
添加完成后在onCreate中调用 Jsb_connection();
之后就是在Cocos的购买界面,比如玩家点击商品向Android层发送,测试能成功接收就行,
native.bridge.sendToNative("buyProduct", "商品订单id");
接下来就是在Android添加一下其他的方法使其打通,创建UserIapData类用来接收用户信息
package com.cocos.game;
public class UserIapData {
private final String amazonUserId;
private final String amazonMarketplace;
public String getAmazonUserId() {
return amazonUserId;
}
public String getAmazonMarketplace() {
return amazonMarketplace;
}
public UserIapData(final String amazonUserId, final String amazonMarketplace) {
this.amazonUserId = amazonUserId;
this.amazonMarketplace = amazonMarketplace;
}
}
创建Sku的enum类用来存放商品id,com.jinbi写成你们自己的id就行
package com.cocos.game;
import java.util.Arrays;
import java.util.List;
public enum Sku {
COINSPACK1("com.jinbi.test1", 2000000,
Arrays.asList("AU", "BR", "CA", "CN", "DE", "ES", "FR", "GB", "IN", "IT", "JP", "MX", "US")),
COINSPACK2("com.jinbi.test2", 750000,
Arrays.asList("AU", "BR", "CA", "CN", "DE", "ES", "FR", "GB", "IN", "IT", "JP", "MX", "US"));
//写一些参数用于拿到商品id,数量那个写可不写,我参考官方的写上了但没用用到
private final String sku;
private final long amount; // 购买后发放的数量
private final List<String> availableMarketplaces;
//=============构建函数============,传入信息方便检测从Cocos那里获取的id是不是存在的
Sku(final String sku, final long amount, final List<String> availableMarketplaces) {
this.sku = sku;
this.amount = amount;
this.availableMarketplaces = availableMarketplaces;
}
//方便获取 sku,数量amount,所有商品availableMarketplaces
public String getSku() { return sku; }
public long getAmount() { return amount; } // 新增
public List<String> getAvailableMarketplaces() { return availableMarketplaces; }
//查找商品,检测是否存在
public static Sku fromSku(final String sku, final String marketplace) {
for (Sku s : Sku.values()) {
if (s.getSku().equals(sku)
&& s.getAvailableMarketplaces().contains(marketplace.toUpperCase())) {
return s;
}
}
return null;
}
}
创建SampleSQLiteHelper类用来存放订单的记录(数据库的一张表)
package com.cocos.game;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class SampleSQLiteHelper extends SQLiteOpenHelper {
// 表名
public static final String TABLE_PURCHASES = "purchases";
// 字段名
public static final String COLUMN_RECEIPT_ID = "receipt_id"; // 购买凭证ID(主键)
public static final String COLUMN_USER_ID = "user_id"; // 亚马逊用户ID
public static final String COLUMN_MARKETPLACE = "marketplace"; // 市场(US/CN/JP...)
public static final String COLUMN_SKU = "sku"; // 购买的商品SKU
public static final String COLUMN_STATUS = "status"; // 状态
public static final String COLUMN_PURCHASE_DATE = "purchase_date"; // 购买时间戳
// 状态常量(status 字段的取值)
public static final String STATUS_PENDING = "PENDING"; // 已收到回调,未发货
public static final String STATUS_FULFILLED = "FULFILLED"; // 已发货
public static final String STATUS_FAILED = "FAILED"; // 发货失败
// 数据库配置
private static final String DATABASE_NAME = "receipts.db";
private static final int DATABASE_VERSION = 2; // 版本升为2(新增字段)
// 建表 SQL
private static final String DATABASE_CREATE =
"CREATE TABLE " + TABLE_PURCHASES + " ("
+ COLUMN_RECEIPT_ID + " TEXT PRIMARY KEY NOT NULL, "
+ COLUMN_USER_ID + " TEXT NOT NULL, "
+ COLUMN_MARKETPLACE + " TEXT NOT NULL, "
+ COLUMN_SKU + " TEXT NOT NULL, "
+ COLUMN_STATUS + " TEXT NOT NULL, "
+ COLUMN_PURCHASE_DATE + " INTEGER NOT NULL"
+ ");";
//构造函数
public SampleSQLiteHelper(final Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(final SQLiteDatabase database) {
database.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
Log.w(SampleSQLiteHelper.class.getName(),
"Upgrading database from version " + oldVersion + " to " + newVersion);
// 旧表数据不需要保留,直接删除重建
// db.execSQL("DROP TABLE IF EXISTS " + TABLE_PURCHASES);
// onCreate(db);
}
}
创建PurchaseDataSource类用来增,改,查订单数据表
package com.cocos.game;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
public class PurchaseDataSource {
private static final String TAG = "PurchaseDataSource";
private final SampleSQLiteHelper dbHelper;
// 查询时返回的字段列表(与 SampleSQLiteHelper 中定义的字段对应)
private final String[] allColumns = {
SampleSQLiteHelper.COLUMN_RECEIPT_ID,
SampleSQLiteHelper.COLUMN_USER_ID,
SampleSQLiteHelper.COLUMN_MARKETPLACE,
SampleSQLiteHelper.COLUMN_SKU,
SampleSQLiteHelper.COLUMN_STATUS,
SampleSQLiteHelper.COLUMN_PURCHASE_DATE
};
private SQLiteDatabase database;
public PurchaseDataSource(final Context context) {
dbHelper = new SampleSQLiteHelper(context);
}
public void open() throws SQLException {
database = dbHelper.getWritableDatabase();
}
public void close() {
dbHelper.close();
}
/**
* 创建一条新的购买记录(状态初始为 PAID)
* 在发货前调用,防止崩溃丢单
*
* @param receiptId 亚马逊收据ID(主键)
* @param userId 亚马逊用户ID
* @param marketplace 市场(如 "US")
* @param sku 购买的商品SKU
* @param status 初始状态(通常为 PAID)
*/
public void createPurchase(final String receiptId,
final String userId,
final String marketplace,
final String sku,
final PurchaseStatus status) {
Log.d(TAG, "createPurchase: receiptId=" + receiptId
+ " userId=" + userId
+ " sku=" + sku
+ " status=" + status);
final ContentValues values = new ContentValues();
values.put(SampleSQLiteHelper.COLUMN_RECEIPT_ID, receiptId);
values.put(SampleSQLiteHelper.COLUMN_USER_ID, userId);
values.put(SampleSQLiteHelper.COLUMN_MARKETPLACE, marketplace);
values.put(SampleSQLiteHelper.COLUMN_SKU, sku);
values.put(SampleSQLiteHelper.COLUMN_STATUS, status.toString());
values.put(SampleSQLiteHelper.COLUMN_PURCHASE_DATE, System.currentTimeMillis());
try {
database.insertOrThrow(SampleSQLiteHelper.TABLE_PURCHASES, null, values);
Log.d(TAG, "createPurchase: success for receiptId=" + receiptId);
} catch (final SQLException e) {
// 主键冲突:同一个 receiptId 已存在,忽略即可(防重复插入)
Log.w(TAG, "createPurchase: receiptId already exists, skipping. receiptId=" + receiptId);
}
}
// ==================== 创建购买记录 ====================
/**
* 根据 receiptId 和 userId 查询购买记录
* 用于防重复发货检查
*
* @param receiptId 亚马逊收据ID
* @param userId 亚马逊用户ID
* @return 匹配的购买记录,找不到返回 null
*/
public SampleIapManager.PurchaseRecord getPurchaseRecord(final String receiptId, final String userId) {
Log.d(TAG, "getPurchaseRecord: receiptId=" + receiptId + " userId=" + userId);
final Cursor cursor = database.query(
SampleSQLiteHelper.TABLE_PURCHASES,
allColumns,
SampleSQLiteHelper.COLUMN_RECEIPT_ID + " = ?",
new String[]{receiptId},
null, null, null
);
try {
cursor.moveToFirst();
if (cursor.isAfterLast()) {
Log.d(TAG, "getPurchaseRecord: no record found for receiptId=" + receiptId);
return null;
}
final SampleIapManager.PurchaseRecord record = cursorToPurchaseRecord(cursor);
// 验证 userId 是否匹配,防止跨用户数据泄露
if (record.getUserId() != null && record.getUserId().equalsIgnoreCase(userId)) {
Log.d(TAG, "getPurchaseRecord: found record, status=" + record.getStatus());
return record;
} else {
Log.w(TAG, "getPurchaseRecord: userId mismatch for receiptId=" + receiptId);
return null;
}
} finally {
cursor.close(); // 确保 cursor 一定被关闭
}
}
// ==================== 查询购买记录 ====================
/**
* 更新购买记录状态(如 PAID -> FULFILLED)
* fromStatus 不为 null 时,只更新状态匹配的记录(防并发重复更新)
*
* @param receiptId 收据ID
* @param fromStatus 当前状态(可为 null,表示不限制当前状态)
* @param toStatus 目标状态
* @return true 表示更新成功,false 表示没有匹配记录(可能已被其他线程更新)
*/
public boolean updatePurchaseStatus(final String receiptId,
final PurchaseStatus fromStatus,
final PurchaseStatus toStatus) {
Log.d(TAG, "updatePurchaseStatus: receiptId=" + receiptId
+ " " + fromStatus + " -> " + toStatus);
String where = SampleSQLiteHelper.COLUMN_RECEIPT_ID + " = ?";
String[] whereArgs = new String[]{receiptId};
// 如果指定了 fromStatus,加上状态条件(防止并发重复更新)
if (fromStatus != null) {
where += " AND " + SampleSQLiteHelper.COLUMN_STATUS + " = ?";
whereArgs = new String[]{receiptId, fromStatus.toString()};
}
final ContentValues values = new ContentValues();
values.put(SampleSQLiteHelper.COLUMN_STATUS, toStatus.toString());
final int updated = database.update(
SampleSQLiteHelper.TABLE_PURCHASES, values, where, whereArgs);
Log.d(TAG, "updatePurchaseStatus: updated rows=" + updated);
return updated > 0;
}
// ==================== 更新购买状态 ====================
/**
* 将数据库游标转换为 PurchaseRecord 对象
*/
private SampleIapManager.PurchaseRecord cursorToPurchaseRecord(final Cursor cursor) {
final SampleIapManager.PurchaseRecord record = new SampleIapManager.PurchaseRecord();
record.setReceiptId(cursor.getString(
cursor.getColumnIndexOrThrow(SampleSQLiteHelper.COLUMN_RECEIPT_ID)));
record.setUserId(cursor.getString(
cursor.getColumnIndexOrThrow(SampleSQLiteHelper.COLUMN_USER_ID)));
// 解析状态字段,异常时默认 UNKNOWN
try {
record.setStatus(PurchaseStatus.valueOf(cursor.getString(
cursor.getColumnIndexOrThrow(SampleSQLiteHelper.COLUMN_STATUS))));
} catch (final Exception e) {
Log.w(TAG, "cursorToPurchaseRecord: unknown status, defaulting to UNKNOWN");
record.setStatus(PurchaseStatus.UNKNOWN);
}
return record;
}
// ==================== 私有方法:Cursor 转对象 ====================
public enum PurchaseStatus {
PAID, // 已付款,待发货
FULFILLED, // 已发货
UNAVAILABLE, // SKU 已下架
UNKNOWN // 未知状态(数据异常时使用)
}
}
创建SamplePurchasingListener类用来接点击购买之后触发回调的数据,比如购买成功,失败,到账之类的
package com.cocos.game;
import android.util.Log;
import com.amazon.device.iap.PurchasingListener;
import com.amazon.device.iap.PurchasingService;
import com.amazon.device.iap.model.ProductDataResponse;
import com.amazon.device.iap.model.PurchaseResponse;
import com.amazon.device.iap.model.PurchaseUpdatesResponse;
import com.amazon.device.iap.model.Receipt;
import com.amazon.device.iap.model.UserDataResponse;
import java.util.HashSet;
import java.util.Set;
public class SamplePurchasingListener implements PurchasingListener {
private static final String TAG = "SampleIAPConsumablesApp";
private final SampleIapManager iapManager;
public SamplePurchasingListener(final SampleIapManager iapManager) {
this.iapManager = iapManager;
}
// ==================== 1. 用户数据回调 ====================
/**
* getUserData() 的回调
* App 启动时获取当前登录的亚马逊用户信息
*/
@Override
public void onUserDataResponse(final UserDataResponse response) {
final UserDataResponse.RequestStatus status = response.getRequestStatus();
Log.d(TAG, "onUserDataResponse: status=" + status);
switch (status) {
case SUCCESSFUL:
final String userId = response.getUserData().getUserId();
final String marketplace = response.getUserData().getMarketplace();
Log.d(TAG, "onUserDataResponse: userId=" + userId + " marketplace=" + marketplace);
iapManager.setAmazonUserId(userId, marketplace);
break;
case FAILED:
case NOT_SUPPORTED:
Log.w(TAG, "onUserDataResponse: failed, status=" + status);
iapManager.setAmazonUserId(null, null);
break;
}
}
// ==================== 2. 商品信息回调 ====================
/**
* getProductData() 的回调
* 获取商品列表后,通知 IapManager 哪些 SKU 可用/不可用
*/
@Override
public void onProductDataResponse(final ProductDataResponse response) {
final ProductDataResponse.RequestStatus status = response.getRequestStatus();
Log.d(TAG, "onProductDataResponse: status=" + status);
switch (status) {
case SUCCESSFUL:
// 通知可用商品 → Cocos 层启用对应购买按钮
iapManager.enablePurchaseForSkus(response.getProductData());
// 通知不可用商品 → Cocos 层禁用对应购买按钮
iapManager.disablePurchaseForSkus(response.getUnavailableSkus());
Log.d(TAG, "onProductDataResponse: unavailable SKUs count="
+ response.getUnavailableSkus().size());
break;
case FAILED:
case NOT_SUPPORTED:
// 获取商品信息失败,禁用所有购买按钮
Log.w(TAG, "onProductDataResponse: failed, disabling all purchases");
iapManager.disableAllPurchases();
break;
}
}
// ==================== 3. 历史购买记录回调 ====================
/**
* getPurchaseUpdates() 的回调
* App 启动时检查未完成的购买记录(状态非 FULFILLED 的收据)
* 用于补发:App 崩溃或网络中断后,重新发货给玩家
*/
@Override
public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse response) {
final PurchaseUpdatesResponse.RequestStatus status = response.getRequestStatus();
Log.d(TAG, "onPurchaseUpdatesResponse: status=" + status
+ " userId=" + response.getUserData().getUserId());
switch (status) {
case SUCCESSFUL:
// 同步用户信息
iapManager.setAmazonUserId(
response.getUserData().getUserId(),
response.getUserData().getMarketplace()
);
// 处理每一条未完成的购买记录
for (final Receipt receipt : response.getReceipts()) {
Log.d(TAG, "onPurchaseUpdatesResponse: processing receipt="
+ receipt.getReceiptId() + " sku=" + receipt.getSku());
iapManager.handleReceipt(receipt, response.getUserData());
}
// 如果还有更多记录,继续获取
if (response.hasMore()) {
Log.d(TAG, "onPurchaseUpdatesResponse: has more, fetching next page");
PurchasingService.getPurchaseUpdates(false);
}
break;
case FAILED:
case NOT_SUPPORTED:
Log.w(TAG, "onPurchaseUpdatesResponse: failed, disabling all purchases");
iapManager.disableAllPurchases();
break;
}
}
// ==================== 4. 购买结果回调(最重要)====================
/**
* purchase() 的回调
* 玩家点击购买按钮后,亚马逊处理完成会触发此回调
*/
@Override
public void onPurchaseResponse(final PurchaseResponse response) {
final PurchaseResponse.RequestStatus status = response.getRequestStatus();
final String userId = response.getUserData().getUserId();
Log.d(TAG, "onPurchaseResponse: status=" + status + " userId=" + userId);
switch (status) {
case SUCCESSFUL:
// 购买成功 → 发货
final Receipt receipt = response.getReceipt();
Log.d(TAG, "onPurchaseResponse: receipt=" + receipt.toJSON());
iapManager.setAmazonUserId(
response.getUserData().getUserId(),
response.getUserData().getMarketplace()
);
iapManager.handleReceipt(receipt, response.getUserData());
break;
case PENDING:
// 待审核(部分市场需要家长审批等)
// 审批通过后会通过 getPurchaseUpdates 回调补发
Log.d(TAG, "onPurchaseResponse: purchase is pending remote approval");
break;
case ALREADY_PURCHASED:
// 消耗型商品不会出现此状态,只有非消耗型/订阅型才会
Log.w(TAG, "onPurchaseResponse: already purchased (unexpected for consumable)");
break;
case INVALID_SKU:
// SKU 无效,禁用该商品的购买按钮
Log.e(TAG, "onPurchaseResponse: invalid SKU=" + response.getReceipt().getSku());
final Set<String> invalidSkus = new HashSet<>();
invalidSkus.add(response.getReceipt().getSku());
iapManager.disablePurchaseForSkus(invalidSkus);
break;
case FAILED:
case NOT_SUPPORTED:
// 购买失败,通知 Cocos 层
Log.e(TAG, "onPurchaseResponse: purchase failed");
iapManager.showPurchaseFailedMessage(response.getReceipt().getSku());
break;
}
}
}
创建SampleIapManager用来统一管理,发货,到账,存取信息等
package com.cocos.game;
import android.content.Context;
import android.util.Log;
import com.amazon.device.iap.PurchasingService;
import com.amazon.device.iap.model.FulfillmentResult;
import com.amazon.device.iap.model.Product;
import com.amazon.device.iap.model.Receipt;
import com.amazon.device.iap.model.UserData;
import java.util.Map;
import java.util.Set;
public class SampleIapManager {
private static final String TAG = "SampleIAPManager";
// 统一使用 PurchaseDataSource.PurchaseStatus,不再重复定义
private static final PurchaseDataSource.PurchaseStatus PAID = PurchaseDataSource.PurchaseStatus.PAID;
private static final PurchaseDataSource.PurchaseStatus FULFILLED = PurchaseDataSource.PurchaseStatus.FULFILLED;
private static final PurchaseDataSource.PurchaseStatus UNAVAILABLE = PurchaseDataSource.PurchaseStatus.UNAVAILABLE;
private final Context context;
private final AppActivity appActivity;
private final PurchaseDataSource dataSource;
private UserIapData userIapData;
// ==================== 构造函数 ====================
public SampleIapManager(final AppActivity appActivity) {
this.appActivity = appActivity;
this.context = appActivity.getApplicationContext();
this.dataSource = new PurchaseDataSource(context);
}
// ==================== 生命周期 ====================
public void activate() {
dataSource.open();
}
public void deactivate() {
dataSource.close();
}
// ==================== 用户管理 ====================
public void setAmazonUserId(final String newAmazonUserId, final String newAmazonMarketplace) {
if (newAmazonUserId == null) {
if (userIapData != null) {
userIapData = null;
notifyCocosUserNotLoggedIn();
}
} else if (userIapData == null || !newAmazonUserId.equals(userIapData.getAmazonUserId())) {
userIapData = new UserIapData(newAmazonUserId, newAmazonMarketplace);
Log.i(TAG, "Amazon user set: " + newAmazonUserId + " marketplace: " + newAmazonMarketplace);
}
}
public UserIapData getUserIapData() {
return this.userIapData;
}
// ==================== 商品可用性控制 ====================
public void enablePurchaseForSkus(final Map<String, Product> productData) {
for (Sku mySku : Sku.values()) {
if (productData.containsKey(mySku.getSku())) {
Log.i(TAG, "SKU available: " + mySku.getSku());
notifyCocosSkuEnabled(mySku.getSku(), productData.get(mySku.getSku()));
}
}
}
public void disablePurchaseForSkus(final Set<String> unavailableSkus) {
for (String sku : unavailableSkus) {
Log.w(TAG, "SKU unavailable: " + sku);
notifyCocosSkuDisabled(sku);
}
}
public void disableAllPurchases() {
for (Sku mySku : Sku.values()) {
notifyCocosSkuDisabled(mySku.getSku());
}
}
// ==================== 购买失败处理 ====================
public void showPurchaseFailedMessage(final String sku) {
Log.e(TAG, "Purchase failed for SKU: " + sku);
notifyCocosPurchaseFailed(sku);
}
// ==================== 核心购买流程 ====================
public void handleReceipt(final Receipt receipt, final com.amazon.device.iap.model.UserData userData) {
switch (receipt.getProductType()) {
case CONSUMABLE:
handleConsumablePurchase(receipt, userData);
break;
case ENTITLED:
case SUBSCRIPTION:
break;
}
}
public void handleConsumablePurchase(final Receipt receipt, final com.amazon.device.iap.model.UserData userData) {
try {
if (receipt.isCanceled()) {
revokeConsumablePurchase(receipt, userData);
return;
}
if (!verifyReceiptFromYourService(receipt.getReceiptId(), userData)) {
appActivity.showMessage("Purchase cannot be verified, please retry later.");
notifyCocosPurchaseFailed(receipt.getSku());
return;
}
if (receiptAlreadyFulfilled(receipt.getReceiptId(), userData)) {
PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
return;
}
grantConsumablePurchase(receipt, userData);
} catch (final Throwable e) {
Log.e(TAG, "handleConsumablePurchase error: " + e.getMessage());
appActivity.showMessage("Purchase cannot be completed, please retry");
notifyCocosPurchaseFailed(receipt.getSku());
}
}
private void grantConsumablePurchase(final Receipt receipt, final com.amazon.device.iap.model.UserData userData) {
try {
// 1. 先写数据库(防丢单)
dataSource.createPurchase(
receipt.getReceiptId(),
userData.getUserId(),
userIapData.getAmazonMarketplace(),
receipt.getSku(),
PAID
);
// 2. 验证 SKU 有效性
final Sku mySku = Sku.fromSku(receipt.getSku(), userIapData.getAmazonMarketplace());
if (mySku == null) {
Log.w(TAG, "SKU [" + receipt.getSku() + "] is no longer valid");
dataSource.updatePurchaseStatus(receipt.getReceiptId(), null, UNAVAILABLE);
PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.UNAVAILABLE);
return;
}
// 3. 更新数据库 PAID -> FULFILLED
if (dataSource.updatePurchaseStatus(receipt.getReceiptId(), PAID, FULFILLED)) {
// 4. 通知 Cocos 层发放道具
grantItemToCocosPlayer(mySku, receipt.getReceiptId());
// 5. 通知亚马逊完成
// PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
Log.i(TAG, "Fulfilled SKU: " + mySku.getSku() + " receiptId: " + receipt.getReceiptId());
} else {
Log.w(TAG, "Duplicate fulfillment for receiptId: " + receipt.getReceiptId());
}
} catch (final Throwable e) {
Log.e(TAG, "grantConsumablePurchase error: " + e.getMessage());
}
}
private void revokeConsumablePurchase(final Receipt receipt, final com.amazon.device.iap.model.UserData userData) {
Log.w(TAG, "Purchase revoked for receiptId: " + receipt.getReceiptId());
}
/**
* JS 层确认收到道具后,Java 层调用此方法完成发货
* 由 AppActivity 的 JsbBridge 回调触发
*/
public void onJsConfirmPurchase(final String receiptId) {
// 更新数据库 PAID -> FULFILLED
final boolean updated = dataSource.updatePurchaseStatus(
receiptId,
PurchaseDataSource.PurchaseStatus.PAID,
PurchaseDataSource.PurchaseStatus.FULFILLED
);
if (updated) {
// 通知亚马逊发货完成
PurchasingService.notifyFulfillment(receiptId, FulfillmentResult.FULFILLED);
Log.i(TAG, "onJsConfirmPurchase: FULFILLED receiptId=" + receiptId);
} else {
// 可能已经被其他回调处理过了
Log.w(TAG, "onJsConfirmPurchase: already fulfilled receiptId=" + receiptId);
PurchasingService.notifyFulfillment(receiptId, FulfillmentResult.FULFILLED);
}
}
// ==================== 通知 Cocos 层 ====================
private void grantItemToCocosPlayer(final Sku sku, final String receiptId) {
// arg0 = "purchaseSuccess"
// arg1 = "sku|amount|receiptId"
final String arg1 ="['" + sku.getSku() + "', '" + receiptId + "']";//合并结果是:['商品id',’订单编号‘]
appActivity.sendToJs("purchaseSuccessIAP", arg1);
Log.d(TAG, "grantItemToCocosPlayer: 发货成功!");
}
private void notifyCocosUserNotLoggedIn() {
appActivity.sendToJs("userNotLoggedIn", "");
}
private void notifyCocosSkuEnabled(final String sku, final Product product) {
final String price = product != null ? product.getPrice() : "";
// arg1 = "sku|price"
appActivity.sendToJs("skuEnabled", sku + "|" + price);
}
private void notifyCocosSkuDisabled(final String sku) {
appActivity.sendToJs("skuDisabled", sku);
}
private void notifyCocosPurchaseFailed(final String sku) {
appActivity.sendToJs("purchaseFailed", sku);
}
// ==================== 数据库辅助 ====================
private boolean updatePurchaseStatus(final String receiptId,
final PurchaseDataSource.PurchaseStatus from,
final PurchaseDataSource.PurchaseStatus to) {
return dataSource.updatePurchaseStatus(receiptId, from, to);
}
private boolean receiptAlreadyFulfilled(final String receiptId, final com.amazon.device.iap.model.UserData userData) {
final PurchaseRecord record = dataSource.getPurchaseRecord(receiptId, userData.getUserId());
if (record == null) return false;
return !(FULFILLED == record.getStatus() || UNAVAILABLE == record.getStatus());
}
private boolean verifyReceiptFromYourService(final String receiptId, final UserData userData) {
return true; // TODO: 接入服务端验证
}
// ==================== 内部类:购买记录 ====================
public static class PurchaseRecord {
private PurchaseDataSource.PurchaseStatus status; // 统一使用同一个枚举
private String receiptId;
private String userId;
public PurchaseDataSource.PurchaseStatus getStatus() {
return status;
}
public void setStatus(final PurchaseDataSource.PurchaseStatus status) {
this.status = status;
}
public String getReceiptId() {
return receiptId;
}
public void setReceiptId(final String receiptId) {
this.receiptId = receiptId;
}
public String getUserId() {
return userId;
}
public void setUserId(final String userId) {
this.userId = userId;
}
}
}
以上配置完成后,在AppActivity.java添加一些代码,在这2个周期里面添加如下,用来触发补单信息
@Override
protected void onStop() {
super.onStop();
SDKWrapper.shared().onStop();
iapManager.deactivate();
}
@Override
protected void onResume() {
super.onResume();
SDKWrapper.shared().onResume();
if (budanState) {
iapManager.activate();
PurchasingService.getUserData();
PurchasingService.getPurchaseUpdates(false);
Log.d("Cocos", "onResume: 补发货物");
}
}
接下在指令里分别执行,项目是你项目的位置,第一个debug不提示,第二个提示
1 file pushed, 0 skipped. 1.6 MB/s (2705 bytes in 0.002s) 就是成功
adb shell setprop debug.amazon.sandboxmode debug
adb -s G0011J05946619AU push 项目/native/engine/android/app/src/main/assets/amazon.sdktester.json sdcard/

接下来找打开Amazon的fire os设备
里面这个app查看是否有商品数据
打开游戏,点击内购,就可以触发成功了,到账成功后要在发送消息,玩家的道具成功到账后要告知商店订单已完成
native.bridge.sendToNative("dingdanGood", dindan.receiptId);

更多推荐
所有评论(0)