384 lines
12 KiB
JavaScript
384 lines
12 KiB
JavaScript
import { generateNewId, generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||
import { getAccountEntity, getPayClazz } from '../utils/payClazz';
|
||
import assert from 'assert';
|
||
import { updateWithdrawState } from './withdraw';
|
||
/**
|
||
* 开始退款的逻辑
|
||
* @param context
|
||
* @param data
|
||
*/
|
||
async function startRefunding(context, data) {
|
||
assert(!data.pay && data.payId);
|
||
const [pay] = await context.select('pay', {
|
||
data: {
|
||
id: 1,
|
||
paid: 1,
|
||
refunded: 1,
|
||
iState: 1,
|
||
deposit: {
|
||
id: 1,
|
||
accountId: 1,
|
||
},
|
||
order: {
|
||
id: 1,
|
||
settled: 1,
|
||
}
|
||
},
|
||
filter: {
|
||
id: data.payId,
|
||
}
|
||
}, { dontCollect: true });
|
||
const { paid, refunded, deposit, order } = pay;
|
||
assert(paid - refunded >= data.price);
|
||
if (deposit) {
|
||
// 是提现,其Account相应的计算在withdraw进行
|
||
assert(data.withdrawId);
|
||
}
|
||
else {
|
||
assert(order);
|
||
if (order.settled) {
|
||
// 如果已经分账,退款的钱要有明确的来源account
|
||
const { accountOper$entity: opers } = data;
|
||
assert(opers && opers instanceof Array, '订单退款一定要有相应的account来源');
|
||
let amount = 0;
|
||
opers.forEach(({ action, data }) => {
|
||
assert(action === 'create');
|
||
const { type, totalPlus, availPlus, refundablePlus } = data;
|
||
assert(type === 'refund');
|
||
assert(totalPlus === availPlus);
|
||
amount += totalPlus;
|
||
});
|
||
assert(amount === data.price);
|
||
}
|
||
}
|
||
data.pay = {
|
||
id: 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 });
|
||
assert(pays.length === 1);
|
||
const [pay] = pays;
|
||
const { price, paid, refunded, refund$pay: refunds, application, applicationId, entity, entityId } = pay;
|
||
assert(refunds.length === 1);
|
||
const [refund] = refunds;
|
||
const { price: refundPrice, loss: refundLoss, withdrawId } = refund;
|
||
const refunded2 = refunded + refundPrice;
|
||
assert(refunded2 <= paid);
|
||
const action = refunded2 === paid ? 'refundAll' : 'refundPartially';
|
||
await context.operate('pay', {
|
||
id: await generateNewIdAsync(),
|
||
action,
|
||
data: {
|
||
refunded: refunded2,
|
||
refundable: refunded2 < paid,
|
||
},
|
||
filter: {
|
||
id: pay.id,
|
||
}
|
||
}, {});
|
||
const payClazz = await getPayClazz(entity, entityId, context);
|
||
// 实际执行的退款的额度应该是price - loss
|
||
const [delta, sysAccountEntity, sysAccountEntityId] = payClazz.calcRefundTax(refundPrice - refundLoss);
|
||
// sysAccount上减掉实际退款的额度-税费
|
||
await context.operate('sysAccountOper', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id: await generateNewIdAsync(),
|
||
delta: refundLoss - refundPrice - delta,
|
||
entity: sysAccountEntity,
|
||
entityId: sysAccountEntityId,
|
||
refundId: refund.id,
|
||
type: 'refund',
|
||
}
|
||
}, {});
|
||
let cnt = 2;
|
||
if (refundLoss || delta) {
|
||
// 如果有退回或者要缴纳的税费,进入system相关联的account账户
|
||
const [account] = await context.select('account', {
|
||
data: {
|
||
id: 1,
|
||
},
|
||
filter: {
|
||
entity: 'system',
|
||
entityId: application.systemId,
|
||
},
|
||
}, { dontCollect: true });
|
||
if (delta) {
|
||
await context.operate('accountOper', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id: await generateNewIdAsync(),
|
||
accountId: account.id,
|
||
type: delta < 0 ? 'taxRefund' : 'tax',
|
||
totalPlus: -delta,
|
||
availPlus: -delta,
|
||
entity: 'refund',
|
||
entityId: refund.id,
|
||
},
|
||
}, {});
|
||
}
|
||
cnt++;
|
||
if (refundLoss) {
|
||
assert(refundLoss > 0);
|
||
await context.operate('accountOper', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id: await generateNewIdAsync(),
|
||
accountId: account.id,
|
||
type: 'earn',
|
||
totalPlus: refundLoss,
|
||
availPlus: refundLoss,
|
||
entity: 'refund',
|
||
entityId: refund.id,
|
||
},
|
||
}, {});
|
||
}
|
||
}
|
||
// 如果有withdraw,尝试去同步withdraw的状态
|
||
if (withdrawId) {
|
||
return cnt + await 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 });
|
||
assert(pays.length === 1);
|
||
const [pay] = pays;
|
||
const { refunded, refund$pay: refunds } = pay;
|
||
assert(refunds.length === 1);
|
||
const [refund] = refunds;
|
||
const { withdrawId } = refund;
|
||
const action = refunded === 0 ? 'stopRefunding' : 'refundPartially';
|
||
await context.operate('pay', {
|
||
id: await generateNewIdAsync(),
|
||
action,
|
||
data: {},
|
||
filter: {
|
||
id: pay.id,
|
||
}
|
||
}, {});
|
||
// 如果有withdraw,尝试去同步withdraw的状态
|
||
if (withdrawId) {
|
||
return 1 + await 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;
|
||
assert(totalPlus < 0 && totalPlus === availPlus);
|
||
await context.operate('accountOper', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id: await generateNewIdAsync(),
|
||
totalPlus: -totalPlus,
|
||
availPlus: -availPlus,
|
||
type: 'refundFailure',
|
||
entity: 'refund',
|
||
entityId: refundId,
|
||
accountId,
|
||
}
|
||
}, {});
|
||
amount += -availPlus;
|
||
}
|
||
assert(amount === refund.price);
|
||
return 1 + accountOpers.length;
|
||
}
|
||
}
|
||
const triggers = [
|
||
{
|
||
entity: 'refund',
|
||
action: 'create',
|
||
when: 'commit',
|
||
name: '当refund建立时,尝试触发外部退款操作',
|
||
strict: 'makeSure',
|
||
fn: async ({ ids }, context) => {
|
||
assert(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 getPayClazz(entity, entityId, context);
|
||
// 若对应entity的账户里钱不够,则不发起退款
|
||
const accountEntity = getAccountEntity(entity);
|
||
if (entity !== 'account') {
|
||
const accountAmount = await payClazz.getAccountAmount(context);
|
||
if (accountAmount < price) {
|
||
return;
|
||
}
|
||
}
|
||
const data = await payClazz.refund(refund, context);
|
||
if (data) {
|
||
assert(data.externalId);
|
||
const closeFn = context.openRootMode();
|
||
await context.operate('refund', {
|
||
id: await 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;
|
||
assert(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;
|
||
assert(typeof refundId === 'string');
|
||
return failRefunding(context, refundId);
|
||
}
|
||
},
|
||
{
|
||
entity: 'refund',
|
||
name: '当发起退款时,置pay的状态并做相应检查',
|
||
action: 'create',
|
||
asRoot: true,
|
||
when: 'before',
|
||
fn: async ({ operation }, context) => {
|
||
const { data } = operation;
|
||
assert(!(data instanceof Array));
|
||
await startRefunding(context, data);
|
||
return 1;
|
||
}
|
||
},
|
||
];
|
||
export default triggers;
|