编写了大量支付相关的逻辑

This commit is contained in:
Xu Chang 2024-04-26 20:58:30 +08:00
parent 288426babb
commit f9166d1c9c
124 changed files with 1817 additions and 285 deletions

View File

@ -1,5 +1,7 @@
import aoCheckers from './accountOper'; import aoCheckers from './accountOper';
import payCheckers from './pay';
const checkers = [ const checkers = [
...aoCheckers, ...aoCheckers,
...payCheckers,
]; ];
export default checkers; export default checkers;

View File

@ -21,11 +21,11 @@ declare const List: <T extends keyof EntityDict>(props: ReactComponentProps<Enti
type: "checkbox" | "radio"; type: "checkbox" | "radio";
selectedRowKeys?: string[] | undefined; selectedRowKeys?: string[] | undefined;
onChange: (selectedRowKeys: string[], row: RowWithActions<EntityDict, T>[], info?: { onChange: (selectedRowKeys: string[], row: RowWithActions<EntityDict, T>[], info?: {
type: "multiple" | "none" | "single"; type: "multiple" | "single" | "none";
} | undefined) => void; } | undefined) => void;
} | undefined; } | undefined;
hideHeader: boolean; hideHeader: boolean;
size?: "small" | "middle" | "large" | undefined; size?: "small" | "large" | "middle" | undefined;
scroll?: any; scroll?: any;
locale?: any; locale?: any;
}>) => React.ReactElement; }>) => React.ReactElement;
@ -47,11 +47,11 @@ declare const ListPro: <T extends keyof EntityDict>(props: {
type: "checkbox" | "radio"; type: "checkbox" | "radio";
selectedRowKeys?: string[] | undefined; selectedRowKeys?: string[] | undefined;
onChange: (selectedRowKeys: string[], row: RowWithActions<EntityDict, T>[], info?: { onChange: (selectedRowKeys: string[], row: RowWithActions<EntityDict, T>[], info?: {
type: "multiple" | "none" | "single"; type: "multiple" | "single" | "none";
} | undefined) => void; } | undefined) => void;
} | undefined; } | undefined;
disableSerialNumber?: boolean | undefined; disableSerialNumber?: boolean | undefined;
size?: "small" | "middle" | "large" | undefined; size?: "small" | "large" | "middle" | undefined;
scroll?: any; scroll?: any;
locale?: any; locale?: any;
}) => React.ReactElement; }) => React.ReactElement;
@ -62,14 +62,14 @@ declare const Detail: <T extends keyof EntityDict>(props: ReactComponentProps<En
data: Partial<EntityDict[T]["Schema"]>; data: Partial<EntityDict[T]["Schema"]>;
title?: string | undefined; title?: string | undefined;
bordered?: boolean | undefined; bordered?: boolean | undefined;
layout?: "vertical" | "horizontal" | undefined; layout?: "horizontal" | "vertical" | undefined;
}>) => React.ReactElement; }>) => React.ReactElement;
declare const Upsert: <T extends keyof EntityDict>(props: ReactComponentProps<EntityDict, T, false, { declare const Upsert: <T extends keyof EntityDict>(props: ReactComponentProps<EntityDict, T, false, {
helps: Record<string, string>; helps: Record<string, string>;
entity: T; entity: T;
attributes: OakAbsAttrUpsertDef<EntityDict, T, string | number>[]; attributes: OakAbsAttrUpsertDef<EntityDict, T, string | number>[];
data: EntityDict[T]["Schema"]; data: EntityDict[T]["Schema"];
layout: "vertical" | "horizontal"; layout: "horizontal" | "vertical";
mode: "default" | "card"; mode: "default" | "card";
}>) => React.ReactElement; }>) => React.ReactElement;
export { FilterPanel, List, ListPro, Detail, Upsert, ReactComponentProps, ColumnProps, RowWithActions, OakExtraActionProps, OakAbsAttrDef, onActionFnDef, }; export { FilterPanel, List, ListPro, Detail, Upsert, ReactComponentProps, ColumnProps, RowWithActions, OakExtraActionProps, OakAbsAttrDef, onActionFnDef, };

View File

@ -0,0 +1,10 @@
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, keyof import("../../../oak-app-domain").EntityDict, boolean, {
depositMax: number;
onSetPrice: (price: null | number) => void;
onSetChannel: (channel: string) => void;
onSetMeta: (meta: any) => void;
price: number | null;
channel: string;
meta: any;
}>) => React.ReactElement;
export default _default;

View File

@ -0,0 +1,31 @@
import { PAY_CHANNEL_ACCOUNT_NAME, PAY_CHANNEL_OFFLINE_NAME } from '../../../types/PayConfig';
export default OakComponent({
properties: {
depositMax: 10000,
onSetPrice: (price) => undefined,
onSetChannel: (channel) => undefined,
onSetMeta: (meta) => undefined,
price: null,
channel: '',
meta: {},
},
formData({ data }) {
const payConfig2 = this.features.pay.getPayConfigs();
let accountConfig;
const payConfig = [];
for (const config of payConfig2) {
if (config.channel === PAY_CHANNEL_ACCOUNT_NAME) {
accountConfig = config;
}
else if (config.channel !== PAY_CHANNEL_OFFLINE_NAME || config.allowUser) {
payConfig.push(config);
}
}
return {
account: data,
payConfig,
// accountConfig,
};
},
features: ['application'],
});

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,7 @@
{
"placeholder": "一次最大充值%{max}元",
"label": {
"depPrice": "充值金额",
"channel": "充值渠道"
}
}

View File

@ -0,0 +1,8 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
import { AccountPayConfig, PayConfig } from '../../../types/PayConfig';
export default function Render(props: WebComponentProps<EntityDict, 'account', false, {
depositMax: number;
payConfig: PayConfig;
accountConfig?: AccountPayConfig;
}>): null;

View File

@ -0,0 +1,9 @@
import { useState } from 'react';
export default function Render(props) {
const { depositMax, payConfig } = props.data;
const { t } = props.methods;
const [depPrice, setDepPrice] = useState(null);
const [depositChannel, setDepositChannel] = useState();
const [depositMeta, setDepositMeta] = useState();
return null;
}

View File

@ -0,0 +1,15 @@
import React from 'react';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
import { AccountPayConfig, PayConfig } from '../../../types/PayConfig';
export default function Render(props: WebComponentProps<EntityDict, 'account', false, {
depositMax: number;
payConfig: PayConfig;
accountConfig?: AccountPayConfig;
onSetPrice: (price: null | number) => void;
onSetChannel: (channel: string) => void;
onSetMeta: (meta: any) => void;
price: number;
channel: string;
meta: any;
}>): React.JSX.Element | null;

View File

@ -0,0 +1,24 @@
import React from 'react';
import { Form, InputNumber } from 'antd';
import ChannelPicker from '../../pay/channelPicker';
export default function Render(props) {
const { depositMax, payConfig, price, channel, meta, onSetChannel, onSetMeta, onSetPrice } = props.data;
const { t } = props.methods;
if (payConfig) {
return (<Form labelCol={{ span: 4 }} wrapperCol={{ span: 14 }} layout="horizontal" style={{ minWidth: 600 }} colon={false}>
<Form.Item label={<span>{t("label.depPrice")}:</span>}>
<InputNumber placeholder={t('placeholder', { max: depositMax })} max={depositMax} min={1} value={price} addonAfter={t('common::pay.symbol')} onChange={(value) => {
onSetPrice(value);
}}/>
</Form.Item>
<Form.Item label={<span style={{ marginTop: 10 }}>
{t('label.channel')}:
</span>}>
<ChannelPicker payConfig={payConfig} onPick={(channel) => {
onSetChannel(channel);
}} channel={channel} meta={meta} onSetMeta={(meta) => onSetMeta(meta)}/>
</Form.Item>
</Form>);
}
return null;
}

View File

@ -0,0 +1,5 @@
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "account", false, {
depositMax: number;
onDeposit: (payId: string) => void;
}>) => React.ReactElement;
export default _default;

View File

@ -0,0 +1,55 @@
import { generateNewIdAsync } from "oak-domain/lib/utils/uuid";
export default OakComponent({
entity: 'account',
isList: false,
projection: {
id: 1,
total: 1,
avail: 1,
systemId: 1,
entity: 1,
entityId: 1,
},
properties: {
depositMax: 10000,
onDeposit: (payId) => undefined,
},
formData({ data }) {
return {
account: data,
};
},
actions: ['deposit', 'withdraw'],
methods: {
async deposit(price, channel, meta, success) {
const payId = await generateNewIdAsync();
const { onDeposit, oakId } = this.props;
await this.execute(undefined, undefined, undefined, [
{
entity: 'account',
operation: {
id: await generateNewIdAsync(),
action: 'deposit',
data: {
pay$account: {
id: await generateNewIdAsync(),
action: 'create',
data: {
id: payId,
channel,
price,
meta,
},
},
},
filter: {
id: oakId,
}
},
}
]);
success();
onDeposit && onDeposit(payId);
}
}
});

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,10 @@
{
"title": "帐户详情",
"total": "总余额",
"avail": "可用余额",
"loan": "抵押金额",
"depositing": "充值中",
"deposit": {
"title": "帐户充值"
}
}

6
es/components/account/detail/web.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
import React from 'react';
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function Render(props: WebComponentProps<EntityDict, 'account', false, {
account: RowWithActions<EntityDict, 'account'>;
}>): React.JSX.Element;

View File

@ -0,0 +1,4 @@
import React from 'react';
export default function Render(props) {
return <div>account/detail</div>;
}

View File

@ -0,0 +1,9 @@
import React from 'react';
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function Render(props: WebComponentProps<EntityDict, 'account', false, {
account: RowWithActions<EntityDict, 'account'>;
depositMax: number;
}, {
deposit: (price: number, channel: string, meta: any, success: () => void) => Promise<void>;
}>): React.JSX.Element | null;

View File

