第一次提交

This commit is contained in:
gxwebsoft
2023-08-04 13:14:48 +08:00
commit 1b923e5cff
1030 changed files with 128016 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
/**
* 全局错误码
*/
const ERROR = {
50403: 50403,
// 参数错误
51001: 51001,
51002: 51002,
51003: 51003,
51004: 51004,
51005: 51005,
51006: 51006,
51007: 51007,
51008: 51008,
51009: 51009,
51010: 51010,
// 数据不存在
52001: 52001,
52002: 52002,
// 运行错误
53001: 53001,
53002: 53002,
53003: 53003,
53004: 53004,
53005: 53005,
}
const errSubject = "uni-pay";
function isUniPayError(errCode) {
return Object.values(ERROR).includes(errCode);
}
class UniCloudError extends Error {
constructor(options = {}) {
super(options.message);
this.errMsg = options.message || '';
this.code = this.errCode = options.code;
this.errSubject = options.subject || errSubject;
this.forceReturn = options.forceReturn || false;
this.cause = options.cause;
}
toJson(level = 0) {
if (level >= 10) {
return
}
level++
return {
errCode: this.errCode,
errMsg: this.errMsg,
code: this.errCode,
message: this.message,
errSubject: this.errSubject,
cause: this.cause && this.cause.toJson ? this.cause.toJson(level) : this.cause
}
}
}
module.exports = {
ERROR,
isUniPayError,
UniCloudError
}

View File

@@ -0,0 +1,9 @@
// 各接口权限配置,未配置接口表示允许任何用户访问(包括未登录用户)
module.exports = {
refund: {
// auth: true // 已登录用户方可操作,配置角色或权限时此项可不写
role: ['admin'] // 允许进行此操作的角色,包含任一角色均可操作。
// permission: [] // 允许进行此操作的权限,包含任一权限均可操作。
// 权限角色均配置时,用户拥有任一权限或任一角色均可操作
}
}

View File

@@ -0,0 +1,6 @@
const uniPayOrders = require("./uniPayOrders");
const uniIdUsers = require("./uniIdUsers");
module.exports = {
uniPayOrders,
uniIdUsers
};

View File

@@ -0,0 +1 @@
dao名词解释Data Access Object

View File

@@ -0,0 +1,25 @@
const dbName = {
user: "uni-id-users"
}
const db = uniCloud.database();
const _ = db.command;
var dao = {};
/**
* 获取 - 第三方支付订单数据
let userInfo = await dao.uniIdUsers.findById(id);
*/
dao.findById = async (id) => {
let res = await db.collection(dbName.user).doc(id).get();
if (res.data && res.data.length > 0) {
return res.data[0];
} else {
return null;
}
return res;
};
module.exports = dao;

View File

@@ -0,0 +1,108 @@
const dbName = {
payOrders: "uni-pay-orders" // 数据库表名 - 第三方支付订单表
}
const db = uniCloud.database();
const _ = db.command;
var dao = {};
/**
* 添加 - 第三方支付订单数据
await dao.uniPayOrders.add({
});
*/
dao.add = async (dataJson = {}) => {
// 数据库操作开始-----------------------------------------------------------
let res = await db.collection(dbName.payOrders).add(dataJson);
// 数据库操作结束-----------------------------------------------------------
return res.id ? res.id : null;
};
/**
* 获取 - 第三方支付订单数据
let payOrderInfo = await dao.uniPayOrders.find({
order_no,
out_trade_no
});
*/
dao.find = async (where) => {
let res = await db.collection(dbName.payOrders)
.where(where)
.limit(1)
.get();
if (res.data && res.data.length > 0) {
return res.data[0];
} else {
return null;
}
return res;
};
/**
* 修改 - 第三方支付订单数据
await dao.uniPayOrders.updateById(id, {
});
*/
dao.updateById = async (id = "___", dataJson) => {
// 数据库操作开始-----------------------------------------------------------
let res = await db.collection(dbName.payOrders).doc(id).update(dataJson);
// 数据库操作结束-----------------------------------------------------------
return res ? res.updated : 0;
};
/**
* 修改 - 第三方支付订单数据
await dao.uniPayOrders.update({
whereJson:{
},
dataJson:{
}
});
*/
dao.update = async (obj) => {
let { whereJson, dataJson } = obj;
// 数据库操作开始-----------------------------------------------------------
let res = await db.collection(dbName.payOrders).where(whereJson).update(dataJson);
// 数据库操作结束-----------------------------------------------------------
return res ? res.updated : 0;
};
/**
* 修改 - 第三方支付订单数据
await dao.uniPayOrders.updateAndReturn({
whereJson:{
},
dataJson:{
}
});
*/
dao.updateAndReturn = async (obj) => {
let { whereJson, dataJson } = obj;
// 数据库操作开始-----------------------------------------------------------
let res = await db.collection(dbName.payOrders).where(whereJson).updateAndReturn(dataJson);
// 数据库操作结束-----------------------------------------------------------
return res.doc ? res.doc : null;
};
/**
* 删除超过3天还未支付款的订单
await dao.uniPayOrders.deleteExpPayOrders();
*/
dao.deleteExpPayOrders = async () => {
// 数据库操作开始-----------------------------------------------------------
let time = Date.now() - 1000 * 3600 * 24 * 3;
let res = await db.collection(dbName.payOrders)
.where({
status: _.in([-1,0]),
create_date: _.lt(time)
})
.remove();
// 数据库操作结束-----------------------------------------------------------
return res ? res.updated : 0;
};
module.exports = dao;

View File

