diff --git a/es/checkers/accountOper.js b/es/checkers/accountOper.js index eaa7e439..9be1b6d8 100644 --- a/es/checkers/accountOper.js +++ b/es/checkers/accountOper.js @@ -62,6 +62,12 @@ const checkers = [ } break; } + case 'loss': { + if (totalPlus >= 0 || availPlus > 0 || totalPlus !== availPlus) { + throw new OakInputIllegalException('accountOper', ['availPlus'], 'accountOper为consume时,其totalPlus/availPlus必须为0或负数,且totalPlus的绝对值要更大'); + } + break; + } default: { assert(false); break; diff --git a/es/components/accountOper/pure/List.pc.js b/es/components/accountOper/pure/List.pc.js index b85734a9..f444605b 100644 --- a/es/components/accountOper/pure/List.pc.js +++ b/es/components/accountOper/pure/List.pc.js @@ -6,15 +6,15 @@ export default function Render(props) { return ( { const { $$createAt$$, type, totalPlus, availPlus } = item; const plus = ToYuan(totalPlus || availPlus); - const sign = plus > 0 ? '+' : '-'; + const sign = plus > 0 ? '+' : ''; const d = dayjs($$createAt$$); return ( {t(`accountOper:v.type.${type}`).at(0)}} title={t(`accountOper:v.type.${type}`)} description={d.format('M月D日 HH时mm分')}/>
- {sign}{ThousandCont(plus)} + {sign}{ThousandCont(plus, 2)}
); }}/>); diff --git a/es/context/BackendRuntimeContext.js b/es/context/BackendRuntimeContext.js index dc37390d..26bbd9f9 100644 --- a/es/context/BackendRuntimeContext.js +++ b/es/context/BackendRuntimeContext.js @@ -20,6 +20,7 @@ export class BackendRuntimeContext extends DependentBackendRuntimeContext { payConfig.forEach((config) => { unset(config, 'publicKeyFilePath'); unset(config, 'privateKeyFilePath'); + unset(config, 'apiV3Key'); }); } } diff --git a/es/entities/AccountOper.d.ts b/es/entities/AccountOper.d.ts index 0e0614c6..8efff539 100644 --- a/es/entities/AccountOper.d.ts +++ b/es/entities/AccountOper.d.ts @@ -2,7 +2,7 @@ import { String, Price } from 'oak-domain/lib/types/DataType'; import { EntityShape } from 'oak-domain/lib/types/Entity'; import { EntityDesc } from 'oak-domain/lib/types'; import { Schema as Account } from './Account'; -type Type = 'deposit' | 'withdraw' | 'consume' | 'loan' | 'repay' | 'withdrawBack'; +type Type = 'deposit' | 'withdraw' | 'consume' | 'loan' | 'repay' | 'withdrawBack' | 'loss'; export interface Schema extends EntityShape { account: Account; type: Type; diff --git a/es/entities/AccountOper.js b/es/entities/AccountOper.js index bd9f5932..7e2482a3 100644 --- a/es/entities/AccountOper.js +++ b/es/entities/AccountOper.js @@ -19,6 +19,7 @@ export const entityDesc = { loan: '抵押', repay: '偿还', withdrawBack: '提现失败', + loss: '损耗' }, }, }, @@ -32,6 +33,7 @@ export const entityDesc = { consume: '#A569BD', loan: '#CD6155', repay: '#82E0AA', + loss: '#2E4053' } } }, diff --git a/es/oak-app-domain/AccountOper/Schema.d.ts b/es/oak-app-domain/AccountOper/Schema.d.ts index 5de73765..7ce05662 100644 --- a/es/oak-app-domain/AccountOper/Schema.d.ts +++ b/es/oak-app-domain/AccountOper/Schema.d.ts @@ -7,7 +7,7 @@ import { Price, String } from "oak-domain/lib/types/DataType"; import * as Account from "../Account/Schema"; import * as Pay from "../Pay/Schema"; import * as Withdraw from "../Withdraw/Schema"; -type Type = "deposit" | "withdraw" | "consume" | "loan" | "repay" | "withdrawBack"; +type Type = "deposit" | "withdraw" | "consume" | "loan" | "repay" | "withdrawBack" | "loss"; export type OpSchema = EntityShape & { accountId: ForeignKey<"account">; type: Type; diff --git a/es/oak-app-domain/AccountOper/Storage.js b/es/oak-app-domain/AccountOper/Storage.js index 5d3851d5..a00aaf44 100644 --- a/es/oak-app-domain/AccountOper/Storage.js +++ b/es/oak-app-domain/AccountOper/Storage.js @@ -9,7 +9,7 @@ export const desc = { type: { notNull: true, type: "enum", - enumeration: ["deposit", "withdraw", "consume", "loan", "repay", "withdrawBack"] + enumeration: ["deposit", "withdraw", "consume", "loan", "repay", "withdrawBack", "loss"] }, totalPlus: { notNull: true, diff --git a/es/oak-app-domain/AccountOper/Style.js b/es/oak-app-domain/AccountOper/Style.js index 0f7ed6de..3617734e 100644 --- a/es/oak-app-domain/AccountOper/Style.js +++ b/es/oak-app-domain/AccountOper/Style.js @@ -7,6 +7,7 @@ export const style = { consume: '#A569BD', loan: '#CD6155', repay: '#82E0AA', + loss: '#2E4053' } } }; diff --git a/es/oak-app-domain/AccountOper/locales/zh_CN.json b/es/oak-app-domain/AccountOper/locales/zh_CN.json index ceb971e6..6091e6ed 100644 --- a/es/oak-app-domain/AccountOper/locales/zh_CN.json +++ b/es/oak-app-domain/AccountOper/locales/zh_CN.json @@ -1 +1 @@ -{ "name": "帐号操作", "attr": { "account": "帐号", "type": "类型", "totalPlus": "余额变化", "availPlus": "可用余额变化", "entity": "关联对象", "entityId": "关联对象Id" }, "v": { "type": { "deposit": "充值", "withdraw": "提现", "consume": "消费", "loan": "抵押", "repay": "偿还", "withdrawBack": "提现失败" } } } +{ "name": "帐号操作", "attr": { "account": "帐号", "type": "类型", "totalPlus": "余额变化", "availPlus": "可用余额变化", "entity": "关联对象", "entityId": "关联对象Id" }, "v": { "type": { "deposit": "充值", "withdraw": "提现", "consume": "消费", "loan": "抵押", "repay": "偿还", "withdrawBack": "提现失败", "loss": "损耗" } } } diff --git a/es/oak-app-domain/Pay/locales/zh_CN.json b/es/oak-app-domain/Pay/locales/zh_CN.json index 7dd96872..778610ad 100644 --- a/es/oak-app-domain/Pay/locales/zh_CN.json +++ b/es/oak-app-domain/Pay/locales/zh_CN.json @@ -1 +1 @@ -{ "name": "订单", "attr": { "price": "订单金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "channel": "支付渠道", "order": "所属订单", "timeoutAt": "过期时间", "forbidRefundAt": "停止退款时间", "account": "充值帐户", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } } +{ "name": "订单", "attr": { "price": "应支付金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "channel": "支付渠道", "order": "所属订单", "timeoutAt": "过期时间", "forbidRefundAt": "停止退款时间", "account": "充值帐户", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } } diff --git a/es/oak-app-domain/Token/Schema.d.ts b/es/oak-app-domain/Token/Schema.d.ts index 35adfb08..87e674d6 100644 --- a/es/oak-app-domain/Token/Schema.d.ts +++ b/es/oak-app-domain/Token/Schema.d.ts @@ -22,6 +22,7 @@ export type OpSchema = EntityShape & { env: Environment; refreshedAt: Datetime; value: String<64>; + oldValue?: String<64> | null; ableState?: AbleState | null; }; export type OpAttr = keyof OpSchema; @@ -35,6 +36,7 @@ export type Schema = EntityShape & { env: Environment; refreshedAt: Datetime; value: String<64>; + oldValue?: String<64> | null; ableState?: AbleState | null; application?: Application.Schema | null; user?: User.Schema | null; @@ -63,6 +65,7 @@ type AttrFilter = { env: JsonFilter; refreshedAt: Q_DateValue; value: Q_StringValue; + oldValue: Q_StringValue; ableState: Q_EnumValue; email: Email.Filter; mobile: Mobile.Filter; @@ -89,6 +92,7 @@ export type Projection = { env?: number | JsonProjection; refreshedAt?: number; value?: number; + oldValue?: number; ableState?: number; email?: Email.Projection; mobile?: Mobile.Projection; @@ -149,6 +153,8 @@ export type SortAttr = { refreshedAt: number; } | { value: number; +} | { + oldValue: number; } | { ableState: number; } | { diff --git a/es/oak-app-domain/Token/Storage.js b/es/oak-app-domain/Token/Storage.js index 00037e5c..bf958ad5 100644 --- a/es/oak-app-domain/Token/Storage.js +++ b/es/oak-app-domain/Token/Storage.js @@ -46,6 +46,12 @@ export const desc = { length: 64 } }, + oldValue: { + type: "varchar", + params: { + length: 64 + } + }, ableState: { type: "enum", enumeration: ["enabled", "disabled"] diff --git a/es/oak-app-domain/Token/Style.js b/es/oak-app-domain/Token/Style.js index 9338e188..463063ce 100644 --- a/es/oak-app-domain/Token/Style.js +++ b/es/oak-app-domain/Token/Style.js @@ -6,7 +6,7 @@ export const style = { color: { ableState: { enabled: '#008000', - disabled: '#A9A9A9' + disabled: '#A9A9A9', }, }, }; diff --git a/es/oak-app-domain/Token/locales/zh_CN.json b/es/oak-app-domain/Token/locales/zh_CN.json index 65fca6de..e183f233 100644 --- a/es/oak-app-domain/Token/locales/zh_CN.json +++ b/es/oak-app-domain/Token/locales/zh_CN.json @@ -1 +1 @@ -{ "name": "令牌", "attr": { "application": "应用", "entity": "关联对象", "entityId": "关联对象id", "user": "用户", "player": "扮演者", "env": "环境", "ableState": "状态", "disablesAt": "禁用时间", "refreshedAt": "刷新时间", "value": "令牌值" }, "action": { "enable": "激活", "disable": "禁用" }, "v": { "ableState": { "enabled": "使用中", "disabled": "已禁用" } } } +{ "name": "令牌", "attr": { "application": "应用", "entity": "关联对象", "entityId": "关联对象id", "user": "用户", "player": "扮演者", "env": "环境", "ableState": "状态", "disablesAt": "禁用时间", "refreshedAt": "刷新时间", "value": "令牌值", "oldValue": "老令牌" }, "action": { "enable": "激活", "disable": "禁用" }, "v": { "ableState": { "enabled": "使用中", "disabled": "已禁用" } } } diff --git a/es/triggers/account.d.ts b/es/triggers/account.d.ts new file mode 100644 index 00000000..cf64eb08 --- /dev/null +++ b/es/triggers/account.d.ts @@ -0,0 +1,5 @@ +import { Trigger } from 'oak-domain/lib/types/Trigger'; +import { EntityDict } from '../oak-app-domain'; +import { BRC } from '../types/RuntimeCxt'; +declare const triggers: Trigger[]; +export default triggers; diff --git a/es/triggers/account.js b/es/triggers/account.js new file mode 100644 index 00000000..71f4a5d5 --- /dev/null +++ b/es/triggers/account.js @@ -0,0 +1,20 @@ +import { DATA_SUBSCRIBER_KEYS } from '../config/constants'; +import assert from 'assert'; +const triggers = [ + { + name: '当account帐户的值发生变化时,向订阅者推送', + entity: 'account', + action: ['deposit', 'withdraw', 'withdrawBack', 'consume', 'loan', 'repay'], + check(operation) { + return operation.data.hasOwnProperty('total') || operation.data.hasOwnProperty('avail'); + }, + when: 'after', + fn: async ({ operation }, context, option) => { + const { id, filter } = operation; + assert(typeof filter?.id === 'string'); + context.saveOperationToEvent(id, `${DATA_SUBSCRIBER_KEYS.accountNumberChanged}-${filter.id}`); + return 1; + }, + }, +]; +export default triggers; diff --git a/es/triggers/accountOper.js b/es/triggers/accountOper.js index d86ccbb8..5889d672 100644 --- a/es/triggers/accountOper.js +++ b/es/triggers/accountOper.js @@ -1,6 +1,7 @@ import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid'; import assert from 'assert'; import { DATA_SUBSCRIBER_KEYS } from '../config/constants'; +import { getDepositRatio } from '../utils/pay'; const triggers = [ { name: '当生成accountOper时,修改account中的值,并向订阅者推送', @@ -36,6 +37,61 @@ const triggers = [ context.saveOperationToEvent(id, `${DATA_SUBSCRIBER_KEYS.accountNumberChanged}-${accountId}`); return 1; }, - } + }, + { + name: '当account depoist时,根据系统配置,扣除掉损耗', + entity: 'accountOper', + action: 'create', + when: 'after', + check(operation) { + const { data } = operation; + assert(!(data instanceof Array)); + return data.type === 'deposit'; + }, + fn: async ({ operation }, context, option) => { + const { data } = operation; + assert(!(data instanceof Array)); + const { entity, entityId } = data; + assert(entity === 'pay' && entityId); + const [pay] = await context.select('pay', { + data: { + id: 1, + price: 1, + channel: 1, + application: { + id: 1, + payConfig: 1, + system: { + id: 1, + payConfig: 1, + }, + } + }, + filter: { + id: entityId, + }, + }, {}); + const { price, application, channel } = pay; + const ratio = getDepositRatio(channel, application); + if (ratio > 0) { + const loss = Math.ceil(price * ratio / 100); + await context.operate('accountOper', { + id: await generateNewIdAsync(), + action: 'create', + data: { + id: await generateNewIdAsync(), + type: 'loss', + totalPlus: -loss, + availPlus: -loss, + entity, + entityId, + accountId: data.accountId, + } + }, {}); + return 1; + } + return 0; + } + }, ]; export default triggers; diff --git a/es/triggers/index.js b/es/triggers/index.js index 3060656f..e35ddb53 100644 --- a/es/triggers/index.js +++ b/es/triggers/index.js @@ -1,7 +1,7 @@ import aoTriggers from './accountOper'; import payTriggers from './pay'; import userSystemTriggers from './userSystem'; -import accountTriggers from './acount'; +import accountTriggers from './account'; const triggers = [ ...aoTriggers, ...payTriggers, diff --git a/es/types/PayConfig.d.ts b/es/types/PayConfig.d.ts index c1abecec..58bcaf02 100644 --- a/es/types/PayConfig.d.ts +++ b/es/types/PayConfig.d.ts @@ -2,29 +2,31 @@ export type WechatPayChannel = 'WECHAT_JS' | 'WECHAT_MP' | 'WECHAT_NATIVE' | 'WE export interface ConfigBase { channel: string; } -export type WechatPayConfig = { +export interface ThirdPartyConfig extends ConfigBase { + payNotifyUrl: string; + refundNotifyUrl: string; + lossRatio: number; +} +export interface WechatPayConfig extends ThirdPartyConfig { channel: WechatPayChannel; mchId: string; publicKeyFilePath: string; privateKeyFilePath: string; apiV3Key: string; - payNotifyUrl: string; - refundNotifyUrl: string; - lossRatio: number; -}; +} export type AccountPayChannel = 'ACCOUNT'; -export type AccountPayConfig = { +export interface AccountPayConfig extends ConfigBase { channel: AccountPayChannel; depositLoss: boolean; depositLossRatio?: number; -}; +} export type OfflinePayChannel = 'OFFLINE'; -export type OfflinePayConfig = { +export interface OfflinePayConfig extends ConfigBase { tips: string; channel: OfflinePayChannel; options?: string[]; allowUser?: boolean; -}; +} export type Channel = WechatPayChannel | AccountPayChannel | OfflinePayChannel; export type PayConfig = Array; export declare const PAY_CHANNEL_ACCOUNT_NAME = "ACCOUNT"; @@ -34,3 +36,4 @@ export declare const PAY_CHANNEL_WECHAT_MP_NAME = "WECHAT_MP"; export declare const PAY_CHANNEL_WECHAT_H5_NAME = "WECHAT_H5"; export declare const PAY_CHANNEL_WECHAT_APP_NAME = "WECHAT_APP"; export declare const PAY_CHANNEL_WECHAT_NATIVE_NAME = "WECHAT_NATIVE"; +export declare const PAY_WECHAT_CHANNELS: string[]; diff --git a/es/types/PayConfig.js b/es/types/PayConfig.js index d3ce99df..fd862222 100644 --- a/es/types/PayConfig.js +++ b/es/types/PayConfig.js @@ -1,3 +1,5 @@ +; +; export const PAY_CHANNEL_ACCOUNT_NAME = 'ACCOUNT'; export const PAY_CHANNEL_OFFLINE_NAME = 'OFFLINE'; export const PAY_CHANNEL_WECHAT_JS_NAME = 'WECHAT_JS'; @@ -5,3 +7,10 @@ export const PAY_CHANNEL_WECHAT_MP_NAME = 'WECHAT_MP'; export const PAY_CHANNEL_WECHAT_H5_NAME = 'WECHAT_H5'; export const PAY_CHANNEL_WECHAT_APP_NAME = 'WECHAT_APP'; export const PAY_CHANNEL_WECHAT_NATIVE_NAME = 'WECHAT_NATIVE'; +export const PAY_WECHAT_CHANNELS = [ + PAY_CHANNEL_WECHAT_JS_NAME, + PAY_CHANNEL_WECHAT_MP_NAME, + PAY_CHANNEL_WECHAT_H5_NAME, + PAY_CHANNEL_WECHAT_APP_NAME, + PAY_CHANNEL_WECHAT_NATIVE_NAME +]; diff --git a/es/utils/pay.d.ts b/es/utils/pay.d.ts index 0bc7be80..f352f637 100644 --- a/es/utils/pay.d.ts +++ b/es/utils/pay.d.ts @@ -5,3 +5,10 @@ import BackendRuntimeContext from '../context/BackendRuntimeContext'; import { IncomingHttpHeaders } from 'http'; export declare const fullPayProjection: EntityDict['pay']['Selection']['data']; export declare function payNotify(context: BackendRuntimeContext, body: any, payId: string, headers: IncomingHttpHeaders): Promise; +/** + * 计算充值的损耗比例 + * @param context + * @param channel + * @param application + */ +export declare function getDepositRatio(channel: string, application: ED['application']['Schema']): number; diff --git a/es/utils/pay.js b/es/utils/pay.js index 5c8f2bd7..20b8b3f4 100644 --- a/es/utils/pay.js +++ b/es/utils/pay.js @@ -1,6 +1,7 @@ import { getPayClazz } from './payClazz'; import assert from 'assert'; import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid'; +import { PAY_CHANNEL_ACCOUNT_NAME, PAY_WECHAT_CHANNELS } from '../types/PayConfig'; export const fullPayProjection = { id: 1, applicationId: 1, @@ -75,3 +76,25 @@ export async function payNotify(context, body, payId, headers) { } return; } +/** + * 计算充值的损耗比例 + * @param context + * @param channel + * @param application + */ +export function getDepositRatio(channel, application) { + const { payConfig, system } = application; + const { payConfig: systemPayConfig } = system; + const accountConfig = systemPayConfig.find(ele => ele.channel === PAY_CHANNEL_ACCOUNT_NAME); + const { depositLoss, depositLossRatio } = accountConfig; + if (!depositLoss) { + return 0; + } + if (depositLossRatio) { + return depositLossRatio; + } + const config = systemPayConfig?.find(ele => ele.channel === channel) || payConfig?.find(ele => ele.channel === channel); + assert(config && PAY_WECHAT_CHANNELS.includes(channel)); + const { lossRatio } = config; + return lossRatio || 0; +} diff --git a/lib/checkers/accountOper.js b/lib/checkers/accountOper.js index 2e831cf7..cf179e85 100644 --- a/lib/checkers/accountOper.js +++ b/lib/checkers/accountOper.js @@ -65,6 +65,12 @@ const checkers = [ } break; } + case 'loss': { + if (totalPlus >= 0 || availPlus > 0 || totalPlus !== availPlus) { + throw new types_1.OakInputIllegalException('accountOper', ['availPlus'], 'accountOper为consume时,其totalPlus/availPlus必须为0或负数,且totalPlus的绝对值要更大'); + } + break; + } default: { (0, assert_1.default)(false); break; diff --git a/lib/context/BackendRuntimeContext.js b/lib/context/BackendRuntimeContext.js index 5ebe34cb..f6987e1d 100644 --- a/lib/context/BackendRuntimeContext.js +++ b/lib/context/BackendRuntimeContext.js @@ -23,6 +23,7 @@ class BackendRuntimeContext extends DependentContext_1.BackendRuntimeContext { payConfig.forEach((config) => { (0, lodash_1.unset)(config, 'publicKeyFilePath'); (0, lodash_1.unset)(config, 'privateKeyFilePath'); + (0, lodash_1.unset)(config, 'apiV3Key'); }); } } diff --git a/lib/entities/AccountOper.d.ts b/lib/entities/AccountOper.d.ts index 0e0614c6..8efff539 100644 --- a/lib/entities/AccountOper.d.ts +++ b/lib/entities/AccountOper.d.ts @@ -2,7 +2,7 @@ import { String, Price } from 'oak-domain/lib/types/DataType'; import { EntityShape } from 'oak-domain/lib/types/Entity'; import { EntityDesc } from 'oak-domain/lib/types'; import { Schema as Account } from './Account'; -type Type = 'deposit' | 'withdraw' | 'consume' | 'loan' | 'repay' | 'withdrawBack'; +type Type = 'deposit' | 'withdraw' | 'consume' | 'loan' | 'repay' | 'withdrawBack' | 'loss'; export interface Schema extends EntityShape { account: Account; type: Type; diff --git a/lib/entities/AccountOper.js b/lib/entities/AccountOper.js index 984863cc..0d11c621 100644 --- a/lib/entities/AccountOper.js +++ b/lib/entities/AccountOper.js @@ -22,6 +22,7 @@ exports.entityDesc = { loan: '抵押', repay: '偿还', withdrawBack: '提现失败', + loss: '损耗' }, }, }, @@ -35,6 +36,7 @@ exports.entityDesc = { consume: '#A569BD', loan: '#CD6155', repay: '#82E0AA', + loss: '#2E4053' } } }, diff --git a/lib/oak-app-domain/AccountOper/Schema.d.ts b/lib/oak-app-domain/AccountOper/Schema.d.ts index 5de73765..7ce05662 100644 --- a/lib/oak-app-domain/AccountOper/Schema.d.ts +++ b/lib/oak-app-domain/AccountOper/Schema.d.ts @@ -7,7 +7,7 @@ import { Price, String } from "oak-domain/lib/types/DataType"; import * as Account from "../Account/Schema"; import * as Pay from "../Pay/Schema"; import * as Withdraw from "../Withdraw/Schema"; -type Type = "deposit" | "withdraw" | "consume" | "loan" | "repay" | "withdrawBack"; +type Type = "deposit" | "withdraw" | "consume" | "loan" | "repay" | "withdrawBack" | "loss"; export type OpSchema = EntityShape & { accountId: ForeignKey<"account">; type: Type; diff --git a/lib/oak-app-domain/AccountOper/Storage.js b/lib/oak-app-domain/AccountOper/Storage.js index f51321b8..34f3644c 100644 --- a/lib/oak-app-domain/AccountOper/Storage.js +++ b/lib/oak-app-domain/AccountOper/Storage.js @@ -12,7 +12,7 @@ exports.desc = { type: { notNull: true, type: "enum", - enumeration: ["deposit", "withdraw", "consume", "loan", "repay", "withdrawBack"] + enumeration: ["deposit", "withdraw", "consume", "loan", "repay", "withdrawBack", "loss"] }, totalPlus: { notNull: true, diff --git a/lib/oak-app-domain/AccountOper/Style.js b/lib/oak-app-domain/AccountOper/Style.js index 3f09c205..13b5a0fc 100644 --- a/lib/oak-app-domain/AccountOper/Style.js +++ b/lib/oak-app-domain/AccountOper/Style.js @@ -10,6 +10,7 @@ exports.style = { consume: '#A569BD', loan: '#CD6155', repay: '#82E0AA', + loss: '#2E4053' } } }; diff --git a/lib/oak-app-domain/AccountOper/locales/zh_CN.json b/lib/oak-app-domain/AccountOper/locales/zh_CN.json index ceb971e6..6091e6ed 100644 --- a/lib/oak-app-domain/AccountOper/locales/zh_CN.json +++ b/lib/oak-app-domain/AccountOper/locales/zh_CN.json @@ -1 +1 @@ -{ "name": "帐号操作", "attr": { "account": "帐号", "type": "类型", "totalPlus": "余额变化", "availPlus": "可用余额变化", "entity": "关联对象", "entityId": "关联对象Id" }, "v": { "type": { "deposit": "充值", "withdraw": "提现", "consume": "消费", "loan": "抵押", "repay": "偿还", "withdrawBack": "提现失败" } } } +{ "name": "帐号操作", "attr": { "account": "帐号", "type": "类型", "totalPlus": "余额变化", "availPlus": "可用余额变化", "entity": "关联对象", "entityId": "关联对象Id" }, "v": { "type": { "deposit": "充值", "withdraw": "提现", "consume": "消费", "loan": "抵押", "repay": "偿还", "withdrawBack": "提现失败", "loss": "损耗" } } } diff --git a/lib/oak-app-domain/Pay/locales/zh_CN.json b/lib/oak-app-domain/Pay/locales/zh_CN.json index 7dd96872..778610ad 100644 --- a/lib/oak-app-domain/Pay/locales/zh_CN.json +++ b/lib/oak-app-domain/Pay/locales/zh_CN.json @@ -1 +1 @@ -{ "name": "订单", "attr": { "price": "订单金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "channel": "支付渠道", "order": "所属订单", "timeoutAt": "过期时间", "forbidRefundAt": "停止退款时间", "account": "充值帐户", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } } +{ "name": "订单", "attr": { "price": "应支付金额", "paid": "已支付金额", "refunded": "已退款金额", "iState": "支付状态", "channel": "支付渠道", "order": "所属订单", "timeoutAt": "过期时间", "forbidRefundAt": "停止退款时间", "account": "充值帐户", "meta": "支付metadata", "externalId": "外部订单Id", "opers": "被关联帐户操作", "application": "关联应用", "creator": "创建者", "phantom1": "索引项一", "phantom2": "索引项二", "phantom3": "索引项三", "phantom4": "索引项四" }, "action": { "startPaying": "开始支付", "succeedPaying": "支付成功", "close": "关闭", "startRefunding": "开始退款", "refundAll": "完全退款", "refundPartially": "部分退款" }, "v": { "iState": { "unpaid": "待付款", "paying": "支付中", "paid": "已付款", "closed": "已关闭", "refunding": "退款中", "refunded": "已退款", "partiallyRefunded": "已部分退款" } } } diff --git a/lib/oak-app-domain/Token/Schema.d.ts b/lib/oak-app-domain/Token/Schema.d.ts index 35adfb08..87e674d6 100644 --- a/lib/oak-app-domain/Token/Schema.d.ts +++ b/lib/oak-app-domain/Token/Schema.d.ts @@ -22,6 +22,7 @@ export type OpSchema = EntityShape & { env: Environment; refreshedAt: Datetime; value: String<64>; + oldValue?: String<64> | null; ableState?: AbleState | null; }; export type OpAttr = keyof OpSchema; @@ -35,6 +36,7 @@ export type Schema = EntityShape & { env: Environment; refreshedAt: Datetime; value: String<64>; + oldValue?: String<64> | null; ableState?: AbleState | null; application?: Application.Schema | null; user?: User.Schema | null; @@ -63,6 +65,7 @@ type AttrFilter = { env: JsonFilter; refreshedAt: Q_DateValue; value: Q_StringValue; + oldValue: Q_StringValue; ableState: Q_EnumValue; email: Email.Filter; mobile: Mobile.Filter; @@ -89,6 +92,7 @@ export type Projection = { env?: number | JsonProjection; refreshedAt?: number; value?: number; + oldValue?: number; ableState?: number; email?: Email.Projection; mobile?: Mobile.Projection; @@ -149,6 +153,8 @@ export type SortAttr = { refreshedAt: number; } | { value: number; +} | { + oldValue: number; } | { ableState: number; } | { diff --git a/lib/oak-app-domain/Token/Storage.js b/lib/oak-app-domain/Token/Storage.js index 46b8676e..5f5719d1 100644 --- a/lib/oak-app-domain/Token/Storage.js +++ b/lib/oak-app-domain/Token/Storage.js @@ -49,6 +49,12 @@ exports.desc = { length: 64 } }, + oldValue: { + type: "varchar", + params: { + length: 64 + } + }, ableState: { type: "enum", enumeration: ["enabled", "disabled"] diff --git a/lib/oak-app-domain/Token/Style.js b/lib/oak-app-domain/Token/Style.js index 44a0ab5f..82309c43 100644 --- a/lib/oak-app-domain/Token/Style.js +++ b/lib/oak-app-domain/Token/Style.js @@ -9,7 +9,7 @@ exports.style = { color: { ableState: { enabled: '#008000', - disabled: '#A9A9A9' + disabled: '#A9A9A9', }, }, }; diff --git a/lib/oak-app-domain/Token/locales/zh_CN.json b/lib/oak-app-domain/Token/locales/zh_CN.json index 65fca6de..e183f233 100644 --- a/lib/oak-app-domain/Token/locales/zh_CN.json +++ b/lib/oak-app-domain/Token/locales/zh_CN.json @@ -1 +1 @@ -{ "name": "令牌", "attr": { "application": "应用", "entity": "关联对象", "entityId": "关联对象id", "user": "用户", "player": "扮演者", "env": "环境", "ableState": "状态", "disablesAt": "禁用时间", "refreshedAt": "刷新时间", "value": "令牌值" }, "action": { "enable": "激活", "disable": "禁用" }, "v": { "ableState": { "enabled": "使用中", "disabled": "已禁用" } } } +{ "name": "令牌", "attr": { "application": "应用", "entity": "关联对象", "entityId": "关联对象id", "user": "用户", "player": "扮演者", "env": "环境", "ableState": "状态", "disablesAt": "禁用时间", "refreshedAt": "刷新时间", "value": "令牌值", "oldValue": "老令牌" }, "action": { "enable": "激活", "disable": "禁用" }, "v": { "ableState": { "enabled": "使用中", "disabled": "已禁用" } } } diff --git a/lib/triggers/account.d.ts b/lib/triggers/account.d.ts new file mode 100644 index 00000000..cf64eb08 --- /dev/null +++ b/lib/triggers/account.d.ts @@ -0,0 +1,5 @@ +import { Trigger } from 'oak-domain/lib/types/Trigger'; +import { EntityDict } from '../oak-app-domain'; +import { BRC } from '../types/RuntimeCxt'; +declare const triggers: Trigger[]; +export default triggers; diff --git a/lib/triggers/account.js b/lib/triggers/account.js new file mode 100644 index 00000000..8feeb388 --- /dev/null +++ b/lib/triggers/account.js @@ -0,0 +1,23 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = require("tslib"); +const constants_1 = require("../config/constants"); +const assert_1 = tslib_1.__importDefault(require("assert")); +const triggers = [ + { + name: '当account帐户的值发生变化时,向订阅者推送', + entity: 'account', + action: ['deposit', 'withdraw', 'withdrawBack', 'consume', 'loan', 'repay'], + check(operation) { + return operation.data.hasOwnProperty('total') || operation.data.hasOwnProperty('avail'); + }, + when: 'after', + fn: async ({ operation }, context, option) => { + const { id, filter } = operation; + (0, assert_1.default)(typeof filter?.id === 'string'); + context.saveOperationToEvent(id, `${constants_1.DATA_SUBSCRIBER_KEYS.accountNumberChanged}-${filter.id}`); + return 1; + }, + }, +]; +exports.default = triggers; diff --git a/lib/triggers/accountOper.js b/lib/triggers/accountOper.js index 7a8129a4..e89a3743 100644 --- a/lib/triggers/accountOper.js +++ b/lib/triggers/accountOper.js @@ -4,6 +4,7 @@ const tslib_1 = require("tslib"); const uuid_1 = require("oak-domain/lib/utils/uuid"); const assert_1 = tslib_1.__importDefault(require("assert")); const constants_1 = require("../config/constants"); +const pay_1 = require("../utils/pay"); const triggers = [ { name: '当生成accountOper时,修改account中的值,并向订阅者推送', @@ -39,6 +40,61 @@ const triggers = [ context.saveOperationToEvent(id, `${constants_1.DATA_SUBSCRIBER_KEYS.accountNumberChanged}-${accountId}`); return 1; }, - } + }, + { + name: '当account depoist时,根据系统配置,扣除掉损耗', + entity: 'accountOper', + action: 'create', + when: 'after', + check(operation) { + const { data } = operation; + (0, assert_1.default)(!(data instanceof Array)); + return data.type === 'deposit'; + }, + fn: async ({ operation }, context, option) => { + const { data } = operation; + (0, assert_1.default)(!(data instanceof Array)); + const { entity, entityId } = data; + (0, assert_1.default)(entity === 'pay' && entityId); + const [pay] = await context.select('pay', { + data: { + id: 1, + price: 1, + channel: 1, + application: { + id: 1, + payConfig: 1, + system: { + id: 1, + payConfig: 1, + }, + } + }, + filter: { + id: entityId, + }, + }, {}); + const { price, application, channel } = pay; + const ratio = (0, pay_1.getDepositRatio)(channel, application); + if (ratio > 0) { + const loss = Math.ceil(price * ratio / 100); + await context.operate('accountOper', { + id: await (0, uuid_1.generateNewIdAsync)(), + action: 'create', + data: { + id: await (0, uuid_1.generateNewIdAsync)(), + type: 'loss', + totalPlus: -loss, + availPlus: -loss, + entity, + entityId, + accountId: data.accountId, + } + }, {}); + return 1; + } + return 0; + } + }, ]; exports.default = triggers; diff --git a/lib/triggers/index.js b/lib/triggers/index.js index 1c7c736d..f9e48390 100644 --- a/lib/triggers/index.js +++ b/lib/triggers/index.js @@ -4,11 +4,11 @@ const tslib_1 = require("tslib"); const accountOper_1 = tslib_1.__importDefault(require("./accountOper")); const pay_1 = tslib_1.__importDefault(require("./pay")); const userSystem_1 = tslib_1.__importDefault(require("./userSystem")); -const acount_1 = tslib_1.__importDefault(require("./acount")); +const account_1 = tslib_1.__importDefault(require("./account")); const triggers = [ ...accountOper_1.default, ...pay_1.default, ...userSystem_1.default, - ...acount_1.default, + ...account_1.default, ]; exports.default = triggers; diff --git a/lib/types/PayConfig.d.ts b/lib/types/PayConfig.d.ts index c1abecec..58bcaf02 100644 --- a/lib/types/PayConfig.d.ts +++ b/lib/types/PayConfig.d.ts @@ -2,29 +2,31 @@ export type WechatPayChannel = 'WECHAT_JS' | 'WECHAT_MP' | 'WECHAT_NATIVE' | 'WE export interface ConfigBase { channel: string; } -export type WechatPayConfig = { +export interface ThirdPartyConfig extends ConfigBase { + payNotifyUrl: string; + refundNotifyUrl: string; + lossRatio: number; +} +export interface WechatPayConfig extends ThirdPartyConfig { channel: WechatPayChannel; mchId: string; publicKeyFilePath: string; privateKeyFilePath: string; apiV3Key: string; - payNotifyUrl: string; - refundNotifyUrl: string; - lossRatio: number; -}; +} export type AccountPayChannel = 'ACCOUNT'; -export type AccountPayConfig = { +export interface AccountPayConfig extends ConfigBase { channel: AccountPayChannel; depositLoss: boolean; depositLossRatio?: number; -}; +} export type OfflinePayChannel = 'OFFLINE'; -export type OfflinePayConfig = { +export interface OfflinePayConfig extends ConfigBase { tips: string; channel: OfflinePayChannel; options?: string[]; allowUser?: boolean; -}; +} export type Channel = WechatPayChannel | AccountPayChannel | OfflinePayChannel; export type PayConfig = Array; export declare const PAY_CHANNEL_ACCOUNT_NAME = "ACCOUNT"; @@ -34,3 +36,4 @@ export declare const PAY_CHANNEL_WECHAT_MP_NAME = "WECHAT_MP"; export declare const PAY_CHANNEL_WECHAT_H5_NAME = "WECHAT_H5"; export declare const PAY_CHANNEL_WECHAT_APP_NAME = "WECHAT_APP"; export declare const PAY_CHANNEL_WECHAT_NATIVE_NAME = "WECHAT_NATIVE"; +export declare const PAY_WECHAT_CHANNELS: string[]; diff --git a/lib/types/PayConfig.js b/lib/types/PayConfig.js index d1dc2624..0ed6b4fd 100644 --- a/lib/types/PayConfig.js +++ b/lib/types/PayConfig.js @@ -1,6 +1,8 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.PAY_CHANNEL_WECHAT_NATIVE_NAME = exports.PAY_CHANNEL_WECHAT_APP_NAME = exports.PAY_CHANNEL_WECHAT_H5_NAME = exports.PAY_CHANNEL_WECHAT_MP_NAME = exports.PAY_CHANNEL_WECHAT_JS_NAME = exports.PAY_CHANNEL_OFFLINE_NAME = exports.PAY_CHANNEL_ACCOUNT_NAME = void 0; +exports.PAY_WECHAT_CHANNELS = exports.PAY_CHANNEL_WECHAT_NATIVE_NAME = exports.PAY_CHANNEL_WECHAT_APP_NAME = exports.PAY_CHANNEL_WECHAT_H5_NAME = exports.PAY_CHANNEL_WECHAT_MP_NAME = exports.PAY_CHANNEL_WECHAT_JS_NAME = exports.PAY_CHANNEL_OFFLINE_NAME = exports.PAY_CHANNEL_ACCOUNT_NAME = void 0; +; +; exports.PAY_CHANNEL_ACCOUNT_NAME = 'ACCOUNT'; exports.PAY_CHANNEL_OFFLINE_NAME = 'OFFLINE'; exports.PAY_CHANNEL_WECHAT_JS_NAME = 'WECHAT_JS'; @@ -8,3 +10,10 @@ exports.PAY_CHANNEL_WECHAT_MP_NAME = 'WECHAT_MP'; exports.PAY_CHANNEL_WECHAT_H5_NAME = 'WECHAT_H5'; exports.PAY_CHANNEL_WECHAT_APP_NAME = 'WECHAT_APP'; exports.PAY_CHANNEL_WECHAT_NATIVE_NAME = 'WECHAT_NATIVE'; +exports.PAY_WECHAT_CHANNELS = [ + exports.PAY_CHANNEL_WECHAT_JS_NAME, + exports.PAY_CHANNEL_WECHAT_MP_NAME, + exports.PAY_CHANNEL_WECHAT_H5_NAME, + exports.PAY_CHANNEL_WECHAT_APP_NAME, + exports.PAY_CHANNEL_WECHAT_NATIVE_NAME +]; diff --git a/lib/utils/pay.d.ts b/lib/utils/pay.d.ts index 0bc7be80..f352f637 100644 --- a/lib/utils/pay.d.ts +++ b/lib/utils/pay.d.ts @@ -5,3 +5,10 @@ import BackendRuntimeContext from '../context/BackendRuntimeContext'; import { IncomingHttpHeaders } from 'http'; export declare const fullPayProjection: EntityDict['pay']['Selection']['data']; export declare function payNotify(context: BackendRuntimeContext, body: any, payId: string, headers: IncomingHttpHeaders): Promise; +/** + * 计算充值的损耗比例 + * @param context + * @param channel + * @param application + */ +export declare function getDepositRatio(channel: string, application: ED['application']['Schema']): number; diff --git a/lib/utils/pay.js b/lib/utils/pay.js index 78a4a1a3..1856c437 100644 --- a/lib/utils/pay.js +++ b/lib/utils/pay.js @@ -1,10 +1,11 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.payNotify = exports.fullPayProjection = void 0; +exports.getDepositRatio = exports.payNotify = exports.fullPayProjection = void 0; const tslib_1 = require("tslib"); const payClazz_1 = require("./payClazz"); const assert_1 = tslib_1.__importDefault(require("assert")); const uuid_1 = require("oak-domain/lib/utils/uuid"); +const PayConfig_1 = require("../types/PayConfig"); exports.fullPayProjection = { id: 1, applicationId: 1, @@ -80,3 +81,26 @@ async function payNotify(context, body, payId, headers) { return; } exports.payNotify = payNotify; +/** + * 计算充值的损耗比例 + * @param context + * @param channel + * @param application + */ +function getDepositRatio(channel, application) { + const { payConfig, system } = application; + const { payConfig: systemPayConfig } = system; + const accountConfig = systemPayConfig.find(ele => ele.channel === PayConfig_1.PAY_CHANNEL_ACCOUNT_NAME); + const { depositLoss, depositLossRatio } = accountConfig; + if (!depositLoss) { + return 0; + } + if (depositLossRatio) { + return depositLossRatio; + } + const config = systemPayConfig?.find(ele => ele.channel === channel) || payConfig?.find(ele => ele.channel === channel); + (0, assert_1.default)(config && PayConfig_1.PAY_WECHAT_CHANNELS.includes(channel)); + const { lossRatio } = config; + return lossRatio || 0; +} +exports.getDepositRatio = getDepositRatio; diff --git a/src/checkers/accountOper.ts b/src/checkers/accountOper.ts index a02964aa..a3f017c6 100644 --- a/src/checkers/accountOper.ts +++ b/src/checkers/accountOper.ts @@ -66,6 +66,12 @@ const checkers: Checker[] = [ } break; } + case 'loss' : { + if (totalPlus >= 0 || availPlus > 0 || totalPlus !== availPlus) { + throw new OakInputIllegalException('accountOper', ['availPlus'], 'accountOper为consume时,其totalPlus/availPlus必须为0或负数,且totalPlus的绝对值要更大'); + } + break; + } default: { assert(false); break; diff --git a/src/components/accountOper/pure/List.pc.tsx b/src/components/accountOper/pure/List.pc.tsx index 7f32baf6..0e8b9b8c 100644 --- a/src/components/accountOper/pure/List.pc.tsx +++ b/src/components/accountOper/pure/List.pc.tsx @@ -15,7 +15,7 @@ export default function Render(props: { renderItem={(item, index) => { const { $$createAt$$, type, totalPlus, availPlus } = item; const plus = ToYuan(totalPlus || availPlus); - const sign = plus > 0 ? '+' : '-'; + const sign = plus > 0 ? '+' : ''; const d = dayjs($$createAt$$ as number); return ( @@ -26,9 +26,9 @@ export default function Render(props: { />
- {sign}{ThousandCont(plus)} + {sign}{ThousandCont(plus, 2)}
); diff --git a/src/context/BackendRuntimeContext.ts b/src/context/BackendRuntimeContext.ts index 29179043..4fa839d5 100644 --- a/src/context/BackendRuntimeContext.ts +++ b/src/context/BackendRuntimeContext.ts @@ -28,6 +28,7 @@ export class BackendRuntimeContext exten (config) => { unset(config, 'publicKeyFilePath'); unset(config, 'privateKeyFilePath'); + unset(config, 'apiV3Key'); } ); } diff --git a/src/entities/AccountOper.ts b/src/entities/AccountOper.ts index 815ba7b7..8fe5f7fe 100644 --- a/src/entities/AccountOper.ts +++ b/src/entities/AccountOper.ts @@ -9,7 +9,7 @@ import { EntityShape } from 'oak-domain/lib/types/Entity'; import { EntityDesc, ActionDef } from 'oak-domain/lib/types'; import { Schema as Account } from './Account'; -type Type = 'deposit' | 'withdraw' | 'consume' | 'loan' | 'repay' | 'withdrawBack'; +type Type = 'deposit' | 'withdraw' | 'consume' | 'loan' | 'repay' | 'withdrawBack' | 'loss'; export interface Schema extends EntityShape { account: Account; @@ -42,6 +42,7 @@ export const entityDesc: EntityDesc[] = [ { @@ -43,7 +44,66 @@ const triggers: Trigger[] = [ context.saveOperationToEvent(id, `${DATA_SUBSCRIBER_KEYS.accountNumberChanged}-${accountId}`); return 1; }, - } as CreateTriggerInTxn + } as CreateTriggerInTxn, + { + name: '当account depoist时,根据系统配置,扣除掉损耗', + entity: 'accountOper', + action: 'create', + when: 'after', + check(operation) { + const { data } = operation; + assert(!(data instanceof Array)); + return data.type ==='deposit'; + }, + fn: async({ operation }, context, option) => { + const { data } = operation; + assert(!(data instanceof Array)); + const { entity, entityId } = data; + assert(entity === 'pay' && entityId); + const [pay] = await context.select('pay', { + data: { + id: 1, + price: 1, + channel: 1, + application: { + id: 1, + payConfig: 1, + system: { + id: 1, + payConfig: 1, + }, + } + }, + filter: { + id: entityId, + }, + }, {}); + + const { price, application, channel } = pay; + const ratio = getDepositRatio(channel!, application!); + + if (ratio > 0) { + const loss = Math.ceil(price! * ratio / 100); + await context.operate('accountOper', { + id: await generateNewIdAsync(), + action: 'create', + data: { + id: await generateNewIdAsync(), + type: 'loss', + totalPlus: -loss, + availPlus: -loss, + entity, + entityId, + accountId: data.accountId!, + } + }, {}); + + return 1; + } + + return 0; + } + } as CreateTriggerInTxn, ]; export default triggers; \ No newline at end of file diff --git a/src/triggers/index.ts b/src/triggers/index.ts index 3435c11a..8f269535 100644 --- a/src/triggers/index.ts +++ b/src/triggers/index.ts @@ -4,7 +4,7 @@ import { BRC } from '@project/types/RuntimeCxt'; import aoTriggers from './accountOper'; import payTriggers from './pay'; import userSystemTriggers from './userSystem'; -import accountTriggers from './acount'; +import accountTriggers from './account'; const triggers = [ ...aoTriggers, diff --git a/src/types/PayConfig.ts b/src/types/PayConfig.ts index c9e9d4cf..427f1851 100644 --- a/src/types/PayConfig.ts +++ b/src/types/PayConfig.ts @@ -4,26 +4,29 @@ export interface ConfigBase { channel: string; } -export type WechatPayConfig = { +export interface ThirdPartyConfig extends ConfigBase { + payNotifyUrl: string; // 支付通知回调接口 + refundNotifyUrl: string; // 退款通知回调接口 + lossRatio: number; // 损耗比例,百分数 +} + +export interface WechatPayConfig extends ThirdPartyConfig { channel: WechatPayChannel; mchId: string; publicKeyFilePath: string; privateKeyFilePath: string; apiV3Key: string; - payNotifyUrl: string; // 支付通知回调接口 - refundNotifyUrl: string; // 退款通知回调接口 - lossRatio: number; // 损耗比例,百分数 }; export type AccountPayChannel = 'ACCOUNT'; -export type AccountPayConfig = { +export interface AccountPayConfig extends ConfigBase { channel: AccountPayChannel; depositLoss: boolean; depositLossRatio?: number; // 充值损耗额度,百分数,不设置则按充值途径的损耗扣 }; export type OfflinePayChannel = 'OFFLINE'; -export type OfflinePayConfig = { +export interface OfflinePayConfig extends ConfigBase { tips: string; channel: OfflinePayChannel; options?: string[]; @@ -41,3 +44,11 @@ export const PAY_CHANNEL_WECHAT_MP_NAME = 'WECHAT_MP'; export const PAY_CHANNEL_WECHAT_H5_NAME = 'WECHAT_H5'; export const PAY_CHANNEL_WECHAT_APP_NAME = 'WECHAT_APP'; export const PAY_CHANNEL_WECHAT_NATIVE_NAME = 'WECHAT_NATIVE'; + +export const PAY_WECHAT_CHANNELS = [ + PAY_CHANNEL_WECHAT_JS_NAME, + PAY_CHANNEL_WECHAT_MP_NAME, + PAY_CHANNEL_WECHAT_H5_NAME, + PAY_CHANNEL_WECHAT_APP_NAME, + PAY_CHANNEL_WECHAT_NATIVE_NAME +]; \ No newline at end of file diff --git a/src/utils/pay.ts b/src/utils/pay.ts index 69000409..c83502fb 100644 --- a/src/utils/pay.ts +++ b/src/utils/pay.ts @@ -6,6 +6,7 @@ import { getPayClazz } from './payClazz'; import { BRC } from '../types/RuntimeCxt'; import assert from 'assert'; import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid'; +import { AccountPayConfig, PAY_CHANNEL_ACCOUNT_NAME, PAY_CHANNEL_WECHAT_APP_NAME, PAY_WECHAT_CHANNELS, PayConfig, ThirdPartyConfig } from '@project/types/PayConfig'; export const fullPayProjection: EntityDict['pay']['Selection']['data'] = { id: 1, @@ -85,4 +86,42 @@ export async function payNotify(context: } return; +} + +/** + * 计算充值的损耗比例 + * @param context + * @param channel + * @param application + */ +export function getDepositRatio( + channel: string, + application: ED['application']['Schema']) { + const { payConfig, system } = application!; + const { payConfig: systemPayConfig } = system!; + + const accountConfig = systemPayConfig!.find( + ele => ele.channel === PAY_CHANNEL_ACCOUNT_NAME + )! as AccountPayConfig; + + const { depositLoss, depositLossRatio } = accountConfig; + if (!depositLoss) { + return 0; + } + + if (depositLossRatio) { + return depositLossRatio; + } + + const config = systemPayConfig?.find( + ele => ele.channel === channel + ) || payConfig?.find( + ele => ele.channel === channel + ); + + assert(config && PAY_WECHAT_CHANNELS.includes(channel)); + + const { lossRatio } = config as ThirdPartyConfig; + + return lossRatio || 0; } \ No newline at end of file