管理中心
我的

开发商品购买

本章节主要介绍接入华为应用内支付的客户端开发步骤,以帮助您快速了解华为应用内支付提供的客户端接口及其使用方法。

判断是否支持应用内支付

在使用应用内支付之前,您的应用需要向HMS Core(APK)发送isEnvReady请求,以此判断用户当前登录的华为帐号所在的服务地是否在华为IAP支持结算的国家/地区中。

如果您的应用未接入华为帐号的登录接口,您可以通过该接口完成登录操作。

开发步骤如下:

发起isEnvReady请求,并设置两个回调监听来接收接口请求的结果。

  • 当接口请求成功时,您的应用将获取到一个IsEnvReadyResult实例对象,表示用户当前登录的华为帐号所在的服务地支持IAP。
  • 当接口请求失败时,IAP会返回一个Exception对象,若该对象为IapApiException对象,可使用其getStatusCode方法获取此次请求的返回码。

    当返回帐号未登录(OrderStatusCode.ORDER_HWID_NOT_LOGIN)时,可使用IapApiException对象的getStatus方法获取到一个Status对象,然后通过Status对象的startResolutionForResult方法拉起华为帐号登录页面,此后在Activity的onActivityResult方法中获取结果信息。从onActivityResult返回的Intent对象中解析出returnCode,当returnCode=OrderStatusCode.ORDER_STATE_SUCCESS时,则表示当前帐号所在服务地支持IAP,其他则表示此次请求有异常。

    说明

    Status不支持序列化,请勿对IAP接口返回的Status对象执行序列化操作。

    Java
    Kotlin
    // 获取调用接口的Activity对象
    final Activity activity = getActivity();
    Task<IsEnvReadyResult> task = Iap.getIapClient(activity).isEnvReady();
    task.addOnSuccessListener(new OnSuccessListener<IsEnvReadyResult>() {
        @Override
        public void onSuccess(IsEnvReadyResult result) {
            // 获取接口请求的结果
            String carrierId = result.getCarrierId();
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(Exception e) {
            if (e instanceof IapApiException) {
                IapApiException apiException = (IapApiException) e;
                Status status = apiException.getStatus();
                if (status.getStatusCode() == OrderStatusCode.ORDER_HWID_NOT_LOGIN) {
                    // 未登录帐号
                    if (status.hasResolution()) {
                        try {
                            // 6666是您自定义的常量
                            // 启动IAP返回的登录页面
                            status.startResolutionForResult(activity, 6666);
                        } catch (IntentSender.SendIntentException exp) {
                        }
                    }
                } else if (status.getStatusCode() == OrderStatusCode.ORDER_ACCOUNT_AREA_NOT_SUPPORTED) {
                    // 用户当前登录的华为帐号所在的服务地不在华为IAP支持结算的国家/地区中
                }
            } else {
                // 其他外部错误
            }
        }
    });
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 6666) {
            if (data != null) {
                // 使用parseRespCodeFromIntent方法获取接口请求结果
                int returnCode = IapClientHelper.parseRespCodeFromIntent(data);
            }
        }
    }
    // 获取调用接口的Activity对象
    val activity: Activity = getActivity()
    val task = Iap.getIapClient(activity).isEnvReady
    task.addOnSuccessListener { result ->
        // 获取接口请求的结果
        val carrierId = result.carrierId
    }.addOnFailureListener { e ->
        if (e is IapApiException) {
            val apiException = e as IapApiException
            val status: Status = apiException.status
            if (status.getStatusCode() === OrderStatusCode.ORDER_HWID_NOT_LOGIN) {
                // 未登录帐号
                if (status.hasResolution()) {
                    try {
                        // 6666是您自定义的常量
                        // 启动IAP返回的登录页面
                        status.startResolutionForResult(activity, 6666)
                    } catch (exp: SendIntentException) {
                    }
                }
            } else if (status.getStatusCode() === OrderStatusCode.ORDER_ACCOUNT_AREA_NOT_SUPPORTED) {
                // 用户当前登录的华为帐号所在的服务地不在华为IAP支持结算的国家/地区中
            }
        } else {
           // 其他外部错误
        }
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 6666) {
            if (data != null) {
                // 使用parseRespCodeFromIntent方法获取接口请求结果
                val returnCode = IapClientHelper.parseRespCodeFromIntent(data)
            }
        }
    }

展示商品信息

若您的应用提供PMS商品,则需要在华为AppGallery Connect网站上完成商品的配置。商品配置完成后,您需要在您的应用中使用obtainProductInfo接口来获取此类商品的详细信息。