@@ -0,0 +1,268 @@
/**
* uni-pay-co 统一支付云对象
*/
// 加载服务
const service = require('./service');
// 加载全局错误码
const { UniCloudError, isUniPayError, ERROR } = require('./common/error');
// 加载全局中间件
const middleware = require('./middleware/index');
// 加载uniId公共模块
const uniIdCommon = require('uni-id-common');
module.exports = {
/**
* 中间件(请求前执行)
*/
async _before() {
const params = this.getParams();
let clientInfo;
if (params && params[0] && params[0].clientInfo) {
clientInfo = params[0].clientInfo;
} else {
clientInfo = this.getClientInfo();
}
// 挂载uni-id实例到this上方便后续调用
this.uniIdCommon = uniIdCommon.createInstance({
clientInfo
});
// 国际化开始
const i18n = uniCloud.initI18n({
locale: clientInfo.locale || 'zh-Hans',
fallbackLocale: 'zh-Hans',
messages: require('./lang/index')
})
this.t = i18n.t.bind(i18n);
// 国际化结束
// 挂载中间件
this.middleware = {}
for (const mwName in middleware) {
this.middleware[mwName] = middleware[mwName].bind(this);
}
// 尝试从token获取用户信息
await this.middleware.auth(false);
// 通用权限校验模块
await this.middleware.accessControl();
// 设置全局获取userId公共函数可在此云对象的任意其他函数内通过 this.getUserId() 获取当前登录用户的id
this.getUserId = () => {
return this.authInfo && this.authInfo.uid ? this.authInfo.uid : undefined;
}
},
/**
* 中间件(请求后执行)
*/
_after(error, result) {
if (error) {
if (error.errCode) {
let errCode = error.errCode
if (!isUniPayError(errCode)) {
// 如果不是插件预设的错误码,则原样返回错误信息
return error;
}
return new UniCloudError({
code: errCode,
message: error.errMsg || this.t(errCode, error.errMsgValue),
});
}
throw error
}
return result;
},
/**
* 创建支付订单
*/
async createOrder(data) {
let {
provider, // 支付供应商 如 wxpay alipay 参考 https://uniapp.dcloud.net.cn/api/plugins/provider.html#
total_fee, // 订单总金额单位为分100等于1元
openid, // 发起支付的用户openid
order_no, // 业务系统订单号 建议控制在20-28位(不可以是24位,24位在阿里云空间可能会有问题)(可重复,代表1个业务订单会有多次付款的情况)
out_trade_no, // 支付插件订单号(需控制唯一,不传则由插件自动生成)
description, // 支付描述uniCloud个人版包月套餐
type, // 订单类型 goods订单付款 recharge余额充值付款 vipvip充值付款 等等,可自定义
qr_code, // true 强制开启二维码支付模式
custom, // 自定义参数(不会发送给第三方支付服务器)
other, // 其他请求参数(会发送给第三方支付服务器),
clientInfo, // 兼容云对象调用云对象模式
cloudInfo, // 兼容云对象调用云对象模式
} = data;
if (!clientInfo) clientInfo = this.getClientInfo();
if (!cloudInfo) cloudInfo = this.getCloudInfo();
// 获取当前登录的user_id
let user_id = this.getUserId();
let res = await service.pay.createOrder({
provider,
total_fee,
user_id,
openid,
order_no,
out_trade_no,
description,
type,
qr_code,
custom,
other,
clientInfo,
cloudInfo,
});
return res;
},
/**
* 接收支付异步通知
*/
async payNotify(data) {
const httpInfo = this.getHttpInfo();
return service.pay.paymentNotify({
httpInfo,
});
},
/**
* 查询支付状态
*/
async getOrder(data) {
let {
out_trade_no, // 插件订单号
transaction_id, // 第三方支付交易单号
await_notify = false, // 是否需要等待异步通知执行完成若为了响应速度可以设置为false若需要等待异步回调执行完成则设置为true
} = data;
res = await service.pay.getOrder({
out_trade_no,
transaction_id,
await_notify
});
return res;
},
/**
* 发起退款
* 此api只有admin角色可以访问
*/
async refund(data) {
let {
out_trade_no, // 插件订单号
out_refund_no, // 插件退款订单号
refund_desc, // 退款描述
refund_fee, // 退款金额
} = data;
res = await service.pay.refund({
out_trade_no,
out_refund_no,
refund_desc,
refund_fee,
});
return res;
},
/**
* 查询退款(查询退款情况)
*/
async getRefund(data) {
let {
out_trade_no, // 插件订单号
} = data;
res = await service.pay.getRefund({
out_trade_no,
});
return res;
},
/**
* 关闭订单(只有订单未支付时,才可以关闭,关闭后,用户即使在付款页面也无法付款)
*/
async closeOrder(data) {
let {
out_trade_no, // 插件订单号
} = data;
res = await service.pay.closeOrder({
out_trade_no,
});
return res;
},
/**
* 根据code获取openid
*/
async getOpenid(data = {}) {
let {
provider,
code,
clientInfo, // 兼容云对象调用云对象模式
} = data;
if (!clientInfo) clientInfo = this.getClientInfo();
res = await service.pay.getOpenid({
provider,
code,
clientInfo
});
return res;
},
/**
* 获取当前支持的支付方式
*/
async getPayProviderFromCloud() {
return await service.pay.getPayProviderFromCloud();
},
/**
* 获取支付配置内的appid主要用于获取获取微信公众号的appid用以获取code
*/
async getProviderAppId(data) {
let {
provider,
provider_pay_type
} = data;
// 注意,前往不要直接把 conifg 内的所有内容返回给前端
let conifg = service.pay.getConfig();
try {
return {
errorCode: 0,
appid: conifg[provider][provider_pay_type].appId,
}
} catch (err) {
return {
errorCode: 0,
appid: null
};
}
},
/**
* 验证iosIap苹果内购支付凭据
*/
async verifyReceiptFromAppleiap(data) {
let {
out_trade_no,
transaction_receipt,
transaction_identifier,
} = data;
return await service.pay.verifyReceiptFromAppleiap({
out_trade_no,
transaction_receipt,
transaction_identifier
});
},
}

