oak-pay-business/es/components/pay/detail/web.pc.js

172 lines
7.0 KiB
JavaScript

import React, { useEffect, useState } from 'react';
import { Input, Tag, Card, QRCode, Form, Descriptions, Typography, Alert, Select, Button, Modal } from 'antd';
import { CheckCircleOutlined } from '@ant-design/icons';
import { CentToString } from 'oak-domain/lib/utils/money';
import Styles from './web.pc.module.less';
import * as dayJs from 'dayjs';
import duration from 'dayjs/plugin/duration';
dayJs.extend(duration);
import { PAY_CHANNEL_OFFLINE_NAME, PAY_CHANNEL_WECHAT_APP_NAME, PAY_CHANNEL_WECHAT_H5_NAME, PAY_CHANNEL_WECHAT_JS_NAME, PAY_CHANNEL_WECHAT_MP_NAME, PAY_CHANNEL_WECHAT_NATIVE_NAME } from '../../../types/PayConfig';
export function RenderOffline(props) {
const { pay, t, offline, updateMeta, metaUpdatable } = props;
const { meta, iState } = pay;
return (<>
<Form labelCol={{ span: 4 }} wrapperCol={{ span: 14 }} layout="horizontal" style={{ width: '100%', marginTop: 12 }}>
{metaUpdatable && <Form.Item label={t("offline.label.tips")}>
<span style={{ wordBreak: 'break-all', textDecoration: 'underline' }}>{offline.tips}</span>
</Form.Item>}
<Form.Item label={t("offline.label.option")}>
<Select value={meta?.option} options={offline.options?.map(ele => ({
label: ele,
value: ele,
}))} disabled={!metaUpdatable} onSelect={(option) => updateMeta({
...meta,
option,
})}/>
</Form.Item>
<Form.Item label={t("offline.label.serial")}>
<Input value={meta?.serial} disabled={!metaUpdatable} placeholder={metaUpdatable ? t('offline.placeholder.serial') : t('offline.placeholder.none')} onChange={({ currentTarget }) => updateMeta({
...meta,
serial: currentTarget.value,
})}/>
</Form.Item>
</Form>
</>);
}
function Counter(props) {
const { deadline } = props;
const [counter, setCounter] = useState('');
const timerFn = () => {
const now = Date.now();
if (now < deadline) {
const duration = dayJs.duration(deadline - now);
setCounter(duration.format('HH:mm:ss'));
setTimeout(timerFn, 1000);
}
};
useEffect(() => {
timerFn();
}, []);
return (<Typography.Title level={3}>{counter}</Typography.Title>);
}
function RenderWechatPay(props) {
const { pay, t } = props;
const { externalId, channel, timeoutAt, iState } = pay;
if (iState === 'paid') {
return (<div className={Styles.paid}>
<CheckCircleOutlined style={{
fontSize: 72,
color: 'green',
fontWeight: 'bold',
}}/>
<div className={Styles.text}>
{t('success')}
</div>
</div>);
}
switch (channel) {
case PAY_CHANNEL_WECHAT_NATIVE_NAME: {
if (iState === 'paying') {
return (<>
<Counter deadline={timeoutAt}/>
<QRCode value={externalId} size={280}/>
<div className={Styles.qrCodeTips}>
{process.env.NODE_ENV === 'production' ?
<Alert type="info" message={t('wechat.native.tips')}/> :
<Alert type="warning" message={t('wechat.native.tips2')}/>}
</div>
</>);
}
break;
}
}
return null;
}
function RenderPayMeta(props) {
const { pay, notSameApp, t, payConfig, updateMeta, metaUpdatable } = props;
const { iState, channel } = pay;
if (metaUpdatable && notSameApp) {
return <Alert type='warning' message={t('notSameApp')}/>;
}
switch (channel) {
case PAY_CHANNEL_OFFLINE_NAME: {
return (<>
{iState === 'paying' && <Alert type='info' message={t('offline.tips')}/>}
{RenderOffline({
pay,
t,
offline: payConfig.find(ele => ele.channel === PAY_CHANNEL_OFFLINE_NAME),
updateMeta,
metaUpdatable
})}
</>);
}
case PAY_CHANNEL_WECHAT_APP_NAME:
case PAY_CHANNEL_WECHAT_H5_NAME:
case PAY_CHANNEL_WECHAT_JS_NAME:
case PAY_CHANNEL_WECHAT_MP_NAME:
case PAY_CHANNEL_WECHAT_NATIVE_NAME: {
return <RenderWechatPay pay={pay} t={t}/>;
}
}
return null;
}
export default function Render(props) {
const { pay, iStateColor, payConfig, notSameApp, type, startPayable, oakExecutable, onClose, closable, metaUpdatable } = props.data;
const { t, update, execute, clean, goBack } = props.methods;
if (pay) {
const { iState, channel, price } = pay;
const BtnPart = oakExecutable === true ? (<>
<Button type="primary" onClick={() => execute()}>
{t('common::action.update')}
</Button>
<Button onClick={() => clean()}>
{t('common::reset')}
</Button>
</>) : closable ? (<Button type="primary" onClick={() => {
Modal.confirm({
title: t('cc.title'),
content: t('cc.content'),
onOk: async () => {
await execute('close');
onClose();
}
});
}}>
{t('pay:action.close')}
</Button>) : (<Button type="primary" onClick={goBack}>
{t('common::back')}
</Button>);
return (<Card title={t('title')} extra={<Tag color={iStateColor}>{t(`pay:v.iState.${iState}`)}</Tag>}>
<div className={Styles.container}>
<div className={Styles.detail}>
<Descriptions column={1} bordered items={[
{
key: '0',
label: t('type.label'),
children: <span className={Styles.value}>{t(`type.${type}`)}</span>,
},
{
key: '1',
label: t('pay:attr.price'),
children: <span className={Styles.value}>{`${t('common::pay.symbol')} ${CentToString(price, 2)}`}</span>,
},
{
key: '2',
label: t('pay:attr.channel'),
children: <span className={Styles.value}>{t(`payChannel::${channel}`)}</span>,
}
]}/>
</div>
<div className={Styles.oper}>
<RenderPayMeta pay={pay} t={t} notSameApp={notSameApp} payConfig={payConfig} updateMeta={(meta) => update({ meta })} metaUpdatable={metaUpdatable}/>
</div>
<div className={Styles.btn}>
{BtnPart}
</div>
</div>
</Card>);
}
return null;
}