开发步骤如下:

  1. 构建请求参数ProductInfoReq,发起obtainProductInfo请求并设置OnSuccessListener和OnFailureListener回调监听器以接收接口请求的结果。您需要在ProductInfoReq中携带您此前已在华为AppGallery Connect网站上定义并生效的商品ID,并根据实际配置的商品指定其priceType。

    说明

    obtainProductInfo每次只能查询一种类型的商品。

  2. 当接口请求成功时,IAP将返回一个ProductInfoResult对象,您的应用可通过该对象的getProductInfoList方法获取到包含了单个商品信息的ProductInfo对象的列表。您可以使用ProductInfo对象包含的商品价格、名称和描述等信息,向用户展示可供购买的商品列表。

    Java
    Kotlin
    List<String> productIdList = new ArrayList<>();
    // 查询的商品必须是您在AppGallery Connect网站配置的商品
    productIdList.add("ConsumeProduct1001");
    ProductInfoReq req = new ProductInfoReq();
    // priceType: 0:消耗型商品; 1:非消耗型商品; 2:订阅型商品
    req.setPriceType(0);
    req.setProductIds(productIdList);
    // 获取调用接口的Activity对象
    final Activity activity = getActivity();
    // 调用obtainProductInfo接口获取AppGallery Connect网站配置的商品的详情信息
    Task<ProductInfoResult> task = Iap.getIapClient(activity).obtainProductInfo(req);
    task.addOnSuccessListener(new OnSuccessListener<ProductInfoResult>() {
        @Override
        public void onSuccess(ProductInfoResult result) {
            // 获取接口请求成功时返回的商品详情信息
            List<ProductInfo> productList = result.getProductInfoList();
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(Exception e) {
            if (e instanceof IapApiException) {
                IapApiException apiException = (IapApiException) e;
                int returnCode = apiException.getStatusCode();
            } else {
                // 其他外部错误
            }
        }
    });
    val productIdList: MutableList<String> = ArrayList()
    // 查询的商品必须是您在AppGallery Connect网站配置的商品
    productIdList.add("ConsumeProduct1001")
    val req = ProductInfoReq()
    // priceType: 0:消耗型商品; 1:非消耗型商品; 2:订阅型商品
    req.setPriceType(0)
    req.productIds = productIdList
    // 获取调用接口的Activity对象
    val activity: Activity = getActivity()
    // 调用obtainProductInfo接口获取AppGallery Connect网站配置的商品的详情信息
    val task = Iap.getIapClient(activity).obtainProductInfo(req)
    task.addOnSuccessListener { result ->
        // 获取接口请求成功时返回的商品详情信息
        val productList = result.getProductInfoList()
    }.addOnFailureListener { e ->
        if (e is IapApiException) {
            val apiException = e as IapApiException
            val returnCode = apiException.statusCode
        } else {
            // 其他外部错误
        }
    }

发起购买

购买PMS商品

PMS商品指在华为AppGallery Connect网站上配置的商品,支持消耗型、非消耗型和订阅型商品。您的应用可通过createPurchaseIntent接口发起此类商品的购买请求。开发步骤如下:

  1. 构建请求参数PurchaseIntentReq,发起createPurchaseIntent请求。您需要在PurchaseIntentReq中携带您此前已在AppGallery Connect网站上定义并生效的商品ID。当接口请求成功时,您可获取到一个PurchaseIntentResult对象,其getStatus方法返回了一个Status对象,您的应用需要通过Status对象的startResolutionForResult方法来启动华为IAP收银台。

    说明

    Status不支持序列化,请勿对IAP接口返回的Status对象执行序列化操作。

    Java
    Kotlin
    // 构造一个PurchaseIntentReq对象
    PurchaseIntentReq req = new PurchaseIntentReq();
    // 通过createPurchaseIntent接口购买的商品必须是您在AppGallery Connect网站配置的商品
    req.setProductId("CProduct1");
    // priceType: 0:消耗型商品; 1:非消耗型商品; 2:订阅型商品
    req.setPriceType(0);
    req.setDeveloperPayload("test");
    // 获取调用接口的Activity对象
    final Activity activity = getActivity();
    // 调用createPurchaseIntent接口创建托管商品订单
    Task<PurchaseIntentResult> task = Iap.getIapClient(activity).createPurchaseIntent(req);
    task.addOnSuccessListener(new OnSuccessListener<PurchaseIntentResult>() {
        @Override
        public void onSuccess(PurchaseIntentResult result) {
            // 获取创建订单的结果
            Status status = result.getStatus();
            if (status.hasResolution()) {
                try {
                    // 6666是您自定义的常量
                    // 启动IAP返回的收银台页面
                    status.startResolutionForResult(activity, 6666);
                } catch (IntentSender.SendIntentException exp) {
                }
            }
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(Exception e) {
            if (e instanceof IapApiException) {
                IapApiException apiException = (IapApiException) e;
                Status status = apiException.getStatus();
                int returnCode = apiException.getStatusCode();
            } else {
                // 其他外部错误
            }
        }
    });
    // 构造一个PurchaseIntentReq对象
    val req = PurchaseIntentReq()
    // 通过createPurchaseIntent接口购买的商品必须是您在AppGallery Connect网站配置的商品。
    req.setProductId("CProduct1")
    // priceType: 0:消耗型商品; 1:非消耗型商品; 2:订阅型商品
    req.setPriceType(0)
    req.setDeveloperPayload("test")
    // 获取调用接口的Activity对象
    val activity: Activity = getActivity()
    // 调用createPurchaseIntent接口创建托管商品订单
    val task = Iap.getIapClient(activity).createPurchaseIntent(req)
    task.addOnSuccessListener { result ->
        // 获取创建订单的结果
        val status: Status = result.status
        if (status.hasResolution()) {
            try {
                // 6666是您自定义的常量
                // 启动IAP返回的收银台页面
                status.startResolutionForResult(activity, 6666)
            } catch (exp: SendIntentException) {
            }
        }
    }.addOnFailureListener { e ->
        if (e is IapApiException) {
            val apiException = e as IapApiException
            val status: Status = apiException.status
            val returnCode = apiException.statusCode
        } else {
            // 其他外部错误
        }
    }

  2. 在您的应用拉起收银台并且当用户完成支付后(成功购买商品或取消购买),华为IAP会通过onActivityResult方式将此次支付结果返回给您的应用。您可以使用parsePurchaseResultInfoFromIntent方法获取包含结果信息的PurchaseResultInfo对象。

    • 当用户购买成功时,可从PurchaseResultInfo对象中获取到购买数据InAppPurchaseData及其签名数据,您需要使用在华为AppGallery Connect分配的公钥进行签名验证。为避免资金损失,您在验签成功后,必须校验InAppPurchaseData中的productId、price、currency等信息的一致性,验证一致后发货。验证方法和公钥获取请参见验证InAppPurchaseData。
      说明

      IAP SDK的InAppPurchaseData类可用于解析InAppPurchaseData字符串,您可使用该类构造一个InAppPurchaseData对象并从该对象中获取相关信息。

    • 用户购买消耗型商品时,如果返回以下支付异常则需要检查是否存在掉单情况,具体请参见消耗型商品的补单流程。
      • 支付失败(OrderStatusCode.ORDER_STATE_FAILED)
      • 已拥有该商品(OrderStatusCode.ORDER_PRODUCT_OWNED)
      • 默认返回码(OrderStatusCode.ORDER_STATE_DEFAULT_CODE)
        Java
        Kotlin
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == 6666) {
                if (data == null) {
                    Log.e("onActivityResult", "data is null");
                    return;
                }
                // 调用parsePurchaseResultInfoFromIntent方法解析支付结果数据
                PurchaseResultInfo purchaseResultInfo = Iap.getIapClient(this).parsePurchaseResultInfoFromIntent(data);
                switch(purchaseResultInfo.getReturnCode()) {
                    case OrderStatusCode.ORDER_STATE_CANCEL:
                        // 用户取消
                        break;
                    case OrderStatusCode.ORDER_STATE_FAILED:
                    case OrderStatusCode.ORDER_STATE_DEFAULT_CODE:
                    case OrderStatusCode.ORDER_PRODUCT_OWNED:
                        // 检查是否存在未发货商品
                        break;
                    case OrderStatusCode.ORDER_STATE_SUCCESS:
                        // 支付成功
                        String inAppPurchaseData = purchaseResultInfo.getInAppPurchaseData();
                        String inAppPurchaseDataSignature = purchaseResultInfo.getInAppDataSignature();
                        // 使用您应用的IAP公钥验证签名
                        // 若验签成功,必须校验InAppPurchaseData中的productId、price、currency等信息的一致性,验证一致后发货
                        // 若用户购买商品为消耗型商品,您需要在发货成功后调用consumeOwnedPurchase接口进行消耗
                        break;
                    default:
                        break;
                }
            }
        }
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            super.onActivityResult(requestCode, resultCode, data)
            if (requestCode == 6666) {
                if (data == null) {
                    Log.e("onActivityResult", "data is null")
                    return
                }
                // 调用parsePurchaseResultInfoFromIntent方法解析支付结果数据
                val purchaseResultInfo = Iap.getIapClient(this).parsePurchaseResultInfoFromIntent(data)
                when (purchaseResultInfo.returnCode) {
                    OrderStatusCode.ORDER_STATE_CANCEL -> {
                    }
                    OrderStatusCode.ORDER_STATE_FAILED, OrderStatusCode.ORDER_PRODUCT_OWNED, OrderStatusCode.ORDER_STATE_DEFAULT_CODE -> {
                    }
                    OrderStatusCode.ORDER_STATE_SUCCESS -> {
                        // 支付成功
                        val inAppPurchaseData = purchaseResultInfo.inAppPurchaseData
                        val inAppPurchaseDataSignature = purchaseResultInfo.inAppDataSignature
                    }
                    else -> {
                    }
                }
            }
        }