View File

@@ -0,0 +1,29 @@
const word = {
};
const sentence = {
50403: 'Permission denied',
51001: 'Invalid out_trade_no',
51002: 'Invalid code',
51003: 'Invalid order_no',
51004: 'Invalid type',
51005: 'Invalid total_fee',
51006: 'Invalid description',
51007: 'Invalid provider',
51008: 'Invalid clientInfo',
51009: 'Invalid cloudInfo',
51010: 'Invalid out_trade_no or transaction_id',
52001: 'NotExist payOrder',
52002: 'NotExist notifyUrl',
53001: 'Create payment error',
53002: 'Refund error',
53003: 'Query refund error',
53004: 'Close order error',
53005: 'Cert verify error',
};
module.exports = {
...word,
...sentence
}

View File

@@ -0,0 +1,22 @@
let lang = {
'zh-Hans': require('./zh-hans'),
en: require('./en')
}
function mergeLanguage(lang1, lang2) {
const localeList = Object.keys(lang1)
localeList.push(...Object.keys(lang2))
const result = {}
for (let i = 0; i < localeList.length; i++) {
const locale = localeList[i]
result[locale] = Object.assign({}, lang1[locale], lang2[locale])
}
return result
}
try {
const langPath = require.resolve('uni-config-center/uni-id/lang/index.js')
lang = mergeLanguage(lang, require(langPath))
} catch (error) {}
module.exports = lang

View File

@@ -0,0 +1,29 @@
const word = {
};
const sentence = {
50403: '权限错误',
51001: '支付单号out_trade_no不能为空',
51002: 'code不能为空',
51003: '订单号order_no不能为空',
51004: '回调类型type不能为空如设置为goods代表商品订单',
51005: '支付金额total_fee必须为正整数>0的整数注意100=1元',
51006: '支付描述description不能为空',
51007: '支付供应商provider不能为空',
51008: 'clientInfo不能为空',
51009: 'cloudInfo不能为空',
51010: '支付单号或第三方交易单号不能同时为空',
52001: '支付订单不存在',
52002: '请先配置正确的异步回调URL',
53001: '获取支付信息失败,请稍后再试',
53002: '退款失败',
53003: '查询退款信息失败,请稍后再试',
53004: '关闭订单失败,请稍后再试',
53005: '证书错误,请检查支付证书',
};
module.exports = {
...word,
...sentence
}

View File

@@ -0,0 +1,145 @@
/**
* 支付宝相关函数
*/
const common = require('./common');
const crypto = require('crypto');
const ALIPAY_ALGORITHM_MAPPING = {
RSA: 'RSA-SHA1',
RSA2: 'RSA-SHA256'
}
var alipay = {
/**
* 获取openid
*/
async getOpenid(data) {
let {
config={},
code,
} = data;
if (!config.appId) throw new Error("uni-pay配置alipay.mp节点下的appId不能为空");
if (!config.privateKey) throw new Error("uni-pay配置alipay.mp节点下的privateKey不能为空");
let timestamp = common.timeFormat(new Date(), "yyyy-MM-dd hh:mm:ss");
let method = "alipay.system.oauth.token";
let params = {
timestamp,
code,
grant_type: "authorization_code"
};
let signData = this._getSign(method, params, config);
// 格式化url和请求参数
const { url, execParams } = this._formatUrl(signData);
let res = await uniCloud.httpclient.request(url, {
method: 'POST',
data: execParams,
dataType: 'text',
});
const result = JSON.parse(res.data)
let response = result.alipay_system_oauth_token_response;
if (res.status === 200 && response.user_id) {
return {
errCode: 0,
errMsg: 'ok',
openid: response.user_id,
user_id: response.user_id,
}
} else {
return {
errCode: -1,
errMsg: result.error_response.sub_msg
}
}
},
/**
* 签名
* @param {String} method 方法名
* @param {Object} params 参数
* @param {Object} config 配置
*/
_getSign(method, params, config) {
let signParams = Object.assign({
method,
app_id: config.appId,
charset: config.charset || "utf-8",
version: config.version || "1.0",
sign_type: config.signType || "RSA2",
}, params);
if (config.appCertSn && config.alipayRootCertSn) {
signParams = Object.assign({
app_cert_sn: config.appCertSn,
alipay_root_cert_sn: config.alipayRootCertSn,
}, signParams);
}
const bizContent = params.biz_content;
if (bizContent) {
signParams.biz_content = JSON.stringify(bizContent);
}
// 排序
const decamelizeParams = this._sortObj(signParams);
// 拼接url参数
let signStr = this._objectToUrl(decamelizeParams);
let keyType = config.keyType || 'PKCS8';
const privateKeyType = keyType === 'PKCS8' ? 'PRIVATE KEY' : 'RSA PRIVATE KEY'
let privateKey = this._formatKey(config.privateKey, privateKeyType);
// 计算签名
const sign = crypto.createSign(ALIPAY_ALGORITHM_MAPPING[signParams.sign_type])
.update(signStr, 'utf8').sign(privateKey, 'base64');
return Object.assign(decamelizeParams, { sign });
},
_formatKey(key, type) {
return `-----BEGIN ${type}-----\n${key}\n-----END ${type}-----`
},
_sortObj(params) {
let keysArr = Object.keys(params).sort();
let sortObj = {};
for (let i in keysArr) {
sortObj[keysArr[i]] = (params[keysArr[i]]);
}
return sortObj;
},
_objectToUrl(obj) {
let str = "";
for (let key in obj) {
if (obj[key]) {
str += `&${key}=${obj[key]}`;
}
}
if (str) str = str.substring(1);
return str;
},
_formatUrl(params, url = "https://openapi.alipay.com/gateway.do") {
let requestUrl = url;
// 需要放在 url 中的参数列表
const urlArgs = [
'app_id',
'method',
'format',
'charset',
'sign_type',
'sign',
'timestamp',
'version',
'notify_url',
'return_url',
'auth_token',
'app_auth_token'
]
for (const key in params) {
if (urlArgs.indexOf(key) > -1) {
const val = encodeURIComponent(params[key])
requestUrl = `${requestUrl}${requestUrl.includes('?') ? '&' : '?'
}${key}=${val}`
// 删除 postData 中对应的数据
delete params[key]
}
}
return { execParams: params, url: requestUrl }
}
};
module.exports = alipay;

