This commit is contained in:
lxy 2024-06-07 22:07:18 +08:00
parent b40c4d8316
commit fca287127e
105 changed files with 805 additions and 531 deletions

View File

@ -1,2 +1,15 @@
const checkers = [];
const checkers = [
{
entity: 'account',
type: 'row',
filter: {
system: {
account$entity: {
"#sqp": 'not in',
},
},
},
action: 'create',
}
];
export default checkers;

View File

@ -81,6 +81,12 @@ const checkers = [
}
break;
}
case 'tax': {
if (totalPlus >= 0 || availPlus >= 0 || totalPlus !== availPlus) {
throw new OakInputIllegalException('accountOper', ['totalPlus', 'availPlus'], 'accountOper为tax时其totalPlus/availPlus必须为负且相等');
}
break;
}
default: {
assert(false);
break;

View File

@ -1,4 +1,4 @@
import { OakInputIllegalException } from 'oak-domain/lib/types';
import { OakAttrNotNullException, OakInputIllegalException } from 'oak-domain/lib/types';
import { pipeline } from 'oak-domain/lib/utils/executor';
import assert from 'assert';
const checkers = [
@ -98,6 +98,18 @@ const checkers = [
};
}
},
{
entity: 'pay',
action: 'succeedPaying',
type: 'data',
checker(data) {
assert(!(data instanceof Array));
const { successAt } = data;
if (!successAt) {
throw new OakAttrNotNullException('pay', ['successAt']);
}
}
},
{
// 如果在开始支付或者继续支付过程中paid达到了pricepay的状态可以改为paid
entity: 'pay',

View File

@ -58,14 +58,14 @@ declare const Detail: <T extends keyof EntityDict>(props: ReactComponentProps<En
data: Partial<EntityDict[T]["Schema"]>;
title?: string | undefined;
bordered?: boolean | undefined;
layout?: "vertical" | "horizontal" | undefined;
layout?: "horizontal" | "vertical" | undefined;
}>) => React.ReactElement;
declare const Upsert: <T extends keyof EntityDict>(props: ReactComponentProps<EntityDict, T, false, {
helps: Record<string, string>;
entity: T;
attributes: OakAbsAttrUpsertDef<EntityDict, T, string | number>[];
data: EntityDict[T]["Schema"];
layout: "vertical" | "horizontal";
layout: "horizontal" | "vertical";
mode: "default" | "card";
}>) => React.ReactElement;
export { FilterPanel, List, ListPro, Detail, Upsert, ReactComponentProps, ColumnProps, RowWithActions, OakExtraActionProps, OakAbsAttrDef, onActionFnDef, };

View File