购买非PMS商品

非PMS商品支付仅支持消耗型商品和非消耗型商品。您的应用可通过createPurchaseIntentWithPrice接口发起购买请求。开发步骤如下:

  1. 构建请求参数PurchaseIntentWithPriceReq,发起createPurchaseIntentWithPrice请求。

    • 当接口请求成功时,IAP将返回一个PurchaseIntentResult对象,您需要使用IAP公钥对其包含的paymentData进行签名校验,具体请参见对返回结果验签。公钥获取方法请参见查询支付服务信息。验签成功之后,您的应用可通过PurchaseIntentResult对象getStatus方法获取Status对象,并通过Status对象的startResolutionForResult方法来启动华为IAP收银台。
      说明

      Status不支持序列化,请勿对IAP接口返回的Status对象执行序列化操作。

    • 当接口请求失败时,您的应用可获取到一个Exception对象。若该异常对象为IapApiException实例对象,使用其getStatusCode方法获取此次请求的返回码,具体请参见错误码。当返回下面的几种错误时,需要您进行处理:
      • 未登录(OrderStatusCode.ORDER_HWID_NOT_LOGIN)

        Iap在IapApiException对象中携带了一个Status对象,可通过其startResolutionForResult方法拉起华为帐号登录页面,用户操作结果通过Activity的onActivityResult方法回调。onActivityResult方法返回的Intent对象中携带的returnCode字段即为登录的结果,您可使用parseRespCodeFromIntent方法获取该字段。

        说明

        用户登录成功后,您需要重新调用createPurchaseIntentWithPrice接口才能完成支付。

      • 未同意协议(OrderStatusCode.ORDER_NOT_ACCEPT_AGREEMENT)

        可通过返回的IapApiException对象中的status拉起华为支付协议页面,处理方法同未登录场景。

        说明
        • 该错误场景必须处理,否则无法继续完成购买。
        • 该场景下用户同意协议后,您需要重新调用createPurchaseIntentWithPrice接口才能完成支付。
      • 已拥有该商品(OrderStatusCode.ORDER_PRODUCT_OWNED)

        若购买为消耗型商品,说明您的应用此前可能存在掉单的场景,需要触发补单操作,具体请参见消耗型商品的补单流程。若购买为非消耗型商品,则不能再次进行购买,请确认应用是否已经发货。

  2. 在您的应用拉起收银台并且当用户完成支付后,华为IAP会通过onActivityResult方式将这次支付结果返回给您的应用。可使用parsePurchaseResultInfoFromIntent方法获取包含结果信息的PurchaseResultInfo对象。

    • 当用户购买成功时,可从PurchaseResultInfo对象中获取到购买数据InAppPurchaseData及其签名数据,您需要使用在华为AppGallery Connect分配的公钥进行签名验证。为避免资金损失,您在验签成功后,必须校验InAppPurchaseData中的productId、price、currency等信息的一致性,验证一致后发货。验证方法和公钥获取请参见验证InAppPurchaseData。
      说明

      IAP SDK的InAppPurchaseData类可用于解析InAppPurchaseData字符串,您可使用该类构造一个InAppPurchaseData对象并从该对象中获取相关信息。

    • 用户购买消耗型商品时,如果返回以下支付异常则需要检查是否存在掉单情况,具体请参见消耗型商品的补单流程。
      • 支付失败(OrderStatusCode.ORDER_STATE_FAILED)
      • 已拥有该商品(OrderStatusCode.ORDER_PRODUCT_OWNED)
      • 默认返回码(OrderStatusCode.ORDER_STATE_DEFAULT_CODE)

