第一次提交
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
@@ -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;
|
||||
Reference in New Issue
Block a user