@ -0,0 +1,64 @@
import React, { useState } from 'react';
import { Button, Modal, Card, Flex } from 'antd';
import Styles from './web.pc.module.less';
import { CentToString } from 'oak-domain/lib/utils/money';
import classNames from 'classnames';
import AccountDeposit from '../deposit';
export default function Render(props) {
const { account, depositMax } = props.data;
const { t, deposit } = props.methods;
const [depositOpen, setDepositOpen] = useState(false);
const [depPrice, setDepPrice] = useState(null);
const [depositChannel, setDepositChannel] = useState();
const [depositMeta, setDepositMeta] = useState();
const [depositing, setDepositing] = useState(false);
if (account) {
const { total, avail, '#oakLegalActions': legalActions } = account;
return (<div className={Styles.container}>
<Card className={Styles.card} title={t('title')} extra={<Flex gap="middle">
{legalActions?.includes('deposit') && <Button type="primary" onClick={() => setDepositOpen(true)}>
{t('account:action.deposit')}
</Button>}
{legalActions?.includes('withdraw') && <Button>
{t('account:action.withdraw')}
</Button>}
</Flex>}>
<Flex gap="middle" justify='space-around'>
<div className={classNames(Styles.grid, Styles.fortify)}>
<span className={Styles.label}>{t('avail')}</span>
<span className={Styles.value}>{t('common::pay.symbol')} {CentToString(avail, 2)}</span>
</div>
<div className={Styles.grid}>
<span className={Styles.label}>{t('total')}</span>
<span className={Styles.value}>{t('common::pay.symbol')} {CentToString(total, 2)}</span>
</div>
<div className={Styles.grid}>
<span className={Styles.label}>{t('loan')}</span>
<span className={Styles.value}>{t('common::pay.symbol')} {CentToString(total - avail, 2)}</span>
</div>
</Flex>
</Card>
<Modal title={t('deposit.title')} open={depositOpen} onCancel={() => {
setDepositOpen(false);
setDepPrice(null);
setDepositChannel(undefined);
setDepositMeta(undefined);
}} destroyOnClose={true} footer={<Button loading={depositing} type="primary" disabled={!depPrice || !depositChannel || depositing} onClick={() => {
setDepositing(true);
deposit(depPrice, depositChannel, depositMeta || {}, () => {
setDepPrice(null);
setDepositChannel(undefined);
setDepositMeta(undefined);
setDepositing(false);
});
}}>
{depositing ? t('depositing') : t('common::confirm')}
</Button>}>
<div style={{ padding: 12 }}>
<AccountDeposit depositMax={depositMax} price={depPrice} channel={depositChannel} meta={depositMeta} onSetPrice={(price) => setDepPrice(price)} onSetChannel={(channel) => setDepositChannel(channel)} onSetMeta={(meta) => setDepositMeta(meta)}/>
</div>
</Modal>
</div>);
}
return null;
}

View File

@ -0,0 +1,49 @@
.container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
.card {
margin-top: 18px;
margin-bottom: 18px;
width: 80%;
min-width: 800px;
}
.grid {
min-width: 133px;
border-radius: 6px;
padding: 16px;
display: flex;
flex-direction: column;
margin: 13px;
.label {
font-size: 16px;
font-weight: 400;
color: var(--oak-color-primary);
margin-bottom: 12px;
}
.value {
font-size: 24px;
font-weight: 600;
color: var(--oak-color-primary);
line-height: 33.6px;
}
}
.fortify {
background-color: var(--oak-color-primary-light);
.label {
font-weight: 900;
}
.value {
font-weight: bolder;
}
}
}

View File

@ -0,0 +1,23 @@
/// <reference types="react" />
import { PayConfig } from "../../../types/PayConfig";
/**
*
* by Xc 20240426
*/
type ExtraPicker = {
label: string;
name: string;
icon: React.ForwardRefExoticComponent<any>;
component?: React.ForwardRefExoticComponent<{
onSetMeta?: (meta: object) => void;
}>;
};
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, keyof import("../../../oak-app-domain").EntityDict, boolean, {
payConfig: PayConfig;
channel: string;
meta: object;
extraChannelPickers: ExtraPicker[];
onPick: (channel: string) => void;
onSetMeta: (meta?: object) => void;
}>) => React.ReactElement;
export default _default;

View File

@ -0,0 +1,20 @@
import { PAY_CHANNEL_OFFLINE_NAME } from "../../../types/PayConfig";
import assert from 'assert';
export default OakComponent({
properties: {
payConfig: [],
channel: '',
meta: {},
extraChannelPickers: [],
onPick: (channel) => undefined,
onSetMeta: (meta) => undefined,
},
formData() {
const { payConfig } = this.props;
assert(payConfig);
const offlineConfig = payConfig.find(ele => ele.channel === PAY_CHANNEL_OFFLINE_NAME);
return {
offlineConfig,
};
}
});

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,5 @@
{
"label": {
"option": "渠道"
}
}

View File

@ -0,0 +1,7 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
import { PayConfig } from '../../../types/PayConfig';
export default function Render(props: WebComponentProps<EntityDict, keyof EntityDict, false, {
channels: PayConfig[];
onPick: (channel: string, meta?: object) => void;
}>): null;

View File

@ -0,0 +1,3 @@
export default function Render(props) {
return null;
}

View File

@ -0,0 +1,12 @@
import React from 'react';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
import { OfflinePayConfig, PayConfig } from '../../../types/PayConfig';
export default function Render(props: WebComponentProps<EntityDict, keyof EntityDict, false, {
payConfig: PayConfig;
offlineConfig?: OfflinePayConfig;
channel: string;
meta?: any;
onPick: (channel: string, meta?: object) => void;
onSetMeta: (meta?: object) => void;
}>): React.JSX.Element;

View File

@ -0,0 +1,41 @@
import React from 'react';
import { Radio, Space, Select, Flex } from 'antd';
import Styles from './web.pc.module.less';
import { PAY_CHANNEL_ACCOUNT_NAME, PAY_CHANNEL_OFFLINE_NAME, PAY_CHANNEL_WECHAT_APP_NAME, PAY_CHANNEL_WECHAT_H5_NAME, PAY_CHANNEL_WECHAT_JS_NAME, PAY_CHANNEL_WECHAT_MP_NAME, PAY_CHANNEL_WECHAT_NATIVE_NAME } from '../../../types/PayConfig';
import { WechatOutlined, WalletOutlined } from '@ant-design/icons';
const ChannelIconDict = {
[PAY_CHANNEL_ACCOUNT_NAME]: <WalletOutlined />,
[PAY_CHANNEL_OFFLINE_NAME]: <WalletOutlined />,
[PAY_CHANNEL_WECHAT_APP_NAME]: <WechatOutlined />,
[PAY_CHANNEL_WECHAT_JS_NAME]: <WechatOutlined />,
[PAY_CHANNEL_WECHAT_NATIVE_NAME]: <WechatOutlined />,
[PAY_CHANNEL_WECHAT_H5_NAME]: <WechatOutlined />,
[PAY_CHANNEL_WECHAT_MP_NAME]: <WechatOutlined />,
};
export default function Render(props) {
const { payConfig, offlineConfig, channel, onPick, meta, onSetMeta } = props.data;
const { t } = props.methods;
const Offline = offlineConfig && offlineConfig.options?.length && (<span className={Styles.span}>
<Select style={{ width: 140 }} value={meta?.option} options={offlineConfig.options.map(ele => ({
label: ele,
value: ele,
}))} onSelect={(value) => {
onSetMeta({
...meta,
option: value,
});
}}/>
</span>);
return (<Radio.Group onChange={({ target }) => onPick(target.value)} value={channel}>
<Space direction="vertical">
{payConfig.map((v) => (<Radio value={v.channel} key={v.channel}>
<Flex gap="middle" className={Styles.option} align='center'>
<span className={Styles.span}>
{t(`payChannel::${v.channel}`)}
</span>
{v.channel === 'OFFLINE' && channel === 'OFFLINE' && Offline}
</Flex>
</Radio>))}
</Space>
</Radio.Group>);
}

View File

@ -0,0 +1,7 @@
.span {
margin-left: 6px;
}
.option {
height: 44px;
}

View File

@ -87,6 +87,7 @@ export default function render(props) {
]} onTabClick={(activeKey) => { ]} onTabClick={(activeKey) => {
if (key && operation) { if (key && operation) {
const { application$system } = operation.data; const { application$system } = operation.data;
if (application$system) {
const { filter } = application$system; const { filter } = application$system;
if (filter?.id === key) { if (filter?.id === key) {
setMessage({ setMessage({
@ -97,6 +98,7 @@ export default function render(props) {
}); });
} }
} }
}
setKey(activeKey); setKey(activeKey);
}}/> }}/>
</div>); </div>);

View File

