一.简介
华为应用内支付服务(In-App Purchases,IAP)为App提供便捷的应用内支付体验和简便的接入流程。App通过集成华为应用内支付SDK,再调用SDK接口启动IAP收银台,即可实现应用内支付。
通过应用内支付服务,用户可以在App内购买各种类型的虚拟商品,包括一次性商品和订阅型商品
一次性商品:
用户以一次性付款方式购买的商品,包括消耗型商品和非消耗型商品
消耗型商品:
使用一次后即消耗掉,随使用减少,需要再次购买的商品。例:游戏货币,游戏道具等
非消耗型商品:
一次性购买,永久拥有,无需消耗.例:游戏中额外的游戏关卡、应用中无时限的高级会员等
订阅型商品:
用户购买后在一段时间内允许访问增值功能或内容,周期结束后自动续期购买下一期的服务。例:应用中有时限的高级会员,如视频月度会员
二.场景介绍
场景一:购买PMS商品
华为应用内支付服务包含商品管理系统(Product Management System,PMS),在华为AppGallery Connect网站录入商品ID和定价之后,即可托管商品。应用需使用createPurchaseIntent接口发起此类商品的购买,发起购买时只需传入此处配置的商品ID和商品类型
当前最新版本支持以下功能:
应用(含游戏)发布:一个应用(含游戏)包发布、支持多个国家/地区应用内商品的价格和语言管理。PMS可支持的国家/地区以配置商品价格时的页面提示为准
币种与语言:华为为每个国家/地区指定一个默认币种和一个默认语言,不支持一个国家/地区配置多种语言
商品定价:华为根据商家设置的汇率换算价格(含税)和汇率提供已上线华为支付各国参考价,商家可自行修改各国价格。例如,假设您输入的是1欧元,会根据汇率自动设置其他国家/地区的价格,如果欧元和人民币之间的汇率是7.88,会自动将中国的价格设置为7.88元,商家可在此基础上修改价格。
汇率:固定汇率,此汇率非实时汇率,华为将根据此汇率更新货币价格。华为更新汇率不会影响商家的商品价格。如需变更商品价格,商家可手动修改。
使用对象:全球开发者。若商家准备将应用(含游戏)发布到多个国家/地区,则需要对应用内商品的价格和语言进行管理以实现本地化语言和价格展示。
场景二:购买非PMS商品
若商家的应用仅在中国大陆发布,也可使用IAP提供的createPurchaseIntentWithPrice接口自行完成商品的定价及支付,而不需要在华为AppGallery Connect网站上录入商品信息。支付的时候需要商家传入商品的价格,商品名称、币种等信息。需要注意,该非PMS支付接口仅支持消耗型和非消耗型商品,若您需要使用订阅服务,请使用createPurchaseIntent。
注意
为避免资金损失,您在对支付结果验签成功后,必须校验 InAppPurchaseData 中的productId、price、currency等信息的一致性。
createPurchaseIntentWithPrice 接口将不再更新维护,不推荐使用,建议您使用 createPurchaseIntent 替换 createPurchaseIntentWithPrice ,以免影响后续版本升级。
三.使用入门
1.Android系统
(1).开发环境
JDK 1.8及以上版本
安装Android Studio 3.6.1及以上版本
minSdkVersion 19及以上
targetSdkVersion 30(推荐)
compileSdkVersion 30(推荐)
Gradle 5.4.1及以上(推荐)
如果同时使用多个HMS Core的服务,则需要使用各个Kit对应的最大值。
测试应用的设备:EMUI 3.0及以上的华为手机或Android 4.4及以上的非华为手机
(2).开发流程
需要按照流程来完成应用的开发工作,完整的开发流程如下
序号 | 步骤 | 说明 |
1 | 在开发应用前,需要在AppGallery Connect中配置相关信息。包括:注册成为开发者、开通商户服务、创建应用、生成签名证书指纹、配置签名证书指纹、打开相关服务、配置支付服务参数。 | |
2 | 在开发应用前,您需要将IAP SDK集成到您的开发环境中。 | |
3 | 编译APK前需要配置混淆配置文件,避免混淆HMS Core SDK导致功能异常。 | |
4 | 若您的应用提供PMS商品,则需要在AppGallery Connect中完成商品配置,在您的客户端调用购买接口时,只需传入此处配置的商品ID和商品类型。 | |
5 | IAP支持的商品类型包括:消耗型商品、非消耗型商品、订阅型商品。 开发商品购买提供了3种商品购买的接入流程。若您的应用提供消耗型商品,则需额外在您的应用中添加消耗型商品的补单流程,订阅相关的说明可参见订阅专用功能说明。 开发者根据业务需求,完成相应功能开发。 | |
6 | 当您需要与IAP服务器进行业务调用(如Order服务需调用IAP服务器提供的验证购买Token接口),或仅仅依靠您的客户端无法处理更加复杂的业务逻辑时,可开发服务端以满足业务需求。 | |
7 | 华为提供对应用自动检查的能力。 | |
8 | 开发完成后需要在AppGallery Connect中将应用信息补充完整并提交上架申请。 |
(3).开发准备
见官网: https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/config-agc-0000001050033072
(4).应用开发
1).商品购买流程
1.1消耗型商品购买流程
消耗型商品典型购买流程如下:

用户发起购买后,应用客户端向HMS Core(APK)发起购买请求,携带商品ID、商品类型等信息。
HMS Core(APK)生成订单,返回IAP收银台。
应用客户端启动IAP收银台。
说明
若使用 createPurchaseIntentWithPrice 接口发起支付,您需要使用IAP公钥对接口返回的 paymentData 数据进行签名校验,具体请参见 对返回结果验签 。公钥获取方法请参见 查询支付服务信息 。验签成功后再启动IAP收银台。
用户完成支付后,HMS Core(APK)返回购买数据InAppPurchaseData(包含是否购买成功、订单ID、商品ID、购买Token等 )及其签名数据。
应用客户端向应用服务器上报购买数据及其签名数据。
(建议)若您的应用对安全性要求较高,可通过服务端Order服务验证购买Token接口,向IAP服务器查询购买数据及其签名数据,通过此接口可进一步确认订单的准确性。验证购买Token时,需携带购买Token和商品ID信息。
(建议)IAP服务端返回购买数据及其签名数据。
应用服务器使用IAP公钥验证购买数据签名。为避免资金损失,您在验签成功后,必须校验InAppPurchaseData中的productId、price、currency等信息的一致性。验证方法和公钥获取方式可参见验证InAppPurchaseData。
验证成功后,发放商品,记录商品的发货状态(购买Token和商品ID)。
注意
务必保存已发货商品的购买Token,后续即使消耗失败也可从您的服务器获取商品的发货状态,避免重复发货的情况。
应用服务器返回发货结果给应用客户端。
应用通知IAP服务器更新商品发货状态。请务必确保发货成功后再调用本步骤。发货成功后应用客户端向HMS Core(APK)发送consumeOwnedPurchase请求以消耗该商品,以此通知IAP服务器更新商品的发货状态。发送consumeOwnedPurchase请求时,需携带购买数据中的purchaseToken。您也可使用服务端Order服务确认购买接口消耗商品,用于替换IAP客户端consumeOwnedPurchase接口。
注意
如果您在消耗之后再进行发货,将存在以下异常场景:您的应用发起消耗请求之后,华为应用内服务器已消耗成功但因网络异常等原因没有成功将结果回调给您的应用,该情况将造成服务器已经消耗但是应用没有发货的场景。此场景后续很难处理,为了避免不必要的问题,您需要在发货成功之后再执行消耗操作。
HMS Core(APK)向应用返回消耗结果。应用成功执行消耗之后,IAP服务器会将相应商品重新设置为可购买状态,用户即可再次购买该商品。
说明
当前流程对单机游戏同样适用,只需要将流程图中服务端的处理放在客户端即可。
开发过程请参见 开发商品购买 。
消耗型商品请务必实现 消耗型商品的补单流程 ,避免掉单问题发生。
1.2非消耗型商品购买流程
非消耗型商品典型购买流程如下:

用户发起购买后,应用客户端向HMS Core(APK)发起购买请求,携带商品ID、商品类型等信息。
HMS Core(APK)生成订单,返回IAP收银台。
应用客户端启动IAP收银台。
说明
若使用 createPurchaseIntentWithPrice 接口发起支付,您需要使用IAP公钥对接口返回的 paymentData 数据进行签名校验,具体请参见 对返回结果验签 。公钥获取方法请参见 查询支付服务信息 。验签成功后再启动IAP收银台。
用户完成支付后,HMS Core(APK)返回购买数据InAppPurchaseData(包含是否购买成功、订单ID、商品ID、购买Token等)及其签名数据。
应用客户端向应用服务器上报购买数据及其签名数据。
(建议)若您的应用对安全性要求较高,可通过服务端Order服务验证购买Token接口,向IAP服务器查询购买数据及其签名数据,通过此接口可进一步确认订单的准确性。验证购买Token时,需携带购买Token和商品ID信息。
(建议)IAP服务端返回购买数据及其签名数据。
应用服务器使用IAP公钥验证购买数据签名。为避免资金损失,您在验签成功后,必须校验InAppPurchaseData中的productId、price、currency等信息的一致性。验证方法和公钥获取方式可参见验证InAppPurchaseData。
验证成功之后需通过购买数据中的purchaseState字段判断商品的购买状态。当购买状态为已购买(purchaseState=0)时,发放相应商品。
说明
对于非消耗型商品,用户只需购买一次就可以永久拥有该商品。后期提供服务请参见 提供非消耗型商品对应的服务 。
开发过程请参见 开发商品购买 。
1.3订阅型商品购买流程
订阅型商品典型购买流程如下:

用户发起购买后,应用客户端向HMS Core(APK)发起createPurchaseIntent购买请求,携带商品ID、商品类型等信息。
HMS Core(APK)生成订单,返回IAP收银台。
应用客户端启动IAP收银台。
用户完成支付后,HMS Core(APK)返回购买数据InAppPurchaseData(包含是否购买成功、订阅ID、商品ID、购买Token等 )及其签名数据。
应用客户端向应用服务器上报购买数据及其签名数据。
(建议)若您的应用对安全性要求较高,可通过服务端Subscription服务验证购买Token接口,向IAP服务器查询购买数据及签名数据,通过此接口可进一步确认订单的准确性。验证购买Token时,需携带购买Token和订阅ID信息。
(建议)IAP服务端返回购买数据及其签名数据。
应用服务器使用IAP公钥验证购买数据签名。为避免资金损失,您在验签成功后,必须校验InAppPurchaseData中的productId、price、currency等信息的一致性。验证方法和公钥获取方式可参见验证InAppPurchaseData。
验证成功后,向用户提供相应的商品服务。用户购买订阅型商品后,在订阅仍生效期间,您的应用需要持续向用户提供商品服务,步骤如下:
应用客户端向HMS Core(APK)发送obtainOwnedPurchases请求查询已购订阅型商品。
HMS Core(APK)返回用户所有已订阅且正生效的商品的购买数据InAppPurchaseData(包含是否购买成功、订阅ID、商品ID、购买Token等)以及每个购买数据的签名数据。
应用客户端向应用服务器上报购买数据及其签名数据。
应用服务器使用IAP公钥验证购买数据签名。为避免资金损失,您在验签成功后,必须校验InAppPurchaseData中的productId、price、currency等信息的一致性。验证方法和公钥获取方式可参见验证InAppPurchaseData。
验证成功后,向用户提供相应的商品服务。
说明
开发过程请参见 订阅专用功能说明 和 开发商品购买 。
2).开发商品购买
2.1判断是否支持应用内支付
在使用应用内支付之前,您的应用需要向HMS Core(APK)发送 isEnvReady请求,以此判断用户当前登录的华为帐号所在的服务地是否在华为IAP支持结算的国家/地区中。
说明
若您的应用上架AppTouch,需使用 isEnvReady (boolean isSupportAppTouch)接口替换 isEnvReady ()接口,并指定参数为true。
2.2展示商品信息
若您的应用提供 PMS商品,则需要在华为AppGallery Connect网站上完成商品的配置。商品配置完成后,您需要在您的应用中使用 obtainProductInfo接口来获取此类商品的详细信息。
开发步骤如下:
2.2.1构建请求参数ProductInfoReq,发起obtainProductInfo请求并设置OnSuccessListener和OnFailureListener回调监听器以接收接口请求的结果。您需要在ProductInfoReq中携带您此前已在华为AppGallery Connect网站上定义并生效的商品ID,并根据实际配置的商品指定其priceType。
说明
obtainProductInfo 每次只能查询一种类型的商品。
2.2.2当接口请求成功时,IAP将返回一个ProductInfoResult对象,您的应用可通过该对象的getProductInfoList方法获取到包含了单个商品信息的ProductInfo对象的列表。您可以使用ProductInfo对象包含的商品价格、名称和描述等信息,向用户展示可供购买的商品列表
2.3发起购买
2.3.1购买PMS商品
PMS商品指在华为AppGallery Connect网站上配置的商品,支持消耗型、非消耗型和订阅型商品。您的应用可通过 createPurchaseIntent接口发起此类商品的购买请求。
开发步骤如下:
2.3.1.1
构建请求参数PurchaseIntentReq,发起createPurchaseIntent请求。您需要在PurchaseIntentReq中携带您此前已在AppGallery Connect网站上定义并生效的商品ID。当接口请求成功时,您可获取到一个PurchaseIntentResult对象,其getStatus方法返回了一个Status对象,您的应用需要通过Status对象的startResolutionForResult方法来启动华为IAP收银台。
说明: Status 不支持序列化,请勿对IAP接口返回的 Status 对象执行序列化操作。
2.3.1.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)
2.3.2购买非PMS商品
非PMS商品支付仅支持消耗型商品和非消耗型商品。您的应用可通过 createPurchaseIntentWithPrice接口发起购买请求。
开发步骤如下:
2.3.2.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.3.2.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)
2.4确认交易
用户完成一次支付之后,您需要根据购买数据 InAppPurchaseData的purchaseState字段来判断订单是否已成功支付。若purchaseState为已支付(取值为0),您需要发放相应的商品或提供相应的服务,此后需要向华为IAP发送发货确认请求。
对于消耗型商品,您需要从InAppPurchaseData JSON字符串中解析出purchaseToken信息,用于确认商品的发货状态。
在成功发货并记录已发货的商品的purchaseToken之后,您的应用需要使用 consumeOwnedPurchase接口消耗该商品,以此通知华为应用内支付服务器更新商品的发货状态。发送 consumeOwnedPurchase请求时,请在请求参数中携带purchaseToken。应用成功执行消耗之后,华为应用内支付服务器会将相应商品重新设置为可购买状态,用户即可再次购买该商品
对于非消耗型商品,华为应用内支付服务器默认返回已确认的购买数据,在用户购买成功之后无需确认交易。您需要在用户购买成功之后持续向用户提供相应的商品服务。具体请参见提供非消耗型商品对应的服务。
对于订阅型商品,在用户购买成功后,无需您额外执行确认交易操作,但需要在订阅生效期间(InAppPurchaseData.subIsvalid = true)持续向用户提供相应的商品服务,具体请参见提供订阅型商品对应的服务。
3).消耗型商品的补单流程
在用户完成消耗型商品的支付之后,若出现异常(网络错误、进程被中止等)将导致应用无法知道用户实际是否支付成功,即出现掉单情况。华为应用内支付针对此场景,提供了 消耗型商品的补单机制。您的应用可参考以下流程图进行处理:

你需要在以下场景触发补单机制:
应用启动时。
购买请求返回-1(OrderStatusCode.ORDER_STATE_FAILED)时。
购买请求返回60051(OrderStatusCode.ORDER_PRODUCT_OWNED)时。
购买请求返回1(OrderStatusCode.ORDER_STATE_DEFAULT_CODE)时。
开发步骤如下:
3.1使用obtainOwnedPurchases获取用户已购未发货的消耗型商品的购买信息。您的应用需要在请求参数OwnedPurchasesReq中指定查询的priceType为0。
当接口请求成功时,IAP将返回一个 OwnedPurchasesResult对象,该对象包含用户所有已购但未发货的商品购买信息及其签名数据,您需要使用在华为AppGallery Connect分配的公钥进行签名验证。为避免资金损失,您在验签成功后,必须校验 InAppPurchaseData中的productId、price、currency等信息的一致性。验证方法请参见 验证InAppPurchaseData。
每个购买信息均以JSON格式的String形式呈现,包含的参数请参见 InAppPurchaseData。验证成功后,您需要从 InAppPurchaseData的字符串中解析出purchaseState字段,当purchaseState为0时表示此次交易是成功的,您的应用仅需要对这部分商品进行补发货操作。
说明: IAP SDK的 InAppPurchaseData 类可用于解析 InAppPurchaseData 字符串,您可使用该类构造一个 InAppPurchaseData 对象并从 InAppPurchaseData 对象中获取相关信息。
3.2使用consumeOwnedPurchase接口对已发货商品进行消耗。
您需要对 obtainOwnedPurchases返回的每个商品数据进行发货确认,确认已发货后使用 consumeOwnedPurchase接口消耗所有已发货商品,以此通知华为应用内支付服务器更新商品的发货状态。对于消耗型商品,应用成功执行消耗之后,华为服务器会将相应商品重新设置为可购买状态,用户即可再次购买该商品。
2.HarmonyOS系统
见官网: https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/harmonyos-js-introduction-0000001313465237
四.服务端下单以及回调校验
1.消耗型商品和非消耗型商品Order服务验证购买Token
在完成一笔支付后,您可从HMS Core(APK)获取到商品购买单详情的JSON字符串 InAppPurchaseData及其签名字符串。 InAppPurchaseData中有用于唯一标识商品和用户对应关系的purchaseToken。通过签名验证,可确保 InAppPurchaseData有效性。您可以解析出 InAppPurchaseData中的purchaseToken数据,调用华为应用内支付服务器提供的验证购买Token的接口,进一步验证购买信息的正确性
购买Token校验流程如下图:

具体请参见Order服务验证购买Token
2.订阅型商品Subscription服务验证购买Token
在完成一笔支付后,HMS Core(APK)会返回商品购买单详情JSON字符串 InAppPurchaseData和签名字符串。 InAppPurchaseData中有用于唯一标识商品和用户对应关系的purchaseToken。通过签名验证,可确保 InAppPurchaseData有效性。您还可以解析出 InAppPurchaseData中的purchaseToken数据,调用华为应用内支付服务器提供的Subscription服务验证购买Token接口,获取购买数据及其签名数据。为避免资金损失,您在验签成功后,必须校验 InAppPurchaseData中的productId、price、currency等信息的一致性,验证一致后发货。验证方法请参考 验证InAppPurchaseData。通常应该先判断商品是否已经成功购买,对于订阅型商品返回最近的收费情况,包括什么时候收费以及是否在有效期内。
具体实现请参见Subscription服务验证购买Token
3.Order服务确认购买
用户购买消耗型商品之后需要进行确认(即消耗)。例如,当用户成功购买一个游戏道具,IAP会将此道具购买成功的订单加上标记,当您确认购买之后,该订单标记被取消。例如,用户第一次购买了游戏道具,如果没有调用消耗接口,在用户发起第二次购买时,会提示该商品还没有消耗,需要您调用确认接口,消耗第一次购买的商品,表明用户已经完成第一次商品的购买,才可以购买下一次。消耗的好处,可以避免用户重复下单购买(若未执行消耗商品操作,不允许再次购买此道具),可以避免漏单。
具体实现请参见Order服务确认购买
4.验证InAppPurchaseData
在接口调用过程中,请求方在获取接收方的响应结果后,返回结果中包含了 InAppPurchaseData及其签名字符串,请求方可以对签名字符串使用IAP公钥进行验签,确认返回结果没有被篡改。公钥获取参见 查询支付服务信息。建议您把公钥存放在服务端并在服务端来完成签名校验,保证接口调用的安全性。
4.1获取需要验签的返回结果字符串
例如 obtainOwnedPurchases接口返回的inAppPurchaseDataList(购买数据 InAppPurchaseData的JSON字符串列表)需要验签,先取inAppPurchaseDataList的第1条字符串参与验签。
4.2获取对应的签名字符串
例如 obtainOwnedPurchases接口返回的inAppSignature(对应inAppPurchaseDataList的签名字符串列表),取inAppSignature的第1条签名字符串参与验签。
4.3使用IAP公钥对结果字符串和对应的签名字符串进行验签
可从返回对象( PurchaseResultInfo、 OwnedPurchasesResult和 ConsumeOwnedPurchaseResult)中获取signatureAlgorithm(例如: OwnedPurchasesResult.getSignatureAlgorithm),然后使用获取到的算法进行验签。若获取到的算法为空,则使用SHA256WithRSA算法进行验签。
4.4验证信息后发货
为避免资金损失,您在验签成功后,必须校验 InAppPurchaseData中的productId、price、currency等信息的一致性,验证一致后发货
五.具体的后端业务代码
1.购买非PMS商品的业务逻辑
语言以php7.2版本,框架yii2为参考, 购买非PMS商品的业务逻辑,步骤如下:
1).获取商品列表
客户端获取app设置的商品列表(后台设置商品相关代码,提供给客户端商品列表所需的api接口),展示到app页面
2).下单
用户点击购买操作,向服务端发送购买请求,服务端生成订单后,返回给客户端华为内支付服务所需要的数据
/**
* 华为内支付下单
* @param array $params 订单信息
* @return array
*/
public static function Pay($params)
{
//获取商品内部订单号
$innerOno = self::genInnerOno(date("Y-m-d H:i:s"), $params['user_id'], $paymentType);
//生成订单
$order = new Order();
$order->order_id = $innerOno; // 内部订单号
$order->product_name = $params['name']; // 商品名称
$order->number = $params['num']; // 购买商品个数
$order->price = $params['price']; // 商品单价, 1个商品价格
$order->total_price = $params['total_price']; // 总的价格
$order->pay_amount = $params['pay_amount']; // 应付总额: 实际应该支付的价格= 总金额 - 优惠金额
$order->count = $params['count']; // 购买后得到商品对应的产品数量
$order->status = 0; // 状态:0 待支付
$order->user_id = $params['user_id']; // 用户游戏ID
$order->expired_at = $params['expired_at']; // 过期时间
$order->created_by = $params['created_by']; // 创建者
$order->updated_by = $params['updated_by']; // 创建者
$result = $order->save();
if (!$result) {
$errors = '';
if ($order->hasErrors()) {
$tmp = $order->getErrors();
foreach ($tmp as $rows) {
foreach ($rows as $row) {
$errors .= $row . '<br/>';
}
}
}
throw new \Exception($errors);
}
$orderId = $order->id;
//构建华为支付所需要的数据
$title = $order->product_name;
$title = replaceStr($title);
$price = $order->pay_amount;
//构建向华为内支付发送的参数
$data = [
'amount' => $price, //单位:元
'country' => "CN", // 国家/地区码: CN 中国大陆
'currency' => "CNY", // 币种:CNY 中国大陆
'priceType' => 0, //商品类型:0:消耗型商品; 1:非消耗型商品; 2:订阅型商品; 不设置时默认商品类型为0,即消耗型商品
'productId' => $model->product_id, //应用自定义的商品ID,商品ID用于唯一标识一个商品
'productName' => $title, //商品名称,由应用自定义
'order_id' => $order_id, //内部订单号
'sdkChannel' => 1, //渠道信息:0 代表自有应用,无渠道; 1:代表应用市场渠道; 2:表预装渠道; 3:代表游戏中心; 4:代表运动健康渠道
'serviceCatalog' => "X6", //商品所属的产品类型 X4:主题; X5:应用商店; X6:游戏;
];
return $data;
}
/**
* 生成本系统内部订单号, 在向第三方服务商发起支付时需要使用.
*
* 下单时的处理流程:
* 1. 属性 ono 不要赋值, 成功插入(保存)订单.
* 1. 支付前生成本系统内部订单号, 向第三方发起支付请求.
* 1. 接收到支付成功回调通知时, 将第三方订单号保存到本次付款的订单的 ono 字段上.
*
* 格式: 下单时间+支付方式+ 4位数字修正值. 如 "20150930140041+1+1234".
* 最大长度 32 个字符. 这也是 wx 可接受的订单号最大长度.
*
* @param int $createdAt 订单创建时间戳, 即 下单时间戳.
* @param int $createdBy 订单创建人 ID, 即 下单会员 ID.
* @return string
* @throws \Exception
*/
public static function genInnerOno($createdAt, $createdBy)
{
$createdAt = strtotime($createdAt);
if (1 > $createdAt || 1 > $createdBy) {
throw new \Exception('参数错误');
}
// 生成一个修正值, 一定程度上增加订单号的随机性.
$tmp = intval(substr($createdAt, -4)) + intval(substr($createdBy, -2));
if (4 < strlen($tmp)) {
$tmp = substr($tmp, -4);
}
$tmp = date('YmdHis', $createdAt) . $tmp . rand(100, 900);
return $tmp;
}
3).支付
客户端拿到华为内支付服务所需要到的数据后,拉起sdk(客户端准备以及安装相关sdk)进行支付操作,支付成功后,华为会返回支付相关数据,客户端需要把该数据发送给服务器进行校验
4).购买凭证校验以及后续业务逻辑处理
服务端拿到客户端返回的支付凭证数据后,向华为发送校验购买凭证的回调请求,根据返回的结果,进行业务处理(改变订单状态等操作),最后返回校验结果给前端
客服端支付后,向服务端返回的购买凭证json案例
{
"InAppDataSignature": "QILWEAvS0vtAj+JAeVDaxxx...N6ixbga0+H",
"Kind": 0,
"DeveloperChallenge": "",
"ConsumptionState": 0,
"PayOrderId": "sandbox20230xxxxxxxxx",
"PayType": "4",
"ApplicationId": "107282039",
"AutoRenewing": false,
"OrderID": "2023xxx.xxxx",
"PackageName": "com.xxx.xxx.HUAWEI",
"ProductId": "3",
"ProductName": "18元档",
"PurchaseTime": xxxx,
"PurchaseState": 0,
"DeveloperPayload": "",
"PurchaseToken": "xxxx.1.xxxx",
"PurchaseType": 0,
"Currency": "CNY",
"Price": 1800,
"Country": "CN",
"AccountFlag": xxxx,
"SelfOrderId": "app内部订单号"
}
服务端回调方法
/**
* 华为内支付回调处理: 用于处理商家订单状态等逻辑操作
*/
public function actionNotify()
{
//获取回调通知数据
$input_data = post();
if (!isJsonString($input_data)) {
jsonFail();
}
//转换通知的JSON文本消息为PHP Array数组
$input_data = ArrayHelper::toArray(json_decode($input_data));
//获取请求参数
//获取签名数据
$inAppDataSignature = $input_data['InAppDataSignature'];
unset($input_data['InAppDataSignature']);
//获取内部订单号
$order_id = $input_data['SelfOrderId'];
unset($input_data['SelfOrderId']);
//获取商品回调数据
$InAppPurchaseData = $input_data;
if (!$inAppDataSignature || !$InAppPurchaseData) {
jsonFail('数据错误');
}
//把商品回调数据转成json,后续使用
$InAppPurchaseData = json_encode($InAppPurchaseData);
$trans = Yii::$app->db->beginTransaction();
try {
//验证签名
// 封装好的sdk下载:链接: https://pan.baidu.com/s/13KZwFN4j6ReTALZHQ6aFyw 提取码: ws2d
//华为应用内支付服务端示例代码:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Examples/server-sample-code-0000001050145549
//hms-iap-server_php官方文档地址,PHP示例代码gitee下载地址:https://gitee.com/hms-core/hms-iap-serverdemo/tree/demo_php
$dir = Yii::getAlias('@common/lib/pay/hms-iap-server_php/event');
require_once $dir . '/rsa_sha256.php';
$result = \RSA::doCheck($InAppPurchaseData, $inAppDataSignature, Yii::$app->params['hwpay']['publicKey']);
if ($result) { // 1 failure
throw new \Exception('验证失败');
}
// 文本转换为PHP Array数组
$inBodyResourceArray = ArrayHelper::toArray(json_decode($InAppPurchaseData, true));
if ($inBodyResourceArray['PurchaseState'] != 0) { //订单交易状态,-1:初始化, 0:已购买, 1:已取消, 2:已退款, 3:待处理
throw new \Exception("订单交易状态为" . $inBodyResourceArray['PurchaseState'] != 0);
}
// 处理业务逻辑
$pay_order_id = $inBodyResourceArray["PayOrderId"]; //交易单号,用户支付后生成的华为支付交易单号
// 订单状态
$status = UserOrder::STATUS_BUY;
//判断订单是否存在
$user_order = UserOrder::findOne(['order_id' => $order_id]);
if (!$user_order) {
throw new \Exception("订单不存在");
}
if ($user_order->status == $status) {
throw new \Exception("商品已支付");
}
//变更订单属性
UserOrder::changeAttribute(
['order_id' => $order_id],
[
'status' => $status, //订单状态更新
'paid_at' => substr($inBodyResourceArray['PurchaseTime'], 0, 10),//支付时间更新
'pay_order_id' => $pay_order_id,//华为交易单号
]
);
$trans->commit();
$msg = '支付成功后回调: ' . $order_id . '订单更新成功';
jsonSuccess();
} catch (\Exception $e) {
$msg = 'error: ' . $e->getMessage() . ' (file: ' . $e->getFile() . ' [' . $e->getLine() . '])';
$trans->rollBack();
jsonFail($e->getMessage());
}
}
上面需要使用到的公共方法
/**
* 校验一个字符串是不是json字符串
* @param string $data
* @param bool $assoc
* @return bool
*/
function isJsonString($data = '', $assoc = true) {
$data = json_decode($data, $assoc);
if(($data && is_object($data)) || (is_array($data) && !empty($data))){
return true;
}
return false;
}
/**
* 操作成功
* @example1 jsonSuccess();
*/
function jsonSuccess($data = NULL, $message = '操作成功')
{
header("Content-Type:application/json");
$json = array();
$json['code'] = 0;
$json['data'] = $data;
$json['message'] = $message;
$json['request_time'] = now();
echo Json::encode($json);
exit();
}
/**
* 错误信息
* @example1 jsonFail();
* @example1 jsonFail("删除失败!");
* @example2 jsonFail($model->getErrors());
*/
function jsonFail($message = '请求失败', $code = 1, $data = null)
{
header("Content-Type:application/json");
$json = array();
$json['code'] = $code;
$json['data'] = $data;
$json['message'] = $message;
$json['request_time'] = now();
echo Json::encode($json);
exit();
}
Yii2华为内支付相关配置:params-local.php
<?php
return [
// 华为内支付配置:需要到华为账户后台获取
'hwpay' => [
'client_id' => "xxx", //appId
'client_secret' => "xxx", // app 密钥
'publicKey' => 'xxx', //公钥
'pay_test' => 1, //是否支付测试
'is_screen_pay' => 1, //是否屏蔽支付
],
];
5).返回客户端
前端拿到服务端返回的结果,视结果处理商品状态
2.购买PMS商品
需要在华为后台配置商品数据,客户端从华为拉起商品列表进行下单操作,其余操作和 购买非PMS商品的业务逻辑一致,不过在返回客户端这一步,客户端需要根据返回结果向华为发送请求,是否改变商品属性,避免重复下单而引起不必要的错误
参考:
华为应用内支付服务官网:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides/introduction-0000001050033062
华为应用内支付服务端示例代码https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Examples/server-sample-code-0000001050145549
PHP示例代码gitee下载地址:https://gitee.com/hms-core/hms-iap-serverdemo/tree/demo_php