View File

@@ -0,0 +1,293 @@
/**
* 通用公共函数
*/
var common = {};
/**
* 日期格式化
*/
common.timeFormat = function(time, fmt = 'yyyy-MM-dd hh:mm:ss', targetTimezone = 8) {
try {
if (!time) {
return "";
}
if (typeof time === "string" && !isNaN(time)) time = Number(time);
// 其他更多是格式化有如下:
// yyyy-MM-dd hh:mm:ss|yyyy年MM月dd日 hh时MM分等,可自定义组合
let date;
if (typeof time === "number") {
if (time.toString().length == 10) time *= 1000;
date = new Date(time);
} else {
date = time;
}
const dif = date.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
const east8time = date.getTime() + timeDif;
date = new Date(east8time);
let opt = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (let k in opt) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (opt[k]) : (("00" + opt[k]).substr(("" + opt[
k]).length)));
}
}
return fmt;
} catch (err) {
// 若格式错误,则原值显示
return time;
}
};
/**
* 产生订单号,不依赖数据库,高并发时性能高(理论上会重复,但概率非常非常低)
*/
common.createOrderNo = function(prefix = "", num = 25) {
// 获取当前时间字符串格式如20200803093000123
let timeStr = common.timeFormat(Date.now(), "yyyyMMddhhmmssS");
timeStr = timeStr.substring(2);
let randomNum = num - (prefix + timeStr).length;
return prefix + timeStr + common.random(randomNum, "123456789");
};
/**
* 产生随机数
*/
common.random = function(length, list = "123456789") {
let s = "";
for (let i = 0; i < length; i++) {
let code = list[Math.floor(Math.random() * list.length)];
s += code;
}
return s;
};
/**
* 休眠,等待(单位毫秒)
* @param {Number} ms 毫秒
* await common.sleep(1000);
*/
common.sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
/**
* 获取platform
* let provider_pay_type = common.getPlatform(platform);
*/
common.getPlatform = function(platform) {
if (["h5", "web"].indexOf(platform) > -1) {
platform = "web";
} else if (["app", "app-plus"].indexOf(platform) > -1) {
platform = "app";
}
return platform;
};
/**
* 获取 provider_pay_type
let provider_pay_type = common.getProviderPayType({
platform,
provider,
ua,
qr_code
});
*/
common.getProviderPayType = function(data) {
let {
platform,
provider,
ua,
qr_code
} = data;
// 扫码支付
if (qr_code) return "native";
// 小程序支付
if (platform.indexOf("mp") > -1) return "mp";
// APP支付
if (platform.indexOf("app") > -1) return "app";
// 微信公众号支付
if (platform === "web" && provider === "wxpay" && ua.toLowerCase().indexOf("micromessenger") > -1) return "jsapi";
// 微信外部浏览器支付
if (platform === "web" && provider === "wxpay" && ua.toLowerCase().indexOf("micromessenger") === -1) return "mweb";
if (platform === "web" && provider === "alipay") return "native";
throw new Error(`不支持的支付方式${provider}-${platform}`);
};
/**
* 获取uniPay交易类型
let tradeType = common.getTradeType({ provider, provider_pay_type });
*/
common.getTradeType = function(data) {
let { provider, provider_pay_type } = data;
let pay_type = `${provider}_${provider_pay_type}`;
let obj = {
// 微信
"wxpay_app": "APP", // 微信app支付
"wxpay_mp": "JSAPI", // 微信小程序支付
"wxpay_native": "NATIVE", // 微信扫码支付
"wxpay_mweb": "MWEB", // 微信外部浏览器支付
"wxpay_jsapi": "JSAPI", // 微信公众号支付
// 支付宝
"alipay_app": "APP", // 支付宝app支付
"alipay_mp": "JSAPI", // 支付宝小程序支付
"alipay_native": "NATIVE", // 支付宝扫码支付
"alipay_mweb": "NATIVE", // 支付宝外部浏览器支付
};
return obj[pay_type];
};
/**
* 给第三方服务器返回成功通知
*/
common.returnNotifySUCCESS = function(data) {
let { provider, provider_pay_type } = data;
if (provider === "wxpay") {
// 微信支付需返回 xml 格式的字符串
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/xml;charset=utf-8'
},
body: "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"
};
} else if (provider === "alipay") {
// 支付宝支付直接返回 success 字符串
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/plain'
},
body: "success"
}
}
return "success";
};
// 获取异步通知的参数,并转成json对象
common.getNotifyData = function(data) {
let {
provider,
httpInfo
} = data;
let json = {};
let body = httpInfo.body;
if (httpInfo.isBase64Encoded) {
body = Buffer.from(body, 'base64').toString('utf-8');
}
if (provider === "wxpay") {
if (body.indexOf("<xml>") > -1) {
// 微信支付v2
json = common.parseXML(body);
} else {
// 微信支付v3
json = common.urlStringToJson(body);
}
} else if (provider === "alipay") {
// 支付宝支付
json = common.urlStringToJson(body);
}
return json;
};
// 简易版XML转Object只可在微信支付时使用不支持嵌套
common.parseXML = function(xml) {
const xmlReg = /<(?:xml|root).*?>([\s|\S]*)<\/(?:xml|root)>/
const str = xmlReg.exec(xml)[1]
const obj = {}
const nodeReg = /<(.*?)>(?:<!\[CDATA\[){0,1}(.*?)(?:\]\]>){0,1}<\/.*?>/g
let matches = null
// eslint-disable-next-line no-cond-assign
while ((matches = nodeReg.exec(str))) {
obj[matches[1]] = matches[2]
}
return obj
};
// url参数转json
common.urlStringToJson = function(str) {
let json = {};
if (str != "" && str != undefined && str != null) {
let arr = str.split("&");
for (let i = 0; i < arr.length; i++) {
let arrstr = arr[i].split("=");
let k = arrstr[0];
let v = arrstr[1];
json[k] = v;
}
}
return json;
};
const isSnakeCase = new RegExp('_(\\w)', 'g');
const isCamelCase = new RegExp('[A-Z]', 'g');
function parseObjectKeys(obj, type) {
let parserReg;
let parser;
switch (type) {
case 'snake2camel':
parser = common.snake2camel
parserReg = isSnakeCase
break
case 'camel2snake':
parser = common.camel2snake
parserReg = isCamelCase
break
}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (parserReg.test(key)) {
const keyCopy = parser(key)
obj[keyCopy] = obj[key]
delete obj[key]
if (Object.prototype.toString.call((obj[keyCopy])) === '[object Object]') {
obj[keyCopy] = parseObjectKeys(obj[keyCopy], type)
} else if (Array.isArray(obj[keyCopy])) {
obj[keyCopy] = obj[keyCopy].map((item) => {
return parseObjectKeys(item, type)
})
}
}
}
}
return obj
}
common.snake2camel = function(value) {
return value.replace(isSnakeCase, (_, c) => (c ? c.toUpperCase() : ''))
}
common.camel2snake = function(value) {
return value.replace(isCamelCase, str => '_' + str.toLowerCase())
}
// 转驼峰
common.snake2camelJson = function(obj) {
return parseObjectKeys(obj, 'snake2camel');
};
// 转蛇形
common.camel2snakeJson = function(obj) {
return parseObjectKeys(obj, 'camel2snake');
};
module.exports = common;

