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

237 lines
10 KiB
JavaScript

import React, { useEffect, useState } from 'react';
import { Input, Tag, Card, QRCode, Form, Descriptions, Typography, Alert, Button, Modal, Radio } 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';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
dayJs.extend(duration);
export function RenderOffline(props) {
const { pay, t, offline, offlines, updateOfflineId, updateExternalId } = props;
const { iState, phantom3, externalId } = pay;
const { type, channel, name, qrCode, color } = offline;
const [show, setShow] = useState(false);
const [showQrCode, setShowQrCode] = useState(false);
const items2 = [
<Form.Item key="type" label={<span className={Styles.bold}>{`${t('channel.prefix')}`}</span>}>
{iState === 'paying' && offlines.length > 1 ?
<Button onClick={() => setShow(true)}>{t(`offlineAccount:v.type.${type}`)}</Button> :
<span className={Styles.value}>{t(`offlineAccount:v.type.${type}`)}</span>}
</Form.Item>,
<Form.Item key="code" label={<span className={Styles.bold}>{t('code.label')}</span>}>
<Tag color="red">
<span style={{ fontSize: 'large' }}>{phantom3}</span>
</Tag>
</Form.Item>,
<Form.Item key="externalId" label={<span className={Styles.bold}>{t('externalId.label')}</span>}>
<Input value={externalId || ''} onChange={({ currentTarget }) => {
const { value } = currentTarget;
updateExternalId(value);
}} placeholder={t('externalId.help')} disabled={iState !== 'paying'}/>
</Form.Item>
];
if (type === 'bank') {
items2.push(<Form.Item key="channel" label={t('offlineAccount::label.channel.bank')}>
<span className={Styles.value}>{channel}</span>
</Form.Item>, <Form.Item key="name" label={t('offlineAccount::label.name.bank')}>
<span className={Styles.value}>{name}</span>
</Form.Item>, <Form.Item key="qrCode" label={t('offlineAccount::label.qrCode.bank')}>
<span className={Styles.value}>{qrCode}</span>
</Form.Item>);
}
else {
if (type === 'others') {
items2.push(<Form.Item key="channel" label={t('offlineAccount::label.channel.others')}>
<span className={Styles.value}>{channel}</span>
</Form.Item>);
}
if (name) {
items2.push(<Form.Item key="name" label={t(`offlineAccount::label.name.${type}`)}>
<span className={Styles.value}>{name}</span>
</Form.Item>);
}
if (qrCode) {
items2.push(<Form.Item key="qrCode" label={t(`offlineAccount::label.qrCode.${type}`)}>
<span className={Styles.qrCode} onClick={() => setShowQrCode(true)}>
<QRCode value={qrCode} size={180} color={color}/>
</span>
</Form.Item>);
}
}
return (<>
<Modal open={show} onCancel={() => setShow(false)} closeIcon={null} footer={null}>
<Radio.Group onChange={({ target }) => {
updateOfflineId(target.value);
setShow(false);
}} value={offline.id}>
{offlines.map((ele, idx) => (<Radio key={idx} value={ele.id}>
{t(`offlineAccount:v.type.${ele.type}`)}
</Radio>))}
</Radio.Group>
</Modal>
<Modal open={showQrCode} closeIcon={null} footer={null} onCancel={() => setShowQrCode(false)}>
<QRCode value={qrCode} size={400} color={color}/>
</Modal>
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 12 }} layout="horizontal" style={{ width: '100%', marginTop: 12 }}>
{items2}
</Form>
</>);
return null;
}
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, wpProduct, 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 (wpProduct.type) {
case 'native': {
if (iState === 'paying') {
return (<div className={Styles.paid}>
<Counter deadline={timeoutAt}/>
<QRCode value={externalId} size={280} color="#04BE02"/>
<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>
</div>);
}
break;
}
}
return null;
}
function RenderPayMeta(props) {
const { pay, notSameApp, t, offlines, offline, updateOfflineId, updateExternalId } = props;
const { iState, entity } = pay;
if (entity !== 'offlineAccount' && notSameApp) {
return <Alert type='warning' message={t('notSameApp')}/>;
}
switch (entity) {
case 'offlineAccount': {
if (offline && offlines) {
return (<>
{iState === 'paying' && <Alert type='info' message={t('code.help')} style={{ marginBottom: 10 }}/>}
<RenderOffline t={t} pay={pay} offline={offline} offlines={offlines} updateOfflineId={updateOfflineId} updateExternalId={updateExternalId}/>
</>);
}
return null;
}
case 'wpProduct': {
return <RenderWechatPay pay={pay} t={t}/>;
}
}
// todo 要支持注入第三方支付的渲染组件
return null;
}
export default function Render(props) {
const { pay, iStateColor, notSameApp, type, startPayable, oakExecutable, onClose, closable, offline, offlines } = props.data;
const { t, update, execute, clean, goBack, startPay } = props.methods;
if (pay) {
const { iState, price, entity } = pay;
const BtnPart = startPayable ? (<Button style={{ backgroundColor: '#04BE02' }} className={Styles.btnWechatPay} onClick={() => startPay()}>
{t('pay')}
</Button>) : oakExecutable === true ? (<>
<Button type="primary" onClick={() => execute()}>
{t('common::action.update')}
</Button>
<Button onClick={() => clean()}>
{t('common::reset')}
</Button>
</>) : closable ? (<Button danger onClick={() => {
Modal.confirm({
title: t('cc.title'),
content: t('cc.content'),
onOk: async () => {
await execute('close');
onClose();
},
okText: t('common::confirm'),
cancelText: t('common::action.cancel'),
});
}}>
{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.entity'),
children: <span className={Styles.value}>{t(`payChannel::${entity}`)}</span>,
}
]}/>
</div>
<div className={Styles.oper}>
<RenderPayMeta pay={pay} t={t} notSameApp={notSameApp} offline={offline} offlines={offlines} updateOfflineId={async (entityId) => {
execute(undefined, undefined, undefined, [
{
entity: 'pay',
operation: {
id: await generateNewIdAsync(),
action: 'update',
data: {
entity: 'offlineAccount',
entityId,
},
filter: {
id: props.data.oakId,
},
}
}
]);
}} updateExternalId={(externalId) => {
update({
externalId,
});
}}/>
</div>
<div className={Styles.btn}>
{BtnPart}
</div>
</div>
</Card>);
}
return null;
}