2 Commits

Author SHA1 Message Date
Zhukj
86e43e3e6c feat:新添加华为支付 2025-12-06 09:16:39 +08:00
Zhukj
bf09d8ad26 feat:新增华为支付 2025-12-06 08:41:52 +08:00
5 changed files with 567 additions and 286 deletions

152
common/js/huaweiPay.js Normal file
View File

@@ -0,0 +1,152 @@
/**
* 华为支付核心工具类
* 适配端华为快应用原生支付、微信小程序H5支付、H5端H5支付
* 核心统一封装支付调用逻辑返回H5支付链接适配web-view组件
*/
/**
* 华为支付调用封装
* @param {String} outTradeNo 前端生成的唯一订单号
* @param {Number} amount 支付金额单位保留2位小数
* @param {String} subject 订单标题
* @param {String} payType 支付类型默认huaweipay
* @returns {Promise} 支付结果含H5支付链接
*/
export function invokeHuaweiPay(outTradeNo, amount, subject, payType = 'huaweipay') {
return new Promise(async (resolve, reject) => {
try {
// 1. 显示加载中提示
uni.showLoading({
title: '发起支付...'
});
// 2. 调用后端生成订单
const orderRes = await uni.request({
url: getApiUrl() + '/api/huawei/pay/createOrder',
method: 'POST',
data: {
out_trade_no: outTradeNo,
total_amount: amount,
subject: subject,
pay_type: payType
}
});
// 3. 校验后端返回结果
if (orderRes.data.code !== 0) {
uni.hideLoading();
reject(new Error(orderRes.data.msg || '生成支付订单失败'));
return;
}
// 4. 区分运行端处理
const systemInfo = uni.getSystemInfoSync();
const accountInfo = uni.getAccountInfoSync();
const isHuaweiQuickApp = systemInfo.platform === 'quickapp-huawei'; // 华为快应用
const isWechatMini = accountInfo?.miniProgram?.appId?.includes('wx'); // 微信小程序
const isH5 = systemInfo.platform === 'web' || !accountInfo?.miniProgram; // H5端
if (isHuaweiQuickApp) {
// 4.1 华为快应用:原生唤起支付控件
// 注意这里需要根据实际的华为快应用支付SDK调用
try {
// 示例代码,实际需要根据华为快应用文档调整
const huaweiPay = require('@service.pay.huawei');
huaweiPay.pay({
orderInfo: orderRes.data.data.orderInfo,
success: (payRes) => {
uni.hideLoading();
resolve({
code: 0,
msg: '华为支付控件唤起成功',
data: payRes
});
},
fail: (err) => {
uni.hideLoading();
const errMsg = err.message || err.code || '未知错误';
reject(new Error(`支付失败:${errMsg}`));
}
});
} catch (sdkError) {
// SDK不存在降级到H5支付
if (orderRes.data.data.payUrl) {
uni.hideLoading();
resolve({
code: 0,
msg: '跳转华为支付H5页面',
data: { payUrl: orderRes.data.data.payUrl }
});
} else {
uni.hideLoading();
reject(new Error('华为支付SDK不可用且无H5支付链接'));
}
}
} else if (isWechatMini || isH5) {
// 4.2 微信小程序/H5端返回H5支付链接适配web-view
if (!orderRes.data.data.payUrl) {
uni.hideLoading();
reject(new Error('未获取到华为支付H5跳转链接'));
return;
}
uni.hideLoading();
resolve({
code: 0,
msg: '跳转华为支付H5页面',
data: { payUrl: orderRes.data.data.payUrl }
});
} else {
// 4.3 其他端:提示不支持
uni.hideLoading();
reject(new Error('当前环境暂不支持华为支付'));
}
} catch (err) {
uni.hideLoading();
reject(new Error(`支付异常:${err.message || '网络请求失败'}`));
}
});
}
/**
* 校验支付最终状态(统一适配所有支付方式)
* @param {String} outTradeNo 前端生成的订单号
* @returns {Promise} 校验结果(包含订单实际支付状态)
*/
export function checkPayStatus(outTradeNo) {
return new Promise(async (resolve, reject) => {
try {
// 统一调用后端状态校验接口(适配所有支付类型)
const checkRes = await uni.request({
url: getApiUrl() + '/api/pay/checkStatus',
method: 'POST',
data: {
out_trade_no: outTradeNo
}
});
resolve(checkRes.data);
} catch (err) {
reject(new Error(`校验支付状态失败:${err.message || '网络请求失败'}`));
}
});
}
/**
* 获取API基础URL
*/
function getApiUrl() {
// 尝试获取配置的API地址
try {
// #ifdef H5
const config = require('@/common/js/config.js').default;
return config.baseUrl || '';
// #endif
// #ifndef H5
const config = require('@/common/js/config.js').default;
return config.baseUrl || '';
// #endif
} catch (e) {
console.warn('获取API配置失败使用空字符串');
return '';
}
}