@ -37,9 +37,9 @@ export default function Offline(props) {
}; };
return (<div className={Styles.container}> return (<div className={Styles.container}>
<Alert type='info' message={t('tips')}/> <Alert type='info' message={t('tips')}/>
<Form labelCol={{ span: 6 }} wrapperCol={{ span: 12 }} layout="horizontal" style={{ minWidth: 600 }}> <Form labelCol={{ span: 6 }} wrapperCol={{ span: 12 }} layout="horizontal" style={{ minWidth: 600, marginTop: 22 }}>
<Form.Item label={t('options')}> <Form.Item label={t('options')}>
<Flex gap="4px 0" wrap="wrap" style={{ marginTop: 22 }}> <Flex gap="4px 0" wrap="wrap">
{options.map((option, idx) => <Tag bordered={false} closable key={idx} onClose={() => { {options.map((option, idx) => <Tag bordered={false} closable key={idx} onClose={() => {
options.splice(idx, 1); options.splice(idx, 1);
update({ update({

View File

@ -1,5 +1,48 @@
// 本文件为自动编译产生,请勿直接修改 // 本文件为自动编译产生,请勿直接修改
const i18ns = [ const i18ns = [
{
id: "a7dab1e9edd21024ad094a553064102d",
namespace: "oak-pay-business-c-account-deposit",
language: "zh-CN",
module: "oak-pay-business",
position: "src/components/account/deposit",
data: {
"placeholder": "一次最大充值%{max}元",
"label": {
"depPrice": "充值金额",
"channel": "充值渠道"
}
}
},
{
id: "f86292a7fab630dee76783fe38c35381",
namespace: "oak-pay-business-c-account-detail",
language: "zh-CN",
module: "oak-pay-business",
position: "src/components/account/detail",
data: {
"title": "帐户详情",
"total": "总余额",
"avail": "可用余额",
"loan": "抵押金额",
"depositing": "充值中",
"deposit": {
"title": "帐户充值"
}
}
},
{
id: "3500cc465492fca3797b75c9c0dbf517",
namespace: "oak-pay-business-c-pay-channelPicker",
language: "zh-CN",
module: "oak-pay-business",
position: "src/components/pay/channelPicker",
data: {
"label": {
"option": "渠道"
}
}
},
{ {
id: "548c6f23c4d08f495b6eed1b757aceb5", id: "548c6f23c4d08f495b6eed1b757aceb5",
namespace: "oak-pay-business-c-payConfig-system", namespace: "oak-pay-business-c-payConfig-system",
@ -116,7 +159,11 @@ const i18ns = [
"close": "关", "close": "关",
"enter": "请输入", "enter": "请输入",
"change": "修改", "change": "修改",
"finish": "完成" "finish": "完成",
"pay": {
"symbol": "¥",
"scale": "元"
}
} }
}, },
{ {
@ -126,13 +173,13 @@ const i18ns = [
module: "oak-pay-business", module: "oak-pay-business",
position: "locales/payChannel", position: "locales/payChannel",
data: { data: {
"ACCOUNT": "系统帐户", "ACCOUNT": "系统帐户",
"OFFLINE": "系统外支付", "OFFLINE": "线下支付",
"WECHAT_JS": "微信支付JS", "WECHAT_JS": "微信支付",
"WECHAT_MP": "微信小程序", "WECHAT_MP": "微信支付",
"WECHAT_NATIVE": "微信Native", "WECHAT_NATIVE": "微信支付",
"WECHAT_H5": "微信H5", "WECHAT_H5": "微信支付",
"WECHAT_APP": "微信APP" "WECHAT_APP": "微信支付"
} }
} }
]; ];

View File

@ -10,7 +10,7 @@ export interface Schema extends EntityShape {
timeoutAt: Datetime; timeoutAt: Datetime;
} }
type IAction = 'startPaying' | 'payAll' | 'payPartially' | 'payNone' | 'timeout' | 'cancel' | 'startRefunding' | 'refundAll' | 'refundPartially' | 'refundNone'; 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 IState = 'paid' | 'unpaid' | 'timeout' | 'cancelled' | 'paying' | 'partiallyPaid' | 'paid' | 'refunding' | 'partiallyRefunded' | 'refunded';
export declare const IActionDef: ActionDef<IAction, IState>; export declare const IActionDef: ActionDef<IAction, IState>;
type Action = IAction; type Action = IAction;
export declare const entityDesc: EntityDesc<Schema, Action, '', { export declare const entityDesc: EntityDesc<Schema, Action, '', {

View File

@ -1,18 +1,18 @@
; ;
export const IActionDef = { export const IActionDef = {
stm: { stm: {
startPaying: ['unPaid', 'paying'], startPaying: ['unpaid', 'paying'],
payAll: [['unPaid', 'paying', 'partiallyPaid'], 'paid'], payAll: [['unpaid', 'paying', 'partiallyPaid'], 'paid'],
payPartially: [['unPaid', 'paying'], 'partiallyPaid'], payPartially: [['unpaid', 'paying'], 'partiallyPaid'],
payNone: ['paying', 'unPaid'], payNone: ['paying', 'unpaid'],
timeout: ['unPaid', 'timeout'], timeout: ['unpaid', 'timeout'],
cancel: ['unPaid', 'cancelled'], cancel: ['unpaid', 'cancelled'],
startRefunding: [['paid', 'partiallyPaid'], 'refunding'], startRefunding: [['paid', 'partiallyPaid'], 'refunding'],
refundAll: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'refunded'], refundAll: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'refunded'],
refundPartially: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'partiallyRefunded'], refundPartially: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'partiallyRefunded'],
refundNone: ['refunding', 'paid'], refundNone: ['refunding', 'paid'],
}, },
is: 'unPaid', is: 'unpaid',
}; };
export const entityDesc = { export const entityDesc = {
indexes: [ indexes: [
@ -55,7 +55,7 @@ export const entityDesc = {
paid: '已付款', paid: '已付款',
partiallyPaid: '部分支付', partiallyPaid: '部分支付',
paying: '支付中', paying: '支付中',
unPaid: '待付款', unpaid: '待付款',
timeout: '已超时', timeout: '已超时',
cancelled: '已取消', cancelled: '已取消',
refunded: '已退款', refunded: '已退款',
@ -80,7 +80,7 @@ export const entityDesc = {
}, },
color: { color: {
iState: { iState: {
unPaid: '#52BE80', unpaid: '#52BE80',
partiallyPaid: '#5DADE2', partiallyPaid: '#5DADE2',
cancelled: '#D6DBDF', cancelled: '#D6DBDF',
paid: '#2E86C1', paid: '#2E86C1',

View File

@ -14,7 +14,7 @@ export interface Schema extends EntityShape {
paid: Price; paid: Price;
refunded: Price; refunded: Price;
channel: String<32>; channel: String<32>;
timeoutAt: Datetime; timeoutAt?: Datetime;
forbidRefundAt?: Datetime; forbidRefundAt?: Datetime;
account?: Account; account?: Account;
order?: Order; order?: Order;

9
es/features/Pay.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { Feature } from "oak-frontend-base";
import { FeatureDict as GeneralFeatures } from 'oak-general-business';
import { EntityDict } from '../oak-app-domain';
export default class Pay extends Feature {
private application;
constructor(application: GeneralFeatures<EntityDict>['application']);
getPayChannels(): string[];
getPayConfigs(): (import("../types/PayConfig").WechatPayConfig | import("../types/PayConfig").AccountPayConfig | import("../types/PayConfig").OfflinePayConfig)[];
}

20
es/features/Pay.js Normal file
View File

@ -0,0 +1,20 @@
import { Feature } from "oak-frontend-base";
export default class Pay extends Feature {
application;
constructor(application) {
super();
this.application = application;
}
getPayChannels() {
const application = this.application.getApplication();
const { payConfig, system } = application;
const { payConfig: systemPayConfig } = system;
return (payConfig || []).map(ele => ele.channel).concat((systemPayConfig || []).map(ele => ele.channel));
}
getPayConfigs() {
const application = this.application.getApplication();
const { payConfig, system } = application;
const { payConfig: systemPayConfig } = system;
return (payConfig || []).concat((systemPayConfig || []));
}
}

View File

@ -1,6 +1,16 @@
import { EntityDict } from '../oak-app-domain'; import { EntityDict } from '../oak-app-domain';
import { AccessConfiguration } from 'oak-domain/lib/types/Configuration';
import { BasicFeatures } from 'oak-frontend-base'; import { BasicFeatures } from 'oak-frontend-base';
import { FeatureDict as Ogb0FeatureDict } from "oak-general-business"; import { FeatureDict as Ogb0FeatureDict } from "oak-general-business";
export declare function create<ED extends EntityDict>(features: BasicFeatures<ED> & Ogb0FeatureDict<ED>): {}; import Cos from 'oak-general-business/es/types/Cos';
export type FeatureDict<ED extends EntityDict> = {}; import Pay from './Pay';
export declare function initialize<ED extends EntityDict>(features: FeatureDict<ED> & BasicFeatures<ED> & Ogb0FeatureDict<ED>): Promise<void>; export declare function create<ED extends EntityDict>(features: BasicFeatures<ED> & Ogb0FeatureDict<ED>): {
pay: Pay;
};
export type FeatureDict<ED extends EntityDict> = {
pay: Pay;
};
export declare function initialize<ED extends EntityDict>(features: FeatureDict<ED> & BasicFeatures<ED> & Ogb0FeatureDict<ED>, access: AccessConfiguration, config?: {
applicationExtraProjection?: ED['application']['Selection']['data'];
dontAutoLoginInWechatmp?: true;
}, clazzes?: Array<new () => Cos<ED>>): Promise<void>;

View File

@ -1,5 +1,23 @@
import { merge } from 'oak-domain/lib/utils/lodash';
import { initialize as initializeGeneral } from 'oak-general-business/es/features';
import Pay from './Pay';
export function create(features) { export function create(features) {
return {}; const pay = new Pay(features.application);
return {
pay,
};
} }
export async function initialize(features) { export async function initialize(features, access, config, clazzes) {
const applicationProjection = {
payConfig: 1,
system: {
payConfig: 1,
},
};
await initializeGeneral(features, access, config ? {
dontAutoLoginInWechatmp: config.dontAutoLoginInWechatmp,
applicationExtraProjection: merge(applicationProjection, config.applicationExtraProjection || {}),
} : {
applicationExtraProjection: applicationProjection,
}, clazzes);
} }

View File

@ -45,5 +45,9 @@
"close": "关", "close": "关",
"enter": "请输入", "enter": "请输入",
"change": "修改", "change": "修改",
"finish": "完成" "finish": "完成",
"pay": {
"symbol": "¥",
"scale": "元"
}
} }

View File

@ -1,9 +1,9 @@
{ {
"ACCOUNT": "系统帐户", "ACCOUNT": "系统帐户",
"OFFLINE": "系统外支付", "OFFLINE": "线下支付",
"WECHAT_JS": "微信支付JS", "WECHAT_JS": "微信支付",
"WECHAT_MP": "微信小程序", "WECHAT_MP": "微信支付",
"WECHAT_NATIVE": "微信Native", "WECHAT_NATIVE": "微信支付",
"WECHAT_H5": "微信H5", "WECHAT_H5": "微信支付",
"WECHAT_APP": "微信APP" "WECHAT_APP": "微信支付"
} }

View File

@ -93,16 +93,10 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "relationId" | "
} | { } | {
relation?: never; relation?: never;
relationId?: ForeignKey<"relation">; relationId?: ForeignKey<"relation">;
}) & ({ }) & {
pathId?: never;
path: Path.CreateSingleOperation;
} | {
pathId: ForeignKey<"path">;
path?: Path.UpdateOperation;
} | {
path?: never; path?: never;
pathId: ForeignKey<"path">; pathId: ForeignKey<"path">;
})); });
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>; export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>; export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation; export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
@ -118,26 +112,15 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "relationId" | "
} | { } | {
relation?: never; relation?: never;
relationId?: ForeignKey<"relation"> | null; relationId?: ForeignKey<"relation"> | null;
}) & ({ }) & {
path?: Path.CreateSingleOperation;
pathId?: never;
} | {
path?: Path.UpdateOperation;
pathId?: never;
} | {
path?: Path.RemoveOperation;
pathId?: never;
} | {
path?: never; path?: never;
pathId?: ForeignKey<"path">; pathId?: ForeignKey<"path">;
})) & { }) & {
[k: string]: any; [k: string]: any;
}; };
export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>; export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>;
export type RemoveOperationData = {} & (({ export type RemoveOperationData = {} & (({
relation?: Relation.UpdateOperation | Relation.RemoveOperation; relation?: Relation.UpdateOperation | Relation.RemoveOperation;
}) & ({
path?: Path.UpdateOperation | Path.RemoveOperation;
})); }));
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>; export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>;
export type Operation = CreateOperation | UpdateOperation | RemoveOperation; export type Operation = CreateOperation | UpdateOperation | RemoveOperation;

View File

@ -1,7 +1,7 @@
import { ActionDef } from "oak-domain/lib/types/Action"; import { ActionDef } from "oak-domain/lib/types/Action";
import { GenericAction } from "oak-domain/lib/actions/action"; import { GenericAction } from "oak-domain/lib/actions/action";
export type IAction = 'startPaying' | 'payAll' | 'payPartially' | 'payNone' | 'timeout' | 'cancel' | 'startRefunding' | 'refundAll' | 'refundPartially' | 'refundNone' | string; export type IAction = 'startPaying' | 'payAll' | 'payPartially' | 'payNone' | 'timeout' | 'cancel' | 'startRefunding' | 'refundAll' | 'refundPartially' | 'refundNone' | string;
export type IState = 'paid' | 'unPaid' | 'timeout' | 'cancelled' | 'paying' | 'partiallyPaid' | 'paid' | 'refunding' | 'partiallyRefunded' | 'refunded' | string; export type IState = 'paid' | 'unpaid' | 'timeout' | 'cancelled' | 'paying' | 'partiallyPaid' | 'paid' | 'refunding' | 'partiallyRefunded' | 'refunded' | string;
export declare const IActionDef: ActionDef<IAction, IState>; export declare const IActionDef: ActionDef<IAction, IState>;
export type ParticularAction = IAction; export type ParticularAction = IAction;
export declare const actions: string[]; export declare const actions: string[];

View File

@ -1,17 +1,17 @@
export const IActionDef = { export const IActionDef = {
stm: { stm: {
startPaying: ['unPaid', 'paying'], startPaying: ['unpaid', 'paying'],
payAll: [['unPaid', 'paying', 'partiallyPaid'], 'paid'], payAll: [['unpaid', 'paying', 'partiallyPaid'], 'paid'],
payPartially: [['unPaid', 'paying'], 'partiallyPaid'], payPartially: [['unpaid', 'paying'], 'partiallyPaid'],
payNone: ['paying', 'unPaid'], payNone: ['paying', 'unpaid'],
timeout: ['unPaid', 'timeout'], timeout: ['unpaid', 'timeout'],
cancel: ['unPaid', 'cancelled'], cancel: ['unpaid', 'cancelled'],
startRefunding: [['paid', 'partiallyPaid'], 'refunding'], startRefunding: [['paid', 'partiallyPaid'], 'refunding'],
refundAll: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'refunded'], refundAll: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'refunded'],
refundPartially: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'partiallyRefunded'], refundPartially: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'partiallyRefunded'],
refundNone: ['refunding', 'paid'], refundNone: ['refunding', 'paid'],
}, },
is: 'unPaid', is: 'unpaid',
}; };
export const actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "startPaying", "payAll", "payPartially", "payNone", "timeout", "cancel", "startRefunding", "refundAll", "refundPartially", "refundNone"]; export const actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "startPaying", "payAll", "payPartially", "payNone", "timeout", "cancel", "startRefunding", "refundAll", "refundPartially", "refundNone"];
export const actionDefDict = { export const actionDefDict = {

View File

@ -33,7 +33,7 @@ export const desc = {
}, },
iState: { iState: {
type: "enum", type: "enum",
enumeration: ["paid", "unPaid", "timeout", "cancelled", "paying", "partiallyPaid", "paid", "refunding", "partiallyRefunded", "refunded"] enumeration: ["paid", "unpaid", "timeout", "cancelled", "paying", "partiallyPaid", "paid", "refunding", "partiallyRefunded", "refunded"]
} }
}, },
actionType: "crud", actionType: "crud",

