436 lines
14 KiB
JavaScript
436 lines
14 KiB
JavaScript
"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,
|
||
}
|
||
},
|
||
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);
|
||
if (order.settled) {
|
||
// 如果已经分账,退款的钱要有明确的来源account
|
||
const { accountOper$entity: opers } = data;
|
||
(0, assert_1.default)(opers && opers instanceof Array, '订单退款一定要有相应的account来源');
|
||
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);
|
||
}
|
||
}
|
||
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,
|
||
},
|
||
}
|
||
},
|
||
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 } = 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 {
|
||
// 说明是从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 });
|
||
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);
|
||
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;
|