From fe8caf3da5b6212a91444f1451492fb196763384 Mon Sep 17 00:00:00 2001 From: Xc Date: Tue, 7 May 2024 21:32:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BA=86N=E5=A4=9Apay?= =?UTF-8?q?=E7=9A=84=E9=80=BB=E8=BE=91=E5=92=8Ccomponent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- es/checkers/order.js | 6 +- es/checkers/pay.js | 3 + es/components/account/detail/index.js | 2 +- es/components/order/pay/index.d.ts | 7 + es/components/order/pay/index.js | 114 ++++++++ es/components/order/pay/index.json | 1 + es/components/order/pay/locales/zh-CN.json | 8 + es/components/order/pay/web.pc.d.ts | 23 ++ es/components/order/pay/web.pc.js | 71 +++++ es/components/order/pay/web.pc.module.less | 60 ++++ es/components/pay/channelPicker/index.d.ts | 2 +- es/configuration/attrUpdateMatrix.js | 11 +- es/data/i18n.js | 15 + es/entities/Order.js | 2 +- es/features/Pay.d.ts | 2 +- es/oak-app-domain/Order/Action.js | 2 +- es/triggers/pay.js | 222 ++++++++------- es/utils/payClazz/Account.js | 4 + es/watchers/pay.js | 6 +- lib/checkers/order.js | 6 +- lib/checkers/pay.js | 3 + lib/configuration/attrUpdateMatrix.js | 9 + lib/data/i18n.js | 15 + lib/entities/Order.js | 2 +- lib/oak-app-domain/Order/Action.js | 2 +- lib/triggers/pay.js | 224 ++++++++------- lib/utils/payClazz/Account.js | 4 + lib/watchers/pay.js | 6 +- src/checkers/order.ts | 6 +- src/checkers/pay.ts | 3 + src/components/accountOper/list/web.tsx | 14 + src/components/order/pay/index.json | 1 + src/components/order/pay/index.ts | 130 +++++++++ src/components/order/pay/info.module.less | 25 ++ src/components/order/pay/info.tsx | 14 + src/components/order/pay/locales/zh-CN.json | 8 + .../order/pay/web.mobile.module.less | 36 +++ src/components/order/pay/web.pc.module.less | 36 +++ src/components/order/pay/web.pc.tsx | 166 ++++++++++++ src/components/order/pay/web.tsx | 167 ++++++++++++ src/components/pay/channelPicker/index.ts | 2 +- .../pay/channelPicker/web.mobile.module.less | 5 + src/components/pay/channelPicker/web.tsx | 1 + src/components/pay/detail/index.ts | 1 + .../pay/detail/web.mobile.module.less | 35 +++ src/components/pay/detail/web.pc.tsx | 40 +-- src/components/pay/detail/web.tsx | 256 +++++++++++++++++- src/configuration/attrUpdateMatrix.ts | 11 +- src/data/i18n.ts | 15 + src/entities/Order.ts | 2 +- src/entities/Pay.ts | 2 +- src/triggers/pay.ts | 224 ++++++++------- src/utils/payClazz/Account.ts | 4 + src/watchers/pay.ts | 6 +- 54 files changed, 1683 insertions(+), 359 deletions(-) create mode 100644 es/components/order/pay/index.d.ts create mode 100644 es/components/order/pay/index.js create mode 100644 es/components/order/pay/index.json create mode 100644 es/components/order/pay/locales/zh-CN.json create mode 100644 es/components/order/pay/web.pc.d.ts create mode 100644 es/components/order/pay/web.pc.js create mode 100644 es/components/order/pay/web.pc.module.less create mode 100644 src/components/accountOper/list/web.tsx create mode 100644 src/components/order/pay/index.json create mode 100644 src/components/order/pay/index.ts create mode 100644 src/components/order/pay/info.module.less create mode 100644 src/components/order/pay/info.tsx create mode 100644 src/components/order/pay/locales/zh-CN.json create mode 100644 src/components/order/pay/web.mobile.module.less create mode 100644 src/components/order/pay/web.pc.module.less create mode 100644 src/components/order/pay/web.pc.tsx create mode 100644 src/components/order/pay/web.tsx create mode 100644 src/components/pay/detail/web.mobile.module.less diff --git a/es/checkers/order.js b/es/checkers/order.js index 985d31b7..d21e4cfd 100644 --- a/es/checkers/order.js +++ b/es/checkers/order.js @@ -5,7 +5,11 @@ const checkers = [ action: 'create', checker: (operation, context) => { const { data } = operation; - data.creatorId = context.getCurrentUserId(); + if (!data.creatorId) { + data.creatorId = context.getCurrentUserId(); + } + data.paid = 0; + data.refunded = 0; return 1; } } diff --git a/es/checkers/pay.js b/es/checkers/pay.js index 622c19cb..69aca5c0 100644 --- a/es/checkers/pay.js +++ b/es/checkers/pay.js @@ -11,6 +11,9 @@ const checkers = [ data.paid = 0; data.applicationId = context.getApplicationId(); data.creatorId = context.getCurrentUserId(); + if (!data.meta) { + data.meta = {}; + } }, }, { diff --git a/es/components/account/detail/index.js b/es/components/account/detail/index.js index c5052df7..8a726687 100644 --- a/es/components/account/detail/index.js +++ b/es/components/account/detail/index.js @@ -58,7 +58,7 @@ export default OakComponent({ }, actions: ['deposit', 'withdraw'], methods: { - async createDepositPay(price, channel, meta, success) { + async createDepositPay(price, channel, meta) { const payId = await generateNewIdAsync(); const { oakId } = this.props; await this.execute(undefined, undefined, undefined, [ diff --git a/es/components/order/pay/index.d.ts b/es/components/order/pay/index.d.ts new file mode 100644 index 00000000..af8911c6 --- /dev/null +++ b/es/components/order/pay/index.d.ts @@ -0,0 +1,7 @@ +import { EntityDict } from "../../../oak-app-domain"; +declare const _default: (props: import("oak-frontend-base").ReactComponentProps void; +}>) => React.ReactElement; +export default _default; diff --git a/es/components/order/pay/index.js b/es/components/order/pay/index.js new file mode 100644 index 00000000..294b7c6e --- /dev/null +++ b/es/components/order/pay/index.js @@ -0,0 +1,114 @@ +import { PAY_CHANNEL_ACCOUNT_NAME } from "../../../types/PayConfig"; +import { generateNewId } from "oak-domain/lib/utils/uuid"; +export default OakComponent({ + entity: 'order', + projection: { + id: 1, + price: 1, + paid: 1, + iState: 1, + pay$order: { + $entity: 'pay', + data: { + id: 1, + price: 1, + paid: 1, + iState: 1, + }, + filter: { + iState: 'paying', + }, + }, + }, + isList: false, + properties: { + accountId: '', // 是否可以使用帐户中的余额抵扣 + accountAvailMax: 0, // 本次交易可以使用的帐户中的Avail max值,调用者自己保证此数值的一致性,不要扣成负数 + onPayReady: (operation, payId) => undefined, + }, + formData({ data }) { + const payConfig = this.features.pay.getPayConfigs(); + const accountConfig = payConfig?.find(ele => ele.channel === PAY_CHANNEL_ACCOUNT_NAME); + const payConfig2 = payConfig?.filter(ele => ele !== accountConfig); + const activePay = data && data.pay$order?.[0]; + const { accountPrice } = this.state; + return { + order: data, + activePay, + accountConfig, + payConfig: payConfig2, + rest: data ? data.price - data.paid - accountPrice : 0, + legal: !!(data?.['#oakLegalActions']?.includes('startPaying')) + }; + }, + features: ['application'], + data: { + useAccount: false, + accountPrice: 0, + channel: '', + meta: undefined, + }, + methods: { + setUseAccount(v) { + this.setState({ useAccount: v }, () => this.tryCreatePay()); + }, + setAccountPrice(price) { + const { order } = this.state; + this.setState({ accountPrice: price, rest: order.price - order.paid - price }, () => this.tryCreatePay()); + }, + onPickChannel(channel) { + this.setState({ + channel, + }, () => this.tryCreatePay()); + }, + onSetChannelMeta(meta) { + this.setState({ + meta, + }, () => this.tryCreatePay()); + }, + tryCreatePay() { + const { oakId, accountId } = this.props; + const { useAccount, accountPrice, channel, meta, order } = this.state; + const pays = []; + let rest = order.price - order.paid; + let payId = ''; + if (useAccount && accountPrice) { + pays.push({ + id: generateNewId(), + channel: PAY_CHANNEL_ACCOUNT_NAME, + price: accountPrice, + accountId, + }); + rest = rest - accountPrice; + } + if (rest && channel) { + payId = generateNewId(); + pays.push({ + id: payId, + channel, + meta, + price: rest, + }); + rest = 0; + } + const { onPayReady } = this.props; + if (rest === 0) { + onPayReady({ + id: generateNewId(), + action: 'startPaying', + data: { + pay$order: pays.map(ele => ({ + id: generateNewId(), + action: 'create', + data: ele, + })), + }, + filter: { + id: oakId, + }, + }, payId); + } + } + }, + actions: ['startPaying'], +}); diff --git a/es/components/order/pay/index.json b/es/components/order/pay/index.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/es/components/order/pay/index.json @@ -0,0 +1 @@ +{} diff --git a/es/components/order/pay/locales/zh-CN.json b/es/components/order/pay/locales/zh-CN.json new file mode 100644 index 00000000..3dbb3b00 --- /dev/null +++ b/es/components/order/pay/locales/zh-CN.json @@ -0,0 +1,8 @@ +{ + "price": "订单金额", + "choose": "请选择支付方式(%{price}元)", + "useAccount": "使用余额抵扣", + "accountMax": "当前可用余额为%{max}元", + "illegalState": "订单的当前状态【%{state}】无法开始支付", + "paying": "有一个正在支付的订单" +} diff --git a/es/components/order/pay/web.pc.d.ts b/es/components/order/pay/web.pc.d.ts new file mode 100644 index 00000000..75f5c4f0 --- /dev/null +++ b/es/components/order/pay/web.pc.d.ts @@ -0,0 +1,23 @@ +import React from 'react'; +import { WebComponentProps } from 'oak-frontend-base'; +import { EntityDict } from '../../../oak-app-domain'; +import { AccountPayConfig, PayConfig } from '../../../types/PayConfig'; +export default function Render(props: WebComponentProps void; + onPickChannel: (channel: string) => void; + onSetChannelMeta: (meta?: object) => void; + setUseAccount: (v: boolean) => void; +}>): React.JSX.Element | null; diff --git a/es/components/order/pay/web.pc.js b/es/components/order/pay/web.pc.js new file mode 100644 index 00000000..dc6e9481 --- /dev/null +++ b/es/components/order/pay/web.pc.js @@ -0,0 +1,71 @@ +import React from 'react'; +import { ToYuan, ToCent } from 'oak-domain/lib/utils/money'; +import Styles from './web.pc.module.less'; +import PayChannelPicker from '../../pay/channelPicker'; +import { Divider, Checkbox, InputNumber, Flex, Result } from 'antd'; +function RenderPayChannel(props) { + const { price, payConfig, t, channel, meta, onPick, onSetMeta } = props; + return (
+
+
+ {t('choose', { price: ToYuan(price) })} +
+ + +
+
); +} +function RenderAccountPay(props) { + const { max, t, accountPrice, setAccountPrice, useAccount, setUseAccount, accountAvail } = props; + return (
+
+ { + setUseAccount(!useAccount); + if (useAccount) { + setAccountPrice(0); + } + }}> + {t('useAccount')} + + {useAccount && (<> + + + { + if (typeof v === 'number') { + setAccountPrice(Math.floor(ToCent(v))); + } + }}/> +
{t('accountMax', { max: ToYuan(accountAvail) })}
+
+ )} +
+
); +} +export default function Render(props) { + const { accountId, accountAvailMax, legal, accountPrice, useAccount, order, activePay, payConfig, channel, meta, rest } = props.data; + const { t, setAccountPrice, onPickChannel, onSetChannelMeta, setUseAccount } = props.methods; + if (order) { + if (activePay) { + return (); + } + if (!legal) { + return (); + } + return (
+
+
{t('price')}
+
+
{t('common::pay.symbol')}
+
{ToYuan(order.price)}
+
+
+ {accountId && accountAvailMax &&
+ +
} + {!!(rest && rest > 0) &&
+ +
} +
); + } + return null; +} diff --git a/es/components/order/pay/web.pc.module.less b/es/components/order/pay/web.pc.module.less new file mode 100644 index 00000000..86f4c947 --- /dev/null +++ b/es/components/order/pay/web.pc.module.less @@ -0,0 +1,60 @@ +.container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: stretch; + background-color: var(--oak-bg-color-page); + // min-height: 500px; + + .info { + margin: 8px; + height: 140px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: var(--oak-color-info); + + .should { + font-size: large; + font-weight: bold; + } + + .price { + display: flex; + flex-direction: row; + padding: 3px; + font-size: xx-large; + font-weight: bolder; + color: var(--oak-color-primary) + } + } + + .ctrl { + margin: 8px; + margin-top: 10px; + flex: 1; + + .pc1 { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + margin-top: 4px; + + .content { + background-color: var(--oak-bg-color-container); + padding: 4px; + border-radius: 2px; + padding: 16px; + + .tips { + color: var(--oak-color-warning); + font-size: small; + margin-left: 10px; + } + } + } + } +} \ No newline at end of file diff --git a/es/components/pay/channelPicker/index.d.ts b/es/components/pay/channelPicker/index.d.ts index 8ef03986..7662ee3e 100644 --- a/es/components/pay/channelPicker/index.d.ts +++ b/es/components/pay/channelPicker/index.d.ts @@ -4,7 +4,7 @@ import { PayConfig } from "../../../types/PayConfig"; * 支持自定义支付通道的选择器注入 * 未来遇到真正需求时再实现,by Xc 20240426 */ -type ExtraPicker = { +export type ExtraPicker = { label: string; name: string; icon: React.ForwardRefExoticComponent; diff --git a/es/configuration/attrUpdateMatrix.js b/es/configuration/attrUpdateMatrix.js index ea68926f..f7da1c52 100644 --- a/es/configuration/attrUpdateMatrix.js +++ b/es/configuration/attrUpdateMatrix.js @@ -1,4 +1,4 @@ -import { PAY_CHANNEL_OFFLINE_NAME } from '../types/PayConfig'; +import { PAY_CHANNEL_ACCOUNT_NAME, PAY_CHANNEL_OFFLINE_NAME } from '../types/PayConfig'; const attrUpdateMatrix = { pay: { meta: { @@ -13,6 +13,15 @@ const attrUpdateMatrix = { } ] } + }, + accountOper$entity: { + actions: ['succeedPaying'], + filter: { + accountId: { + $exists: true, + }, + channel: PAY_CHANNEL_ACCOUNT_NAME, + } } } }; diff --git a/es/data/i18n.js b/es/data/i18n.js index 83065b7e..3c1371f1 100644 --- a/es/data/i18n.js +++ b/es/data/i18n.js @@ -45,6 +45,21 @@ const i18ns = [ position: "src/components/accountOper/list", data: {} }, + { + id: "d39ddaee363037f4557bad511722e250", + namespace: "oak-pay-business-c-order-pay", + language: "zh-CN", + module: "oak-pay-business", + position: "src/components/order/pay", + data: { + "price": "订单金额", + "choose": "请选择支付方式(%{price}元)", + "useAccount": "使用余额抵扣", + "accountMax": "当前可用余额为%{max}元", + "illegalState": "订单的当前状态【%{state}】无法开始支付", + "paying": "有一个正在支付的订单" + } + }, { id: "3500cc465492fca3797b75c9c0dbf517", namespace: "oak-pay-business-c-pay-channelPicker", diff --git a/es/entities/Order.js b/es/entities/Order.js index 83479361..ac0aa6c0 100644 --- a/es/entities/Order.js +++ b/es/entities/Order.js @@ -1,7 +1,7 @@ ; export const IActionDef = { stm: { - startPaying: ['unpaid', 'paying'], + startPaying: [['unpaid', 'partiallyPaid'], 'paying'], payAll: [['unpaid', 'paying', 'partiallyPaid'], 'paid'], payPartially: [['unpaid', 'paying'], 'partiallyPaid'], payNone: ['paying', 'unpaid'], diff --git a/es/features/Pay.d.ts b/es/features/Pay.d.ts index fc952058..d7cb8b7a 100644 --- a/es/features/Pay.d.ts +++ b/es/features/Pay.d.ts @@ -5,5 +5,5 @@ export default class Pay extends Feature { private application; constructor(application: GeneralFeatures['application']); getPayChannels(): string[]; - getPayConfigs(): (import("../types/PayConfig").AccountPayConfig | import("../types/PayConfig").WechatPayConfig | import("../types/PayConfig").OfflinePayConfig)[]; + getPayConfigs(): (import("../types/PayConfig").WechatPayConfig | import("../types/PayConfig").AccountPayConfig | import("../types/PayConfig").OfflinePayConfig)[]; } diff --git a/es/oak-app-domain/Order/Action.js b/es/oak-app-domain/Order/Action.js index e1bb114b..f2379886 100644 --- a/es/oak-app-domain/Order/Action.js +++ b/es/oak-app-domain/Order/Action.js @@ -1,6 +1,6 @@ export const IActionDef = { stm: { - startPaying: ['unpaid', 'paying'], + startPaying: [['unpaid', 'partiallyPaid'], 'paying'], payAll: [['unpaid', 'paying', 'partiallyPaid'], 'paid'], payPartially: [['unpaid', 'paying'], 'partiallyPaid'], payNone: ['paying', 'unpaid'], diff --git a/es/triggers/pay.js b/es/triggers/pay.js index 5915524d..b1ae1953 100644 --- a/es/triggers/pay.js +++ b/es/triggers/pay.js @@ -50,91 +50,95 @@ async function changeOrderStateByPay(filter, context, option) { payRefunded += refunded; } } - if (hasPaying) { - // 一定是在支付状态 - assert(!hasRefunding && payRefunded === 0 && payPaid < orderPrice); - if (orderIState !== 'paying' || orderPaid !== payPaid) { - await context.operate('order', { - id: await generateNewIdAsync(), - action: 'startPaying', - data: { - paid: payPaid, - }, - filter: { - id: orderId, - }, - }, option); - return 1; + const closeFn = context.openRootMode(); + try { + if (hasPaying) { + // 一定是在支付状态 + assert(!hasRefunding && payRefunded === 0 && payPaid < orderPrice); + if (orderIState !== 'paying' || orderPaid !== payPaid) { + await context.operate('order', { + id: await generateNewIdAsync(), + action: 'startPaying', + data: { + paid: payPaid, + }, + filter: { + id: orderId, + }, + }, option); + } } + else if (hasRefunding) { + assert(!hasPaying && payPaid === orderPrice && payPaid === orderPaid && payRefunded < orderPrice); + if (orderIState !== 'refunding' || orderRefunded !== payRefunded) { + await context.operate('order', { + id: await generateNewIdAsync(), + action: 'startRefunding', + data: { + refunded: payRefunded, + }, + filter: { + id: orderId, + }, + }, option); + } + } + else if (payRefunded) { + // 已经在退款流程当中 + assert(payPaid === orderPrice && payPaid === orderPaid); + const iState = payPaid === orderPrice ? 'refunded' : 'partiallyRefunded'; + if (orderIState !== iState || orderRefunded !== payRefunded) { + const action = payPaid === orderPrice ? 'refundAll' : 'refundPartially'; + await context.operate('order', { + id: await generateNewIdAsync(), + action, + data: { + refunded: payRefunded, + }, + filter: { + id: orderId, + }, + }, option); + } + } + else if (payPaid) { + // 在支付流程当中 + assert(orderRefunded === 0); + const iState = payPaid === orderPrice ? 'paid' : 'partiallyPaid'; + if (orderIState !== iState || orderPaid !== payPaid) { + const action = payPaid === orderPrice ? 'payAll' : 'payPartially'; + await context.operate('order', { + id: await generateNewIdAsync(), + action, + data: { + paid: payPaid, + }, + filter: { + id: orderId, + }, + }, option); + } + } + else { + const iState = 'unpaid'; + assert(orderRefunded === 0 && orderPaid === 0); + if (orderIState !== iState) { + await context.operate('order', { + id: await generateNewIdAsync(), + action: 'payNone', + data: {}, + filter: { + id: orderId, + }, + }, option); + } + } + closeFn(); + return 1; } - else if (hasRefunding) { - assert(!hasPaying && payPaid === orderPrice && payPaid === orderPaid && payRefunded < orderPrice); - if (orderIState !== 'refunding' || orderRefunded !== payRefunded) { - await context.operate('order', { - id: await generateNewIdAsync(), - action: 'startRefunding', - data: { - refunded: payRefunded, - }, - filter: { - id: orderId, - }, - }, option); - return 1; - } - } - else if (payRefunded) { - // 已经在退款流程当中 - assert(payPaid === orderPrice && payPaid === orderPaid); - const iState = payPaid === orderPrice ? 'refunded' : 'partiallyRefunded'; - if (orderIState !== iState || orderRefunded !== payRefunded) { - const action = payPaid === orderPrice ? 'refundAll' : 'refundPartially'; - await context.operate('order', { - id: await generateNewIdAsync(), - action, - data: { - refunded: payRefunded, - }, - filter: { - id: orderId, - }, - }, option); - return 1; - } - } - else if (payPaid) { - // 在支付流程当中 - assert(orderRefunded === 0); - const iState = payPaid === orderPrice ? 'paid' : 'partiallyPaid'; - if (orderIState !== iState || orderPaid !== payPaid) { - const action = payPaid === orderPrice ? 'payAll' : 'payPartially'; - await context.operate('order', { - id: await generateNewIdAsync(), - action, - data: { - paid: payPaid, - }, - filter: { - id: orderId, - }, - }, option); - return 1; - } - } - else { - const iState = 'unpaid'; - assert(orderRefunded === 0 && orderPaid === 0); - if (orderIState !== iState) { - await context.operate('order', { - id: await generateNewIdAsync(), - action: 'payNone', - data: {}, - filter: { - id: orderId, - }, - }, option); - return 1; - } + catch (err) { + closeFn(); + throw err; } } const triggers = [ @@ -147,32 +151,42 @@ const triggers = [ const { data } = operation; assert(!(data instanceof Array)); const { accountId, price, orderId, id } = data; - if (orderId) { + /* if (orderId) { if (accountId) { // 使用帐户支付,直接成功并扣款 - await context.operate('pay', { - id: await generateNewIdAsync(), - action: 'payAll', - data: { - accountOper$entity: [ - { - id: await generateNewIdAsync(), - action: 'create', - data: { + const close = context.openRootMode(); + try { + await context.operate('pay', { + id: await generateNewIdAsync(), + action: 'succeedPaying', + data: { + accountOper$entity: [ + { id: await generateNewIdAsync(), - totalPlus: -price, - availPlus: -price, - }, - } - ] - }, - filter: { - id, - } - }, option); + action: 'create', + data: { + id: await generateNewIdAsync(), + totalPlus: -price!, + availPlus: -price!, + type: 'consume', + accountId, + }, + } + ] + }, + filter: { + id, + } + }, option); + close(); + } + catch (err: any) { + close(); + throw err; + } return 1; } - } + } */ // 其余情况都是进入paying流程 await context.operate('pay', { id: await generateNewIdAsync(), diff --git a/es/utils/payClazz/Account.js b/es/utils/payClazz/Account.js index 6696dd5a..848c3e2d 100644 --- a/es/utils/payClazz/Account.js +++ b/es/utils/payClazz/Account.js @@ -18,9 +18,13 @@ export default class Account { id: await generateNewIdAsync(), totalPlus: -price, availPlus: -price, + type: 'consume', + accountId, }, } ]; + data.meta = {}; + data.paid = pay.price; } getState(pay) { throw new Error("account类型的pay不应该需要查询此状态"); diff --git a/es/watchers/pay.js b/es/watchers/pay.js index cfcd8085..face6a13 100644 --- a/es/watchers/pay.js +++ b/es/watchers/pay.js @@ -24,11 +24,12 @@ const watchers = [ fn: async (context, data) => { const results = []; for (const pay of data) { - const { applicationId, channel, timeoutAt } = pay; + const { applicationId, channel, timeoutAt, price } = pay; const clazz = await getPayClazz(applicationId, channel, context); const iState = await clazz.getState(pay); if (iState !== pay.iState) { let action = 'close'; + const data2 = {}; switch (iState) { case 'closed': { // action = 'close'; @@ -36,6 +37,7 @@ const watchers = [ } case 'paid': { action = 'succeedPaying'; + data2.paid = price; break; } default: { @@ -45,7 +47,7 @@ const watchers = [ const result = await context.operate('pay', { id: await generateNewIdAsync(), action, - data: {}, + data: data2, filter: { id: pay.id, } diff --git a/lib/checkers/order.js b/lib/checkers/order.js index 8734220a..34739dfc 100644 --- a/lib/checkers/order.js +++ b/lib/checkers/order.js @@ -7,7 +7,11 @@ const checkers = [ action: 'create', checker: (operation, context) => { const { data } = operation; - data.creatorId = context.getCurrentUserId(); + if (!data.creatorId) { + data.creatorId = context.getCurrentUserId(); + } + data.paid = 0; + data.refunded = 0; return 1; } } diff --git a/lib/checkers/pay.js b/lib/checkers/pay.js index ff40e2ef..163f52e2 100644 --- a/lib/checkers/pay.js +++ b/lib/checkers/pay.js @@ -13,6 +13,9 @@ const checkers = [ data.paid = 0; data.applicationId = context.getApplicationId(); data.creatorId = context.getCurrentUserId(); + if (!data.meta) { + data.meta = {}; + } }, }, { diff --git a/lib/configuration/attrUpdateMatrix.js b/lib/configuration/attrUpdateMatrix.js index 5b60ac21..e40062ee 100644 --- a/lib/configuration/attrUpdateMatrix.js +++ b/lib/configuration/attrUpdateMatrix.js @@ -15,6 +15,15 @@ const attrUpdateMatrix = { } ] } + }, + accountOper$entity: { + actions: ['succeedPaying'], + filter: { + accountId: { + $exists: true, + }, + channel: PayConfig_1.PAY_CHANNEL_ACCOUNT_NAME, + } } } }; diff --git a/lib/data/i18n.js b/lib/data/i18n.js index 937bc0a1..4ec10cd1 100644 --- a/lib/data/i18n.js +++ b/lib/data/i18n.js @@ -47,6 +47,21 @@ const i18ns = [ position: "src/components/accountOper/list", data: {} }, + { + id: "d39ddaee363037f4557bad511722e250", + namespace: "oak-pay-business-c-order-pay", + language: "zh-CN", + module: "oak-pay-business", + position: "src/components/order/pay", + data: { + "price": "订单金额", + "choose": "请选择支付方式(%{price}元)", + "useAccount": "使用余额抵扣", + "accountMax": "当前可用余额为%{max}元", + "illegalState": "订单的当前状态【%{state}】无法开始支付", + "paying": "有一个正在支付的订单" + } + }, { id: "3500cc465492fca3797b75c9c0dbf517", namespace: "oak-pay-business-c-pay-channelPicker", diff --git a/lib/entities/Order.js b/lib/entities/Order.js index 44a69747..e0bb2ad5 100644 --- a/lib/entities/Order.js +++ b/lib/entities/Order.js @@ -4,7 +4,7 @@ exports.entityDesc = exports.IActionDef = void 0; ; exports.IActionDef = { stm: { - startPaying: ['unpaid', 'paying'], + startPaying: [['unpaid', 'partiallyPaid'], 'paying'], payAll: [['unpaid', 'paying', 'partiallyPaid'], 'paid'], payPartially: [['unpaid', 'paying'], 'partiallyPaid'], payNone: ['paying', 'unpaid'], diff --git a/lib/oak-app-domain/Order/Action.js b/lib/oak-app-domain/Order/Action.js index a4f63b55..db9b7f4e 100644 --- a/lib/oak-app-domain/Order/Action.js +++ b/lib/oak-app-domain/Order/Action.js @@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.actionDefDict = exports.actions = exports.IActionDef = void 0; exports.IActionDef = { stm: { - startPaying: ['unpaid', 'paying'], + startPaying: [['unpaid', 'partiallyPaid'], 'paying'], payAll: [['unpaid', 'paying', 'partiallyPaid'], 'paid'], payPartially: [['unpaid', 'paying'], 'partiallyPaid'], payNone: ['paying', 'unpaid'], diff --git a/lib/triggers/pay.js b/lib/triggers/pay.js index 14a7ad8f..5ff19e22 100644 --- a/lib/triggers/pay.js +++ b/lib/triggers/pay.js @@ -53,91 +53,95 @@ async function changeOrderStateByPay(filter, context, option) { payRefunded += refunded; } } - if (hasPaying) { - // 一定是在支付状态 - (0, assert_1.default)(!hasRefunding && payRefunded === 0 && payPaid < orderPrice); - if (orderIState !== 'paying' || orderPaid !== payPaid) { - await context.operate('order', { - id: await (0, uuid_1.generateNewIdAsync)(), - action: 'startPaying', - data: { - paid: payPaid, - }, - filter: { - id: orderId, - }, - }, option); - return 1; + const closeFn = context.openRootMode(); + try { + if (hasPaying) { + // 一定是在支付状态 + (0, assert_1.default)(!hasRefunding && payRefunded === 0 && payPaid < orderPrice); + if (orderIState !== 'paying' || orderPaid !== payPaid) { + await context.operate('order', { + id: await (0, uuid_1.generateNewIdAsync)(), + action: 'startPaying', + data: { + paid: payPaid, + }, + filter: { + id: orderId, + }, + }, option); + } } + else if (hasRefunding) { + (0, assert_1.default)(!hasPaying && payPaid === orderPrice && payPaid === orderPaid && payRefunded < orderPrice); + if (orderIState !== 'refunding' || orderRefunded !== payRefunded) { + await context.operate('order', { + id: await (0, uuid_1.generateNewIdAsync)(), + action: 'startRefunding', + data: { + refunded: payRefunded, + }, + filter: { + id: orderId, + }, + }, option); + } + } + else if (payRefunded) { + // 已经在退款流程当中 + (0, assert_1.default)(payPaid === orderPrice && payPaid === orderPaid); + const iState = payPaid === orderPrice ? 'refunded' : 'partiallyRefunded'; + if (orderIState !== iState || orderRefunded !== payRefunded) { + const action = payPaid === orderPrice ? 'refundAll' : 'refundPartially'; + await context.operate('order', { + id: await (0, uuid_1.generateNewIdAsync)(), + action, + data: { + refunded: payRefunded, + }, + filter: { + id: orderId, + }, + }, option); + } + } + else if (payPaid) { + // 在支付流程当中 + (0, assert_1.default)(orderRefunded === 0); + const iState = payPaid === orderPrice ? 'paid' : 'partiallyPaid'; + if (orderIState !== iState || orderPaid !== payPaid) { + const action = payPaid === orderPrice ? 'payAll' : 'payPartially'; + await context.operate('order', { + id: await (0, uuid_1.generateNewIdAsync)(), + action, + data: { + paid: payPaid, + }, + filter: { + id: orderId, + }, + }, option); + } + } + else { + const iState = 'unpaid'; + (0, assert_1.default)(orderRefunded === 0 && orderPaid === 0); + if (orderIState !== iState) { + await context.operate('order', { + id: await (0, uuid_1.generateNewIdAsync)(), + action: 'payNone', + data: {}, + filter: { + id: orderId, + }, + }, option); + } + } + closeFn(); + return 1; } - else if (hasRefunding) { - (0, assert_1.default)(!hasPaying && payPaid === orderPrice && payPaid === orderPaid && payRefunded < orderPrice); - if (orderIState !== 'refunding' || orderRefunded !== payRefunded) { - await context.operate('order', { - id: await (0, uuid_1.generateNewIdAsync)(), - action: 'startRefunding', - data: { - refunded: payRefunded, - }, - filter: { - id: orderId, - }, - }, option); - return 1; - } - } - else if (payRefunded) { - // 已经在退款流程当中 - (0, assert_1.default)(payPaid === orderPrice && payPaid === orderPaid); - const iState = payPaid === orderPrice ? 'refunded' : 'partiallyRefunded'; - if (orderIState !== iState || orderRefunded !== payRefunded) { - const action = payPaid === orderPrice ? 'refundAll' : 'refundPartially'; - await context.operate('order', { - id: await (0, uuid_1.generateNewIdAsync)(), - action, - data: { - refunded: payRefunded, - }, - filter: { - id: orderId, - }, - }, option); - return 1; - } - } - else if (payPaid) { - // 在支付流程当中 - (0, assert_1.default)(orderRefunded === 0); - const iState = payPaid === orderPrice ? 'paid' : 'partiallyPaid'; - if (orderIState !== iState || orderPaid !== payPaid) { - const action = payPaid === orderPrice ? 'payAll' : 'payPartially'; - await context.operate('order', { - id: await (0, uuid_1.generateNewIdAsync)(), - action, - data: { - paid: payPaid, - }, - filter: { - id: orderId, - }, - }, option); - return 1; - } - } - else { - const iState = 'unpaid'; - (0, assert_1.default)(orderRefunded === 0 && orderPaid === 0); - if (orderIState !== iState) { - await context.operate('order', { - id: await (0, uuid_1.generateNewIdAsync)(), - action: 'payNone', - data: {}, - filter: { - id: orderId, - }, - }, option); - return 1; - } + catch (err) { + closeFn(); + throw err; } } const triggers = [ @@ -150,32 +154,42 @@ const triggers = [ const { data } = operation; (0, assert_1.default)(!(data instanceof Array)); const { accountId, price, orderId, id } = data; - if (orderId) { + /* if (orderId) { if (accountId) { // 使用帐户支付,直接成功并扣款 - await context.operate('pay', { - id: await (0, uuid_1.generateNewIdAsync)(), - action: 'payAll', - data: { - accountOper$entity: [ - { - id: await (0, uuid_1.generateNewIdAsync)(), - action: 'create', - data: { - id: await (0, uuid_1.generateNewIdAsync)(), - totalPlus: -price, - availPlus: -price, - }, - } - ] - }, - filter: { - id, - } - }, option); + const close = context.openRootMode(); + try { + await context.operate('pay', { + id: await generateNewIdAsync(), + action: 'succeedPaying', + data: { + accountOper$entity: [ + { + id: await generateNewIdAsync(), + action: 'create', + data: { + id: await generateNewIdAsync(), + totalPlus: -price!, + availPlus: -price!, + type: 'consume', + accountId, + }, + } + ] + }, + filter: { + id, + } + }, option); + close(); + } + catch (err: any) { + close(); + throw err; + } return 1; } - } + } */ // 其余情况都是进入paying流程 await context.operate('pay', { id: await (0, uuid_1.generateNewIdAsync)(), diff --git a/lib/utils/payClazz/Account.js b/lib/utils/payClazz/Account.js index e1354a46..baeaee9b 100644 --- a/lib/utils/payClazz/Account.js +++ b/lib/utils/payClazz/Account.js @@ -21,9 +21,13 @@ class Account { id: await (0, uuid_1.generateNewIdAsync)(), totalPlus: -price, availPlus: -price, + type: 'consume', + accountId, }, } ]; + data.meta = {}; + data.paid = pay.price; } getState(pay) { throw new Error("account类型的pay不应该需要查询此状态"); diff --git a/lib/watchers/pay.js b/lib/watchers/pay.js index 293478e0..977faa9d 100644 --- a/lib/watchers/pay.js +++ b/lib/watchers/pay.js @@ -27,11 +27,12 @@ const watchers = [ fn: async (context, data) => { const results = []; for (const pay of data) { - const { applicationId, channel, timeoutAt } = pay; + const { applicationId, channel, timeoutAt, price } = pay; const clazz = await (0, payClazz_1.getPayClazz)(applicationId, channel, context); const iState = await clazz.getState(pay); if (iState !== pay.iState) { let action = 'close'; + const data2 = {}; switch (iState) { case 'closed': { // action = 'close'; @@ -39,6 +40,7 @@ const watchers = [ } case 'paid': { action = 'succeedPaying'; + data2.paid = price; break; } default: { @@ -48,7 +50,7 @@ const watchers = [ const result = await context.operate('pay', { id: await (0, uuid_1.generateNewIdAsync)(), action, - data: {}, + data: data2, filter: { id: pay.id, } diff --git a/src/checkers/order.ts b/src/checkers/order.ts index 7821414c..837189c6 100644 --- a/src/checkers/order.ts +++ b/src/checkers/order.ts @@ -9,7 +9,11 @@ const checkers: Checker[] = [ action: 'create', checker: (operation, context) => { const { data } = operation as EntityDict['order']['CreateSingle']; - data.creatorId = context.getCurrentUserId()!; + if (!data.creatorId) { + data.creatorId = context.getCurrentUserId()!; + } + data.paid = 0; + data.refunded = 0; return 1; } diff --git a/src/checkers/pay.ts b/src/checkers/pay.ts index 0ac9a1f3..625e08fb 100644 --- a/src/checkers/pay.ts +++ b/src/checkers/pay.ts @@ -17,6 +17,9 @@ const checkers: Checker[] = [ data.paid = 0; data.applicationId = context.getApplicationId()!; data.creatorId = context.getCurrentUserId()!; + if (!data.meta) { + data.meta = {}; + } }, }, { diff --git a/src/components/accountOper/list/web.tsx b/src/components/accountOper/list/web.tsx new file mode 100644 index 00000000..47ecec55 --- /dev/null +++ b/src/components/accountOper/list/web.tsx @@ -0,0 +1,14 @@ +import React, { useState } from 'react'; +import { Input, Radio, Space, Form, Select, Flex } from 'antd'; +import { RowWithActions, WebComponentProps } from 'oak-frontend-base'; +import { EntityDict } from '@project/oak-app-domain'; +import Styles from './web.pc.module.less'; +import classNames from 'classnames'; + +export default function Render(props: WebComponentProps[]; +}>) { + const { accountOpers } = props.data; + const { t } = props.methods; + return '还没有实现,todo'; +} \ No newline at end of file diff --git a/src/components/order/pay/index.json b/src/components/order/pay/index.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/src/components/order/pay/index.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/components/order/pay/index.ts b/src/components/order/pay/index.ts new file mode 100644 index 00000000..86d611e6 --- /dev/null +++ b/src/components/order/pay/index.ts @@ -0,0 +1,130 @@ +import { EntityDict } from "@project/oak-app-domain"; +import { PAY_CHANNEL_ACCOUNT_NAME } from "../../../types/PayConfig"; +import { generateNewId, generateNewIdAsync } from "oak-domain/lib/utils/uuid"; +import assert from 'assert'; + +export default OakComponent({ + entity: 'order', + projection: { + id: 1, + price: 1, + paid: 1, + iState: 1, + pay$order: { + $entity: 'pay', + data: { + id: 1, + price: 1, + paid: 1, + iState: 1, + }, + filter: { + iState: 'paying', + }, + }, + }, + isList: false, + properties: { + accountId: '', // 是否可以使用帐户中的余额抵扣 + accountAvailMax: 0, // 本次交易可以使用的帐户中的Avail max值,调用者自己保证此数值的一致性,不要扣成负数 + onPayReady: (operation: EntityDict['order']['Update'], payId: string) => undefined as void, + }, + formData({ data }) { + const payConfig = this.features.pay.getPayConfigs(); + const accountConfig = payConfig?.find(ele => ele.channel === PAY_CHANNEL_ACCOUNT_NAME); + const payConfig2 = payConfig?.filter( + ele => ele !== accountConfig + ); + + const activePay = data && data.pay$order?.[0]; + const { accountPrice } = this.state; + return { + order: data, + activePay, + accountConfig, + payConfig: payConfig2, + rest: data ? data.price! - data.paid! - accountPrice : 0, + legal: !!(data?.['#oakLegalActions']?.includes('startPaying')) + }; + }, + features: ['application'], + data: { + useAccount: false, + accountPrice: 0, + channel: '', + meta: undefined as undefined | object, + }, + methods: { + setUseAccount(v: boolean) { + const { accountAvailMax } = this.props; + const { order } = this.state; + const accountMaxPrice = Math.min(accountAvailMax!, order!.price!); + this.setState({ + useAccount: v, + accountPrice: accountMaxPrice, + rest: order.price! - accountMaxPrice, + }, () => this.tryCreatePay()); + }, + setAccountPrice(price: number) { + const { order } = this.state; + this.setState({ accountPrice: price, rest: order!.price! - order!.paid! - price }, () => this.tryCreatePay()); + }, + onPickChannel(channel: string) { + this.setState({ + channel, + }, () => this.tryCreatePay()); + }, + onSetChannelMeta(meta?: object) { + this.setState({ + meta, + }, () => this.tryCreatePay()); + }, + tryCreatePay() { + const { oakId, accountId } = this.props; + const { useAccount, accountPrice, channel, meta, order } = this.state; + const pays: Partial[] = []; + let rest = order!.price! - order!.paid!; + + let payId = ''; + if (useAccount && accountPrice) { + pays.push({ + id: generateNewId(), + channel: PAY_CHANNEL_ACCOUNT_NAME, + price: accountPrice, + accountId, + }); + rest = rest - accountPrice; + } + if (rest && channel) { + payId = generateNewId(); + pays.push({ + id: payId, + channel, + meta, + price: rest, + }); + rest = 0; + } + const { onPayReady } = this.props; + if (rest === 0) { + onPayReady!({ + id: generateNewId(), + action: 'startPaying', + data: { + pay$order: pays.map( + ele => ({ + id: generateNewId(), + action: 'create', + data: ele, + }) + ), + }, + filter: { + id: oakId, + }, + }, payId); + } + } + }, + actions: ['startPaying'], +}) \ No newline at end of file diff --git a/src/components/order/pay/info.module.less b/src/components/order/pay/info.module.less new file mode 100644 index 00000000..4c807d7d --- /dev/null +++ b/src/components/order/pay/info.module.less @@ -0,0 +1,25 @@ + +.info { + margin: 8px; + height: 140px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: var(--oak-color-info); + + .should { + font-size: large; + font-weight: bold; + } + + .price { + display: flex; + flex-direction: row; + padding: 3px; + font-size: xx-large; + font-weight: bolder; + color: var(--oak-color-primary); + align-items: baseline; + } +} \ No newline at end of file diff --git a/src/components/order/pay/info.tsx b/src/components/order/pay/info.tsx new file mode 100644 index 00000000..a8bbc84f --- /dev/null +++ b/src/components/order/pay/info.tsx @@ -0,0 +1,14 @@ +import Styles from './info.module.less'; + +export default function Info(props: { price: number, t: (k: string) => string }) { + const { price, t } = props; + return ( +
+
{t('price')}
+
+
{t('common::pay.symbol')}
+
{price}
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/order/pay/locales/zh-CN.json b/src/components/order/pay/locales/zh-CN.json new file mode 100644 index 00000000..27a72b38 --- /dev/null +++ b/src/components/order/pay/locales/zh-CN.json @@ -0,0 +1,8 @@ +{ + "price": "订单金额", + "choose": "请选择支付方式(%{price}元)", + "useAccount": "使用余额抵扣", + "accountMax": "当前可用余额为%{max}元", + "illegalState": "订单的当前状态【%{state}】无法开始支付", + "paying": "有一个正在支付的订单" +} \ No newline at end of file diff --git a/src/components/order/pay/web.mobile.module.less b/src/components/order/pay/web.mobile.module.less new file mode 100644 index 00000000..f26100e9 --- /dev/null +++ b/src/components/order/pay/web.mobile.module.less @@ -0,0 +1,36 @@ +.container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: stretch; + background-color: var(--oak-bg-color-page); + // min-height: 500px; + + .ctrl { + margin: 8px; + margin-top: 10px; + flex: 1; + + .pc1 { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + margin-top: 4px; + + .content { + background-color: var(--oak-bg-color-container); + padding: 4px; + border-radius: 2px; + padding: 16px; + + .tips { + color: var(--oak-color-warning); + font-size: small; + margin-top: 3px; + } + } + } + } +} \ No newline at end of file diff --git a/src/components/order/pay/web.pc.module.less b/src/components/order/pay/web.pc.module.less new file mode 100644 index 00000000..b98f5051 --- /dev/null +++ b/src/components/order/pay/web.pc.module.less @@ -0,0 +1,36 @@ +.container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: stretch; + background-color: var(--oak-bg-color-page); + // min-height: 500px; + + .ctrl { + margin: 8px; + margin-top: 10px; + flex: 1; + + .pc1 { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + margin-top: 4px; + + .content { + background-color: var(--oak-bg-color-container); + padding: 4px; + border-radius: 2px; + padding: 16px; + + .tips { + color: var(--oak-color-warning); + font-size: small; + margin-left: 10px; + } + } + } + } +} \ No newline at end of file diff --git a/src/components/order/pay/web.pc.tsx b/src/components/order/pay/web.pc.tsx new file mode 100644 index 00000000..9e0d9ff0 --- /dev/null +++ b/src/components/order/pay/web.pc.tsx @@ -0,0 +1,166 @@ +import React, { useState } from 'react'; +import { WebComponentProps } from 'oak-frontend-base'; +import { ToYuan, ToCent } from 'oak-domain/lib/utils/money'; +import { EntityDict } from '../../../oak-app-domain'; +import { AccountPayConfig, PayConfig } from '../../../types/PayConfig'; +import Styles from './web.pc.module.less'; +import PayChannelPicker from '../../pay/channelPicker'; +import { Divider, Checkbox, InputNumber, Flex, Result } from 'antd'; +import Info from './info'; + +function RenderPayChannel(props: { + payConfig?: PayConfig; + price: number; + t: (k: string, params?: any) => string; + channel?: string; + meta?: object; + onPick: (channel: string) => void; + onSetMeta: (meta?: object) => void; +}) { + const { price, payConfig, t, channel, meta, onPick, onSetMeta } = props; + return ( +
+
+
+ {t('choose', { price: ToYuan(price) })} +
+ + +
+
+ ); +} + +function RenderAccountPay(props: { + max: number; + t: (k: string, params?: any) => string; + setAccountPrice: (price: number) => void; + useAccount: boolean; + accountPrice: number; + accountAvail: number; + setUseAccount: (v: boolean) => void; +}) { + const { max, t, accountPrice, setAccountPrice, useAccount, setUseAccount, accountAvail } = props; + + return ( +
+
+ { + setUseAccount(!useAccount); + if (useAccount) { + setAccountPrice(0); + } + }} + > + {t('useAccount')} + + { + useAccount && ( + <> + + + { + if (typeof v === 'number') { + setAccountPrice(Math.floor(ToCent(v))); + } + }} + /> +
{t('accountMax', { max: ToYuan(accountAvail) })}
+
+ + ) + } +
+
+ ) +} + +export default function Render(props: WebComponentProps void; + onPickChannel: (channel: string) => void; + onSetChannelMeta: (meta?: object) => void; + setUseAccount: (v: boolean) => void; +}>) { + const { accountId, accountAvailMax, legal, accountPrice, useAccount, + order, activePay, payConfig, channel, meta, rest } = props.data; + const { t, setAccountPrice, onPickChannel, onSetChannelMeta, setUseAccount } = props.methods; + + if (order) { + if (activePay) { + return ( + + ); + + } + if (!legal) { + return ( + + ); + } + return ( +
+ + { + accountId && accountAvailMax &&
+ +
+ } + {!!(rest && rest > 0) &&
+ +
} +
+ ); + } + + return null; +} \ No newline at end of file diff --git a/src/components/order/pay/web.tsx b/src/components/order/pay/web.tsx new file mode 100644 index 00000000..30f28f03 --- /dev/null +++ b/src/components/order/pay/web.tsx @@ -0,0 +1,167 @@ +import React, { useState } from 'react'; +import { WebComponentProps } from 'oak-frontend-base'; +import { ToYuan, ToCent } from 'oak-domain/lib/utils/money'; +import { EntityDict } from '../../../oak-app-domain'; +import { AccountPayConfig, PayConfig } from '../../../types/PayConfig'; +import Styles from './web.mobile.module.less'; +import PayChannelPicker from '../../pay/channelPicker'; +import { InputNumber } from 'antd'; +import { Checkbox, Divider, ErrorBlock } from 'antd-mobile'; +import Info from './info'; + +function RenderPayChannel(props: { + payConfig?: PayConfig; + price: number; + t: (k: string, params?: any) => string; + channel?: string; + meta?: object; + onPick: (channel: string) => void; + onSetMeta: (meta?: object) => void; +}) { + const { price, payConfig, t, channel, meta, onPick, onSetMeta } = props; + return ( +
+
+
+ {t('choose', { price: ToYuan(price) })} +
+ + +
+
+ ); +} + +function RenderAccountPay(props: { + max: number; + t: (k: string, params?: any) => string; + setAccountPrice: (price: number) => void; + useAccount: boolean; + accountPrice: number; + accountAvail: number; + setUseAccount: (v: boolean) => void; +}) { + const { max, t, accountPrice, setAccountPrice, useAccount, setUseAccount, accountAvail } = props; + + return ( +
+
+ { + setUseAccount(!useAccount); + if (useAccount) { + setAccountPrice(0); + } + }} + > + {t('useAccount')} + + { + useAccount && ( + <> + +
+ { + if (typeof v === 'number') { + setAccountPrice(Math.floor(ToCent(v))); + } + }} + /> +
{t('accountMax', { max: ToYuan(accountAvail) })}
+
+ + ) + } +
+
+ ) +} + +export default function Render(props: WebComponentProps void; + onPickChannel: (channel: string) => void; + onSetChannelMeta: (meta?: object) => void; + setUseAccount: (v: boolean) => void; +}>) { + const { accountId, accountAvailMax, legal, accountPrice, useAccount, + order, activePay, payConfig, channel, meta, rest } = props.data; + const { t, setAccountPrice, onPickChannel, onSetChannelMeta, setUseAccount } = props.methods; + + if (order) { + if (activePay) { + return ( + + ); + + } + if (!legal) { + return ( + + ); + } + return ( +
+ + { + accountId && accountAvailMax &&
+ +
+ } + {!!(rest && rest > 0) &&
+ +
} +
+ ); + } + + return null; +} \ No newline at end of file diff --git a/src/components/pay/channelPicker/index.ts b/src/components/pay/channelPicker/index.ts index cdc1d410..8ad13ffc 100644 --- a/src/components/pay/channelPicker/index.ts +++ b/src/components/pay/channelPicker/index.ts @@ -5,7 +5,7 @@ import assert from 'assert'; * 支持自定义支付通道的选择器注入 * 未来遇到真正需求时再实现,by Xc 20240426 */ -type ExtraPicker = { +export type ExtraPicker = { label: string; name: string; icon: React.ForwardRefExoticComponent; diff --git a/src/components/pay/channelPicker/web.mobile.module.less b/src/components/pay/channelPicker/web.mobile.module.less index 1ffa923e..fb4541d7 100644 --- a/src/components/pay/channelPicker/web.mobile.module.less +++ b/src/components/pay/channelPicker/web.mobile.module.less @@ -1,3 +1,8 @@ .span { margin-right: 10px; +} + + +.radio { + height: 40px; } \ No newline at end of file diff --git a/src/components/pay/channelPicker/web.tsx b/src/components/pay/channelPicker/web.tsx index 6f451277..0b9b9fd5 100644 --- a/src/components/pay/channelPicker/web.tsx +++ b/src/components/pay/channelPicker/web.tsx @@ -78,6 +78,7 @@ export default function Render(props: WebComponentProps ( diff --git a/src/components/pay/detail/index.ts b/src/components/pay/detail/index.ts index cc9f407e..da8cf231 100644 --- a/src/components/pay/detail/index.ts +++ b/src/components/pay/detail/index.ts @@ -40,6 +40,7 @@ export default OakComponent({ application, iStateColor, payConfig, + closable: !!(data?.["#oakLegalActions"]?.includes('close')), }; }, features: ['application'], diff --git a/src/components/pay/detail/web.mobile.module.less b/src/components/pay/detail/web.mobile.module.less new file mode 100644 index 00000000..de54ed73 --- /dev/null +++ b/src/components/pay/detail/web.mobile.module.less @@ -0,0 +1,35 @@ +.container { + display: flex; + align-items: stretch; + flex-direction: column; + height: 100%; +} + +.meta { + margin-top: 40px; + display: flex; + flex-direction: column; + align-items: center; + + .qrCodeTips { + margin-top: 28px; + } + + .counter { + font-size: var(--oak-font-size-headline-medium); + font-weight: bolder; + } +} + +.padding { + flex: 1; +} + +.btn { + display: flex; + flex-direction: row; + + .btnItem { + flex: 1; + } +} diff --git a/src/components/pay/detail/web.pc.tsx b/src/components/pay/detail/web.pc.tsx index 2d3a7544..8078a657 100644 --- a/src/components/pay/detail/web.pc.tsx +++ b/src/components/pay/detail/web.pc.tsx @@ -4,7 +4,6 @@ import { RowWithActions, WebComponentProps } from 'oak-frontend-base'; import { EntityDict } from '@project/oak-app-domain'; import { CentToString } from 'oak-domain/lib/utils/money'; import Styles from './web.pc.module.less'; -import classNames from 'classnames'; import * as dayJs from 'dayjs'; import duration from 'dayjs/plugin/duration'; dayJs.extend(duration); @@ -14,7 +13,6 @@ import { PAY_CHANNEL_WECHAT_H5_NAME, PAY_CHANNEL_WECHAT_JS_NAME, PAY_CHANNEL_WECHAT_MP_NAME, PAY_CHANNEL_WECHAT_NATIVE_NAME, PayConfig } from '../../../types/PayConfig'; -import { WechatOutlined, MoneyCollectOutlined, WalletOutlined } from '@ant-design/icons'; export function RenderOffline(props: { pay: RowWithActions, @@ -34,7 +32,7 @@ export function RenderOffline(props: { style={{ width: '100%', marginTop: 12 }} > - {offline.tips} + {offline.tips}