具体的示例代码如下:

Java
Kotlin
// 构造PurchaseIntentWithPriceReq对象 
PurchaseIntentWithPriceReq req = new PurchaseIntentWithPriceReq(); 
req.setCurrency("CNY"); 
req.setDeveloperPayload("test");
// priceType: 0: 消耗型; 1: 非消耗型
req.setPriceType(0); 
req.setSdkChannel("1"); 
req.setProductName("test"); 
req.setAmount("1.00"); 
req.setProductId("ConsumableProduct1"); 
req.setServiceCatalog("XXX"); 
req.setCountry("CN"); 

// 获取调用接口的Activity对象
final Activity activity = getActivity(); 
// 调用createPurchaseIntentWithPrice接口
Task<PurchaseIntentResult> task = Iap.getIapClient(activity).createPurchaseIntentWithPrice(req); 
task.addOnSuccessListener(new OnSuccessListener<PurchaseIntentResult>() { 
    @Override 
    public void onSuccess(PurchaseIntentResult result) { 
        // 获取支付结果
        String paymentData = result.getPaymentData(); 
        String paymentSignature = result.getPaymentSignature(); 
        // 验证paymentData的签名 
        String publicKey = "XXX"; 
        // CipherUtil为自定义类,需要自定义doCheck方法 
        if (CipherUtil.doCheck(paymentData, paymentSignature, publicKey)) { 
            // 如果成功,则启动请求返回的IAP收银台页面
            Status status = result.getStatus(); 
            if (status.hasResolution()) { 
                try { 
                    status.startResolutionForResult(activity, 6666); 
                } catch (IntentSender.SendIntentException exp) { 
                } 
            } 
        } 
    } 
}).addOnFailureListener(new OnFailureListener() { 
    @Override 
    public void onFailure(Exception e) { 
        if (e instanceof IapApiException) { 
            IapApiException apiException = (IapApiException) e; 
            int returnCode = apiException.getStatusCode(); 
            switch (returnCode) { 
                case OrderStatusCode.ORDER_HWID_NOT_LOGIN: 
                case OrderStatusCode.ORDER_NOT_ACCEPT_AGREEMENT: 
                    // 帐号未登录或用户不同意支付协议 
                    Status status = apiException.getStatus(); 
                    if (status != null && status.hasResolution()) { 
                        try { 
                            status.startResolutionForResult(activity, 8888); 
                        } catch (IntentSender.SendIntentException exp) { 
                        } 
                    } 
                    break; 
                case OrderStatusCode.ORDER_PRODUCT_OWNED: 
                    // 检查是否存在未交付的商品
                    break; 
                default: 
                    break; 
            } 
        } 
    } 
}); 
// 支付结果回调
@Override 
protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
    if (requestCode == 6666) { 
        if (data == null) { 
            Log.e("onActivityResult", "data is null"); 
            return; 
        } 
        PurchaseResultInfo purchaseResultInfo = Iap.getIapClient(this).parsePurchaseResultInfoFromIntent(data); 
        switch(purchaseResultInfo.getReturnCode()) { 
            case OrderStatusCode.ORDER_STATE_CANCEL: 
                // 用户取消支付 
                break; 
            case OrderStatusCode.ORDER_STATE_FAILED: 
            case OrderStatusCode.ORDER_STATE_DEFAULT_CODE:
            case OrderStatusCode.ORDER_PRODUCT_OWNED: 
                // 检查是否存在未交付的商品
                break; 
            case OrderStatusCode.ORDER_STATE_SUCCESS: 
                // 支付成功 
                String inAppPurchaseData = purchaseResultInfo.getInAppPurchaseData(); 
                String inAppPurchaseDataSignature = purchaseResultInfo.getInAppDataSignature(); 
                // 使用您应用的IAP公钥验证签名
                // 若验签成功,必须校验InAppPurchaseData中的productId、price、currency等信息一致性,验证一致后发货
                // 若用户购买商品为消耗型商品,您需要在发货成功后调用consumeOwnedPurchase接口进行消耗
                break; 
            default: 
                break; 
        } 
        return; 
    } 
    if (requestCode == 8888) { 
        int returnCode = IapClientHelper.parseRespCodeFromIntent(data); 
        if (returnCode == OrderStatusCode.ORDER_STATE_SUCCESS) { 
            // 请求成功,需要再次发起createPurchaseIntentWithPrice请求 
        } 
        return; 
    } 
}
// 构造PurchaseIntentWithPriceReq对象 
val req = PurchaseIntentWithPriceReq()
req.setCurrency("CNY")
req.setDeveloperPayload("test")
// priceType: 0: 消耗型; 1: 非消耗型
req.setPriceType(0)
req.setSdkChannel("1")
req.setProductName("test")
req.setAmount("1.00")
req.setProductId("ConsumableProduct1")
req.setServiceCatalog("XXX")
req.setCountry("CN")

