239 lines
8.2 KiB
TypeScript
239 lines
8.2 KiB
TypeScript
import { CreateTriggerInTxn, Trigger, UpdateTriggerInTxn } from 'oak-domain/lib/types/Trigger';
|
||
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||
import { EntityDict } from '@project/oak-app-domain';
|
||
import BackendRuntimeContext from '@project/context/BackendRuntimeContext';
|
||
import assert from 'assert';
|
||
import { OperateOption } from 'oak-domain/lib/types';
|
||
|
||
async function changeOrderStateByPay(
|
||
filter: NonNullable<EntityDict['order']['Update']['filter']>,
|
||
context: BackendRuntimeContext,
|
||
option: OperateOption) {
|
||
const orders = await context.select('order', {
|
||
data: {
|
||
id: 1,
|
||
iState: 1,
|
||
price: 1,
|
||
paid: 1,
|
||
refunded: 1,
|
||
pay$order: {
|
||
$entity: 'pay',
|
||
data: {
|
||
id: 1,
|
||
price: 1,
|
||
paid: 1,
|
||
refunded: 1,
|
||
iState: 1,
|
||
},
|
||
},
|
||
},
|
||
filter,
|
||
}, { dontCollect: true });
|
||
assert(orders.length === 1);
|
||
const [{ id: orderId, pay$order: pays, price: orderPrice,
|
||
paid: orderPaid, refunded: orderRefunded, iState: orderIState }] = orders;
|
||
|
||
let hasPaying = false, hasRefunding = false;
|
||
let payPaid = 0, payRefunded = 0;
|
||
for (const pay of pays!) {
|
||
const { price, iState, paid, refunded } = pay;
|
||
switch (iState!) {
|
||
case 'paying': {
|
||
hasPaying = true;
|
||
break;
|
||
}
|
||
case 'refunding': {
|
||
hasRefunding = true;
|
||
break;
|
||
}
|
||
default: {
|
||
break;
|
||
}
|
||
}
|
||
if (paid) {
|
||
payPaid += paid;
|
||
}
|
||
if (refunded) {
|
||
payRefunded += refunded;
|
||
}
|
||
}
|
||
|
||
if (hasPaying) {
|
||
// 一定是在支付状态
|
||
assert(!hasRefunding && payRefunded === 0 && payPaid < orderPrice!);
|
||
if (orderIState !== 'paying' || orderPaid !== payPaid) {
|
||
await context.operate('order', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'startPaying',
|
||
data: {
|
||
paid: payPaid,
|
||
},
|
||
id: orderId!,
|
||
}, option);
|
||
return 1;
|
||
}
|
||
}
|
||
else if (hasRefunding) {
|
||
assert(!hasPaying && payPaid === orderPrice && payPaid === orderPaid && payRefunded < orderPrice);
|
||
if (orderIState !== 'refunding' || orderRefunded !== payRefunded) {
|
||
await context.operate('order', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'startRefunding',
|
||
data: {
|
||
refunded: payRefunded,
|
||
},
|
||
id: orderId!,
|
||
}, option);
|
||
return 1;
|
||
}
|
||
}
|
||
else if (payRefunded) {
|
||
// 已经在退款流程当中
|
||
assert(payPaid === orderPrice && payPaid === orderPaid);
|
||
const iState = payPaid === orderPrice ? 'refunded' : 'partiallyRefunded';
|
||
if (orderIState !== iState || orderRefunded !== payRefunded) {
|
||
const action = payPaid === orderPrice ? 'refundAll' : 'refundPartially';
|
||
await context.operate('order', {
|
||
id: await generateNewIdAsync(),
|
||
action,
|
||
data: {
|
||
refunded: payRefunded,
|
||
},
|
||
id: orderId!,
|
||
}, option);
|
||
return 1;
|
||
}
|
||
}
|
||
else if (payPaid) {
|
||
// 在支付流程当中
|
||
assert(orderRefunded === 0);
|
||
const iState = payPaid === orderPrice ? 'paid' : 'partiallyPaid';
|
||
if (orderIState !== iState || orderPaid !== payPaid) {
|
||
const action = payPaid === orderPrice ? 'payAll' : 'payPartially';
|
||
await context.operate('order', {
|
||
id: await generateNewIdAsync(),
|
||
action,
|
||
data: {
|
||
paid: payPaid,
|
||
},
|
||
id: orderId!,
|
||
}, option);
|
||
return 1;
|
||
}
|
||
}
|
||
else {
|
||
const iState = unpaid;
|
||
assert(orderRefunded === 0 && orderPaid === 0);
|
||
if (orderIState !== iState) {
|
||
await context.operate('order', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'payNone',
|
||
data: {
|
||
},
|
||
id: orderId!,
|
||
}, option);
|
||
return 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
const triggers: Trigger<EntityDict, 'pay', BackendRuntimeContext>[] = [
|
||
{
|
||
name: '当生成pay时,自动开始支付流程',
|
||
entity: 'pay',
|
||
action: 'create',
|
||
when: 'after',
|
||
fn: async ({ operation }, context, option) => {
|
||
const { data } = operation;
|
||
assert(!(data instanceof Array));
|
||
const { accountId, price, orderId, id } = data;
|
||
if (orderId) {
|
||
if (accountId) {
|
||
// 使用帐户支付,直接成功并扣款
|
||
await context.operate('pay', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'payAll',
|
||
data: {
|
||
accountOper$entity: [
|
||
{
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id: await generateNewIdAsync(),
|
||
totalPlus: -price!,
|
||
availPlus: -price!,
|
||
},
|
||
}
|
||
]
|
||
},
|
||
filter: {
|
||
id,
|
||
}
|
||
}, option);
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
// 其余情况都是进入paying流程
|
||
await context.operate('pay', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'startPaying',
|
||
data: {
|
||
},
|
||
filter: {
|
||
id,
|
||
}
|
||
}, option);
|
||
|
||
return 1;
|
||
},
|
||
} as CreateTriggerInTxn<EntityDict, 'pay', BackendRuntimeContext>,
|
||
{
|
||
name: '当pay的状态发生变化时,修改相应的order的状态或者account的状态',
|
||
entity: 'pay',
|
||
action: ['startPaying', 'succeedPaying', 'startClosing', 'succeedClosing', 'startRefunding',
|
||
'refundAll', 'refundPartially'],
|
||
when: 'after',
|
||
fn: async ({ operation }, context, option) => {
|
||
const { filter, action } = operation as EntityDict['pay']['Update'];
|
||
assert(typeof filter!.id === 'string');
|
||
|
||
const [pay] = await context.select('pay', {
|
||
data: {
|
||
id: 1,
|
||
orderId: 1,
|
||
accountId: 1,
|
||
price: 1,
|
||
},
|
||
filter,
|
||
}, { dontCollect: true });
|
||
const { orderId, accountId } = pay;
|
||
|
||
if (orderId) {
|
||
return await changeOrderStateByPay({ id: orderId }, context, option);
|
||
}
|
||
else {
|
||
assert(accountId);
|
||
// 如果是支付成功,则增加帐户余额,其它暂时不支持(提现暂时设计成不走退款)
|
||
if (action === 'succeedPaying') {
|
||
const payPrice = pay.price!;
|
||
await context.operate('accountOper', {
|
||
id: await generateNewIdAsync(),
|
||
action: 'create',
|
||
data: {
|
||
id: await generateNewIdAsync(),
|
||
totalPlus: payPrice,
|
||
availPlus: payPrice,
|
||
entity: 'pay',
|
||
entityId: pay.id!,
|
||
}
|
||
}, option);
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
},
|
||
} as UpdateTriggerInTxn<EntityDict, 'pay', BackendRuntimeContext>,
|
||
];
|
||
|
||
export default triggers; |