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

521 lines
19 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.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const uuid_1 = require("oak-domain/lib/utils/uuid");
const assert_1 = tslib_1.__importDefault(require("assert"));
const payClazz_1 = require("../utils/payClazz");
const pay_1 = require("../utils/pay");
const constants_1 = require("../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 });
(0, assert_1.default)(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 && iState === 'paid') {
//已支付完成的订单的paid
payPaid += paid;
}
if (refunded) {
payRefunded += refunded;
}
}
const closeFn = context.openRootMode();
try {
if (hasPaying) {
// 一定是在支付状态
(0, assert_1.default)(!hasRefunding && payRefunded === 0);
if (orderIState !== 'paying') {
//若订单未开始支付
await context.operate('order', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'startPaying',
data: {
paid: payPaid,
},
filter: {
id: orderId,
},
}, option);
}
}
else if (hasRefunding) {
(0, assert_1.default)(!hasPaying && payPaid === orderPrice && payPaid === orderPaid && payRefunded < orderPrice);
if (orderIState !== 'refunding' || orderRefunded !== payRefunded) {
await context.operate('order', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'startRefunding',
data: {
refunded: payRefunded,
},
filter: {
id: orderId,
},
}, option);
}
}
else if (payRefunded) {
// 已经在退款流程当中
(0, assert_1.default)(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 (0, uuid_1.generateNewIdAsync)(),
action,
data: {
refunded: payRefunded,
},
filter: {
id: orderId,
},
}, option);
}
}
else if (payPaid) {
// 在支付流程当中
//存在已完成的pay更新order的paid和iState
(0, assert_1.default)(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 (0, uuid_1.generateNewIdAsync)(),
action,
data: {
paid: payPaid,
},
filter: {
id: orderId,
},
}, option);
}
}
else {
const iState = 'unpaid';
(0, assert_1.default)(orderRefunded === 0 && orderPaid === 0);
if (orderIState !== iState) {
await context.operate('order', {
id: await (0, uuid_1.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',
asRoot: true,
fn: async ({ operation }, context, option) => {
const { data } = operation;
(0, assert_1.default)(!(data instanceof Array));
const { id } = data;
await context.operate('pay', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'startPaying',
data: {},
filter: {
id,
}
}, option);
return 1;
},
},
{
name: '当pay的状态发生变化时修改相应的order的状态或者deposit的状态同时尝试向订阅者推送',
entity: 'pay',
action: ['startPaying', 'succeedPaying', 'continuePaying', 'startClosing', 'succeedClosing', 'startRefunding',
'refundAll', 'refundPartially'],
when: 'after',
asRoot: true,
fn: async ({ operation }, context, option) => {
const { data, filter, action, id } = operation;
(0, assert_1.default)(typeof filter.id === 'string');
const [pay] = await context.select('pay', {
data: {
id: 1,
orderId: 1,
price: 1,
refundable: 1,
depositId: 1,
deposit: {
id: 1,
accountId: 1,
price: 1,
loss: 1,
},
application: {
systemId: 1,
},
iState: 1,
},
filter,
}, { dontCollect: true });
const { orderId, depositId, iState, deposit, application } = pay;
context.saveOperationToEvent(id, `${constants_1.DATA_SUBSCRIBER_KEYS.payStateChanged}-${filter.id}`);
if (orderId) {
return await changeOrderStateByPay({ id: orderId }, context, option);
}
else {
(0, assert_1.default)(depositId && deposit);
// 如果是支付成功则使deposit成功生成对应的accountOper这里应该只有这条路径会让deposit成功
if (action === 'succeedPaying') {
const payPrice = pay.price;
const { price, loss } = deposit;
(0, assert_1.default)(price === payPrice);
const accountOpers = [
{
id: await (0, uuid_1.generateNewIdAsync)(),
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
totalPlus: price - loss,
availPlus: price - loss,
refundablePlus: pay.refundable ? price : 0,
accountId: deposit.accountId,
type: 'deposit',
},
action: 'create',
}
];
if (loss > 0) {
// 如果有loss就充入system的account账户
const [account] = await context.select('account', {
data: {
id: 1,
},
filter: {
entity: 'system',
entityId: application?.systemId,
},
}, { dontCollect: true });
accountOpers.push({
id: await (0, uuid_1.generateNewIdAsync)(),
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
totalPlus: loss,
availPlus: loss,
accountId: account.id,
type: 'earn',
},
action: 'create',
});
}
await context.operate('deposit', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'succeed',
data: {
accountOper$entity: accountOpers,
},
filter: {
id: depositId,
},
}, {});
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: pay_1.fullPayProjection,
filter,
}, {});
(0, assert_1.default)(pays.length === 1);
const [pay] = pays;
const { applicationId, entity, entityId, iState } = pay;
(0, assert_1.default)(iState === 'unpaid');
const payClazz = await (0, payClazz_1.getPayClazz)(applicationId, entity, entityId, context);
await payClazz.prepay(pay, data, context);
return 1;
},
},
{
name: '当pay开始close时调用外部关闭订单接口并中止充值',
entity: 'pay',
action: 'close',
when: 'before',
priority: 99,
asRoot: true,
fn: async ({ operation }, context, option) => {
const { data, filter } = operation;
const pays = await context.select('pay', {
data: pay_1.fullPayProjection,
filter,
}, {});
(0, assert_1.default)(pays.length === 1);
const [pay] = pays;
const { applicationId, entity, entityId, iState, depositId } = pay;
(0, assert_1.default)(iState === 'unpaid' || iState === 'paying');
let cnt = 0;
if (iState === 'paying') {
const payClazz = await (0, payClazz_1.getPayClazz)(applicationId, entity, entityId, context);
await payClazz.close(pay);
cnt++;
}
if (depositId) {
await context.operate('deposit', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'fail',
data: {},
filter: {
id: depositId,
},
}, {});
cnt++;
}
return cnt;
},
},
{
name: '当充值类的pay变成unrefundable时计算并扣除account上的refundable',
entity: 'pay',
action: 'closeRefund',
filter: {
orderId: {
$exists: false,
},
},
when: 'before',
fn: async ({ operation }, context, option) => {
const { filter, action, id } = operation;
const pays = await context.select('pay', {
data: {
id: 1,
orderId: 1,
price: 1,
refunded: 1,
deposit: {
id: 1,
accountId: 1,
}
},
filter,
}, { dontCollect: true });
let count = 0;
for (const pay of pays) {
const { orderId, deposit, price, refunded } = pay;
(0, assert_1.default)(!orderId);
if (price - refunded > 0) {
// 减少account上可以refund的部分
await context.operate('accountOper', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
type: 'cutoffRefundable',
availPlus: 0,
totalPlus: 0,
refundablePlus: refunded - price,
accountId: deposit.accountId,
entity: 'pay',
entityId: pay.id,
},
}, {});
count++;
}
}
return count;
},
},
{
name: '当pay完成支付时计算相应的account以及syste account中的余额变化',
entity: 'pay',
action: 'succeedPaying',
when: 'after',
asRoot: true,
fn: async ({ operation }, context) => {
const { data, filter } = operation;
const projection = {
id: 1,
paid: 1,
price: 1,
entity: 1,
entityId: 1,
iState: 1,
depositId: 1,
orderId: 1,
application: {
systemId: 1,
}
};
const pays = await context.select('pay', {
data: projection,
filter,
}, {});
(0, assert_1.default)(pays.length === 1);
const [pay] = pays;
const { price, paid, entity, entityId, iState, applicationId, application } = pay;
(0, assert_1.default)(iState === 'paid' && paid === price);
const clazz = await (0, payClazz_1.getPayClazz)(applicationId, entity, entityId, context);
const [tax, sysAccountEntity, sysAccountEntityId] = clazz.calcPayTax(paid);
await context.operate('sysAccountOper', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
delta: paid - tax,
entity: sysAccountEntity,
entityId: sysAccountEntityId,
payId: pay.id,
type: 'pay',
}
}, {});
if (tax !== 0) {
// tax产生的损失由sys account来承担
const [account] = await context.select('account', {
data: {
id: 1,
},
filter: {
entity: 'system',
entityId: application.systemId,
}
}, { dontCollect: true });
await context.operate('accountOper', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
accountId: account.id,
type: 'tax',
totalPlus: -tax,
availPlus: -tax,
entity: 'pay',
entityId: pay.id,
},
}, {});
return 1;
}
return 0;
},
},
{
name: '当account类型的pay的paid达到price改为支付成功',
entity: 'pay',
asRoot: true,
action: ['startPaying', 'continuePaying'],
check(operation) {
return (!!operation.data.paid) && operation.data.paid > 0;
},
filter: {
entity: 'account',
},
when: 'commit',
fn: async ({ ids }, context) => {
for (const id of ids) {
const [row] = await context.select('pay', {
data: {
id: 1,
orderId: 1,
entity: 1,
price: 1,
paid: 1,
},
filter: {
id,
}
}, { dontCollect: true });
(0, assert_1.default)(row && row.entity === 'account');
if (row.paid && row.paid === row.price) {
const closeFn = context.openRootMode();
await context.operate('pay', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'succeedPaying',
data: {
successAt: Date.now(),
},
filter: {
id,
}
}, {});
closeFn();
}
}
}
},
{
name: '当pay完成支付时计算其refundable和forbidRefundAt',
entity: 'pay',
action: 'succeedPaying',
when: 'before',
fn: async ({ operation }, context) => {
const { data, filter } = operation;
(0, assert_1.default)(data.successAt);
const pays = await context.select('pay', {
data: {
id: 1,
entity: 1,
entityId: 1,
applicationId: 1,
},
filter
}, { dontCollect: true, forUpdate: true });
(0, assert_1.default)(pays.length === 1);
const [pay] = pays;
const { applicationId, entity, entityId } = pay;
const payClazz = await (0, payClazz_1.getPayClazz)(applicationId, entity, entityId, context);
const forbidRefundAt = payClazz.getRefundableAt(data.successAt);
data.forbidRefundAt = forbidRefundAt;
data.refundable = forbidRefundAt > Date.now();
return 1;
},
},
];
exports.default = triggers;