给account充值时扣掉相应的损耗
This commit is contained in:
parent
8df9090cb2
commit
73abb9419e
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ export default function Render(props) {
|
|||
return (<List itemLayout="horizontal" dataSource={accountOpers} 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$$);
|
||||
return (<List.Item key={index}>
|
||||
<List.Item.Meta avatar={<Avatar>{t(`accountOper:v.type.${type}`).at(0)}</Avatar>} title={t(`accountOper:v.type.${type}`)} description={d.format('M月D日 HH时mm分')}/>
|
||||
<div style={{
|
||||
fontSize: 'x-large',
|
||||
color: sign ? 'gold' : 'black'
|
||||
color: sign ? 'gold' : 'rosybrown'
|
||||
}}>
|
||||
{sign}{ThousandCont(plus)}
|
||||
{sign}{ThousandCont(plus, 2)}
|
||||
</div>
|
||||
</List.Item>);
|
||||
}}/>);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export class BackendRuntimeContext extends DependentBackendRuntimeContext {
|
|||
payConfig.forEach((config) => {
|
||||
unset(config, 'publicKeyFilePath');
|
||||
unset(config, 'privateKeyFilePath');
|
||||
unset(config, 'apiV3Key');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const style = {
|
|||
consume: '#A569BD',
|
||||
loan: '#CD6155',
|
||||
repay: '#82E0AA',
|
||||
loss: '#2E4053'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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": "损耗" } } }
|
||||
|
|
|
|||
|
|
@ -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": "已部分退款" } } }
|
||||
|
|
|
|||
|
|
@ -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<Environment>;
|
||||
refreshedAt: Q_DateValue;
|
||||
value: Q_StringValue;
|
||||
oldValue: Q_StringValue;
|
||||
ableState: Q_EnumValue<AbleState>;
|
||||
email: Email.Filter;
|
||||
mobile: Mobile.Filter;
|
||||
|
|
@ -89,6 +92,7 @@ export type Projection = {
|
|||
env?: number | JsonProjection<Environment>;
|
||||
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;
|
||||
} | {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,12 @@ export const desc = {
|
|||
length: 64
|
||||
}
|
||||
},
|
||||
oldValue: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 64
|
||||
}
|
||||
},
|
||||
ableState: {
|
||||
type: "enum",
|
||||
enumeration: ["enabled", "disabled"]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export const style = {
|
|||
color: {
|
||||
ableState: {
|
||||
enabled: '#008000',
|
||||
disabled: '#A9A9A9'
|
||||
disabled: '#A9A9A9',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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": "已禁用" } } }
|
||||
|
|
|
|||
|
|
@ -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<EntityDict, 'account', BRC>[];
|
||||
export default triggers;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<WechatPayConfig | AccountPayConfig | OfflinePayConfig>;
|
||||
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[];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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<ED extends EntityDict & BaseEntityDict>(context: BackendRuntimeContext<ED>, body: any, payId: string, headers: IncomingHttpHeaders): Promise<void>;
|
||||
/**
|
||||
* 计算充值的损耗比例
|
||||
* @param context
|
||||
* @param channel
|
||||
* @param application
|
||||
*/
|
||||
export declare function getDepositRatio<ED extends EntityDict & BaseEntityDict>(channel: string, application: ED['application']['Schema']): number;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ exports.entityDesc = {
|
|||
loan: '抵押',
|
||||
repay: '偿还',
|
||||
withdrawBack: '提现失败',
|
||||
loss: '损耗'
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -35,6 +36,7 @@ exports.entityDesc = {
|
|||
consume: '#A569BD',
|
||||
loan: '#CD6155',
|
||||
repay: '#82E0AA',
|
||||
loss: '#2E4053'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ exports.style = {
|
|||
consume: '#A569BD',
|
||||
loan: '#CD6155',
|
||||
repay: '#82E0AA',
|
||||
loss: '#2E4053'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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": "损耗" } } }
|
||||
|
|
|
|||
|
|
@ -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": "已部分退款" } } }
|
||||
|
|
|
|||
|
|
@ -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<Environment>;
|
||||
refreshedAt: Q_DateValue;
|
||||
value: Q_StringValue;
|
||||
oldValue: Q_StringValue;
|
||||
ableState: Q_EnumValue<AbleState>;
|
||||
email: Email.Filter;
|
||||
mobile: Mobile.Filter;
|
||||
|
|
@ -89,6 +92,7 @@ export type Projection = {
|
|||
env?: number | JsonProjection<Environment>;
|
||||
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;
|
||||
} | {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,12 @@ exports.desc = {
|
|||
length: 64
|
||||
}
|
||||
},
|
||||
oldValue: {
|
||||
type: "varchar",
|
||||
params: {
|
||||
length: 64
|
||||
}
|
||||
},
|
||||
ableState: {
|
||||
type: "enum",
|
||||
enumeration: ["enabled", "disabled"]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ exports.style = {
|
|||
color: {
|
||||
ableState: {
|
||||
enabled: '#008000',
|
||||
disabled: '#A9A9A9'
|
||||
disabled: '#A9A9A9',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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": "已禁用" } } }
|
||||
|
|
|
|||
|
|
@ -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<EntityDict, 'account', BRC>[];
|
||||
export default triggers;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<WechatPayConfig | AccountPayConfig | OfflinePayConfig>;
|
||||
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[];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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<ED extends EntityDict & BaseEntityDict>(context: BackendRuntimeContext<ED>, body: any, payId: string, headers: IncomingHttpHeaders): Promise<void>;
|
||||
/**
|
||||
* 计算充值的损耗比例
|
||||
* @param context
|
||||
* @param channel
|
||||
* @param application
|
||||
*/
|
||||
export declare function getDepositRatio<ED extends EntityDict & BaseEntityDict>(channel: string, application: ED['application']['Schema']): number;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -66,6 +66,12 @@ const checkers: Checker<EntityDict, 'accountOper', RuntimeCxt>[] = [
|
|||
}
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<List.Item key={index}>
|
||||
|
|
@ -26,9 +26,9 @@ export default function Render(props: {
|
|||
/>
|
||||
<div style={{
|
||||
fontSize: 'x-large',
|
||||
color: sign ? 'gold' : 'black'
|
||||
color: sign ? 'gold' : 'rosybrown'
|
||||
}}>
|
||||
{sign}{ThousandCont(plus)}
|
||||
{sign}{ThousandCont(plus, 2)}
|
||||
</div>
|
||||
</List.Item>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export class BackendRuntimeContext<ED extends EntityDict & BaseEntityDict> exten
|
|||
(config) => {
|
||||
unset(config, 'publicKeyFilePath');
|
||||
unset(config, 'privateKeyFilePath');
|
||||
unset(config, 'apiV3Key');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Schema, '', '', {
|
|||
loan: '抵押',
|
||||
repay: '偿还',
|
||||
withdrawBack: '提现失败',
|
||||
loss: '损耗'
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -54,7 +55,8 @@ export const entityDesc: EntityDesc<Schema, '', '', {
|
|||
withdrawBack: '#F7DC6F',
|
||||
consume: '#A569BD',
|
||||
loan: '#CD6155',
|
||||
repay: '#82E0AA',
|
||||
repay: '#82E0AA',
|
||||
loss: '#2E4053'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { EntityDict } from '../oak-app-domain';
|
|||
import { BRC } from '@project/types/RuntimeCxt';
|
||||
import assert from 'assert';
|
||||
import { DATA_SUBSCRIBER_KEYS } from '@project/config/constants';
|
||||
import { getDepositRatio } from '@project/utils/pay';
|
||||
|
||||
const triggers: Trigger<EntityDict, 'accountOper', BRC>[] = [
|
||||
{
|
||||
|
|
@ -43,7 +44,66 @@ const triggers: Trigger<EntityDict, 'accountOper', BRC>[] = [
|
|||
context.saveOperationToEvent(id, `${DATA_SUBSCRIBER_KEYS.accountNumberChanged}-${accountId}`);
|
||||
return 1;
|
||||
},
|
||||
} as CreateTriggerInTxn<EntityDict, 'accountOper', BRC>
|
||||
} as CreateTriggerInTxn<EntityDict, 'accountOper', BRC>,
|
||||
{
|
||||
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<EntityDict, 'accountOper', BRC>,
|
||||
];
|
||||
|
||||
export default triggers;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
];
|
||||
|
|
@ -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<ED extends EntityDict & BaseEntityDict>(context:
|
|||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算充值的损耗比例
|
||||
* @param context
|
||||
* @param channel
|
||||
* @param application
|
||||
*/
|
||||
export function getDepositRatio<ED extends EntityDict & BaseEntityDict>(
|
||||
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;
|
||||
}
|
||||
Loading…
Reference in New Issue