View File

@@ -0,0 +1,82 @@
/**
* 加密模块
*/
/*
加密解密示例
const payCrypto = require('../libs/crypto.js'); // 获取加密服务(注意文件所在相对路径)
let ciphertext = { a:1,b:2 };
let encrypted = payCrypto.aes.encrypt({
data: ciphertext, // 待加密的原文
});
let decrypted = payCrypto.aes.decrypt({
data: encrypted, // 待解密的原文
});
// 最终解密得出 decrypted = { a:1,b:2 }
*/
const configCenter = require("uni-config-center");
const config = configCenter({ pluginId: 'uni-pay' }).requireFile('config.js');
const crypto = require("crypto");
var util = {};
util.aes = {};
/**
* aes加密
* @param {Object} data 待加密的原文
* @param {Object} key 密钥如不传自动取config
* 调用示例
let encrypted = crypto.aes.encrypt({
data: "", // 待加密的原文
});
*/
util.aes.encrypt = function(obj) {
let {
data, // 待加密的原文
key, // 密钥如不传自动取config
} = obj;
if (!key) key = config.notifyKey;
if (typeof data === "object") data = JSON.stringify(data);
const cipher = crypto.createCipher('aes192', key);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
// encrypted 为加密后的内容
return encrypted;
};
/**
* aes解密
* @param {Object} data 待解密的原文
* @param {Object} key 密钥如不传自动取config
* 调用示例
let decrypted = crypto.aes.decrypt({
data: "", // 待解密的原文
});
*/
util.aes.decrypt = function(obj) {
let {
data, // 待解密的原文
key, // 密钥如不传自动取config
} = obj;
if (typeof data === "undefined") {
throw "待解密原文不能为空";
}
if (!key) key = config.notifyKey;
// 解密
let decrypted;
try {
const decipher = crypto.createDecipher('aes192', key);
decrypted = decipher.update(data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
try {
decrypted = JSON.parse(decrypted);
} catch (err) {}
} catch (err) {
throw "解密失败";
}
// decrypted 为解密后的内容即最开始需要加密的原始数据文本data
return decrypted;
};
module.exports = util;

View File

@@ -0,0 +1,13 @@
const wxpay = require('./wxpay');
const alipay = require('./alipay');
const common = require('./common');
const qrcode = require('./qrcode'); // 此源码为npm i qrcode的压缩版本
const crypto = require('./crypto');
module.exports = {
wxpay,
alipay,
common,
qrcode,
crypto
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,82 @@
/**
* 微信支付相关函数
*/
var wxpay = {
async getOpenid(data) {
let {
provider_pay_type
} = data;
if (provider_pay_type === "jsapi") {
return this._getJsOpenid(data);
} else {
return this._getMpOpenid(data);
}
},
async _getMpOpenid(data) {
let {
config = {},
code,
provider_pay_type
} = data;
if (!config.appId) throw new Error("uni-pay配置wxpay.mp节点下的appId不能为空");
if (!config.secret) throw new Error("uni-pay配置wxpay.mp节点下的secret不能为空");
let res = await uniCloud.httpclient.request("https://api.weixin.qq.com/sns/jscode2session", {
method: 'GET',
data: {
appid: config.appId,
secret: config.secret,
js_code: code,
grant_type: "authorization_code"
},
contentType: 'json', // 指定以application/json发送data内的数据
dataType: 'json' // 指定返回值为json格式自动进行parse
});
if (res.data && !res.data.errcode && res.data.openid) {
return {
errCode: 0,
errMsg: 'ok',
openid: res.data.openid,
unionid: res.data.unionid,
}
} else {
return {
errCode: -1,
errMsg: res.data.errmsg
}
}
},
async _getJsOpenid(data) {
let {
config = {},
code,
provider_pay_type
} = data;
if (!config.appId) throw new Error("uni-pay配置wxpay.jsapi节点下的appId不能为空");
if (!config.secret) throw new Error("uni-pay配置wxpay.jsapi节点下的secret不能为空");
let res = await uniCloud.httpclient.request("https://api.weixin.qq.com/sns/oauth2/access_token", {
method: 'GET',
data: {
appid: config.appId,
secret: config.secret,
code: code,
grant_type: "authorization_code"
},
contentType: 'json', // 指定以application/json发送data内的数据
dataType: 'json' // 指定返回值为json格式自动进行parse
});
if (res.data && !res.data.errcode && res.data.openid) {
return {
errCode: 0,
errMsg: 'ok',
openid: res.data.openid,
unionid: res.data.unionid,
}
} else {
return {
errCode: -1,
errMsg: res.data.errmsg
}
}
}
};
module.exports = wxpay;

View File

@@ -0,0 +1,50 @@
/**
* 权限验证中间件,一般情况下,无需修改此处的代码
*/
const methodPermission = require('../config/permission');
const { ERROR } = require('../common/error');
function isAccessAllowed(user = {}, setting) {
const {
role: userRole = [],
permission: userPermission = []
} = user
const {
role: settingRole = [],
permission: settingPermission = []
} = setting
if (userRole.includes('admin')) {
return;
}
if (settingRole.length > 0 && settingRole.every(item => !userRole.includes(item))) {
throw { errCode: ERROR[50403] };
}
if (settingPermission.length > 0 && settingPermission.every(item => !userPermission.includes(item))) {
throw { errCode: ERROR[50403] };
}
}
module.exports = async function() {
const methodName = this.getMethodName();
if (!(methodName in methodPermission)) {
return;
}
const {
auth,
role,
permission
} = methodPermission[methodName];
if (auth || role || permission) {
await this.middleware.auth();
}
if (role && role.length === 0) {
throw new Error('[AccessControl]Empty role array is not supported');
}
if (permission && permission.length === 0) {
throw new Error('[AccessControl]Empty permission array is not supported');
}
return isAccessAllowed(this.authInfo, {
role,
permission
})
}

View File

@@ -0,0 +1,21 @@
module.exports = async function(key = true) {
if (this.authInfo) { // 多次执行auth时如果第一次成功后续不再执行
return;
}
const token = this.getUniIdToken();
const payload = await this.uniIdCommon.checkToken(token);
if (payload.errCode) {
if (key) {
throw payload;
} else {
return;
}
}
this.authInfo = payload;
if (payload.token && typeof this.response === "object") {
this.response.newToken = {
token: payload.token,
tokenExpired: payload.tokenExpired
}
}
}

View File

@@ -0,0 +1,7 @@
const accessControl = require("./access-control");
const auth = require("./auth");
module.exports = {
accessControl,
auth
}

View File

@@ -0,0 +1,78 @@
'use strict';
/**
* 此处建议只改下订单状态,保证能及时返回给第三方支付服务器成功状态
* 限制4秒内必须执行完全部的异步回调逻辑建议将消息发送、返佣、业绩结算等业务逻辑异步处理如用定时任务去处理这些异步逻辑
*/
const payCrypto = require('../libs/crypto.js'); // 获取加密服务
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
order_no,
out_trade_no,
total_fee
} = data; // uni-pay-orders 表内的数据均可获取到
// 此处写你自己的支付成功逻辑开始-----------------------------------------------------------
// 有三种方式
// 方式一:直接写数据库操作
// 方式二:使用 await uniCloud.callFunction 调用其他云函数或云对象,云对象则使用 uniCloud.importObject('云对象名称')来请求
// 方式三:使用 await uniCloud.httpclient.request 调用http接口地址
/*
// 方式二安全模式一(加密)
let encrypted = payCrypto.aes.encrypt({
data: data, // 待加密的原文
});
await uniCloud.callFunction({
name: "你的云函数名称",
data: {
encrypted, // 传输加密数据
},
});
*/
/*
// 方式二安全模式二(只传一个订单号 out_trade_no你自己的回调里查数据库表 uni-pay-orders 判断 status是否等于1来判断是否真的支付了
await uniCloud.callFunction({
name: "你的云函数名称",
data: {
out_trade_no, // 支付插件订单号
},
});
*/
/*
// 方式三安全模式一(加密)
let encrypted = payCrypto.aes.encrypt({
data: data, // 待加密的原文
});
await uniCloud.httpclient.request("你的服务器接口请求地址", {
method: "POST",
data: {
encrypted, // 传输加密数据
},
});
*/
/*
// 方式三安全模式二(只传一个订单号 out_trade_no你自己的回调里执行url请求来请求 uni-pay-co 云对象的 getOrder 接口来判断订单是否真的支付了)
await uniCloud.httpclient.request("你的服务器接口请求地址", {
method: "POST",
data: {
out_trade_no, // 支付插件订单号
},
});
*/
// 此处写你自己的支付成功逻辑结束-----------------------------------------------------------
// user_order_success = true 代表你自己的逻辑处理成功 返回 false 代表你自己的处理逻辑失败。
return user_order_success;
};

