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

更多推荐