28
common/js/payCore.js Normal file
View File

@@ -0,0 +1,28 @@
/**
* 全支付方式统一调用入口
* 整合微信/支付宝/华为支付的所有方法,简化页面引入逻辑
* 依赖payUtils.js、huaweiPay.js无需修改原文件
*/
// 1. 引入原工具类的所有方法(修正方法名)
import {
invokeWechatPay, // 微信支付(完整方法名)
invokeAlipay, // 支付宝支付(完整方法名)
checkPayStatus as payUtilsCheck // 微信/支付宝支付状态校验(完整方法名)
} from './payUtils.js';
import {
invokeHuaweiPay, // 华为支付(完整方法名)
checkPayStatus as huaweiCheck // 华为支付状态校验(完整方法名)
} from './huaweiPay.js';
// 2. 导出所有支付调用方法(修正方法名,和原方法一致)
export {
invokeWechatPay,
invokeAlipay,
invokeHuaweiPay
};
// 3. 导出统一的支付状态校验方法(两个工具类逻辑完全一致,任选其一即可)
export const checkPayStatus = payUtilsCheck;
// 若需使用华为支付工具类的校验逻辑可替换为export const checkPayStatus = huaweiCheck;

212
common/js/payUtils.js Normal file
View File

@@ -0,0 +1,212 @@
/**
* 微信/支付宝支付工具类
* 适配端微信小程序全支付方式、华为快应用全支付方式、H5全支付方式
* 核心统一封装支付调用逻辑返回H5支付链接适配web-view组件
*/
/**
* 微信支付调用封装
* @param {String} outTradeNo 前端生成的唯一订单号
* @param {Number} amount 支付金额单位保留2位小数
* @param {String} subject 订单标题
* @returns {Promise} 支付结果含H5支付链接
*/
export function invokeWechatPay(outTradeNo, amount, subject) {
return new Promise(async (resolve, reject) => {
try {
// 1. 调用后端接口生成微信支付订单
const orderRes = await uni.request({
url: getApiUrl() + '/api/pay/wechat/createOrder',
method: 'POST',
data: {
out_trade_no: outTradeNo,
total_amount: amount,
subject: subject,
pay_type: 'wechatpay'
}
});
// 2. 校验后端返回结果
if (orderRes.data.code !== 0) {
reject(new Error(orderRes.data.msg || '生成微信支付订单失败'));
return;
}
// 3. 区分运行端处理
const systemInfo = uni.getSystemInfoSync();
const accountInfo = uni.getAccountInfoSync();
const isWechatMini = accountInfo?.miniProgram?.appId?.includes('wx'); // 微信小程序
const isHuaweiQuickApp = systemInfo.platform === 'quickapp-huawei'; // 华为快应用
if (isWechatMini) {
// 3.1 微信小程序优先原生唤起失败则返回H5链接
if (orderRes.data.data.timeStamp && orderRes.data.data.paySign) {
uni.requestPayment({
timeStamp: orderRes.data.data.timeStamp,
nonceStr: orderRes.data.data.nonceStr,
package: orderRes.data.data.package,
signType: 'MD5',
paySign: orderRes.data.data.paySign,
success: () => {
resolve({
code: 0,
msg: '微信支付控件唤起成功',
data: {}
});
},
fail: (err) => {
// 原生唤起失败返回H5链接适配web-view
if (orderRes.data.data.payUrl) {
resolve({
code: 0,
msg: '原生支付失败跳转H5支付',
data: { payUrl: orderRes.data.data.payUrl }
});
} else {
reject(new Error(`微信支付失败:${err.errMsg || '无H5支付链接'}`));
}
}
});
} else if (orderRes.data.data.payUrl) {
// 无原生支付参数直接返回H5链接
resolve({
code: 0,
msg: '跳转微信支付H5页面',
data: { payUrl: orderRes.data.data.payUrl }
});
} else {
reject(new Error('缺少微信支付参数(原生/H5'));
}
} else if (isHuaweiQuickApp) {
// 3.2 华为快应用返回H5支付链接
if (!orderRes.data.data.payUrl) {
reject(new Error('未获取到微信支付H5跳转链接'));
return;
}
resolve({
code: 0,
msg: '跳转微信支付H5页面',
data: { payUrl: orderRes.data.data.payUrl }
});
} else {
// 3.3 H5端直接跳转
if (!orderRes.data.data.payUrl) {
reject(new Error('未获取到微信支付跳转链接'));
return;
}
window.location.href = orderRes.data.data.payUrl;
resolve({
code: 0,
msg: '跳转微信支付页面成功',
data: {}
});
}
} catch (err) {
reject(new Error(`微信支付异常:${err.message || '网络请求失败'}`));
}
});
}
/**
* 支付宝支付调用封装全端支持返回H5链接适配web-view
* @param {String} outTradeNo 前端生成的唯一订单号
* @param {Number} amount 支付金额(单位:元)
* @param {String} subject 订单标题
* @returns {Promise} 支付结果含H5支付链接
*/
export function invokeAlipay(outTradeNo, amount, subject) {
return new Promise(async (resolve, reject) => {
try {
// 1. 调用后端接口生成支付宝支付订单
const orderRes = await uni.request({
url: getApiUrl() + '/api/pay/alipay/createOrder',
method: 'POST',
data: {
out_trade_no: outTradeNo,
total_amount: amount,
subject: subject,
pay_type: 'alipay'
}
});
// 2. 校验后端返回结果
if (orderRes.data.code !== 0) {
reject(new Error(orderRes.data.msg || '生成支付宝支付订单失败'));
return;
}
// 3. 区分运行端处理全端返回H5链接
const accountInfo = uni.getAccountInfoSync();
const isWechatMini = accountInfo?.miniProgram?.appId?.includes('wx');
const isHuaweiQuickApp = uni.getSystemInfoSync().platform === 'quickapp-huawei';
if (!orderRes.data.data.payUrl) {
reject(new Error('未获取到支付宝支付H5跳转链接'));
return;
}
if (isWechatMini || isHuaweiQuickApp) {
// 3.1 微信小程序/华为快应用返回H5链接适配web-view
resolve({
code: 0,
msg: '跳转支付宝支付H5页面',
data: { payUrl: orderRes.data.data.payUrl }
});
} else {
// 3.2 H5端直接跳转
window.location.href = orderRes.data.data.payUrl;
resolve({
code: 0,
msg: '跳转支付宝支付页面成功',
data: {}
});
}
} catch (err) {
reject(new Error(`支付宝支付异常:${err.message || '网络请求失败'}`));
}
});
}
/**
* 统一支付状态校验和huaweiPay.js的checkPayStatus对齐
* @param {String} outTradeNo 前端生成的订单号
* @returns {Promise} 校验结果(包含订单实际支付状态)
*/
export function checkPayStatus(outTradeNo) {
return new Promise(async (resolve, reject) => {
try {
// 统一调用后端状态校验接口(适配所有支付类型)
const checkRes = await uni.request({
url: getApiUrl() + '/api/pay/checkStatus', // 和huaweiPay.js使用同一接口
method: 'POST',
data: {
out_trade_no: outTradeNo
}
});
resolve(checkRes.data);
} catch (err) {
reject(new Error(`校验支付状态失败:${err.message || '网络请求失败'}`));
}
});
}
/**
* 获取API基础URL
*/
function getApiUrl() {
// 尝试获取配置的API地址
try {
// #ifdef H5
const config = require('@/common/js/config.js').default;
return config.baseUrl || '';
// #endif
// #ifndef H5
const config = require('@/common/js/config.js').default;
return config.baseUrl || '';
// #endif
} catch (e) {
console.warn('获取API配置失败使用空字符串');
return '';
}
}