View File

@@ -0,0 +1,78 @@
'use strict';
/**
* 此处建议只改下订单状态,保证能及时返回给第三方支付服务器成功状态
* 限制4秒内必须执行完全部的异步回调逻辑建议将消息发送、返佣、业绩结算等业务逻辑异步处理如用定时任务去处理这些异步逻辑
*/
const payCrypto = require('../libs/crypto.js'); // 获取加密服务
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
order_no,
out_trade_no,
total_fee
} = data; // uni-pay-orders 表内的数据均可获取到
// 此处写你自己的支付成功逻辑开始-----------------------------------------------------------
// 有三种方式
// 方式一:直接写数据库操作
// 方式二:使用 await uniCloud.callFunction 调用其他云函数或云对象,云对象则使用 uniCloud.importObject('云对象名称')来请求
// 方式三:使用 await uniCloud.httpclient.request 调用http接口地址
/*
// 方式二安全模式一(加密)
let encrypted = payCrypto.aes.encrypt({
data: data, // 待加密的原文
});
await uniCloud.callFunction({
name: "你的云函数名称",
data: {
encrypted, // 传输加密数据
},
});
*/
/*
// 方式二安全模式二(只传一个订单号 out_trade_no你自己的回调里查数据库表 uni-pay-orders 判断 status是否等于1来判断是否真的支付了
await uniCloud.callFunction({
name: "你的云函数名称",
data: {
out_trade_no, // 支付插件订单号
},
});
*/
/*
// 方式三安全模式一(加密)
let encrypted = payCrypto.aes.encrypt({
data: data, // 待加密的原文
});
await uniCloud.httpclient.request("你的服务器接口请求地址", {
method: "POST",
data: {
encrypted, // 传输加密数据
},
});
*/
/*
// 方式三安全模式二(只传一个订单号 out_trade_no你自己的回调里执行url请求来请求 uni-pay-co 云对象的 getOrder 接口来判断订单是否真的支付了)
await uniCloud.httpclient.request("你的服务器接口请求地址", {
method: "POST",
data: {
out_trade_no, // 支付插件订单号
},
});
*/
// 此处写你自己的支付成功逻辑结束-----------------------------------------------------------
// user_order_success = true 代表你自己的逻辑处理成功 返回 false 代表你自己的处理逻辑失败。
return user_order_success;
};