@ -41,7 +41,7 @@ export default function render(props) {
return t('notnull', { value: t(`offlineAccount:attr.${attr}`) });
};
const errMsg = oakExecutable instanceof OakException && (oakExecutable instanceof OakAttrNotNullException ? getNotNullMessage(oakExecutable.getAttributes()[0]) : t(oakExecutable.message));
const U = (<Modal destroyOnClose width={680} title={`${t('offlineAccount:name')}${t('common::action.add')}`} open={!!upsertId} onCancel={() => {
const U = (<Modal destroyOnClose width={920} title={`${t('offlineAccount:name')}${t('common::action.add')}`} open={!!upsertId} onCancel={() => {
clean();
setUpsertId('');
}} closeIcon={null} onOk={async () => {

View File

@ -10,8 +10,9 @@ export default OakComponent({
allowPay: 1,
systemId: 1,
enabled: 1,
depositLossRatio: 1,
taxlossRatio: 1,
refundCompensateRatio: 1,
taxLossRatio: 1,
refundGapDays: 1,
},
isList: false,
formData({ data }) {

View File

@ -18,8 +18,9 @@
"shouqianba": "请将二维码解析后的字符串填入",
"others": "请将二维码解析后的字符串填入"
},
"taxlossRatio": "渠道收款时收取的手续费百分比0.6代表千分之六",
"depositLossRatio": "充值时收取的手续费百分比0.6代表千分之六"
"taxLossRatio": "渠道收款时收取的手续费百分比0.6代表千分之六",
"refundGapDays": "超过这个天数后将无法退款",
"refundCompensateRatio": "渠道退款时返回的手续费比例50代表渠道将返回当时收取的手续费的一半"
},
"help": {
"allowDeposit": "是否允许用户在系统中主动向此账号发起充值",

View File

@ -4,7 +4,7 @@ export default function render(props) {
const { offlineAccount } = props.data;
const { t, update } = props.methods;
if (offlineAccount) {
return (<Form labelCol={{ span: 6 }} wrapperCol={{ span: 16 }} layout="horizontal" style={{ minWidth: 600 }}>
return (<Form labelCol={{ span: 8 }} wrapperCol={{ span: 12 }} layout="horizontal" style={{ minWidth: 860 }}>
<Form.Item label={t('offlineAccount:attr.type')} required>
<Select value={offlineAccount.type} options={['bank', 'alipay', 'wechat', 'shouqianba', 'others'].map(ele => ({
label: t(`offlineAccount:v.type.${ele}`),
@ -41,16 +41,22 @@ export default function render(props) {
});
}} placeholder={t(`placeholder.qrCode.${offlineAccount.type}`)}/>}
</Form.Item>}
<Form.Item label={t('offlineAccount:attr.taxlossRatio')} help={t('placeholder.taxlossRatio')}>
<InputNumber value={offlineAccount.taxlossRatio} max={5} min={0.01} addonAfter={"%"} step={0.01} precision={2} onChange={(value) => {
const taxlossRatio = value;
update({ taxlossRatio });
<Form.Item label={t('offlineAccount:attr.taxLossRatio')} help={t('placeholder.taxLossRatio')}>
<InputNumber value={offlineAccount.taxLossRatio} max={5} min={0.01} addonAfter={"%"} step={0.01} precision={2} onChange={(value) => {
const taxLossRatio = value;
update({ taxLossRatio });
}}/>
</Form.Item>
<Form.Item label={t('offlineAccount:attr.depositLossRatio')} help={t('placeholder.depositLossRatio')}>
<InputNumber value={offlineAccount.depositLossRatio} max={5} min={0.01} addonAfter={"%"} step={0.01} precision={2} onChange={(value) => {
const depositLossRatio = value;
update({ depositLossRatio });
<Form.Item label={t('offlineAccount:attr.refundGapDays')} help={t('placeholder.refundGapDays')}>
<InputNumber value={offlineAccount.refundGapDays} max={365} min={7} addonAfter={"天"} step={1} onChange={(value) => {
const refundGapDays = value;
update({ refundGapDays });
}}/>
</Form.Item>
<Form.Item label={t('offlineAccount:attr.refundCompensateRatio')} help={t('placeholder.refundCompensateRatio')}>
<InputNumber value={offlineAccount.refundCompensateRatio} max={100} min={1} addonAfter={"%"} step={1} onChange={(value) => {
const refundCompensateRatio = value;
update({ refundCompensateRatio });
}}/>
</Form.Item>
{!!offlineAccount.type && <Form.Item label={t('offlineAccount:attr.allowDeposit')} required help={t('help.allowDeposit')}>

View File

@ -1,4 +1,3 @@
import { PAY_CHANNEL_ACCOUNT_NAME } from "../../../types/PayConfig";
import { generateNewId } from "oak-domain/lib/utils/uuid";
import { ToCent, ToYuan } from "oak-domain/lib/utils/money";
export default OakComponent({
@ -15,6 +14,8 @@ export default OakComponent({
price: 1,
paid: 1,
iState: 1,
entity: 1,
entityId: 1,
},
filter: {
iState: 'paying',
@ -28,9 +29,7 @@ export default OakComponent({
onSetPays: (pays) => undefined,
},
formData({ data }) {
const payConfig = this.features.pay.getPayConfigs();
const accountConfig = payConfig?.find(ele => ele.channel === PAY_CHANNEL_ACCOUNT_NAME);
const payConfig2 = payConfig?.filter(ele => ele !== accountConfig);
const payChannels = features.pay.getPayChannels('pay');
const activePay = data && data.pay$order?.[0];
const { accountPrice } = this.state;
const { accountAvailMax } = this.props;
@ -40,11 +39,10 @@ export default OakComponent({
accountAvailMaxStr: accountAvailMax && ToYuan(accountAvailMax),
order: data,
activePay,
accountConfig,
payConfig: payConfig2,
rest,
restYuan: ToYuan(rest),
legal: !!(data?.['#oakLegalActions']?.includes('startPaying')),
payChannels,
};
},
features: ['application'],
@ -52,7 +50,7 @@ export default OakComponent({
useAccount: false,
accountPrice: 0,
accountPriceYuan: 0,
channel: '',
channel: undefined,
restYuan: 0,
meta: undefined,
onPickMp(channel) {
@ -127,9 +125,9 @@ export default OakComponent({
if (useAccount && accountPrice) {
pays.push({
id: generateNewId(),
channel: PAY_CHANNEL_ACCOUNT_NAME,
entity: 'account',
entityId: accountId,
price: accountPrice,
accountId,
});
rest = rest - accountPrice;
}
@ -137,7 +135,8 @@ export default OakComponent({
payId = generateNewId();
pays.push({
id: payId,
channel,
entity: channel.entity,
entityId: channel.entityId,
meta,
price: rest,
});

View File

@ -12,6 +12,6 @@
"l-icon": "@oak-frontend-base/miniprogram_npm/lin-ui/icon/index",
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"l-switch": "@oak-frontend-base/miniprogram_npm/lin-ui/switch/index",
"channel-picker": "../../pay/channelPicker"
"channel-picker": "../../pay/channelPicker2"
}
}

View File

@ -1,23 +1,24 @@
import React from 'react';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
import { AccountPayConfig, PayConfig } from '../../../types/PayConfig';
import { AccountPayConfig } from '../../../types/PayConfig';
import { PayChannel, PayChannels } from '../../../types/Pay';
export default function Render(props: WebComponentProps<EntityDict, 'order', false, {
accountId?: string;
accountAvailMax: number;
order: EntityDict['order']['OpSchema'];
activePay?: EntityDict['pay']['OpSchema'];
accountConfig?: AccountPayConfig;
payConfig?: PayConfig;
payChannels?: PayChannels;
channel?: PayChannel;
accountPrice: number;
channel?: string;
meta?: object;
useAccount: boolean;
rest: number;
legal: false;
}, {
setAccountPrice: (price: number) => void;
onPickChannel: (channel: string) => void;
onPickChannel: (channel: PayChannel) => void;
onSetChannelMeta: (meta?: object) => void;
switchUseAccount: () => void;
}>): React.JSX.Element | null;

View File

@ -1,25 +1,19 @@
import React from 'react';
import { ToYuan, ToCent } from 'oak-domain/lib/utils/money';
import Styles from './web.mobile.module.less';
// import PayChannelPicker from '../../pay/channelPicker';
import PayChannelPicker from '../../pay/channelPicker2';
import { InputNumber } from 'antd';
import { Checkbox, Divider, ErrorBlock } from 'antd-mobile';
import Info from './info';
function RenderPayChannel(props) {
const { price, payConfig, t, channel, meta, onPick, onSetMeta } = props;
const { price, payChannels, t, channel, meta, onPick, } = props;
return (<div className={Styles.pc1}>
<div className={Styles.content}>
<div>
{t('choose', { price: ToYuan(price) })}
</div>
<Divider />
{/* <PayChannelPicker
payConfig={payConfig}
channel={channel}
meta={meta}
onPick={onPick}
onSetMeta={onSetMeta}
/> */}
<PayChannelPicker payChannels={payChannels} payChannel={channel} onPick={onPick}/>
</div>
</div>);
}
@ -45,7 +39,7 @@ function RenderAccountPay(props) {
</div>);
}
export default function Render(props) {
const { accountId, accountAvailMax, legal, accountPrice, useAccount, order, activePay, payConfig, channel, meta, rest } = props.data;
const { accountId, accountAvailMax, legal, accountPrice, useAccount, order, activePay, payChannels, channel, meta, rest } = props.data;
const { t, setAccountPrice, onPickChannel, onSetChannelMeta, switchUseAccount } = props.methods;
if (order) {
if (activePay) {
@ -60,7 +54,7 @@ export default function Render(props) {
<RenderAccountPay max={Math.min(accountAvailMax, rest + accountPrice)} t={t} setAccountPrice={setAccountPrice} useAccount={useAccount} switchUseAccount={switchUseAccount} accountPrice={accountPrice} accountAvail={accountAvailMax}/>
</div>}
{!!(rest && rest > 0) && <div className={Styles.ctrl}>
<RenderPayChannel payConfig={payConfig} price={rest} t={t} channel={channel} meta={meta} onPick={onPickChannel} onSetMeta={onSetChannelMeta}/>
<RenderPayChannel price={rest} t={t} payChannels={payChannels} channel={channel} meta={meta} onPick={onPickChannel}/>
</div>}
</div>);
}

View File

@ -1,23 +1,24 @@
import React from 'react';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
import { AccountPayConfig, PayConfig } from '../../../types/PayConfig';
import { AccountPayConfig } from '../../../types/PayConfig';
import { PayChannel, PayChannels } from '../../../types/Pay';
export default function Render(props: WebComponentProps<EntityDict, 'order', false, {
accountId?: string;
accountAvailMax: number;
order: EntityDict['order']['OpSchema'];
activePay?: EntityDict['pay']['OpSchema'];
accountConfig?: AccountPayConfig;
payConfig?: PayConfig;
payChannels?: PayChannels;
channel?: PayChannel;
accountPrice: number;
channel?: string;
meta?: object;
useAccount: boolean;
rest: number;
legal: false;
}, {
setAccountPrice: (price: number) => void;
onPickChannel: (channel: string) => void;
onPickChannel: (channel: PayChannel) => void;
onSetChannelMeta: (meta?: object) => void;
switchUseAccount: () => void;
}>): React.JSX.Element | null;

View File

@ -1,24 +1,18 @@
import React from 'react';
import { ToYuan, ToCent } from 'oak-domain/lib/utils/money';
import Styles from './web.pc.module.less';
// import PayChannelPicker from '../../pay/channelPicker';
import PayChannelPicker from '../../pay/channelPicker2';
import { Divider, Checkbox, InputNumber, Flex, Result } from 'antd';
import Info from './info';
function RenderPayChannel(props) {
const { price, payConfig, t, channel, meta, onPick, onSetMeta } = props;
const { price, payChannels, t, channel, meta, onPick, } = props;
return (<div className={Styles.pc1}>
<div className={Styles.content}>
<div>
{t('choose', { price: ToYuan(price) })}
</div>
<Divider />
{/* <PayChannelPicker
payConfig={payConfig}
channel={channel}
meta={meta}
onPick={onPick}
onSetMeta={onSetMeta}
/> */}
<PayChannelPicker payChannels={payChannels} payChannel={channel} onPick={onPick}/>
</div>
</div>);
}
@ -44,7 +38,7 @@ function RenderAccountPay(props) {
</div>);
}
export default function Render(props) {
const { accountId, accountAvailMax, legal, accountPrice, useAccount, order, activePay, payConfig, channel, meta, rest } = props.data;
const { accountId, accountAvailMax, legal, accountPrice, useAccount, order, activePay, payChannels, channel, meta, rest } = props.data;
const { t, setAccountPrice, onPickChannel, onSetChannelMeta, switchUseAccount } = props.methods;
if (order) {
if (activePay) {
@ -59,7 +53,7 @@ export default function Render(props) {
<RenderAccountPay max={Math.min(accountAvailMax, rest + accountPrice)} t={t} setAccountPrice={setAccountPrice} useAccount={useAccount} switchUseAccount={switchUseAccount} accountPrice={accountPrice} accountAvail={accountAvailMax}/>
</div>}
{!!(rest && rest > 0) && <div className={Styles.ctrl}>
<RenderPayChannel payConfig={payConfig} price={rest} t={t} channel={channel} meta={meta} onPick={onPickChannel} onSetMeta={onSetChannelMeta}/>
<RenderPayChannel price={rest} t={t} payChannels={payChannels} channel={channel} meta={meta} onPick={onPickChannel}/>
</div>}
</div>);
}

View File

@ -182,7 +182,7 @@ export default function Render(props) {
{t('pay:attr.price')}
</List.Item>
<List.Item prefix={<GlobalOutline />} extra={t(`payChannel::${entity}`)}>
{t('pay:attr.channel')}
{t('pay:attr.entity')}
</List.Item>
</List>
</div>

View File

@ -3,6 +3,7 @@ export default OakComponent({
isList: false,
projection: {
id: 1,
payConfig: 1,
wpAccount$system: {
$entity: 'wpAccount',
data: {

View File

@ -1,5 +1,22 @@
{
"system": "系统配置",
"appsBelow": "以下为application",
"mayLossUpdate": "%{name}上的更新可能会丢失,请尽快保存"
"system": "系统相关配置",
"help": "说明",
"payConfig": {
"label": {
"depositLoss": "充值手续费",
"withdrawLoss": "提现手续费",
"conservative": "保守策略",
"ratio": "按比例",
"lowest": "最低(分)",
"highest": "最高(分)",
"trim": "去尾",
"jiao": "角",
"yuan": "元",
"null": "不去尾"
},
"help": {
"depositLoss": "若不配置手续费用户充值时渠道的手续费损失将被计算到system关联的账户之中",
"withdrawLoss": "若配置为保守策略系统将自动计算渠道的损耗在system账户不贴现的前提下让用户可提现的额度最大化"
}
}
}

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Tabs } from 'antd';
import React from 'react';
import { Button, Popover, Tabs, Flex, Card, Form, InputNumber, Switch, Radio } from 'antd';
import Styles from './web.pc.module.less';
import OfflineConfig from '../../offlineAccount/config';
import WpAccountConfig from '../../wpAccount/config';
@ -9,13 +9,144 @@ const PayChannelConfigDict = {
export function registerPayChannelComponent(entity, component) {
PayChannelConfigDict[entity] = component;
}
function PayConfig(props) {
const { payConfig, update, t } = props;
const withdrawLoss = payConfig?.withdrawLoss;
const depositLoss = payConfig?.depositLoss;
const updateDepositLoss = (data) => {
update({
depositLoss: {
...depositLoss,
...data,
},
withdrawLoss: withdrawLoss || {
conservative: false,
},
});
};
const updateWithdrawLoss = (data) => {
update({
depositLoss: depositLoss || {},
withdrawLoss: {
conservative: !!(withdrawLoss?.conservative),
...withdrawLoss,
...data,
},
});
};
return (<Flex gap="middle">
<Card title={t('payConfig.label.depositLoss')} extra={<Popover content={t("payConfig.help.depositLoss")}>
<span className={Styles.help}>{t("help")}</span>
</Popover>}>
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 18 }} layout="horizontal" style={{ width: '100%' }}>
<Form.Item label={t('payConfig.label.ratio')}>
<InputNumber value={depositLoss?.ratio} max={20} min={0.01} addonAfter={"%"} step={0.01} precision={2} onChange={(value) => {
const ratio = value;
updateDepositLoss({
ratio: ratio || 0
});
}}/>
</Form.Item>
<Form.Item label={t('payConfig.label.highest')}>
<InputNumber value={depositLoss?.highest} min={0} step={1} onChange={(value) => {
const highest = value;
updateDepositLoss({
highest: highest || undefined
});
return;
}}/>
</Form.Item>
<Form.Item label={t('payConfig.label.lowest')}>
<InputNumber value={depositLoss?.lowest} min={0} step={1} onChange={(value) => {
const lowest = value;
updateDepositLoss({
lowest: lowest || undefined
});
return;
}}/>
</Form.Item>
</Form>
</Card>
<Card title={t('payConfig.label.withdrawLoss')} extra={<Popover content={t('payConfig.help.withdrawLoss')}>
<span className={Styles.help}>{t("help")}</span>
</Popover>}>
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 18 }} layout="horizontal" style={{ width: '100%' }}>
<Form.Item label={t('payConfig.label.conservative')}>
<Switch value={withdrawLoss?.conservative} onChange={(conservative) => {
updateWithdrawLoss({ conservative });
}}/>
</Form.Item>
<Form.Item label={t('payConfig.label.ratio')}>
<InputNumber disabled={!!withdrawLoss?.conservative} value={withdrawLoss?.ratio} max={20} min={0.01} addonAfter={"%"} step={0.01} precision={2} onChange={(value) => {
const ratio = value;
updateWithdrawLoss({
ratio: ratio || 0
});
}}/>
</Form.Item>
<Form.Item label={t('payConfig.label.highest')}>
<InputNumber disabled={!!withdrawLoss?.conservative} value={withdrawLoss?.highest} min={0} step={1} onChange={(value) => {
const highest = value;
updateWithdrawLoss({
highest: highest || undefined
});
return;
}}/>
</Form.Item>
<Form.Item label={t('payConfig.label.lowest')}>
<InputNumber disabled={!!withdrawLoss?.conservative} value={withdrawLoss?.lowest} min={0} step={1} onChange={(value) => {
const lowest = value;
updateWithdrawLoss({
lowest: lowest || undefined
});
return;
}}/>
</Form.Item>
<Form.Item label={t('payConfig.label.trim')}>
<Radio.Group disabled={!!withdrawLoss?.conservative} options={[
{
label: t('payConfig.label.jiao'),
value: 'jiao',
},
{
label: t('payConfig.label.yuan'),
value: 'yuan',
},
{
label: t('payConfig.label.null'),
value: '',
}
]} value={withdrawLoss?.trim} onChange={({ target }) => updateWithdrawLoss({
trim: target.value,
})}/>
</Form.Item>
</Form>
</Card>
</Flex>);
}
export default function render(props) {
const { system, oakFullpath, operation, oakDirty, serverUrl, oakExecutable } = props.data;
const { t, update, setMessage, execute } = props.methods;
const [key, setKey] = useState('');
const { t, update, clean, execute } = props.methods;
if (system && oakFullpath) {
return (<div className={Styles.container}>
<Tabs className={Styles.tabs} tabPosition="left" items={[
{
label: (<div className={Styles.systemLabel}>
{t('system')}
</div>),
key: 'system',
children: (<Flex vertical>
<PayConfig payConfig={system.payConfig} update={(payConfig) => update({ payConfig })} t={t}/>
<Flex gap="middle" justify='end'>
<Button type="primary" disabled={oakExecutable !== true} onClick={() => execute()}>
{t('common::confirm')}
</Button>
<Button disabled={!oakDirty} onClick={() => clean()}>
{t('common::reset')}
</Button>
</Flex>
</Flex>),
},
{
label: (<div className={Styles.systemLabel}>
{t('offlineAccount:name')}

View File

@ -6,9 +6,7 @@ export default OakComponent({
price: 1,
mchId: 1,
refundGapDays: 1,
refundLossRatio: 1,
refundLossFloor: 1,
taxlossRatio: 1,
taxLossRatio: 1,
enabled: 1,
},
formData({ data, legalActions }) {

View File

@ -12,11 +12,10 @@ export default OakComponent({
payNotifyUrl: 1,
refundNotifyUrl: 1,
},
taxlossRatio: 1,
taxLossRatio: 1,
publicKeyFilePath: 1,
privateKeyFilePath: 1,
refundLossFloor: 1,
refundLossRatio: 1,
refundCompensateRatio: 1,
apiV3Key: 1,
systemId: 1,
},
@ -46,7 +45,7 @@ export default OakComponent({
this.update({
price: 0,
enabled: true,
taxlossRatio: 0.6,
taxLossRatio: 0.6,
});
const { systemId } = this.props;
const { data: [wechatPay] } = await this.features.cache.refresh('wechatPay', {

View File

@ -2,14 +2,13 @@
"placeholder": {
"privateKeyFilePath": "服务器上存放apiclient_key.pem的路径注意访问权限",
"publicKeyFilePath": "服务器上存放apiclient_cert.pem的路径注意访问权限",
"taxlossRatio": "微信支付收取的手续费百分比一般为0.6(代表千分之六)",
"taxLossRatio": "微信支付收取的手续费百分比一般为0.6(代表千分之六)",
"refundCompensateRatio": "渠道退款时返回的手续费比例50代表渠道将返回当时收取的手续费的一半",
"depositLossRatio": "充值收取的手续费百分比一般为0.6(代表千分之六)",
"payNotifyUrl": "endpoint",
"refundNotifyUrl": "endpoint",
"apiV3Key": "需要登录商户后台获取",
"refundGapDays": "超过这个天数后无法退款",
"refundLossRatio": "退款时将按这个百分比扣除损耗0.6就代表千分之六)",
"refundLossFloor": "退款时按位向下取整(如按元取整,则忽略角位和分位)"
"refundGapDays": "超过这个天数后无法退款"
},
"wechatPayIsShared": "以上配置是全局的,请谨慎修改"
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Form, Switch, InputNumber, Input, Radio, Divider } from 'antd';
import { Form, Switch, InputNumber, Input, Divider } from 'antd';
import Styles from './web.pc.module.less';
import WechatPayUpsert from '../../wechatPay/upsert';
export default function render(props) {
@ -39,16 +39,16 @@ export default function render(props) {
update({ apiV3Key });
}}/>
</Form.Item>
<Form.Item label={t('wpAccount:attr.taxlossRatio')} help={t('placeholder.taxlossRatio')}>
<InputNumber value={wpAccount.taxlossRatio} max={5} min={0.01} addonAfter={"%"} step={0.01} precision={2} onChange={(value) => {
const taxlossRatio = value;
update({ taxlossRatio });
<Form.Item label={t('wpAccount:attr.taxLossRatio')} help={t('placeholder.taxLossRatio')}>
<InputNumber value={wpAccount.taxLossRatio} max={5} min={0.01} addonAfter={"%"} step={0.01} precision={2} onChange={(value) => {
const taxLossRatio = value;
update({ taxLossRatio });
}}/>
</Form.Item>
<Form.Item label={t('wpAccount:attr.depositLossRatio')} help={t('placeholder.depositLossRatio')}>
<InputNumber value={wpAccount.depositLossRatio} max={5} min={0.01} addonAfter={"%"} step={0.01} precision={2} onChange={(value) => {
const depositLossRatio = value;
update({ depositLossRatio });
<Form.Item label={t('wpAccount:attr.refundCompensateRatio')} help={t('placeholder.refundCompensateRatio')}>
<InputNumber value={wpAccount.refundCompensateRatio} max={100} min={1} addonAfter={"%"} step={1} onChange={(value) => {
const refundCompensateRatio = value;
update({ refundCompensateRatio });
}}/>
</Form.Item>
<Form.Item label={t('wpAccount:attr.refundGapDays')} help={t('placeholder.refundGapDays')}>
@ -57,22 +57,6 @@ export default function render(props) {
update({ refundGapDays });
}}/>
</Form.Item>
<Form.Item label={t('wpAccount:attr.refundLossRatio')} help={t('placeholder.refundLossRatio')}>
<InputNumber value={wpAccount.refundLossRatio} max={5} min={0.01} addonAfter={"%"} step={0.01} precision={2} onChange={(value) => {
const refundLossRatio = value;
update({ refundLossRatio });
}}/>
</Form.Item>
<Form.Item label={t('wpAccount:attr.refundLossFloor')} help={t('placeholder.refundLossFloor')}>
<Radio.Group onChange={({ target }) => {
const { value } = target;
const refundLossFloor = value;
update({ refundLossFloor });
}} value={wpAccount.refundLossFloor}>
<Radio value={"jiao"}></Radio>
<Radio value={"yuan"}></Radio>
</Radio.Group>
</Form.Item>
<Form.Item label={t('wpAccount:attr.enabled')} required>
<Switch value={wpAccount.enabled} onChange={(enabled) => {
update({ enabled });

View File

@ -1,5 +1,5 @@
{
"placeholder": {
"taxlossRatio": "如果配置了产品的手续费,则覆盖微信支付账号上配置的手续费"
"taxLossRatio": "如果配置了产品的手续费,则覆盖微信支付账号上配置的手续费"
}
}

View File

@ -22,10 +22,10 @@ export default function render(props) {
update({ enabled });
}}/>
</Form.Item>
<Form.Item label={t('wpAccount:attr.taxlossRatio')} help={t('placeholder.taxlossRatio')}>
<InputNumber value={wpProduct.taxlossRatio} max={5} min={0.01} addonAfter={"%"} step={0.01} precision={2} onChange={(value) => {
const taxlossRatio = value;
update({ taxlossRatio });
<Form.Item label={t('wpAccount:attr.taxLossRatio')} help={t('placeholder.taxLossRatio')}>
<InputNumber value={wpProduct.taxLossRatio} max={5} min={0.01} addonAfter={"%"} step={0.01} precision={2} onChange={(value) => {
const taxLossRatio = value;
update({ taxLossRatio });
}}/>
</Form.Item>
</Form>);

View File

@ -43,6 +43,9 @@ const attrUpdateMatrix = {
forbidRefundAt: {
actions: ['succeedPaying'],
},
successAt: {
actions: ['succeedPaying'],
},
entity: {
actions: ['update', 'succeedPaying'],
filter: {
@ -108,12 +111,18 @@ const attrUpdateMatrix = {
enabled: {
actions: ['update'],
},
taxlossRatio: {
taxLossRatio: {
actions: ['update'],
},
depositLossRatio: {
actions: ['update'],
},
refundCompensateRatio: {
actions: ['update'],
},
refundGapDays: {
actions: ['update'],
},
price: {
actions: ['pay', 'refund', 'deposit', 'withdraw', 'tax'],
},
@ -134,7 +143,7 @@ const attrUpdateMatrix = {
wechatPay: {
actions: ['update'],
},
taxlossRatio: {
taxLossRatio: {
actions: ['update'],
},
depositLossRatio: {

View File

@ -115,8 +115,9 @@ const i18ns = [
"shouqianba": "请将二维码解析后的字符串填入",
"others": "请将二维码解析后的字符串填入"
},
"taxlossRatio": "渠道收款时收取的手续费百分比0.6代表千分之六",
"depositLossRatio": "充值时收取的手续费百分比0.6代表千分之六"
"taxLossRatio": "渠道收款时收取的手续费百分比0.6代表千分之六",
"refundGapDays": "超过这个天数后将无法退款",
"refundCompensateRatio": "渠道退款时返回的手续费比例50代表渠道将返回当时收取的手续费的一半"
},
"help": {
"allowDeposit": "是否允许用户在系统中主动向此账号发起充值",
@ -229,9 +230,26 @@ const i18ns = [
module: "oak-pay-business",
position: "src/components/payConfig/system",
data: {
"system": "系统配置",
"appsBelow": "以下为application",
"mayLossUpdate": "%{name}上的更新可能会丢失,请尽快保存"
"system": "系统相关配置",
"help": "说明",
"payConfig": {
"label": {
"depositLoss": "充值手续费",
"withdrawLoss": "提现手续费",
"conservative": "保守策略",
"ratio": "按比例",
"lowest": "最低(分)",
"highest": "最高(分)",
"trim": "去尾",
"jiao": "角",
"yuan": "元",
"null": "不去尾"
},
"help": {
"depositLoss": "若不配置手续费用户充值时渠道的手续费损失将被计算到system关联的账户之中",
"withdrawLoss": "若配置为保守策略系统将自动计算渠道的损耗在system账户不贴现的前提下让用户可提现的额度最大化"
}
}
}
},
{
@ -472,14 +490,13 @@ const i18ns = [
"placeholder": {
"privateKeyFilePath": "服务器上存放apiclient_key.pem的路径注意访问权限",
"publicKeyFilePath": "服务器上存放apiclient_cert.pem的路径注意访问权限",
"taxlossRatio": "微信支付收取的手续费百分比一般为0.6(代表千分之六)",
"taxLossRatio": "微信支付收取的手续费百分比一般为0.6(代表千分之六)",
"refundCompensateRatio": "渠道退款时返回的手续费比例50代表渠道将返回当时收取的手续费的一半",
"depositLossRatio": "充值收取的手续费百分比一般为0.6(代表千分之六)",
"payNotifyUrl": "endpoint",
"refundNotifyUrl": "endpoint",
"apiV3Key": "需要登录商户后台获取",
"refundGapDays": "超过这个天数后无法退款",
"refundLossRatio": "退款时将按这个百分比扣除损耗0.6就代表千分之六)",
"refundLossFloor": "退款时按位向下取整(如按元取整,则忽略角位和分位)"
"refundGapDays": "超过这个天数后无法退款"
},
"wechatPayIsShared": "以上配置是全局的,请谨慎修改"
}
@ -506,7 +523,7 @@ const i18ns = [
position: "src/components/wpProduct/upsert",
data: {
"placeholder": {
"taxlossRatio": "如果配置了产品的手续费,则覆盖微信支付账号上配置的手续费"
"taxLossRatio": "如果配置了产品的手续费,则覆盖微信支付账号上配置的手续费"
}
}
},
@ -576,7 +593,9 @@ const i18ns = [
},
"deposit": {
"lossReason": {
"ratio": "按百分比%{value}%扣除"
"ratio": "按百分比%{value}%扣除",
"highest": "按单笔充值最高手续费%{value}扣除",
"lowest": "按单笔充值最低手续费%{value}扣除"
}
}
}

View File

@ -2,13 +2,9 @@ import { Int, Decimal } from 'oak-domain/lib/types/DataType';
import { EntityShape } from 'oak-domain/lib/types/Entity';
import { EntityDesc } from 'oak-domain/lib/types';
export interface Schema extends EntityShape {
taxlossRatio: Decimal<4, 2>;
depositLossRatio?: Decimal<4, 2>;
taxLossRatio: Decimal<4, 2>;
refundGapDays?: Int<4>;
refundLossRatio?: Decimal<4, 2>;
refundLossFloor?: 'yuan' | 'jiao';
refundCompensateRatio?: Int<4>;
}
export type Action = 'pay' | 'refund' | 'deposit' | 'withdraw' | 'tax';
export declare const entityDesc: EntityDesc<Schema, Action, '', {
refundLossFloor: NonNullable<Schema['refundLossFloor']>;
}>;
export declare const entityDesc: EntityDesc<Schema, Action>;

View File

@ -4,17 +4,9 @@ export const entityDesc = {
zh_CN: {
name: '抽象支付帐号',
attr: {
taxlossRatio: '渠道手续费(百分比)',
depositLossRatio: '充值损耗百分比',
taxLossRatio: '渠道手续费(百分比)',
refundGapDays: '(支付后)允许退款的天数',
refundLossRatio: '退款损耗百分比',
refundLossFloor: '退款向下取整位数',
},
v: {
refundLossFloor: {
yuan: '元',
jiao: '角'
},
refundCompensateRatio: '退款补偿百分比',
},
action: {
pay: '支付',
@ -26,12 +18,6 @@ export const entityDesc = {
},
},
style: {
color: {
refundLossFloor: {
yuan: '#FFFF00',
jiao: '#00FF00'
}
},
icon: {
pay: '',
refund: '',

View File

@ -2,7 +2,7 @@ import { String, Price } from 'oak-domain/lib/types/DataType';
import { EntityShape } from 'oak-domain/lib/types/Entity';
import { EntityDesc } from 'oak-domain/lib/types';
import { Schema as Account } from './Account';
type Type = 'deposit' | 'withdraw' | 'consume' | 'loan' | 'repay' | 'withdrawBack' | 'earn' | 'encash' | 'cutoffRefundable';
type Type = 'deposit' | 'withdraw' | 'consume' | 'loan' | 'repay' | 'withdrawBack' | 'earn' | 'encash' | 'cutoffRefundable' | 'tax';
export interface Schema extends EntityShape {
account: Account;
type: Type;

View File

@ -22,6 +22,7 @@ export const entityDesc = {
withdrawBack: '提现失败',
earn: '赚取',
encash: '兑现',
tax: '渠道费',
cutoffRefundable: '削减可自由退额度'
},
},
@ -38,6 +39,7 @@ export const entityDesc = {
repay: '#82E0AA',
earn: '#FF3333',
encash: '#FF3399',
tax: '#A569BD',
cutoffRefundable: '#2E4053',
}
}

View File

@ -1,9 +1,4 @@
<<<<<<< HEAD
import { String, Text, Price, Boolean } from 'oak-domain/lib/types/DataType';
=======
import { String, Text, Price, Boolean, Decimal } from 'oak-domain/lib/types/DataType';
import { EntityShape } from 'oak-domain/lib/types/Entity';
>>>>>>> 99d2876ae88266ffcff6aa944c537941daeab7ce
import { EntityDesc } from 'oak-domain/lib/types';
import { Schema as System } from './System';
import { Schema as Pay } from './Pay';
@ -12,8 +7,6 @@ import { Schema as AbstractAccount } from './AbstractAccount';
export interface Schema extends AbstractAccount {
type: 'bank' | 'alipay' | 'wechat' | 'shouqianba' | 'others';
channel?: String<32>;
taxlossRatio?: Decimal<4, 2>;
depositLossRatio?: Decimal<4, 2>;
name?: String<64>;
qrCode?: Text;
allowDeposit: Boolean;

View File

@ -8,14 +8,9 @@ export const entityDesc = {
channel: '通道',
name: '用户/帐号',
qrCode: '收款二维码',
taxlossRatio: '商户号手续费(百分比)',
depositLossRatio: '充值损耗百分比',
<<<<<<< HEAD
taxLossRatio: '商户号手续费(百分比)',
refundCompensateRatio: '退款补偿百分比',
refundGapDays: '(支付后)允许退款的天数',
refundLossRatio: '退款损耗百分比',
refundLossFloor: '退款向下取整位数',
=======
>>>>>>> 99d2876ae88266ffcff6aa944c537941daeab7ce
allowDeposit: '允许主动充值',
allowPay: '允许主动支付',
system: '所属系统',

View File

@ -18,6 +18,7 @@ export interface Schema extends EntityShape {
entity: String<32>;
entityId: String<64>;
timeoutAt?: Datetime;
successAt?: Datetime;
forbidRefundAt?: Datetime;
refundable: Boolean;
deposit?: Deposit;

View File

@ -64,6 +64,7 @@ export const entityDesc = {
order: '所属订单',
deposit: '充值',
timeoutAt: '过期时间',
successAt: '完成时间',
forbidRefundAt: '停止退款时间',
refundable: '是否可退款',
meta: '支付metadata',

View File

@ -2,8 +2,24 @@ import { EntityDesc } from 'oak-domain/lib/types/EntityDesc';
import { Schema as System } from 'oak-general-business/lib/entities/System';
import { Schema as Account } from './Account';
import { Schema as WithdrawAccount } from './WithdrawAccount';
type PayConfig = {
withdrawLoss: {
conservative: boolean;
ratio?: number;
lowest?: number;
highest?: number;
trim?: "jiao" | "yuan";
};
depositLoss: {
ratio?: number;
lowest?: number;
highest?: number;
};
};
export interface Schema extends System {
accounts: Account[];
withdrawAccounts: WithdrawAccount[];
payConfig?: PayConfig;
}
export declare const entityDesc: EntityDesc<Schema>;
export {};

View File

@ -7,6 +7,7 @@ export const entityDesc = {
name: '名称',
description: '描述',
config: '设置',
payConfig: '支付相关设置',
platform: '平台',
super: '超级系统',
folder: '代码目录名',

View File

@ -16,6 +16,4 @@ export interface Schema extends AbstractAccount {
enabled: Boolean;
}
export type Action = 'pay' | 'refund' | 'deposit' | 'withdraw' | 'tax';
export declare const entityDesc: EntityDesc<Schema, Action, '', {
refundLossFloor: NonNullable<Schema['refundLossFloor']>;
}>;
export declare const entityDesc: EntityDesc<Schema, Action>;

View File

@ -5,11 +5,9 @@ export const entityDesc = {
name: '微信支付帐号',
attr: {
wechatPay: '微信支付',
taxlossRatio: '商户号手续费(百分比)',
depositLossRatio: '充值损耗百分比',
taxLossRatio: '商户号手续费(百分比)',
refundCompensateRatio: '退款补偿百分比',
refundGapDays: '(支付后)允许退款的天数',
refundLossRatio: '退款损耗百分比',
refundLossFloor: '退款向下取整位数',
mchId: '商户号',
publicKeyFilePath: '公钥文件路径',
privateKeyFilePath: '私钥文件路径',
@ -19,12 +17,6 @@ export const entityDesc = {
opers: '操作记录',
enabled: '是否启用',
},
v: {
refundLossFloor: {
yuan: '元',
jiao: '角'
},
},
action: {
pay: '支付',
refund: '退款',
@ -35,12 +27,6 @@ export const entityDesc = {
},
},
style: {
color: {
refundLossFloor: {
yuan: '#FFFF00',
jiao: '#00FF00'
}
},
icon: {
pay: '',
refund: '',

View File

@ -7,7 +7,7 @@ import { Schema as Pay } from './Pay';
export interface Schema extends EntityShape {
wpAccount: WpAccount;
type: 'native' | 'mp' | 'jsapi' | 'h5' | 'app';
taxlossRatio?: Decimal<4, 2>;
taxLossRatio?: Decimal<4, 2>;
application: Application;
enabled: Boolean;
pays: Pay[];

View File

@ -8,7 +8,7 @@ export const entityDesc = {
type: '类型',
application: '关联应用',
enabled: '有效中',
taxlossRatio: '产品手续费(百分比)',
taxLossRatio: '产品手续费(百分比)',
pays: '支付',
},
v: {

View File

@ -50,7 +50,7 @@ export default class Pay extends Feature {
}
calcDepositLoss(price, channel) {
const { entity, entityId } = channel;
return getDepositLoss(price, entity, entityId, this.application.getApplication());
return getDepositLoss(price, this.application.getApplication());
}
getDepositRatio(channel) {
throw new Error('method not implemented');

View File

@ -58,7 +58,9 @@
},
"deposit": {
"lossReason": {
"ratio": "按百分比%{value}%扣除"
"ratio": "按百分比%{value}%扣除",
"highest": "按单笔充值最高手续费%{value}扣除",
"lowest": "按单笔充值最低手续费%{value}扣除"
}
}
}

View File

@ -1,22 +1,18 @@
import { Q_DateValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, ExprOp, ExpressionKey } from "oak-domain/lib/types/Demand";
import { Q_DateValue, Q_NumberValue, Q_StringValue, NodeId, MakeFilter, ExprOp, ExpressionKey } from "oak-domain/lib/types/Demand";
import { OneOf } from "oak-domain/lib/types/Polyfill";
import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction, EntityShape } from "oak-domain/lib/types/Entity";
import { Action, ParticularAction } from "./Action";
import { Decimal, Int } from "oak-domain/lib/types/DataType";
export type OpSchema = EntityShape & {
taxlossRatio: Decimal<4, 2>;
depositLossRatio?: Decimal<4, 2> | null;
taxLossRatio: Decimal<4, 2>;
refundGapDays?: Int<4> | null;
refundLossRatio?: Decimal<4, 2> | null;
refundLossFloor?: ("yuan" | "jiao") | null;
refundCompensateRatio?: Int<4> | null;
};
export type OpAttr = keyof OpSchema;
export type Schema = EntityShape & {
taxlossRatio: Decimal<4, 2>;
depositLossRatio?: Decimal<4, 2> | null;
taxLossRatio: Decimal<4, 2>;
refundGapDays?: Int<4> | null;
refundLossRatio?: Decimal<4, 2> | null;
refundLossFloor?: ("yuan" | "jiao") | null;
refundCompensateRatio?: Int<4> | null;
} & {
[A in ExpressionKey]?: any;
};
@ -25,11 +21,9 @@ type AttrFilter = {
$$createAt$$: Q_DateValue;
$$seq$$: Q_NumberValue;
$$updateAt$$: Q_DateValue;
taxlossRatio: Q_NumberValue;
depositLossRatio: Q_NumberValue;
taxLossRatio: Q_NumberValue;
refundGapDays: Q_NumberValue;
refundLossRatio: Q_NumberValue;
refundLossFloor: Q_EnumValue<"yuan" | "jiao">;
refundCompensateRatio: Q_NumberValue;
};
export type Filter = MakeFilter<AttrFilter & ExprOp<OpAttr | string>>;
export type Projection = {
@ -39,11 +33,9 @@ export type Projection = {
$$createAt$$?: number;
$$updateAt$$?: number;
$$seq$$?: number;
taxlossRatio?: number;
depositLossRatio?: number;
taxLossRatio?: number;
refundGapDays?: number;
refundLossRatio?: number;
refundLossFloor?: number;
refundCompensateRatio?: number;
} & Partial<ExprOp<OpAttr | string>>;
type AbstractAccountIdProjection = OneOf<{
id: number;
@ -57,15 +49,11 @@ export type SortAttr = {
} | {
$$updateAt$$: number;
} | {
taxlossRatio: number;
} | {
depositLossRatio: number;
taxLossRatio: number;
} | {
refundGapDays: number;
} | {
refundLossRatio: number;
} | {
refundLossFloor: number;
refundCompensateRatio: number;
} | {
[k: string]: any;
} | OneOf<ExprOp<OpAttr | string>>;

View File

@ -1,7 +1,7 @@
import { actions } from "./Action";
export const desc = {
attributes: {
taxlossRatio: {
taxLossRatio: {
notNull: true,
type: "decimal",
params: {
@ -9,13 +9,6 @@ export const desc = {
scale: 2
}
},
depositLossRatio: {
type: "decimal",
params: {
precision: 4,
scale: 2
}
},
refundGapDays: {
type: "int",
params: {
@ -23,16 +16,12 @@ export const desc = {
signed: true
}
},
refundLossRatio: {
type: "decimal",
refundCompensateRatio: {
type: "int",
params: {
precision: 4,
scale: 2
width: 4,
signed: true
}
},
refundLossFloor: {
type: "enum",
enumeration: ["yuan", "jiao"]
}
},
actionType: "crud",

View File

@ -1,10 +1,4 @@
export const style = {
color: {
refundLossFloor: {
yuan: '#FFFF00',
jiao: '#00FF00'
}
},
icon: {
pay: '',
refund: '',

View File

@ -1 +1 @@
{ "name": "抽象支付帐号", "attr": { "taxlossRatio": "渠道手续费(百分比)", "depositLossRatio": "充值损耗百分比", "refundGapDays": "(支付后)允许退款的天数", "refundLossRatio": "退款损耗百分比", "refundLossFloor": "退款向下取整位数" }, "v": { "refundLossFloor": { "yuan": "元", "jiao": "角" } }, "action": { "pay": "支付", "refund": "退款", "deposit": "充值", "withdraw": "提现", "tax": "渠道费" } }
{ "name": "抽象支付帐号", "attr": { "taxLossRatio": "渠道手续费(百分比)", "refundGapDays": "(支付后)允许退款的天数", "refundCompensateRatio": "退款补偿百分比" }, "action": { "pay": "支付", "refund": "退款", "deposit": "充值", "withdraw": "提现", "tax": "渠道费" } }

View File

@ -155,7 +155,6 @@ export type Sorter = SortNode[];
export type SelectOperation<P extends Object = Projection> = OakSelection<"select", P, Filter, Sorter>;
export type Selection<P extends Object = Projection> = SelectOperation<P>;
export type Aggregation = DeduceAggregation<Projection, Filter, Sorter>;
<<<<<<< HEAD
export type CreateOperationData = FormCreateData<Omit<OpSchema, "entity" | "entityId" | "ofSystemId">> & (({
ofSystemId?: never;
ofSystem: System.CreateSingleOperation;
@ -166,9 +165,6 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "entity" | "enti
ofSystem?: never;
ofSystemId: ForeignKey<"ofSystem">;
})) & ({
=======
export type CreateOperationData = FormCreateData<Omit<OpSchema, "entity" | "entityId" | "ofSystemId">> & ({
>>>>>>> 99d2876ae88266ffcff6aa944c537941daeab7ce
entity?: never;
entityId?: never;
system: System.CreateSingleOperation;
@ -205,7 +201,6 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "entity" | "enti
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
<<<<<<< HEAD
export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "entity" | "entityId" | "ofSystemId">> & (({
ofSystem?: System.CreateSingleOperation;
ofSystemId?: never;
@ -223,13 +218,6 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "entity" | "enti
entityId?: never;
entity?: never;
} | {
=======
export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "entity" | "entityId" | "ofSystemId">> & ({
system?: System.CreateSingleOperation | System.UpdateOperation | System.RemoveOperation;
entityId?: never;
entity?: never;
} | {
>>>>>>> 99d2876ae88266ffcff6aa944c537941daeab7ce
user?: User.CreateSingleOperation | User.UpdateOperation | User.RemoveOperation;
entityId?: never;
entity?: never;
@ -246,13 +234,9 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "entity" | "enti
pay$entity?: OakOperation<Pay.UpdateOperation["action"], Omit<Pay.UpdateOperationData, "entity" | "entityId">, Omit<Pay.Filter, "entity" | "entityId">> | OakOperation<Pay.RemoveOperation["action"], Omit<Pay.RemoveOperationData, "entity" | "entityId">, Omit<Pay.Filter, "entity" | "entityId">> | OakOperation<"create", Omit<Pay.CreateOperationData, "entity" | "entityId">[]> | Array<OakOperation<"create", Omit<Pay.CreateOperationData, "entity" | "entityId">> | OakOperation<Pay.UpdateOperation["action"], Omit<Pay.UpdateOperationData, "entity" | "entityId">, Omit<Pay.Filter, "entity" | "entityId">> | OakOperation<Pay.RemoveOperation["action"], Omit<Pay.RemoveOperationData, "entity" | "entityId">, Omit<Pay.Filter, "entity" | "entityId">>>;
};
export type UpdateOperation = OakOperation<"update" | ParticularAction | string, UpdateOperationData, Filter, Sorter>;
<<<<<<< HEAD
export type RemoveOperationData = {} & (({
ofSystem?: System.UpdateOperation | System.RemoveOperation;
})) & ({
=======
export type RemoveOperationData = {} & ({
>>>>>>> 99d2876ae88266ffcff6aa944c537941daeab7ce
system?: System.UpdateOperation | System.RemoveOperation;
} | {
user?: User.UpdateOperation | User.RemoveOperation;

View File

@ -8,7 +8,7 @@ import * as Account from "../Account/Schema";
import * as Deposit from "../Deposit/Schema";
import * as Pay from "../Pay/Schema";
import * as Withdraw from "../Withdraw/Schema";
type Type = "deposit" | "withdraw" | "consume" | "loan" | "repay" | "withdrawBack" | "earn" | "encash" | "cutoffRefundable";
type Type = "deposit" | "withdraw" | "consume" | "loan" | "repay" | "withdrawBack" | "earn" | "encash" | "cutoffRefundable" | "tax";
export type OpSchema = EntityShape & {
accountId: ForeignKey<"account">;
type: Type;

View File

@ -9,7 +9,7 @@ export const desc = {
type: {
notNull: true,
type: "enum",
enumeration: ["deposit", "withdraw", "consume", "loan", "repay", "withdrawBack", "earn", "encash", "cutoffRefundable"]
enumeration: ["deposit", "withdraw", "consume", "loan", "repay", "withdrawBack", "earn", "encash", "cutoffRefundable", "tax"]
},
totalPlus: {
notNull: true,

View File

@ -9,6 +9,7 @@ export const style = {
repay: '#82E0AA',
earn: '#FF3333',
encash: '#FF3399',
tax: '#A569BD',
cutoffRefundable: '#2E4053',
}
}

View File

@ -1 +1 @@
{ "name": "帐号操作", "attr": { "account": "帐号", "type": "类型", "totalPlus": "余额变化", "availPlus": "可用余额变化", "refundablePlus": "可退款余额变化", "entity": "关联对象", "entityId": "关联对象Id" }, "v": { "type": { "deposit": "充值", "withdraw": "提现", "consume": "消费", "loan": "抵押", "repay": "偿还", "withdrawBack": "提现失败", "earn": "赚取", "encash": "兑现", "cutoffRefundable": "削减可自由退额度" } } }
{ "name": "帐号操作", "attr": { "account": "帐号", "type": "类型", "totalPlus": "余额变化", "availPlus": "可用余额变化", "refundablePlus": "可退款余额变化", "entity": "关联对象", "entityId": "关联对象Id" }, "v": { "type": { "deposit": "充值", "withdraw": "提现", "consume": "消费", "loan": "抵押", "repay": "偿还", "withdrawBack": "提现失败", "earn": "赚取", "encash": "兑现", "tax": "渠道费", "cutoffRefundable": "削减可自由退额度" } } }

View File

@ -3,24 +3,16 @@ import { Q_DateValue, Q_BooleanValue, Q_NumberValue, Q_StringValue, Q_EnumValue,
import { OneOf } from "oak-domain/lib/types/Polyfill";
import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction, AggregationResult, EntityShape } from "oak-domain/lib/types/Entity";
import { Action, ParticularAction } from "./Action";
<<<<<<< HEAD
import { Decimal, Int, String, Text, Boolean, Price } from "oak-domain/lib/types/DataType";
=======
import { String, Decimal, Text, Boolean, Price } from "oak-domain/lib/types/DataType";
>>>>>>> 99d2876ae88266ffcff6aa944c537941daeab7ce
import * as System from "../System/Schema";
import * as Pay from "../Pay/Schema";
import * as SysAccountOper from "../SysAccountOper/Schema";
export type OpSchema = EntityShape & {
taxlossRatio: Decimal<4, 2>;
depositLossRatio?: Decimal<4, 2> | null;
taxLossRatio: Decimal<4, 2>;
refundGapDays?: Int<4> | null;
refundLossRatio?: Decimal<4, 2> | null;
refundLossFloor?: ("yuan" | "jiao") | null;
refundCompensateRatio?: Int<4> | null;
type: "bank" | "alipay" | "wechat" | "shouqianba" | "others";
channel?: String<32> | null;
taxlossRatio?: Decimal<4, 2> | null;
depositLossRatio?: Decimal<4, 2> | null;
name?: String<64> | null;
qrCode?: Text | null;
allowDeposit: Boolean;
@ -31,15 +23,11 @@ export type OpSchema = EntityShape & {
};
export type OpAttr = keyof OpSchema;
export type Schema = EntityShape & {
taxlossRatio: Decimal<4, 2>;
depositLossRatio?: Decimal<4, 2> | null;
taxLossRatio: Decimal<4, 2>;
refundGapDays?: Int<4> | null;
refundLossRatio?: Decimal<4, 2> | null;
refundLossFloor?: ("yuan" | "jiao") | null;
refundCompensateRatio?: Int<4> | null;
type: "bank" | "alipay" | "wechat" | "shouqianba" | "others";
channel?: String<32> | null;
taxlossRatio?: Decimal<4, 2> | null;
depositLossRatio?: Decimal<4, 2> | null;
name?: String<64> | null;
qrCode?: Text | null;
allowDeposit: Boolean;
@ -60,15 +48,11 @@ type AttrFilter = {
$$createAt$$: Q_DateValue;
$$seq$$: Q_NumberValue;
$$updateAt$$: Q_DateValue;
taxlossRatio: Q_NumberValue;
depositLossRatio: Q_NumberValue;
taxLossRatio: Q_NumberValue;
refundGapDays: Q_NumberValue;
refundLossRatio: Q_NumberValue;
refundLossFloor: Q_EnumValue<"yuan" | "jiao">;
refundCompensateRatio: Q_NumberValue;
type: Q_EnumValue<"bank" | "alipay" | "wechat" | "shouqianba" | "others">;
channel: Q_StringValue;
taxlossRatio: Q_NumberValue;
depositLossRatio: Q_NumberValue;
name: Q_StringValue;
qrCode: Q_StringValue;
allowDeposit: Q_BooleanValue;
@ -88,15 +72,11 @@ export type Projection = {
$$createAt$$?: number;
$$updateAt$$?: number;
$$seq$$?: number;
taxlossRatio?: number;
depositLossRatio?: number;
taxLossRatio?: number;
refundGapDays?: number;
refundLossRatio?: number;
refundLossFloor?: number;
refundCompensateRatio?: number;
type?: number;
channel?: number;
taxlossRatio?: number;
depositLossRatio?: number;
name?: number;
qrCode?: number;
allowDeposit?: number;
@ -133,23 +113,15 @@ export type SortAttr = {
} | {
$$updateAt$$: number;
} | {
taxlossRatio: number;
} | {
depositLossRatio: number;
taxLossRatio: number;
} | {
refundGapDays: number;
} | {
refundLossRatio: number;
} | {
refundLossFloor: number;
refundCompensateRatio: number;
} | {
type: number;
} | {
channel: number;
} | {
taxlossRatio: number;
} | {
depositLossRatio: number;
} | {
name: number;
} | {

View File

@ -1,7 +1,7 @@
import { actions } from "./Action";
export const desc = {
attributes: {
taxlossRatio: {
taxLossRatio: {
notNull: true,
type: "decimal",
params: {
@ -9,13 +9,6 @@ export const desc = {
scale: 2
}
},
depositLossRatio: {
type: "decimal",
params: {
precision: 4,
scale: 2
}
},
refundGapDays: {
type: "int",
params: {
@ -23,17 +16,13 @@ export const desc = {
signed: true
}
},
refundLossRatio: {
type: "decimal",
refundCompensateRatio: {
type: "int",
params: {
precision: 4,
scale: 2
width: 4,
signed: true
}
},
refundLossFloor: {
type: "enum",
enumeration: ["yuan", "jiao"]
},
type: {
notNull: true,
type: "enum",

View File

@ -1 +1 @@
{ "name": "线下账户", "attr": { "type": "类型", "channel": "通道", "name": "用户/帐号", "qrCode": "收款二维码", "taxlossRatio": "商户号手续费(百分比)", "depositLossRatio": "充值损耗百分比", "refundGapDays": "(支付后)允许退款的天数", "refundLossRatio": "退款损耗百分比", "refundLossFloor": "退款向下取整位数", "allowDeposit": "允许主动充值", "allowPay": "允许主动支付", "system": "所属系统", "price": "余额", "pays": "支付", "opers": "操作记录", "enabled": "是否启用" }, "v": { "type": { "bank": "银行", "alipay": "支付宝", "wechat": "微信", "shouqianba": "收钱吧", "others": "其它" } }, "action": { "pay": "支付", "refund": "退款", "deposit": "充值", "withdraw": "提现", "tax": "渠道费" } }
{ "name": "线下账户", "attr": { "type": "类型", "channel": "通道", "name": "用户/帐号", "qrCode": "收款二维码", "taxLossRatio": "商户号手续费(百分比)", "refundCompensateRatio": "退款补偿百分比", "refundGapDays": "(支付后)允许退款的天数", "allowDeposit": "允许主动充值", "allowPay": "允许主动支付", "system": "所属系统", "price": "余额", "pays": "支付", "opers": "操作记录", "enabled": "是否启用" }, "v": { "type": { "bank": "银行", "alipay": "支付宝", "wechat": "微信", "shouqianba": "收钱吧", "others": "其它" } }, "action": { "pay": "支付", "refund": "退款", "deposit": "充值", "withdraw": "提现", "tax": "渠道费" } }

View File

@ -21,6 +21,7 @@ export type OpSchema = EntityShape & {
entity: "account" | "offlineAccount" | "wpProduct" | string;
entityId: String<64>;
timeoutAt?: Datetime | null;
successAt?: Datetime | null;
forbidRefundAt?: Datetime | null;
refundable: Boolean;
depositId?: ForeignKey<"deposit"> | null;
@ -43,6 +44,7 @@ export type Schema = EntityShape & {
entity: "account" | "offlineAccount" | "wpProduct" | string;
entityId: String<64>;
timeoutAt?: Datetime | null;
successAt?: Datetime | null;
forbidRefundAt?: Datetime | null;
refundable: Boolean;
depositId?: ForeignKey<"deposit"> | null;
@ -83,6 +85,7 @@ type AttrFilter = {
entity: Q_EnumValue<"account" | "offlineAccount" | "wpProduct" | string>;
entityId: Q_StringValue;
timeoutAt: Q_DateValue;
successAt: Q_DateValue;
forbidRefundAt: Q_DateValue;
refundable: Q_BooleanValue;
depositId: Q_StringValue;
@ -121,6 +124,7 @@ export type Projection = {
entity?: number;
entityId?: number;
timeoutAt?: number;
successAt?: number;
forbidRefundAt?: number;
refundable?: number;
depositId?: number;
@ -204,6 +208,8 @@ export type SortAttr = {
entityId: number;
} | {
timeoutAt: number;
} | {
successAt: number;
} | {
forbidRefundAt: number;
} | {

View File

@ -31,6 +31,9 @@ export const desc = {
timeoutAt: {
type: "datetime"
},
successAt: {
type: "datetime"
},
forbidRefundAt: {
type: "datetime"
},

View File

@ -1 +1 @@
{ "name": "订单", "attr": { "price": "应支付金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "entity": "支付渠道", "entityId": "支付渠道id", "order": "所属订单", "deposit": "充值", "timeoutAt": "过期时间", "forbidRefundAt": "停止退款时间", "refundable": "是否可退款", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "continuePaying": "继续支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款", "closeRefund": "禁止退款", "stopRefunding": "停止退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } }
{ "name": "订单", "attr": { "price": "应支付金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "entity": "支付渠道", "entityId": "支付渠道id", "order": "所属订单", "deposit": "充值", "timeoutAt": "过期时间", "successAt": "完成时间", "forbidRefundAt": "停止退款时间", "refundable": "是否可退款", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "continuePaying": "继续支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款", "closeRefund": "禁止退款", "stopRefunding": "停止退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } }

View File

@ -18,6 +18,20 @@ import * as WechatPay from "../WechatPay/Schema";
import * as WithdrawAccount from "../WithdrawAccount/Schema";
import * as WithdrawChannel from "../WithdrawChannel/Schema";
import * as WpAccount from "../WpAccount/Schema";
type PayConfig = {
withdrawLoss: {
conservative: boolean;
ratio?: number;
lowest?: number;
highest?: number;
trim?: "jiao" | "yuan";
};
depositLoss: {
ratio?: number;
lowest?: number;
highest?: number;
};
};
export type OpSchema = EntityShape & {
name: String<32>;
description: Text;
@ -28,6 +42,7 @@ export type OpSchema = EntityShape & {
style?: Style | null;
entity?: String<32> | null;
entityId?: String<64> | null;
payConfig?: PayConfig | null;
};
export type OpAttr = keyof OpSchema;
export type Schema = EntityShape & {
@ -40,6 +55,7 @@ export type Schema = EntityShape & {
style?: Style | null;
entity?: String<32> | null;
entityId?: String<64> | null;
payConfig?: PayConfig | null;
platform?: Platform.Schema | null;
application$system?: Array<Application.Schema>;
application$system$$aggr?: AggregationResult<Application.Schema>;
@ -85,6 +101,7 @@ type AttrFilter = {
style: JsonFilter<Style>;
entity: Q_StringValue;
entityId: Q_StringValue;
payConfig: JsonFilter<PayConfig>;
application$system: Application.Filter & SubQueryPredicateMetadata;
domain$system: Domain.Filter & SubQueryPredicateMetadata;
messageSystem$system: MessageSystem.Filter & SubQueryPredicateMetadata;
@ -117,6 +134,7 @@ export type Projection = {
style?: number | JsonProjection<Style>;
entity?: number;
entityId?: number;
payConfig?: number | JsonProjection<PayConfig>;
application$system?: Application.Selection & {
$entity: "application";
};
@ -230,6 +248,8 @@ export type SortAttr = {
entity: number;
} | {
entityId: number;
} | {
payConfig: number;
} | {
[k: string]: any;
} | OneOf<ExprOp<OpAttr | string>>;

View File

@ -30,7 +30,8 @@ export const desc = {
super: {
type: "boolean"
},
style: {
style // 如果为true则按照渠道taxLossRatio和refundCompensateRatio进行扣取(如果因为depositLossRatio已经大于taxLossRatio则全额退款)
: {
type: "object"
},
entity: {
@ -44,6 +45,9 @@ export const desc = {
params: {
length: 64
}
},
payConfig: {
type: "object"
}
},
actionType: "crud",

View File

@ -1 +1 @@
{ "name": "系统", "attr": { "name": "名称", "description": "描述", "config": "设置", "platform": "平台", "super": "超级系统", "folder": "代码目录名", "style": "样式", "entity": "关联对象", "entityId": "关联对象id", "accounts": "关联账户", "withdrawAccounts": "关联提现账户" } }
{ "name": "系统", "attr": { "name": "名称", "description": "描述", "config": "设置", "payConfig": "支付相关设置", "platform": "平台", "super": "超级系统", "folder": "代码目录名", "style": "样式", "entity": "关联对象", "entityId": "关联对象id", "accounts": "关联账户", "withdrawAccounts": "关联提现账户" } }

View File

@ -164,7 +164,6 @@ export type SelectOperation<P extends Object = Projection> = OakSelection<"selec
export type Selection<P extends Object = Projection> = SelectOperation<P>;
export type Aggregation = DeduceAggregation<Projection, Filter, Sorter>;
export type CreateOperationData = FormCreateData<Omit<OpSchema, "entity" | "entityId" | "ofSystemId" | "channelId">> & (({
<<<<<<< HEAD
ofSystemId?: never;
ofSystem: System.CreateSingleOperation;
} | {
@ -174,8 +173,6 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "entity" | "enti
ofSystem?: never;
ofSystemId: ForeignKey<"ofSystem">;
}) & ({
=======
>>>>>>> 99d2876ae88266ffcff6aa944c537941daeab7ce
channelId?: never;
channel: WithdrawChannel.CreateSingleOperation;
} | {
@ -221,7 +218,6 @@ export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "entity" | "entityId" | "ofSystemId" | "channelId">> & (({
<<<<<<< HEAD
ofSystem?: System.CreateSingleOperation;
ofSystemId?: never;
} | {
@ -234,8 +230,6 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "entity" | "enti
ofSystem?: never;
ofSystemId?: ForeignKey<"ofSystem">;
}) & ({
=======
>>>>>>> 99d2876ae88266ffcff6aa944c537941daeab7ce
channel?: WithdrawChannel.CreateSingleOperation;
channelId?: never;
} | {

View File

@ -1,5 +1,5 @@
import { ForeignKey } from "oak-domain/lib/types/DataType";
import { Q_DateValue, Q_BooleanValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, ExprOp, ExpressionKey, SubQueryPredicateMetadata } from "oak-domain/lib/types/Demand";
import { Q_DateValue, Q_BooleanValue, Q_NumberValue, Q_StringValue, NodeId, MakeFilter, ExprOp, ExpressionKey, SubQueryPredicateMetadata } from "oak-domain/lib/types/Demand";
import { OneOf } from "oak-domain/lib/types/Polyfill";
import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction, AggregationResult, EntityShape } from "oak-domain/lib/types/Entity";
import { Action, ParticularAction } from "./Action";
@ -11,11 +11,9 @@ import * as ModiEntity from "../ModiEntity/Schema";
import * as OperEntity from "../OperEntity/Schema";
import * as SysAccountOper from "../SysAccountOper/Schema";
export type OpSchema = EntityShape & {
taxlossRatio: Decimal<4, 2>;
depositLossRatio?: Decimal<4, 2> | null;
taxLossRatio: Decimal<4, 2>;
refundGapDays?: Int<4> | null;
refundLossRatio?: Decimal<4, 2> | null;
refundLossFloor?: ("yuan" | "jiao") | null;
refundCompensateRatio?: Int<4> | null;
wechatPayId: ForeignKey<"wechatPay">;
mchId: String<128>;
publicKeyFilePath: Text;
@ -27,11 +25,9 @@ export type OpSchema = EntityShape & {
};
export type OpAttr = keyof OpSchema;
export type Schema = EntityShape & {
taxlossRatio: Decimal<4, 2>;
depositLossRatio?: Decimal<4, 2> | null;
taxLossRatio: Decimal<4, 2>;
refundGapDays?: Int<4> | null;
refundLossRatio?: Decimal<4, 2> | null;
refundLossFloor?: ("yuan" | "jiao") | null;
refundCompensateRatio?: Int<4> | null;
wechatPayId: ForeignKey<"wechatPay">;
mchId: String<128>;
publicKeyFilePath: Text;
@ -58,11 +54,9 @@ type AttrFilter = {
$$createAt$$: Q_DateValue;
$$seq$$: Q_NumberValue;
$$updateAt$$: Q_DateValue;
taxlossRatio: Q_NumberValue;
depositLossRatio: Q_NumberValue;
taxLossRatio: Q_NumberValue;
refundGapDays: Q_NumberValue;
refundLossRatio: Q_NumberValue;
refundLossFloor: Q_EnumValue<"yuan" | "jiao">;
refundCompensateRatio: Q_NumberValue;
wechatPayId: Q_StringValue;
wechatPay: WechatPay.Filter;
mchId: Q_StringValue;
@ -86,11 +80,9 @@ export type Projection = {
$$createAt$$?: number;
$$updateAt$$?: number;
$$seq$$?: number;
taxlossRatio?: number;
depositLossRatio?: number;
taxLossRatio?: number;
refundGapDays?: number;
refundLossRatio?: number;
refundLossFloor?: number;
refundCompensateRatio?: number;
wechatPayId?: number;
wechatPay?: WechatPay.Projection;
mchId?: number;
@ -144,15 +136,11 @@ export type SortAttr = {
} | {
$$updateAt$$: number;
} | {
taxlossRatio: number;
} | {
depositLossRatio: number;
taxLossRatio: number;
} | {
refundGapDays: number;
} | {
refundLossRatio: number;
} | {
refundLossFloor: number;
refundCompensateRatio: number;
} | {
wechatPayId: number;
} | {

View File

@ -1,7 +1,7 @@
import { actions } from "./Action";
export const desc = {
attributes: {
taxlossRatio: {
taxLossRatio: {
notNull: true,
type: "decimal",
params: {
@ -9,13 +9,6 @@ export const desc = {
scale: 2
}
},
depositLossRatio: {
type: "decimal",
params: {
precision: 4,
scale: 2
}
},
refundGapDays: {
type: "int",
params: {
@ -23,17 +16,13 @@ export const desc = {
signed: true
}
},
refundLossRatio: {
type: "decimal",
refundCompensateRatio: {
type: "int",
params: {
precision: 4,
scale: 2
width: 4,
signed: true
}
},
refundLossFloor: {
type: "enum",
enumeration: ["yuan", "jiao"]
},
wechatPayId: {
notNull: true,
type: "ref",

View File

@ -1,10 +1,4 @@
export const style = {
color: {
refundLossFloor: {
yuan: '#FFFF00',
jiao: '#00FF00'
}
},
icon: {
pay: '',
refund: '',

View File

@ -1 +1 @@
{ "name": "微信支付帐号", "attr": { "wechatPay": "微信支付", "taxlossRatio": "商户号手续费(百分比)", "depositLossRatio": "充值损耗百分比", "refundGapDays": "(支付后)允许退款的天数", "refundLossRatio": "退款损耗百分比", "refundLossFloor": "退款向下取整位数", "mchId": "商户号", "publicKeyFilePath": "公钥文件路径", "privateKeyFilePath": "私钥文件路径", "apiV3Key": "apiV3Key", "price": "余额", "system": "关联系统", "opers": "操作记录", "enabled": "是否启用" }, "v": { "refundLossFloor": { "yuan": "元", "jiao": "角" } }, "action": { "pay": "支付", "refund": "退款", "deposit": "充值", "withdraw": "提现", "tax": "渠道费" } }
{ "name": "微信支付帐号", "attr": { "wechatPay": "微信支付", "taxLossRatio": "商户号手续费(百分比)", "refundCompensateRatio": "退款补偿百分比", "refundGapDays": "(支付后)允许退款的天数", "mchId": "商户号", "publicKeyFilePath": "公钥文件路径", "privateKeyFilePath": "私钥文件路径", "apiV3Key": "apiV3Key", "price": "余额", "system": "关联系统", "opers": "操作记录", "enabled": "是否启用" }, "action": { "pay": "支付", "refund": "退款", "deposit": "充值", "withdraw": "提现", "tax": "渠道费" } }

View File

@ -12,7 +12,7 @@ import * as Pay from "../Pay/Schema";
export type OpSchema = EntityShape & {
wpAccountId: ForeignKey<"wpAccount">;
type: "native" | "mp" | "jsapi" | "h5" | "app";
taxlossRatio?: Decimal<4, 2> | null;
taxLossRatio?: Decimal<4, 2> | null;
applicationId: ForeignKey<"application">;
enabled: Boolean;
};
@ -20,7 +20,7 @@ export type OpAttr = keyof OpSchema;
export type Schema = EntityShape & {
wpAccountId: ForeignKey<"wpAccount">;
type: "native" | "mp" | "jsapi" | "h5" | "app";
taxlossRatio?: Decimal<4, 2> | null;
taxLossRatio?: Decimal<4, 2> | null;
applicationId: ForeignKey<"application">;
enabled: Boolean;
wpAccount: WpAccount.Schema;
@ -42,7 +42,7 @@ type AttrFilter = {
wpAccountId: Q_StringValue;
wpAccount: WpAccount.Filter;
type: Q_EnumValue<"native" | "mp" | "jsapi" | "h5" | "app">;
taxlossRatio: Q_NumberValue;
taxLossRatio: Q_NumberValue;
applicationId: Q_StringValue;
application: Application.Filter;
enabled: Q_BooleanValue;
@ -61,7 +61,7 @@ export type Projection = {
wpAccountId?: number;
wpAccount?: WpAccount.Projection;
type?: number;
taxlossRatio?: number;
taxLossRatio?: number;
applicationId?: number;
application?: Application.Projection;
enabled?: number;
@ -108,7 +108,7 @@ export type SortAttr = {
} | {
type: number;
} | {
taxlossRatio: number;
taxLossRatio: number;
} | {
applicationId: number;
} | {

View File

@ -11,7 +11,7 @@ export const desc = {
type: "enum",
enumeration: ["native", "mp", "jsapi", "h5", "app"]
},
taxlossRatio: {
taxLossRatio: {
type: "decimal",
params: {
precision: 4,

View File

@ -1 +1 @@
{ "name": "微信支付产品", "attr": { "wpAccount": "微信支付帐号", "type": "类型", "application": "关联应用", "enabled": "有效中", "taxlossRatio": "产品手续费(百分比)", "pays": "支付" }, "v": { "type": { "native": "扫码", "mp": "小程序", "jsapi": "jsApi", "h5": "H5唤起微信", "app": "App" } } }
{ "name": "微信支付产品", "attr": { "wpAccount": "微信支付帐号", "type": "类型", "application": "关联应用", "enabled": "有效中", "taxLossRatio": "产品手续费(百分比)", "pays": "支付" }, "v": { "type": { "native": "扫码", "mp": "小程序", "jsapi": "jsApi", "h5": "H5唤起微信", "app": "App" } } }

View File

@ -2,10 +2,10 @@ import { Trigger } from 'oak-domain/lib/types/Trigger';
import { EntityDict } from '../oak-app-domain';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import { BRC } from '../types/RuntimeCxt';
type CalcPaySysAccountChangeOption<ED extends EntityDict & BaseEntityDict> = {
type CalcPayChannelTaxOption<ED extends EntityDict & BaseEntityDict> = {
projection: ED['pay']['Selection']['data'];
fn: (pay: ED['pay']['Schema']) => [number, string, string];
};
export declare function registerCalcPaySysAccountChangeOption<ED extends EntityDict & BaseEntityDict>(entity: keyof ED, option: CalcPaySysAccountChangeOption<ED>): void;
export declare function registerCalcPayChannelTaxOption<ED extends EntityDict & BaseEntityDict>(entity: keyof ED, option: CalcPayChannelTaxOption<ED>): void;
declare const triggers: Trigger<EntityDict, 'pay', BRC>[];
export default triggers;

View File

@ -4,44 +4,42 @@ import { getPayClazz } from '../utils/payClazz';
import { fullPayProjection } from '../utils/pay';
import { DATA_SUBSCRIBER_KEYS } from '../config/constants';
import { merge } from 'oak-domain/lib/utils/lodash';
const CalcPaySysAccountChangeDict = {
const CalcPayChannelTaxDict = {
wpProduct: {
projection: {
wpProduct: {
id: 1,
taxlossRatio: 1,
taxLossRatio: 1,
wpAccountId: 1,
wpAccount: {
id: 1,
taxlossRatio: 1,
taxLossRatio: 1,
},
},
},
fn: (pay) => {
const { paid, wpProduct } = pay;
const taxlossRatio = wpProduct.taxlossRatio || wpProduct.wpAccount.taxlossRatio;
assert(typeof taxlossRatio === 'number');
const tax = Math.round(paid * taxlossRatio / 100);
return [paid - tax, 'wpAccount', wpProduct.wpAccountId];
const taxLossRatio = wpProduct.taxLossRatio || wpProduct.wpAccount.taxLossRatio;
assert(typeof taxLossRatio === 'number', '微信渠道的手续费率未配置');
return [Math.round(paid * taxLossRatio / 100), 'wpAccount', wpProduct.wpAccountId];
},
},
offlineAccount: {
projection: {
offlineAccount: {
id: 1,
taxlossRatio: 1,
taxLossRatio: 1,
},
},
fn: (pay) => {
const { offlineAccount, paid } = pay;
const { taxlossRatio } = offlineAccount;
const tax = taxlossRatio ? Math.round(paid * taxlossRatio / 100) : 0;
return [paid - tax, pay.entity, pay.entityId];
const { taxLossRatio } = offlineAccount;
return [taxLossRatio ? Math.round(paid * taxLossRatio / 100) : 0, pay.entity, pay.entityId];
},
},
};
export function registerCalcPaySysAccountChangeOption(entity, option) {
CalcPaySysAccountChangeDict[entity] = option;
export function registerCalcPayChannelTaxOption(entity, option) {
CalcPayChannelTaxDict[entity] = option;
}
async function changeOrderStateByPay(filter, context, option) {
const orders = await context.select('order', {
@ -227,11 +225,14 @@ const triggers = [
price: 1,
loss: 1,
},
application: {
systemId: 1,
},
iState: 1,
},
filter,
}, { dontCollect: true });
const { orderId, depositId, iState, deposit } = pay;
const { orderId, depositId, iState, deposit, application } = pay;
context.saveOperationToEvent(id, `${DATA_SUBSCRIBER_KEYS.payStateChanged}-${filter.id}`);
if (orderId) {
return await changeOrderStateByPay({ id: orderId }, context, option);
@ -243,24 +244,48 @@ const triggers = [
const payPrice = pay.price;
const { price, loss } = deposit;
assert(price === payPrice);
const accountOpers = [
{
id: await generateNewIdAsync(),
data: {
id: await generateNewIdAsync(),
totalPlus: price - loss,
availPlus: price - loss,
refundablePlus: pay.refundable ? price : 0,
accountId: deposit.accountId,
type: 'deposit',
},
action: 'create',
}
];
if (loss > 0) {
// 如果有loss就充入system的account账户
const [account] = await context.select('account', {
data: {
id: 1,
},
filter: {
entity: 'system',
entityId: application?.systemId,
},
}, { dontCollect: true });
accountOpers.push({
id: await generateNewIdAsync(),
data: {
id: await generateNewIdAsync(),
totalPlus: loss,
availPlus: loss,
accountId: account.id,
type: 'earn',
},
action: 'create',
});
}
await context.operate('deposit', {
id: await generateNewIdAsync(),
action: 'succeed',
data: {
accountOper$entity: [
{
id: await generateNewIdAsync(),
data: {
id: await generateNewIdAsync(),
totalPlus: price - loss,
availPlus: price - loss,
refundablePlus: pay.refundable ? price : 0,
accountId: deposit.accountId,
type: 'deposit',
},
action: 'create',
}
]
accountOper$entity: accountOpers,
},
filter: {
id: depositId,
@ -381,7 +406,7 @@ const triggers = [
},
},
{
name: '当pay完成支付时修改相应的sys account中的余额',
name: '当pay完成支付时计算相应的account以及syste account中的余额变化',
entity: 'pay',
action: 'succeedPaying',
when: 'after',
@ -395,9 +420,13 @@ const triggers = [
entityId: 1,
iState: 1,
depositId: 1,
orderId: 1,
application: {
systemId: 1,
}
};
for (const key in CalcPaySysAccountChangeDict) {
merge(projection, CalcPaySysAccountChangeDict[key].projection);
for (const key in CalcPayChannelTaxDict) {
merge(projection, CalcPayChannelTaxDict[key].projection);
}
const pays = await context.select('pay', {
data: projection,
@ -405,33 +434,56 @@ const triggers = [
}, {});
assert(pays.length === 1);
const [pay] = pays;
const { price, paid, entity, entityId, iState, depositId } = pay;
const { price, paid, entity, entityId, iState, application } = pay;
assert(iState === 'paid');
const option = CalcPaySysAccountChangeDict[entity];
let cnt = 0;
const option = CalcPayChannelTaxDict[entity];
if (option) {
const [delta, sysAccountEntity, sysAccountEntityId] = option.fn(pay);
if (delta !== 0) {
assert(delta > 0);
await context.operate('sysAccountOper', {
const [tax, sysAccountEntity, sysAccountEntityId] = option.fn(pay);
await context.operate('sysAccountOper', {
id: await generateNewIdAsync(),
action: 'create',
data: {
id: await generateNewIdAsync(),
delta: paid - tax,
entity: sysAccountEntity,
entityId: sysAccountEntityId,
payId: pay.id,
type: 'pay',
}
}, {});
if (tax !== 0) {
// tax产生的损失由sys account来承担
const [account] = await context.select('account', {
data: {
id: 1,
},
filter: {
entity: 'system',
entityId: application.systemId,
}
}, { dontCollect: true });
await context.operate('accountOper', {
id: await generateNewIdAsync(),
action: 'create',
data: {
id: await generateNewIdAsync(),
delta,
entity: sysAccountEntity,
entityId: sysAccountEntityId,
payId: pay.id,
type: 'pay',
}
accountId: account.id,
type: 'tax',
totalPlus: -tax,
availPlus: -tax,
entity: 'pay',
entityId: pay.id,
},
}, {});
return 1;
}
cnt++;
}
return 0;
return cnt;
},
},
{
name: '当pay的paid达到price支付成功',
name: '当account类型的pay的paid达到price改为支付成功',
entity: 'pay',
action: ['startPaying', 'continuePaying'],
check(operation) {
@ -461,7 +513,9 @@ const triggers = [
await context.operate('pay', {
id: await generateNewIdAsync(),
action: 'succeedPaying',
data: {},
data: {
successAt: Date.now(),
},
filter: {
id,
}
@ -471,5 +525,32 @@ const triggers = [
}
}
},
{
name: '当pay完成支付时计算其refundable和forbidRefundAt',
entity: 'pay',
action: 'succeedPaying',
when: 'before',
fn: async ({ operation }, context) => {
const { data, filter } = operation;
assert(data.successAt);
const pays = await context.select('pay', {
data: {
id: 1,
entity: 1,
entityId: 1,
applicationId: 1,
},
filter
}, { dontCollect: true, forUpdate: true });
assert(pays.length === 1);
const [pay] = pays;
const { applicationId, entity, entityId } = pay;
const payClazz = await getPayClazz(applicationId, entity, entityId, context);
const forbidRefundAt = payClazz.getRefundableAt(data.successAt);
data.forbidRefundAt = forbidRefundAt;
data.refundable = forbidRefundAt > Date.now();
return 1;
},
},
];
export default triggers;

View File

@ -7,22 +7,22 @@ const CalcRefundSysAccountChangeDict = {
projection: {
wpProduct: {
id: 1,
taxlossRatio: 1,
taxLossRatio: 1,
wpAccountId: 1,
wpAccount: {
id: 1,
taxlossRatio: 1,
taxLossRatio: 1,
},
},
},
fn: (refund) => {
const { price, pay, loss } = refund;
const { wpProduct } = pay;
const taxlossRatio = wpProduct.taxlossRatio || wpProduct.wpAccount.taxlossRatio;
assert(typeof taxlossRatio === 'number');
const taxLossRatio = wpProduct.taxLossRatio || wpProduct.wpAccount.taxLossRatio;
assert(typeof taxLossRatio === 'number');
// 微信支付的退款部分不计手续费(这里计算的不够精细,直接四舍五入可能会产生误差积累)
const toRefund = price - loss;
const tax = Math.round(toRefund * taxlossRatio / 100);
const tax = Math.round(toRefund * taxLossRatio / 100);
const price2 = toRefund - tax;
return [-price2, 'wpAccount', wpProduct.wpAccountId];
},

View File

@ -13,6 +13,7 @@ export default interface PayClazz {
}>;
refund(refund: EntityDict['refund']['OpSchema']): Promise<EntityDict['refund']['Update']['data'] | undefined>;
closeRefund(refund: EntityDict['refund']['OpSchema']): Promise<void>;
getRefundableAt(successAt: number): number;
getRefundState(refund: EntityDict['refund']['OpSchema']): Promise<[EntityDict['refund']['OpSchema']['iState'], EntityDict['refund']['Update']['data'] | undefined]>;
}
export {};

View File

@ -11,15 +11,13 @@ export const applicationProjection = {
type: 1,
wpAccount: {
id: 1,
depositLossRatio: 1,
refundGapDays: 1,
refundLossFloor: 1,
refundLossRatio: 1,
mchId: 1,
}
},
},
system: {
payConfig: 1,
offlineAccount$system: {
$entity: 'offlineAccount',
data: {
@ -28,13 +26,16 @@ export const applicationProjection = {
name: 1,
type: 1,
allowDeposit: 1,
depositLossRatio: 1,
refundGapDays: 1,
refundLossFloor: 1,
refundLossRatio: 1,
allowPay: 1,
}
}
},
account$entity: {
$entity: 'account',
data: {
id: 1,
},
},
}
};
export const mergedProjection = merge({}, ogbAppProjection, applicationProjection);

6
es/utils/pay.d.ts vendored
View File

@ -6,15 +6,13 @@ import { IncomingHttpHeaders } from 'http';
export declare const fullPayProjection: EntityDict['pay']['Selection']['data'];
export declare function payNotify<ED extends EntityDict & BaseEntityDict>(context: BackendRuntimeContext<ED>, body: any, payId: string, headers: IncomingHttpHeaders): Promise<void>;
type CalcLoss = <ED extends EntityDict & BaseEntityDict>(entityId: string, application: ED['application']['Schema'], price: number) => [number, string, any];
export declare function registerCalcRefundLoss<ED extends EntityDict & BaseEntityDict>(entity: string, projection: ED['application']['Selection']['data'], fn: CalcLoss): void;
export declare function registerCalcDepositLoss<ED extends EntityDict & BaseEntityDict>(entity: string, projection: ED['application']['Selection']['data'], fn: CalcLoss): void;
/**
*
* attention: 这个函数目前是在前端调用的offlineAccount的信息前端能取的到
* attention: 这个函数目前是在前端调用的
* @param context
* @param channel
* @param application
*/
export declare function getDepositLoss<ED extends EntityDict & BaseEntityDict>(price: number, entity: keyof ED, entityId: string, application: ED['application']['Schema']): ReturnType<CalcLoss>;
export declare function getDepositLoss<ED extends EntityDict & BaseEntityDict>(price: number, application: ED['application']['Schema']): ReturnType<CalcLoss>;
export declare function getAccountPayRefunds<ED extends EntityDict & BaseEntityDict>(context: BackendRuntimeContext<ED>, accountId: string, totalPrice?: number): Promise<Omit<import("../oak-app-domain/Refund/Schema").CreateOperationData, "entity" | "entityId">[]>;
export {};

View File

@ -1,8 +1,6 @@
import { getPayClazz } from './payClazz';
import assert from 'assert';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { RefundExceedMax } from '../types/Exception';
import { merge } from 'oak-domain/lib/utils/lodash';
export const fullPayProjection = {
id: 1,
applicationId: 1,
@ -85,7 +83,10 @@ export async function payNotify(context, body, payId, headers) {
}
return;
}
const CalcRefundLossDict = {
/* const CalcRefundLossDict: Record<string, {
projection: EntityDict['application']['Selection']['data'],
fn: CalcLoss,
}> = {
'wpProduct': {
projection: {
wpProduct$application: {
@ -102,8 +103,8 @@ const CalcRefundLossDict = {
},
fn: (entityId, application, price) => {
const { wpProduct$application: wpProducts } = application;
const wpProduct = wpProducts.find(ele => ele.id === entityId);
const { refundLossFloor, refundLossRatio } = wpProduct.wpAccount;
const wpProduct = wpProducts!.find(ele => ele.id === entityId);
const { refundLossFloor, refundLossRatio } = wpProduct!.wpAccount!;
if (refundLossRatio) {
return [
Math.ceil(price * refundLossRatio / 100),
@ -129,83 +130,45 @@ const CalcRefundLossDict = {
}
}
};
const CalcDepositLossDict = {
wpProduct: {
projection: {
wpProduct$application: {
$entity: 'wpProduct',
data: {
wpAccount: {
id: 1,
depositLossRatio: 1,
},
},
},
},
fn: (entityId, application, price) => {
const { wpProduct$application: wpProducts } = application;
const wpProduct = wpProducts.find(ele => ele.id === entityId);
const { depositLossRatio } = wpProduct.wpAccount;
if (depositLossRatio) {
return [Math.ceil(price * depositLossRatio / 100), 'common::deposit.lossReason.ratio', { value: depositLossRatio }];
}
return [0, '', undefined];
},
},
offlineAccount: {
projection: {
system: {
offlineAccount$system: {
$entity: 'offlineAccount',
data: {
id: 1,
depositLossRatio: 1,
}
}
}
},
fn: (entityId, application, price) => {
const { system } = application;
const { offlineAccount$system: offlineAccounts } = system;
const offlineAccount = offlineAccounts.find(ele => ele.id === entityId);
const { depositLossRatio } = offlineAccount;
if (depositLossRatio) {
return [Math.ceil(price * depositLossRatio / 100), 'common::deposit.lossReason.ratio', { value: depositLossRatio }];
}
return [0, '', undefined];
}
}
};
export function registerCalcRefundLoss(entity, projection, fn) {
export function registerCalcRefundLoss<ED extends EntityDict & BaseEntityDict>(entity: string, projection: ED['application']['Selection']['data'], fn: CalcLoss) {
assert(!CalcRefundLossDict[entity], `${entity}上已经定义了CalcLoss`);
CalcRefundLossDict[entity] = {
projection,
fn,
};
}
export function registerCalcDepositLoss(entity, projection, fn) {
assert(!CalcDepositLossDict[entity], `${entity}上已经定义了CalcLoss`);
CalcDepositLossDict[entity] = {
projection,
fn,
};
}
*/
/**
* 计算充值的损耗比例
* attention: 这个函数目前是在前端调用的所以要确保对应的offlineAccount的信息前端能取的到
* attention: 这个函数目前是在前端调用的
* @param context
* @param channel
* @param application
*/
export function getDepositLoss(price, entity, entityId, application) {
const option = CalcDepositLossDict[entity];
if (option) {
return option.fn(entityId, application, price);
export function getDepositLoss(price, application) {
const { system } = application;
const { payConfig } = system;
const depositLoss = payConfig?.depositLoss;
if (depositLoss) {
const { ratio, highest, lowest } = depositLoss;
let loss = ratio && Math.round(price * ratio / 100) || 0;
if (highest && loss > highest) {
return [highest, 'common::deposit.lossReason.highest', { value: (highest / 100).toFixed(2) }];
}
if (lowest && loss < lowest) {
return [lowest, 'common::deposit.lossReason.lowest', { value: (lowest / 100).toFixed(2) }];
}
if (loss > 0) {
return [loss, 'common::deposit.lossReason.ratio', { value: ratio }];
}
}
return [0, '', undefined];
}
export async function getAccountPayRefunds(context, accountId, totalPrice) {
const appProj = {
const refundData = [];
/* const appProj: EntityDict['application']['Selection']['data'] = {
id: 1,
};
for (const k in CalcRefundLossDict) {
@ -240,25 +203,28 @@ export async function getAccountPayRefunds(context, accountId, totalPrice) {
}
],
}, { dontCollect: true, forUpdate: true });
let price2 = 0;
const refundData = [];
for (const pay of pays) {
const { price, paid, refunded, refundable, entity, entityId, application } = pay;
assert(price === paid && refundable);
assert(!['account', 'offlineAccount'].includes(entity));
const rest = paid - refunded;
assert(!['account', 'offlineAccount'].includes(entity!));
const rest = paid! - refunded!;
assert(rest > 0);
let refundPrice = rest;
if (totalPrice && totalPrice - price2 < rest) {
refundPrice = totalPrice - price2;
}
const [loss, lossExplanation, lossExplanationParams] = CalcRefundLossDict[entity].fn(entityId, application, price);
const [loss, lossExplanation, lossExplanationParams] = CalcRefundLossDict[entity!]!.fn(entityId!, application as EntityDict['application']['Schema'], price!);
refundData.push({
id: await generateNewIdAsync(),
price: refundPrice,
loss,
creatorId: context.getCurrentUserId(),
payId: pay.id,
payId: pay.id!,
iState: 'refunding',
meta: {
channel: entity,
@ -266,13 +232,16 @@ export async function getAccountPayRefunds(context, accountId, totalPrice) {
lossExplanationParams,
}
});
price2 += refundPrice;
if (totalPrice && price2 === totalPrice) {
break;
}
}
if (totalPrice && price2 < totalPrice) {
throw new RefundExceedMax();
}
} */
return refundData;
}

View File

@ -3,6 +3,7 @@ import { Schema, OpSchema as Pay, UpdateOperationData as PayUpdateData } from ".
import PayClazz from "../../types/PayClazz";
import { BRC } from "../../types/RuntimeCxt";
export default class Account implements PayClazz {
getRefundableAt(successAt: number): number;
refund(refund: Refund): Promise<undefined>;
closeRefund(refund: Refund): Promise<void>;
getRefundState(refund: Refund): Promise<[Refund['iState'], undefined]>;

View File

@ -1,6 +1,10 @@
import assert from 'assert';
import dayJs from 'dayjs';
import { generateNewIdAsync } from "oak-domain/lib/utils/uuid";
export default class Account {
getRefundableAt(successAt) {
return dayJs(successAt).add(1000, 'y').valueOf();
}
async refund(refund) {
return;
}

View File

@ -2,7 +2,11 @@ import { OpSchema, UpdateOperationData } from "../../oak-app-domain/Refund/Schem
import { OpSchema as Pay, UpdateOperationData as PayUpdateData } from "../../oak-app-domain/Pay/Schema";
import PayClazz from "../../types/PayClazz";
import { BRC } from "../../types/RuntimeCxt";
import { EntityDict } from "../../oak-app-domain";
export default class Offline implements PayClazz {
offlineAccount: EntityDict['offlineAccount']['OpSchema'];
constructor(offlineAccount: EntityDict['offlineAccount']['OpSchema']);
getRefundableAt(successTime: number): number;
refund(refund: OpSchema): Promise<UpdateOperationData | undefined>;
closeRefund(refund: OpSchema): Promise<void>;
getRefundState(refund: OpSchema): Promise<[Pay['iState'], PayUpdateData | undefined]>;

View File

@ -1,5 +1,14 @@
import assert from "assert";
import dayJs from 'dayjs';
export default class Offline {
offlineAccount;
constructor(offlineAccount) {
this.offlineAccount = offlineAccount;
}
getRefundableAt(successTime) {
const { refundGapDays } = this.offlineAccount;
return refundGapDays ? dayJs(successTime).add(refundGapDays, 'day').subtract(12, 'hour').valueOf() : 0;
}
async refund(refund) {
// 啥也不做
return;

View File

@ -32,4 +32,5 @@ export default class WechatPay implements PayClazz {
iState: EntityDict['pay']['OpSchema']['iState'];
extra?: PayUpdateData;
}>;
getRefundableAt(successAt: number): number;
}

View File

@ -24,5 +24,6 @@ export default class WechatPay implements PayClazz {
*/
prepay(pay: Pay, data: PayUpdateData, context: BRC): Promise<void>;
getState(pay: Pay): Promise<[string, PayUpdateData]>;
getRefundableAt(successAt: number): number;
close(pay: Pay): Promise<void>;
}

View File

@ -78,10 +78,12 @@ export default class WechatPay {
return ['closed', {}];
}
return ['paid', {
forbidRefundAt: Date.now() + 24 * 3600 * 1000,
refundable: true,
successAt: Date.now(),
}];
}
getRefundableAt(successAt) {
return successAt + 24 * 3600 * 1000;
}
async close(pay) {
}
}

View File

@ -68,7 +68,7 @@ export default class WechatPay {
if (gapDays < WechatPay.MIN_REFUND_DAYS_GAP) {
gapDays = WechatPay.MIN_REFUND_DAYS_GAP;
}
return dayJs(successTime).add(gapDays, 'day').subtract(12, 'hour').millisecond();
return dayJs(successTime).add(gapDays, 'day').subtract(12, 'hour').valueOf();
}
async prepay(pay, data, context) {
const applicationId = context.getApplicationId();
@ -159,8 +159,7 @@ export default class WechatPay {
}
};
if (iState === 'paid') {
updateData.forbidRefundAt = this.caclRefundDeadline(success_time);
updateData.refundable = true;
updateData.forbidRefundAt = dayJs(success_time).millisecond();
}
return [iState, updateData];
}
@ -276,8 +275,7 @@ export default class WechatPay {
meta: omit(result, ['mchid',]),
};
if (iState === 'paid') {
extra.forbidRefundAt = this.caclRefundDeadline(success_time);
extra.refundable = true;
extra.successAt = success_time;
}
return {
payId,
@ -285,4 +283,7 @@ export default class WechatPay {
extra,
};
}
getRefundableAt(successAt) {
return this.caclRefundDeadline(successAt);
}
}

View File

@ -5,7 +5,30 @@ import WechatPay from './WechatPay';
const PayChannelDict = {};
const PayClazzConstructorDict = {
'account': async () => new Account(),
'offlineAccount': async () => new Offline(),
'offlineAccount': async (applicationId, entityId, context) => {
const [offlineAccount] = await context.select('offlineAccount', {
data: {
id: 1,
type: 1,
channel: 1,
name: 1,
qrCode: 1,
taxLossRatio: 1,
refundCompensateRatio: 1,
refundGapDays: 1,
allowDeposit: 1,
allowPay: 1,
systemId: 1,
price: 1,
enabled: 1,
},
filter: {
id: entityId,
},
}, { dontCollect: true });
assert(offlineAccount.enabled);
return new Offline(offlineAccount);
},
'wpProduct': async (applicationId, entityId, context) => {
const [[wpProduct], [application]] = await Promise.all([
context.select('wpProduct', {
@ -24,6 +47,9 @@ const PayClazzConstructorDict = {
refundNotifyUrl: 1,
}
}
},
filter: {
id: entityId,
}
}, { dontCollect: true }),
context.select('application', {

View File

@ -101,6 +101,18 @@ const checkers = [
};
}
},
{
entity: 'pay',
action: 'succeedPaying',
type: 'data',
checker(data) {
(0, assert_1.default)(!(data instanceof Array));
const { successAt } = data;
if (!successAt) {
throw new types_1.OakAttrNotNullException('pay', ['successAt']);
}
}
},
{
// 如果在开始支付或者继续支付过程中paid达到了pricepay的状态可以改为paid
entity: 'pay',

View File

@ -25,7 +25,7 @@ function PayConfig(props) {
...data,
},
withdrawLoss: withdrawLoss || {
conservative: !!(withdrawLoss?.conservative),
conservative: false,
},
});
};

View File

@ -45,6 +45,9 @@ const attrUpdateMatrix = {
forbidRefundAt: {
actions: ['succeedPaying'],
},
successAt: {
actions: ['succeedPaying'],
},
entity: {
actions: ['update', 'succeedPaying'],
filter: {

View File

@ -18,6 +18,7 @@ export interface Schema extends EntityShape {
entity: String<32>;
entityId: String<64>;
timeoutAt?: Datetime;
successAt?: Datetime;
forbidRefundAt?: Datetime;
refundable: Boolean;
deposit?: Deposit;

View File

@ -67,6 +67,7 @@ exports.entityDesc = {
order: '所属订单',
deposit: '充值',
timeoutAt: '过期时间',
successAt: '完成时间',
forbidRefundAt: '停止退款时间',
refundable: '是否可退款',
meta: '支付metadata',

View File

@ -21,6 +21,7 @@ export type OpSchema = EntityShape & {
entity: "account" | "offlineAccount" | "wpProduct" | string;
entityId: String<64>;
timeoutAt?: Datetime | null;
successAt?: Datetime | null;
forbidRefundAt?: Datetime | null;
refundable: Boolean;
depositId?: ForeignKey<"deposit"> | null;
@ -43,6 +44,7 @@ export type Schema = EntityShape & {
entity: "account" | "offlineAccount" | "wpProduct" | string;
entityId: String<64>;
timeoutAt?: Datetime | null;
successAt?: Datetime | null;
forbidRefundAt?: Datetime | null;
refundable: Boolean;
depositId?: ForeignKey<"deposit"> | null;
@ -83,6 +85,7 @@ type AttrFilter = {
entity: Q_EnumValue<"account" | "offlineAccount" | "wpProduct" | string>;
entityId: Q_StringValue;
timeoutAt: Q_DateValue;
successAt: Q_DateValue;
forbidRefundAt: Q_DateValue;
refundable: Q_BooleanValue;
depositId: Q_StringValue;
@ -121,6 +124,7 @@ export type Projection = {
entity?: number;
entityId?: number;
timeoutAt?: number;
successAt?: number;
forbidRefundAt?: number;
refundable?: number;
depositId?: number;
@ -204,6 +208,8 @@ export type SortAttr = {
entityId: number;
} | {
timeoutAt: number;
} | {
successAt: number;
} | {
forbidRefundAt: number;
} | {

View File

@ -34,6 +34,9 @@ exports.desc = {
timeoutAt: {
type: "datetime"
},
successAt: {
type: "datetime"
},
forbidRefundAt: {
type: "datetime"
},

View File

@ -1 +1 @@
{ "name": "订单", "attr": { "price": "应支付金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "entity": "支付渠道", "entityId": "支付渠道id", "order": "所属订单", "deposit": "充值", "timeoutAt": "过期时间", "forbidRefundAt": "停止退款时间", "refundable": "是否可退款", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "continuePaying": "继续支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款", "closeRefund": "禁止退款", "stopRefunding": "停止退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } }
{ "name": "订单", "attr": { "price": "应支付金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "entity": "支付渠道", "entityId": "支付渠道id", "order": "所属订单", "deposit": "充值", "timeoutAt": "过期时间", "successAt": "完成时间", "forbidRefundAt": "停止退款时间", "refundable": "是否可退款", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "continuePaying": "继续支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款", "closeRefund": "禁止退款", "stopRefunding": "停止退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } }

View File

@ -23,8 +23,7 @@ exports.desc = {
type: "ref",
ref: "platform"
},
folder // 提现的loss在用户提现时计算
: {
folder: {
notNull: true,
type: "varchar",
params: {
@ -34,11 +33,11 @@ exports.desc = {
super: {
type: "boolean"
},
style: {
style // 如果为true则按照渠道taxLossRatio和refundCompensateRatio进行扣取(如果因为depositLossRatio已经大于taxLossRatio则全额退款)
: {
type: "object"
},
entity // 如果为true则按照渠道taxLossRatio和refundCompensateRatio进行扣取(如果因为depositLossRatio已经大于taxLossRatio则全额退款)
: {
entity: {
type: "varchar",
params: {
length: 32

View File

@ -2,12 +2,6 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.style = void 0;
exports.style = {
color: {
refundLossFloor: {
yuan: '#FFFF00',
jiao: '#00FF00'
}
},
icon: {
pay: '',
refund: '',

View File

@ -518,7 +518,9 @@ const triggers = [
await context.operate('pay', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'succeedPaying',
data: {},
data: {
successAt: Date.now(),
},
filter: {
id,
}
@ -528,5 +530,32 @@ const triggers = [
}
}
},
{
name: '当pay完成支付时计算其refundable和forbidRefundAt',
entity: 'pay',
action: 'succeedPaying',
when: 'before',
fn: async ({ operation }, context) => {
const { data, filter } = operation;
(0, assert_1.default)(data.successAt);
const pays = await context.select('pay', {
data: {
id: 1,
entity: 1,
entityId: 1,
applicationId: 1,
},
filter
}, { dontCollect: true, forUpdate: true });
(0, assert_1.default)(pays.length === 1);
const [pay] = pays;
const { applicationId, entity, entityId } = pay;
const payClazz = await (0, payClazz_1.getPayClazz)(applicationId, entity, entityId, context);
const forbidRefundAt = payClazz.getRefundableAt(data.successAt);
data.forbidRefundAt = forbidRefundAt;
data.refundable = forbidRefundAt > Date.now();
return 1;
},
},
];
exports.default = triggers;

View File

@ -13,6 +13,7 @@ export default interface PayClazz {
}>;
refund(refund: EntityDict['refund']['OpSchema']): Promise<EntityDict['refund']['Update']['data'] | undefined>;
closeRefund(refund: EntityDict['refund']['OpSchema']): Promise<void>;
getRefundableAt(successAt: number): number;
getRefundState(refund: EntityDict['refund']['OpSchema']): Promise<[EntityDict['refund']['OpSchema']['iState'], EntityDict['refund']['Update']['data'] | undefined]>;
}
export {};

View File

@ -3,6 +3,7 @@ import { Schema, OpSchema as Pay, UpdateOperationData as PayUpdateData } from ".
import PayClazz from "../../types/PayClazz";
import { BRC } from "../../types/RuntimeCxt";
export default class Account implements PayClazz {
getRefundableAt(successAt: number): number;
refund(refund: Refund): Promise<undefined>;
closeRefund(refund: Refund): Promise<void>;
getRefundState(refund: Refund): Promise<[Refund['iState'], undefined]>;

View File

@ -2,8 +2,12 @@
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const assert_1 = tslib_1.__importDefault(require("assert"));
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
const uuid_1 = require("oak-domain/lib/utils/uuid");
class Account {
getRefundableAt(successAt) {
return (0, dayjs_1.default)(successAt).add(1000, 'y').valueOf();
}
async refund(refund) {
return;
}

View File

@ -2,7 +2,11 @@ import { OpSchema, UpdateOperationData } from "../../oak-app-domain/Refund/Schem
import { OpSchema as Pay, UpdateOperationData as PayUpdateData } from "../../oak-app-domain/Pay/Schema";
import PayClazz from "../../types/PayClazz";
import { BRC } from "../../types/RuntimeCxt";
import { EntityDict } from "../../oak-app-domain";
export default class Offline implements PayClazz {
offlineAccount: EntityDict['offlineAccount']['OpSchema'];
constructor(offlineAccount: EntityDict['offlineAccount']['OpSchema']);
getRefundableAt(successTime: number): number;
refund(refund: OpSchema): Promise<UpdateOperationData | undefined>;
closeRefund(refund: OpSchema): Promise<void>;
getRefundState(refund: OpSchema): Promise<[Pay['iState'], PayUpdateData | undefined]>;

View File

@ -2,7 +2,16 @@
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const assert_1 = tslib_1.__importDefault(require("assert"));
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
class Offline {
offlineAccount;
constructor(offlineAccount) {
this.offlineAccount = offlineAccount;
}
getRefundableAt(successTime) {
const { refundGapDays } = this.offlineAccount;
return refundGapDays ? (0, dayjs_1.default)(successTime).add(refundGapDays, 'day').subtract(12, 'hour').valueOf() : 0;
}
async refund(refund) {
// 啥也不做
return;

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