// 获取调用接口的Activity对象
val activity: Activity = getActivity()
// 调用createPurchaseIntentWithPrice接口
val task = Iap.getIapClient(activity).createPurchaseIntentWithPrice(req)
task.addOnSuccessListener { result ->
    // 获取支付结果
    val paymentData = result.getPaymentData()
    val paymentSignature = result.getPaymentSignature()
    // 验证paymentData的签名 
    val publicKey = "XXX"
    // CipherUtil为自定义类,需要自定义doCheck方法 
    if (CipherUtil.doCheck(paymentData, paymentSignature, publicKey)) {
        // 如果成功,则启动请求返回的IAP收银台页面
        val status: Status = result.status
        if (status.hasResolution()) {
            try {
                status.startResolutionForResult(activity, 6666)
            } catch (exp: SendIntentException) {
            }
        }
    }
}.addOnFailureListener { e ->
    if (e is IapApiException) {
        val apiException = e as IapApiException
        val returnCode = apiException.statusCode
        when (returnCode) {
            OrderStatusCode.ORDER_HWID_NOT_LOGIN, OrderStatusCode.ORDER_NOT_ACCEPT_AGREEMENT -> {
                // 帐号未登录或用户不同意支付协议 
                val status: Status? = apiException.status
                if (status != null && status.hasResolution()) {
                    try {
                        status.startResolutionForResult(activity, 8888)
                    } catch (exp: SendIntentException) {
                    }
                }
            }
            OrderStatusCode.ORDER_PRODUCT_OWNED -> {
            }
            else -> {
            }
        }
    }
}
// 支付结果回调
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == 6666) {
        if (data == null) {
            Log.e("onActivityResult", "data is null")
            return
        }
        val purchaseResultInfo = Iap.getIapClient(this).parsePurchaseResultInfoFromIntent(data)
        when (purchaseResultInfo.returnCode) {
            OrderStatusCode.ORDER_STATE_CANCEL -> {
            }
            OrderStatusCode.ORDER_STATE_FAILED, OrderStatusCode.ORDER_PRODUCT_OWNED, OrderStatusCode.ORDER_STATE_DEFAULT_CODE -> {
            }
            OrderStatusCode.ORDER_STATE_SUCCESS -> {
                // 支付成功 
                val inAppPurchaseData = purchaseResultInfo.inAppPurchaseData
                val inAppPurchaseDataSignature = purchaseResultInfo.inAppDataSignature
            }
            else -> {
            }
        }
        return
    }
    if (requestCode == 8888) {
        val returnCode = IapClientHelper.parseRespCodeFromIntent(data)
        if (returnCode == OrderStatusCode.ORDER_STATE_SUCCESS) {
            // 请求成功,需要再次发起createPurchaseIntentWithPrice请求 
        }
        return
    }
}

