退款的逻辑和界面
This commit is contained in:
parent
3908215be5
commit
5b5a7f1b70
|
|
@ -1,4 +1,5 @@
|
|||
import { BRC } from '../types/RuntimeCxt';
|
||||
export declare function getAccountPayRefunds(params: {
|
||||
accountId: string;
|
||||
price: number;
|
||||
}, context: BRC): Promise<Omit<import("../oak-app-domain/Refund/Schema").CreateOperationData, "entity" | "entityId">[]>;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getAccountPayRefunds as getAprsFn } from '../utils/pay';
|
||||
export async function getAccountPayRefunds(params, context) {
|
||||
return getAprsFn(context, params.accountId);
|
||||
return getAprsFn(context, params.accountId, params.price);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
depositMinCent: number;
|
||||
depositMaxCent: number;
|
||||
onDepositPayId: (payId: string) => void;
|
||||
onWithdraw: () => void;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ export default OakComponent({
|
|||
depositMinCent: 0,
|
||||
depositMaxCent: 1000000,
|
||||
onDepositPayId: (payId) => undefined,
|
||||
onWithdraw: () => undefined,
|
||||
},
|
||||
formData({ data }) {
|
||||
const unfinishedPayId = data?.pay$account?.[0]?.id;
|
||||
|
|
@ -158,6 +159,9 @@ export default OakComponent({
|
|||
const { onDepositPayId } = this.props;
|
||||
const { unfinishedPayId } = this.state;
|
||||
onDepositPayId && onDepositPayId(unfinishedPayId);
|
||||
},
|
||||
onWithdrawClick() {
|
||||
this.props.onWithdraw();
|
||||
}
|
||||
},
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
<l-button
|
||||
type="success"
|
||||
size="long"
|
||||
bind:lintap="onWithdrawClick"
|
||||
>
|
||||
{{t('account:action.withdraw')}}
|
||||
</l-button>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'account', f
|
|||
depositMinCent: number;
|
||||
unfinishedPayId?: string;
|
||||
onDepositPayId: (payId: string) => void;
|
||||
onWithdraw: () => void;
|
||||
depositOpen: boolean;
|
||||
ufOpen: boolean;
|
||||
depPrice: number | null;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import classNames from 'classnames';
|
|||
import { CentToString } from 'oak-domain/lib/utils/money';
|
||||
import AccountDeposit from '../deposit';
|
||||
export default function Render(props) {
|
||||
const { account, depositMaxCent, unfinishedPayId, onDepositPayId, depositOpen, depositMinCent, ufOpen, depPrice, depositChannel, depositMeta, depositing } = props.data;
|
||||
const { account, depositMaxCent, unfinishedPayId, onDepositPayId, depositOpen, depositMinCent, ufOpen, depPrice, depositChannel, depositMeta, depositing, onWithdraw, } = props.data;
|
||||
const { t, createDepositPay, setMessage, setDepositOpen, setUfOpen, setDepPrice, setDepositChannel, setDepositMeta, setDepositing, onDepositClick, onDepositModalClose, onUfModalClose } = props.methods;
|
||||
if (account) {
|
||||
const { total, avail, '#oakLegalActions': legalActions, accountOper$account: opers } = account;
|
||||
|
|
@ -40,10 +40,7 @@ export default function Render(props) {
|
|||
</Button>
|
||||
</div>}
|
||||
{legalActions?.includes('withdraw') && <div className={Styles.item}>
|
||||
<Button block onClick={() => setMessage({
|
||||
type: 'warning',
|
||||
content: '尚未实现'
|
||||
})}>
|
||||
<Button block onClick={() => onWithdraw()}>
|
||||
{t('account:action.withdraw')}
|
||||
</Button>
|
||||
</div>}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'account', f
|
|||
depositMinCent: number;
|
||||
unfinishedPayId?: string;
|
||||
onDepositPayId: (payId: string) => void;
|
||||
onWithdraw: () => void;
|
||||
depositOpen: boolean;
|
||||
ufOpen: boolean;
|
||||
depPrice: number | null;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import AccountDeposit from '../deposit';
|
|||
import AccountOperList from '../../accountOper/pure/List.pc';
|
||||
import { AccountBookOutlined, ScheduleOutlined } from '@ant-design/icons';
|
||||
export default function Render(props) {
|
||||
const { account, depositMaxCent, unfinishedPayId, onDepositPayId, depositOpen, depositMinCent, ufOpen, depPrice, depositChannel, depositMeta, depositing } = props.data;
|
||||
const { account, depositMaxCent, unfinishedPayId, onDepositPayId, depositOpen, depositMinCent, ufOpen, depPrice, depositChannel, depositMeta, depositing, onWithdraw, } = props.data;
|
||||
const { t, createDepositPay, setMessage, setDepositOpen, setUfOpen, setDepPrice, setDepositChannel, setDepositMeta, setDepositing, onDepositClick, onDepositModalClose, } = props.methods;
|
||||
if (account) {
|
||||
const { total, avail, '#oakLegalActions': legalActions, accountOper$account: opers } = account;
|
||||
|
|
@ -16,10 +16,7 @@ export default function Render(props) {
|
|||
{legalActions?.includes('deposit') && <Button type="primary" disabled={ufOpen || depositOpen} onClick={() => onDepositClick()}>
|
||||
{t('account:action.deposit')}
|
||||
</Button>}
|
||||
{legalActions?.includes('withdraw') && <Button onClick={() => setMessage({
|
||||
type: 'warning',
|
||||
content: '尚未实现'
|
||||
})}>
|
||||
{legalActions?.includes('withdraw') && <Button onClick={() => onWithdraw()}>
|
||||
{t('account:action.withdraw')}
|
||||
</Button>}
|
||||
</Flex>}>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import { EntityDict } from "../../../oak-app-domain";
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, keyof EntityDict, boolean, {
|
||||
accountId: string;
|
||||
withdrawAccountFilter: import("../../../oak-app-domain/WithdrawAccount/Schema").Filter | undefined;
|
||||
onNewWithdrawAccount: () => void;
|
||||
onCreateWithdraw: (id: string) => void;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
import { ToCent, ToYuan, ThousandCont } from "oak-domain/lib/utils/money";
|
||||
import assert from "assert";
|
||||
import { RefundExceedMax } from "../../../types/Exception";
|
||||
import { generateNewIdAsync } from "oak-domain/lib/utils/uuid";
|
||||
export default OakComponent({
|
||||
properties: {
|
||||
accountId: '',
|
||||
withdrawAccountFilter: {},
|
||||
onNewWithdrawAccount: () => undefined,
|
||||
onCreateWithdraw: (id) => undefined,
|
||||
},
|
||||
formData({ features }) {
|
||||
const { accountId, withdrawAccountFilter } = this.props;
|
||||
const [account] = features.cache.get('account', {
|
||||
data: {
|
||||
id: 1,
|
||||
avail: 1,
|
||||
refundable: 1,
|
||||
},
|
||||
filter: {
|
||||
id: accountId,
|
||||
}
|
||||
});
|
||||
const withdrawAccounts = features.cache.get('withdrawAccount', {
|
||||
data: {
|
||||
id: 1,
|
||||
org: 1,
|
||||
name: 1,
|
||||
code: 1,
|
||||
channel: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
lossRatio: 1,
|
||||
}
|
||||
},
|
||||
filter: withdrawAccountFilter
|
||||
});
|
||||
const { avail, refundable } = account;
|
||||
const refundAmount = Math.min(avail, refundable);
|
||||
const manualAmount = avail - refundAmount;
|
||||
const { value, withdrawData } = this.state;
|
||||
const refundData = withdrawData && withdrawData.refund$entity?.map(ele => ele.data).map((refund) => {
|
||||
const { meta, price, loss } = refund;
|
||||
const { refundLossRatio, refundLossFloor, channel } = meta;
|
||||
return {
|
||||
lossExp: refundLossRatio ? this.t('refund.lossExp.ratio', { ratio: refundLossRatio }) : (refundLossFloor ? this.t(`refund.lossExp.floor.${refundLossFloor}`) : this.t('refund.lossExp.none')),
|
||||
channel: this.t(`payChannel::${channel}`),
|
||||
priceYuan: ThousandCont(ToYuan(price), 2),
|
||||
lossYuan: ThousandCont(ToYuan(loss), 2),
|
||||
finalYuan: ThousandCont(ToYuan(price - loss), 2),
|
||||
};
|
||||
});
|
||||
const withdrawExactPrice = ThousandCont(ToYuan(withdrawData ? withdrawData.price - withdrawData.loss : 0), 2);
|
||||
return {
|
||||
executale: value > 0,
|
||||
account,
|
||||
withdrawAccounts,
|
||||
refundAmount,
|
||||
refundAmountYuan: ToYuan(refundAmount),
|
||||
manualAmount,
|
||||
manualAmountYuan: ToYuan(manualAmount),
|
||||
avail,
|
||||
availYuan: ToYuan(avail),
|
||||
withdrawMethod: refundData && refundData.length ? 'refund' : undefined,
|
||||
refundData,
|
||||
withdrawExactPrice,
|
||||
};
|
||||
},
|
||||
features: ['cache'],
|
||||
lifetimes: {
|
||||
ready() {
|
||||
const { withdrawAccountFilter } = this.props;
|
||||
this.features.cache.refresh('withdrawAccount', {
|
||||
data: {
|
||||
id: 1,
|
||||
org: 1,
|
||||
name: 1,
|
||||
code: 1,
|
||||
channel: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
lossRatio: 1,
|
||||
}
|
||||
},
|
||||
filter: withdrawAccountFilter
|
||||
});
|
||||
}
|
||||
},
|
||||
data: {
|
||||
value: 0,
|
||||
valueYuan: 0,
|
||||
showMethodHelp: false,
|
||||
showLossHelp: false,
|
||||
withdrawData: null,
|
||||
},
|
||||
methods: {
|
||||
setValue(valueYuan) {
|
||||
let valueYuan2 = typeof valueYuan === 'string' ? parseInt(valueYuan) : valueYuan;
|
||||
const value = valueYuan2 ? ToCent(valueYuan2) : null;
|
||||
this.setState({ value, valueYuan: valueYuan2 }, () => this.reRender());
|
||||
},
|
||||
switchHelp(type) {
|
||||
switch (type) {
|
||||
case 'method': {
|
||||
this.setState({
|
||||
showMethodHelp: !this.state.showMethodHelp,
|
||||
showLossHelp: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'loss': {
|
||||
this.setState({
|
||||
showMethodHelp: false,
|
||||
showLossHelp: !this.state.showLossHelp,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
async createWithdrawData() {
|
||||
const { value, refundAmount, avail, manualAmount } = this.state;
|
||||
if (value > avail) {
|
||||
this.setMessage({
|
||||
type: 'error',
|
||||
content: this.t('error.overflow'),
|
||||
});
|
||||
}
|
||||
else if (refundAmount) {
|
||||
if (value > refundAmount) {
|
||||
this.setMessage({
|
||||
type: 'error',
|
||||
content: this.t('error.overflowRefundAmount'),
|
||||
});
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const { result: refundData } = (await this.features.cache.exec('getAccountPayRefunds', {
|
||||
accountId: this.props.accountId,
|
||||
price: value,
|
||||
}));
|
||||
let loss = 0;
|
||||
(refundData).forEach((ele) => {
|
||||
loss += ele.loss || 0;
|
||||
});
|
||||
this.setState({
|
||||
withdrawData: {
|
||||
id: await generateNewIdAsync(),
|
||||
accountId: this.props.accountId,
|
||||
price: value,
|
||||
loss,
|
||||
refund$entity: await Promise.all(refundData.map(async (data) => ({
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'create',
|
||||
data,
|
||||
}))),
|
||||
creatorId: this.features.token.getUserId(),
|
||||
},
|
||||
}, () => this.reRender());
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof RefundExceedMax) {
|
||||
this.features.cache.refresh('account', {
|
||||
data: {
|
||||
id: 1,
|
||||
avail: 1,
|
||||
refundable: 1,
|
||||
},
|
||||
filter: {
|
||||
id: this.props.accountId,
|
||||
}
|
||||
});
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(manualAmount);
|
||||
if (value > manualAmount) {
|
||||
this.setMessage({
|
||||
type: 'error',
|
||||
content: this.t('error.overflowManualAmount'),
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.warn('还没实现');
|
||||
}
|
||||
}
|
||||
},
|
||||
clearWithdrawData() {
|
||||
this.setState({
|
||||
withdrawData: null,
|
||||
}, () => this.reRender());
|
||||
},
|
||||
async createWithdraw() {
|
||||
const { withdrawData } = this.state;
|
||||
assert(withdrawData);
|
||||
const id = withdrawData.id;
|
||||
await this.features.cache.exec('operate', {
|
||||
entity: 'withdraw',
|
||||
operation: {
|
||||
id: await generateNewIdAsync(),
|
||||
data: withdrawData,
|
||||
action: 'create',
|
||||
}
|
||||
});
|
||||
const { onCreateWithdraw } = this.props;
|
||||
onCreateWithdraw && onCreateWithdraw(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"label": "提现金额",
|
||||
"placeholder": "请输入提现金额",
|
||||
"tips": {
|
||||
"1-1": "当前帐户可用余额",
|
||||
"1-2": "元,其中:",
|
||||
"2-1": "系统自动退款额度",
|
||||
"2-2": "元",
|
||||
"3-1": "人工划款额度",
|
||||
"3-2": "元",
|
||||
"fill": "全部提现"
|
||||
},
|
||||
"helps": {
|
||||
"label": {
|
||||
"method": "提现途径说明",
|
||||
"loss": "提现手续费说明"
|
||||
},
|
||||
"content": {
|
||||
"method": "提现时,帐户中可以退款的金额部分优现从充值的途径返回。若充值途径已经不能退款,则由人工划款到用户指定的提现账户中。一次提现不能同时使用两种提现方法,需要先将自动退款额度退完,剩余部分再申请人工划款",
|
||||
"loss": "因支付途径损耗,提现过程可能存在一定的手续费,其中,自动退款的手续费额度由相应的充值途径决定,人工划款的手续费额度由相应的提现途径决定。当您申请退款确认后,可以看到相应的手续费额度"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"overflow": "提现额度不能超过帐户可用额度",
|
||||
"overflowRefundAmount": "请先提现自动退款部分的额度",
|
||||
"overflowManualAmount": "提现额度不能超过人工划款的额度"
|
||||
},
|
||||
"refund": {
|
||||
"lossExp": {
|
||||
"ratio": "%{ratio}%",
|
||||
"floor": {
|
||||
"1": "无",
|
||||
"2": "按角取整",
|
||||
"3": "按元取整"
|
||||
},
|
||||
"none": "无"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
import { EntityDict } from "../../../oak-app-domain";
|
||||
import { WebComponentProps } from "oak-frontend-base";
|
||||
export default function render(props: WebComponentProps<EntityDict, 'withdraw', false, {
|
||||
account: EntityDict['account']['OpSchema'];
|
||||
value: number;
|
||||
valueYuan: number;
|
||||
refundAmount: number;
|
||||
manualAmount: number;
|
||||
refundAmountYuan: number;
|
||||
manualAmountYuan: number;
|
||||
avail: number;
|
||||
availYuan: number;
|
||||
showMethodHelp: boolean;
|
||||
showLossHelp: boolean;
|
||||
executale: boolean;
|
||||
withdrawMethod?: 'refund' | 'channel';
|
||||
refundData?: ({
|
||||
lossExp: string;
|
||||
channel: string;
|
||||
priceYuan: string;
|
||||
lossYuan: string;
|
||||
finalYuan: string;
|
||||
})[];
|
||||
withdrawExactPrice: string;
|
||||
}, {
|
||||
setValue: (v: string | number | null) => void;
|
||||
switchHelp: (type: 'method' | 'loss') => void;
|
||||
createWithdrawData: () => void;
|
||||
clearWithdrawData: () => void;
|
||||
createWithdraw: () => void;
|
||||
}>): React.JSX.Element | undefined;
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import React from 'react';
|
||||
import { Button, Input } from 'antd-mobile';
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons';
|
||||
import Styles from './web.module.less';
|
||||
import classNames from 'classnames';
|
||||
import WithdrawDetail from '../dry/Detail.mobile';
|
||||
export default function render(props) {
|
||||
const { account, value, refundAmount, manualAmount, avail, availYuan, valueYuan, manualAmountYuan, refundAmountYuan, showMethodHelp, showLossHelp, executale, withdrawMethod, refundData, withdrawExactPrice } = props.data;
|
||||
const { t, setValue, switchHelp, createWithdrawData, clearWithdrawData, createWithdraw } = props.methods;
|
||||
if (refundData) {
|
||||
return (<div className={Styles.container}>
|
||||
<WithdrawDetail withdrawExactPrice={withdrawExactPrice} withdrawMethod={withdrawMethod} refundData={refundData} t={t} step={0}/>
|
||||
<div className={Styles.btns}>
|
||||
<Button className={Styles.btn} block onClick={clearWithdrawData}>
|
||||
{t('common::action.cancel')}
|
||||
</Button>
|
||||
<Button className={Styles.btn} block color="primary" onClick={createWithdraw}>
|
||||
{t('common::action.commit')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
if (account) {
|
||||
if (avail > 0) {
|
||||
return (<div className={Styles.main}>
|
||||
<div className={Styles.label}>
|
||||
{t('label')}
|
||||
</div>
|
||||
<div className={Styles.input}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
<Input autoFocus type="number" min={0} max={availYuan} placeholder={t('placeholder')} value={valueYuan ? `${valueYuan}` : undefined} onChange={(value) => setValue(value)} style={{ '--font-size': '28px' }}/>
|
||||
</div>
|
||||
<div className={Styles.tips}>
|
||||
<div className={Styles.tipLine}>
|
||||
<span>{t('tips.1-1')}</span>
|
||||
<span className={Styles.value}>{availYuan}</span>
|
||||
<span>{t('tips.1-2')}</span>
|
||||
</div>
|
||||
{refundAmount > 0 && <div className={Styles.tipLine}>
|
||||
<span>{t('tips.2-1')}</span>
|
||||
<span className={Styles.value}>
|
||||
{refundAmountYuan}
|
||||
</span>
|
||||
<span>{t('tips.2-2')}</span>
|
||||
<span className={classNames(Styles.value, Styles.clickable)} onClick={() => setValue(refundAmountYuan)}>
|
||||
{t('tips.fill')}
|
||||
</span>
|
||||
</div>}
|
||||
{manualAmount > 0 && <div className={Styles.tipLine}>
|
||||
<span>{t('tips.3-1')}</span>
|
||||
{refundAmount === 0 ? <span className={Styles.value} onClick={() => setValue(manualAmountYuan)}>
|
||||
{manualAmountYuan}
|
||||
</span> : <span className={Styles.value}>
|
||||
{manualAmountYuan}
|
||||
</span>}
|
||||
<span>{t('tips.3-2')}</span>
|
||||
{refundAmount === 0 && <span className={classNames(Styles.value, Styles.clickable)} onClick={() => setValue(manualAmountYuan)}>
|
||||
{t('tips.fill')}
|
||||
</span>}
|
||||
</div>}
|
||||
</div>
|
||||
<div className={Styles.helps}>
|
||||
<Button size="small" className={Styles.label2} fill="none" onClick={() => switchHelp('method')}>
|
||||
<span className={showMethodHelp ? Styles.up : Styles.down} style={{ marginRight: 8 }}>
|
||||
{showMethodHelp ? <UpOutlined /> : <DownOutlined />}
|
||||
</span>
|
||||
<span className={showMethodHelp ? Styles.up : Styles.down}>
|
||||
{t('helps.label.method')}
|
||||
</span>
|
||||
</Button>
|
||||
{showMethodHelp && <div className={Styles.content2}>{t('helps.content.method')}</div>}
|
||||
<Button size="small" className={Styles.label2} fill="none" onClick={() => switchHelp('loss')}>
|
||||
<span className={showLossHelp ? Styles.up : Styles.down} style={{ marginRight: 8 }}>
|
||||
{showLossHelp ? <UpOutlined /> : <DownOutlined />}
|
||||
</span>
|
||||
<span className={showLossHelp ? Styles.up : Styles.down}>
|
||||
{t('helps.label.loss')}
|
||||
</span>
|
||||
</Button>
|
||||
{showLossHelp && <div className={Styles.content2}>{t('helps.content.loss')}</div>}
|
||||
</div>
|
||||
<div style={{ flex: 1 }}/>
|
||||
<Button className={Styles.btn} block color="primary" disabled={!executale} onClick={() => createWithdrawData()}>
|
||||
{t('common::confirm')}
|
||||
</Button>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
|
||||
.main {
|
||||
background-color: var(--oak-bg-color-container);
|
||||
height: calc(100% - 44px);
|
||||
border-radius: 4px;
|
||||
padding: 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.label {
|
||||
margin-top: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
font-size: 28px;
|
||||
border-bottom: solid 0.1px silver;
|
||||
|
||||
.symbol {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
margin-top: 12px;
|
||||
|
||||
.tipLine {
|
||||
margin-top: 8px;
|
||||
color: dimgrey;
|
||||
font-size: x-small;
|
||||
|
||||
.value {
|
||||
color: var(--oak-color-primary);
|
||||
font-weight: bolder;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.helps {
|
||||
margin-top: 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
button {
|
||||
span {
|
||||
font-size: small;
|
||||
}
|
||||
.up {
|
||||
color: blue;
|
||||
}
|
||||
.down {
|
||||
color: skyblue;
|
||||
}
|
||||
}
|
||||
|
||||
.label2 {
|
||||
color: value(--oak-color-primary)
|
||||
}
|
||||
.content2 {
|
||||
margin-bottom: 8px;
|
||||
margin-left: 22px;
|
||||
color: slategrey;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
background-color: var(--oak-bg-color-container);
|
||||
|
||||
|
||||
.btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 8px;
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
margin-left: 8px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
import { EntityDict } from "../../../oak-app-domain";
|
||||
import { WebComponentProps } from "oak-frontend-base";
|
||||
export default function render(props: WebComponentProps<EntityDict, 'withdraw', false, {
|
||||
account: EntityDict['account']['OpSchema'];
|
||||
value: number;
|
||||
valueYuan: number;
|
||||
refundAmount: number;
|
||||
manualAmount: number;
|
||||
refundAmountYuan: number;
|
||||
manualAmountYuan: number;
|
||||
avail: number;
|
||||
availYuan: number;
|
||||
showMethodHelp: boolean;
|
||||
showLossHelp: boolean;
|
||||
executale: boolean;
|
||||
withdrawMethod?: 'refund' | 'channel';
|
||||
refundData?: ({
|
||||
lossExp: string;
|
||||
channel: string;
|
||||
priceYuan: string;
|
||||
lossYuan: string;
|
||||
finalYuan: string;
|
||||
})[];
|
||||
withdrawExactPrice: string;
|
||||
}, {
|
||||
setValue: (v: number | null) => void;
|
||||
switchHelp: (type: 'method' | 'loss') => void;
|
||||
createWithdrawData: () => void;
|
||||
clearWithdrawData: () => void;
|
||||
createWithdraw: () => void;
|
||||
}>): React.JSX.Element | undefined;
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react';
|
||||
import { Button, Alert, InputNumber } from 'antd';
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons';
|
||||
import Styles from './web.pc.module.less';
|
||||
import classNames from 'classnames';
|
||||
import WithdrawDetail from '../dry/Detail.pc';
|
||||
export default function render(props) {
|
||||
const { account, value, refundAmount, manualAmount, avail, availYuan, valueYuan, manualAmountYuan, refundAmountYuan, showMethodHelp, showLossHelp, executale, withdrawMethod, refundData, withdrawExactPrice } = props.data;
|
||||
const { t, setValue, switchHelp, createWithdrawData, clearWithdrawData, createWithdraw } = props.methods;
|
||||
if (refundData) {
|
||||
return (<div className={Styles.container}>
|
||||
<WithdrawDetail withdrawExactPrice={withdrawExactPrice} withdrawMethod={withdrawMethod} refundData={refundData} t={t} step={0}/>
|
||||
<div style={{ flex: 1 }}/>
|
||||
<div className={Styles.btns}>
|
||||
<Button className={Styles.btn} size="large" onClick={clearWithdrawData}>
|
||||
{t('common::action.cancel')}
|
||||
</Button>
|
||||
<Button className={Styles.btn} size="large" type="primary" onClick={createWithdraw}>
|
||||
{t('common::action.commit')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
if (account) {
|
||||
if (avail > 0) {
|
||||
return (<div className={Styles.main}>
|
||||
<div className={Styles.label}>
|
||||
{t('label')}
|
||||
</div>
|
||||
<InputNumber autoFocus addonBefore={t('common::pay.symbol')} className={Styles.input} size="large" value={valueYuan || null} onChange={(value) => setValue(value)} placeholder={t('placeholder')}/>
|
||||
<div className={Styles.tips}>
|
||||
<div className={Styles.tipLine}>
|
||||
<span>{t('tips.1-1')}</span>
|
||||
<span className={Styles.value}>{availYuan}</span>
|
||||
<span>{t('tips.1-2')}</span>
|
||||
</div>
|
||||
{refundAmount > 0 && <div className={Styles.tipLine}>
|
||||
<span>{t('tips.2-1')}</span>
|
||||
<span className={classNames(Styles.value, Styles.clickable)} onClick={() => setValue(refundAmountYuan)}>
|
||||
{refundAmountYuan}
|
||||
</span>
|
||||
<span>{t('tips.2-2')}</span>
|
||||
</div>}
|
||||
{manualAmount > 0 && <div className={Styles.tipLine}>
|
||||
<span>{t('tips.3-1')}</span>
|
||||
{refundAmount === 0 ? <span className={classNames(Styles.value, Styles.clickable)} onClick={() => setValue(manualAmountYuan)}>
|
||||
{manualAmountYuan}
|
||||
</span> : <span className={Styles.value}>
|
||||
{manualAmountYuan}
|
||||
</span>}
|
||||
<span>{t('tips.3-2')}</span>
|
||||
</div>}
|
||||
</div>
|
||||
<div className={Styles.helps}>
|
||||
<Button size="small" className={Styles.label2} type="link" icon={showMethodHelp ? <UpOutlined /> : <DownOutlined />} onClick={() => switchHelp('method')}>
|
||||
{t('helps.label.method')}
|
||||
</Button>
|
||||
{showMethodHelp && <Alert className={Styles.content2} type="info" message={t('helps.content.method')}/>}
|
||||
<Button size="small" className={Styles.label2} type="link" icon={showLossHelp ? <UpOutlined /> : <DownOutlined />} onClick={() => switchHelp('loss')}>
|
||||
{t('helps.label.loss')}
|
||||
</Button>
|
||||
{showLossHelp && <Alert className={Styles.content2} type="info" message={t('helps.content.loss')}/>}
|
||||
</div>
|
||||
<div style={{ flex: 1 }}/>
|
||||
<Button className={Styles.btn} size="large" type="primary" disabled={!executale} onClick={() => createWithdrawData()}>
|
||||
{t('common::confirm')}
|
||||
</Button>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
|
||||
.main {
|
||||
background-color: var(--oak-bg-color-container);
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
padding: 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.label {
|
||||
margin-top: 18px;
|
||||
font-size: smaller;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.tips {
|
||||
margin-top: 12px;
|
||||
|
||||
.tipLine {
|
||||
margin-top: 8px;
|
||||
color: dimgrey;
|
||||
font-size: x-small;
|
||||
|
||||
.value {
|
||||
color: var(--oak-color-primary);
|
||||
font-weight: bolder;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.helps {
|
||||
margin-top: 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.label2 {
|
||||
color: value(--oak-color-primary)
|
||||
}
|
||||
.content2 {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
background-color: var(--oak-bg-color-container);
|
||||
|
||||
|
||||
.btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
padding: 8px;
|
||||
|
||||
.btn {
|
||||
margin-left: 8px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
/// <reference types="wechat-miniprogram" />
|
||||
import { EntityDict } from "../../../oak-app-domain";
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "withdraw", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import { ToYuan, ThousandCont } from "oak-domain/lib/utils/money";
|
||||
import dayJs from 'dayjs';
|
||||
export default OakComponent({
|
||||
entity: 'withdraw',
|
||||
isList: false,
|
||||
projection: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
loss: 1,
|
||||
dealLoss: 1,
|
||||
dealPrice: 1,
|
||||
iState: 1,
|
||||
withdrawAccount: {
|
||||
id: 1,
|
||||
name: 1,
|
||||
},
|
||||
reason: 1,
|
||||
meta: 1,
|
||||
refund$entity: {
|
||||
$entity: 'refund',
|
||||
data: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
loss: 1,
|
||||
meta: 1,
|
||||
iState: 1,
|
||||
reason: 1,
|
||||
$$updateAt$$: 1,
|
||||
},
|
||||
},
|
||||
$$createAt$$: 1,
|
||||
},
|
||||
formData({ data }) {
|
||||
const refundData = data && (data.refund$entity).map((refund) => {
|
||||
const { meta, price, loss, iState, $$updateAt$$, reason } = refund;
|
||||
const { refundLossRatio, refundLossFloor, channel } = meta;
|
||||
return {
|
||||
iState,
|
||||
iStateColor: this.features.style.getColor('refund', 'iState', iState),
|
||||
lossExp: refundLossRatio ? this.t('refund.lossExp.ratio', { ratio: refundLossRatio }) : (refundLossFloor ? this.t(`refund.lossExp.floor.${refundLossFloor}`) : this.t('refund.lossExp.none')),
|
||||
channel: this.t(`payChannel::${channel}`),
|
||||
priceYuan: ThousandCont(ToYuan(price), 2),
|
||||
lossYuan: ThousandCont(ToYuan(loss), 2),
|
||||
finalYuan: ThousandCont(ToYuan(price - loss), 2),
|
||||
successAt: iState === 'refunded' && dayJs($$updateAt$$).format('YYYY-MM-DD HH:mm'),
|
||||
reason,
|
||||
};
|
||||
});
|
||||
const withdrawExactPrice = ThousandCont(ToYuan(data ? data.price - data.loss : 0), 2);
|
||||
return {
|
||||
createAtStr: data && dayJs(data.$$createAt$$).format('YYYY-MM-DD HH:mm'),
|
||||
withdrawExactPrice,
|
||||
refundData,
|
||||
withdrawMethod: refundData && refundData.length ? 'refund' : undefined,
|
||||
step: data?.iState === 'withdrawing' ? 1 : 2,
|
||||
failed: data?.iState === 'failed',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"refund": {
|
||||
"lossExp": {
|
||||
"ratio": "%{ratio}%",
|
||||
"floor": {
|
||||
"1": "无",
|
||||
"2": "按角取整",
|
||||
"3": "按元取整"
|
||||
},
|
||||
"none": "无"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
import { EntityDict } from "../../../oak-app-domain";
|
||||
import { WebComponentProps } from "oak-frontend-base";
|
||||
export default function render(props: WebComponentProps<EntityDict, 'withdraw', false, {
|
||||
createAtStr: string;
|
||||
withdrawMethod?: 'refund' | 'channel';
|
||||
refundData?: ({
|
||||
lossExp: string;
|
||||
channel: string;
|
||||
priceYuan: string;
|
||||
lossYuan: string;
|
||||
finalYuan: string;
|
||||
iState: EntityDict['refund']['OpSchema']['iState'];
|
||||
iStateColor: string;
|
||||
})[];
|
||||
withdrawExactPrice: string;
|
||||
step: 1 | 2;
|
||||
failed?: boolean;
|
||||
}>): React.JSX.Element | null;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import WithdrawDetail from '../dry/Detail.mobile';
|
||||
import Styles from './web.module.less';
|
||||
export default function render(props) {
|
||||
const { withdrawExactPrice, withdrawMethod, refundData, createAtStr, step, failed } = props.data;
|
||||
const { t } = props.methods;
|
||||
if (refundData) {
|
||||
return (<div className={Styles.container}>
|
||||
<WithdrawDetail withdrawExactPrice={withdrawExactPrice} withdrawMethod={withdrawMethod} refundData={refundData} t={t} step={step} createAt={createAtStr} failed={failed}/>
|
||||
</div>);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
background-color: var(--oak-bg-color-container);
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
import { EntityDict } from "../../../oak-app-domain";
|
||||
import { WebComponentProps } from "oak-frontend-base";
|
||||
export default function render(props: WebComponentProps<EntityDict, 'withdraw', false, {
|
||||
createAtStr: string;
|
||||
withdrawMethod?: 'refund' | 'channel';
|
||||
refundData?: ({
|
||||
lossExp: string;
|
||||
channel: string;
|
||||
priceYuan: string;
|
||||
lossYuan: string;
|
||||
finalYuan: string;
|
||||
iState: EntityDict['refund']['OpSchema']['iState'];
|
||||
iStateColor: string;
|
||||
})[];
|
||||
withdrawExactPrice: string;
|
||||
step: 1 | 2;
|
||||
failed?: boolean;
|
||||
}>): React.JSX.Element | null;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import WithdrawDetail from '../dry/Detail.pc';
|
||||
import Styles from './web.pc.module.less';
|
||||
export default function render(props) {
|
||||
const { withdrawExactPrice, withdrawMethod, refundData, createAtStr, step, failed } = props.data;
|
||||
const { t } = props.methods;
|
||||
if (refundData) {
|
||||
return (<div className={Styles.container}>
|
||||
<WithdrawDetail withdrawExactPrice={withdrawExactPrice} withdrawMethod={withdrawMethod} refundData={refundData} t={t} step={step} createAt={createAtStr} failed={failed}/>
|
||||
</div>);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
background-color: var(--oak-bg-color-container);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/// <reference types="react" />
|
||||
export default function Detail(props: {
|
||||
withdrawMethod?: 'refund' | 'channel';
|
||||
refundData: ({
|
||||
lossExp: string;
|
||||
channel: string;
|
||||
priceYuan: number;
|
||||
lossYuan: number;
|
||||
finalYuan: number;
|
||||
})[];
|
||||
withdrawExactPrice: string;
|
||||
t: (k: string, p?: any) => string;
|
||||
step: 0 | 1 | 2;
|
||||
}): import("react").JSX.Element;
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import { Steps } from 'antd-mobile';
|
||||
import { FileAddOutlined, FieldTimeOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
||||
import Styles from './Detail.pc.module.less';
|
||||
import classNames from 'classnames';
|
||||
export default function Detail(props) {
|
||||
const { withdrawExactPrice, withdrawMethod, refundData, t, step } = props;
|
||||
const H = (<>
|
||||
<div className={Styles.header}>
|
||||
<div className={Styles.label}>
|
||||
{t('header.label')}
|
||||
</div>
|
||||
<div className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
<span>{withdrawExactPrice}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={Styles.step}>
|
||||
<Steps labelPlacement="vertical" current={step} items={[
|
||||
{
|
||||
title: <span className={step === 0 ? classNames(Styles.label, Styles.active) : Styles.label}>{t('steps.1.title')}</span>,
|
||||
icon: <FileAddOutlined className={step === 0 ? Styles.active : undefined}/>
|
||||
},
|
||||
{
|
||||
title: <span className={step === 1 ? classNames(Styles.label, Styles.active) : Styles.label}>{t('steps.2.title')}</span>,
|
||||
icon: <FieldTimeOutlined className={step === 1 ? Styles.active : undefined}/>,
|
||||
description: t(`method.v.${withdrawMethod}`)
|
||||
},
|
||||
{
|
||||
title: <span className={step === 2 ? classNames(Styles.label, Styles.active) : Styles.label}>{t('steps.3.title')}</span>,
|
||||
icon: <CheckCircleOutlined className={step === 2 ? Styles.active : undefined}/>
|
||||
}
|
||||
]}/>
|
||||
</div>
|
||||
</>);
|
||||
return (<>
|
||||
{H}
|
||||
<div className={Styles.refunds}>
|
||||
{refundData.map((data) => (<div className={Styles.refundItem}>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.0')}</span>
|
||||
<span className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
{data.priceYuan}
|
||||
</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.1')}</span>
|
||||
<span className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
{data.finalYuan}
|
||||
</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.2')}</span>
|
||||
<span className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
{data.lossYuan}
|
||||
</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.3')}</span>
|
||||
<span className={Styles.value}>{data.lossExp}</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.4')}</span>
|
||||
<span className={Styles.value}>{data.channel}</span>
|
||||
</div>
|
||||
</div>))}
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/// <reference types="react" />
|
||||
import { EntityDict } from '../../../oak-app-domain';
|
||||
export default function Detail(props: {
|
||||
createAt?: string;
|
||||
withdrawMethod?: 'refund' | 'channel';
|
||||
refundData: ({
|
||||
lossExp: string;
|
||||
channel: string;
|
||||
priceYuan: string;
|
||||
lossYuan: string;
|
||||
finalYuan: string;
|
||||
iState?: EntityDict['refund']['OpSchema']['iState'];
|
||||
iStateColor?: string;
|
||||
successAt?: string;
|
||||
reason?: string;
|
||||
})[];
|
||||
withdrawExactPrice: string;
|
||||
t: (k: string, p?: any) => string;
|
||||
step: 0 | 1 | 2;
|
||||
failed?: boolean;
|
||||
}): import("react").JSX.Element;
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import { Tag, Steps } from 'antd-mobile';
|
||||
import Styles from './Detail.module.less';
|
||||
import classNames from 'classnames';
|
||||
export default function Detail(props) {
|
||||
const { withdrawExactPrice, withdrawMethod, refundData, t, step, createAt, failed } = props;
|
||||
return (<>
|
||||
<div className={Styles.header}>
|
||||
<div className={Styles.label}>
|
||||
{t('header.label')}
|
||||
</div>
|
||||
<div className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
<span>{withdrawExactPrice}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={Styles.scroll}>
|
||||
<div className={Styles.step}>
|
||||
<Steps current={step}>
|
||||
<Steps.Step title={<span className={step >= 0 ? classNames(Styles.label, Styles.active) : Styles.label}>{t('steps.1.title')}</span>} description={<span className={step >= 0 ? classNames(Styles.label, Styles.active) : Styles.label}>{createAt}</span>} key="1"/>
|
||||
<Steps.Step title={<span className={step >= 1 ? classNames(Styles.label, Styles.active) : Styles.label}>{t('steps.2.title')}</span>} description={<span className={step >= 1 ? classNames(Styles.label, Styles.active) : Styles.label}>{t(`method.v.${withdrawMethod}`)}</span>} key="2"/>
|
||||
<Steps.Step title={<span className={step >= 2 ? classNames(Styles.label, failed ? Styles.failed : Styles.success) : Styles.label}>{failed ? t('steps.3.failed') : t('steps.3.title')}</span>} key="3"/>
|
||||
</Steps>
|
||||
</div>
|
||||
{refundData.map((data, idx) => (<div className={Styles.refundItem} key={idx}>
|
||||
{data.iState && <div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund:attr.iState')}</span>
|
||||
<span className={Styles.value}>
|
||||
<Tag color={data.iStateColor}>
|
||||
{t(`refund:v.iState.${data.iState}`)}
|
||||
</Tag>
|
||||
</span>
|
||||
</div>}
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.0')}</span>
|
||||
<span className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
{data.priceYuan}
|
||||
</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.1')}</span>
|
||||
<span className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
{data.finalYuan}
|
||||
</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.2')}</span>
|
||||
<span className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
{data.lossYuan}
|
||||
</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.3')}</span>
|
||||
<span className={Styles.value}>{data.lossExp}</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.4')}</span>
|
||||
<span className={Styles.value}>{data.channel}</span>
|
||||
</div>
|
||||
{!!data.successAt && <div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.successAt')}</span>
|
||||
<span className={Styles.value}>{data.successAt}</span>
|
||||
</div>}
|
||||
{!!data.reason && <div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.reason')}</span>
|
||||
<span className={Styles.value}>{data.reason}</span>
|
||||
</div>}
|
||||
</div>))}
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
.header {
|
||||
background-color: var(--oak-color-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 120px;
|
||||
|
||||
.label {
|
||||
align-self: flex-start;
|
||||
margin-left: 36px;
|
||||
font-size: small;
|
||||
color: white;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: x-large;
|
||||
font-weight: bolder;
|
||||
color: white;
|
||||
|
||||
.symbol {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scroll {
|
||||
flex: 1;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.step {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.label {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.active {
|
||||
color: var(--oak-color-primary);
|
||||
}
|
||||
|
||||
.success {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.failed {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
.refundItem {
|
||||
margin: 12px;
|
||||
padding: 8px;
|
||||
font-size: small;
|
||||
border-top: solid 0.1px silver;
|
||||
|
||||
.item {
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.label {
|
||||
color: var(--oak-text-color-secondary);
|
||||
}
|
||||
|
||||
.value {
|
||||
color: var(--oak-text-color-primary);
|
||||
|
||||
.symbol {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/// <reference types="react" />
|
||||
import { EntityDict } from '../../../oak-app-domain';
|
||||
export default function Detail(props: {
|
||||
createAt?: string;
|
||||
withdrawMethod?: 'refund' | 'channel';
|
||||
refundData: ({
|
||||
lossExp: string;
|
||||
channel: string;
|
||||
priceYuan: string;
|
||||
lossYuan: string;
|
||||
finalYuan: string;
|
||||
iState?: EntityDict['refund']['OpSchema']['iState'];
|
||||
iStateColor?: string;
|
||||
successAt?: string;
|
||||
reason?: string;
|
||||
})[];
|
||||
withdrawExactPrice: string;
|
||||
t: (k: string, p?: any) => string;
|
||||
step: 0 | 1 | 2;
|
||||
failed?: boolean;
|
||||
}): import("react").JSX.Element;
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import { Steps, Tag } from 'antd';
|
||||
import { CloseCircleOutlined, FileAddOutlined, FieldTimeOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
||||
import Styles from './Detail.pc.module.less';
|
||||
export default function Detail(props) {
|
||||
const { withdrawExactPrice, withdrawMethod, refundData, t, step, createAt, failed } = props;
|
||||
const H = (<>
|
||||
<div className={Styles.header}>
|
||||
<div className={Styles.label}>
|
||||
{t('header.label')}
|
||||
</div>
|
||||
<div className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
<span>{withdrawExactPrice}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={Styles.step}>
|
||||
<Steps labelPlacement="vertical" current={step} items={[
|
||||
{
|
||||
title: t('steps.1.title'),
|
||||
icon: <FileAddOutlined />,
|
||||
description: createAt,
|
||||
},
|
||||
{
|
||||
title: t('steps.2.title'),
|
||||
icon: <FieldTimeOutlined />,
|
||||
description: t(`method.v.${withdrawMethod}`)
|
||||
},
|
||||
{
|
||||
title: failed ? <span style={{ color: 'red' }}>{t('steps.3.failed')}</span> : <span style={{ color: 'green' }}>{t('steps.3.title')}</span>,
|
||||
icon: failed ? <CloseCircleOutlined style={{ color: 'red' }}/> : <CheckCircleOutlined style={{ color: 'green' }}/>
|
||||
}
|
||||
]}/>
|
||||
</div>
|
||||
</>);
|
||||
return (<>
|
||||
{H}
|
||||
<div className={Styles.refunds}>
|
||||
{refundData.map((data, idx) => (<div className={Styles.refundItem} key={idx}>
|
||||
{data.iState && <div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund:attr.iState')}</span>
|
||||
<span className={Styles.value}>
|
||||
<Tag color={data.iStateColor} style={{
|
||||
marginInlineEnd: 0,
|
||||
}}>
|
||||
{t(`refund:v.iState.${data.iState}`)}
|
||||
</Tag>
|
||||
</span>
|
||||
</div>}
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.0')}</span>
|
||||
<span className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
{data.priceYuan}
|
||||
</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.1')}</span>
|
||||
<span className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
{data.finalYuan}
|
||||
</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.2')}</span>
|
||||
<span className={Styles.value}>
|
||||
<span className={Styles.symbol}>{t('common::pay.symbol')}</span>
|
||||
{data.lossYuan}
|
||||
</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.3')}</span>
|
||||
<span className={Styles.value}>{data.lossExp}</span>
|
||||
</div>
|
||||
<div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.4')}</span>
|
||||
<span className={Styles.value}>{data.channel}</span>
|
||||
</div>
|
||||
{!!data.successAt && <div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.successAt')}</span>
|
||||
<span className={Styles.value}>{data.successAt}</span>
|
||||
</div>}
|
||||
{!!data.reason && <div className={Styles.item}>
|
||||
<span className={Styles.label}>{t('refund.label.reason')}</span>
|
||||
<span className={Styles.value}>{data.reason}</span>
|
||||
</div>}
|
||||
</div>))}
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
.header {
|
||||
background-color: var(--oak-color-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 120px;
|
||||
|
||||
.label {
|
||||
align-self: flex-start;
|
||||
margin-left: 36px;
|
||||
font-size: small;
|
||||
color: white;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: x-large;
|
||||
font-weight: bolder;
|
||||
color: white;
|
||||
|
||||
.symbol {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.step {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.label {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.active {
|
||||
// color: var(--oak-color-primary);
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-steps-item-content {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.refunds {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.refundItem {
|
||||
width: 188px;
|
||||
margin: 12px;
|
||||
padding: 8px;
|
||||
font-size: small;
|
||||
border: solid 0.1px var(--oak-color-info);
|
||||
border-radius: 4px;
|
||||
|
||||
.item {
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.label {
|
||||
color: var(--oak-text-color-secondary);
|
||||
}
|
||||
|
||||
.value {
|
||||
color: var(--oak-text-color-primary);
|
||||
|
||||
.symbol {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"header": {
|
||||
"label": "预计到帐金额"
|
||||
},
|
||||
"steps": {
|
||||
"1": {
|
||||
"title": "发起提现申请"
|
||||
},
|
||||
"2": {
|
||||
"title": "处理中"
|
||||
},
|
||||
"3": {
|
||||
"title": "到帐成功",
|
||||
"failed": "提现失败"
|
||||
}
|
||||
},
|
||||
"method": {
|
||||
"label": "提现方法",
|
||||
"v": {
|
||||
"refund": "原路退款",
|
||||
"manual": "人工划拨"
|
||||
}
|
||||
},
|
||||
"refund": {
|
||||
"label": {
|
||||
"0": "申请金额",
|
||||
"1": "到帐金额",
|
||||
"2": "手续费",
|
||||
"3": "手续费率",
|
||||
"4": "到帐通道",
|
||||
"successAt": "到帐时间",
|
||||
"reason": "异常原因"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,17 @@ const attrUpdateMatrix = {
|
|||
forbidRefundAt: {
|
||||
actions: ['succeedPaying'],
|
||||
}
|
||||
},
|
||||
refund: {
|
||||
externalId: {
|
||||
actions: ['update'],
|
||||
filter: {
|
||||
iState: 'refunding',
|
||||
},
|
||||
},
|
||||
reason: {
|
||||
actions: ['failRefunding', 'makeAbnormal'],
|
||||
}
|
||||
}
|
||||
};
|
||||
export default attrUpdateMatrix;
|
||||
|
|
|
|||
108
es/data/i18n.js
108
es/data/i18n.js
|
|
@ -244,6 +244,114 @@ const i18ns = [
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "6a1df36d072d367121b49dfc665b4100",
|
||||
namespace: "oak-pay-business-c-withdraw-create",
|
||||
language: "zh-CN",
|
||||
module: "oak-pay-business",
|
||||
position: "src/components/withdraw/create",
|
||||
data: {
|
||||
"label": "提现金额",
|
||||
"placeholder": "请输入提现金额",
|
||||
"tips": {
|
||||
"1-1": "当前帐户可用余额",
|
||||
"1-2": "元,其中:",
|
||||
"2-1": "系统自动退款额度",
|
||||
"2-2": "元",
|
||||
"3-1": "人工划款额度",
|
||||
"3-2": "元",
|
||||
"fill": "全部提现"
|
||||
},
|
||||
"helps": {
|
||||
"label": {
|
||||
"method": "提现途径说明",
|
||||
"loss": "提现手续费说明"
|
||||
},
|
||||
"content": {
|
||||
"method": "提现时,帐户中可以退款的金额部分优现从充值的途径返回。若充值途径已经不能退款,则由人工划款到用户指定的提现账户中。一次提现不能同时使用两种提现方法,需要先将自动退款额度退完,剩余部分再申请人工划款",
|
||||
"loss": "因支付途径损耗,提现过程可能存在一定的手续费,其中,自动退款的手续费额度由相应的充值途径决定,人工划款的手续费额度由相应的提现途径决定。当您申请退款确认后,可以看到相应的手续费额度"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"overflow": "提现额度不能超过帐户可用额度",
|
||||
"overflowRefundAmount": "请先提现自动退款部分的额度",
|
||||
"overflowManualAmount": "提现额度不能超过人工划款的额度"
|
||||
},
|
||||
"refund": {
|
||||
"lossExp": {
|
||||
"ratio": "%{ratio}%",
|
||||
"floor": {
|
||||
"1": "无",
|
||||
"2": "按角取整",
|
||||
"3": "按元取整"
|
||||
},
|
||||
"none": "无"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "a80b0aea4e216e86ec8d07cd59750d1b",
|
||||
namespace: "oak-pay-business-c-withdraw-detail",
|
||||
language: "zh-CN",
|
||||
module: "oak-pay-business",
|
||||
position: "src/components/withdraw/detail",
|
||||
data: {
|
||||
"refund": {
|
||||
"lossExp": {
|
||||
"ratio": "%{ratio}%",
|
||||
"floor": {
|
||||
"1": "无",
|
||||
"2": "按角取整",
|
||||
"3": "按元取整"
|
||||
},
|
||||
"none": "无"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "999905578f10b092c9c9c6666fbffe1f",
|
||||
namespace: "oak-pay-business-c-withdraw-dry",
|
||||
language: "zh-CN",
|
||||
module: "oak-pay-business",
|
||||
position: "src/components/withdraw/dry",
|
||||
data: {
|
||||
"header": {
|
||||
"label": "预计到帐金额"
|
||||
},
|
||||
"steps": {
|
||||
"1": {
|
||||
"title": "发起提现申请"
|
||||
},
|
||||
"2": {
|
||||
"title": "处理中"
|
||||
},
|
||||
"3": {
|
||||
"title": "到帐成功",
|
||||
"failed": "提现失败"
|
||||
}
|
||||
},
|
||||
"method": {
|
||||
"label": "提现方法",
|
||||
"v": {
|
||||
"refund": "原路退款",
|
||||
"manual": "人工划拨"
|
||||
}
|
||||
},
|
||||
"refund": {
|
||||
"label": {
|
||||
"0": "申请金额",
|
||||
"1": "到帐金额",
|
||||
"2": "手续费",
|
||||
"3": "手续费率",
|
||||
"4": "到帐通道",
|
||||
"successAt": "到帐时间",
|
||||
"reason": "异常原因"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "37b438f926095fc0c5de592f6d23e912",
|
||||
namespace: "oak-pay-business-c-withdrawChannel-list",
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export interface Schema extends EntityShape {
|
|||
phantom3?: Int<4>;
|
||||
phantom4?: Int<8>;
|
||||
}
|
||||
type IAction = 'startPaying' | 'succeedPaying' | 'close' | 'startRefunding' | 'refundAll' | 'refundPartially';
|
||||
type IAction = 'startPaying' | 'succeedPaying' | 'close' | 'startRefunding' | 'refundAll' | 'refundPartially' | 'stopRefunding';
|
||||
type IState = 'unpaid' | 'paying' | 'paid' | 'closed' | 'refunding' | 'partiallyRefunded' | 'refunded';
|
||||
export declare const IActionDef: ActionDef<IAction, IState>;
|
||||
type Action = IAction | 'closeRefund';
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const IActionDef = {
|
|||
startRefunding: [['paid', 'partiallyRefunded', 'refunding'], 'refunding'],
|
||||
refundAll: [['paid', 'refunding', 'partiallyRefunded'], 'refunded'],
|
||||
refundPartially: [['paid', 'refunding', 'partiallyRefunded'], 'partiallyRefunded'],
|
||||
stopRefunding: ['refunding', 'paid'],
|
||||
},
|
||||
is: 'unpaid',
|
||||
};
|
||||
|
|
@ -78,7 +79,8 @@ export const entityDesc = {
|
|||
startRefunding: '开始退款',
|
||||
refundAll: '完全退款',
|
||||
refundPartially: '部分退款',
|
||||
closeRefund: '禁止退款'
|
||||
closeRefund: '禁止退款',
|
||||
stopRefunding: '停止退款',
|
||||
},
|
||||
v: {
|
||||
iState: {
|
||||
|
|
@ -102,6 +104,7 @@ export const entityDesc = {
|
|||
refundAll: '',
|
||||
refundPartially: '',
|
||||
closeRefund: '',
|
||||
stopRefunding: '',
|
||||
},
|
||||
color: {
|
||||
iState: {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export interface Schema extends EntityShape {
|
|||
externalId?: String<64>;
|
||||
price: Price;
|
||||
creator: User;
|
||||
reason: Text;
|
||||
reason?: Text;
|
||||
}
|
||||
type IState = 'refunding' | 'refunded' | 'failed' | 'abnormal';
|
||||
type IAction = 'succeedRefunding' | 'failRefunding' | 'makeAbnormal';
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ export interface Schema extends EntityShape {
|
|||
account: Account;
|
||||
price: Price;
|
||||
loss: Price;
|
||||
dealPrice: Price;
|
||||
dealLoss: Price;
|
||||
withdrawAccount?: WithdrawAccount;
|
||||
opers: AccountOper[];
|
||||
creator: User;
|
||||
|
|
@ -17,8 +19,8 @@ export interface Schema extends EntityShape {
|
|||
meta?: Object;
|
||||
refunds: Refund[];
|
||||
}
|
||||
type IState = 'withdrawing' | 'successful' | 'failed' | 'applying';
|
||||
type IAction = 'succeed' | 'fail';
|
||||
type IState = 'withdrawing' | 'successful' | 'partiallySuccessful' | 'failed' | 'applying';
|
||||
type IAction = 'succeed' | 'fail' | 'succeedPartially';
|
||||
export declare const IActionDef: ActionDef<IAction, IState>;
|
||||
type Action = IAction;
|
||||
export declare const entityDesc: EntityDesc<Schema, Action, '', {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
export const IActionDef = {
|
||||
stm: {
|
||||
succeed: [['withdrawing', 'applying'], 'successful'],
|
||||
fail: ['applying', 'failed'],
|
||||
fail: [['withdrawing', 'applying'], 'failed'],
|
||||
succeedPartially: [['withdrawing', 'applying'], 'partiallySuccessful']
|
||||
},
|
||||
is: 'withdrawing',
|
||||
};
|
||||
|
|
@ -14,6 +15,8 @@ export const entityDesc = {
|
|||
account: '帐户',
|
||||
price: '金额',
|
||||
loss: "损耗",
|
||||
dealPrice: '完成金额',
|
||||
dealLoss: '完成损耗',
|
||||
withdrawAccount: '提现帐户',
|
||||
iState: '状态',
|
||||
opers: '被关联帐户操作',
|
||||
|
|
@ -26,12 +29,14 @@ export const entityDesc = {
|
|||
iState: {
|
||||
withdrawing: '提现中',
|
||||
successful: '成功的',
|
||||
partiallySuccessful: '部分成功的',
|
||||
failed: '失败的',
|
||||
applying: '申请中'
|
||||
},
|
||||
},
|
||||
action: {
|
||||
succeed: '成功',
|
||||
succeedPartially: '部分成功',
|
||||
fail: '失败',
|
||||
},
|
||||
},
|
||||
|
|
@ -40,11 +45,13 @@ export const entityDesc = {
|
|||
icon: {
|
||||
succeed: '',
|
||||
fail: '',
|
||||
succeedPartially: '',
|
||||
},
|
||||
color: {
|
||||
iState: {
|
||||
withdrawing: '#D2B4DE',
|
||||
successful: '#2E86C1',
|
||||
partiallySuccessful: '#A9DFBF',
|
||||
failed: '#D6DBDF',
|
||||
applying: '#52BE80',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { String } from 'oak-domain/lib/types/DataType';
|
||||
import { String, Boolean } from 'oak-domain/lib/types/DataType';
|
||||
import { EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDesc } from 'oak-domain/lib/types';
|
||||
import { Schema as WithdrawChannel } from './WithdrawChannel';
|
||||
|
|
@ -10,5 +10,6 @@ export interface Schema extends EntityShape {
|
|||
channel: WithdrawChannel;
|
||||
entity: String<32>;
|
||||
entityId: String<64>;
|
||||
isDefault: Boolean;
|
||||
}
|
||||
export declare const entityDesc: EntityDesc<Schema>;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ export const entityDesc = {
|
|||
data: 'metadata',
|
||||
channel: '提现通道',
|
||||
entity: '关联对象',
|
||||
entityId: '关联对象Id'
|
||||
entityId: '关联对象Id',
|
||||
isDefault: '是否默认'
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ActionDef } from "oak-domain/lib/types/Action";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
export type IAction = 'startPaying' | 'succeedPaying' | 'close' | 'startRefunding' | 'refundAll' | 'refundPartially' | string;
|
||||
export type IAction = 'startPaying' | 'succeedPaying' | 'close' | 'startRefunding' | 'refundAll' | 'refundPartially' | 'stopRefunding' | string;
|
||||
export type IState = 'unpaid' | 'paying' | 'paid' | 'closed' | 'refunding' | 'partiallyRefunded' | 'refunded' | string;
|
||||
export declare const IActionDef: ActionDef<IAction, IState>;
|
||||
export type ParticularAction = IAction | 'closeRefund';
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ export const IActionDef = {
|
|||
startRefunding: [['paid', 'partiallyRefunded', 'refunding'], 'refunding'],
|
||||
refundAll: [['paid', 'refunding', 'partiallyRefunded'], 'refunded'],
|
||||
refundPartially: [['paid', 'refunding', 'partiallyRefunded'], 'partiallyRefunded'],
|
||||
stopRefunding: ['refunding', 'paid'],
|
||||
},
|
||||
is: 'unpaid',
|
||||
};
|
||||
export const actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "startPaying", "succeedPaying", "close", "startRefunding", "refundAll", "refundPartially", "closeRefund"];
|
||||
export const actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "startPaying", "succeedPaying", "close", "startRefunding", "refundAll", "refundPartially", "stopRefunding", "closeRefund"];
|
||||
export const actionDefDict = {
|
||||
iState: IActionDef
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const style = {
|
|||
refundAll: '',
|
||||
refundPartially: '',
|
||||
closeRefund: '',
|
||||
stopRefunding: '',
|
||||
},
|
||||
color: {
|
||||
iState: {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{ "name": "订单", "attr": { "price": "应支付金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "channel": "支付渠道", "order": "所属订单", "timeoutAt": "过期时间", "forbidRefundAt": "停止退款时间", "refundable": "是否可退款", "account": "充值帐户", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款", "closeRefund": "禁止退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } }
|
||||
{ "name": "订单", "attr": { "price": "应支付金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "channel": "支付渠道", "order": "所属订单", "timeoutAt": "过期时间", "forbidRefundAt": "停止退款时间", "refundable": "是否可退款", "account": "充值帐户", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款", "closeRefund": "禁止退款", "stopRefunding": "停止退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } }
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export type OpSchema = EntityShape & {
|
|||
externalId?: String<64> | null;
|
||||
price: Price;
|
||||
creatorId: ForeignKey<"user">;
|
||||
reason: Text;
|
||||
reason?: Text | null;
|
||||
iState?: IState | null;
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
|
|
@ -29,7 +29,7 @@ export type Schema = EntityShape & {
|
|||
externalId?: String<64> | null;
|
||||
price: Price;
|
||||
creatorId: ForeignKey<"user">;
|
||||
reason: Text;
|
||||
reason?: Text | null;
|
||||
iState?: IState | null;
|
||||
pay: Pay.Schema;
|
||||
creator: User.Schema;
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ export const desc = {
|
|||
ref: "user"
|
||||
},
|
||||
reason: {
|
||||
notNull: true,
|
||||
type: "text"
|
||||
},
|
||||
iState: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ActionDef } from "oak-domain/lib/types/Action";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
export type IState = 'withdrawing' | 'successful' | 'failed' | 'applying' | string;
|
||||
export type IAction = 'succeed' | 'fail' | string;
|
||||
export type IState = 'withdrawing' | 'successful' | 'partiallySuccessful' | 'failed' | 'applying' | string;
|
||||
export type IAction = 'succeed' | 'fail' | 'succeedPartially' | string;
|
||||
export declare const IActionDef: ActionDef<IAction, IState>;
|
||||
export type ParticularAction = IAction;
|
||||
export declare const actions: string[];
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
export const IActionDef = {
|
||||
stm: {
|
||||
succeed: [['withdrawing', 'applying'], 'successful'],
|
||||
fail: ['applying', 'failed'],
|
||||
fail: [['withdrawing', 'applying'], 'failed'],
|
||||
succeedPartially: [['withdrawing', 'applying'], 'partiallySuccessful']
|
||||
},
|
||||
is: 'withdrawing',
|
||||
};
|
||||
export const actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "succeed", "fail"];
|
||||
export const actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "succeed", "fail", "succeedPartially"];
|
||||
export const actionDefDict = {
|
||||
iState: IActionDef
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ export type OpSchema = EntityShape & {
|
|||
accountId: ForeignKey<"account">;
|
||||
price: Price;
|
||||
loss: Price;
|
||||
dealPrice: Price;
|
||||
dealLoss: Price;
|
||||
withdrawAccountId?: ForeignKey<"withdrawAccount"> | null;
|
||||
creatorId: ForeignKey<"user">;
|
||||
reason?: Text | null;
|
||||
|
|
@ -26,6 +28,8 @@ export type Schema = EntityShape & {
|
|||
accountId: ForeignKey<"account">;
|
||||
price: Price;
|
||||
loss: Price;
|
||||
dealPrice: Price;
|
||||
dealLoss: Price;
|
||||
withdrawAccountId?: ForeignKey<"withdrawAccount"> | null;
|
||||
creatorId: ForeignKey<"user">;
|
||||
reason?: Text | null;
|
||||
|
|
@ -54,6 +58,8 @@ type AttrFilter = {
|
|||
account: Account.Filter;
|
||||
price: Q_NumberValue;
|
||||
loss: Q_NumberValue;
|
||||
dealPrice: Q_NumberValue;
|
||||
dealLoss: Q_NumberValue;
|
||||
withdrawAccountId: Q_StringValue;
|
||||
withdrawAccount: WithdrawAccount.Filter;
|
||||
creatorId: Q_StringValue;
|
||||
|
|
@ -78,6 +84,8 @@ export type Projection = {
|
|||
account?: Account.Projection;
|
||||
price?: number;
|
||||
loss?: number;
|
||||
dealPrice?: number;
|
||||
dealLoss?: number;
|
||||
withdrawAccountId?: number;
|
||||
withdrawAccount?: WithdrawAccount.Projection;
|
||||
creatorId?: number;
|
||||
|
|
@ -138,6 +146,10 @@ export type SortAttr = {
|
|||
price: number;
|
||||
} | {
|
||||
loss: number;
|
||||
} | {
|
||||
dealPrice: number;
|
||||
} | {
|
||||
dealLoss: number;
|
||||
} | {
|
||||
withdrawAccountId: number;
|
||||
} | {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,14 @@ export const desc = {
|
|||
notNull: true,
|
||||
type: "money"
|
||||
},
|
||||
dealPrice: {
|
||||
notNull: true,
|
||||
type: "money"
|
||||
},
|
||||
dealLoss: {
|
||||
notNull: true,
|
||||
type: "money"
|
||||
},
|
||||
withdrawAccountId: {
|
||||
type: "ref",
|
||||
ref: "withdrawAccount"
|
||||
|
|
@ -31,7 +39,7 @@ export const desc = {
|
|||
},
|
||||
iState: {
|
||||
type: "enum",
|
||||
enumeration: ["withdrawing", "successful", "failed", "applying"]
|
||||
enumeration: ["withdrawing", "successful", "partiallySuccessful", "failed", "applying"]
|
||||
}
|
||||
},
|
||||
actionType: "crud",
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ export const style = {
|
|||
icon: {
|
||||
succeed: '',
|
||||
fail: '',
|
||||
succeedPartially: '',
|
||||
},
|
||||
color: {
|
||||
iState: {
|
||||
withdrawing: '#D2B4DE',
|
||||
successful: '#2E86C1',
|
||||
partiallySuccessful: '#A9DFBF',
|
||||
failed: '#D6DBDF',
|
||||
applying: '#52BE80',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{ "name": "提现", "attr": { "account": "帐户", "price": "金额", "loss": "损耗", "withdrawAccount": "提现帐户", "iState": "状态", "opers": "被关联帐户操作", "reason": "原因", "meta": "metadata", "creator": "创建者", "refunds": "退款" }, "v": { "iState": { "withdrawing": "提现中", "successful": "成功的", "failed": "失败的", "applying": "申请中" } }, "action": { "succeed": "成功", "fail": "失败" } }
|
||||
{ "name": "提现", "attr": { "account": "帐户", "price": "金额", "loss": "损耗", "dealPrice": "完成金额", "dealLoss": "完成损耗", "withdrawAccount": "提现帐户", "iState": "状态", "opers": "被关联帐户操作", "reason": "原因", "meta": "metadata", "creator": "创建者", "refunds": "退款" }, "v": { "iState": { "withdrawing": "提现中", "successful": "成功的", "partiallySuccessful": "部分成功的", "failed": "失败的", "applying": "申请中" } }, "action": { "succeed": "成功", "succeedPartially": "部分成功", "fail": "失败" } }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { ForeignKey } from "oak-domain/lib/types/DataType";
|
||||
import { Q_DateValue, 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, Q_EnumValue, 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 { GenericAction } from "oak-domain/lib/actions/action";
|
||||
import { String } from "oak-domain/lib/types/DataType";
|
||||
import { String, Boolean } from "oak-domain/lib/types/DataType";
|
||||
import * as WithdrawChannel from "../WithdrawChannel/Schema";
|
||||
import * as User from "../User/Schema";
|
||||
import * as Withdraw from "../Withdraw/Schema";
|
||||
|
|
@ -17,6 +17,7 @@ export type OpSchema = EntityShape & {
|
|||
channelId: ForeignKey<"withdrawChannel">;
|
||||
entity: "user" | string;
|
||||
entityId: String<64>;
|
||||
isDefault: Boolean;
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
export type Schema = EntityShape & {
|
||||
|
|
@ -27,6 +28,7 @@ export type Schema = EntityShape & {
|
|||
channelId: ForeignKey<"withdrawChannel">;
|
||||
entity: "user" | string;
|
||||
entityId: String<64>;
|
||||
isDefault: Boolean;
|
||||
channel: WithdrawChannel.Schema;
|
||||
user?: User.Schema;
|
||||
withdraw$withdrawAccount?: Array<Withdraw.Schema>;
|
||||
|
|
@ -51,6 +53,7 @@ type AttrFilter = {
|
|||
channel: WithdrawChannel.Filter;
|
||||
entity: Q_EnumValue<"user" | string>;
|
||||
entityId: Q_StringValue;
|
||||
isDefault: Q_BooleanValue;
|
||||
user: User.Filter;
|
||||
withdraw$withdrawAccount: Withdraw.Filter & SubQueryPredicateMetadata;
|
||||
modiEntity$entity: ModiEntity.Filter & SubQueryPredicateMetadata;
|
||||
|
|
@ -72,6 +75,7 @@ export type Projection = {
|
|||
channel?: WithdrawChannel.Projection;
|
||||
entity?: number;
|
||||
entityId?: number;
|
||||
isDefault?: number;
|
||||
user?: User.Projection;
|
||||
withdraw$withdrawAccount?: Withdraw.Selection & {
|
||||
$entity: "withdraw";
|
||||
|
|
@ -123,6 +127,8 @@ export type SortAttr = {
|
|||
entity: number;
|
||||
} | {
|
||||
entityId: number;
|
||||
} | {
|
||||
isDefault: number;
|
||||
} | {
|
||||
user: User.SortAttr;
|
||||
} | {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ export const desc = {
|
|||
params: {
|
||||
length: 64
|
||||
}
|
||||
},
|
||||
isDefault: {
|
||||
notNull: true,
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
actionType: "crud",
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{ "name": "提现帐号", "attr": { "org": "机构", "name": "姓名", "code": "帐号", "data": "metadata", "channel": "提现通道", "entity": "关联对象", "entityId": "关联对象Id" } }
|
||||
{ "name": "提现帐号", "attr": { "org": "机构", "name": "姓名", "code": "帐号", "data": "metadata", "channel": "提现通道", "entity": "关联对象", "entityId": "关联对象Id", "isDefault": "是否默认" } }
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ const triggers = [
|
|||
}
|
||||
else {
|
||||
assert(accountId);
|
||||
// 如果是支付成功,则增加帐户余额,其它暂时不支持(提现还未设计)
|
||||
// 如果是支付成功,则增加帐户余额
|
||||
if (action === 'succeedPaying') {
|
||||
const payPrice = pay.price;
|
||||
await context.operate('accountOper', {
|
||||
|
|
@ -306,8 +306,7 @@ const triggers = [
|
|||
when: 'before',
|
||||
fn: async ({ operation }, context, option) => {
|
||||
const { filter, action, id } = operation;
|
||||
assert(typeof filter.id === 'string');
|
||||
const [pay] = await context.select('pay', {
|
||||
const pays = await context.select('pay', {
|
||||
data: {
|
||||
id: 1,
|
||||
orderId: 1,
|
||||
|
|
@ -317,25 +316,30 @@ const triggers = [
|
|||
},
|
||||
filter,
|
||||
}, { dontCollect: true });
|
||||
const { orderId, accountId, price, refunded } = pay;
|
||||
assert(!orderId);
|
||||
if (price - refunded > 0) {
|
||||
// 减少account上可以refund的部分
|
||||
await context.operate('accountOper', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'create',
|
||||
data: {
|
||||
let count = 0;
|
||||
for (const pay of pays) {
|
||||
const { orderId, accountId, price, refunded } = pay;
|
||||
assert(!orderId);
|
||||
if (price - refunded > 0) {
|
||||
// 减少account上可以refund的部分
|
||||
await context.operate('accountOper', {
|
||||
id: await generateNewIdAsync(),
|
||||
type: 'cutoffRefundable',
|
||||
availPlus: 0,
|
||||
totalPlus: 0,
|
||||
refundablePlus: refunded - price,
|
||||
accountId: accountId,
|
||||
},
|
||||
}, {});
|
||||
return 1;
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await generateNewIdAsync(),
|
||||
type: 'cutoffRefundable',
|
||||
availPlus: 0,
|
||||
totalPlus: 0,
|
||||
refundablePlus: refunded - price,
|
||||
accountId: accountId,
|
||||
entity: 'pay',
|
||||
entityId: pay.id,
|
||||
},
|
||||
}, {});
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return count;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,72 @@
|
|||
import { generateNewId, generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||
import { getPayClazz } from '../utils/payClazz';
|
||||
import assert from 'assert';
|
||||
/**
|
||||
* 当refund完成或失败时,如果关联有提现,去更新提现的状态
|
||||
* @param context
|
||||
* @param refunds
|
||||
*/
|
||||
async function changeWithdrawStateByRefunds(context, refunds) {
|
||||
const withdraws = await context.select('withdraw', {
|
||||
data: {
|
||||
id: 1,
|
||||
iState: 1,
|
||||
price: 1,
|
||||
refund$entity: {
|
||||
$entity: 'refund',
|
||||
data: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
iState: 1,
|
||||
loss: 1,
|
||||
},
|
||||
filter: {
|
||||
iState: 'refunded',
|
||||
},
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
id: {
|
||||
$in: refunds.filter(ele => ele.entity === 'withdraw').map(ele => ele.entityId),
|
||||
}
|
||||
}
|
||||
}, {});
|
||||
let count = 0;
|
||||
for (const withdraw of withdraws) {
|
||||
const { price, iState, refund$entity: relatedRefunds } = withdraw;
|
||||
assert(iState === 'withdrawing');
|
||||
let dealPrice = 0;
|
||||
let dealLoss = 0;
|
||||
let allRefundsOver = true;
|
||||
for (const refund2 of relatedRefunds) {
|
||||
if (refund2.iState === 'refunding') {
|
||||
allRefundsOver = false;
|
||||
break;
|
||||
}
|
||||
if (refund2.iState === 'refunded') {
|
||||
dealPrice += refund2.price;
|
||||
dealLoss += refund2.loss;
|
||||
}
|
||||
}
|
||||
if (!allRefundsOver) {
|
||||
continue;
|
||||
}
|
||||
const action = dealPrice === price ? 'succeed' : 'partiallySucceed';
|
||||
await context.operate('withdraw', {
|
||||
id: await generateNewIdAsync(),
|
||||
action,
|
||||
data: {
|
||||
dealPrice,
|
||||
dealLoss,
|
||||
},
|
||||
filter: {
|
||||
id: withdraw.id,
|
||||
}
|
||||
}, {});
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
const triggers = [
|
||||
{
|
||||
entity: 'refund',
|
||||
|
|
@ -33,7 +99,19 @@ const triggers = [
|
|||
const { id, price, pay } = refund;
|
||||
const { price: payPrice, refunded, channel, applicationId } = pay;
|
||||
const payClazz = await getPayClazz(applicationId, channel, context);
|
||||
await payClazz.refund(refund);
|
||||
const data = await payClazz.refund(refund);
|
||||
if (data) {
|
||||
const closeFn = context.openRootMode();
|
||||
await context.operate('refund', {
|
||||
id: await generateNewIdAsync(),
|
||||
data,
|
||||
action: 'update',
|
||||
filter: {
|
||||
id,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
closeFn();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -66,7 +144,7 @@ const triggers = [
|
|||
}, {});
|
||||
for (const refund of refunds) {
|
||||
const { id, price, iState, pay } = refund;
|
||||
assert(iState === 'refunding' && pay.iState === 'refunding');
|
||||
assert(iState === 'refunded' && pay.iState === 'refunding');
|
||||
const { price: payPrice, refunded } = pay;
|
||||
const refunded2 = refunded + price;
|
||||
assert(refunded2 <= payPrice, '退款金额不应高于pay的总金额');
|
||||
|
|
@ -82,43 +160,51 @@ const triggers = [
|
|||
}
|
||||
}, {});
|
||||
}
|
||||
const withdraws = await context.select('withdraw', {
|
||||
return refunds.length + await changeWithdrawStateByRefunds(context, refunds);
|
||||
}
|
||||
},
|
||||
{
|
||||
entity: 'refund',
|
||||
action: 'failRefunding',
|
||||
when: 'after',
|
||||
name: '退款失败时,更新对应的pay状态以及对应的withdraw状态',
|
||||
fn: async ({ operation }, context) => {
|
||||
const { filter } = operation;
|
||||
const refunds = await context.select('refund', {
|
||||
data: {
|
||||
id: 1,
|
||||
iState: 1,
|
||||
price: 1,
|
||||
refund$entity: {
|
||||
$entity: 'refund',
|
||||
data: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
iState: 1,
|
||||
},
|
||||
filter: {
|
||||
iState: 'refunded',
|
||||
},
|
||||
iState: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
pay: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
iState: 1,
|
||||
refunded: 1,
|
||||
channel: 1,
|
||||
applicationId: 1,
|
||||
orderId: 1,
|
||||
accountId: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
id: {
|
||||
$in: refunds.filter(ele => ele.entity === 'withdraw').map(ele => ele.entityId),
|
||||
}
|
||||
}
|
||||
filter,
|
||||
}, {});
|
||||
const successfulWithdraws = withdraws.filter(({ price, iState, refund$entity: relatedRefunds }) => {
|
||||
assert(iState === 'refunding');
|
||||
let refundedPrice = 0;
|
||||
relatedRefunds?.forEach(({ price }) => refundedPrice += price);
|
||||
return relatedRefunds === price;
|
||||
});
|
||||
if (successfulWithdraws.length > 0) {
|
||||
await context.operate('withdraw', {
|
||||
for (const refund of refunds) {
|
||||
const { id, iState, pay } = refund;
|
||||
assert(iState === 'failed' && pay.iState === 'refunding');
|
||||
const { refunded } = pay;
|
||||
const action = refunded === 0 ? 'stopRefunding' : 'refundPartially';
|
||||
await context.operate('pay', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'succeed',
|
||||
action,
|
||||
data: {},
|
||||
filter: {
|
||||
id: pay.id,
|
||||
}
|
||||
}, {});
|
||||
}
|
||||
return refunds.length + successfulWithdraws.length;
|
||||
return refunds.length + await changeWithdrawStateByRefunds(context, refunds);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,19 +12,43 @@ const triggers = [
|
|||
assert(!(data instanceof Array));
|
||||
const { withdrawAccountId, accountId, price } = data;
|
||||
if (!withdrawAccountId) {
|
||||
// 没有指定渠道则尝试走退款
|
||||
const refundData = await getAccountPayRefunds(context, accountId, price);
|
||||
data.refund$entity = refundData.map((data) => ({
|
||||
id: generateNewId(),
|
||||
action: 'create',
|
||||
data,
|
||||
}));
|
||||
// 没有指定渠道则走退款,前台通过getAccountPayRefunds的aspect去获得refund数据并挂载
|
||||
if (!data.refund$entity) {
|
||||
const refundData = await getAccountPayRefunds(context, accountId, price);
|
||||
data.refund$entity = refundData.map((data) => ({
|
||||
id: generateNewId(),
|
||||
action: 'create',
|
||||
data,
|
||||
}));
|
||||
let loss = 0;
|
||||
data.refund$entity.forEach((ele) => {
|
||||
const { data } = ele;
|
||||
loss += data.loss || 0;
|
||||
});
|
||||
data.loss = loss;
|
||||
}
|
||||
data.iState = 'withdrawing';
|
||||
}
|
||||
else {
|
||||
// 否则走渠道提现,暂时假设为人工操作
|
||||
const [withdrawAccount] = await context.select('withdrawAccount', {
|
||||
data: {
|
||||
id: 1,
|
||||
channel: {
|
||||
id: 1,
|
||||
lossRatio: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
id: withdrawAccountId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
const { channel } = withdrawAccount;
|
||||
const { lossRatio } = channel;
|
||||
data.loss = lossRatio ? Math.ceil(data.price * lossRatio / 100) : 0;
|
||||
data.iState = 'applying';
|
||||
}
|
||||
data.dealPrice = data.dealLoss = 0;
|
||||
data.creatorId = context.getCurrentUserId();
|
||||
data.accountOper$entity = [
|
||||
{
|
||||
|
|
@ -56,13 +80,14 @@ const triggers = [
|
|||
accountId: 1,
|
||||
iState: 1,
|
||||
price: 1,
|
||||
dealPrice: 1,
|
||||
withdrawAccountId: 1,
|
||||
},
|
||||
filter,
|
||||
}, {});
|
||||
for (const withdraw of withdraws) {
|
||||
const { accountId, withdrawAccountId, price } = withdraw;
|
||||
assert(withdrawAccountId); // 只有走渠道的提现才可以失败
|
||||
const { accountId, price, dealPrice } = withdraw;
|
||||
assert(dealPrice === 0);
|
||||
await context.operate('accountOper', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'create',
|
||||
|
|
@ -77,6 +102,42 @@ const triggers = [
|
|||
}
|
||||
return withdraws.length;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '当withdraw部分成功时,将差价部分还回到帐户中',
|
||||
entity: 'withdraw',
|
||||
action: 'succeedPartially',
|
||||
when: 'after',
|
||||
fn: async ({ operation }, context) => {
|
||||
const { filter, data } = operation;
|
||||
const withdraws = await context.select('withdraw', {
|
||||
data: {
|
||||
id: 1,
|
||||
accountId: 1,
|
||||
iState: 1,
|
||||
price: 1,
|
||||
dealPrice: 1,
|
||||
withdrawAccountId: 1,
|
||||
},
|
||||
filter,
|
||||
}, {});
|
||||
for (const withdraw of withdraws) {
|
||||
const { accountId, price, dealPrice } = withdraw;
|
||||
assert(price > dealPrice && dealPrice > 0);
|
||||
await context.operate('accountOper', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await generateNewIdAsync(),
|
||||
accountId,
|
||||
type: 'withdrawBack',
|
||||
totalPlus: price - dealPrice,
|
||||
availPlus: price - dealPrice,
|
||||
},
|
||||
}, {});
|
||||
}
|
||||
return withdraws.length;
|
||||
}
|
||||
}
|
||||
];
|
||||
export default triggers;
|
||||
|
|
|
|||
|
|
@ -104,7 +104,10 @@ export async function getAccountPayRefunds(context, accountId, totalPrice) {
|
|||
data: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
paid: 1,
|
||||
refundable: 1,
|
||||
refunded: 1,
|
||||
channel: 1,
|
||||
},
|
||||
filter: {
|
||||
refundable: true,
|
||||
|
|
@ -165,6 +168,11 @@ export async function getAccountPayRefunds(context, accountId, totalPrice) {
|
|||
creatorId: context.getCurrentUserId(),
|
||||
payId: pay.id,
|
||||
iState: 'refunding',
|
||||
meta: {
|
||||
refundLossRatio,
|
||||
refundLossFloor,
|
||||
channel,
|
||||
}
|
||||
});
|
||||
price2 += refundPrice;
|
||||
if (totalPrice && price2 === totalPrice) {
|
||||
|
|
|
|||
|
|
@ -10,16 +10,18 @@ export default class WechatPay {
|
|||
this.channel = channel;
|
||||
}
|
||||
async refund(refund) {
|
||||
return;
|
||||
return {
|
||||
externalId: Math.random().toString(),
|
||||
};
|
||||
}
|
||||
async closeRefund(refund) {
|
||||
return;
|
||||
}
|
||||
async getRefundState(refund) {
|
||||
const r = Math.random();
|
||||
if (r < 0.3) {
|
||||
/* if (r < 0.3) {
|
||||
return ['refunding', {}];
|
||||
}
|
||||
} */
|
||||
return ['refunded', {}];
|
||||
}
|
||||
decodePayNotification(params, body) {
|
||||
|
|
@ -74,7 +76,10 @@ export default class WechatPay {
|
|||
else if (r > 0.95) {
|
||||
return ['closed', {}];
|
||||
}
|
||||
return ['paid', {}];
|
||||
return ['paid', {
|
||||
forbidRefundAt: Date.now() + 24 * 3600 * 1000,
|
||||
refundable: true,
|
||||
}];
|
||||
}
|
||||
async close(pay) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import orderWatchers from './order';
|
||||
import payWatchers from './pay';
|
||||
import refundWatchers from './refund';
|
||||
const watchers = [
|
||||
...orderWatchers,
|
||||
...payWatchers,
|
||||
...refundWatchers,
|
||||
];
|
||||
export default watchers;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const watchers = [
|
|||
filter: async () => {
|
||||
const now = Date.now();
|
||||
return {
|
||||
iState: 'refund',
|
||||
iState: 'refunding',
|
||||
$$updateAt$$: {
|
||||
$lte: now - QUERY_PAYING_STATE_GAP,
|
||||
},
|
||||
|
|
@ -40,7 +40,6 @@ const watchers = [
|
|||
let action = 'succeedRefunding';
|
||||
switch (iState) {
|
||||
case 'refunded': {
|
||||
// action = 'close';
|
||||
break;
|
||||
}
|
||||
case 'failed': {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { BRC } from '../types/RuntimeCxt';
|
||||
export declare function getAccountPayRefunds(params: {
|
||||
accountId: string;
|
||||
price: number;
|
||||
}, context: BRC): Promise<Omit<import("../oak-app-domain/Refund/Schema").CreateOperationData, "entity" | "entityId">[]>;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
exports.getAccountPayRefunds = void 0;
|
||||
const pay_1 = require("../utils/pay");
|
||||
async function getAccountPayRefunds(params, context) {
|
||||
return (0, pay_1.getAccountPayRefunds)(context, params.accountId);
|
||||
return (0, pay_1.getAccountPayRefunds)(context, params.accountId, params.price);
|
||||
}
|
||||
exports.getAccountPayRefunds = getAccountPayRefunds;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,17 @@ const attrUpdateMatrix = {
|
|||
forbidRefundAt: {
|
||||
actions: ['succeedPaying'],
|
||||
}
|
||||
},
|
||||
refund: {
|
||||
externalId: {
|
||||
actions: ['update'],
|
||||
filter: {
|
||||
iState: 'refunding',
|
||||
},
|
||||
},
|
||||
reason: {
|
||||
actions: ['failRefunding', 'makeAbnormal'],
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.default = attrUpdateMatrix;
|
||||
|
|
|
|||
108
lib/data/i18n.js
108
lib/data/i18n.js
|
|
@ -246,6 +246,114 @@ const i18ns = [
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "6a1df36d072d367121b49dfc665b4100",
|
||||
namespace: "oak-pay-business-c-withdraw-create",
|
||||
language: "zh-CN",
|
||||
module: "oak-pay-business",
|
||||
position: "src/components/withdraw/create",
|
||||
data: {
|
||||
"label": "提现金额",
|
||||
"placeholder": "请输入提现金额",
|
||||
"tips": {
|
||||
"1-1": "当前帐户可用余额",
|
||||
"1-2": "元,其中:",
|
||||
"2-1": "系统自动退款额度",
|
||||
"2-2": "元",
|
||||
"3-1": "人工划款额度",
|
||||
"3-2": "元",
|
||||
"fill": "全部提现"
|
||||
},
|
||||
"helps": {
|
||||
"label": {
|
||||
"method": "提现途径说明",
|
||||
"loss": "提现手续费说明"
|
||||
},
|
||||
"content": {
|
||||
"method": "提现时,帐户中可以退款的金额部分优现从充值的途径返回。若充值途径已经不能退款,则由人工划款到用户指定的提现账户中。一次提现不能同时使用两种提现方法,需要先将自动退款额度退完,剩余部分再申请人工划款",
|
||||
"loss": "因支付途径损耗,提现过程可能存在一定的手续费,其中,自动退款的手续费额度由相应的充值途径决定,人工划款的手续费额度由相应的提现途径决定。当您申请退款确认后,可以看到相应的手续费额度"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"overflow": "提现额度不能超过帐户可用额度",
|
||||
"overflowRefundAmount": "请先提现自动退款部分的额度",
|
||||
"overflowManualAmount": "提现额度不能超过人工划款的额度"
|
||||
},
|
||||
"refund": {
|
||||
"lossExp": {
|
||||
"ratio": "%{ratio}%",
|
||||
"floor": {
|
||||
"1": "无",
|
||||
"2": "按角取整",
|
||||
"3": "按元取整"
|
||||
},
|
||||
"none": "无"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "a80b0aea4e216e86ec8d07cd59750d1b",
|
||||
namespace: "oak-pay-business-c-withdraw-detail",
|
||||
language: "zh-CN",
|
||||
module: "oak-pay-business",
|
||||
position: "src/components/withdraw/detail",
|
||||
data: {
|
||||
"refund": {
|
||||
"lossExp": {
|
||||
"ratio": "%{ratio}%",
|
||||
"floor": {
|
||||
"1": "无",
|
||||
"2": "按角取整",
|
||||
"3": "按元取整"
|
||||
},
|
||||
"none": "无"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "999905578f10b092c9c9c6666fbffe1f",
|
||||
namespace: "oak-pay-business-c-withdraw-dry",
|
||||
language: "zh-CN",
|
||||
module: "oak-pay-business",
|
||||
position: "src/components/withdraw/dry",
|
||||
data: {
|
||||
"header": {
|
||||
"label": "预计到帐金额"
|
||||
},
|
||||
"steps": {
|
||||
"1": {
|
||||
"title": "发起提现申请"
|
||||
},
|
||||
"2": {
|
||||
"title": "处理中"
|
||||
},
|
||||
"3": {
|
||||
"title": "到帐成功",
|
||||
"failed": "提现失败"
|
||||
}
|
||||
},
|
||||
"method": {
|
||||
"label": "提现方法",
|
||||
"v": {
|
||||
"refund": "原路退款",
|
||||
"manual": "人工划拨"
|
||||
}
|
||||
},
|
||||
"refund": {
|
||||
"label": {
|
||||
"0": "申请金额",
|
||||
"1": "到帐金额",
|
||||
"2": "手续费",
|
||||
"3": "手续费率",
|
||||
"4": "到帐通道",
|
||||
"successAt": "到帐时间",
|
||||
"reason": "异常原因"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "37b438f926095fc0c5de592f6d23e912",
|
||||
namespace: "oak-pay-business-c-withdrawChannel-list",
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export interface Schema extends EntityShape {
|
|||
phantom3?: Int<4>;
|
||||
phantom4?: Int<8>;
|
||||
}
|
||||
type IAction = 'startPaying' | 'succeedPaying' | 'close' | 'startRefunding' | 'refundAll' | 'refundPartially';
|
||||
type IAction = 'startPaying' | 'succeedPaying' | 'close' | 'startRefunding' | 'refundAll' | 'refundPartially' | 'stopRefunding';
|
||||
type IState = 'unpaid' | 'paying' | 'paid' | 'closed' | 'refunding' | 'partiallyRefunded' | 'refunded';
|
||||
export declare const IActionDef: ActionDef<IAction, IState>;
|
||||
type Action = IAction | 'closeRefund';
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ exports.IActionDef = {
|
|||
startRefunding: [['paid', 'partiallyRefunded', 'refunding'], 'refunding'],
|
||||
refundAll: [['paid', 'refunding', 'partiallyRefunded'], 'refunded'],
|
||||
refundPartially: [['paid', 'refunding', 'partiallyRefunded'], 'partiallyRefunded'],
|
||||
stopRefunding: ['refunding', 'paid'],
|
||||
},
|
||||
is: 'unpaid',
|
||||
};
|
||||
|
|
@ -81,7 +82,8 @@ exports.entityDesc = {
|
|||
startRefunding: '开始退款',
|
||||
refundAll: '完全退款',
|
||||
refundPartially: '部分退款',
|
||||
closeRefund: '禁止退款'
|
||||
closeRefund: '禁止退款',
|
||||
stopRefunding: '停止退款',
|
||||
},
|
||||
v: {
|
||||
iState: {
|
||||
|
|
@ -105,6 +107,7 @@ exports.entityDesc = {
|
|||
refundAll: '',
|
||||
refundPartially: '',
|
||||
closeRefund: '',
|
||||
stopRefunding: '',
|
||||
},
|
||||
color: {
|
||||
iState: {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export interface Schema extends EntityShape {
|
|||
externalId?: String<64>;
|
||||
price: Price;
|
||||
creator: User;
|
||||
reason: Text;
|
||||
reason?: Text;
|
||||
}
|
||||
type IState = 'refunding' | 'refunded' | 'failed' | 'abnormal';
|
||||
type IAction = 'succeedRefunding' | 'failRefunding' | 'makeAbnormal';
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ export interface Schema extends EntityShape {
|
|||
account: Account;
|
||||
price: Price;
|
||||
loss: Price;
|
||||
dealPrice: Price;
|
||||
dealLoss: Price;
|
||||
withdrawAccount?: WithdrawAccount;
|
||||
opers: AccountOper[];
|
||||
creator: User;
|
||||
|
|
@ -17,8 +19,8 @@ export interface Schema extends EntityShape {
|
|||
meta?: Object;
|
||||
refunds: Refund[];
|
||||
}
|
||||
type IState = 'withdrawing' | 'successful' | 'failed' | 'applying';
|
||||
type IAction = 'succeed' | 'fail';
|
||||
type IState = 'withdrawing' | 'successful' | 'partiallySuccessful' | 'failed' | 'applying';
|
||||
type IAction = 'succeed' | 'fail' | 'succeedPartially';
|
||||
export declare const IActionDef: ActionDef<IAction, IState>;
|
||||
type Action = IAction;
|
||||
export declare const entityDesc: EntityDesc<Schema, Action, '', {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ exports.entityDesc = exports.IActionDef = void 0;
|
|||
exports.IActionDef = {
|
||||
stm: {
|
||||
succeed: [['withdrawing', 'applying'], 'successful'],
|
||||
fail: ['applying', 'failed'],
|
||||
fail: [['withdrawing', 'applying'], 'failed'],
|
||||
succeedPartially: [['withdrawing', 'applying'], 'partiallySuccessful']
|
||||
},
|
||||
is: 'withdrawing',
|
||||
};
|
||||
|
|
@ -17,6 +18,8 @@ exports.entityDesc = {
|
|||
account: '帐户',
|
||||
price: '金额',
|
||||
loss: "损耗",
|
||||
dealPrice: '完成金额',
|
||||
dealLoss: '完成损耗',
|
||||
withdrawAccount: '提现帐户',
|
||||
iState: '状态',
|
||||
opers: '被关联帐户操作',
|
||||
|
|
@ -29,12 +32,14 @@ exports.entityDesc = {
|
|||
iState: {
|
||||
withdrawing: '提现中',
|
||||
successful: '成功的',
|
||||
partiallySuccessful: '部分成功的',
|
||||
failed: '失败的',
|
||||
applying: '申请中'
|
||||
},
|
||||
},
|
||||
action: {
|
||||
succeed: '成功',
|
||||
succeedPartially: '部分成功',
|
||||
fail: '失败',
|
||||
},
|
||||
},
|
||||
|
|
@ -43,11 +48,13 @@ exports.entityDesc = {
|
|||
icon: {
|
||||
succeed: '',
|
||||
fail: '',
|
||||
succeedPartially: '',
|
||||
},
|
||||
color: {
|
||||
iState: {
|
||||
withdrawing: '#D2B4DE',
|
||||
successful: '#2E86C1',
|
||||
partiallySuccessful: '#A9DFBF',
|
||||
failed: '#D6DBDF',
|
||||
applying: '#52BE80',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { String } from 'oak-domain/lib/types/DataType';
|
||||
import { String, Boolean } from 'oak-domain/lib/types/DataType';
|
||||
import { EntityShape } from 'oak-domain/lib/types/Entity';
|
||||
import { EntityDesc } from 'oak-domain/lib/types';
|
||||
import { Schema as WithdrawChannel } from './WithdrawChannel';
|
||||
|
|
@ -10,5 +10,6 @@ export interface Schema extends EntityShape {
|
|||
channel: WithdrawChannel;
|
||||
entity: String<32>;
|
||||
entityId: String<64>;
|
||||
isDefault: Boolean;
|
||||
}
|
||||
export declare const entityDesc: EntityDesc<Schema>;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ exports.entityDesc = {
|
|||
data: 'metadata',
|
||||
channel: '提现通道',
|
||||
entity: '关联对象',
|
||||
entityId: '关联对象Id'
|
||||
entityId: '关联对象Id',
|
||||
isDefault: '是否默认'
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ActionDef } from "oak-domain/lib/types/Action";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
export type IAction = 'startPaying' | 'succeedPaying' | 'close' | 'startRefunding' | 'refundAll' | 'refundPartially' | string;
|
||||
export type IAction = 'startPaying' | 'succeedPaying' | 'close' | 'startRefunding' | 'refundAll' | 'refundPartially' | 'stopRefunding' | string;
|
||||
export type IState = 'unpaid' | 'paying' | 'paid' | 'closed' | 'refunding' | 'partiallyRefunded' | 'refunded' | string;
|
||||
export declare const IActionDef: ActionDef<IAction, IState>;
|
||||
export type ParticularAction = IAction | 'closeRefund';
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ exports.IActionDef = {
|
|||
startRefunding: [['paid', 'partiallyRefunded', 'refunding'], 'refunding'],
|
||||
refundAll: [['paid', 'refunding', 'partiallyRefunded'], 'refunded'],
|
||||
refundPartially: [['paid', 'refunding', 'partiallyRefunded'], 'partiallyRefunded'],
|
||||
stopRefunding: ['refunding', 'paid'],
|
||||
},
|
||||
is: 'unpaid',
|
||||
};
|
||||
exports.actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "startPaying", "succeedPaying", "close", "startRefunding", "refundAll", "refundPartially", "closeRefund"];
|
||||
exports.actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "startPaying", "succeedPaying", "close", "startRefunding", "refundAll", "refundPartially", "stopRefunding", "closeRefund"];
|
||||
exports.actionDefDict = {
|
||||
iState: exports.IActionDef
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ exports.style = {
|
|||
refundAll: '',
|
||||
refundPartially: '',
|
||||
closeRefund: '',
|
||||
stopRefunding: '',
|
||||
},
|
||||
color: {
|
||||
iState: {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{ "name": "订单", "attr": { "price": "应支付金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "channel": "支付渠道", "order": "所属订单", "timeoutAt": "过期时间", "forbidRefundAt": "停止退款时间", "refundable": "是否可退款", "account": "充值帐户", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款", "closeRefund": "禁止退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } }
|
||||
{ "name": "订单", "attr": { "price": "应支付金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "channel": "支付渠道", "order": "所属订单", "timeoutAt": "过期时间", "forbidRefundAt": "停止退款时间", "refundable": "是否可退款", "account": "充值帐户", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款", "closeRefund": "禁止退款", "stopRefunding": "停止退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } }
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export type OpSchema = EntityShape & {
|
|||
externalId?: String<64> | null;
|
||||
price: Price;
|
||||
creatorId: ForeignKey<"user">;
|
||||
reason: Text;
|
||||
reason?: Text | null;
|
||||
iState?: IState | null;
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
|
|
@ -29,7 +29,7 @@ export type Schema = EntityShape & {
|
|||
externalId?: String<64> | null;
|
||||
price: Price;
|
||||
creatorId: ForeignKey<"user">;
|
||||
reason: Text;
|
||||
reason?: Text | null;
|
||||
iState?: IState | null;
|
||||
pay: Pay.Schema;
|
||||
creator: User.Schema;
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ exports.desc = {
|
|||
ref: "user"
|
||||
},
|
||||
reason: {
|
||||
notNull: true,
|
||||
type: "text"
|
||||
},
|
||||
iState: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ActionDef } from "oak-domain/lib/types/Action";
|
||||
import { GenericAction } from "oak-domain/lib/actions/action";
|
||||
export type IState = 'withdrawing' | 'successful' | 'failed' | 'applying' | string;
|
||||
export type IAction = 'succeed' | 'fail' | string;
|
||||
export type IState = 'withdrawing' | 'successful' | 'partiallySuccessful' | 'failed' | 'applying' | string;
|
||||
export type IAction = 'succeed' | 'fail' | 'succeedPartially' | string;
|
||||
export declare const IActionDef: ActionDef<IAction, IState>;
|
||||
export type ParticularAction = IAction;
|
||||
export declare const actions: string[];
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ exports.actionDefDict = exports.actions = exports.IActionDef = void 0;
|
|||
exports.IActionDef = {
|
||||
stm: {
|
||||
succeed: [['withdrawing', 'applying'], 'successful'],
|
||||
fail: ['applying', 'failed'],
|
||||
fail: [['withdrawing', 'applying'], 'failed'],
|
||||
succeedPartially: [['withdrawing', 'applying'], 'partiallySuccessful']
|
||||
},
|
||||
is: 'withdrawing',
|
||||
};
|
||||
exports.actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "succeed", "fail"];
|
||||
exports.actions = ["count", "stat", "download", "select", "aggregate", "create", "remove", "update", "succeed", "fail", "succeedPartially"];
|
||||
exports.actionDefDict = {
|
||||
iState: exports.IActionDef
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ export type OpSchema = EntityShape & {
|
|||
accountId: ForeignKey<"account">;
|
||||
price: Price;
|
||||
loss: Price;
|
||||
dealPrice: Price;
|
||||
dealLoss: Price;
|
||||
withdrawAccountId?: ForeignKey<"withdrawAccount"> | null;
|
||||
creatorId: ForeignKey<"user">;
|
||||
reason?: Text | null;
|
||||
|
|
@ -26,6 +28,8 @@ export type Schema = EntityShape & {
|
|||
accountId: ForeignKey<"account">;
|
||||
price: Price;
|
||||
loss: Price;
|
||||
dealPrice: Price;
|
||||
dealLoss: Price;
|
||||
withdrawAccountId?: ForeignKey<"withdrawAccount"> | null;
|
||||
creatorId: ForeignKey<"user">;
|
||||
reason?: Text | null;
|
||||
|
|
@ -54,6 +58,8 @@ type AttrFilter = {
|
|||
account: Account.Filter;
|
||||
price: Q_NumberValue;
|
||||
loss: Q_NumberValue;
|
||||
dealPrice: Q_NumberValue;
|
||||
dealLoss: Q_NumberValue;
|
||||
withdrawAccountId: Q_StringValue;
|
||||
withdrawAccount: WithdrawAccount.Filter;
|
||||
creatorId: Q_StringValue;
|
||||
|
|
@ -78,6 +84,8 @@ export type Projection = {
|
|||
account?: Account.Projection;
|
||||
price?: number;
|
||||
loss?: number;
|
||||
dealPrice?: number;
|
||||
dealLoss?: number;
|
||||
withdrawAccountId?: number;
|
||||
withdrawAccount?: WithdrawAccount.Projection;
|
||||
creatorId?: number;
|
||||
|
|
@ -138,6 +146,10 @@ export type SortAttr = {
|
|||
price: number;
|
||||
} | {
|
||||
loss: number;
|
||||
} | {
|
||||
dealPrice: number;
|
||||
} | {
|
||||
dealLoss: number;
|
||||
} | {
|
||||
withdrawAccountId: number;
|
||||
} | {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ exports.desc = {
|
|||
notNull: true,
|
||||
type: "money"
|
||||
},
|
||||
dealPrice: {
|
||||
notNull: true,
|
||||
type: "money"
|
||||
},
|
||||
dealLoss: {
|
||||
notNull: true,
|
||||
type: "money"
|
||||
},
|
||||
withdrawAccountId: {
|
||||
type: "ref",
|
||||
ref: "withdrawAccount"
|
||||
|
|
@ -34,7 +42,7 @@ exports.desc = {
|
|||
},
|
||||
iState: {
|
||||
type: "enum",
|
||||
enumeration: ["withdrawing", "successful", "failed", "applying"]
|
||||
enumeration: ["withdrawing", "successful", "partiallySuccessful", "failed", "applying"]
|
||||
}
|
||||
},
|
||||
actionType: "crud",
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ exports.style = {
|
|||
icon: {
|
||||
succeed: '',
|
||||
fail: '',
|
||||
succeedPartially: '',
|
||||
},
|
||||
color: {
|
||||
iState: {
|
||||
withdrawing: '#D2B4DE',
|
||||
successful: '#2E86C1',
|
||||
partiallySuccessful: '#A9DFBF',
|
||||
failed: '#D6DBDF',
|
||||
applying: '#52BE80',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{ "name": "提现", "attr": { "account": "帐户", "price": "金额", "loss": "损耗", "withdrawAccount": "提现帐户", "iState": "状态", "opers": "被关联帐户操作", "reason": "原因", "meta": "metadata", "creator": "创建者", "refunds": "退款" }, "v": { "iState": { "withdrawing": "提现中", "successful": "成功的", "failed": "失败的", "applying": "申请中" } }, "action": { "succeed": "成功", "fail": "失败" } }
|
||||
{ "name": "提现", "attr": { "account": "帐户", "price": "金额", "loss": "损耗", "dealPrice": "完成金额", "dealLoss": "完成损耗", "withdrawAccount": "提现帐户", "iState": "状态", "opers": "被关联帐户操作", "reason": "原因", "meta": "metadata", "creator": "创建者", "refunds": "退款" }, "v": { "iState": { "withdrawing": "提现中", "successful": "成功的", "partiallySuccessful": "部分成功的", "failed": "失败的", "applying": "申请中" } }, "action": { "succeed": "成功", "succeedPartially": "部分成功", "fail": "失败" } }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { ForeignKey } from "oak-domain/lib/types/DataType";
|
||||
import { Q_DateValue, 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, Q_EnumValue, 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 { GenericAction } from "oak-domain/lib/actions/action";
|
||||
import { String } from "oak-domain/lib/types/DataType";
|
||||
import { String, Boolean } from "oak-domain/lib/types/DataType";
|
||||
import * as WithdrawChannel from "../WithdrawChannel/Schema";
|
||||
import * as User from "../User/Schema";
|
||||
import * as Withdraw from "../Withdraw/Schema";
|
||||
|
|
@ -17,6 +17,7 @@ export type OpSchema = EntityShape & {
|
|||
channelId: ForeignKey<"withdrawChannel">;
|
||||
entity: "user" | string;
|
||||
entityId: String<64>;
|
||||
isDefault: Boolean;
|
||||
};
|
||||
export type OpAttr = keyof OpSchema;
|
||||
export type Schema = EntityShape & {
|
||||
|
|
@ -27,6 +28,7 @@ export type Schema = EntityShape & {
|
|||
channelId: ForeignKey<"withdrawChannel">;
|
||||
entity: "user" | string;
|
||||
entityId: String<64>;
|
||||
isDefault: Boolean;
|
||||
channel: WithdrawChannel.Schema;
|
||||
user?: User.Schema;
|
||||
withdraw$withdrawAccount?: Array<Withdraw.Schema>;
|
||||
|
|
@ -51,6 +53,7 @@ type AttrFilter = {
|
|||
channel: WithdrawChannel.Filter;
|
||||
entity: Q_EnumValue<"user" | string>;
|
||||
entityId: Q_StringValue;
|
||||
isDefault: Q_BooleanValue;
|
||||
user: User.Filter;
|
||||
withdraw$withdrawAccount: Withdraw.Filter & SubQueryPredicateMetadata;
|
||||
modiEntity$entity: ModiEntity.Filter & SubQueryPredicateMetadata;
|
||||
|
|
@ -72,6 +75,7 @@ export type Projection = {
|
|||
channel?: WithdrawChannel.Projection;
|
||||
entity?: number;
|
||||
entityId?: number;
|
||||
isDefault?: number;
|
||||
user?: User.Projection;
|
||||
withdraw$withdrawAccount?: Withdraw.Selection & {
|
||||
$entity: "withdraw";
|
||||
|
|
@ -123,6 +127,8 @@ export type SortAttr = {
|
|||
entity: number;
|
||||
} | {
|
||||
entityId: number;
|
||||
} | {
|
||||
isDefault: number;
|
||||
} | {
|
||||
user: User.SortAttr;
|
||||
} | {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ exports.desc = {
|
|||
params: {
|
||||
length: 64
|
||||
}
|
||||
},
|
||||
isDefault: {
|
||||
notNull: true,
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
actionType: "crud",
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{ "name": "提现帐号", "attr": { "org": "机构", "name": "姓名", "code": "帐号", "data": "metadata", "channel": "提现通道", "entity": "关联对象", "entityId": "关联对象Id" } }
|
||||
{ "name": "提现帐号", "attr": { "org": "机构", "name": "姓名", "code": "帐号", "data": "metadata", "channel": "提现通道", "entity": "关联对象", "entityId": "关联对象Id", "isDefault": "是否默认" } }
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ const triggers = [
|
|||
}
|
||||
else {
|
||||
(0, assert_1.default)(accountId);
|
||||
// 如果是支付成功,则增加帐户余额,其它暂时不支持(提现还未设计)
|
||||
// 如果是支付成功,则增加帐户余额
|
||||
if (action === 'succeedPaying') {
|
||||
const payPrice = pay.price;
|
||||
await context.operate('accountOper', {
|
||||
|
|
@ -309,8 +309,7 @@ const triggers = [
|
|||
when: 'before',
|
||||
fn: async ({ operation }, context, option) => {
|
||||
const { filter, action, id } = operation;
|
||||
(0, assert_1.default)(typeof filter.id === 'string');
|
||||
const [pay] = await context.select('pay', {
|
||||
const pays = await context.select('pay', {
|
||||
data: {
|
||||
id: 1,
|
||||
orderId: 1,
|
||||
|
|
@ -320,25 +319,30 @@ const triggers = [
|
|||
},
|
||||
filter,
|
||||
}, { dontCollect: true });
|
||||
const { orderId, accountId, price, refunded } = pay;
|
||||
(0, assert_1.default)(!orderId);
|
||||
if (price - refunded > 0) {
|
||||
// 减少account上可以refund的部分
|
||||
await context.operate('accountOper', {
|
||||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||
action: 'create',
|
||||
data: {
|
||||
let count = 0;
|
||||
for (const pay of pays) {
|
||||
const { orderId, accountId, price, refunded } = pay;
|
||||
(0, assert_1.default)(!orderId);
|
||||
if (price - refunded > 0) {
|
||||
// 减少account上可以refund的部分
|
||||
await context.operate('accountOper', {
|
||||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||
type: 'cutoffRefundable',
|
||||
availPlus: 0,
|
||||
totalPlus: 0,
|
||||
refundablePlus: refunded - price,
|
||||
accountId: accountId,
|
||||
},
|
||||
}, {});
|
||||
return 1;
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||
type: 'cutoffRefundable',
|
||||
availPlus: 0,
|
||||
totalPlus: 0,
|
||||
refundablePlus: refunded - price,
|
||||
accountId: accountId,
|
||||
entity: 'pay',
|
||||
entityId: pay.id,
|
||||
},
|
||||
}, {});
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return count;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -4,6 +4,72 @@ const tslib_1 = require("tslib");
|
|||
const uuid_1 = require("oak-domain/lib/utils/uuid");
|
||||
const payClazz_1 = require("../utils/payClazz");
|
||||
const assert_1 = tslib_1.__importDefault(require("assert"));
|
||||
/**
|
||||
* 当refund完成或失败时,如果关联有提现,去更新提现的状态
|
||||
* @param context
|
||||
* @param refunds
|
||||
*/
|
||||
async function changeWithdrawStateByRefunds(context, refunds) {
|
||||
const withdraws = await context.select('withdraw', {
|
||||
data: {
|
||||
id: 1,
|
||||
iState: 1,
|
||||
price: 1,
|
||||
refund$entity: {
|
||||
$entity: 'refund',
|
||||
data: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
iState: 1,
|
||||
loss: 1,
|
||||
},
|
||||
filter: {
|
||||
iState: 'refunded',
|
||||
},
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
id: {
|
||||
$in: refunds.filter(ele => ele.entity === 'withdraw').map(ele => ele.entityId),
|
||||
}
|
||||
}
|
||||
}, {});
|
||||
let count = 0;
|
||||
for (const withdraw of withdraws) {
|
||||
const { price, iState, refund$entity: relatedRefunds } = withdraw;
|
||||
(0, assert_1.default)(iState === 'withdrawing');
|
||||
let dealPrice = 0;
|
||||
let dealLoss = 0;
|
||||
let allRefundsOver = true;
|
||||
for (const refund2 of relatedRefunds) {
|
||||
if (refund2.iState === 'refunding') {
|
||||
allRefundsOver = false;
|
||||
break;
|
||||
}
|
||||
if (refund2.iState === 'refunded') {
|
||||
dealPrice += refund2.price;
|
||||
dealLoss += refund2.loss;
|
||||
}
|
||||
}
|
||||
if (!allRefundsOver) {
|
||||
continue;
|
||||
}
|
||||
const action = dealPrice === price ? 'succeed' : 'partiallySucceed';
|
||||
await context.operate('withdraw', {
|
||||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||
action,
|
||||
data: {
|
||||
dealPrice,
|
||||
dealLoss,
|
||||
},
|
||||
filter: {
|
||||
id: withdraw.id,
|
||||
}
|
||||
}, {});
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
const triggers = [
|
||||
{
|
||||
entity: 'refund',
|
||||
|
|
@ -36,7 +102,19 @@ const triggers = [
|
|||
const { id, price, pay } = refund;
|
||||
const { price: payPrice, refunded, channel, applicationId } = pay;
|
||||
const payClazz = await (0, payClazz_1.getPayClazz)(applicationId, channel, context);
|
||||
await payClazz.refund(refund);
|
||||
const data = await payClazz.refund(refund);
|
||||
if (data) {
|
||||
const closeFn = context.openRootMode();
|
||||
await context.operate('refund', {
|
||||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||
data,
|
||||
action: 'update',
|
||||
filter: {
|
||||
id,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
closeFn();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -69,7 +147,7 @@ const triggers = [
|
|||
}, {});
|
||||
for (const refund of refunds) {
|
||||
const { id, price, iState, pay } = refund;
|
||||
(0, assert_1.default)(iState === 'refunding' && pay.iState === 'refunding');
|
||||
(0, assert_1.default)(iState === 'refunded' && pay.iState === 'refunding');
|
||||
const { price: payPrice, refunded } = pay;
|
||||
const refunded2 = refunded + price;
|
||||
(0, assert_1.default)(refunded2 <= payPrice, '退款金额不应高于pay的总金额');
|
||||
|
|
@ -85,43 +163,51 @@ const triggers = [
|
|||
}
|
||||
}, {});
|
||||
}
|
||||
const withdraws = await context.select('withdraw', {
|
||||
return refunds.length + await changeWithdrawStateByRefunds(context, refunds);
|
||||
}
|
||||
},
|
||||
{
|
||||
entity: 'refund',
|
||||
action: 'failRefunding',
|
||||
when: 'after',
|
||||
name: '退款失败时,更新对应的pay状态以及对应的withdraw状态',
|
||||
fn: async ({ operation }, context) => {
|
||||
const { filter } = operation;
|
||||
const refunds = await context.select('refund', {
|
||||
data: {
|
||||
id: 1,
|
||||
iState: 1,
|
||||
price: 1,
|
||||
refund$entity: {
|
||||
$entity: 'refund',
|
||||
data: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
iState: 1,
|
||||
},
|
||||
filter: {
|
||||
iState: 'refunded',
|
||||
},
|
||||
iState: 1,
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
pay: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
iState: 1,
|
||||
refunded: 1,
|
||||
channel: 1,
|
||||
applicationId: 1,
|
||||
orderId: 1,
|
||||
accountId: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
id: {
|
||||
$in: refunds.filter(ele => ele.entity === 'withdraw').map(ele => ele.entityId),
|
||||
}
|
||||
}
|
||||
filter,
|
||||
}, {});
|
||||
const successfulWithdraws = withdraws.filter(({ price, iState, refund$entity: relatedRefunds }) => {
|
||||
(0, assert_1.default)(iState === 'refunding');
|
||||
let refundedPrice = 0;
|
||||
relatedRefunds?.forEach(({ price }) => refundedPrice += price);
|
||||
return relatedRefunds === price;
|
||||
});
|
||||
if (successfulWithdraws.length > 0) {
|
||||
await context.operate('withdraw', {
|
||||
for (const refund of refunds) {
|
||||
const { id, iState, pay } = refund;
|
||||
(0, assert_1.default)(iState === 'failed' && pay.iState === 'refunding');
|
||||
const { refunded } = pay;
|
||||
const action = refunded === 0 ? 'stopRefunding' : 'refundPartially';
|
||||
await context.operate('pay', {
|
||||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||
action: 'succeed',
|
||||
action,
|
||||
data: {},
|
||||
filter: {
|
||||
id: pay.id,
|
||||
}
|
||||
}, {});
|
||||
}
|
||||
return refunds.length + successfulWithdraws.length;
|
||||
return refunds.length + await changeWithdrawStateByRefunds(context, refunds);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,19 +15,43 @@ const triggers = [
|
|||
(0, assert_1.default)(!(data instanceof Array));
|
||||
const { withdrawAccountId, accountId, price } = data;
|
||||
if (!withdrawAccountId) {
|
||||
// 没有指定渠道则尝试走退款
|
||||
const refundData = await (0, pay_1.getAccountPayRefunds)(context, accountId, price);
|
||||
data.refund$entity = refundData.map((data) => ({
|
||||
id: (0, uuid_1.generateNewId)(),
|
||||
action: 'create',
|
||||
data,
|
||||
}));
|
||||
// 没有指定渠道则走退款,前台通过getAccountPayRefunds的aspect去获得refund数据并挂载
|
||||
if (!data.refund$entity) {
|
||||
const refundData = await (0, pay_1.getAccountPayRefunds)(context, accountId, price);
|
||||
data.refund$entity = refundData.map((data) => ({
|
||||
id: (0, uuid_1.generateNewId)(),
|
||||
action: 'create',
|
||||
data,
|
||||
}));
|
||||
let loss = 0;
|
||||
data.refund$entity.forEach((ele) => {
|
||||
const { data } = ele;
|
||||
loss += data.loss || 0;
|
||||
});
|
||||
data.loss = loss;
|
||||
}
|
||||
data.iState = 'withdrawing';
|
||||
}
|
||||
else {
|
||||
// 否则走渠道提现,暂时假设为人工操作
|
||||
const [withdrawAccount] = await context.select('withdrawAccount', {
|
||||
data: {
|
||||
id: 1,
|
||||
channel: {
|
||||
id: 1,
|
||||
lossRatio: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
id: withdrawAccountId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
const { channel } = withdrawAccount;
|
||||
const { lossRatio } = channel;
|
||||
data.loss = lossRatio ? Math.ceil(data.price * lossRatio / 100) : 0;
|
||||
data.iState = 'applying';
|
||||
}
|
||||
data.dealPrice = data.dealLoss = 0;
|
||||
data.creatorId = context.getCurrentUserId();
|
||||
data.accountOper$entity = [
|
||||
{
|
||||
|
|
@ -59,13 +83,14 @@ const triggers = [
|
|||
accountId: 1,
|
||||
iState: 1,
|
||||
price: 1,
|
||||
dealPrice: 1,
|
||||
withdrawAccountId: 1,
|
||||
},
|
||||
filter,
|
||||
}, {});
|
||||
for (const withdraw of withdraws) {
|
||||
const { accountId, withdrawAccountId, price } = withdraw;
|
||||
(0, assert_1.default)(withdrawAccountId); // 只有走渠道的提现才可以失败
|
||||
const { accountId, price, dealPrice } = withdraw;
|
||||
(0, assert_1.default)(dealPrice === 0);
|
||||
await context.operate('accountOper', {
|
||||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||
action: 'create',
|
||||
|
|
@ -80,6 +105,42 @@ const triggers = [
|
|||
}
|
||||
return withdraws.length;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '当withdraw部分成功时,将差价部分还回到帐户中',
|
||||
entity: 'withdraw',
|
||||
action: 'succeedPartially',
|
||||
when: 'after',
|
||||
fn: async ({ operation }, context) => {
|
||||
const { filter, data } = operation;
|
||||
const withdraws = await context.select('withdraw', {
|
||||
data: {
|
||||
id: 1,
|
||||
accountId: 1,
|
||||
iState: 1,
|
||||
price: 1,
|
||||
dealPrice: 1,
|
||||
withdrawAccountId: 1,
|
||||
},
|
||||
filter,
|
||||
}, {});
|
||||
for (const withdraw of withdraws) {
|
||||
const { accountId, price, dealPrice } = withdraw;
|
||||
(0, assert_1.default)(price > dealPrice && dealPrice > 0);
|
||||
await context.operate('accountOper', {
|
||||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||
accountId,
|
||||
type: 'withdrawBack',
|
||||
totalPlus: price - dealPrice,
|
||||
availPlus: price - dealPrice,
|
||||
},
|
||||
}, {});
|
||||
}
|
||||
return withdraws.length;
|
||||
}
|
||||
}
|
||||
];
|
||||
exports.default = triggers;
|
||||
|
|
|
|||
|
|
@ -110,7 +110,10 @@ async function getAccountPayRefunds(context, accountId, totalPrice) {
|
|||
data: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
paid: 1,
|
||||
refundable: 1,
|
||||
refunded: 1,
|
||||
channel: 1,
|
||||
},
|
||||
filter: {
|
||||
refundable: true,
|
||||
|
|
@ -171,6 +174,11 @@ async function getAccountPayRefunds(context, accountId, totalPrice) {
|
|||
creatorId: context.getCurrentUserId(),
|
||||
payId: pay.id,
|
||||
iState: 'refunding',
|
||||
meta: {
|
||||
refundLossRatio,
|
||||
refundLossFloor,
|
||||
channel,
|
||||
}
|
||||
});
|
||||
price2 += refundPrice;
|
||||
if (totalPrice && price2 === totalPrice) {
|
||||
|
|
|
|||
|
|
@ -14,16 +14,18 @@ class WechatPay {
|
|||
this.channel = channel;
|
||||
}
|
||||
async refund(refund) {
|
||||
return;
|
||||
return {
|
||||
externalId: Math.random().toString(),
|
||||
};
|
||||
}
|
||||
async closeRefund(refund) {
|
||||
return;
|
||||
}
|
||||
async getRefundState(refund) {
|
||||
const r = Math.random();
|
||||
if (r < 0.3) {
|
||||
/* if (r < 0.3) {
|
||||
return ['refunding', {}];
|
||||
}
|
||||
} */
|
||||
return ['refunded', {}];
|
||||
}
|
||||
decodePayNotification(params, body) {
|
||||
|
|
@ -78,7 +80,10 @@ class WechatPay {
|
|||
else if (r > 0.95) {
|
||||
return ['closed', {}];
|
||||
}
|
||||
return ['paid', {}];
|
||||
return ['paid', {
|
||||
forbidRefundAt: Date.now() + 24 * 3600 * 1000,
|
||||
refundable: true,
|
||||
}];
|
||||
}
|
||||
async close(pay) {
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue