HarmonyNext接入华为应用内支付、微信支付、支付宝支付,代码封装

今天Harmony Next项目中需要使用到购买功能,这里做个记录,防止下次使用后忘记。纯干货,直接拿来用

一。微信支付功能

①添加微信依赖包,我这里是添加在entry/oh-package.json5

"@tencent/wechat_open_sdk": "1.0.11"

②由于是跨应用跳转,我们还需要添加微信的querySchemes,本人是在entry/src/main/module.json5中添加

"querySchemes": [
    "weixin",
    "wxopensdk"
    ]

③编写相关支付代码

3.1,在UIAbility中添加如下代码

 private handleWeChatCallIfNeed(want: Want) {
    WXApi.handleWant(want, WXEventHandler)
  }
 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    this.handleWeChatCallIfNeed(want)
  }
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    logger.info(TAG, 'onNewWant want:' + JSON.stringify(want));
    this.handleParams(want);
    this.restoringData(want, launchParam);
    this.handleWeChatCallIfNeed(want)
  }

3.2,添加微信相关回调监听代码

import { CommonConstants } from '@ohos/utils';
import * as wxopensdk from '@tencent/wechat_open_sdk';

export type OnWXReq = (req: wxopensdk.BaseReq) => void
export type OnWXResp = (resp: wxopensdk.BaseResp) => void

const kTag = "WXApiEventHandlerImpl"

class WXApiEventHandlerImpl implements wxopensdk.WXApiEventHandler {
  private onReqCallbacks: Map<OnWXReq, OnWXReq> = new Map
  private onRespCallbacks: Map<OnWXResp, OnWXResp> = new Map

  registerOnWXReqCallback(on: OnWXReq) {
    this.onReqCallbacks.set(on, on)
  }
  unregisterOnWXReqCallback(on: OnWXReq) {
    this.onReqCallbacks.delete(on)
  }

  registerOnWXRespCallback(on: OnWXResp) {
    this.onRespCallbacks.set(on, on)
  }
  unregisterOnWXRespCallback(on: OnWXResp) {
    this.onRespCallbacks.delete(on)
  }

  onReq(req: wxopensdk.BaseReq): void {
    wxopensdk.Log.i(kTag, "onReq:%s", JSON.stringify(req))
    this.onReqCallbacks.forEach((on) => {
      on(req)
    })
  }

  onResp(resp: wxopensdk.BaseResp): void {
    wxopensdk.Log.i(kTag, "onResp:%s", JSON.stringify(resp))
    this.onRespCallbacks.forEach((on) => {
      on(resp)
    })
  }
}

export const WXApi = wxopensdk.WXAPIFactory.createWXAPI(CommonConstants.WX_APP_ID)
export const WXEventHandler = new WXApiEventHandlerImpl

3.3,在你的支付页面添加监听

import { OnWXResp, WXApi, WXEventHandler } from '../model/WXApiWrap';
 private wxApi = WXApi
  private wxEventHandler = WXEventHandler
  private onWXResp: OnWXResp = (resp) => {
    let str = JSON.stringify(resp ?? {}, null, 2)
    Logger.debug("微信支付", str)
    let wxPayResultBean = JSONUtil.jsonToBean<WxPayResultBean>(WxPayResultBean, str)
    if (wxPayResultBean?.errCode == 0) {
      this.toRequestUserInfo(true)
    } else {
      ToastUtil.showToast('支付失败')
    }
  }

  aboutToAppear(): void {
 this.wxEventHandler.registerOnWXRespCallback(this.onWXResp)
}

  aboutToDisappear(): void {
    this.wxEventHandler.unregisterOnWXRespCallback(this.onWXResp)
  }

3.3,开始下单支付 

//判断是否安装微信
 let link = 'weixin://';
let canOpen = bundleManager.canOpenLink(link);//true已经安装,false提示安装
//获取服务端的订单信息后给到微信下单
  public toWeixinPay(params: Map<string, object>, wxApi: WXApi): Promise<boolean> {
    return new Promise((resolve) => {
      GeneralUtils.showLoadingDialog("wx_pay")
      this.loginViewModel.toCreatOrder(params).then(async (resolve2: HdResponse<OrderBean>) => {
        GeneralUtils.dismissLoadingDialog("wx_pay")
        let orderBean = JSONUtil.jsonToBean<OrderBean>(OrderBean, resolve2.result.newWxSignOrder)
        if (orderBean != null) {
          let req = new wxopensdk.PayReq
          req.partnerId = orderBean.partnerid
          req.appId = orderBean.appid
          req.packageValue = orderBean.package
          req.prepayId = orderBean.prepayid
          req.nonceStr = orderBean.noncestr
          req.timeStamp = orderBean.timestamp
          req.sign = orderBean.sign
          let result: boolean = await wxApi.sendReq(AppUtil.getContext(), req)
          resolve(result)
        }
      }).catch((error: BusinessError) => {
        GeneralUtils.dismissLoadingDialog("wx_pay")
        resolve(false)
      })

    })
  }

二。支付宝支付

①,添加支付宝依赖包

 "@cashier_alipay/cashiersdk": "^15.8.25",

②、添加querySchemes

"querySchemes": ["alipays"]

③、下单支付

    let link = 'alipays://';
let canOpen = bundleManager.canOpenLink(link);
   new Pay().pay(orderBean.signOrder, true).then((result) => {
                          this.loadingdialog?.close()
                          let message =
                            `resultStatus: ${result.get('resultStatus')} memo: ${result.get('memo')} result: ${result.get('result')}`;
                          console.log(message);
                          let resultStatus1: string | undefined = result.get("resultStatus")
                          if (resultStatus1 != undefined && StringUtils.isStrNotBlank(resultStatus1)) {
                            switch (resultStatus1) {
                              case "9000":
                                ToastUtil.showToast("支付成功")
                                this.getUserInfo()
                                break
                              case "6001":
                                ToastUtil.showToast("支付取消")
                                break
                              default:
                                ToastUtil.showToast("支付失败")
                                break
                            }

                          }

到此就接入完成了,整体来看和安卓差不了多少。

三、华为订阅商品和非消耗商品

这里封装的是华为订阅商品和非消耗商品、消耗品其实也差不不了多少,各位可以自己去官方demo查看

官方说明文档:华为应用内支付服务

demo地址:华为应用内支付服务demo地址

首先需要开通商户服务,这里就不多说了

①、添加项目的client_id

 "metadata": [
      // 配置信息如下
      {
        "name": "client_id",
        "value": "您项目的client_id"
      }
     
    ]

直接上封装后的代码,直接调用即可,本人亲测可用,并且上线使用

import { iap } from "@kit.IAPKit";
import { AppUtil, JSONUtil, ToastUtil } from "@pura/harmony-utils";
import { BusinessError } from "@kit.BasicServicesKit";
import { CommonConstants, Logger, StringUtils } from "@ohos/utils";
import { GeneralUtils } from "./GeneralUtils";
import { GoodBean, OrderBean } from "@ohos/databean";
import { LoginViewModel } from "@ohos/login";
import { HdResponse, logger } from "@ohos/network";
import { JWSUtil } from "./JWSUtil";
import * as wxopensdk from '@tencent/wechat_open_sdk';
import { WXApi } from "@tencent/wechat_open_sdk";

/**
 * 支付工具类
 */
export class PayUtils {
  private static instance: PayUtils
  private loginViewModel: LoginViewModel = LoginViewModel.getInstance()

  public static getInstance(): PayUtils {
    if (!PayUtils.instance) {
      PayUtils.instance = new PayUtils()
    }
    return PayUtils.instance
  }

  /**
   * 开始华为订阅/非消耗品
   * @param 这个是下单给到服务端的参数
   * isSubCribes 是否是订阅
   */
  public toHuaWeiSubcribe(params: Map<string, object>, isSubCribes: boolean): Promise<boolean> {
    return new Promise((resolve) => {
      this.onCase().then((isCase: boolean) => {
        if (isCase) {
          GeneralUtils.showLoadingDialog('huawei_pay')
          this.loginViewModel.toCreatOrder(params).then((res: HdResponse<OrderBean>) => {
            //开始订阅
            GeneralUtils.dismissLoadingDialog('huawei_pay')
            if (res.result) {
              let orderBean = res.result
              logger.debug(CommonConstants.TAGS, `华为支付下单的订单信息为${JSON.stringify(orderBean)}`)
              let map = new Map<string, string>()
              map['orderId'] = orderBean.orderId
              this.subscribe(orderBean.goodsId, orderBean.orderId, JSON.stringify(map),
                isSubCribes ? iap.ProductType.AUTORENEWABLE : iap.ProductType.NONCONSUMABLE, isSubCribes)
                .then((isSuccess: boolean) => {
                  resolve(isSuccess)
                })

            } else {
              resolve(false)
            }
          }).catch((error: BusinessError) => {
            GeneralUtils.dismissLoadingDialog('huawei_pay')
            resolve(false)
          })
        }
      })
    })
  }

  subscribe(id: string, orderId: string, reserverdInfo: string, type: iap.ProductType,
    isSubCribe: boolean): Promise<boolean> {
    return new Promise((resolve) => {
      try {
        let createPurchaseParam: iap.PurchaseParameter = {
          productId: id,
          productType: type,
          reservedInfo: reserverdInfo
        }
        iap.createPurchase(AppUtil.getContext(), createPurchaseParam).then(async (result) => {
          const msg: string = 'Succeeded in creating purchase.';
          logger.debug(msg);
          let jwsSubscriptionStatus =
            isSubCribe ? (JSON.parse(result.purchaseData) as PurchaseData).jwsSubscriptionStatus :
            (JSON.parse(result.purchaseData) as PurchaseData).jwsPurchaseOrder;

          if (!jwsSubscriptionStatus) {
            Logger.error('createPurchase, jwsSubscriptionStatus invalid');
            resolve(false)
            return;
          }
          let subscriptionStatus = JWSUtil.decodeJwsObj(jwsSubscriptionStatus);
          if (!subscriptionStatus) {
            Logger.error('createPurchase, subscriptionStatus invalid');
            resolve(false)
            return;
          }
          if (isSubCribe) {
            let subGroupStatusPayload: SubGroupStatusPayload = JSON.parse(subscriptionStatus);
            let lastSubscriptionStatus = subGroupStatusPayload.lastSubscriptionStatus;
            if (!lastSubscriptionStatus || !lastSubscriptionStatus.status) {
              Logger.error('createPurchase, lastSubscriptionStatus is invalid');
              resolve(false)
              return;
            }
            Logger.debug(CommonConstants.TAGS, 'subscriptionId-->' + lastSubscriptionStatus.subscriptionId)
            let purchaseOrderPayload = lastSubscriptionStatus.lastPurchaseOrder;
            if (purchaseOrderPayload) {
              this.finishSubCribePurchase(orderId, lastSubscriptionStatus.subscriptionId,
                subGroupStatusPayload.subGroupId,
                purchaseOrderPayload).then((isSuccess: boolean) => {
                resolve(isSuccess)
              })
            } else {
              resolve(false)
            }
          } else {
            //单次购买非消耗品
            const purchaseOrderPayload = JSON.parse(subscriptionStatus) as PurchaseOrderPayload;
            if (purchaseOrderPayload && purchaseOrderPayload.finishStatus !== FinishStatus.FINISHED) {
              this.finishPurchase(purchaseOrderPayload, orderId).then((isSuccess: boolean) => {
                resolve(isSuccess)
              })
            } else {
              resolve(false)
            }
          }

        }).catch((err: BusinessError) => {
          resolve(false)
          const msg: string = isSubCribe ? `创建订阅失败了. 错误码是 ${err.code}, 信息为 ${err.message}` :
            `创建购买失败了. 错误码是 ${err.code}, 信息为 ${err.message}`;
          Logger.error(msg);
          // ToastUtil.showToast(msg);
          switch (err.code) {
            case iap.IAPErrorCode.PRODUCT_OWNED:
              ToastUtil.showToast(isSubCribe ? "您已经订阅了该商品" : "您已经购买了该商品")
              break
            case iap.IAPErrorCode.SYSTEM_ERROR:
              ToastUtil.showToast(isSubCribe ? "创建订阅失败,系统错误" : "创建购买失败,系统错误")
              break
            case iap.IAPErrorCode.USER_CANCELED:
              ToastUtil.showToast("您取消了支付")
              break
            default:
              ToastUtil.showToast(msg);
              break
          }
        })
      } catch (err) {
        const e: BusinessError = err as BusinessError;
        const msg: string = isSubCribe ? `创建订阅失败了. 错误码是 ${err.code}, 信息为 ${err.message}` :
          `创建购买失败了. 错误码是 ${err.code}, 信息为 ${err.message}`;
        // const msg: string = `创建订阅失败. 错误码是 ${e.code}, 信息为 ${e.message}`;
        Logger.error(msg);
        ToastUtil.showToast(msg);
      }
    })


  }

  /**
   * 订阅成功后调用,确认收货操作,这一步可以放到服务端进行
   * @param orderId
   * @param subscriptionId
   * @param subGroupId
   * @param purchaseOrder
   */
  finishSubCribePurchase(orderId: string, subscriptionId: string, subGroupId: string,
    purchaseOrder: PurchaseOrderPayload): Promise<boolean> {
    return new Promise((resolve) => {
      let finishPurchaseParam: iap.FinishPurchaseParameter = {
        productType: purchaseOrder.productType,
        purchaseToken: purchaseOrder.purchaseToken,
        purchaseOrderId: purchaseOrder.purchaseOrderId
      };
      iap.finishPurchase(AppUtil.getContext(), finishPurchaseParam).then(() => {
        Logger.info('Succeeded in finishing purchase.' + finishPurchaseParam.purchaseToken + '\n' +
        finishPurchaseParam.purchaseOrderId);
        let mapParams = new Map<string, string>()
        mapParams['purchaseToken'] = finishPurchaseParam.purchaseToken
        mapParams['subscriptionId'] = subscriptionId
        mapParams['subGroupId'] = subGroupId
        let mapParams2 = new Map<string, object>()
        mapParams2['orderId'] = orderId
        mapParams2['receipt'] = JSON.stringify(mapParams)
        GeneralUtils.showLoadingDialog('finish_subcribe')
        this.loginViewModel.toConfirmOrder(mapParams2).then((res: HdResponse<string>) => {
          GeneralUtils.dismissLoadingDialog('finish_subcribe')
          ToastUtil.showToast("订阅成功")
          resolve(true)
        }).catch((error: BusinessError) => {
          resolve(false)
          GeneralUtils.dismissLoadingDialog('finish_subcribe')
        })
      }).catch((err: BusinessError) => {
        resolve(false)
        ToastUtil.showToast(`订阅失败. 错误码是 ${err.code}, 信息为 ${err.message}`)
        Logger.error(`Failed to finish purchase. Code is ${err.code}, message is ${err.message}`);
      });
    })

  }

  onCase(): Promise<boolean> {
    return new Promise((resolve) => {
      this.queryEnv().then((queryEnvCode: number) => {
        if (queryEnvCode !== 0) {
          let queryEnvFailedText = 'app不支持该订阅/购买服务';
          if (queryEnvCode === iap.IAPErrorCode.ACCOUNT_NOT_LOGGED_IN) {
            queryEnvFailedText = '请先登录您的华为账号后再进行操作';
          }
          ToastUtil.showToast(queryEnvFailedText);
          resolve(false)
        }
        resolve(true)
      }).catch((error: BusinessError) => {
        resolve(false)
      });

    });
  }

  finishPurchase(purchaseOrder: PurchaseOrderPayload, orderId: string): Promise<boolean> {
    return new Promise((resolve) => {
      if (!purchaseOrder.productType) {
        logger.error('finishPurchase but productType is empty');
        resolve(false)
        return;
      }
      const finishPurchaseParam: iap.FinishPurchaseParameter = {
        productType: Number(purchaseOrder.productType),
        purchaseToken: purchaseOrder.purchaseToken,
        purchaseOrderId: purchaseOrder.purchaseOrderId
      };
      iap.finishPurchase(AppUtil.getContext(), finishPurchaseParam).then(() => {
        logger.info('Succeeded in finishing purchase.');
        let mapParams = new Map<string, string>()
        mapParams['purchaseToken'] = finishPurchaseParam.purchaseToken
        mapParams['purchaseOrderId'] = purchaseOrder.purchaseOrderId
        let mapParams2 = new Map<string, object>()
        mapParams2['orderId'] = orderId
        mapParams2['receipt'] = JSON.stringify(mapParams)
        GeneralUtils.showLoadingDialog('finish_subcribe')
        this.loginViewModel.toConfirmOrder(mapParams2).then((res: HdResponse<string>) => {
          GeneralUtils.dismissLoadingDialog('finish_subcribe')
          ToastUtil.showToast("购买成功")
          resolve(true)
        }).catch((error: BusinessError) => {
          resolve(false)
          GeneralUtils.dismissLoadingDialog('finish_subcribe')
        })
      }).catch((err: BusinessError) => {
        resolve(false)
        logger.error(`Failed to finish purchase. Code is ${err.code}, message is ${err.message}`);
      });
    })
  }

  queryEnv(): Promise<number> {
    return new Promise((resolve) => {
      iap.queryEnvironmentStatus(AppUtil.getContext()).then(() => {
        Logger.info('Succeeded in querying environment status.');
        resolve(0);
      }).catch((err: BusinessError) => {
        Logger.error(`Failed to query environment status. Code is ${err.code}, message is ${err.message}`);
        resolve(err.code);
      })
    });
  }

  //单次购买消耗品

  /**
   * 开始微信支付
   */
  public toWeixinPay(params: Map<string, object>, wxApi: WXApi): Promise<boolean> {
    return new Promise((resolve) => {
      GeneralUtils.showLoadingDialog("wx_pay")
      this.loginViewModel.toCreatOrder(params).then(async (resolve2: HdResponse<OrderBean>) => {
        GeneralUtils.dismissLoadingDialog("wx_pay")
        let orderBean = JSONUtil.jsonToBean<OrderBean>(OrderBean, resolve2.result.newWxSignOrder)
        if (orderBean != null) {
          let req = new wxopensdk.PayReq
          req.partnerId = orderBean.partnerid
          req.appId = orderBean.appid
          req.packageValue = orderBean.package
          req.prepayId = orderBean.prepayid
          req.nonceStr = orderBean.noncestr
          req.timeStamp = orderBean.timestamp
          req.sign = orderBean.sign
          let result: boolean = await wxApi.sendReq(AppUtil.getContext(), req)
          resolve(result)
        }
      }).catch((error: BusinessError) => {
        GeneralUtils.dismissLoadingDialog("wx_pay")
        resolve(false)
      })

    })
  }
}

interface PurchaseData {
  type: number;
  jwsPurchaseOrder?: string;
  jwsSubscriptionStatus?: string;
}

interface SubGroupStatusPayload {
  environment: string;
  applicationId: string;
  packageName: string;
  subGroupId: string;
  lastSubscriptionStatus?: SubscriptionStatus;
  historySubscriptionStatusList?: SubscriptionStatus;
}

interface SubscriptionStatus {
  subGroupGenerationId: string;
  subscriptionId: string;
  purchaseToken: string;
  status: string;
  expiresTime: number;
  lastPurchaseOrder?: PurchaseOrderPayload;
  renewalInfo?: SubRenewalInfo;
}

interface PurchaseOrderPayload {
  purchaseOrderId: string;
  purchaseToken: string;
  productType: number;
  finishStatus?: FinishStatus;
}

interface SubRenewalInfo {
  productId: string;
}

export enum FinishStatus {
  FINISHED = '1',
  UNFINISHED = '2'
}

到这里支付就完成了,可以到华为市场添加测试账户进行沙箱测试。