oak-pay-business/lib/utils/payClazz/WechatPay/WechatPay.js

380 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const fs_1 = tslib_1.__importDefault(require("fs"));
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
const wechat_pay_nodejs_1 = require("wechat-pay-nodejs");
const uuid_1 = require("oak-domain/lib/utils/uuid");
const Exception_1 = require("../../../types/Exception");
const assert_1 = tslib_1.__importDefault(require("assert"));
const lodash_1 = require("oak-domain/lib/utils/lodash");
const WechatPay_debug_1 = tslib_1.__importDefault(require("./WechatPay.debug"));
const TRADE_STATE_MATRIX = {
'SUCCESS': 'paid',
'USERPAYING': 'paying',
'NOTPAY': 'paying',
'CLOSED': 'closed',
'PAYERROR': 'closed',
'REVOKED': 'closed',
};
const REFUND_STATE_MATRIX = {
'SUCCESS': "successful",
'CLOSED': "failed",
"ABNORMAL": "refunding",
"PROCESSING": 'refunding',
};
class WechatPay extends WechatPay_debug_1.default {
wechatPay;
refundGapDays;
mchId;
publicKeyFilePath;
privateKeyFilePath;
apiV3Key;
payNotifyUrl;
refundNotifyUrl;
static MAX_REFUND_DAYS_GAP = 365;
static MIN_REFUND_DAYS_GAP = 7;
static DEFAULT_REFUND_DAYS_GAP = 300;
constructor(wpProduct, appId) {
super(wpProduct);
const { wpAccount } = wpProduct;
this.wpProduct = wpProduct;
// 这此不记也行,懒得改了
this.refundGapDays = wpAccount.refundGapDays;
this.mchId = wpAccount.mchId;
this.publicKeyFilePath = wpAccount.publicKeyFilePath;
this.privateKeyFilePath = wpAccount.privateKeyFilePath;
this.apiV3Key = wpAccount.apiV3Key;
this.payNotifyUrl = wpAccount.wechatPay.payNotifyUrl;
this.refundNotifyUrl = wpAccount.wechatPay.refundNotifyUrl;
this.wechatPay = new wechat_pay_nodejs_1.WechatPay({
appid: appId,
mchid: this.mchId,
cert_private_content: fs_1.default.readFileSync(this.privateKeyFilePath),
cert_public_content: fs_1.default.readFileSync(this.publicKeyFilePath),
});
}
async refund(refund, context) {
const { id, price, pay } = refund;
const serverUrl = context.composeAccessPath();
const endpoint = this.refundNotifyUrl;
const refundNotifyUrl = `${serverUrl}/endpoint/${endpoint}/${pay.id}`;
const result = await this.wechatPay.createRefund({
out_trade_no: (0, uuid_1.compressTo32)(pay.id),
out_refund_no: (0, uuid_1.compressTo32)(id),
notify_url: refundNotifyUrl,
amount: {
refund: price,
total: pay.price,
currency: 'CNY',
}
});
const { refund_id } = this.analyzeResult(result);
return {
externalId: refund_id,
};
}
async getRefundState(refund) {
const result = await this.wechatPay.queryRefundByOutRefundNo((0, uuid_1.compressTo32)(refund.id));
const { status, success_time } = this.analyzeResult(result);
/**
* 【退款状态】 退款到银行发现用户的卡作废或者冻结了导致原路退款银行卡失败可前往商户平台pay.weixin.qq.com-交易中心,手动处理此笔退款。
可选取值:
SUCCESS: 退款成功
CLOSED: 退款关闭
PROCESSING: 退款处理中
ABNORMAL: 退款异常
*/
const iState = REFUND_STATE_MATRIX[status];
return [iState, success_time ? { successAt: (0, dayjs_1.default)(success_time).millisecond() } : undefined];
}
analyzeResult(result) {
const { success, data, } = result;
if (!success) {
console.error(JSON.stringify(result));
throw new Exception_1.ExternalPayUtilException(result);
}
return data;
}
caclRefundDeadline(successTime) {
let gapDays = this.refundGapDays || WechatPay.DEFAULT_REFUND_DAYS_GAP;
if (gapDays > WechatPay.MAX_REFUND_DAYS_GAP) {
gapDays = WechatPay.MAX_REFUND_DAYS_GAP;
}
if (gapDays < WechatPay.MIN_REFUND_DAYS_GAP) {
gapDays = WechatPay.MIN_REFUND_DAYS_GAP;
}
return (0, dayjs_1.default)(successTime).add(gapDays, 'day').subtract(12, 'hour').valueOf();
}
async prepay(pay, data, context) {
const applicationId = context.getApplicationId();
const userId = context.getCurrentUserId();
const serverUrl = context.composeAccessPath();
const endpoint = this.payNotifyUrl;
const payNotifyUrl = `${serverUrl}/endpoint/${endpoint}/${pay.id}`;
switch (this.wpProduct.type) {
case 'native': {
const result = await this.wechatPay.prepayNative({
description: pay.order?.desc || pay.orderId ? '订单支付' : '帐户充值',
out_trade_no: (0, uuid_1.compressTo32)(pay.id),
notify_url: payNotifyUrl,
amount: {
total: pay.price,
},
});
const { code_url } = this.analyzeResult(result);
data.externalId = code_url;
data.meta = {
codeUrl: code_url,
};
break;
}
case 'jsapi':
case 'mp': {
const [wechatUser] = await context.select('wechatUser', {
data: {
id: 1,
openId: 1,
},
filter: {
applicationId,
userId,
}
}, { dontCollect: true });
(0, assert_1.default)(wechatUser, `user【${userId}】找不到相应的openId无法小程序支付`);
const result = await this.wechatPay.prepayJsapi({
description: pay.order?.desc || pay.orderId ? '订单支付' : '帐户充值',
out_trade_no: (0, uuid_1.compressTo32)(pay.id),
notify_url: payNotifyUrl,
amount: {
total: pay.price,
},
payer: {
openid: wechatUser.openId,
}
});
const prepayMeta = this.analyzeResult(result);
const prepayId = prepayMeta.package.slice(11); // `prepay_id=${prepay_id}`
data.externalId = prepayId;
data.meta = {
...pay.meta,
prepayMeta,
payId: userId,
payOpenId: wechatUser.openId,
};
break;
}
default: {
throw new Error('本支付方法还没来的及实现呢');
}
}
}
async getState(pay) {
const outTradeNo = (0, uuid_1.compressTo32)(pay.id);
const result = await this.wechatPay.queryOrderByOutTradeNo(outTradeNo);
// 返回结果中没有声明这个域
/**
* trade_state
必填
string(32)
【交易状态】 交易状态,枚举值:
* SUCCESS支付成功
* REFUND转入退款
* NOTPAY未支付
* CLOSED已关闭
* REVOKED已撤销仅付款码支付会返回
* USERPAYING用户支付中仅付款码支付会返回
* PAYERROR支付失败仅付款码支付会返回
*/
const { trade_state: tradeState, success_time } = this.analyzeResult(result);
const iState = TRADE_STATE_MATRIX[tradeState];
(0, assert_1.default)(iState);
const updateData = {
meta: {
getState: (0, lodash_1.omit)(result, ['mchid', 'appid', 'out_trade_no']),
}
};
if (iState === 'paid') {
updateData.forbidRefundAt = (0, dayjs_1.default)(success_time).millisecond();
}
return [iState, updateData];
}
async close(pay) {
const outTradeNo = (0, uuid_1.compressTo32)(pay.id);
const result = await this.wechatPay.closeOrder(outTradeNo);
this.analyzeResult(result);
}
async decodePayNotification(params, body) {
const { resource } = body;
if (process.env.NODE_ENV !== 'production') {
console.log('decodePayNotification-resource', JSON.stringify(resource));
}
const { ciphertext, nonce, associated_data } = resource;
const result2 = this.wechatPay.decryptAesGcm({
ciphertext,
nonce,
apiV3Key: this.apiV3Key,
associatedData: associated_data,
});
const result = JSON.parse(result2);
if (process.env.NODE_ENV !== 'production') {
console.log('decodePayNotification-decrypt', JSON.stringify(result));
}
/**
* {
"transaction_id":"1217752501201407033233368018",
"amount":{
"payer_total":100,
"total":100,
"currency":"CNY",
"payer_currency":"CNY"
},
"mchid":"1230000109",
"trade_state":"SUCCESS",
"bank_type":"CMC",
"promotion_detail":[
{
"amount":100,
"wechatpay_contribute":0,
"coupon_id":"109519",
"scope":"GLOBAL",
"merchant_contribute":0,
"name":"单品惠-6",
"other_contribute":0,
"currency":"CNY",
"stock_id":"931386",
"goods_detail":[
{
"goods_remark":"商品备注信息",
"quantity":1,
"discount_amount":1,
"goods_id":"M1006",
"unit_price":100
},
{
"goods_remark":"商品备注信息",
"quantity":1,
"discount_amount":1,
"goods_id":"M1006",
"unit_price":100
}
]
},
{
"amount":100,
"wechatpay_contribute":0,
"coupon_id":"109519",
"scope":"GLOBAL",
"merchant_contribute":0,
"name":"单品惠-6",
"other_contribute":0,
"currency":"CNY",
"stock_id":"931386",
"goods_detail":[
{
"goods_remark":"商品备注信息",
"quantity":1,
"discount_amount":1,
"goods_id":"M1006",
"unit_price":100
},
{
"goods_remark":"商品备注信息",
"quantity":1,
"discount_amount":1,
"goods_id":"M1006",
"unit_price":100
}
]
}
],
"success_time":"2018-06-08T10:34:56+08:00",
"payer":{
"openid":"oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
},
"out_trade_no":"1217752501201407033233368018",
"AppID":"wxd678efh567hg6787",
"trade_state_desc":"支付成功",
"trade_type":"MICROPAY",
"attach":"自定义数据",
"scene_info":{
"device_id":"013467007045764"
}
}
*/
const { out_trade_no, mchid, trade_state, success_time, } = result;
const payId = (0, uuid_1.decompressFrom32)(out_trade_no);
(0, assert_1.default)(mchid === this.mchId);
const iState = TRADE_STATE_MATRIX[trade_state];
(0, assert_1.default)(iState);
const extra = {
meta: (0, lodash_1.omit)(result, ['mchid',]),
};
if (iState === 'paid') {
extra.successAt = success_time;
}
return {
payId,
iState,
extra,
};
}
async decodeRefundNotification(params, body) {
const { resource } = body;
if (process.env.NODE_ENV !== 'production') {
console.log('decodeRefundNotification-resource', JSON.stringify(resource));
}
const { ciphertext, nonce, associated_data } = resource;
const result2 = this.wechatPay.decryptAesGcm({
ciphertext,
nonce,
apiV3Key: this.apiV3Key,
associatedData: associated_data,
});
const result = JSON.parse(result2);
if (process.env.NODE_ENV !== 'production') {
console.log('decodeRefundNotification-decrypt', JSON.stringify(result));
}
/**
* 对resource对象进行解密后得到的资源对象示例
* https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/refund-result-notice.html
* {
"mchid": "1900000100",
"transaction_id": "1008450740201411110005820873",
"out_trade_no": "20150806125346",
"refund_id": "50200207182018070300011301001",
"out_refund_no": "7752501201407033233368018",
"refund_status": "SUCCESS",
"success_time": "2018-06-08T10:34:56+08:00",
"user_received_account": "招商银行信用卡0403",
"amount" : {
"total": 999,
"refund": 999,
"payer_total": 999,
"payer_refund": 999
}
}
*/
const { out_refund_no, mchid, refund_status, success_time, } = result;
const refundId = (0, uuid_1.decompressFrom32)(out_refund_no);
(0, assert_1.default)(mchid === this.mchId);
const iState = REFUND_STATE_MATRIX[refund_status];
(0, assert_1.default)(iState);
const extra = {
meta: (0, lodash_1.omit)(result, ['mchid',]),
};
if (iState === 'successful') {
extra.successAt = success_time;
}
return {
refundId,
iState,
extra,
};
}
getRefundableAt(successAt) {
return this.caclRefundDeadline(successAt);
}
}
exports.default = WechatPay;