View File

@@ -0,0 +1,51 @@
# 异步通知回调执行逻辑目录
**提示:异步通知写在 `uni-pay-co/notify` 目录下在此目录新建2个js文件分别为 `recharge.js`、`goods.js` 文件同时复制以下代码要你新建的2个js文件里。**
**注意**
为什么要你自己创建.js文件而不是插件默认给你创建好这是因为后面当插件更新时你写的代码会被插件更新的代码覆盖一键合并功能因此只要插件这里没有文件而是你自己新建的文件那么插件更新时不会覆盖你自己新建的文件内的代码。
```js
'use strict';
/**
* 此处建议只改下订单状态,保证能及时返回给第三方支付服务器成功状态
* 且where条件可以增加判断服务器推送过来的金额和订单表中订单需支付金额是否一致
* 将消息发送、返佣、业绩结算等业务逻辑异步处理(写入异步任务队列表)
* 如开启定时器每隔5秒触发一次处理订单
*/
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
order_no,
out_trade_no,
total_fee
} = data; // uni-pay-orders 表内的数据均可获取到
// 此处写你自己的支付成功逻辑开始-----------------------------------------------------------
// 有三种方式
// 方式一:直接写数据库操作
// 方式二:使用 await uniCloud.callFunction 调用其他云函数
// 方式三:使用 await uniCloud.httpclient.request 调用http接口地址
// 此处写你自己的支付成功逻辑结束-----------------------------------------------------------
// user_order_success = true 代表你自己的逻辑处理成功 返回 false 代表你自己的处理逻辑失败。
return user_order_success;
};
```
其中
- `recharge.js` 内可以写余额充值相关的回调逻辑
- `goods.js` 内可以写商品订单付款成功后的回调逻辑
最终调用哪个回调逻辑是根据你创建支付订单时,`type` 参数填的什么,`type` 如果填 `recharge` 则支付成功后就会执行 `recharge.js` 内的代码逻辑。
即前端调用支付时的这个 `type` 参数
```js
// 打开支付收银台
this.$refs.uniPay.open({
type: "recharge", // 支付回调类型 recharge 代表余额充值(当然你可以自己自定义)
});
```

View File

