oak-pay-business/es/triggers/pay.js

297 lines
11 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.

import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import assert from 'assert';
import { getPayClazz } from '../utils/payClazz';
import { fullPayProjection } from '../utils/pay';
import { PAY_CHANNEL_ACCOUNT_NAME, PAY_CHANNEL_OFFLINE_NAME } from '../types/PayConfig';
import { DATA_SUBSCRIBER_KEYS } from '../config/constants';
async function changeOrderStateByPay(filter, context, option) {
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;
}
}
const closeFn = context.openRootMode();
try {
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,
},
filter: {
id: orderId,
},
}, option);
}
}
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,
},
filter: {
id: orderId,
},
}, option);
}
}
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,
},
filter: {
id: orderId,
},
}, option);
}
}
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,
},
filter: {
id: orderId,
},
}, option);
}
}
else {
const iState = 'unpaid';
assert(orderRefunded === 0 && orderPaid === 0);
if (orderIState !== iState) {
await context.operate('order', {
id: await generateNewIdAsync(),
action: 'payNone',
data: {},
filter: {
id: orderId,
},
}, option);
}
}
closeFn();
return 1;
}
catch (err) {
closeFn();
throw err;
}
}
const triggers = [
{
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) {
// 使用帐户支付,直接成功并扣款
const close = context.openRootMode();
try {
await context.operate('pay', {
id: await generateNewIdAsync(),
action: 'succeedPaying',
data: {
accountOper$entity: [
{
id: await generateNewIdAsync(),
action: 'create',
data: {
id: await generateNewIdAsync(),
totalPlus: -price!,
availPlus: -price!,
type: 'consume',
accountId,
},
}
]
},
filter: {
id,
}
}, option);
close();
}
catch (err: any) {
close();
throw err;
}
return 1;
}
} */
// 其余情况都是进入paying流程
await context.operate('pay', {
id: await generateNewIdAsync(),
action: 'startPaying',
data: {},
filter: {
id,
}
}, option);
return 1;
},
},
{
name: '当pay的状态发生变化时修改相应的order的状态或者account的状态同时尝试向订阅者推送',
entity: 'pay',
action: ['startPaying', 'succeedPaying', 'startClosing', 'succeedClosing', 'startRefunding',
'refundAll', 'refundPartially'],
when: 'after',
fn: async ({ operation }, context, option) => {
const { filter, action, id } = operation;
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;
context.saveOperationToEvent(id, `${DATA_SUBSCRIBER_KEYS.payStateChanged}-${filter.id}`);
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,
accountId,
type: 'deposit',
entity: 'pay',
entityId: pay.id,
}
}, option);
return 1;
}
return 0;
}
},
},
{
name: '当pay变为paying状态时调用外部下单接口',
entity: 'pay',
action: ['startPaying'],
when: 'before',
priority: 99,
fn: async ({ operation }, context, option) => {
const { data, filter } = operation;
const pays = await context.select('pay', {
data: fullPayProjection,
filter,
}, {});
assert(pays.length === 1);
const [pay] = pays;
const { applicationId, channel, iState } = pay;
assert(iState === 'unpaid');
const payClazz = await getPayClazz(applicationId, channel, context);
await payClazz.prepay(pay, data, context);
return 1;
},
},
{
name: '当pay开始close时调用外部关闭订单接口',
entity: 'pay',
action: ['close'],
when: 'before',
priority: 99,
fn: async ({ operation }, context, option) => {
const { data, filter } = operation;
const pays = await context.select('pay', {
data: fullPayProjection,
filter,
}, {});
assert(pays.length === 1);
const [pay] = pays;
const { applicationId, channel, iState, externalId } = pay;
assert(iState === 'unpaid' || iState === 'paying');
if (iState === 'paying' && externalId && ![PAY_CHANNEL_ACCOUNT_NAME, PAY_CHANNEL_OFFLINE_NAME].includes(channel)) {
const payClazz = await getPayClazz(applicationId, channel, context);
await payClazz.close(pay);
return 1;
}
return 0;
},
},
];
export default triggers;