View File

@@ -56,6 +56,9 @@ import {
} from 'common/js/wx-jssdk.js'; } from 'common/js/wx-jssdk.js';
// #endif // #endif
// 引入统一支付工具类
import { invokeWechatPay, invokeAlipay, invokeHuaweiPay } from '../../common/js/payCore.js';
export default { export default {
name: 'payment', name: 'payment',
components: { components: {
@@ -87,6 +90,13 @@ export default {
type: 'wechatpay' type: 'wechatpay'
}, },
// #endif // #endif
// #ifdef H5 || QUICKAPP-HUAWEI
{
name: '华为支付',
icon: 'icon-zhekou',
type: 'huaweipay'
},
// #endif
{ {
name: '线下支付', name: '线下支付',
icon: 'icondiy icon-yuezhifu', icon: 'icondiy icon-yuezhifu',
@@ -254,119 +264,86 @@ export default {
// #ifdef H5 // #ifdef H5
pay() { pay() {
var payType = this.payTypeList[this.payIndex]; var payType = this.payTypeList[this.payIndex];
var return_url = ''; if (!payType || payType.type === 'offlinepay') {
if (this.payInfo.event == 'BlindboxGoodsOrderPayNotify') { // 线下支付仍使用原有逻辑
return_url = '/pages_promotion/blindbox/index?outTradeNo='; this.payOffline();
} else {
return_url = '/pages_tool/pay/result?code=';
}
this.$api.sendRequest({
url: '/api/pay/pay',
data: {
out_trade_no: this.payInfo.out_trade_no,
pay_type: payType ? payType.type : '',
return_url: encodeURIComponent(this.$config.h5Domain + return_url + this.payInfo.out_trade_no),
is_balance: this.isBalance
},
success: res => {
uni.hideLoading();
if (res.code >= 0) {
if (res.data.pay_success) {
this.paySuccess();
return; return;
} }
switch (payType.type) {
case 'alipay': // 使用统一支付工具类进行支付
if (this.$util.isWeiXin()) { this.invokeUnifiedPay(payType.type);
var wx_alipay = encodeURIComponent(res.data.data); },
this.$util.redirectTo('/pages_tool/pay/wx_pay', {
wx_alipay: wx_alipay, /**
out_trade_no: this.payInfo.out_trade_no * 统一支付调用方法(使用新的支付工具类)
}, '', 'redirectTo'); */
this.repeatFlag = false; async invokeUnifiedPay(payType) {
} else { try {
this.repeatFlag = false; let payResult;
location.href = res.data.data; const amount = this.payMoney;
this.checkPayStatus(); const outTradeNo = this.payInfo.out_trade_no;
} const subject = '订单支付';
break;
// 根据支付类型调用对应的支付方法
switch (payType) {
case 'wechatpay': case 'wechatpay':
if (this.$util.isWeiXin()) { payResult = await invokeWechatPay(outTradeNo, amount, subject);
if (uni.getSystemInfoSync().platform == 'ios') {
var url = uni.getStorageSync('initUrl');
} else {
var url = location.href;
}
// 获取jssdk配置
this.$api.sendRequest({
url: '/wechat/api/wechat/jssdkconfig',
data: {
url: url
},
success: jssdkRes => {
var wxJS = new Weixin(),
payData = res.data.data;
wxJS.init(jssdkRes.data);
wxJS.pay({
timestamp: payData.timestamp ? payData.timestamp : payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.paySign
},
res => {
if (res.errMsg == 'chooseWXPay:ok') {
this.paySuccess();
this.repeatFlag = false;
} else {
this.$util.showToast({
title: res.errMsg
});
setTimeout(() => {
this.close();
this.repeatFlag = false;
}, 1500)
}
},
res => {
this.$util.showToast({
title: '您已取消支付'
});
this.resetpay();
this.repeatFlag = false;
}
);
}
});
} else {
this.repeatFlag = false;
location.href = res.data.url;
this.checkPayStatus();
}
break; break;
case 'offlinepay': case 'alipay':
payResult = await invokeAlipay(outTradeNo, amount, subject);
break;
case 'huaweipay':
payResult = await invokeHuaweiPay(outTradeNo, amount, subject);
break;
default:
throw new Error('不支持的支付方式');
}
// 处理H5支付跳转
if (payResult.code === 0 && payResult.data?.payUrl) {
// H5端直接跳转其他端需要在WebView中显示
const systemInfo = uni.getSystemInfoSync();
if (systemInfo.platform === 'web') {
window.location.href = payResult.data.payUrl;
this.checkPayStatus();
} else {
// 微信小程序/华为快应用中显示WebView
this.showPayWebView(payResult.data.payUrl);
}
} else if (payResult.code === 0) {
// 原生支付成功
this.paySuccess();
} else {
throw new Error(payResult.msg || '支付失败');
}
} catch (error) {
this.repeatFlag = false;
this.$util.showToast({
title: error.message || '支付失败'
});
}
},
/**
* 显示支付WebView用于微信小程序/华为快应用的H5支付
*/
showPayWebView(payUrl) {
// 这里可以打开一个新的WebView页面来显示支付
// 或者使用现有的跳转逻辑
this.$util.redirectTo('/pages/Pay/Pay', {
payUrl: payUrl,
outTradeNo: this.payInfo.out_trade_no
});
},
/**
* 线下支付处理(保留原有逻辑)
*/
payOffline() {
this.$util.redirectTo('/pages_tool/pay/offlinepay', { this.$util.redirectTo('/pages_tool/pay/offlinepay', {
outTradeNo: this.payInfo.out_trade_no outTradeNo: this.payInfo.out_trade_no
}); });
this.repeatFlag = false; this.repeatFlag = false;
break;
}
} else {
this.$util.showToast({
title: res.message
});
this.repeatFlag = false;
}
},
fail: res => {
uni.hideLoading();
this.$util.showToast({
title: 'request:fail'
});
this.repeatFlag = false;
}
});
}, },
checkPayStatus() { checkPayStatus() {
this.timer = setInterval(() => { this.timer = setInterval(() => {
@@ -392,110 +369,17 @@ export default {
// #ifdef MP // #ifdef MP
pay() { pay() {
var payType = this.payTypeList[this.payIndex]; var payType = this.payTypeList[this.payIndex];
this.$api.sendRequest({ if (!payType || payType.type === 'offlinepay') {
url: '/api/pay/pay', // 线下支付仍使用原有逻辑
data: {
out_trade_no: this.payInfo.out_trade_no,
pay_type: payType ? payType.type : '',
scene: uni.getStorageSync('is_test') ? 1175 : wx.getLaunchOptionsSync().scene,
is_balance: this.isBalance
},
success: res => {
uni.hideLoading();
if (res.code >= 0) {
if (res.data.pay_success) {
this.paySuccess();
this.repeatFlag = false;
return;
}
if (payType.type == 'offlinepay') {
this.$util.redirectTo('/pages_tool/pay/offlinepay', { this.$util.redirectTo('/pages_tool/pay/offlinepay', {
outTradeNo: this.payInfo.out_trade_no outTradeNo: this.payInfo.out_trade_no
}); });
this.repeatFlag = false; this.repeatFlag = false;
} else { return;
var payData = res.data.data; }
// #ifdef MP-WEIXIN // 小程序端也使用统一支付工具类
var scene = uni.getStorageSync('is_test') ? 1175 : wx.getLaunchOptionsSync().scene; this.invokeUnifiedPay(payType.type);
if ([1175, 1176, 1177, 1191, 1195].indexOf(scene) != -1) {
uni.requestOrderPayment({
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.paySign,
success: res => {
this.paySuccess();
this.repeatFlag = false;
},
fail: res => {
this.flag = false;
if (res.errMsg == 'requestOrderPayment:fail cancel') {
this.$util.showToast({
title: '您已取消支付'
});
this.resetpay();
this.repeatFlag = false;
} else {
uni.showModal({
content: '支付失败,失败原因: ' + res.errMsg,
showCancel: false
});
setTimeout(() => {
this.close();
this.repeatFlag = false;
}, 1500)
}
}
});
return
}
// #endif
uni.requestPayment({
provider: payType.provider,
...payData,
success: res => {
this.paySuccess();
this.repeatFlag = false;
},
fail: res => {
this.flag = false;
if (res.errMsg == 'requestPayment:fail cancel') {
this.$util.showToast({
title: '您已取消支付'
});
this.resetpay();
this.repeatFlag = false;
} else {
uni.showModal({
content: '支付失败,失败原因: ' + res.errMsg,
showCancel: false
});
setTimeout(() => {
this.close();
this.repeatFlag = false;
}, 1500)
}
}
});
}
} else {
this.$util.showToast({
title: res.message
});
this.repeatFlag = false;
}
},
fail: res => {
uni.hideLoading();
this.$util.showToast({
title: 'request:fail'
});
this.repeatFlag = false;
}
});
}, },
// #endif // #endif
/** /**
@@ -644,6 +528,8 @@ export default {
color: #00a0e9; color: #00a0e9;
} }
.icon-checkboxblank { .icon-checkboxblank {
font-size: 40rpx; font-size: 40rpx;
color: $color-line; color: $color-line;

View File

@@ -1,7 +1,8 @@
{ {
"description": "项目配置文件", "description": "项目配置文件",
"packOptions": { "packOptions": {
"ignore": [] "ignore": [],
"include": []
}, },
"setting": { "setting": {
"urlCheck": true, "urlCheck": true,
@@ -37,20 +38,20 @@
"userConfirmedBundleSwitch": false, "userConfirmedBundleSwitch": false,
"packNpmManually": false, "packNpmManually": false,
"packNpmRelationList": [], "packNpmRelationList": [],
"minifyWXSS": true "minifyWXSS": true,
"compileWorklet": false,
"minifyWXML": true,
"localPlugins": false,
"disableUseStrict": false,
"useCompilerPlugins": false,
"condition": false,
"swc": false,
"disableSWC": true
}, },
"compileType": "miniprogram", "compileType": "miniprogram",
"libVersion": "2.16.1", "libVersion": "2.16.1",
"appid": "wx29215aa1bd97bbd6", "appid": "wx29215aa1bd97bbd6",
"projectname": "niushop_b2c_v4_uniapp", "projectname": "niushop_b2c_v4_uniapp",
"debugOptions": {
"hidedInDevtools": []
},
"scripts": {},
"staticServerOptions": {
"baseURL": "",
"servePath": ""
},
"isGameTourist": false, "isGameTourist": false,
"condition": { "condition": {
"search": { "search": {
@@ -71,5 +72,7 @@
"miniprogram": { "miniprogram": {
"list": [] "list": []
} }
} },
"simulatorPluginLibVersion": {},
"editorSetting": {}
} }