View File

@ -13,7 +13,7 @@ export const style = {
}, },
color: { color: {
iState: { iState: {
unPaid: '#52BE80', unpaid: '#52BE80',
partiallyPaid: '#5DADE2', partiallyPaid: '#5DADE2',
cancelled: '#D6DBDF', cancelled: '#D6DBDF',
paid: '#2E86C1', paid: '#2E86C1',

View File

@ -1 +1 @@
{ "name": "订单", "attr": { "price": "订单金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "订单状态", "title": "订单标题", "desc": "订单描述", "timeoutAt": "过期时间" }, "action": { "startPaying": "开始支付", "payAll": "全部支付", "payPartially": "部分支付", "payNone": "支付失败", "timeout": "过期", "cancel": "放弃", "startRefunding": "开始退款", "refundAll": "完全退款", "refundNone": "退款失败", "refundPartially": "部分退款" }, "v": { "iState": { "paid": "已付款", "partiallyPaid": "部分支付", "paying": "支付中", "unPaid": "待付款", "timeout": "已超时", "cancelled": "已取消", "refunded": "已退款", "partiallyRefunded": "已部分退款", "refunding": "退款中" } } } { "name": "订单", "attr": { "price": "订单金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "订单状态", "title": "订单标题", "desc": "订单描述", "timeoutAt": "过期时间" }, "action": { "startPaying": "开始支付", "payAll": "全部支付", "payPartially": "部分支付", "payNone": "支付失败", "timeout": "过期", "cancel": "放弃", "startRefunding": "开始退款", "refundAll": "完全退款", "refundNone": "退款失败", "refundPartially": "部分退款" }, "v": { "iState": { "paid": "已付款", "partiallyPaid": "部分支付", "paying": "支付中", "unpaid": "待付款", "timeout": "已超时", "cancelled": "已取消", "refunded": "已退款", "partiallyRefunded": "已部分退款", "refunding": "退款中" } } }

View File

@ -27,6 +27,7 @@ export const desc = {
} }
} }
}, },
static: true,
actionType: "crud", actionType: "crud",
actions, actions,
indexes: [ indexes: [

View File

@ -13,7 +13,7 @@ export type OpSchema = EntityShape & {
paid: Price; paid: Price;
refunded: Price; refunded: Price;
channel: String<32>; channel: String<32>;
timeoutAt: Datetime; timeoutAt?: Datetime | null;
forbidRefundAt?: Datetime | null; forbidRefundAt?: Datetime | null;
accountId?: ForeignKey<"account"> | null; accountId?: ForeignKey<"account"> | null;
orderId?: ForeignKey<"order"> | null; orderId?: ForeignKey<"order"> | null;
@ -32,7 +32,7 @@ export type Schema = EntityShape & {
paid: Price; paid: Price;
refunded: Price; refunded: Price;
channel: String<32>; channel: String<32>;
timeoutAt: Datetime; timeoutAt?: Datetime | null;
forbidRefundAt?: Datetime | null; forbidRefundAt?: Datetime | null;
accountId?: ForeignKey<"account"> | null; accountId?: ForeignKey<"account"> | null;
orderId?: ForeignKey<"order"> | null; orderId?: ForeignKey<"order"> | null;

View File

@ -21,7 +21,6 @@ export const desc = {
} }
}, },
timeoutAt: { timeoutAt: {
notNull: true,
type: "datetime" type: "datetime"
}, },
forbidRefundAt: { forbidRefundAt: {

View File

@ -98,16 +98,10 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "sourceRelationI
} | { } | {
sourceRelation?: never; sourceRelation?: never;
sourceRelationId: ForeignKey<"sourceRelation">; sourceRelationId: ForeignKey<"sourceRelation">;
}) & ({ }) & {
pathId?: never;
path: Path.CreateSingleOperation;
} | {
pathId: ForeignKey<"path">;
path?: Path.UpdateOperation;
} | {
path?: never; path?: never;
pathId: ForeignKey<"path">; pathId: ForeignKey<"path">;
}) & ({ } & ({
destRelationId?: never; destRelationId?: never;
destRelation: Relation.CreateSingleOperation; destRelation: Relation.CreateSingleOperation;
} | { } | {
@ -132,19 +126,10 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "sourceRelationI
} | { } | {
sourceRelation?: never; sourceRelation?: never;
sourceRelationId?: ForeignKey<"sourceRelation">; sourceRelationId?: ForeignKey<"sourceRelation">;
}) & ({ }) & {
path?: Path.CreateSingleOperation;
pathId?: never;
} | {
path?: Path.UpdateOperation;
pathId?: never;
} | {
path?: Path.RemoveOperation;
pathId?: never;
} | {
path?: never; path?: never;
pathId?: ForeignKey<"path">; pathId?: ForeignKey<"path">;
}) & ({ } & ({
destRelation?: Relation.CreateSingleOperation; destRelation?: Relation.CreateSingleOperation;
destRelationId?: never; destRelationId?: never;
} | { } | {
@ -162,8 +147,6 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "sourceRelationI
export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>; export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>;
export type RemoveOperationData = {} & (({ export type RemoveOperationData = {} & (({
sourceRelation?: Relation.UpdateOperation | Relation.RemoveOperation; sourceRelation?: Relation.UpdateOperation | Relation.RemoveOperation;
}) & ({
path?: Path.UpdateOperation | Path.RemoveOperation;
}) & ({ }) & ({
destRelation?: Relation.UpdateOperation | Relation.RemoveOperation; destRelation?: Relation.UpdateOperation | Relation.RemoveOperation;
})); }));

View File

@ -234,6 +234,7 @@ const triggers = [
entity: 'pay', entity: 'pay',
action: ['startPaying'], action: ['startPaying'],
when: 'before', when: 'before',
priority: 99,
fn: async ({ operation }, context, option) => { fn: async ({ operation }, context, option) => {
const { data, filter } = operation; const { data, filter } = operation;
const pays = await context.select('pay', { const pays = await context.select('pay', {
@ -244,14 +245,9 @@ const triggers = [
const [pay] = pays; const [pay] = pays;
const { applicationId, channel, iState } = pay; const { applicationId, channel, iState } = pay;
assert(iState === 'unpaid'); assert(iState === 'unpaid');
if (![PAY_CHANNEL_ACCOUNT_NAME, PAY_CHANNEL_OFFLINE_NAME].includes(channel)) { const payClazz = await getPayClazz(applicationId, channel, context);
const payClazz = getPayClazz(applicationId, channel); await payClazz.prepay(pay, data, context);
const [prePayId, meta] = await payClazz.prepay(pay);
data.externalId = prePayId;
data.meta = meta;
return 1; return 1;
}
return 0;
}, },
}, },
{ {
@ -270,7 +266,7 @@ const triggers = [
const { applicationId, channel, iState, externalId } = pay; const { applicationId, channel, iState, externalId } = pay;
assert(iState === 'unpaid' || iState === 'paying'); assert(iState === 'unpaid' || iState === 'paying');
if (iState === 'paying' && externalId && ![PAY_CHANNEL_ACCOUNT_NAME, PAY_CHANNEL_OFFLINE_NAME].includes(channel)) { if (iState === 'paying' && externalId && ![PAY_CHANNEL_ACCOUNT_NAME, PAY_CHANNEL_OFFLINE_NAME].includes(channel)) {
const payClazz = getPayClazz(applicationId, channel); const payClazz = await getPayClazz(applicationId, channel, context);
await payClazz.close(pay); await payClazz.close(pay);
return 1; return 1;
} }

View File

@ -1,8 +1,9 @@
import { EntityDict } from '../oak-app-domain'; import { EntityDict } from '../oak-app-domain';
import { BRC } from '../types/RuntimeCxt';
type IState = EntityDict['pay']['OpSchema']['iState']; type IState = EntityDict['pay']['OpSchema']['iState'];
export default interface PayClazz { export default interface PayClazz {
type: string; channel: string;
prepay(pay: EntityDict['pay']['OpSchema']): Promise<[string, object]>; prepay(pay: EntityDict['pay']['OpSchema'], data: EntityDict['pay']['Update']['data'], context: BRC): Promise<void>;
getState(pay: EntityDict['pay']['OpSchema']): Promise<IState>; getState(pay: EntityDict['pay']['OpSchema']): Promise<IState>;
close(pay: EntityDict['pay']['OpSchema']): Promise<void>; close(pay: EntityDict['pay']['OpSchema']): Promise<void>;
decodeNotification(params: Record<string, string>, body: any): Promise<Array<{ decodeNotification(params: Record<string, string>, body: any): Promise<Array<{

14
es/utils/payClazz/Account.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
import { OpSchema, UpdateOperationData } from "../../oak-app-domain/Pay/Schema";
import PayClazz from "../../types/PayClazz";
export default class Account implements PayClazz {
channel: string;
prepay(pay: OpSchema, data: UpdateOperationData): Promise<void>;
getState(pay: OpSchema): Promise<string | null | undefined>;
close(pay: OpSchema): Promise<void>;
decodeNotification(params: Record<string, string>, body: any): Promise<{
payId: string;
iState: string | null | undefined;
extra?: any;
answer: string;
}[]>;
}

View File

@ -0,0 +1,34 @@
import { PAY_CHANNEL_ACCOUNT_NAME } from '../../types/PayConfig';
import assert from 'assert';
import { generateNewIdAsync } from "oak-domain/lib/utils/uuid";
export default class Account {
channel = PAY_CHANNEL_ACCOUNT_NAME;
async prepay(pay, data) {
const { accountId, price } = pay;
assert(accountId);
/**
* account类型的支付就是直接从account中扣除款项
*/
data.iState = 'paid',
data.accountOper$entity = [
{
id: await generateNewIdAsync(),
action: 'create',
data: {
id: await generateNewIdAsync(),
totalPlus: -price,
availPlus: -price,
},
}
];
}
getState(pay) {
throw new Error("account类型的pay不应该需要查询此状态");
}
close(pay) {
throw new Error("account类型的pay无法关闭");
}
decodeNotification(params, body) {
throw new Error("account类型的pay不需调用此接口");
}
}

15
es/utils/payClazz/Offline.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
import { OpSchema, UpdateOperationData } from "../../oak-app-domain/Pay/Schema";
import PayClazz from "../../types/PayClazz";
import { BRC } from "../../types/RuntimeCxt";
export default class Offline implements PayClazz {
channel: string;
prepay(pay: OpSchema, data: UpdateOperationData, context: BRC): Promise<void>;
getState(pay: OpSchema): Promise<string | null | undefined>;
close(pay: OpSchema): Promise<void>;
decodeNotification(params: Record<string, string>, body: any): Promise<{
payId: string;
iState: string | null | undefined;
extra?: any;
answer: string;
}[]>;
}

View File

@ -0,0 +1,19 @@
import { PAY_CHANNEL_OFFLINE_NAME } from "../../types/PayConfig";
import assert from "assert";
export default class Offline {
channel = PAY_CHANNEL_OFFLINE_NAME;
async prepay(pay, data, context) {
return;
}
async getState(pay) {
const { iState } = pay;
assert(iState === 'paying');
return iState;
}
async close(pay) {
return;
}
decodeNotification(params, body) {
throw new Error("offline类型的pay不应当跑到这里");
}
}

View File

@ -1,3 +1,7 @@
import { EntityDict } from '../../oak-app-domain';
import PayClazz from '../../types/PayClazz'; import PayClazz from '../../types/PayClazz';
export declare function registerPayClazz(appId: string, channel: string, clazz: PayClazz): void; import { BRC } from '../../types/RuntimeCxt';
export declare function getPayClazz(appId: string, channel: string): PayClazz; type PayClazzConstructor = <ED extends EntityDict>(application: ED['application']['Schema'], channel: string, context: BRC) => Promise<PayClazz>;
export declare function registerAppPayClazzConstructor(channel: string, constructor: PayClazzConstructor): void;
export declare function getPayClazz(appId: string, channel: string, context: BRC): Promise<PayClazz>;
export {};

View File

@ -1,12 +1,38 @@
import { PAY_CHANNEL_ACCOUNT_NAME, PAY_CHANNEL_OFFLINE_NAME } from "../../types/PayConfig";
import assert from 'assert'; import assert from 'assert';
import Offline from './Offline';
import Account from './Account';
const PayChannelDict = {}; const PayChannelDict = {};
export function registerPayClazz(appId, channel, clazz) { const PayClazzConstructorDict = {
const key = `${appId}.${channel}`; [PAY_CHANNEL_OFFLINE_NAME]: async () => new Offline(),
assert(!PayChannelDict.hasOwnProperty(key)); [PAY_CHANNEL_ACCOUNT_NAME]: async () => new Account(),
PayChannelDict[key] = clazz; };
export function registerAppPayClazzConstructor(channel, constructor) {
PayClazzConstructorDict[channel] = constructor;
} }
export function getPayClazz(appId, channel) { export async function getPayClazz(appId, channel, context) {
const key = `${appId}.${channel}`; const key = `${appId}.${channel}`;
assert(PayChannelDict.hasOwnProperty(key)); if (PayChannelDict.hasOwnProperty(key)) {
return PayChannelDict[key]; return PayChannelDict[key];
} }
const [application] = await context.select('application', {
data: {
id: 1,
config: 1,
payConfig: 1,
type: 1,
system: {
id: 1,
config: 1,
payConfig: 1,
},
},
filter: {
id: appId,
}
}, { dontCollect: true });
assert(PayClazzConstructorDict.hasOwnProperty(channel));
const PayClazz = await PayClazzConstructorDict[channel](application, channel, context);
PayChannelDict[key] = PayClazz;
return PayClazz;
}

View File

@ -25,7 +25,7 @@ const watchers = [
const results = []; const results = [];
for (const pay of data) { for (const pay of data) {
const { applicationId, channel, timeoutAt } = pay; const { applicationId, channel, timeoutAt } = pay;
const clazz = getPayClazz(applicationId, channel); const clazz = await getPayClazz(applicationId, channel, context);
const iState = await clazz.getState(pay); const iState = await clazz.getState(pay);
if (iState !== pay.iState) { if (iState !== pay.iState) {
let action = 'close'; let action = 'close';

View File

@ -2,7 +2,9 @@
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib"); const tslib_1 = require("tslib");
const accountOper_1 = tslib_1.__importDefault(require("./accountOper")); const accountOper_1 = tslib_1.__importDefault(require("./accountOper"));
const pay_1 = tslib_1.__importDefault(require("./pay"));
const checkers = [ const checkers = [
...accountOper_1.default, ...accountOper_1.default,
...pay_1.default,
]; ];
exports.default = checkers; exports.default = checkers;

View File

@ -2,6 +2,49 @@
// 本文件为自动编译产生,请勿直接修改 // 本文件为自动编译产生,请勿直接修改
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const i18ns = [ const i18ns = [
{
id: "a7dab1e9edd21024ad094a553064102d",
namespace: "oak-pay-business-c-account-deposit",
language: "zh-CN",
module: "oak-pay-business",
position: "src/components/account/deposit",
data: {
"placeholder": "一次最大充值%{max}元",
"label": {
"depPrice": "充值金额",
"channel": "充值渠道"
}
}
},
{
id: "f86292a7fab630dee76783fe38c35381",
namespace: "oak-pay-business-c-account-detail",
language: "zh-CN",
module: "oak-pay-business",
position: "src/components/account/detail",
data: {
"title": "帐户详情",
"total": "总余额",
"avail": "可用余额",
"loan": "抵押金额",
"depositing": "充值中",
"deposit": {
"title": "帐户充值"
}
}
},
{
id: "3500cc465492fca3797b75c9c0dbf517",
namespace: "oak-pay-business-c-pay-channelPicker",
language: "zh-CN",
module: "oak-pay-business",
position: "src/components/pay/channelPicker",
data: {
"label": {
"option": "渠道"
}
}
},
{ {
id: "548c6f23c4d08f495b6eed1b757aceb5", id: "548c6f23c4d08f495b6eed1b757aceb5",
namespace: "oak-pay-business-c-payConfig-system", namespace: "oak-pay-business-c-payConfig-system",
@ -118,7 +161,11 @@ const i18ns = [
"close": "关", "close": "关",
"enter": "请输入", "enter": "请输入",
"change": "修改", "change": "修改",
"finish": "完成" "finish": "完成",
"pay": {
"symbol": "¥",
"scale": "元"
}
} }
}, },
{ {
@ -128,13 +175,13 @@ const i18ns = [
module: "oak-pay-business", module: "oak-pay-business",
position: "locales/payChannel", position: "locales/payChannel",
data: { data: {
"ACCOUNT": "系统帐户", "ACCOUNT": "系统帐户",
"OFFLINE": "系统外支付", "OFFLINE": "线下支付",
"WECHAT_JS": "微信支付JS", "WECHAT_JS": "微信支付",
"WECHAT_MP": "微信小程序", "WECHAT_MP": "微信支付",
"WECHAT_NATIVE": "微信Native", "WECHAT_NATIVE": "微信支付",
"WECHAT_H5": "微信H5", "WECHAT_H5": "微信支付",
"WECHAT_APP": "微信APP" "WECHAT_APP": "微信支付"
} }
} }
]; ];

View File

@ -10,7 +10,7 @@ export interface Schema extends EntityShape {
timeoutAt: Datetime; timeoutAt: Datetime;
} }
type IAction = 'startPaying' | 'payAll' | 'payPartially' | 'payNone' | 'timeout' | 'cancel' | 'startRefunding' | 'refundAll' | 'refundPartially' | 'refundNone'; 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 IState = 'paid' | 'unpaid' | 'timeout' | 'cancelled' | 'paying' | 'partiallyPaid' | 'paid' | 'refunding' | 'partiallyRefunded' | 'refunded';
export declare const IActionDef: ActionDef<IAction, IState>; export declare const IActionDef: ActionDef<IAction, IState>;
type Action = IAction; type Action = IAction;
export declare const entityDesc: EntityDesc<Schema, Action, '', { export declare const entityDesc: EntityDesc<Schema, Action, '', {

View File

@ -4,18 +4,18 @@ exports.entityDesc = exports.IActionDef = void 0;
; ;
exports.IActionDef = { exports.IActionDef = {
stm: { stm: {
startPaying: ['unPaid', 'paying'], startPaying: ['unpaid', 'paying'],
payAll: [['unPaid', 'paying', 'partiallyPaid'], 'paid'], payAll: [['unpaid', 'paying', 'partiallyPaid'], 'paid'],
payPartially: [['unPaid', 'paying'], 'partiallyPaid'], payPartially: [['unpaid', 'paying'], 'partiallyPaid'],
payNone: ['paying', 'unPaid'], payNone: ['paying', 'unpaid'],
timeout: ['unPaid', 'timeout'], timeout: ['unpaid', 'timeout'],
cancel: ['unPaid', 'cancelled'], cancel: ['unpaid', 'cancelled'],
startRefunding: [['paid', 'partiallyPaid'], 'refunding'], startRefunding: [['paid', 'partiallyPaid'], 'refunding'],
refundAll: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'refunded'], refundAll: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'refunded'],
refundPartially: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'partiallyRefunded'], refundPartially: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'partiallyRefunded'],
refundNone: ['refunding', 'paid'], refundNone: ['refunding', 'paid'],
}, },
is: 'unPaid', is: 'unpaid',
}; };
exports.entityDesc = { exports.entityDesc = {
indexes: [ indexes: [
@ -58,7 +58,7 @@ exports.entityDesc = {
paid: '已付款', paid: '已付款',
partiallyPaid: '部分支付', partiallyPaid: '部分支付',
paying: '支付中', paying: '支付中',
unPaid: '待付款', unpaid: '待付款',
timeout: '已超时', timeout: '已超时',
cancelled: '已取消', cancelled: '已取消',
refunded: '已退款', refunded: '已退款',
@ -83,7 +83,7 @@ exports.entityDesc = {
}, },
color: { color: {
iState: { iState: {
unPaid: '#52BE80', unpaid: '#52BE80',
partiallyPaid: '#5DADE2', partiallyPaid: '#5DADE2',
cancelled: '#D6DBDF', cancelled: '#D6DBDF',
paid: '#2E86C1', paid: '#2E86C1',

View File

@ -14,7 +14,7 @@ export interface Schema extends EntityShape {
paid: Price; paid: Price;
refunded: Price; refunded: Price;
channel: String<32>; channel: String<32>;
timeoutAt: Datetime; timeoutAt?: Datetime;
forbidRefundAt?: Datetime; forbidRefundAt?: Datetime;
account?: Account; account?: Account;
order?: Order; order?: Order;

9
lib/features/Pay.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { Feature } from "oak-frontend-base";
import { FeatureDict as GeneralFeatures } from 'oak-general-business';
import { EntityDict } from '../oak-app-domain';
export default class Pay extends Feature {
private application;
constructor(application: GeneralFeatures<EntityDict>['application']);
getPayChannels(): string[];
getPayConfigs(): (import("../types/PayConfig").WechatPayConfig | import("../types/PayConfig").AccountPayConfig | import("../types/PayConfig").OfflinePayConfig)[];
}

23
lib/features/Pay.js Normal file
View File

@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const oak_frontend_base_1 = require("oak-frontend-base");
class Pay extends oak_frontend_base_1.Feature {
application;
constructor(application) {
super();
this.application = application;
}
getPayChannels() {
const application = this.application.getApplication();
const { payConfig, system } = application;
const { payConfig: systemPayConfig } = system;
return (payConfig || []).map(ele => ele.channel).concat((systemPayConfig || []).map(ele => ele.channel));
}
getPayConfigs() {
const application = this.application.getApplication();
const { payConfig, system } = application;
const { payConfig: systemPayConfig } = system;
return (payConfig || []).concat((systemPayConfig || []));
}
}
exports.default = Pay;

View File

@ -1,6 +1,16 @@
import { EntityDict } from '../oak-app-domain'; import { EntityDict } from '../oak-app-domain';
import { AccessConfiguration } from 'oak-domain/lib/types/Configuration';
import { BasicFeatures } from 'oak-frontend-base'; import { BasicFeatures } from 'oak-frontend-base';
import { FeatureDict as Ogb0FeatureDict } from "oak-general-business"; import { FeatureDict as Ogb0FeatureDict } from "oak-general-business";
export declare function create<ED extends EntityDict>(features: BasicFeatures<ED> & Ogb0FeatureDict<ED>): {}; import Cos from 'oak-general-business/es/types/Cos';
export type FeatureDict<ED extends EntityDict> = {}; import Pay from './Pay';
export declare function initialize<ED extends EntityDict>(features: FeatureDict<ED> & BasicFeatures<ED> & Ogb0FeatureDict<ED>): Promise<void>; export declare function create<ED extends EntityDict>(features: BasicFeatures<ED> & Ogb0FeatureDict<ED>): {
pay: Pay;
};
export type FeatureDict<ED extends EntityDict> = {
pay: Pay;
};
export declare function initialize<ED extends EntityDict>(features: FeatureDict<ED> & BasicFeatures<ED> & Ogb0FeatureDict<ED>, access: AccessConfiguration, config?: {
applicationExtraProjection?: ED['application']['Selection']['data'];
dontAutoLoginInWechatmp?: true;
}, clazzes?: Array<new () => Cos<ED>>): Promise<void>;

View File

@ -1,10 +1,29 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.initialize = exports.create = void 0; exports.initialize = exports.create = void 0;
const tslib_1 = require("tslib");
const lodash_1 = require("oak-domain/lib/utils/lodash");
const features_1 = require("oak-general-business/es/features");
const Pay_1 = tslib_1.__importDefault(require("./Pay"));
function create(features) { function create(features) {
return {}; const pay = new Pay_1.default(features.application);
return {
pay,
};
} }
exports.create = create; exports.create = create;
async function initialize(features) { async function initialize(features, access, config, clazzes) {
const applicationProjection = {
payConfig: 1,
system: {
payConfig: 1,
},
};
await (0, features_1.initialize)(features, access, config ? {
dontAutoLoginInWechatmp: config.dontAutoLoginInWechatmp,
applicationExtraProjection: (0, lodash_1.merge)(applicationProjection, config.applicationExtraProjection || {}),
} : {
applicationExtraProjection: applicationProjection,
}, clazzes);
} }
exports.initialize = initialize; exports.initialize = initialize;

View File

@ -45,5 +45,9 @@
"close": "关", "close": "关",
"enter": "请输入", "enter": "请输入",
"change": "修改", "change": "修改",
"finish": "完成" "finish": "完成",
"pay": {
"symbol": "¥",
"scale": "元"
}
} }

View File

@ -1,9 +1,9 @@
{ {
"ACCOUNT": "系统帐户", "ACCOUNT": "系统帐户",
"OFFLINE": "系统外支付", "OFFLINE": "线下支付",
"WECHAT_JS": "微信支付JS", "WECHAT_JS": "微信支付",
"WECHAT_MP": "微信小程序", "WECHAT_MP": "微信支付",
"WECHAT_NATIVE": "微信Native", "WECHAT_NATIVE": "微信支付",
"WECHAT_H5": "微信H5", "WECHAT_H5": "微信支付",
"WECHAT_APP": "微信APP" "WECHAT_APP": "微信支付"
} }

View File

@ -93,16 +93,10 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "relationId" | "
} | { } | {
relation?: never; relation?: never;
relationId?: ForeignKey<"relation">; relationId?: ForeignKey<"relation">;
}) & ({ }) & {
pathId?: never;
path: Path.CreateSingleOperation;
} | {
pathId: ForeignKey<"path">;
path?: Path.UpdateOperation;
} | {
path?: never; path?: never;
pathId: ForeignKey<"path">; pathId: ForeignKey<"path">;
})); });
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>; export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>; export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation; export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
@ -118,26 +112,15 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "relationId" | "
} | { } | {
relation?: never; relation?: never;
relationId?: ForeignKey<"relation"> | null; relationId?: ForeignKey<"relation"> | null;
}) & ({ }) & {
path?: Path.CreateSingleOperation;
pathId?: never;
} | {
path?: Path.UpdateOperation;
pathId?: never;
} | {
path?: Path.RemoveOperation;
pathId?: never;
} | {
path?: never; path?: never;
pathId?: ForeignKey<"path">; pathId?: ForeignKey<"path">;
})) & { }) & {
[k: string]: any; [k: string]: any;
}; };
export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>; export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>;
export type RemoveOperationData = {} & (({ export type RemoveOperationData = {} & (({
relation?: Relation.UpdateOperation | Relation.RemoveOperation; relation?: Relation.UpdateOperation | Relation.RemoveOperation;
}) & ({
path?: Path.UpdateOperation | Path.RemoveOperation;
})); }));
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>; export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>;
export type Operation = CreateOperation | UpdateOperation | RemoveOperation; export type Operation = CreateOperation | UpdateOperation | RemoveOperation;

View File

@ -1,7 +1,7 @@
import { ActionDef } from "oak-domain/lib/types/Action"; import { ActionDef } from "oak-domain/lib/types/Action";
import { GenericAction } from "oak-domain/lib/actions/action"; import { GenericAction } from "oak-domain/lib/actions/action";
export type IAction = 'startPaying' | 'payAll' | 'payPartially' | 'payNone' | 'timeout' | 'cancel' | 'startRefunding' | 'refundAll' | 'refundPartially' | 'refundNone' | string; export type IAction = 'startPaying' | 'payAll' | 'payPartially' | 'payNone' | 'timeout' | 'cancel' | 'startRefunding' | 'refundAll' | 'refundPartially' | 'refundNone' | string;
export type IState = 'paid' | 'unPaid' | 'timeout' | 'cancelled' | 'paying' | 'partiallyPaid' | 'paid' | 'refunding' | 'partiallyRefunded' | 'refunded' | string; export type IState = 'paid' | 'unpaid' | 'timeout' | 'cancelled' | 'paying' | 'partiallyPaid' | 'paid' | 'refunding' | 'partiallyRefunded' | 'refunded' | string;
export declare const IActionDef: ActionDef<IAction, IState>; export declare const IActionDef: ActionDef<IAction, IState>;
export type ParticularAction = IAction; export type ParticularAction = IAction;
export declare const actions: string[]; export declare const actions: string[];

View File

@ -3,18 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.actionDefDict = exports.actions = exports.IActionDef = void 0; exports.actionDefDict = exports.actions = exports.IActionDef = void 0;
exports.IActionDef = { exports.IActionDef = {
stm: { stm: {
startPaying: ['unPaid', 'paying'], startPaying: ['unpaid', 'paying'],
payAll: [['unPaid', 'paying', 'partiallyPaid'], 'paid'], payAll: [['unpaid', 'paying', 'partiallyPaid'], 'paid'],
payPartially: [['unPaid', 'paying'], 'partiallyPaid'], payPartially: [['unpaid', 'paying'], 'partiallyPaid'],
payNone: ['paying', 'unPaid'], payNone: ['paying', 'unpaid'],
timeout: ['unPaid', 'timeout'], timeout: ['unpaid', 'timeout'],
cancel: ['unPaid', 'cancelled'], cancel: ['unpaid', 'cancelled'],
startRefunding: [['paid', 'partiallyPaid'], 'refunding'], startRefunding: [['paid', 'partiallyPaid'], 'refunding'],
refundAll: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'refunded'], refundAll: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'refunded'],
refundPartially: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'partiallyRefunded'], refundPartially: [['paid', 'refunding', 'partiallyPaid', 'partiallyRefunded'], 'partiallyRefunded'],
refundNone: ['refunding', 'paid'], refundNone: ['refunding', 'paid'],
}, },
is: 'unPaid', is: 'unpaid',
}; };
exports.actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "startPaying", "payAll", "payPartially", "payNone", "timeout", "cancel", "startRefunding", "refundAll", "refundPartially", "refundNone"]; exports.actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "startPaying", "payAll", "payPartially", "payNone", "timeout", "cancel", "startRefunding", "refundAll", "refundPartially", "refundNone"];
exports.actionDefDict = { exports.actionDefDict = {

View File

@ -36,7 +36,7 @@ exports.desc = {
}, },
iState: { iState: {
type: "enum", type: "enum",
enumeration: ["paid", "unPaid", "timeout", "cancelled", "paying", "partiallyPaid", "paid", "refunding", "partiallyRefunded", "refunded"] enumeration: ["paid", "unpaid", "timeout", "cancelled", "paying", "partiallyPaid", "paid", "refunding", "partiallyRefunded", "refunded"]
} }
}, },
actionType: "crud", actionType: "crud",

View File

@ -16,7 +16,7 @@ exports.style = {
}, },
color: { color: {
iState: { iState: {
unPaid: '#52BE80', unpaid: '#52BE80',
partiallyPaid: '#5DADE2', partiallyPaid: '#5DADE2',
cancelled: '#D6DBDF', cancelled: '#D6DBDF',
paid: '#2E86C1', paid: '#2E86C1',

View File

@ -1 +1 @@
{ "name": "订单", "attr": { "price": "订单金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "订单状态", "title": "订单标题", "desc": "订单描述", "timeoutAt": "过期时间" }, "action": { "startPaying": "开始支付", "payAll": "全部支付", "payPartially": "部分支付", "payNone": "支付失败", "timeout": "过期", "cancel": "放弃", "startRefunding": "开始退款", "refundAll": "完全退款", "refundNone": "退款失败", "refundPartially": "部分退款" }, "v": { "iState": { "paid": "已付款", "partiallyPaid": "部分支付", "paying": "支付中", "unPaid": "待付款", "timeout": "已超时", "cancelled": "已取消", "refunded": "已退款", "partiallyRefunded": "已部分退款", "refunding": "退款中" } } } { "name": "订单", "attr": { "price": "订单金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "订单状态", "title": "订单标题", "desc": "订单描述", "timeoutAt": "过期时间" }, "action": { "startPaying": "开始支付", "payAll": "全部支付", "payPartially": "部分支付", "payNone": "支付失败", "timeout": "过期", "cancel": "放弃", "startRefunding": "开始退款", "refundAll": "完全退款", "refundNone": "退款失败", "refundPartially": "部分退款" }, "v": { "iState": { "paid": "已付款", "partiallyPaid": "部分支付", "paying": "支付中", "unpaid": "待付款", "timeout": "已超时", "cancelled": "已取消", "refunded": "已退款", "partiallyRefunded": "已部分退款", "refunding": "退款中" } } }

View File

@ -30,6 +30,7 @@ exports.desc = {
} }
} }
}, },
static: true,
actionType: "crud", actionType: "crud",
actions: action_1.genericActions, actions: action_1.genericActions,
indexes: [ indexes: [

View File

@ -13,7 +13,7 @@ export type OpSchema = EntityShape & {
paid: Price; paid: Price;
refunded: Price; refunded: Price;
channel: String<32>; channel: String<32>;
timeoutAt: Datetime; timeoutAt?: Datetime | null;
forbidRefundAt?: Datetime | null; forbidRefundAt?: Datetime | null;
accountId?: ForeignKey<"account"> | null; accountId?: ForeignKey<"account"> | null;
orderId?: ForeignKey<"order"> | null; orderId?: ForeignKey<"order"> | null;
@ -32,7 +32,7 @@ export type Schema = EntityShape & {
paid: Price; paid: Price;
refunded: Price; refunded: Price;
channel: String<32>; channel: String<32>;
timeoutAt: Datetime; timeoutAt?: Datetime | null;
forbidRefundAt?: Datetime | null; forbidRefundAt?: Datetime | null;
accountId?: ForeignKey<"account"> | null; accountId?: ForeignKey<"account"> | null;
orderId?: ForeignKey<"order"> | null; orderId?: ForeignKey<"order"> | null;

View File

@ -24,7 +24,6 @@ exports.desc = {
} }
}, },
timeoutAt: { timeoutAt: {
notNull: true,
type: "datetime" type: "datetime"
}, },
forbidRefundAt: { forbidRefundAt: {

View File

@ -98,16 +98,10 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "sourceRelationI
} | { } | {
sourceRelation?: never; sourceRelation?: never;
sourceRelationId: ForeignKey<"sourceRelation">; sourceRelationId: ForeignKey<"sourceRelation">;
}) & ({ }) & {
pathId?: never;
path: Path.CreateSingleOperation;
} | {
pathId: ForeignKey<"path">;
path?: Path.UpdateOperation;
} | {
path?: never; path?: never;
pathId: ForeignKey<"path">; pathId: ForeignKey<"path">;
}) & ({ } & ({
destRelationId?: never; destRelationId?: never;
destRelation: Relation.CreateSingleOperation; destRelation: Relation.CreateSingleOperation;
} | { } | {
@ -132,19 +126,10 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "sourceRelationI
} | { } | {
sourceRelation?: never; sourceRelation?: never;
sourceRelationId?: ForeignKey<"sourceRelation">; sourceRelationId?: ForeignKey<"sourceRelation">;
}) & ({ }) & {
path?: Path.CreateSingleOperation;
pathId?: never;
} | {
path?: Path.UpdateOperation;
pathId?: never;
} | {
path?: Path.RemoveOperation;
pathId?: never;
} | {
path?: never; path?: never;
pathId?: ForeignKey<"path">; pathId?: ForeignKey<"path">;
}) & ({ } & ({
destRelation?: Relation.CreateSingleOperation; destRelation?: Relation.CreateSingleOperation;
destRelationId?: never; destRelationId?: never;
} | { } | {
@ -162,8 +147,6 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "sourceRelationI
export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>; export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>;
export type RemoveOperationData = {} & (({ export type RemoveOperationData = {} & (({
sourceRelation?: Relation.UpdateOperation | Relation.RemoveOperation; sourceRelation?: Relation.UpdateOperation | Relation.RemoveOperation;
}) & ({
path?: Path.UpdateOperation | Path.RemoveOperation;
}) & ({ }) & ({
destRelation?: Relation.UpdateOperation | Relation.RemoveOperation; destRelation?: Relation.UpdateOperation | Relation.RemoveOperation;
})); }));

View File

@ -237,6 +237,7 @@ const triggers = [
entity: 'pay', entity: 'pay',
action: ['startPaying'], action: ['startPaying'],
when: 'before', when: 'before',
priority: 99,
fn: async ({ operation }, context, option) => { fn: async ({ operation }, context, option) => {
const { data, filter } = operation; const { data, filter } = operation;
const pays = await context.select('pay', { const pays = await context.select('pay', {
@ -247,14 +248,9 @@ const triggers = [
const [pay] = pays; const [pay] = pays;
const { applicationId, channel, iState } = pay; const { applicationId, channel, iState } = pay;
(0, assert_1.default)(iState === 'unpaid'); (0, assert_1.default)(iState === 'unpaid');
if (![PayConfig_1.PAY_CHANNEL_ACCOUNT_NAME, PayConfig_1.PAY_CHANNEL_OFFLINE_NAME].includes(channel)) { const payClazz = await (0, payClazz_1.getPayClazz)(applicationId, channel, context);
const payClazz = (0, payClazz_1.getPayClazz)(applicationId, channel); await payClazz.prepay(pay, data, context);
const [prePayId, meta] = await payClazz.prepay(pay);
data.externalId = prePayId;
data.meta = meta;
return 1; return 1;
}
return 0;
}, },
}, },
{ {
@ -273,7 +269,7 @@ const triggers = [
const { applicationId, channel, iState, externalId } = pay; const { applicationId, channel, iState, externalId } = pay;
(0, assert_1.default)(iState === 'unpaid' || iState === 'paying'); (0, assert_1.default)(iState === 'unpaid' || iState === 'paying');
if (iState === 'paying' && externalId && ![PayConfig_1.PAY_CHANNEL_ACCOUNT_NAME, PayConfig_1.PAY_CHANNEL_OFFLINE_NAME].includes(channel)) { if (iState === 'paying' && externalId && ![PayConfig_1.PAY_CHANNEL_ACCOUNT_NAME, PayConfig_1.PAY_CHANNEL_OFFLINE_NAME].includes(channel)) {
const payClazz = (0, payClazz_1.getPayClazz)(applicationId, channel); const payClazz = await (0, payClazz_1.getPayClazz)(applicationId, channel, context);
await payClazz.close(pay); await payClazz.close(pay);
return 1; return 1;
} }

View File

@ -1,8 +1,9 @@
import { EntityDict } from '../oak-app-domain'; import { EntityDict } from '../oak-app-domain';
import { BRC } from '../types/RuntimeCxt';
type IState = EntityDict['pay']['OpSchema']['iState']; type IState = EntityDict['pay']['OpSchema']['iState'];
export default interface PayClazz { export default interface PayClazz {
type: string; channel: string;
prepay(pay: EntityDict['pay']['OpSchema']): Promise<[string, object]>; prepay(pay: EntityDict['pay']['OpSchema'], data: EntityDict['pay']['Update']['data'], context: BRC): Promise<void>;
getState(pay: EntityDict['pay']['OpSchema']): Promise<IState>; getState(pay: EntityDict['pay']['OpSchema']): Promise<IState>;
close(pay: EntityDict['pay']['OpSchema']): Promise<void>; close(pay: EntityDict['pay']['OpSchema']): Promise<void>;
decodeNotification(params: Record<string, string>, body: any): Promise<Array<{ decodeNotification(params: Record<string, string>, body: any): Promise<Array<{

14
lib/utils/payClazz/Account.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
import { OpSchema, UpdateOperationData } from "../../oak-app-domain/Pay/Schema";
import PayClazz from "../../types/PayClazz";
export default class Account implements PayClazz {
channel: string;
prepay(pay: OpSchema, data: UpdateOperationData): Promise<void>;
getState(pay: OpSchema): Promise<string | null | undefined>;
close(pay: OpSchema): Promise<void>;
decodeNotification(params: Record<string, string>, body: any): Promise<{
payId: string;
iState: string | null | undefined;
extra?: any;
answer: string;
}[]>;
}

View File

@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const PayConfig_1 = require("../../types/PayConfig");
const assert_1 = tslib_1.__importDefault(require("assert"));
const uuid_1 = require("oak-domain/lib/utils/uuid");
class Account {
channel = PayConfig_1.PAY_CHANNEL_ACCOUNT_NAME;
async prepay(pay, data) {
const { accountId, price } = pay;
(0, assert_1.default)(accountId);
/**
* account类型的支付就是直接从account中扣除款项
*/
data.iState = 'paid',
data.accountOper$entity = [
{
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
totalPlus: -price,
availPlus: -price,
},
}
];
}
getState(pay) {
throw new Error("account类型的pay不应该需要查询此状态");
}
close(pay) {
throw new Error("account类型的pay无法关闭");
}
decodeNotification(params, body) {
throw new Error("account类型的pay不需调用此接口");
}
}
exports.default = Account;

15
lib/utils/payClazz/Offline.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
import { OpSchema, UpdateOperationData } from "../../oak-app-domain/Pay/Schema";
import PayClazz from "../../types/PayClazz";
import { BRC } from "../../types/RuntimeCxt";
export default class Offline implements PayClazz {
channel: string;
prepay(pay: OpSchema, data: UpdateOperationData, context: BRC): Promise<void>;
getState(pay: OpSchema): Promise<string | null | undefined>;
close(pay: OpSchema): Promise<void>;
decodeNotification(params: Record<string, string>, body: any): Promise<{
payId: string;
iState: string | null | undefined;
extra?: any;
answer: string;
}[]>;
}

View File

@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const PayConfig_1 = require("../../types/PayConfig");
const assert_1 = tslib_1.__importDefault(require("assert"));
class Offline {
channel = PayConfig_1.PAY_CHANNEL_OFFLINE_NAME;
async prepay(pay, data, context) {
return;
}
async getState(pay) {
const { iState } = pay;
(0, assert_1.default)(iState === 'paying');
return iState;
}
async close(pay) {
return;
}
decodeNotification(params, body) {
throw new Error("offline类型的pay不应当跑到这里");
}
}
exports.default = Offline;

View File

@ -1,3 +1,7 @@
import { EntityDict } from '../../oak-app-domain';
import PayClazz from '../../types/PayClazz'; import PayClazz from '../../types/PayClazz';
export declare function registerPayClazz(appId: string, channel: string, clazz: PayClazz): void; import { BRC } from '../../types/RuntimeCxt';
export declare function getPayClazz(appId: string, channel: string): PayClazz; type PayClazzConstructor = <ED extends EntityDict>(application: ED['application']['Schema'], channel: string, context: BRC) => Promise<PayClazz>;
export declare function registerAppPayClazzConstructor(channel: string, constructor: PayClazzConstructor): void;
export declare function getPayClazz(appId: string, channel: string, context: BRC): Promise<PayClazz>;
export {};

View File

@ -1,18 +1,44 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.getPayClazz = exports.registerPayClazz = void 0; exports.getPayClazz = exports.registerAppPayClazzConstructor = void 0;
const tslib_1 = require("tslib"); const tslib_1 = require("tslib");
const PayConfig_1 = require("../../types/PayConfig");
const assert_1 = tslib_1.__importDefault(require("assert")); const assert_1 = tslib_1.__importDefault(require("assert"));
const Offline_1 = tslib_1.__importDefault(require("./Offline"));
const Account_1 = tslib_1.__importDefault(require("./Account"));
const PayChannelDict = {}; const PayChannelDict = {};
function registerPayClazz(appId, channel, clazz) { const PayClazzConstructorDict = {
const key = `${appId}.${channel}`; [PayConfig_1.PAY_CHANNEL_OFFLINE_NAME]: async () => new Offline_1.default(),
(0, assert_1.default)(!PayChannelDict.hasOwnProperty(key)); [PayConfig_1.PAY_CHANNEL_ACCOUNT_NAME]: async () => new Account_1.default(),
PayChannelDict[key] = clazz; };
function registerAppPayClazzConstructor(channel, constructor) {
PayClazzConstructorDict[channel] = constructor;
} }
exports.registerPayClazz = registerPayClazz; exports.registerAppPayClazzConstructor = registerAppPayClazzConstructor;
function getPayClazz(appId, channel) { async function getPayClazz(appId, channel, context) {
const key = `${appId}.${channel}`; const key = `${appId}.${channel}`;
(0, assert_1.default)(PayChannelDict.hasOwnProperty(key)); if (PayChannelDict.hasOwnProperty(key)) {
return PayChannelDict[key]; return PayChannelDict[key];
} }
const [application] = await context.select('application', {
data: {
id: 1,
config: 1,
payConfig: 1,
type: 1,
system: {
id: 1,
config: 1,
payConfig: 1,
},
},
filter: {
id: appId,
}
}, { dontCollect: true });
(0, assert_1.default)(PayClazzConstructorDict.hasOwnProperty(channel));
const PayClazz = await PayClazzConstructorDict[channel](application, channel, context);
PayChannelDict[key] = PayClazz;
return PayClazz;
}
exports.getPayClazz = getPayClazz; exports.getPayClazz = getPayClazz;

View File

@ -28,7 +28,7 @@ const watchers = [
const results = []; const results = [];
for (const pay of data) { for (const pay of data) {
const { applicationId, channel, timeoutAt } = pay; const { applicationId, channel, timeoutAt } = pay;
const clazz = (0, payClazz_1.getPayClazz)(applicationId, channel); const clazz = await (0, payClazz_1.getPayClazz)(applicationId, channel, context);
const iState = await clazz.getState(pay); const iState = await clazz.getState(pay);
if (iState !== pay.iState) { if (iState !== pay.iState) {
let action = 'close'; let action = 'close';

View File

@ -2,9 +2,11 @@ import { EntityDict } from '@oak-app-domain';
import { Checker } from 'oak-domain/lib/types'; import { Checker } from 'oak-domain/lib/types';
import { RuntimeCxt } from '../types/RuntimeCxt'; import { RuntimeCxt } from '../types/RuntimeCxt';
import aoCheckers from './accountOper'; import aoCheckers from './accountOper';
import payCheckers from './pay';
const checkers = [ const checkers = [
...aoCheckers, ...aoCheckers,
...payCheckers,
] as Checker<EntityDict, keyof EntityDict, RuntimeCxt>[]; ] as Checker<EntityDict, keyof EntityDict, RuntimeCxt>[];
export default checkers; export default checkers;

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,32 @@
import { AccountPayConfig, OfflinePayConfig,
PAY_CHANNEL_ACCOUNT_NAME, PAY_CHANNEL_OFFLINE_NAME } from '../../../types/PayConfig';
export default OakComponent({
properties: {
depositMax: 10000,
onSetPrice: (price: null | number) => undefined as void,
onSetChannel: (channel: string) => undefined as void,
onSetMeta: (meta: any) => undefined as void,
price: null as number | null,
channel: '',
meta: {} as any,
},
formData({ data }) {
const payConfig2 = this.features.pay.getPayConfigs();
let accountConfig: AccountPayConfig | undefined;
const payConfig: typeof payConfig2 = [];
for (const config of payConfig2) {
if (config.channel === PAY_CHANNEL_ACCOUNT_NAME) {
accountConfig = config;
}
else if (config.channel !== PAY_CHANNEL_OFFLINE_NAME || (<OfflinePayConfig>config).allowUser) {
payConfig.push(config);
}
}
return {
account: data,
payConfig,
// accountConfig,
};
},
features: ['application'],
})

View File

@ -0,0 +1,7 @@
{
"placeholder": "一次最大充值%{max}元",
"label": {
"depPrice": "充值金额",
"channel": "充值渠道"
}
}

View File

@ -0,0 +1,63 @@
import React, { useState } from 'react';
import { Form, InputNumber } from 'antd';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '@project/oak-app-domain';
import ChannelPicker from '../../pay/channelPicker';
import { AccountPayConfig, PayConfig } from '@project/types/PayConfig';
export default function Render(props: WebComponentProps<EntityDict, 'account', false, {
depositMax: number;
payConfig: PayConfig;
accountConfig?: AccountPayConfig;
onSetPrice: (price: null | number) => void;
onSetChannel: (channel: string) => void;
onSetMeta: (meta: any) => void;
price: number;
channel: string;
meta: any;
}>) {
const { depositMax, payConfig, price, channel, meta,
onSetChannel, onSetMeta, onSetPrice } = props.data;
const { t } = props.methods;
if (payConfig) {
return (
<Form
labelCol={{ span: 4 }}
wrapperCol={{ span: 14 }}
layout="horizontal"
style={{ minWidth: 600 }}
colon={false}
>
<Form.Item label={<span>{t("label.depPrice")}:</span>}>
<InputNumber
placeholder={t('placeholder', { max: depositMax })}
max={depositMax}
min={1}
value={price}
addonAfter={t('common::pay.symbol')}
onChange={(value) => {
onSetPrice(value);
}}
/>
</Form.Item>
<Form.Item label={<span style={{ marginTop: 10 }}>
{t('label.channel')}:
</span>}>
<ChannelPicker
payConfig={payConfig}
onPick={(channel) => {
onSetChannel(channel);
}}
channel={channel}
meta={meta}
onSetMeta={(meta) => onSetMeta(meta)}
/>
</Form.Item>
</Form>
);
}
return null;
}

View File

@ -0,0 +1,21 @@
import React, { useState } from 'react';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '@project/oak-app-domain';
import ChannelPicker from '../../pay/channelPicker';
import { AccountPayConfig, PayConfig } from '@project/types/PayConfig';
export default function Render(props: WebComponentProps<EntityDict, 'account', false, {
depositMax: number;
payConfig: PayConfig;
accountConfig?: AccountPayConfig;
}>) {
const { depositMax, payConfig } = props.data;
const { t } = props.methods;
const [depPrice, setDepPrice] = useState<null | number>(null);
const [depositChannel, setDepositChannel] = useState<string | undefined>();
const [depositMeta, setDepositMeta] = useState<any>();
return null;
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,56 @@
import { generateNewIdAsync } from "oak-domain/lib/utils/uuid";
export default OakComponent({
entity: 'account',
isList: false,
projection: {
id: 1,
total: 1,
avail: 1,
systemId: 1,
entity: 1,
entityId: 1,
},
properties: {
depositMax: 10000,
onDeposit: (payId: string) => undefined as void,
},
formData({ data }) {
return {
account: data,
};
},
actions: ['deposit', 'withdraw'],
methods: {
async deposit(price: number, channel: string, meta: object, success: () => void) {
const payId = await generateNewIdAsync();
const { onDeposit, oakId } = this.props;
await this.execute(undefined, undefined, undefined, [
{
entity: 'account',
operation: {
id: await generateNewIdAsync(),
action: 'deposit',
data: {
pay$account: {
id: await generateNewIdAsync(),
action: 'create',
data: {
id: payId,
channel,
price,
meta,
},
},
},
filter: {
id: oakId!,
}
},
}
]);
success();
onDeposit && onDeposit(payId);
}
}
})

Some files were not shown because too many files have changed in this diff Show More