NGTools/uni_modules/uni-pay/uniCloud/cloudfunctions/uni-pay-co/service/pay.js

1130 lines
35 KiB
JavaScript
Raw Permalink Normal View History

/**
* uni-pay-co 统一支付服务实现
*/
const crypto = require("crypto");
const uniPay = require("uni-pay");
const configCenter = require("uni-config-center");
const config = configCenter({ pluginId: 'uni-pay' }).requireFile('config.js');
const dao = require('../dao');
const libs = require('../libs');
const { UniCloudError, isUniPayError, ERROR } = require('../common/error')
const db = uniCloud.database();
const _ = db.command;
const notifyPath = "/payNotify/";
class service {
constructor(obj) {
}
/**
* 获取支付插件的完整配置
*/
getConfig() {
return config;
}
/**
* 支付成功 - 异步通知
*/
async paymentNotify(data = {}) {
let {
httpInfo,
clientInfo,
cloudInfo,
isWxpayVirtual
} = data;
console.log('httpInfo: ', httpInfo);
let path = httpInfo.path;
let pay_type = path.substring(notifyPath.length);
let provider = pay_type.split("-")[0]; // 获取支付供应商
let provider_pay_type = pay_type.split("-")[1]; // 获取支付方式
if (isWxpayVirtual) {
// 微信虚拟支付固定参数
provider = "wxpay-virtual";
provider_pay_type = "mp";
}
// 初始化uniPayInstance
let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
let notifyType = await uniPayInstance.checkNotifyType(httpInfo);
console.log('notifyType: ', notifyType)
if (notifyType === "token") {
let verifyResult = await uniPayInstance.verifyTokenNotify(httpInfo);
console.log('verifyResult: ', verifyResult)
if (!verifyResult) {
console.log('---------!签名验证未通过!---------');
return;
}
// 校验token的测试接口直接返回echostr
return verifyResult.echostr;
}
if (notifyType !== "payment") {
// 非支付通知直接返回成功
console.log(`---------!非支付通知!---------`);
return libs.common.returnNotifySUCCESS({ provider, provider_pay_type });
}
// 支付通知,验证签名
let verifyResult = await uniPayInstance.verifyPaymentNotify(httpInfo);
if (!verifyResult) {
console.log('---------!签名验证未通过!---------');
console.log('---------!签名验证未通过!---------');
console.log('---------!签名验证未通过!---------');
return {}
}
console.log('---------!签名验证通过!---------');
verifyResult = JSON.parse(JSON.stringify(verifyResult)); // 这一句代码有用,请勿删除。
console.log('verifyResult: ', verifyResult)
let {
outTradeNo,
totalFee,
transactionId,
resultCode, // 微信支付v2和支付宝支付判断成功的字段
openid,
appId,
tradeState, // 微信支付v3和微信虚拟支付判断支付成功的字段
} = verifyResult;
if (resultCode == "SUCCESS" || tradeState === "SUCCESS") {
let time = Date.now();
let payOrderInfo = await dao.uniPayOrders.updateAndReturn({
whereJson: {
status: 0, // status:0 为必须条件,防止重复推送时的错误
out_trade_no: outTradeNo, // 商户订单号
},
dataJson: {
status: 1, // 设置为已付款
transaction_id: transactionId, // 第三方支付单号
pay_date: time, // 更新支付时间(暂无法准确获取到支付时间,故用通知时间代替支付时间)
notify_date: time, // 更新通知时间
openid,
provider, // 更新provider
provider_pay_type, // 更新provider_pay_type
original_data: httpInfo, // http回调信息便于丢单时手动触发回调
}
});
//console.log('payOrderInfo: ', payOrderInfo)
if (payOrderInfo) {
// 只有首次推送才执行用户自己的逻辑处理。
// 用户自己的逻辑处理 开始-----------------------------------------------------------
let userOrderSuccess = false;
let orderPaySuccess;
try {
// 加载自定义异步回调函数
orderPaySuccess = require(`../notify/${payOrderInfo.type}`);
} catch (err) {
console.log(err);
}
if (typeof orderPaySuccess === "function") {
console.log('用户自己的回调逻辑 - 开始执行');
try {
userOrderSuccess = await orderPaySuccess({
verifyResult,
data: payOrderInfo,
clientInfo,
cloudInfo
});
console.log('用户自己的回调逻辑 - 执行完成');
} catch(err){
userOrderSuccess = false;
console.log('用户自己的回调逻辑 - 执行异常', err);
}
}
console.log('userOrderSuccess', userOrderSuccess);
// 用户自己的逻辑处理 结束-----------------------------------------------------------
await dao.uniPayOrders.updateAndReturn({
whereJson: {
status: 1,
out_trade_no: outTradeNo,
},
dataJson: {
user_order_success: userOrderSuccess,
}
});
} else {
console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
console.log('verifyResult:', verifyResult);
}
} else {
console.log('verifyResult:', verifyResult);
}
return libs.common.returnNotifySUCCESS({ provider, provider_pay_type });
}
/**
* 微信虚拟支付异步通知
*/
async wxpayVirtualNotify(data = {}) {
return this.paymentNotify({
...data,
isWxpayVirtual: true
});
}
/**
* 统一支付 - 创建支付订单
*/
async createOrder(data = {}) {
let {
provider, // 支付供应商
total_fee, // 支付金额
user_id, // 用户user_id统计需要
openid, // 用户openid
order_no, // 订单号
out_trade_no, // 支付插件订单号
description, // 订单描述
type, // 回调类型
qr_code, // 是否强制使用扫码支付
custom, // 自定义参数(不会发送给第三方支付服务器)
other, // 其他请求参数(会发送给第三方支付服务器)
clientInfo, // 客户端信息
cloudInfo, // 云端信息
wxpay_virtual, // 仅用于微信虚拟支付
} = data;
let subject = description;
let body = description;
if (!out_trade_no) out_trade_no = libs.common.createOrderNo();
if (!order_no || typeof order_no !== "string") {
throw { errCode: ERROR[51003] };
}
if (!type || typeof type !== "string") {
throw { errCode: ERROR[51004] };
}
if (provider === "wxpay-virtual") {
if (typeof wxpay_virtual !== "object") {
throw { errCode: ERROR[51011] };
}
if (typeof wxpay_virtual.buy_quantity !== "number" || wxpay_virtual.buy_quantity <= 0) {
throw { errCode: ERROR[51012] };
}
} else {
if (typeof total_fee !== "number" || total_fee <= 0 || total_fee % 1 !== 0) {
throw { errCode: ERROR[51005] };
}
}
if (!description || typeof description !== "string") {
throw { errCode: ERROR[51006] };
}
if (!provider || typeof provider !== "string") {
throw { errCode: ERROR[51007] };
}
if (!clientInfo) {
throw { errCode: ERROR[51008] };
}
if (!cloudInfo) {
throw { errCode: ERROR[51009] };
}
let res = { errCode: 0, errMsg: 'ok', order_no, out_trade_no, provider };
let {
clientIP: client_ip,
userAgent: ua,
appId: appid,
deviceId: device_id,
platform
} = clientInfo;
let {
spaceId, // 服务空间ID
} = cloudInfo;
let {
notifyUrl = {}
} = config;
// 业务逻辑开始-----------------------------------------------------------
// 以下代码是为了兼容公测版迁移到正式版的空间
let notifySpaceId = spaceId;
if (!notifyUrl[notifySpaceId]) {
if (notifySpaceId.indexOf("mp-") === 0) {
notifySpaceId = notifySpaceId.substring(3);
} else {
notifySpaceId = `mp-${notifySpaceId}`
}
}
// 以上代码是为了兼容公测版迁移到正式版的空间
let currentNotifyUrl = notifyUrl[notifySpaceId] || notifyUrl["default"]; // 异步回调地址
if (!currentNotifyUrl || currentNotifyUrl.indexOf("http") !== 0) {
throw { errCode: ERROR[52002] };
}
platform = libs.common.getPlatform(platform);
// 如果需要二维码支付模式则清空下openid
if (qr_code) {
openid = undefined;
res.qr_code = qr_code;
}
// 获取并自动匹配支付供应商的支付类型
let provider_pay_type = libs.common.getProviderPayType({
platform,
provider,
ua,
qr_code
});
res.provider_pay_type = provider_pay_type;
// 拼接实际异步回调地址
let finalNotifyUrl = `${currentNotifyUrl}${notifyPath}${provider}-${provider_pay_type}`;
// 获取uniPay交易类型
let tradeType = libs.common.getTradeType({ provider, provider_pay_type });
let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
// 初始化uniPayInstance
let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
// 获取支付信息
let getOrderInfoParam = {
openid: openid,
subject: subject,
body: body,
outTradeNo: out_trade_no,
totalFee: total_fee,
notifyUrl: finalNotifyUrl,
tradeType: tradeType
};
if (provider === "wxpay" && provider_pay_type === "mweb") {
getOrderInfoParam.spbillCreateIp = client_ip;
if (uniPayConifg.version !== 3) {
// v2版本
getOrderInfoParam.sceneInfo = uniPayConifg.sceneInfo;
} else {
// v3版本特殊处理
getOrderInfoParam.sceneInfo = JSON.parse(JSON.stringify(uniPayConifg.sceneInfo));
if (getOrderInfoParam.sceneInfo.h5_info.wap_url) {
getOrderInfoParam.sceneInfo.h5_info.app_url = getOrderInfoParam.sceneInfo.h5_info.wap_url;
delete getOrderInfoParam.sceneInfo.h5_info.wap_url;
}
if (getOrderInfoParam.sceneInfo.h5_info.wap_name) {
getOrderInfoParam.sceneInfo.h5_info.app_name = getOrderInfoParam.sceneInfo.h5_info.wap_name;
delete getOrderInfoParam.sceneInfo.h5_info.wap_name;
}
}
}
let expand_data;
try {
// 如果是苹果内购不需要执行uniPayInstance.getOrderInfo等操作
if (provider !== "appleiap") {
// 第三方支付服务器返回的订单信息
let orderInfo;
if (other) {
// other 内的键名转驼峰
other = libs.common.snake2camelJson(other);
getOrderInfoParam = Object.assign(getOrderInfoParam, other);
}
getOrderInfoParam = JSON.parse(JSON.stringify(getOrderInfoParam)); // 此为去除undefined的参数
if (provider === "wxpay-virtual") {
// 微信虚拟支付扩展数据
expand_data = {
mode: wxpay_virtual.mode, // short_series_coin 代币充值; short_series_goods 道具直购
buy_quantity: wxpay_virtual.buy_quantity,
rate: uniPayConifg.rate || 100,
sandbox: uniPayConifg.sandbox,
};
if (wxpay_virtual.mode === "short_series_goods") {
expand_data.product_id = wxpay_virtual.product_id;
expand_data.goods_price = wxpay_virtual.goods_price;
}
// 获取用户的sessionKey
let { session_key } = await dao.opendbOpenData.getSessionKey({
appId: uniPayConifg.appId,
platform: "weixin-mp",
openid: openid
});
getOrderInfoParam.sessionKey = session_key;
getOrderInfoParam.mode = expand_data.mode;
getOrderInfoParam.buyQuantity = expand_data.buy_quantity;
getOrderInfoParam.productId = expand_data.product_id;
getOrderInfoParam.goodsPrice = expand_data.goods_price;
if (getOrderInfoParam.mode === "short_series_coin") {
// 计算支付金额
total_fee = expand_data.buy_quantity / (expand_data.rate || 100) * 100;
} else if (getOrderInfoParam.mode === "short_series_goods") {
// 计算支付金额
total_fee = expand_data.buy_quantity * expand_data.goods_price;
}
}
orderInfo = await uniPayInstance.getOrderInfo(getOrderInfoParam);
if (qr_code && orderInfo.codeUrl) {
res.qr_code_image = await libs.qrcode.toDataURL(orderInfo.codeUrl, {
type: "image/png",
width: 200,
margin: 1,
scale: 1,
color: {
dark: "#000000",
light: "#ffffff",
},
errorCorrectionLevel: "Q",
quality: 1
});
}
// 支付宝支付参数特殊处理
if (provider === "alipay") {
if (typeof orderInfo === "object" && orderInfo.code && orderInfo.code !== "10000") {
res.errCode = orderInfo.code;
res.errMsg = orderInfo.subMsg;
}
}
res.order = orderInfo;
}
} catch (err) {
let errMsg = err.errorMessage || err.message;
console.error("data: ", data);
console.error("getOrderInfoParam: ", getOrderInfoParam);
console.error("err: ", err);
console.error("errMsg: ", errMsg);
throw { errCode: ERROR[53001], errMsg };
}
// 尝试获取下订单信息
let payOrderInfo = await dao.uniPayOrders.find({
order_no,
out_trade_no
});
let create_date = Date.now();
// 如果订单不存在,则添加
if (!payOrderInfo) {
// 添加数据库(数据库的out_trade_no字段需设置为唯一索引)
let stat_platform = clientInfo.platform;
if (stat_platform === "app") {
stat_platform = clientInfo.os;
}
let nickname;
if (user_id) {
// 获取nickname冗余昵称
let userInfo = await dao.uniIdUsers.findById(user_id);
if (userInfo) nickname = userInfo.nickname;
}
await dao.uniPayOrders.add({
provider,
provider_pay_type,
uni_platform: platform,
status: 0,
type,
order_no,
out_trade_no,
user_id,
nickname,
device_id,
client_ip,
openid,
description,
total_fee,
refund_fee: 0,
refund_count: 0,
provider_appid: uniPayConifg.appId,
appid,
custom,
create_date,
expand_data,
stat_data: {
platform: stat_platform,
app_version: clientInfo.appVersion,
app_version_code: clientInfo.appVersionCode,
app_wgt_version: clientInfo.appWgtVersion,
os: clientInfo.os,
ua: clientInfo.ua,
channel: clientInfo.channel ? clientInfo.channel : String(clientInfo.scene),
scene: clientInfo.scene
}
});
} else {
// 如果订单已经存在,则修改下支付方式(用户可能先点微信支付,未付款,又点了支付宝支付)
await dao.uniPayOrders.updateById(payOrderInfo._id, {
provider,
provider_pay_type,
provider_appid: uniPayConifg.appId,
custom
});
}
// 自动删除3天前的订单未付款订单
// await dao.uniPayOrders.deleteExpPayOrders();
// 业务逻辑结束-----------------------------------------------------------
return res;
}
/**
* 统一支付结果查询
* @description 根据商户订单号或者平台订单号查询订单信息主要用于未接收到支付通知时可以使用此接口进行支付结果验证
*/
async getOrder(data = {}) {
let {
out_trade_no, // 支付插件订单号
transaction_id, // 支付平台的交易单号
await_notify = false, // 是否需要等待异步通知执行完成才返回前端支付结果
} = data;
let res = { errCode: 0, errMsg: 'ok' };
// 业务逻辑开始-----------------------------------------------------------
if (!out_trade_no && !transaction_id) {
throw { errCode: ERROR[51010] };
}
let payOrderInfo;
if (transaction_id) {
payOrderInfo = await dao.uniPayOrders.find({
transaction_id
});
} else if (out_trade_no) {
payOrderInfo = await dao.uniPayOrders.find({
out_trade_no
});
}
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
// 初始化uniPayInstance
let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
let orderQueryJson = {};
if (out_trade_no) {
orderQueryJson.outTradeNo = out_trade_no;
} else {
orderQueryJson.transactionId = transaction_id;
}
if (payOrderInfo.provider === "wxpay-virtual") {
orderQueryJson.openid = payOrderInfo.openid;
}
let queryRes;
if (typeof uniPayInstance.orderQuery === "function") {
queryRes = await uniPayInstance.orderQuery(orderQueryJson);
console.log('queryRes: ', queryRes)
} else {
// 无uniPayInstance.orderQuery函数时的兼容处理
if ([1, 2].indexOf(payOrderInfo.status) > -1) {
queryRes = {
tradeState: "SUCCESS",
tradeStateDesc: "订单已支付"
};
} else if ([3].indexOf(payOrderInfo.status) > -1) {
queryRes = {
tradeState: "REFUNDED",
tradeStateDesc: "订单已退款"
};
} else {
queryRes = {
tradeState: "NOPAY",
tradeStateDesc: "订单未支付"
};
}
}
if (queryRes.tradeState === 'SUCCESS' || queryRes.tradeState === 'FINISHED') {
if (typeof payOrderInfo.user_order_success == "undefined" && await_notify) {
let whileTime = 0; // 当前循环已执行的时间(毫秒)
let whileInterval = 500; // 每次循环间隔时间(毫秒)
let maxTime = 20000; // 循环执行时间超过此值则退出循环(毫秒)
while (typeof payOrderInfo.user_order_success == "undefined" && whileTime <= maxTime) {
await libs.common.sleep(whileInterval);
whileTime += whileInterval;
payOrderInfo = await dao.uniPayOrders.find({
out_trade_no
});
}
}
res = {
errCode: 0,
errMsg: "ok",
has_paid: true, // 标记用户是否已付款成功(此参数只能表示用户确实付款了,但系统的异步回调逻辑可能还未执行完成)
out_trade_no, // 支付插件订单号
transaction_id, // 支付平台订单号
status: payOrderInfo.status, // 标记当前支付订单状态 -1已关闭 0未支付 1已支付 2已部分退款 3已全额退款
user_order_success: payOrderInfo.user_order_success, // 用户异步通知逻辑是否全部执行完成且无异常建议前端通过此参数是否为true来判断是否支付成功
pay_order: payOrderInfo,
}
} else {
let errMsg = queryRes.tradeStateDesc || "未支付或已退款";
if (errMsg.indexOf("订单发生过退款") > -1) {
errMsg = "订单已退款";
}
res = {
errCode: -1,
errMsg: errMsg,
has_paid: false,
out_trade_no, // 支付插件订单号
transaction_id, // 支付平台订单号
}
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
/**
* 统一退款
* @description 当交易发生之后一段时间内由于买家或者卖家的原因需要退款时卖家可以通过退款接口将支付款退还给买家
*/
async refund(data = {}) {
let {
out_trade_no, // 插件支付单号
out_refund_no, // 退款单号(若不传,则自动生成)
refund_desc = "用户申请退款",
refund_fee: myRefundFee,
refund_fee_type = "CNY"
} = data;
let res = { errCode: 0, errMsg: 'ok' };
// 业务逻辑开始-----------------------------------------------------------
if (!out_trade_no) {
throw { errCode: ERROR[51001] };
}
let payOrderInfo = await dao.uniPayOrders.find({
out_trade_no
});
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
let refund_count = payOrderInfo.refund_count || 0;
refund_count++;
// 生成退款订单号
let outRefundNo = out_refund_no ? out_refund_no : `${out_trade_no}-${refund_count}`;
// 订单总金额
let totalFee = payOrderInfo.total_fee;
// 退款总金额
let refundFee = myRefundFee || totalFee;
let provider = payOrderInfo.provider;
let uniPayConifg = await this.getUniPayConfig(payOrderInfo);
let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
let refundParams = {
outTradeNo: out_trade_no,
outRefundNo,
totalFee,
refundFee,
refundDesc: refund_desc,
refundFeeType: refund_fee_type
};
if (payOrderInfo.provider === "wxpay-virtual") {
refundParams.openid = payOrderInfo.openid;
refundParams.refundReason = "3"; // 0-暂无描述 1-产品问题,影响使用或效果不佳 2-售后问题,无法满足需求 3-意愿问题,用户主动退款 4-价格问题 5-其他原因
refundParams.reqFrom = "2"; // 当前只支持"1"-人工客服退款,即用户电话给客服,由客服发起退款流程 "2"-用户自己发起退款流程 "3"-其他
// 查询当前可退款金额
refundParams.leftFee = totalFee - (payOrderInfo.refund_fee || 0);
// 实时查询当前可退款金额(此处注释可省一次请求)
// const orderQueryRes = await uniPayInstance.orderQuery({
// openid: payOrderInfo.openid,
// outTradeNo: payOrderInfo.out_trade_no
// });
// refundParams.leftFee = orderQueryRes.leftFee;
}
console.log(`---- ${out_trade_no} -- ${outRefundNo} -- ${totalFee/100} -- ${refundFee/100}`)
// 退款操作
try {
res.result = await uniPayInstance.refund(refundParams);
} catch (err) {
console.error(err);
let errMsg = err.message;
if (errMsg) {
if (errMsg.indexOf("verify failure") > -1) {
throw { errCode: ERROR[53005] };
}
if (errMsg.indexOf("header too long") > -1) {
throw { errCode: ERROR[53005] };
}
}
return { errCode: -1, errMsg: errMsg, err }
}
if (res.result.refundFee) {
res.errCode = 0;
res.errMsg = "ok";
// 修改数据库
try {
let time = Date.now();
// 修改订单状态
payOrderInfo = await dao.uniPayOrders.updateAndReturn({
whereJson: {
_id: payOrderInfo._id,
"refund_list.out_refund_no": _.neq(outRefundNo)
},
dataJson: {
status: 2,
refund_fee: _.inc(refundFee),
refund_count: refund_count,
refund_date: time, // 更新最近一次退款时间
// 记录每次的退款详情
refund_list: _.unshift({
refund_date: time,
refund_fee: refundFee,
out_refund_no: outRefundNo,
refund_desc
})
}
});
if (payOrderInfo && payOrderInfo.refund_fee >= payOrderInfo.total_fee) {
// 修改订单状态为已全额退款
await dao.uniPayOrders.updateById(payOrderInfo._id, {
status: 3,
refund_fee: payOrderInfo.total_fee,
});
}
} catch (err) {
console.error(err);
}
} else {
console.log('res.result: ', res.result);
throw { errCode: ERROR[53002] };
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
/**
* 查询退款查询退款情况
* @description 提交退款申请后通过调用该接口查询退款状态
*/
async getRefund(data = {}) {
let {
out_trade_no, // 插件支付单号
} = data;
if (!out_trade_no) {
throw { errCode: ERROR[51001] };
}
let payOrderInfo = await dao.uniPayOrders.find({
out_trade_no
});
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
let provider = payOrderInfo.provider;
let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
let queryRes;
try {
let refundQueryJson = {
outTradeNo: out_trade_no,
outRefundNo: payOrderInfo.refund_list[0].out_refund_no
};
if (provider === "wxpay-virtual") {
refundQueryJson.openid = payOrderInfo.openid;
}
queryRes = await uniPayInstance.refundQuery(refundQueryJson);
} catch (err) {
throw { errCode: ERROR[53003], errMsg: err.errMsg };
}
let orderInfo = {
total_fee: payOrderInfo.total_fee,
refund_fee: payOrderInfo.refund_fee,
refund_count: payOrderInfo.refund_count,
refund_list: payOrderInfo.refund_list,
provider: payOrderInfo.provider,
provider_pay_type: payOrderInfo.provider_pay_type,
status: payOrderInfo.status,
type: payOrderInfo.type,
out_trade_no: payOrderInfo.out_trade_no,
transaction_id: payOrderInfo.transaction_id,
};
if (queryRes.refundFee > 0) {
let msg = "ok";
if (payOrderInfo.refund_list && payOrderInfo.refund_list.length > 0) {
msg = `合计退款 ${payOrderInfo.refund_fee/100}\r\n`;
for (let i in payOrderInfo.refund_list) {
let item = payOrderInfo.refund_list[i];
let index = Number(i) + 1;
let timeStr = libs.common.timeFormat(item.refund_date, "yyyy-MM-dd hh:mm:ss");
msg += `${index}${timeStr} \r\n退款 ${item.refund_fee/100} \r\n`;
}
}
return {
errCode: 0,
errMsg: msg,
pay_order: orderInfo,
result: queryRes
}
} else {
throw { errCode: ERROR[53003] };
}
}
/**
* 关闭订单
* @description 用于交易创建后用户在一定时间内未进行支付可调用该接口直接将未付款的交易进行关闭避免重复支付
* 注意
* 微信支付订单生成后不能马上调用关单接口最短调用时间间隔为 5 分钟
* 微信虚拟支付不支持关闭订单
*/
async closeOrder(data = {}) {
let {
out_trade_no, // 插件支付单号
} = data;
if (!out_trade_no) {
throw { errCode: ERROR[51001] };
}
let payOrderInfo = await dao.uniPayOrders.find({
out_trade_no
});
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
let { provider } = payOrderInfo;
let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
let closeOrderRes = await uniPayInstance.closeOrder({
outTradeNo: out_trade_no
});
let wxpayResult = (provider === "wxpay" && closeOrderRes.resultCode === "SUCCESS");
let alipayResult = (provider === "alipay" && closeOrderRes.code === "10000");
if (wxpayResult || alipayResult) {
// 修改订单状态为已取消
await dao.uniPayOrders.update({
whereJson: {
_id: payOrderInfo._id,
status: 0
},
dataJson: {
status: -1,
cancel_date: Date.now()
}
});
return {
errCode: 0,
errMsg: "订单关闭成功",
result: closeOrderRes
}
} else {
throw { errCode: ERROR[53004] };
}
}
/**
* 根据code获取openid
*/
async getOpenid(data = {}) {
let {
provider, // 支付供应商
code, // 用户登录获取的code
clientInfo, // 客户端环境
} = data;
if (!code) {
throw { errCode: ERROR[51002] };
}
let { platform, ua } = clientInfo;
// 获取并自动匹配支付供应商的支付类型
let provider_pay_type = libs.common.getProviderPayType({
provider,
platform,
ua
});
let needCacheSessionKey = false;
let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
if (provider === "wxpay") {
try {
// 如果配置了微信虚拟支付则使用微信虚拟支付的配置作为微信获取openid的配置
let wxpayVirtualPayConifg = await this.getUniPayConfig({ provider: "wxpay-virtual", provider_pay_type: "mp" });
if (wxpayVirtualPayConifg && wxpayVirtualPayConifg.appId && wxpayVirtualPayConifg.secret) {
uniPayConifg = wxpayVirtualPayConifg;
needCacheSessionKey = true;
}
} catch (err) {}
let res = await libs.wxpay.getOpenid({
config: uniPayConifg,
code,
provider_pay_type,
});
if (needCacheSessionKey) {
// 将session_key保存到缓存表中
let cacheKey = {
appId: uniPayConifg.appId,
platform: "weixin-mp",
openid: res.openid
}
let session_key = res.session_key;
delete res.session_key;
await dao.opendbOpenData.setSessionKey(cacheKey, { session_key }, 30 * 24 * 60 * 60);
}
return res;
} else if (provider === "alipay") {
return await libs.alipay.getOpenid({
config: uniPayConifg,
code,
});
}
}
/**
* 获取支持的支付方式
* let payTypes = await service.pay.getPayProviderFromCloud();
*/
async getPayProviderFromCloud() {
let wxpay = config.wxpay && config.wxpay.enable ? true : false;
let alipay = config.alipay && config.alipay.enable ? true : false;
let provider = [];
if (wxpay) provider.push("wxpay");
if (alipay) provider.push("alipay");
return {
errCode: 0,
errMsg: "ok",
wxpay,
alipay,
provider
};
}
/**
* 验证iosIap苹果内购支付凭据
* let payTypes = await service.pay.verifyReceiptFromAppleiap();
*/
async verifyReceiptFromAppleiap(data) {
let {
out_trade_no,
transaction_receipt,
transaction_identifier,
} = data;
if (!out_trade_no) {
throw { errCode: ERROR[51001] };
}
// 初始化uniPayInstance
let uniPayInstance = await this.initUniPayInstance({ provider: "appleiap", provider_pay_type: "app" });
let verifyReceiptRes = await uniPayInstance.verifyReceipt({
receiptData: transaction_receipt
});
let userOrderSuccess = false;
let pay_date;
if (verifyReceiptRes.tradeState !== "SUCCESS") {
// 尝试使用相反的环境再次验证
let uniPayConifg = await this.getUniPayConfig({ provider: "appleiap", provider_pay_type: "app" });
uniPayInstance = uniPay.initAppleIapPayment({
...uniPayConifg,
sandbox: !uniPayConifg.sandbox,
});
verifyReceiptRes = await uniPayInstance.verifyReceipt({
receiptData: transaction_receipt
});
if (verifyReceiptRes.tradeState !== "SUCCESS") {
// 如果还是不成功,则校验不通过
throw { errCode: ERROR[54002] };
}
}
// 支付成功
pay_date = Number(verifyReceiptRes.receipt.receipt_creation_date_ms);
let inAppList = verifyReceiptRes.receipt.in_app;
let inApp = inAppList.find((item) => {
return item.transaction_id === transaction_identifier;
});
if (!inApp) {
// 校验不通过
throw { errCode: ERROR[54002] };
}
let quantity = inApp.quantity; // 购买数量
let product_id = inApp.product_id; // 对应的内购产品id
let transaction_id = inApp.transaction_id; // 本次交易id
if ((Date.now() - 1000 * 3600 * 24) > pay_date) {
// 订单已超24小时不做处理通知前端直接关闭订单。
return {
errCode: 0,
errMsg: "ok"
};
}
// 查询该transaction_id是否使用过如果已使用则不做处理通知前端直接关闭订单。
let findOrderInfo = await dao.uniPayOrders.find({
transaction_id,
});
if (findOrderInfo) {
return {
errCode: 0,
errMsg: "ok"
};
}
// 否则,执行用户回调
// 用户自己的逻辑处理 开始-----------------------------------------------------------
let orderPaySuccess;
let payOrderInfo = await dao.uniPayOrders.find({
out_trade_no,
});
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
try {
// 加载自定义异步回调函数
orderPaySuccess = require(`../notify/${payOrderInfo.type}`);
} catch (err) {
console.log(err);
}
if (typeof orderPaySuccess === "function") {
payOrderInfo = await dao.uniPayOrders.updateAndReturn({
whereJson: {
status: 0, // status:0 为必须条件,防止重复推送时的错误
out_trade_no: out_trade_no, // 商户订单号
},
dataJson: {
status: 1, // 设置为已付款
transaction_id: transaction_id, // 第三方支付单号
pay_date: pay_date,
notify_date: pay_date,
original_data: verifyReceiptRes
}
});
console.log('用户自己的回调逻辑 - 开始执行');
userOrderSuccess = await orderPaySuccess({
verifyResult: verifyReceiptRes,
data: payOrderInfo,
});
console.log('用户自己的回调逻辑 - 执行完成');
payOrderInfo = await dao.uniPayOrders.updateAndReturn({
whereJson: {
status: 1,
out_trade_no,
},
dataJson: {
user_order_success: userOrderSuccess,
}
});
} else {
payOrderInfo = await dao.uniPayOrders.find({
out_trade_no,
});
}
console.log('userOrderSuccess', userOrderSuccess);
// 用户自己的逻辑处理 结束-----------------------------------------------------------
//console.log('verifyReceiptRes: ', verifyReceiptRes);
return {
errCode: 0,
errMsg: "ok",
has_paid: true, // 标记用户是否已付款成功(此参数只能表示用户确实付款了,但系统的异步回调逻辑可能还未执行完成)
out_trade_no, // 支付插件订单号
transaction_id, // 支付平台订单号
status: payOrderInfo.status, // 标记当前支付订单状态 -1已关闭 0未支付 1已支付 2已部分退款 3已全额退款
user_order_success: payOrderInfo.user_order_success, // 用户异步通知逻辑是否全部执行完成且无异常建议前端通过此参数是否为true来判断是否支付成功
pay_order: payOrderInfo,
};
}
/**
* 获取对应支付配置
* let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
*/
async getUniPayConfig(data = {}) {
let {
provider,
provider_pay_type,
} = data;
if (config && config[provider] && config[provider][provider_pay_type]) {
let uniPayConfig = config[provider][provider_pay_type];
if (!uniPayConfig.appId && provider !== "appleiap") {
throw new Error(`uni-pay配置${provider}.${provider_pay_type}节点下的appId不能为空`);
}
return uniPayConfig;
} else {
throw new Error(`${provider}_${provider_pay_type} : 商户支付配置错误`);
}
}
/**
* 初始化uniPayInstance
* let uniPayInstance = await service.pay.initUniPayInstance({ provider, provider_pay_type });
*/
async initUniPayInstance(data = {}) {
let {
provider,
} = data;
let uniPayConifg = await this.getUniPayConfig(data);
let uniPayInstance;
if (provider === "wxpay") {
// 微信
if (uniPayConifg.version === 3) {
try {
uniPayInstance = uniPay.initWeixinV3(uniPayConifg);
} catch (err) {
console.error(err);
let errMsg = err.message;
if (errMsg && errMsg.indexOf("invalid base64 body") > -1) {
throw { errCode: ERROR[53005] };
}
throw err;
}
} else {
uniPayInstance = uniPay.initWeixin(uniPayConifg);
}
} else if (provider === "alipay") {
// 支付宝
uniPayInstance = uniPay.initAlipay(uniPayConifg);
} else if (provider === "appleiap") {
// ios内购
uniPayInstance = uniPay.initAppleIapPayment(uniPayConifg);
} else if (provider === "wxpay-virtual") {
// 微信虚拟支付
// 还需要额外传accessToken
uniPayConifg.accessToken = await this.getAccessToken(data);
uniPayInstance = uniPay.initWeixinVirtualPayment(uniPayConifg);
} else {
throw new Error(`${provider} : 不支持的支付方式`);
}
return uniPayInstance;
}
/**
* 获取accessToken
* let uniPayInstance = await service.pay.getAccessToken({ provider, provider_pay_type });
*/
async getAccessToken(data = {}) {
let uniPayConifg = await this.getUniPayConfig(data);
let cacheKey = {
appId: uniPayConifg.appId,
platform: "weixin-mp"
}
let cacheInfo = await dao.opendbOpenData.getAccessToken(cacheKey);
if (cacheInfo) {
// 缓存有值
return cacheInfo.access_token;
} else {
// 缓存无值
let getAccessTokenRes = await libs.wxpay.getAccessToken(uniPayConifg);
let accessToken = getAccessTokenRes.accessToken;
// 缓存accessToken
await dao.opendbOpenData.setAccessToken(cacheKey, {
access_token: getAccessTokenRes.accessToken,
}, getAccessTokenRes.expiresIn);
return accessToken;
}
}
/**
* 获取sessionKey
* let sessionKey = await service.pay.getSessionKey({ provider, provider_pay_type, openid });
*/
async getSessionKey(data = {}) {
let {
openid,
} = data;
// 获取用户的sessionKey
let uniPayConifg = await this.getUniPayConfig(data);
let { session_key } = await dao.opendbOpenData.getSessionKey({
appId: uniPayConifg.appId,
platform: "weixin-mp",
openid
});
return session_key;
}
/**
* 请求微信小程序虚拟支付API
* let res = await service.pay.requestWxpayVirtualApi(data);
*/
async requestWxpayVirtualApi(options = {}) {
let {
method,
data = {}
} = options;
// 微信虚拟支付固定参数
let provider = "wxpay-virtual";
let provider_pay_type = "mp";
// 获得微信小程序虚拟支付实例
let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
// 调用微信小程序虚拟支付云端API
if (["currencyPay"].indexOf(method) > -1) {
if (!data.sessionKey) {
data.sessionKey = await this.getSessionKey({ ...data, provider, provider_pay_type });
}
}
let res = await uniPayInstance[method](data);
return res;
}
}
module.exports = new service();