今天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'
}
到这里支付就完成了,可以到华为市场添加测试账户进行沙箱测试。