第一次提交
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "uni-pay",
|
||||
"version": "2.0.0",
|
||||
"description": "unipay for uniCloud",
|
||||
"main": "index.js",
|
||||
"homepage": "https://uniapp.dcloud.io/uniCloud/unipay",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://gitee.com/dcloud/uniPay.git"
|
||||
},
|
||||
"scripts": {},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// 各接口权限配置,未配置接口表示允许任何用户访问(包括未登录用户)
|
||||
module.exports = {
|
||||
refund: {
|
||||
// auth: true // 已登录用户方可操作,配置角色或权限时此项可不写
|
||||
role: ['admin'] // 允许进行此操作的角色,包含任一角色均可操作。
|
||||
// permission: [] // 允许进行此操作的权限,包含任一权限均可操作。
|
||||
// 权限角色均配置时,用户拥有任一权限或任一角色均可操作
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
const uniPayOrders = require("./uniPayOrders");
|
||||
const uniIdUsers = require("./uniIdUsers");
|
||||
module.exports = {
|
||||
uniPayOrders,
|
||||
uniIdUsers
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
dao名词解释:Data Access Object
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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:余额充值付款 vip:vip充值付款 等等,可自定义
|
||||
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
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const accessControl = require("./access-control");
|
||||
const auth = require("./auth");
|
||||
|
||||
module.exports = {
|
||||
accessControl,
|
||||
auth
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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 代表余额充值(当然你可以自己自定义)
|
||||
});
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const pay = require("./pay");
|
||||
|
||||
module.exports = {
|
||||
pay
|
||||
};
|
||||
@@ -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
38
uni_modules/uni-pay/uniCloud/database/db_init.json
Normal file
38
uni_modules/uni-pay/uniCloud/database/db_init.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"uni-id-users": {},
|
||||
"uni-pay-orders": {
|
||||
"data": [],
|
||||
"index": [{
|
||||
"IndexName": "order_no",
|
||||
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "order_no", "Direction": "1" }], "MgoIsUnique": false }
|
||||
},
|
||||
{
|
||||
"IndexName": "out_trade_no",
|
||||
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "out_trade_no", "Direction": "1" }], "MgoIsUnique": true }
|
||||
},
|
||||
{
|
||||
"IndexName": "transaction_id",
|
||||
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "transaction_id", "Direction": "1" }], "MgoIsUnique": false }
|
||||
},
|
||||
{
|
||||
"IndexName": "create_date",
|
||||
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "create_date", "Direction": "1" }], "MgoIsUnique": false }
|
||||
},
|
||||
{
|
||||
"IndexName": "pay_date",
|
||||
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "pay_date", "Direction": "1" }], "MgoIsUnique": false }
|
||||
},
|
||||
{
|
||||
"IndexName": "total_fee",
|
||||
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "total_fee", "Direction": "1" }], "MgoIsUnique": false }
|
||||
},
|
||||
{
|
||||
"IndexName": "user_id",
|
||||
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "user_id", "Direction": "1" }], "MgoIsUnique": false }
|
||||
},
|
||||
{
|
||||
"IndexName": "appid",
|
||||
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "appid", "Direction": "1" }], "MgoIsUnique": false }
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user