oak-pay-business/src/triggers/pay.ts

239 lines
8.2 KiB
TypeScript
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.

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;