oak-pay-business/lib/triggers/refund.js

519 lines
18 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 uuid_1 = require("oak-domain/lib/utils/uuid");
const payClazz_1 = require("../utils/payClazz");
const assert_1 = tslib_1.__importDefault(require("assert"));
const withdraw_1 = require("./withdraw");
const Exception_1 = require("../types/Exception");
/**
* 开始退款的逻辑
* @param context
* @param data
*/
async function startRefunding(context, data) {
(0, assert_1.default)(!data.pay && data.payId);
const [pay] = await context.select('pay', {
data: {
id: 1,
paid: 1,
refunded: 1,
iState: 1,
entity: 1,
entityId: 1,
refundable: 1,
deposit: {
id: 1,
accountId: 1,
},
order: {
id: 1,
settled: 1,
settlement$order: {
$entity: 'settlement',
data: {
id: 1,
accountId: 1,
price: 1,
iState: 1,
},
filter: {
iState: 'unsettled',
}
}
}
},
filter: {
id: data.payId,
}
}, { dontCollect: true });
const { paid, refunded, deposit, order } = pay;
if (paid - refunded < data.price) {
throw new Exception_1.RefundExceedMax();
}
if (!pay.refundable) {
throw new Exception_1.PayUnRefundable();
}
if (deposit) {
// 充值不可能从account渠道支付
(0, assert_1.default)(pay.entity !== 'account');
if (!data.withdrawId && !order?.id) {
//充值退款需创建关联的accountOper,若为提现则在withdraw中创建
data.accountOper$entity = [
{
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
accountId: deposit.accountId,
type: 'refund',
totalPlus: -data.price,
availPlus: -data.price,
refundablePlus: -data.price,
},
}
];
}
}
else {
(0, assert_1.default)(order);
//订单退款的钱要有明确的来源account
const { accountOper$entity: opers } = data;
(0, assert_1.default)(opers && opers instanceof Array, '订单退款一定要有相应的account来源');
if (order.settled) {
// 已经分账
let amount = 0;
opers.forEach(({ action, data }) => {
(0, assert_1.default)(action === 'create');
const { type, totalPlus, availPlus, refundablePlus } = data;
(0, assert_1.default)(type === 'refund');
(0, assert_1.default)(totalPlus === availPlus);
amount += -totalPlus;
});
(0, assert_1.default)(amount === data.price);
}
else {
//未完成分账的订单退款关联的AccountOper的totalPlus总和等于refund的price
//同时更新对应的settlement的price
let amount = 0;
const { settlement$order: settlements } = order;
for (const oper of opers) {
const { action, data } = oper;
(0, assert_1.default)(action === 'create');
const { type, totalPlus, accountId } = data;
(0, assert_1.default)(type === 'refund');
amount += -totalPlus;
const settlement = settlements?.find((ele) => ele.accountId === accountId);
(0, assert_1.default)(!!settlement, '请从订单分账的相关账户中选择订单退款的account来源');
const settlementNewPrice = settlement.price + totalPlus; //可能为负值
await context.operate('settlement', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'refund',
data: {
price: settlementNewPrice,
},
filter: {
id: settlement.id,
}
}, {});
}
(0, assert_1.default)(amount === data.price);
}
}
data.pay = {
id: (0, uuid_1.generateNewId)(),
action: 'startRefunding',
data: {},
};
}
/**
* 退款成功的逻辑
* @param context
* @param data
*/
async function succeedRefunding(context, refundId) {
const pays = await context.select('pay', {
data: {
id: 1,
price: 1,
refunded: 1,
entity: 1,
entityId: 1,
iState: 1,
paid: 1,
applicationId: 1,
application: {
systemId: 1,
},
deposit: {
id: 1,
accountId: 1,
},
refund$pay: {
$entity: 'refund',
data: {
id: 1,
price: 1,
iState: 1,
loss: 1,
withdrawId: 1,
},
filter: {
id: refundId,
},
}
},
filter: {
refund$pay: {
'#sqp': 'in',
id: refundId,
},
}
}, { forUpdate: true, dontCollect: true });
(0, assert_1.default)(pays.length === 1);
const [pay] = pays;
const { price, paid, refunded, refund$pay: refunds, application, applicationId, entity, entityId } = pay;
(0, assert_1.default)(refunds.length === 1);
const [refund] = refunds;
const { price: refundPrice, loss: refundLoss, withdrawId } = refund;
const refunded2 = refunded + refundPrice;
(0, assert_1.default)(refunded2 <= paid);
const action = refunded2 === paid ? 'refundAll' : 'refundPartially';
await context.operate('pay', {
id: await (0, uuid_1.generateNewIdAsync)(),
action,
data: {
refunded: refunded2,
refundable: refunded2 < paid,
},
filter: {
id: pay.id,
}
}, {});
let d = undefined; //退回至account的退款无渠道退款补偿
if (entity === 'account') {
//退回至account中的退款创建accountOper更新account余额
await context.operate('accountOper', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
accountId: pay.entityId,
entity: 'refund',
entityId: refund.id,
type: 'consumeBack',
totalPlus: refundPrice - refundLoss,
availPlus: refundPrice - refundLoss,
}
}, {});
}
else {
//订单退回至非account的退款会使得系统对应账户的资金发生流出
const payClazz = await (0, payClazz_1.getPayClazz)(entity, entityId, context);
// 实际执行的退款的额度应该是price - loss
const [delta, sysAccountEntity, sysAccountEntityId] = payClazz.calcRefundTax(refundPrice - refundLoss);
// sysAccount上减掉实际退款的额度-税费
await context.operate('sysAccountOper', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
delta: refundLoss - refundPrice - delta,
entity: sysAccountEntity,
entityId: sysAccountEntityId,
refundId: refund.id,
type: 'refund',
}
}, {});
d = delta;
}
let cnt = 2;
if (refundLoss || d) {
// 如果有退回或者要缴纳的税费进入system相关联的account账户
const [account] = await context.select('account', {
data: {
id: 1,
},
filter: {
entity: 'system',
entityId: application.systemId,
},
}, { dontCollect: true });
if (d) {
await context.operate('accountOper', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
accountId: account.id,
type: d < 0 ? 'taxRefund' : 'tax',
totalPlus: -d,
availPlus: -d,
entity: 'refund',
entityId: refund.id,
},
}, {});
}
cnt++;
if (refundLoss) {
(0, assert_1.default)(refundLoss > 0);
await context.operate('accountOper', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
accountId: account.id,
type: 'earn',
totalPlus: refundLoss,
availPlus: refundLoss,
entity: 'refund',
entityId: refund.id,
},
}, {});
}
}
// 如果有withdraw尝试去同步withdraw的状态
if (withdrawId) {
return cnt + await (0, withdraw_1.updateWithdrawState)(context, withdrawId);
}
return cnt;
}
async function failRefunding(context, refundId) {
const pays = await context.select('pay', {
data: {
id: 1,
price: 1,
refunded: 1,
entity: 1,
entityId: 1,
iState: 1,
paid: 1,
applicationId: 1,
application: {
systemId: 1,
},
deposit: {
id: 1,
accountId: 1,
},
refund$pay: {
$entity: 'refund',
data: {
id: 1,
price: 1,
loss: 1,
iState: 1,
withdrawId: 1,
},
filter: {
id: refundId,
},
},
order: {
id: 1,
settled: 1,
settlement$order: {
$entity: 'settlement',
data: {
id: 1,
accountId: 1,
price: 1,
iState: 1,
},
filter: {
iState: 'unsettled',
}
}
}
},
filter: {
refund$pay: {
'#sqp': 'in',
id: refundId,
},
}
}, { forUpdate: true, dontCollect: true });
(0, assert_1.default)(pays.length === 1);
const [pay] = pays;
const { refunded, refund$pay: refunds, order } = pay;
(0, assert_1.default)(refunds.length === 1);
const [refund] = refunds;
const { withdrawId } = refund;
const action = refunded === 0 ? 'stopRefunding' : 'refundPartially';
await context.operate('pay', {
id: await (0, uuid_1.generateNewIdAsync)(),
action,
data: {
refundable: true,
},
filter: {
id: pay.id,
}
}, {});
// 如果有withdraw尝试去同步withdraw的状态
if (withdrawId) {
return 1 + await (0, withdraw_1.updateWithdrawState)(context, withdrawId);
}
else {
(0, assert_1.default)(order);
// 说明是从order那条线产生的refund此时要把当时扣除掉的account部分返还
const accountOpers = await context.select('accountOper', {
data: {
id: 1,
totalPlus: 1,
availPlus: 1,
accountId: 1,
},
filter: {
type: 'refund',
entity: 'refund',
entityId: refundId,
},
}, { dontCollect: true });
if (order.settled) {
// 已经分账
let amount = 0;
for (const oper of accountOpers) {
const { totalPlus, availPlus, accountId } = oper;
(0, assert_1.default)(totalPlus < 0 && totalPlus === availPlus);
await context.operate('accountOper', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
totalPlus: -totalPlus,
availPlus: -availPlus,
type: 'refundFailure',
entity: 'refund',
entityId: refundId,
accountId,
}
}, {});
amount += -availPlus;
}
(0, assert_1.default)(amount === refund.price);
}
else {
//未完成分账的订单,需要修改settlement
let amount = 0;
const { settlement$order: settlements } = order;
for (const oper of accountOpers) {
const { totalPlus, availPlus, accountId } = oper;
amount += -totalPlus;
const settlement = settlements?.find((ele) => ele.accountId === accountId);
(0, assert_1.default)(!!settlement);
const settlementNewPrice = settlement.price - totalPlus;
await context.operate('settlement', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'refundFailure',
data: {
price: settlementNewPrice,
},
filter: {
id: settlement.id,
}
}, {});
}
(0, assert_1.default)(amount === refund.price);
}
return 1 + accountOpers.length;
}
}
const triggers = [
{
entity: 'refund',
action: 'create',
when: 'commit',
name: '当refund建立时尝试触发外部退款操作',
strict: 'makeSure',
fn: async ({ ids }, context) => {
(0, assert_1.default)(ids.length === 1);
const [refund] = await context.select('refund', {
data: {
id: 1,
price: 1,
loss: 1,
pay: {
id: 1,
price: 1,
iState: 1,
refunded: 1,
entity: 1,
entityId: 1,
applicationId: 1,
},
},
filter: {
id: ids[0],
},
}, {});
const { id, price, pay } = refund;
const { price: payPrice, refunded, entity, entityId, applicationId } = pay;
const payClazz = await (0, payClazz_1.getPayClazz)(entity, entityId, context);
// 若对应entity的账户里钱不够则不发起退款
const accountEntity = (0, payClazz_1.getAccountEntity)(entity);
if (entity !== 'account') {
const accountAmount = await payClazz.getAccountAmount(context);
if (accountAmount < price) {
return;
}
}
const data = await payClazz.refund(refund, context);
if (data) {
(0, assert_1.default)(data.externalId);
const closeFn = context.openRootMode();
await context.operate('refund', {
id: await (0, uuid_1.generateNewIdAsync)(),
data,
action: 'update',
filter: {
id,
}
}, { dontCollect: true });
closeFn();
}
return;
},
},
{
entity: 'refund',
action: 'succeed',
when: 'after',
asRoot: true,
name: '退款成功时更新对应的pay状态以及对应的withdraw状态',
fn: async ({ operation }, context) => {
const { filter } = operation;
const refundId = filter?.id;
(0, assert_1.default)(typeof refundId === 'string');
return succeedRefunding(context, refundId);
}
},
{
entity: 'refund',
action: 'fail',
when: 'after',
asRoot: true,
name: '退款失败时更新对应的pay状态以及对应的withdraw状态',
fn: async ({ operation }, context) => {
const { filter } = operation;
const refundId = filter?.id;
(0, assert_1.default)(typeof refundId === 'string');
return failRefunding(context, refundId);
}
},
{
entity: 'refund',
name: '当发起退款时置pay的状态并做相应检查',
action: 'create',
asRoot: true,
when: 'before',
fn: async ({ operation }, context) => {
const { data } = operation;
(0, assert_1.default)(!(data instanceof Array));
await startRefunding(context, data);
return 1;
}
},
];
exports.default = triggers;