@@ -0,0 +1,17 @@
{
"name": "uni-pay-co",
"dependencies": {
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"uni-id-common": "file:../../../../uni-id-common/uniCloud/cloudfunctions/common/uni-id-common",
"uni-pay": "file:../common/uni-pay"
},
"extensions": {},
"cloudfunction-config": {
"concurrency": 1,
"memorySize": 128,
"path": "/uni-pay-co",
"timeout": 60,
"triggers": [],
"runtime": "Nodejs8"
}
}

View File

@@ -0,0 +1,5 @@
const pay = require("./pay");
module.exports = {
pay
};

View File

@@ -0,0 +1,865 @@
/**
* 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,
} = data;
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]; // 获取支付方式
let original_data = libs.common.getNotifyData({ httpInfo, provider }); // 获取原始回调数据
// 初始化uniPayInstance
let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
let notifyType = await uniPayInstance.checkNotifyType(httpInfo);
if (notifyType !== "payment") {
// 由于支付宝部分退款会触发支付成功的回调,但同时签名验证是算未通过的,为了避免支付宝重复推送,这里可以直接返回成功告知支付宝服务器,不用再推送过来了。
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)); // 这一句代码有用,请勿删除。
let {
outTradeNo,
totalFee,
transactionId,
resultCode, // 微信支付v2和支付宝支付判断成功的字段
openid,
appId,
tradeState, // 微信支付v3判断支付成功的字段
} = verifyResult;
//console.log('verifyResult: ', 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,
original_data,
}
});
//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('用户自己的回调逻辑 - 开始执行');
userOrderSuccess = await orderPaySuccess({
verifyResult,
data: payOrderInfo,
});
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 createOrder(data = {}) {
let {
provider, // 支付供应商
total_fee, // 支付金额
user_id, // 用户user_id统计需要
openid, // 用户openid
order_no, // 订单号
out_trade_no, // 支付插件订单号
description, // 订单描述
type, // 回调类型
qr_code, // 是否强制使用扫码支付
clientInfo, // 客户端信息
cloudInfo, // 云端信息
custom, // 自定义参数(不会发送给第三方支付服务器)
other, // 其他请求参数(会发送给第三方支付服务器)
} = 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 (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 currentNotifyUrl = notifyUrl[spaceId] || 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;
}
}
}
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的参数
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,
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,
});
}
// 自动删除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;
}
let queryRes = await uniPayInstance.orderQuery(orderQueryJson);
if (queryRes.tradeState === 'SUCCESS' || queryRes.tradeState === 'FINISHED') {
if (typeof payOrderInfo.user_order_success == "undefined" && await_notify) {
let whileTime = 0; // 当前循环已执行的时间(毫秒)
let whileInterval = 500; // 每次循环间隔时间(毫秒)
let maxTime = 5000; // 循环执行时间超过此值则退出循环(毫秒)
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);
console.log(`---- ${out_trade_no} -- ${outRefundNo} -- ${totalFee/100} -- ${refundFee/100}`)
// 退款操作
try {
res.result = await uniPayInstance.refund({
outTradeNo: out_trade_no,
outRefundNo,
totalFee,
refundFee,
refundDesc: refund_desc,
refundFeeType: refund_fee_type
});
} catch (err) {
console.error(err);
let errMsg = err.message;
if (errMsg && errMsg.indexOf("verify failure") > -1) {
throw { errCode: ERROR[53005] };
}
return { errCode: -1, errMsg: errMsg, err }
}
if (res.result.refundFee) {
res.errCode = 0;
res.errMsg = "ok";
// 修改数据库
try {
// 修改订单状态
payOrderInfo = await dao.uniPayOrders.updateAndReturn({
whereJson: {
_id: payOrderInfo._id
},
dataJson: {
status: 2,
refund_fee: _.inc(refundFee),
refund_count: refund_count,
// 记录每次的退款详情
refund_list: _.unshift({
refund_date: Date.now(),
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 {
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
};
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
});
if ((provider === "wxpay" && closeOrderRes.resultCode === "SUCCESS") || (provider === "alipay" && closeOrderRes.code ===
"10000")) {
// 修改订单状态为已取消
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 uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
if (provider === "wxpay") {
return await libs.wxpay.getOpenid({
config: uniPayConifg,
code,
provider_pay_type,
});
} 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 transaction_id;
let userOrderSuccess = false;
let pay_date;
if (verifyReceiptRes.tradeState !== "SUCCESS") {
return {
errCode: -1,
errMsg: "未支付"
};
}
// 支付成功
pay_date = verifyReceiptRes.receipt.receipt_creation_date_ms;
let inApp = verifyReceiptRes.receipt.in_app[0];
let quantity = inApp.quantity; // 购买数量
let product_id = inApp.product_id; // 对应的内购产品id
transaction_id = inApp.transaction_id; // 本次交易id
if (transaction_identifier !== transaction_id) {
// 校验不通过
return {
errCode: -1,
errMsg: "ios内购凭据校验不通过"
};
}
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;
try {
// 加载自定义异步回调函数
orderPaySuccess = require(`../notify/appleiap`);
} 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('用户自己的回调逻辑 - 执行完成');
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) {
uniPayInstance = uniPay.initWeixinV3(uniPayConifg);
} else {
uniPayInstance = uniPay.initWeixin(uniPayConifg);
}
} else if (provider === "alipay") {
// 支付宝
uniPayInstance = uniPay.initAlipay(uniPayConifg);
} else if (provider === "appleiap") {
// ios内购
uniPayInstance = uniPay.initAppleIapPayment(uniPayConifg);
} else {
throw new Error(`${pay_type} : 不支持的支付方式`);
}
return uniPayInstance;
}
}
module.exports = new service();

File diff suppressed because one or more lines are too long