* 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";
}
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;
}
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,
openid,
appId,
tradeState,
} = verifyResult;
if (resultCode == "SUCCESS" || tradeState === "SUCCESS") {
let time = Date.now();
let payOrderInfo = await dao.uniPayOrders.updateAndReturn({
whereJson: {
status: 0,
out_trade_no: outTradeNo,
},
dataJson: {
status: 1,
transaction_id: transactionId,
pay_date: time,
notify_date: time,
openid,
provider,
provider_pay_type,
original_data: httpInfo,
}
});
if (payOrderInfo) {
let userOrderSuccess = false;
let orderPaySuccess;
try {
orderPaySuccess = require(`../notify/${payOrderInfo.type}`);
} catch (err) {
console.log(err);
}
if (typeof orderPaySuccess === "function") {
console.log('用户自己的回调逻辑 - 开始执行');
userOrderSuccess = await orderPaySuccess({
verifyResult,
data: payOrderInfo,
clientInfo,
cloudInfo
});
console.log('用户自己的回调逻辑 - 执行完成');
}
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_uid,
openid,
order_no,
out_trade_no,
description,
type,
qr_code,
custom,
other,
clientInfo,
cloudInfo,
wxpay_virtual,
apple_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 (provider === "appleiap") {
if (typeof apple_virtual !== "object") {
throw { errCode: ERROR[51013] };
}
if (typeof apple_virtual.buy_quantity !== "number" || apple_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,
} = 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);
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}`;
let tradeType = libs.common.getTradeType({ provider, provider_pay_type });
let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
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) {
getOrderInfoParam.sceneInfo = uniPayConifg.sceneInfo;
} else {
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 {
if (provider !== "appleiap") {
let orderInfo;
if (other) {
other = libs.common.snake2camelJson(other);
getOrderInfoParam = Object.assign(getOrderInfoParam, other);
}
getOrderInfoParam = JSON.parse(JSON.stringify(getOrderInfoParam));
if (provider === "wxpay-virtual") {
expand_data = {
mode: wxpay_virtual.mode,
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;
}
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) {
let stat_platform = clientInfo.platform;
if (stat_platform === "app") {
stat_platform = clientInfo.os;
}
let nickname;
if (user_uid) {
let userInfo = await dao.uniIdUsers.findById(user_uid);
if (userInfo) nickname = userInfo.nickname;
}
let appleiap_account_token;
if (provider === "appleiap") {
appleiap_account_token = libs.crypto.generateUUID();
res.appleiap_account_token = appleiap_account_token;
}
await dao.uniPayOrders.add({
provider,
provider_pay_type,
uni_platform: platform,
status: 0,
type,
order_no,
out_trade_no,
user_uid,
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,
appleiap_account_token,
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,
});
}
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] };
}
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 {
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,
user_order_success: payOrderInfo.user_order_success,
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";
refundParams.reqFrom = "2";
refundParams.leftFee = totalFee - (payOrderInfo.refund_fee || 0);
}
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,
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 {
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) {
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,
appleiap_account_token,
transaction_receipt,
transaction_identifier,
clientInfo,
} = data;
if (!out_trade_no) {
if (!appleiap_account_token) {
return {
errCode: 0,
errMsg: "Invalid out_trade_no"
}
}
appleiap_account_token = appleiap_account_token.toLowerCase();
let payOrderInfo = await dao.uniPayOrders.find({
provider: "appleiap",
appleiap_account_token
});
if (!payOrderInfo || !payOrderInfo.out_trade_no) {
return {
errCode: 0,
errMsg: "Invalid out_trade_no"
}
}
out_trade_no = payOrderInfo.out_trade_no;
}
let payOrderInfo = await dao.uniPayOrders.find({
out_trade_no,
});
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
const verifyReceipt = async (uniPayConifg) => {
const jwt = libs.jsonwebtoken;
const fs = require('fs');
const privateKey = fs.readFileSync(uniPayConifg.appCertPath, 'utf8');
const header = {
alg: 'ES256',
kid: uniPayConifg.appId,
typ: "JWT"
};
const nowTime = Date.now();
const bundleId = uniPayConifg.sandbox ? uniPayConifg.devBundleId || uniPayConifg.bundleId : uniPayConifg.bundleId;
const payload = {
iss: uniPayConifg.issuerId,
iat: Math.floor(nowTime / 1000),
exp: Math.floor(nowTime / 1000) + 3600,
aud: 'appstoreconnect-v1',
bid: bundleId
};
const iapToken = jwt.sign(payload, privateKey, {
algorithm: 'ES256',
header: header
});
const serviceUrl = uniPayConifg.sandbox ? "https://api.storekit-sandbox.itunes.apple.com" : "https://api.appstoreconnect.apple.com";
const url = `${serviceUrl}/inApps/v1/transactions/${transaction_identifier}`;
let requestRes;
for (let i = 0; i <= 5; i++) {
try {
requestRes = await uniCloud.request({
method: "GET",
header: {
'Authorization': `Bearer ${iapToken}`,
'Content-Type': 'application/json'
},
url
});
break;
} catch (err) {
}
}
if (requestRes.statusCode !== 200) {
return {};
}
const signedInfoTokenArr = requestRes.data.signedTransactionInfo.split('.');
const signedInfoString = Buffer.from(signedInfoTokenArr[1], 'base64').toString('utf8');
const verifyReceiptRes = JSON.parse(signedInfoString);
const appAccountToken = verifyReceiptRes.appAccountToken.toLowerCase();
verifyReceiptRes.tradeState = verifyReceiptRes.inAppOwnershipType === "PURCHASED" && payOrderInfo.appleiap_account_token === appAccountToken ? "SUCCESS" : "fail";
return verifyReceiptRes;
};
let uniPayConifg = await this.getUniPayConfig({ provider: "appleiap", provider_pay_type: "app" });
let verifyReceiptRes = await verifyReceipt(uniPayConifg);
let userOrderSuccess = false;
let pay_date;
if (verifyReceiptRes.tradeState !== "SUCCESS") {
console.log('尝试使用相反的环境再次验证: ');
verifyReceiptRes = await verifyReceipt({
...uniPayConifg,
sandbox: !uniPayConifg.sandbox
});
if (verifyReceiptRes.tradeState !== "SUCCESS") {
throw { errCode: ERROR[54002] };
}
}
let isSubscribe = false;
if (["Auto-Renewable Subscription"].indexOf(verifyReceiptRes.type) > -1) {
isSubscribe = true;
}
pay_date = Number(verifyReceiptRes.purchaseDate);
let quantity = verifyReceiptRes.quantity;
let product_id = verifyReceiptRes.productId;
let transaction_id = verifyReceiptRes.transactionId;
let original_transaction_id = verifyReceiptRes.originalTransactionId;
if ((Date.now() - 1000 * 3600 * 72) > pay_date && !isSubscribe) {
return {
errCode: 0,
errMsg: "ok"
};
}
if (isSubscribe && original_transaction_id !== transaction_id) {
let findOrderInfo = await dao.uniPayOrders.find({
appleiap_account_token: payOrderInfo.appleiap_account_token,
user_order_success: _.exists(true)
});
if (findOrderInfo) {
let quantity = verifyReceiptRes.quantity;
let goods_price = parseFloat((verifyReceiptRes.price / 1000).toFixed(2));
let total_fee = parseFloat((goods_price * 100 * quantity).toFixed(2));
let description = "[自动续期]" + payOrderInfo.description.replace(/\[自动续期\]/g, '');
let stat_platform = clientInfo.platform;
if (stat_platform === "app") {
stat_platform = clientInfo.os;
}
let addId = await dao.uniPayOrders.add({
provider: payOrderInfo.provider,
provider_pay_type: payOrderInfo.provider_pay_type,
uni_platform: clientInfo.platform,
status: 0,
type: payOrderInfo.type,
order_no: payOrderInfo.order_no,
out_trade_no: transaction_id,
user_uid: payOrderInfo.user_uid,
nickname: payOrderInfo.nickname,
device_id: clientInfo.deviceId,
client_ip: clientInfo.client_ip,
openid: payOrderInfo.openid,
description,
total_fee,
refund_fee: 0,
refund_count: 0,
provider_appid: uniPayConifg.appId,
appid: clientInfo.appId,
custom: payOrderInfo.custom,
create_date: Date.now(),
expand_data: payOrderInfo.expand_data,
appleiap_account_token,
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
}
});
payOrderInfo = await dao.uniPayOrders.find({
_id: addId,
});
out_trade_no = transaction_id;
}
}
let findOrderInfo = await dao.uniPayOrders.find({
transaction_id,
});
const repeatReceipt = () => {
return {
errCode: 0,
errMsg: "ok",
repeat: true,
};
};
if (findOrderInfo) {
return repeatReceipt();
}
let orderPaySuccess;
try {
orderPaySuccess = require(`../notify/${payOrderInfo.type}`);
} catch (err) {
console.log(err);
}
if (typeof orderPaySuccess === "function") {
let newPayOrderInfo = await dao.uniPayOrders.updateAndReturn({
whereJson: {
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
}
});
if (!newPayOrderInfo) {
return repeatReceipt();
}
payOrderInfo = newPayOrderInfo;
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);
return {
errCode: 0,
errMsg: "ok",
has_paid: true,
out_trade_no,
transaction_id,
status: payOrderInfo.status,
user_order_success: payOrderInfo.user_order_success,
pay_order: payOrderInfo,
is_subscribe: isSubscribe
};
}
* 获取对应支付配置
* 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") {
uniPayInstance = uniPay.initAppleIapPayment(uniPayConifg);
} else if (provider === "wxpay-virtual") {
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;
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;
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 });
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();