确认交易

用户完成一次支付之后,您需要根据购买数据InAppPurchaseData的purchaseState字段来判断订单是否已成功支付。若purchaseState为已支付(取值为0),您需要发放相应的商品或提供相应的服务,此后需要向华为IAP发送发货确认请求。

  • 对于消耗型商品,您需要从InAppPurchaseData JSON字符串中解析出purchaseToken信息,用于确认商品的发货状态。

    在成功发货并记录已发货的商品的purchaseToken之后,您的应用需要使用consumeOwnedPurchase接口消耗该商品,以此通知华为应用内支付服务器更新商品的发货状态。发送consumeOwnedPurchase请求时,请在请求参数中携带purchaseToken。应用成功执行消耗之后,华为应用内支付服务器会将相应商品重新设置为可购买状态,用户即可再次购买该商品。

    Java
    Kotlin
    // 构造ConsumeOwnedPurchaseReq对象
    ConsumeOwnedPurchaseReq req = new ConsumeOwnedPurchaseReq();
    String purchaseToken = "";
    try {
        // purchaseToken需从购买信息InAppPurchaseData中获取
        InAppPurchaseData inAppPurchaseDataBean = new InAppPurchaseData(inAppPurchaseData);
        purchaseToken = inAppPurchaseDataBean.getPurchaseToken();
    } catch (JSONException e) {
    }
    req.setPurchaseToken(purchaseToken);
    // 获取调用接口的Activity对象
    final Activity activity = getActivity();
    // 消耗型商品发货成功后,需调用consumeOwnedPurchase接口进行消耗
    Task<ConsumeOwnedPurchaseResult> task = Iap.getIapClient(activity).consumeOwnedPurchase(req);
    task.addOnSuccessListener(new OnSuccessListener<ConsumeOwnedPurchaseResult>() {
        @Override
        public void onSuccess(ConsumeOwnedPurchaseResult result) {
            // 获取接口请求结果
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(Exception e) {
            if (e instanceof IapApiException) {
                IapApiException apiException = (IapApiException) e;
                Status status = apiException.getStatus();
                int returnCode = apiException.getStatusCode();
            } else {
                // 其他外部错误
            }
        }
    });
    // 构造ConsumeOwnedPurchaseReq对象
    val req = ConsumeOwnedPurchaseReq()
    var purchaseToken = ""
    try {
        // purchaseToken需从购买信息InAppPurchaseData中获取
        val inAppPurchaseDataBean = InAppPurchaseData(inAppPurchaseData)
        purchaseToken = inAppPurchaseDataBean.purchaseToken
    } catch (e: JSONException) {
    }
    req.setPurchaseToken(purchaseToken)
    // 获取调用接口的Activity对象
    val activity: Activity = getActivity()
    // 消耗型商品发货成功后,需调用consumeOwnedPurchase接口进行消耗
    val task = Iap.getIapClient(activity).consumeOwnedPurchase(req)
    task.addOnSuccessListener {
        // 获取接口请求结果
    }.addOnFailureListener { e ->
        if (e is IapApiException) {
            val apiException = e as IapApiException
            val status: Status = apiException.status
            val returnCode = apiException.statusCode
        } else {
            // 其他外部错误
        }
    }
  • 对于非消耗型商品,华为应用内支付服务器默认返回已确认的购买数据,在用户购买成功之后无需确认交易。您需要在用户购买成功之后持续向用户提供相应的商品服务。具体请参见提供非消耗型商品对应的服务。
  • 对于订阅型商品,在用户购买成功后,无需您额外执行确认交易操作,但需要在订阅生效期间(InAppPurchaseData.subIsvalid = true)持续向用户提供相应的商品服务,具体请参见提供订阅型商品对应的服务。

提供非消耗型商品对应的服务

若您的应用为用户提供非消耗型商品,可在应用启动时通过obtainOwnedPurchases接口获取用户已购的非消耗型商品的购买信息,格式请参见InAppPurchaseData。若返回的购买信息列表不为空,请确认每个购买信息的purchaseState字段。若purchaseState为0,您需要提供相应的商品服务。

开发步骤如下:

  1. 使用obtainOwnedPurchases获取用户已购非消耗型商品的信息。
  2. 您的应用需要在请求参数OwnedPurchasesReq中指定查询的priceType为1。
  3. 为避免资金损失,您在验签成功后,必须校验每个InAppPurchaseData中的productId、price、currency等信息的一致性。验证方法请参考验证InAppPurchaseData。
  4. 验证成功后,您可从返回的每个商品信息中解析出purchaseState,用于判断当前商品的购买状态,以此作为您的应用的发货标志。
说明

IAP SDK的InAppPurchaseData类可用于解析InAppPurchaseData字符串,您可使用该类构造一个InAppPurchaseData对象并从该对象中获取相关信息。

Java
Kotlin
// 构造一个OwnedPurchasesReq对象
OwnedPurchasesReq ownedPurchasesReq = new OwnedPurchasesReq();
// priceType: 1:非消耗型商品
ownedPurchasesReq.setPriceType(1);
// 获取调用接口的Activity对象
final Activity activity = getActivity();
// 调用obtainOwnedPurchases接口
Task<OwnedPurchasesResult> task = Iap.getIapClient(activity).obtainOwnedPurchases(ownedPurchasesReq);
task.addOnSuccessListener(new OnSuccessListener<OwnedPurchasesResult>() {
    @Override
    public void onSuccess(OwnedPurchasesResult result) {
        // 获取接口请求结果
        if (result != null && result.getInAppPurchaseDataList() != null) {
            for (int i = 0; i < result.getInAppPurchaseDataList().size(); i++) {
                String inAppPurchaseData = result.getInAppPurchaseDataList().get(i);
                String inAppSignature = result.getInAppSignature().get(i);
                // 您需要使用您的应用的IAP公钥验证inAppPurchaseData的签名
                // 如果验签成功,为避免资金损失,必须校验InAppPurchaseData中的productId、price、currency等信息的一致性
                // 验证一致后,请检查支付状态
                try {
                    InAppPurchaseData inAppPurchaseDataBean = new InAppPurchaseData(inAppPurchaseData);
                    int purchaseState = inAppPurchaseDataBean.getPurchaseState();
                } catch (JSONException e) {
                }
            }
        }
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(Exception e) {
        if (e instanceof IapApiException) {
            IapApiException apiException = (IapApiException) e;
            Status status = apiException.getStatus();
            int returnCode = apiException.getStatusCode();
        } else {
            // 其他外部错误
        }
    }
});
// 构造一个OwnedPurchasesReq对象
val ownedPurchasesReq = OwnedPurchasesReq()
// priceType: 1:非消耗型商品
ownedPurchasesReq.setPriceType(1)
// 获取调用接口的Activity对象
val activity: Activity = getActivity()
// 调用obtainOwnedPurchases接口
val task = Iap.getIapClient(activity).obtainOwnedPurchases(ownedPurchasesReq)
task.addOnSuccessListener { result ->
    // 获取接口请求结果
    if (result != null && result.getInAppPurchaseDataList() != null) {
        for (i in result.getInAppPurchaseDataList().indices) {
            val inAppPurchaseData = result.getInAppPurchaseDataList()[i]
            val inAppSignature = result.getInAppSignature()[i]
            // 您需要使用您的应用的IAP公钥验证inAppPurchaseData的签名
            // 如果验签成功,为避免资金损失,必须校验InAppPurchaseData中的productId、price、currency等信息的一致性
            // 验证一致后,请检查支付状态
            try {
                val inAppPurchaseDataBean = InAppPurchaseData(inAppPurchaseData)
                val purchaseState = inAppPurchaseDataBean.purchaseState
            } catch (e: JSONException) {
            }
        }
    }
}.addOnFailureListener { e ->
    if (e is IapApiException) {
        val apiException = e as IapApiException
        val status: Status = apiException.status
        val returnCode = apiException.statusCode
    } else {
        // 其他外部错误
    }
}

提供订阅型商品对应的服务

在应用提供服务前,始终应该先判断用户已购的订阅型商品的状态,以决定是否应该提供对应的服务。

可通过下面的方式获取购买数据InAppPurchaseData。

  • 在商品购买完成后,可以从HMS Core(APK)返回信息里获取InAppPurchaseData的JSON字符串。
  • 可以通过IapClient.obtainOwnedPurchases查询已购的订阅型商品。

对依赖当期续费情况的业务(比如:视频会员),若purchaseState为0且InApppurchaseData.subIsvalid为true,则始终应该为用户提供商品对应的服务。

开发步骤如下:

  1. 使用obtainOwnedPurchases获取用户已购订阅型商品的信息。您的应用需要在请求参数OwnedPurchasesReq中指定查询的priceType为2。
  2. 为避免资金损失,您在验签成功后,必须校验每个商品购买数据中的productId、price、currency等信息的一致性。验证方法请参考验证InAppPurchaseData。
  3. 验证成功后,您可从返回的每个商品购买数据中解析出purchaseState和subIsvalid,用于判断当前商品的购买状态,以此作为您的应用的发货标志。
Java
Kotlin
// 构造一个OwnedPurchasesReq对象
OwnedPurchasesReq ownedPurchasesReq = new OwnedPurchasesReq();
// priceType: 2:订阅型商品
ownedPurchasesReq.setPriceType(2);
// 获取调用接口的Activity对象
final Activity activity = getActivity();
// 调用obtainOwnedPurchases接口
Task<OwnedPurchasesResult> task = Iap.getIapClient(activity).obtainOwnedPurchases(ownedPurchasesReq);
task.addOnSuccessListener(new OnSuccessListener<OwnedPurchasesResult>() {
    @Override
    public void onSuccess(OwnedPurchasesResult result) {
        // 获取接口请求结果
        if (result != null && result.getInAppPurchaseDataList() != null) {
            for (int i = 0; i < result.getInAppPurchaseDataList().size(); i++) {
                String inAppPurchaseData = result.getInAppPurchaseDataList().get(i);
                String inAppSignature = result.getInAppSignature().get(i);
                // 您需要使用您的应用的IAP公钥验证inAppPurchaseData的签名
                // 如果验签成功,为避免资金损失,必须校验InAppPurchaseData中的productId、price、currency等信息的一致性
                // 验证一致后,请检查支付状态和订阅状态
                try {
                    InAppPurchaseData inAppPurchaseDataBean = new InAppPurchaseData(inAppPurchaseData);
                    int purchaseState = inAppPurchaseDataBean.getPurchaseState();
                    boolean isSubValid = inAppPurchaseDataBean.isSubValid();
                } catch (JSONException e) {
                }
            }
        }
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(Exception e) {
        if (e instanceof IapApiException) {
            IapApiException apiException = (IapApiException) e;
            Status status = apiException.getStatus();
            int returnCode = apiException.getStatusCode();
        } else {
            // 其他外部错误
        }
    }
});
// 构造一个OwnedPurchasesReq对象
val ownedPurchasesReq = OwnedPurchasesReq()
// priceType: 2:订阅型商品
ownedPurchasesReq.setPriceType(2)
// 获取调用接口的Activity对象
val activity: Activity = getActivity()
// 调用obtainOwnedPurchases接口
val task = Iap.getIapClient(activity).obtainOwnedPurchases(ownedPurchasesReq)
task.addOnSuccessListener { result ->
    // 获取接口请求结果
    if (result != null && result.getInAppPurchaseDataList() != null) {
        for (i in result.getInAppPurchaseDataList().indices) {
            val inAppPurchaseData = result.getInAppPurchaseDataList()[i]
            val inAppSignature = result.getInAppSignature()[i]
            // 您需要使用您的应用的IAP公钥验证inAppPurchaseData的签名
            // 如果验签成功,为避免资金损失,必须校验InAppPurchaseData中的productId、price、currency等信息的一致性
            // 验证一致后,请检查支付状态和订阅状态
            try {
                val inAppPurchaseDataBean = InAppPurchaseData(inAppPurchaseData)
                val purchaseState = inAppPurchaseDataBean.purchaseState
                val isSubValid = inAppPurchaseDataBean.isSubValid
            } catch (e: JSONException) {
            }
        }
    }
}.addOnFailureListener { e ->
    if (e is IapApiException) {
        val apiException = e as IapApiException
        val status: Status = apiException.status
        val returnCode = apiException.statusCode
    } else {
        // 其他外部错误
    }
}

对其他自定义业务,如果需要往期的续费情况(比如:杂志订阅),可以通过IapClient.obtainOwnedPurchaseRecord查询所有订阅型商品的收据,收据格式同InAppPurchaseData。

  • 如果InAppPurchaseData.purchaseState=0,标识该订阅型商品为已购状态。
  • 识别从InAppPurchaseData.purchaseTime到InAppPurchaseData.expirationDate这个时间区间,表示商品的有效时间段。

如果订阅型商品为已购状态,且商品有效时间段匹配上您合法的服务提供时间段,则应该提供商品对应的服务。

本质上,所有的订阅型商品都可以通过这种方式去判断是否应该提供服务。

(可选)查看用户购买历史

对于消耗型商品,可使用obtainOwnedPurchaseRecord接口获取用户所有已消耗的商品信息。若提供订阅型商品,也可使用该接口获取用户的订阅收据。

Java
Kotlin
// 构造一个OwnedPurchasesReq对象
OwnedPurchasesReq req = new OwnedPurchasesReq();
// priceType: 0:消耗型商品; 2:订阅型商品
req.setPriceType(0);
// 获取调用接口的Activity对象
final Activity activity = getActivity();
// 调用obtainOwnedPurchaseRecord接口
Task<OwnedPurchasesResult> task = Iap.getIapClient(activity).obtainOwnedPurchaseRecord(req);
task.addOnSuccessListener(new OnSuccessListener<OwnedPurchasesResult>() {
    @Override
    public void onSuccess(OwnedPurchasesResult result) {
        // 获取接口请求的结果
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(Exception e) {
        if (e instanceof IapApiException) {
            IapApiException apiException = (IapApiException) e;
            int returnCode = apiException.getStatusCode();
        } else {
            // 其他外部错误
        }
    }
});
// 构造一个OwnedPurchasesReq对象
val req = OwnedPurchasesReq()
// priceType: 0:消耗型商品; 2:订阅型商品
req.setPriceType(0)
// 获取调用接口的Activity对象
val activity: Activity = getActivity()
// 调用obtainOwnedPurchaseRecord接口
val task = Iap.getIapClient(activity).obtainOwnedPurchaseRecord(req)
task.addOnSuccessListener {
    // 获取接口请求的结果
}.addOnFailureListener { e ->
    if (e is IapApiException) {
        val apiException = e as IapApiException
        val returnCode = apiException.statusCode
    } else {
        // 其他外部错误
    }
}