支付的部分逻辑代码
This commit is contained in:
parent
855a0467c2
commit
9734af84e0
|
|
@ -0,0 +1,8 @@
|
|||
import { Checker } from 'oak-domain/lib/types/Auth';
|
||||
import { EntityDict } from '../oak-app-domain';
|
||||
import { RuntimeCxt } from '../types/RuntimeCxt';
|
||||
|
||||
const checkers: Checker<EntityDict, 'account', RuntimeCxt>[] = [
|
||||
];
|
||||
|
||||
export default checkers;
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import assert from 'assert';
|
||||
import { Checker } from 'oak-domain/lib/types/Auth';
|
||||
import { EntityDict } from '../oak-app-domain';
|
||||
import { RuntimeCxt } from '../types/RuntimeCxt';
|
||||
import { OakInputIllegalException } from 'oak-domain/lib/types';
|
||||
|
||||
const checkers: Checker<EntityDict, 'accountOper', RuntimeCxt>[] = [
|
||||
{
|
||||
entity: 'accountOper',
|
||||
action: 'create',
|
||||
type: 'row',
|
||||
filter: {
|
||||
account: {
|
||||
ableState: 'enabled',
|
||||
},
|
||||
},
|
||||
errMsg: 'account.update.accountDisabled',
|
||||
},
|
||||
{
|
||||
entity: 'accountOper',
|
||||
action: 'create',
|
||||
type: 'data',
|
||||
checker(data, context) {
|
||||
assert(!(data instanceof Array));
|
||||
const { type, totalPlus, availPlus } = data;
|
||||
if (typeof totalPlus !== 'number') {
|
||||
throw new OakInputIllegalException('accountOper', ['totalPlus'], 'accountOper中的totalPlus不是数字');
|
||||
}
|
||||
if (typeof availPlus !== 'number') {
|
||||
throw new OakInputIllegalException('accountOper', ['availPlus'], 'accountOper中的availPlus不是数字');
|
||||
}
|
||||
switch (type) {
|
||||
case 'consume': {
|
||||
if (totalPlus >= 0 || availPlus > 0 || totalPlus > availPlus) {
|
||||
throw new OakInputIllegalException('accountOper', ['availPlus'], 'accountOper为consume时,其totalPlus/availPlus必须为0或负数,且totalPlus的绝对值要更大');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'deposit': {
|
||||
if (totalPlus < 0 || availPlus < 0 || totalPlus !== availPlus) {
|
||||
throw new OakInputIllegalException('accountOper', ['availPlus'], 'accountOper为deposit时,其totalPlus/availPlus必须为正数且相等');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'loan': {
|
||||
if (totalPlus !== 0 || availPlus >= 0) {
|
||||
throw new OakInputIllegalException('accountOper', ['availPlus'], 'accountOper为loan时,其totalPlus必须为0,且availPlus必须为负数');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'repay': {
|
||||
if (totalPlus !== 0 || availPlus <= 0) {
|
||||
throw new OakInputIllegalException('accountOper', ['availPlus'], 'accountOper为repay时,其totalPlus必须为0,且availPlus必须为正数');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'withdraw': {
|
||||
if (totalPlus >= 0 || availPlus >= 0 || totalPlus !== availPlus) {
|
||||
throw new OakInputIllegalException('accountOper', ['availPlus'], 'accountOper为withdraw时,其totalPlus和availPlus必须为不相等的负数');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'withdrawBack': {
|
||||
if (totalPlus <= 0 || availPlus <= 0 || totalPlus !== availPlus) {
|
||||
throw new OakInputIllegalException('accountOper', ['availPlus'], 'accountOper为withdraw时,其totalPlus和availPlus必须为不相等的正数');
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default checkers;
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { EntityDict } from '@oak-app-domain';
|
||||
import { Checker } from 'oak-domain/lib/types';
|
||||
import { RuntimeCxt } from '../types/RuntimeCxt';
|
||||
import aoCheckers from './accountOper';
|
||||
|
||||
const checkers = [
|
||||
|
||||
...aoCheckers,
|
||||
] as Checker<EntityDict, keyof EntityDict, RuntimeCxt>[];
|
||||
|
||||
export default checkers;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
import { Checker } from 'oak-domain/lib/types/Auth';
|
||||
import { EntityDict } from '@project/oak-app-domain';
|
||||
import { RuntimeCxt } from '../types/RuntimeCxt';
|
||||
import { OakInputIllegalException } from 'oak-domain/lib/types';
|
||||
import { generateNewId, generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||
import assert from 'assert';
|
||||
import { PAY_CHANNEL_ACCOUNT_ID } from '@project/config/constants';
|
||||
|
||||
const checkers: Checker<EntityDict, 'pay', RuntimeCxt>[] = [
|
||||
{
|
||||
entity: 'pay',
|
||||
type: 'logical',
|
||||
action: 'create',
|
||||
checker(operation) {
|
||||
const { data } = operation as EntityDict['pay']['CreateSingle'];
|
||||
data.refunded = 0;
|
||||
data.paid = 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
entity: 'pay',
|
||||
type: 'data',
|
||||
action: 'create',
|
||||
checker(data) {
|
||||
const { channelId, price, orderId, accountId } = data as EntityDict['pay']['CreateSingle']['data'];
|
||||
if (price! <= 0) {
|
||||
throw new OakInputIllegalException('pay', ['price'], '支付金额必须大于0');
|
||||
}
|
||||
if (!orderId) {
|
||||
// 充值类订单
|
||||
if (!accountId) {
|
||||
throw new OakInputIllegalException('pay', ['accountId'], '充值类支付必须指定accountId');
|
||||
}
|
||||
else if (channelId === PAY_CHANNEL_ACCOUNT_ID) {
|
||||
throw new OakInputIllegalException('pay', ['channelId'], '充值类支付不能使用帐户支付');
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (channelId === PAY_CHANNEL_ACCOUNT_ID) {
|
||||
if (!accountId) {
|
||||
throw new OakInputIllegalException('pay', ['accountId'], '使用account支付必须指向accountId');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
entity: 'pay',
|
||||
type: 'logicalData',
|
||||
action: 'create',
|
||||
checker(operation, context) {
|
||||
const { data } = operation as EntityDict['pay']['CreateSingle'];
|
||||
const { orderId, price } = data;
|
||||
|
||||
if (orderId) {
|
||||
// 所有已经支付和正在支付的pay之和不能超过订单总和
|
||||
const order = context.select('order', {
|
||||
data: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
pay$order: {
|
||||
$entity: 'pay',
|
||||
data: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
},
|
||||
filter: {
|
||||
iState: {
|
||||
$in: ['paying', 'paid'],
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
filter: {
|
||||
id: orderId,
|
||||
}
|
||||
}, {});
|
||||
|
||||
const checkPays = (order: Partial<EntityDict['order']['Schema']>) => {
|
||||
const { price: orderPrice, pay$order: pays } = order;
|
||||
let pricePaying = 0;
|
||||
pays!.forEach(
|
||||
(pay) => pricePaying += pay.price!
|
||||
);
|
||||
if (pricePaying + price! > orderPrice!) {
|
||||
throw new OakInputIllegalException('pay', ['price'], 'pay.create.priceOverflow');
|
||||
}
|
||||
};
|
||||
if (order instanceof Promise) {
|
||||
return order.then(
|
||||
([o]) => checkPays(o)
|
||||
);
|
||||
}
|
||||
return checkPays(order[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default checkers;
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import { EntityDict } from '@oak-app-domain';
|
||||
import { Checker } from 'oak-domain/lib/types';
|
||||
import { RuntimeCxt } from '../types/RuntimeCxt';
|
||||
import { checkAttributesNotNull } from 'oak-domain/lib/utils/validator';
|
||||
import { CreateOperationData as CreateStoreData } from '@oak-app-domain/Store/Schema';
|
||||
|
||||
export const checkers: Checker<EntityDict, 'store', RuntimeCxt>[] = [
|
||||
{
|
||||
type: 'data',
|
||||
action: 'create',
|
||||
entity: 'store',
|
||||
checker: (data, context) => {
|
||||
if (data instanceof Array) {
|
||||
data.forEach((ele) => {
|
||||
checkAttributesNotNull('store', data, [
|
||||
'coordinate',
|
||||
'name',
|
||||
'addrDetail',
|
||||
'areaId',
|
||||
]);
|
||||
});
|
||||
} else {
|
||||
checkAttributesNotNull('store', data, [
|
||||
'coordinate',
|
||||
'name',
|
||||
'addrDetail',
|
||||
'areaId',
|
||||
]);
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -1 +1,6 @@
|
|||
export const DATA_SUBSCRIBER_KEYS = {};
|
||||
export const DATA_SUBSCRIBER_KEYS = {};
|
||||
|
||||
export const PAY_CHANNEL_ACCOUNT_ID = 'payChannelAccountId';
|
||||
export const PAY_CHANNEL_ACCOUNT_CODE = 'ACCOUNT';
|
||||
export const PAY_CHANNEL_OFFLINE_ID = 'payChannelOfflineId';
|
||||
export const PAY_CHANNEL_OFFLINE_CODE = 'OFFLINE';
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { CreateOperationData as ActionAuth } from '@oak-app-domain/ActionAuth/Schema';
|
||||
|
||||
|
||||
const actionAuths: ActionAuth[] = [
|
||||
|
||||
];
|
||||
|
||||
export default actionAuths;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// 本文件为自动编译产生,请勿直接修改
|
||||
|
||||
import { CreateOperationData as I18n } from "../oak-app-domain/I18n/Schema";
|
||||
const i18ns: I18n[] = [];
|
||||
export default i18ns;
|
||||
|
|
@ -1,13 +1,7 @@
|
|||
import { relations } from '@project/oak-app-domain/Relation';
|
||||
import actionAuth from './actionAuth';
|
||||
import relationAuth from './relationAuth';
|
||||
import path from './path';
|
||||
import i18n from './i18n';
|
||||
import payChannel from './payChannel';
|
||||
|
||||
export default {
|
||||
relation: relations,
|
||||
actionAuth,
|
||||
relationAuth,
|
||||
path,
|
||||
i18n,
|
||||
payChannel,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
import { CreateOperationData as Path } from '@oak-app-domain/Path/Schema';
|
||||
|
||||
const paths: Path[] = [];
|
||||
|
||||
export default paths;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { PAY_CHANNEL_ACCOUNT_CODE, PAY_CHANNEL_ACCOUNT_ID, PAY_CHANNEL_OFFLINE_CODE, PAY_CHANNEL_OFFLINE_ID } from '@project/config/constants';
|
||||
import { CreateOperationData as PayChannel } from '../oak-app-domain/PayChannel/Schema';
|
||||
const data: PayChannel[] = [
|
||||
{
|
||||
id: PAY_CHANNEL_ACCOUNT_ID,
|
||||
code: PAY_CHANNEL_ACCOUNT_CODE,
|
||||
svg: 'todo url',
|
||||
},
|
||||
{
|
||||
id: PAY_CHANNEL_OFFLINE_ID,
|
||||
code: PAY_CHANNEL_OFFLINE_CODE,
|
||||
svg: 'todo url',
|
||||
}
|
||||
];
|
||||
|
||||
export default data;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { CreateOperationData as RelationAuth } from '@oak-app-domain/RelationAuth/Schema';
|
||||
|
||||
|
||||
const relationAuths: RelationAuth[] = [
|
||||
|
||||
]
|
||||
|
||||
export default relationAuths;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import {
|
||||
String,
|
||||
Text,
|
||||
Price,
|
||||
Boolean,
|
||||
Datetime,
|
||||
} from 'oak-domain/lib/types/DataType';
|
||||
import { AbleAction, AbleState, makeAbleActionDef } from 'oak-domain/lib/actions/action';
|
||||
import { EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDesc, ActionDef } from 'oak-domain/lib/types';
|
||||
|
||||
export interface Schema extends EntityShape {
|
||||
total: Price;
|
||||
avail: Price;
|
||||
};
|
||||
|
||||
type IAction = 'deposit' | 'withdraw' | 'withdrawBack' | 'consume' | 'loan' | 'repay';
|
||||
|
||||
export type Action = AbleAction | IAction;
|
||||
export const AbleActionDef: ActionDef<AbleAction, AbleState> = makeAbleActionDef('enabled');
|
||||
|
||||
export const entityDesc: EntityDesc<Schema, Action, '', {
|
||||
ableState: AbleState;
|
||||
}> = {
|
||||
locales: {
|
||||
zh_CN: {
|
||||
name: '帐户',
|
||||
attr: {
|
||||
total: '总余额',
|
||||
avail: '可用余额',
|
||||
ableState: '状态',
|
||||
},
|
||||
action: {
|
||||
enable: '启用',
|
||||
disable: '禁用',
|
||||
deposit: '充值',
|
||||
withdraw: '提现',
|
||||
withdrawBack: '提现退还',
|
||||
consume: '消费',
|
||||
loan: '抵押',
|
||||
repay: '偿还',
|
||||
},
|
||||
v: {
|
||||
ableState: {
|
||||
enabled: '可用的',
|
||||
disabled: '禁用的',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
style: {
|
||||
icon: {
|
||||
enable: '',
|
||||
disable: '',
|
||||
deposit: '',
|
||||
withdraw: '',
|
||||
consume: '',
|
||||
loan: '',
|
||||
repay: '',
|
||||
},
|
||||
color: {
|
||||
ableState: {
|
||||
enabled: '#008000',
|
||||
disabled: '#A9A9A9'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import {
|
||||
String,
|
||||
Text,
|
||||
Price,
|
||||
Boolean,
|
||||
Datetime,
|
||||
} from 'oak-domain/lib/types/DataType';
|
||||
import { EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDesc, ActionDef } from 'oak-domain/lib/types';
|
||||
import { Schema as Account } from './Account';
|
||||
|
||||
type Type = 'deposit' | 'withdraw' | 'consume' | 'loan' | 'repay' | 'withdrawBack';
|
||||
|
||||
export interface Schema extends EntityShape {
|
||||
account: Account;
|
||||
type: Type;
|
||||
totalPlus: Price;
|
||||
availPlus: Price;
|
||||
entity: String<32>;
|
||||
entityId: String<64>;
|
||||
};
|
||||
|
||||
export const entityDesc: EntityDesc<Schema, '', '', {
|
||||
type: Type;
|
||||
}> = {
|
||||
locales: {
|
||||
zh_CN: {
|
||||
name: '帐号操作',
|
||||
attr: {
|
||||
account: '帐号',
|
||||
type: '类型',
|
||||
totalPlus: '余额变化',
|
||||
availPlus: '可用余额变化',
|
||||
entity: '关联对象',
|
||||
entityId: '关联对象Id',
|
||||
},
|
||||
v: {
|
||||
type: {
|
||||
deposit: '充值',
|
||||
withdraw: '提现',
|
||||
consume: '消费',
|
||||
loan: '抵押',
|
||||
repay: '偿还',
|
||||
withdrawBack: '提现失败',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
style: {
|
||||
color: {
|
||||
type: {
|
||||
deposit: '#3498DB',
|
||||
withdraw: '#F7DC6F',
|
||||
consume: '#A569BD',
|
||||
loan: '#CD6155',
|
||||
repay: '#82E0AA',
|
||||
}
|
||||
}
|
||||
},
|
||||
configuration: {
|
||||
actionType: 'appendOnly',
|
||||
}
|
||||
}
|
||||
|
|
@ -9,35 +9,40 @@ import { EntityShape } from 'oak-domain/lib/types/Entity';
|
|||
import { EntityDesc, ActionDef } from 'oak-domain/lib/types';
|
||||
import { Schema as Order } from './Order';
|
||||
import { Schema as PayChannel } from './PayChannel';
|
||||
import { Schema as Account } from './Account';
|
||||
import { Schema as AccountOper } from './AccountOper';
|
||||
|
||||
/**
|
||||
* 约定:充值类pay,其orderId为null,accountId指向要充值的帐户(channel不能是ACCOUNT)
|
||||
* 订单类pay,其orderId指向订单,accountId指向付款的帐户(channel必须是ACCOUNT)
|
||||
*/
|
||||
export interface Schema extends EntityShape {
|
||||
price: Price;
|
||||
paid: Price;
|
||||
refunded: Price;
|
||||
order: Order;
|
||||
channel: PayChannel;
|
||||
timeoutAt: Datetime;
|
||||
forbidRefundAt?: Datetime;
|
||||
account?: Account;
|
||||
order?: Order;
|
||||
opers: AccountOper[];
|
||||
};
|
||||
|
||||
type IAction = 'startPaying' | 'payAll' | 'payPartially' | 'payNone' | 'timeout' | 'cancel' | 'startRefunding' | 'refundAll' | 'refundPartially' | 'refundNone';
|
||||
type IState = 'paid' | 'unPaid' | 'timeout' | 'cancelled' | 'paying' | 'partiallyPaid' | 'paid' | 'refunding' | 'partiallyRefunded' | 'refunded';
|
||||
type IAction = 'startPaying' | 'succeedPaying' | 'startClosing' | 'succeedClosing' | 'startRefunding' | 'refundAll' | 'refundPartially';
|
||||
type IState = 'unpaid' | 'paying' | 'paid' | 'closing' | 'closed' | 'refunding' | 'partiallyRefunded' | 'refunded';
|
||||
|
||||
|
||||
export const IActionDef: ActionDef<IAction, IState> = {
|
||||
stm: {
|
||||
startPaying: ['unPaid', 'paying'],
|
||||
payAll: [['unPaid', 'paying', 'partiallyPaid'], 'paid'],
|
||||
payPartially: [['unPaid', 'paying'], 'partiallyPaid'],
|
||||
payNone: ['paying', 'unPaid'],
|
||||
timeout: ['unPaid', 'timeout'],
|
||||
cancel: ['unPaid', 'cancelled'],
|
||||
startRefunding: [['paid', 'partiallyPaid'], 'refunding'],
|
||||
refundAll: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'refunded'],
|
||||
refundPartially: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'partiallyRefunded'],
|
||||
refundNone: ['refunding', 'paid'],
|
||||
startPaying: [['unpaid', 'paying'], 'paying'],
|
||||
succeedPaying: [['unpaid', 'paying'], 'paid'],
|
||||
startClosing: ['paying', 'closing'],
|
||||
succeedClosing: [['closing', 'paying'], 'closed'],
|
||||
startRefunding: [['paid', 'partiallyRefunded', 'refunding'], 'refunding'],
|
||||
refundAll: [['paid', 'refunding', 'partiallyRefunded'], 'refunded'],
|
||||
refundPartially: [['paid', 'refunding', 'partiallyRefunded'], 'partiallyRefunded'],
|
||||
},
|
||||
is: 'unPaid',
|
||||
is: 'unpaid',
|
||||
};
|
||||
type Action = IAction;
|
||||
|
||||
|
|
@ -67,6 +72,8 @@ export const entityDesc: EntityDesc<Schema, Action, '', {
|
|||
order: '所属订单',
|
||||
timeoutAt: '过期时间',
|
||||
forbidRefundAt: '停止退款时间',
|
||||
account: '充值帐户',
|
||||
opers: '被关联帐户操作',
|
||||
},
|
||||
action: {
|
||||
startPaying: '开始支付',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
import {
|
||||
String,
|
||||
Text,
|
||||
Price,
|
||||
Boolean,
|
||||
Datetime,
|
||||
} from 'oak-domain/lib/types/DataType';
|
||||
import { EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDesc, ActionDef } from 'oak-domain/lib/types';
|
||||
import { Schema as Account } from './Account';
|
||||
import { Schema as WithdrawChannel } from './WithdrawChannel';
|
||||
import { Schema as AccountOper } from './AccountOper';
|
||||
|
||||
export interface Schema extends EntityShape {
|
||||
account: Account;
|
||||
price: Price;
|
||||
channel: WithdrawChannel;
|
||||
opers: AccountOper[];
|
||||
};
|
||||
|
||||
type IState = 'withdrawing' | 'successful' | 'failed';
|
||||
type IAction = 'succeed' | 'fail';
|
||||
|
||||
export const IActionDef: ActionDef<IAction, IState> = {
|
||||
stm: {
|
||||
succeed: ['withdrawing', 'successful'],
|
||||
fail: ['withdrawing', 'failed'],
|
||||
},
|
||||
is: 'withdrawing',
|
||||
};
|
||||
type Action = IAction;
|
||||
|
||||
export const entityDesc: EntityDesc<Schema, Action, '', {
|
||||
iState: IState;
|
||||
}> = {
|
||||
locales: {
|
||||
zh_CN: {
|
||||
name: '提现',
|
||||
attr: {
|
||||
account: '帐户',
|
||||
price: '金额',
|
||||
channel: '途径',
|
||||
iState: '状态',
|
||||
opers: '被关联帐户操作',
|
||||
},
|
||||
v: {
|
||||
iState: {
|
||||
withdrawing: '提现中',
|
||||
successful: '成功的',
|
||||
failed: '失败的',
|
||||
},
|
||||
},
|
||||
action: {
|
||||
succeed: '成功',
|
||||
fail: '失败',
|
||||
},
|
||||
},
|
||||
},
|
||||
style: {
|
||||
icon: {
|
||||
succeed: '',
|
||||
fail: '',
|
||||
},
|
||||
color: {
|
||||
iState: {
|
||||
withdrawing: '#D2B4DE',
|
||||
successful: '#2E86C1',
|
||||
failed: '#D6DBDF',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import {
|
||||
String,
|
||||
Text,
|
||||
Price,
|
||||
Boolean,
|
||||
Datetime,
|
||||
} from 'oak-domain/lib/types/DataType';
|
||||
import { EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDesc, ActionDef } from 'oak-domain/lib/types';
|
||||
import { Schema as PayChannel } from './PayChannel';
|
||||
|
||||
export interface Schema extends EntityShape {
|
||||
name: String<64>;
|
||||
code: String<64>;
|
||||
data: Object;
|
||||
payChannel?: PayChannel;
|
||||
};
|
||||
|
||||
export const entityDesc: EntityDesc<Schema> = {
|
||||
locales: {
|
||||
zh_CN: {
|
||||
name: '提现途径',
|
||||
attr: {
|
||||
name: '姓名',
|
||||
code: '帐号',
|
||||
data: 'metadata',
|
||||
payChannel: '关联支付途径',
|
||||
}
|
||||
},
|
||||
},
|
||||
indexes: [
|
||||
{
|
||||
name: 'code_uniqe',
|
||||
attributes: [
|
||||
{
|
||||
name: 'code',
|
||||
}
|
||||
],
|
||||
config: {
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import initialize from './initialize.dev';
|
||||
export default initialize;
|
||||
|
||||
console.log('不应该走到这里');
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { CreateTriggerInTxn, Trigger } from 'oak-domain/lib/types/Trigger';
|
||||
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||
import { EntityDict } from '../oak-app-domain';
|
||||
import BackendRuntimeContext from '@project/context/BackendRuntimeContext';
|
||||
import assert from 'assert';
|
||||
|
||||
const triggers: Trigger<EntityDict, 'accountOper', BackendRuntimeContext>[] = [
|
||||
{
|
||||
name: '当生成accountOper时,修改account中的值',
|
||||
entity: 'accountOper',
|
||||
action: 'create',
|
||||
when: 'after',
|
||||
fn: async ({ operation }, context, option) => {
|
||||
const { data } = operation;
|
||||
assert(!(data instanceof Array));
|
||||
const { accountId, totalPlus, availPlus, type } = data;
|
||||
const [account] = await context.select('account', {
|
||||
data: {
|
||||
id: 1,
|
||||
total: 1,
|
||||
avail: 1,
|
||||
},
|
||||
filter: {
|
||||
id: accountId!,
|
||||
}
|
||||
}, { forUpdate: true });
|
||||
|
||||
const { total, avail } = account;
|
||||
await context.operate('account', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: type!,
|
||||
data: {
|
||||
total: total! + totalPlus!,
|
||||
avail: avail! + availPlus!,
|
||||
},
|
||||
filter: {
|
||||
id: accountId!,
|
||||
}
|
||||
}, option);
|
||||
|
||||
return 1;
|
||||
},
|
||||
} as CreateTriggerInTxn<EntityDict, 'accountOper', BackendRuntimeContext>
|
||||
];
|
||||
|
||||
export default triggers;
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
import { EntityDict } from '@oak-app-domain';
|
||||
import { Trigger } from 'oak-domain/lib/types';
|
||||
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
|
||||
|
||||
import aoTriggers from './accountOper';
|
||||
import payTriggers from './pay';
|
||||
|
||||
const triggers = [
|
||||
|
||||
...aoTriggers,
|
||||
...payTriggers,
|
||||
] as Trigger<EntityDict, keyof EntityDict, BackendRuntimeContext>[];
|
||||
|
||||
export default triggers;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
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';
|
||||
|
||||
const triggers: Trigger<EntityDict, 'order', BackendRuntimeContext>[] = [
|
||||
];
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
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;
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
import { Watcher } from 'oak-domain/lib/types';
|
||||
import { EntityDict } from '@oak-app-domain';
|
||||
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
|
||||
import orderWatchers from './order';
|
||||
|
||||
const watchers = [] as Watcher<
|
||||
const watchers = [
|
||||
...orderWatchers,
|
||||
] as Watcher<
|
||||
EntityDict,
|
||||
keyof EntityDict,
|
||||
BackendRuntimeContext
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import { BBWatcher } from 'oak-domain/lib/types/Watcher';
|
||||
import { EntityDict } from '@project/oak-app-domain';
|
||||
|
||||
const watchers: BBWatcher<EntityDict, 'order'>[] = [
|
||||
{
|
||||
name: '使过期的order结束',
|
||||
entity: 'order',
|
||||
filter: () => {
|
||||
const now = Date.now();
|
||||
return {
|
||||
iState: 'unpaid',
|
||||
timeoutAt: {
|
||||
$lte: now,
|
||||
},
|
||||
};
|
||||
},
|
||||
action: 'timeout',
|
||||
actionData: {},
|
||||
}
|
||||
];
|
||||
|
||||
export default watchers;
|
||||
Loading…
Reference in New Issue