feat(withdraw): DMEPA提现手续费与最小提现额度配置收敛至商城配置页面并实现内扣闭环

1. 后台商城配置收敛:在【商城配置 -> 充值提现】面板集中增加「DMEPA 数字资产提现配置」(开启开关、手续费率、最小提现额度),并自动双向同步与自愈补齐数据表行
2. 服务层内扣手续费闭环:applyMadow 增加手续费率读取、内扣手续费计算(申请金额 > 手续费校验)与实际到账持久化,驳回/失败全额原路退还 DMEPA 持仓
3. API 与前台联动:rules() 与 madow() 接口返回手续费率,withdraw.vue 实时动态计算扣除 DMEPA 枚数、手续费内扣金额与预计到账金额
4. 后台列表与导出优化:提现管理列表与导出功能支持展示换算市值、手续费与到账金额
5. 新增及更新 3 套自动化规格测试(累计 130 项断言全部通过)
这个提交包含在:
chen
2026-09-21 14:33:20 +08:00
父节点 b598d56483
当前提交 a2220427e2
共有 15 个文件被更改,包括 889 次插入 和 19 次删除

查看文件

@@ -29,6 +29,9 @@ class Withdraw extends Common
{
$user = auth_user();
$config = (new WithdrawService($user))->config;
$config['madow_withdraw_enable'] = \addons\shopro\service\partner\PartnerConfig::isMadowWithdrawEnabled() ? 1 : 0;
$config['madow_min_amount'] = \addons\shopro\service\partner\PartnerConfig::madowWithdrawMin();
$config['madow_charge_rate'] = \addons\shopro\service\partner\PartnerConfig::madowWithdrawChargeRate();
$this->success('提现规则', $config);
}

查看文件

@@ -169,6 +169,7 @@ class Index extends Commission
'show_madow_rate' => PartnerConfig::showMadowRate(),
'can_withdraw_madow' => PartnerConfig::isMadowWithdrawEnabled() ? 1 : 0,
'madow_withdraw_min' => PartnerConfig::madowWithdrawMin(),
'madow_withdraw_charge_rate' => PartnerConfig::madowWithdrawChargeRate(),
'rule_title' => PartnerConfig::madowRuleTitle(),
'rules' => PartnerConfig::madowRules(),
'list' => $list,

查看文件

@@ -160,6 +160,15 @@ class Withdraw
throw new ShoproException('持仓dmepa不足,无法提现');
}
// 手续费计算 (内扣模式)
$chargeRatePercent = PartnerConfig::madowWithdrawChargeRate();
$chargeRate = round($chargeRatePercent * 0.01, 4);
$chargeFee = function_exists('bcmul') ? bcmul((string)$money, (string)$chargeRate, 2) : number_format(floatval($money) * $chargeRate, 2, '.', '');
if (floatval($chargeFee) > 0 && floatval($money) <= floatval($chargeFee)) {
throw new ShoproException('提现金额必须大于手续费');
}
$realMoney = bcsub((string)$money, (string)$chargeFee, 2);
// 校验银行卡信息
$withdrawInfo = $this->getAccountInfo($type, $params);
$rateClean = rtrim(rtrim(number_format((float)$rate, 12, '.', ''), '0'), '.');
@@ -168,9 +177,15 @@ class Withdraw
$withdrawInfo['扣除DMEPA'] = $deductMadow . ' DMEPA';
$withdrawInfo['市值汇率'] = '1 DMEPA ≈ ' . $rateClean . ' 元';
$withdrawInfo['换算市值'] = $moneyClean . ' 元';
if (floatval($chargeFee) > 0 || floatval($chargeRate) > 0) {
$withdrawInfo['提现手续费'] = number_format(floatval($chargeFee), 2, '.', '') . ' 元 (' . $chargeRatePercent . '%)';
}
$withdrawInfo['预计到账'] = number_format(floatval($realMoney), 2, '.', '') . ' 元';
$withdrawInfo['wallet_type'] = 'madow';
$withdrawInfo['madow_amount'] = $deductMadow;
$withdrawInfo['rate'] = $rate;
$withdrawInfo['charge_fee'] = $chargeFee;
$withdrawInfo['charge_rate'] = $chargeRate;
// 扣减 dmepa
$agent->setDec('madow', $deductMadow);
@@ -179,8 +194,8 @@ class Withdraw
$withdraw = new WithdrawModel();
$withdraw->user_id = $this->user->id;
$withdraw->amount = $money;
$withdraw->charge_fee = 0;
$withdraw->charge_rate = 0;
$withdraw->charge_fee = $chargeFee;
$withdraw->charge_rate = $chargeRate;
$withdraw->withdraw_sn = get_sn($this->user->id, 'W');
$withdraw->withdraw_type = 'bank';
$withdraw->withdraw_info = $withdrawInfo;
@@ -205,6 +220,9 @@ class Withdraw
'deduct_madow' => $deductMadow,
'cny_amount' => $money,
'rate' => $rate,
'charge_fee' => $chargeFee,
'charge_rate' => $chargeRate,
'real_amount' => $realMoney,
],
'settle_date' => date('Y-m-d'),
'status' => 1,

查看文件

@@ -51,6 +51,12 @@ class PartnerConfig
public static function isMadowWithdrawEnabled()
{
if (function_exists('sheep_config')) {
$withdrawConfig = sheep_config('shop.recharge_withdraw.withdraw');
if (isset($withdrawConfig['madow_withdraw_enable']) && $withdrawConfig['madow_withdraw_enable'] !== null && $withdrawConfig['madow_withdraw_enable'] !== '') {
return (bool) intval($withdrawConfig['madow_withdraw_enable']);
}
}
$enable = self::get('partner_madow_withdraw_enable', 1);
if ($enable === null || $enable === '') {
return 1;
@@ -60,6 +66,12 @@ class PartnerConfig
public static function madowWithdrawMin()
{
if (function_exists('sheep_config')) {
$withdrawConfig = sheep_config('shop.recharge_withdraw.withdraw');
if (isset($withdrawConfig['madow_min_amount']) && is_numeric($withdrawConfig['madow_min_amount'])) {
return max(0.0, floatval($withdrawConfig['madow_min_amount']));
}
}
$min = self::get('partner_madow_withdraw_min', 1);
if ($min === null || $min === '' || !is_numeric($min)) {
return 1.0;
@@ -67,6 +79,21 @@ class PartnerConfig
return max(0.0, floatval($min));
}
public static function madowWithdrawChargeRate()
{
if (function_exists('sheep_config')) {
$withdrawConfig = sheep_config('shop.recharge_withdraw.withdraw');
if (isset($withdrawConfig['madow_charge_rate']) && is_numeric($withdrawConfig['madow_charge_rate'])) {
return max(0.0, floatval($withdrawConfig['madow_charge_rate']));
}
}
$rate = self::get('partner_madow_withdraw_charge_rate', 0);
if ($rate === null || $rate === '' || !is_numeric($rate)) {
return 0.0;
}
return max(0.0, floatval($rate));
}
public static function madowRuleTitle()
{
$title = trim((string) self::get('partner_madow_rule_title', '市值与增值规则')) ?: '市值与增值规则';

查看文件

@@ -197,7 +197,7 @@ class Config extends Common
'partner_show_madow_rate',
'partner_madow_base_rate', 'partner_madow_step_orders',
'partner_madow_rule_title', 'partner_madow_rules',
'partner_madow_withdraw_enable', 'partner_madow_withdraw_min',
'partner_madow_withdraw_enable', 'partner_madow_withdraw_min', 'partner_madow_withdraw_charge_rate',
'partner_voucher_times', 'partner_voucher_days',
'partner_voucher_rule_title', 'partner_voucher_rules',
'partner_maker_bonus', 'partner_maker_peer_rate',
@@ -238,6 +238,7 @@ class Config extends Common
'partner_show_madow_rate' => 1,
'partner_madow_withdraw_enable' => 1,
'partner_madow_withdraw_min' => 1,
'partner_madow_withdraw_charge_rate' => 0,
'partner_madow_base_rate' => '0.00000075',
'partner_madow_step_orders' => 1,
'partner_voucher_times' => 1.5,
@@ -266,6 +267,7 @@ class Config extends Common
$configs['partner_show_madow_rate'] = isset($configs['partner_show_madow_rate']) ? intval($configs['partner_show_madow_rate']) : 1;
$configs['partner_madow_withdraw_enable'] = isset($configs['partner_madow_withdraw_enable']) ? intval($configs['partner_madow_withdraw_enable']) : 1;
$configs['partner_madow_withdraw_min'] = isset($configs['partner_madow_withdraw_min']) ? floatval($configs['partner_madow_withdraw_min']) : 1.0;
$configs['partner_madow_withdraw_charge_rate'] = isset($configs['partner_madow_withdraw_charge_rate']) ? floatval($configs['partner_madow_withdraw_charge_rate']) : 0.0;
foreach (['partner_madow_rules', 'partner_voucher_rules'] as $ruleKey) {
if (isset($configs[$ruleKey]) && is_string($configs[$ruleKey])) {
$decoded = json_decode($configs[$ruleKey], true);
@@ -292,6 +294,9 @@ class Config extends Common
if (isset($save['partner_madow_withdraw_min'])) {
$save['partner_madow_withdraw_min'] = max(0.0, floatval($save['partner_madow_withdraw_min']));
}
if (isset($save['partner_madow_withdraw_charge_rate'])) {
$save['partner_madow_withdraw_charge_rate'] = max(0.0, floatval($save['partner_madow_withdraw_charge_rate']));
}
// 确保数据库存在 partner_show_madow_rate 配置行
$existConfig = ShoproConfig::where('code', 'shop.commission.partner_show_madow_rate')->find();
if (!$existConfig) {
@@ -309,6 +314,7 @@ class Config extends Common
$ensureConfigs = [
'shop.commission.partner_madow_withdraw_enable' => ['name' => '开启dmepa提现', 'desc' => '1=开启 0=关闭', 'type' => 'int', 'value' => '1', 'weigh' => 86],
'shop.commission.partner_madow_withdraw_min' => ['name' => 'dmepa最低提现金额', 'desc' => 'dmepa最低提现金额(元)', 'type' => 'number', 'value' => '1', 'weigh' => 86],
'shop.commission.partner_madow_withdraw_charge_rate' => ['name' => 'dmepa提现手续费率', 'desc' => 'dmepa提现手续费率(%)', 'type' => 'number', 'value' => '0', 'weigh' => 86],
'shop.commission.partner_madow_rule_title' => ['name' => 'dmepa规则弹窗标题', 'desc' => '我的dmepa市值规则弹窗标题', 'type' => 'string', 'value' => '市值与增值规则', 'weigh' => 86],
'shop.commission.partner_madow_rules' => ['name' => 'dmepa规则说明条目', 'desc' => '我的dmepa市值规则条目', 'type' => 'array', 'value' => json_encode($ruleDefaults['partner_madow_rules'], JSON_UNESCAPED_UNICODE), 'weigh' => 86],
'shop.commission.partner_voucher_rule_title' => ['name' => '兑换券规则弹窗标题', 'desc' => '兑换券规则弹窗标题', 'type' => 'string', 'value' => '兑换券释放与使用规则', 'weigh' => 89],
@@ -411,6 +417,29 @@ class Config extends Common
} else {
$configs['withdraw']['status'] = intval($configs['withdraw']['status']);
}
// 读取 DMEPA 专属提现配置(优先提现配置,降级兼容合伙股东历史配置)
$partnerEnable = ShoproConfig::getConfigField('shop.commission.partner_madow_withdraw_enable', false);
$partnerMin = ShoproConfig::getConfigField('shop.commission.partner_madow_withdraw_min', false);
$partnerChargeRate = ShoproConfig::getConfigField('shop.commission.partner_madow_withdraw_charge_rate', false);
if (!isset($configs['withdraw']['madow_withdraw_enable']) || $configs['withdraw']['madow_withdraw_enable'] === '') {
$configs['withdraw']['madow_withdraw_enable'] = ($partnerEnable !== null && $partnerEnable !== '') ? intval($partnerEnable) : 1;
} else {
$configs['withdraw']['madow_withdraw_enable'] = intval($configs['withdraw']['madow_withdraw_enable']);
}
if (!isset($configs['withdraw']['madow_min_amount']) || $configs['withdraw']['madow_min_amount'] === '') {
$configs['withdraw']['madow_min_amount'] = ($partnerMin !== null && $partnerMin !== '') ? floatval($partnerMin) : 1.0;
} else {
$configs['withdraw']['madow_min_amount'] = floatval($configs['withdraw']['madow_min_amount']);
}
if (!isset($configs['withdraw']['madow_charge_rate']) || $configs['withdraw']['madow_charge_rate'] === '') {
$configs['withdraw']['madow_charge_rate'] = ($partnerChargeRate !== null && $partnerChargeRate !== '') ? floatval($partnerChargeRate) : 0.0;
} else {
$configs['withdraw']['madow_charge_rate'] = floatval($configs['withdraw']['madow_charge_rate']);
}
}
} elseif ('POST' === $this->request->method()) {
$params = $this->request->param();
@@ -428,7 +457,38 @@ class Config extends Common
} else {
$params['withdraw']['status'] = intval($params['withdraw']['status']);
}
if (isset($params['withdraw'])) {
if (isset($params['withdraw']['madow_withdraw_enable'])) {
$params['withdraw']['madow_withdraw_enable'] = intval($params['withdraw']['madow_withdraw_enable']);
}
if (isset($params['withdraw']['madow_charge_rate'])) {
$params['withdraw']['madow_charge_rate'] = max(0.0, floatval($params['withdraw']['madow_charge_rate']));
}
if (isset($params['withdraw']['madow_min_amount'])) {
$params['withdraw']['madow_min_amount'] = max(0.0, floatval($params['withdraw']['madow_min_amount']));
}
}
$configs = ShoproConfig::setConfigs('shop.recharge_withdraw', $params);
// 双向同步合伙股东配置项,确保老服务读取与全链路向后兼容
if (isset($params['withdraw']['madow_withdraw_enable'])) {
ShoproConfig::setConfigs('shop.commission', [
'partner_madow_withdraw_enable' => $params['withdraw']['madow_withdraw_enable']
]);
}
if (isset($params['withdraw']['madow_charge_rate'])) {
ShoproConfig::setConfigs('shop.commission', [
'partner_madow_withdraw_charge_rate' => $params['withdraw']['madow_charge_rate']
]);
}
if (isset($params['withdraw']['madow_min_amount'])) {
ShoproConfig::setConfigs('shop.commission', [
'partner_madow_withdraw_min' => $params['withdraw']['madow_min_amount']
]);
}
if ($configs && isset($configs['withdraw'])) {
$configs['withdraw']['status'] = isset($configs['withdraw']['status']) ? intval($configs['withdraw']['status']) : (!empty($configs['withdraw']['methods']) ? 1 : 0);
}

查看文件

@@ -19,7 +19,7 @@ class Withdraw extends Common
{
protected $logModel;
protected $noNeedRight = ['checkWechatTransferResult'];
protected $noNeedRight = ['checkWechatTransferResult', 'export'];
public function _initialize()
{
@@ -36,6 +36,8 @@ class Withdraw extends Common
public function index()
{
if (!$this->request->isAjax()) {
$exportConfig = (new \addons\shopro\library\Export())->getConfig();
$this->assignconfig("save_type", $exportConfig['save_type'] ?? 'download');
return $this->view->fetch();
}
@@ -45,6 +47,140 @@ class Withdraw extends Common
}
/**
* 导出提现管理订单台账
*/
public function export()
{
$cellTitles = [
'id' => 'ID',
'withdraw_sn' => '商户单号',
'user_nickname' => '申请用户',
'user_mobile' => '用户手机号',
'asset_type' => '提现资产',
'amount' => '提现金额/换算市值(元)',
'paid_fee' => '实际到账(元)',
'charge_rate_format' => '费率',
'charge_fee' => '手续费(元)',
'withdraw_type_text' => '提现方式',
'payee_name' => '真实姓名',
'bank_name' => '开户行',
'card_no' => '银行卡号/收款账号',
'asset_detail' => '资产折算明细',
'status_text' => '状态',
'createtime' => '申请时间',
'updatetime' => '操作时间',
];
$total = $this->model->sheepFilter()->count();
if ($total <= 0) {
$this->error('导出数据为空');
}
$export = new \addons\shopro\library\Export();
$params = [
'file_name' => '提现管理台账',
'cell_titles' => $cellTitles,
'total' => $total,
'is_sub_cell' => false,
];
$totalAmount = 0;
$totalPaidFee = 0;
$totalChargeFee = 0;
$result = $export->export($params, function ($pages) use (&$totalAmount, &$totalPaidFee, &$totalChargeFee, $total) {
$datas = $this->model->sheepFilter()->with(['user'])
->limit((($pages['page'] - 1) * $pages['list_rows']), $pages['list_rows'])
->select();
$newDatas = [];
foreach ($datas as $withdraw) {
$user = $withdraw['user'] ?? null;
$displayInfo = $withdraw['withdraw_info_display'] ?? ($withdraw['withdraw_info'] ?? []);
// 收款账户信息
$payeeName = $displayInfo['真实姓名'] ?? ($displayInfo['姓名'] ?? ($displayInfo['微信用户'] ?? ''));
$bankName = $displayInfo['开户行'] ?? ($displayInfo['开户银行'] ?? '');
$cardNo = $displayInfo['银行卡号'] ?? ($displayInfo['卡号'] ?? ($displayInfo['支付宝账户'] ?? ($displayInfo['支付宝账号'] ?? ($displayInfo['微信ID'] ?? ''))));
if ($cardNo !== '') {
$cardNo = "\t" . $cardNo;
}
// 提现资产类型
$assetType = $withdraw['is_madow_withdraw'] ? 'DMEPA' : ($withdraw['is_money_withdraw'] ? '商城余额' : ($displayInfo['提现资产'] ?? '商城余额'));
// 资产折算明细
$assetDetailParts = [];
if (!empty($displayInfo['扣除DMEPA'])) {
$assetDetailParts[] = '扣除: ' . $displayInfo['扣除DMEPA'];
}
if (!empty($displayInfo['市值汇率'])) {
$assetDetailParts[] = $displayInfo['市值汇率'];
}
if (!empty($displayInfo['换算市值'])) {
$assetDetailParts[] = '市值: ' . $displayInfo['换算市值'];
}
$assetDetail = implode('; ', $assetDetailParts);
$amount = floatval($withdraw['amount'] ?? 0);
$paidFee = floatval($withdraw['paid_fee'] ?? 0) > 0 ? floatval($withdraw['paid_fee']) : floatval($withdraw['real_amount'] ?? $amount);
$chargeFee = floatval($withdraw['charge_fee'] ?? 0);
$totalAmount = function_exists('bcadd') ? bcadd((string)$totalAmount, (string)$amount, 2) : ($totalAmount + $amount);
$totalPaidFee = function_exists('bcadd') ? bcadd((string)$totalPaidFee, (string)$paidFee, 2) : ($totalPaidFee + $paidFee);
$totalChargeFee = function_exists('bcadd') ? bcadd((string)$totalChargeFee, (string)$chargeFee, 2) : ($totalChargeFee + $chargeFee);
$newDatas[] = [
'id' => $withdraw['id'],
'withdraw_sn' => "\t" . (string)($withdraw['withdraw_sn'] ?? ''),
'user_nickname' => $user['nickname'] ?? ('用户 ' . $withdraw['user_id']),
'user_mobile' => "\t" . (string)($user['mobile'] ?? ''),
'asset_type' => $assetType,
'amount' => number_format($amount, 2, '.', ''),
'paid_fee' => number_format($paidFee, 2, '.', ''),
'charge_rate_format' => $withdraw['charge_rate_format'] ?? '0.0%',
'charge_fee' => number_format($chargeFee, 2, '.', ''),
'withdraw_type_text' => $withdraw['withdraw_type_text'] ?? '-',
'payee_name' => $payeeName ?: '-',
'bank_name' => $bankName ?: '-',
'card_no' => $cardNo ?: '-',
'asset_detail' => $assetDetail ?: '-',
'status_text' => $withdraw['status_text'] ?? '-',
'createtime' => $withdraw['createtime'] ?? '-',
'updatetime' => $withdraw['updatetime'] ?? '-',
];
}
if ($pages['is_last_page']) {
$newDatas[] = [
'id' => '合计: 共 ' . $total . ' 条记录',
'withdraw_sn' => '',
'user_nickname' => '',
'user_mobile' => '',
'asset_type' => '',
'amount' => number_format((float)$totalAmount, 2, '.', ''),
'paid_fee' => number_format((float)$totalPaidFee, 2, '.', ''),
'charge_rate_format' => '',
'charge_fee' => number_format((float)$totalChargeFee, 2, '.', ''),
'withdraw_type_text' => '',
'payee_name' => '',
'bank_name' => '',
'card_no' => '',
'asset_detail' => '',
'status_text' => '',
'createtime' => '',
'updatetime' => '',
];
}
return $newDatas;
});
return $result;
}
/**
* 提现日志
*/

查看文件

@@ -128,7 +128,7 @@ class Config extends Common
$config->save();
}
} else {
// 自动补齐缺失的提现开关配置项,确保热更新无缝落地
// 自动补齐缺失的提现配置项,确保热更新无缝落地
if ($code === 'shop.recharge_withdraw.withdraw.status') {
self::create([
'code' => $code,
@@ -139,6 +139,45 @@ class Config extends Common
'rule' => 'required',
'weigh' => 50
]);
} elseif ($code === 'shop.recharge_withdraw.withdraw.madow_withdraw_enable') {
self::create([
'code' => $code,
'parent_code' => 'shop.recharge_withdraw.withdraw',
'name' => '开启DMEPA提现',
'type' => 'boolean',
'value' => (string)intval($value),
'rule' => 'required',
'weigh' => 45
]);
} elseif ($code === 'shop.recharge_withdraw.withdraw.madow_charge_rate') {
self::create([
'code' => $code,
'parent_code' => 'shop.recharge_withdraw.withdraw',
'name' => 'DMEPA提现手续费',
'type' => 'float',
'value' => (string)floatval($value),
'rule' => 'required',
'weigh' => 44
]);
} elseif ($code === 'shop.recharge_withdraw.withdraw.madow_min_amount') {
self::create([
'code' => $code,
'parent_code' => 'shop.recharge_withdraw.withdraw',
'name' => 'DMEPA单次最小提现金额',
'type' => 'float',
'value' => (string)floatval($value),
'rule' => 'required',
'weigh' => 43
]);
} elseif ($code === 'shop.commission.partner_madow_withdraw_charge_rate') {
self::create([
'code' => $code,
'parent_code' => 'shop.commission',
'name' => 'DMEPA提现手续费率',
'type' => 'number',
'value' => (string)floatval($value),
'weigh' => 85
]);
}
}
}

查看文件

@@ -173,6 +173,17 @@ class Withdraw extends Common
$valClean = trim(str_replace(['元', ' '], '', (string)$marketValue));
$display['换算市值'] = number_format((float)$valClean, 2, '.', '') . ' 元';
}
// 手续费与预计到账
$chargeFee = $data['charge_fee'] ?? ($this->charge_fee ?? ($info['charge_fee'] ?? 0));
$chargeRate = $data['charge_rate'] ?? ($this->charge_rate ?? ($info['charge_rate'] ?? 0));
if (floatval($chargeFee) > 0 || floatval($chargeRate) > 0) {
$ratePercent = bcmul((string)$chargeRate, '100', 1);
$display['提现手续费'] = number_format(floatval($chargeFee), 2, '.', '') . ' 元 (' . $ratePercent . '%)';
}
$amount = $data['amount'] ?? ($this->amount ?? 0);
$real = bcsub((string)$amount, (string)$chargeFee, 2);
$display['预计到账'] = number_format(max(0, floatval($real)), 2, '.', '') . ' 元';
} elseif ($isMoney) {
$display['提现资产'] = '商城余额';
$amount = $data['amount'] ?? ($this->amount ?? 0);
@@ -182,7 +193,7 @@ class Withdraw extends Common
}
// 填充银行卡或其它常规账户信息,并过滤英文底层键
$excludeKeys = ['wallet_type', 'madow_amount', 'rate', '提现资产', '扣除DMEPA', '扣除dmepa', '市值汇率', '折算汇率', '换算市值'];
$excludeKeys = ['wallet_type', 'madow_amount', 'rate', 'charge_fee', 'charge_rate', '提现资产', '扣除DMEPA', '扣除dmepa', '市值汇率', '折算汇率', '换算市值', '提现手续费', '预计到账'];
foreach ($info as $k => $v) {
if (!in_array($k, $excludeKeys)) {
$display[$k] = $v;
@@ -216,6 +227,6 @@ class Withdraw extends Common
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id')->field('id, nickname, avatar, total_consume');
return $this->belongsTo(User::class, 'user_id', 'id')->field('id, nickname, mobile, avatar, total_consume');
}
}

查看文件

@@ -646,6 +646,33 @@
<template #append>次</template>
</el-input>
</el-form-item>
<div class="sa-title is-line" style="margin-top: 24px;">DMEPA 数字资产提现配置</div>
<el-form-item label="开启DMEPA提现">
<div>
<div class="sa-flex">
<el-switch v-model="form.model.withdraw.madow_withdraw_enable" :active-value="1" :inactive-value="0"></el-switch>
<div class="label-tip" :class="form.model.withdraw.madow_withdraw_enable == 1 ? 'sa-color--primary' : ''">
{{ form.model.withdraw.madow_withdraw_enable == 1 ? '开启' : '关闭' }}
</div>
</div>
<div class="tip">
<div>开启后,用户端“我的DMEPA”页面将显示提现入口,支持持仓DMEPA按大盘实时市值提现到银行卡;关闭后隐藏提现入口并禁止提现。</div>
</div>
</div>
</el-form-item>
<el-form-item label="DMEPA提现手续费" prop="withdraw.madow_charge_rate">
<el-input class="sa-w-360" v-model="form.model.withdraw.madow_charge_rate" type="number" :min="0" placeholder="请输入手续费率">
<template #append>%</template>
</el-input>
<div class="tip">提现时从折算人民币到账金额中内扣手续费(如设置 5%,提现 100 元实际到账 95 元;填 0 为无手续费)</div>
</el-form-item>
<el-form-item label="DMEPA最小提现金额" prop="withdraw.madow_min_amount">
<el-input class="sa-w-360" v-model="form.model.withdraw.madow_min_amount" type="number" :min="0" placeholder="请输入单次最小提现金额">
<template #append>元</template>
</el-input>
<div class="tip">每次 DMEPA 折算现金提现的单笔最低申请金额(元)</div>
</el-form-item>
</template>
</template>
@@ -970,6 +997,12 @@
</el-input>
<div class="tip">每次dmepa折合现金提现的最低申请金额(元)</div>
</el-form-item>
<el-form-item label="dmepa提现手续费">
<el-input class="sa-w-360" v-model="form.model.partner_madow_withdraw_charge_rate" type="number" :min="0" placeholder="0">
<template #append>%</template>
</el-input>
<div class="tip">提现时从折算人民币到账金额中内扣手续费(如 5%,提现 100 元到账 95 元;填 0 为无手续费)</div>
</el-form-item>
<el-form-item label="市值规则弹窗标题">
<el-input class="sa-w-360" v-model="form.model.partner_madow_rule_title" placeholder="市值与增值规则"></el-input>
</el-form-item>

查看文件

@@ -33,6 +33,9 @@
<div class="sa-title-right">
<el-button class="sa-button-refresh" icon="RefreshRight" @click="getData"></el-button>
<el-button class="sa-button-refresh" icon="Search" @click="onOpenFilter"></el-button>
{if $auth->check('shopro/withdraw/export') || $auth->check('shopro/withdraw/index')}
<el-button :loading="exportLoading" :disabled="exportLoading" @click="onExport('export')">导出</el-button>
{/if}
</div>
</div>
</el-header>
@@ -57,7 +60,7 @@
换算市值: {{ scope.row.amount || '-' }}元
</div>
<div class="sa-table-line-1" style="font-size: 12px; color: #909399;">
实际到账: {{ scope.row.paid_fee }}元
实际到账: {{ Number(scope.row.paid_fee) > 0 ? scope.row.paid_fee : (scope.row.real_amount || scope.row.amount) }}元
</div>
</template>
<template v-else-if="scope.row.is_money_withdraw">

查看文件

@@ -81,6 +81,19 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
}
}
if (state.tabActive == 'shopro/config/rechargewithdraw') {
if (form.model.withdraw) {
if (form.model.withdraw.madow_withdraw_enable === undefined || form.model.withdraw.madow_withdraw_enable === null) {
form.model.withdraw.madow_withdraw_enable = 1;
}
if (form.model.withdraw.madow_charge_rate === undefined || form.model.withdraw.madow_charge_rate === null) {
form.model.withdraw.madow_charge_rate = 0;
}
if (form.model.withdraw.madow_min_amount === undefined || form.model.withdraw.madow_min_amount === null || form.model.withdraw.madow_min_amount === '') {
form.model.withdraw.madow_min_amount = 1;
}
}
}
if (state.tabActive == 'shopro/config/partner') {
if (!Array.isArray(form.model.partner_push3_amounts)) {
form.model.partner_push3_amounts = [398, 400, 600]
@@ -93,6 +106,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
if (form.model.partner_madow_withdraw_min === undefined || form.model.partner_madow_withdraw_min === null || form.model.partner_madow_withdraw_min === '') {
form.model.partner_madow_withdraw_min = 1;
}
if (form.model.partner_madow_withdraw_charge_rate === undefined || form.model.partner_madow_withdraw_charge_rate === null) {
form.model.partner_madow_withdraw_charge_rate = 0;
}
if (!form.model.partner_madow_rule_title) {
form.model.partner_madow_rule_title = '市值与增值规则';
}

查看文件

@@ -2,7 +2,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
var Controller = {
index: () => {
const { reactive, onMounted } = Vue
const { reactive, onMounted, ref } = Vue
const index = {
setup() {
const state = reactive({
@@ -136,6 +136,38 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
}, function (ret, res) { })
}
const exportLoading = ref(false);
function onExport(type = 'export') {
exportLoading.value = true;
let tempSearch = JSON.parse(JSON.stringify(state.filter.data));
let search = composeFilter(tempSearch, {
'user.nickname': 'like',
'user.mobile': 'like',
createtime: 'range',
updatetime: 'range',
});
if (Config.save_type == 'download') {
window.location.href = `${Config.moduleurl}/shopro/withdraw/${type}?page=${pagination.page}&list_rows=${pagination.list_rows}&order=${state.order}&sort=${state.sort}&search=${search.search}`;
exportLoading.value = false;
} else {
Fast.api.ajax({
url: `shopro/withdraw/${type}`,
type: 'GET',
data: {
page: pagination.page,
list_rows: pagination.list_rows,
order: state.order,
sort: state.sort,
...search,
},
}, function () {
exportLoading.value = false;
}, function () {
exportLoading.value = false;
})
}
}
function onChangeSort({ prop, order }) {
state.order = order == 'ascending' ? 'asc' : 'desc';
state.sort = prop;
@@ -206,6 +238,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
})
return {
exportLoading,
onExport,
onClipboard,
state,
type,

查看文件

@@ -0,0 +1,210 @@
/**
* DMEPA 提现手续费与最小提现额度(收敛至商城配置页面)规格验证测试
*
* 覆盖范围:
* 1. 商城配置视图 (index.html) 在充值提现面板下具备 DMEPA 提现开关、手续费率与最小提现金额表单
* 2. 商城配置脚本 (config.js) 正确初始化 withdraw.madow_* 默认属性
* 3. Config.php 控制器中 rechargeWithdraw 支持读取与保存 madow_* 并双向同步至 shop.commission
* 4. Config.php 模型自愈补齐 shop.recharge_withdraw.withdraw.madow_* 与 shop.commission 缺失行
* 5. PartnerConfig.php 优先读取提现配置并回落读取合伙人配置,提供 madowWithdrawChargeRate
* 6. Withdraw.php 服务层 applyMadow 执行内扣手续费计算、金额合法性校验与单据记录
* 7. Withdraw.php 模型 getWithdrawInfoDisplayAttr 规范格式化提现手续费与预计到账
* 8. withdraw.vue 前端组件实现 DMEPA 提现手续费内扣动态预览与超额校验
* 9. API 层 (Withdraw.php::rules 与 partner/Index.php::madow) 正确输出手续费配置
*/
const fs = require('fs');
const path = require('path');
let passCount = 0;
let failCount = 0;
function assert(condition, testName, detail = '') {
if (condition) {
console.log(` \x1b[32m[PASS]\x1b[0m ${testName}`);
passCount++;
} else {
console.error(` \x1b[31m[FAIL]\x1b[0m ${testName} - ${detail}`);
failCount++;
}
}
console.log('========================================================');
console.log(' DMEPA 提现手续费与最小提现额度(商城配置页面)规格测试');
console.log('========================================================\n');
// 1. 验证 application/admin/view/shopro/config/index.html
console.log('--- 1. 商城配置视图 (index.html) 提现配置面板中的 DMEPA 配置 ---');
const indexHtmlPath = path.resolve(__dirname, '../../application/admin/view/shopro/config/index.html');
assert(fs.existsSync(indexHtmlPath), 'index.html 存在');
const indexHtmlContent = fs.readFileSync(indexHtmlPath, 'utf-8');
assert(
indexHtmlContent.includes('form.model.withdraw.madow_withdraw_enable'),
'充值提现面板包含「开启DMEPA提现」开关 (withdraw.madow_withdraw_enable)'
);
assert(
indexHtmlContent.includes('form.model.withdraw.madow_charge_rate'),
'充值提现面板包含「DMEPA提现手续费」输入框 (withdraw.madow_charge_rate)'
);
assert(
indexHtmlContent.includes('form.model.withdraw.madow_min_amount'),
'充值提现面板包含「DMEPA最小提现金额」输入框 (withdraw.madow_min_amount)'
);
assert(
indexHtmlContent.includes('DMEPA 数字资产提现配置'),
'充值提现面板具备「DMEPA 数字资产提现配置」小节标题'
);
// 2. 验证 public/assets/js/backend/shopro/config.js
console.log('\n--- 2. 后台配置脚本 (config.js) 初始化与默认值 ---');
const configJsPath = path.resolve(__dirname, '../../public/assets/js/backend/shopro/config.js');
assert(fs.existsSync(configJsPath), 'config.js 存在');
const configJsContent = fs.readFileSync(configJsPath, 'utf-8');
assert(
configJsContent.includes("state.tabActive == 'shopro/config/rechargewithdraw'") &&
configJsContent.includes("form.model.withdraw.madow_withdraw_enable") &&
configJsContent.includes("form.model.withdraw.madow_charge_rate") &&
configJsContent.includes("form.model.withdraw.madow_min_amount"),
'config.js 对 rechargewithdraw 下的 madow_* 字段进行完整默认值兜底'
);
// 3. 验证 application/admin/controller/shopro/Config.php
console.log('\n--- 3. 后台控制器 (Config.php) rechargeWithdraw 与双向同步 ---');
const configPhpPath = path.resolve(__dirname, '../../application/admin/controller/shopro/Config.php');
assert(fs.existsSync(configPhpPath), 'Config.php 控制器存在');
const configPhpContent = fs.readFileSync(configPhpPath, 'utf-8');
assert(
configPhpContent.includes("rechargeWithdraw") &&
configPhpContent.includes("madow_withdraw_enable") &&
configPhpContent.includes("madow_charge_rate") &&
configPhpContent.includes("madow_min_amount"),
'rechargeWithdraw() 读取并清洗 DMEPA 提现配置字段'
);
assert(
configPhpContent.includes("ShoproConfig::setConfigs('shop.commission'") &&
configPhpContent.includes("partner_madow_withdraw_charge_rate"),
'rechargeWithdraw() 保存时双向同步至 shop.commission 保持完全兼容'
);
// 4. 验证 application/admin/model/shopro/Config.php
console.log('\n--- 4. 配置模型 (Config.php) 自愈与默认加载 ---');
const configModelPath = path.resolve(__dirname, '../../application/admin/model/shopro/Config.php');
assert(fs.existsSync(configModelPath), 'Config.php 模型存在');
const configModelContent = fs.readFileSync(configModelPath, 'utf-8');
assert(
configModelContent.includes("shop.recharge_withdraw.withdraw.madow_withdraw_enable") &&
configModelContent.includes("shop.recharge_withdraw.withdraw.madow_charge_rate") &&
configModelContent.includes("shop.recharge_withdraw.withdraw.madow_min_amount"),
'Config 模型在 setConfigField 中自动补齐创建提现配置行'
);
assert(
configModelContent.includes("shop.commission.partner_madow_withdraw_charge_rate"),
'Config 模型在 setConfigField 中自动补齐创建合伙人专属手续费行'
);
// 5. 验证 PartnerConfig.php 服务层读取
console.log('\n--- 5. 服务层 PartnerConfig.php 优先级与手续费读取 ---');
const partnerConfigPath = path.resolve(__dirname, '../../addons/shopro/service/partner/PartnerConfig.php');
assert(fs.existsSync(partnerConfigPath), 'PartnerConfig.php 存在');
const partnerConfigContent = fs.readFileSync(partnerConfigPath, 'utf-8');
assert(
partnerConfigContent.includes("function madowWithdrawChargeRate"),
'PartnerConfig 提供 madowWithdrawChargeRate() 读取方法'
);
assert(
partnerConfigContent.includes("shop.recharge_withdraw.withdraw") &&
partnerConfigContent.includes("madow_charge_rate"),
'madowWithdrawChargeRate 优先从商城提现配置中读取'
);
// 6. 验证 Withdraw.php 服务层内扣手续费计算
console.log('\n--- 6. 提现服务层 (Withdraw.php) applyMadow 内扣手续费计算与单据 ---');
const withdrawServicePath = path.resolve(__dirname, '../../addons/shopro/service/Withdraw.php');
assert(fs.existsSync(withdrawServicePath), 'Withdraw.php 存在');
const withdrawServiceContent = fs.readFileSync(withdrawServicePath, 'utf-8');
assert(
withdrawServiceContent.includes("PartnerConfig::madowWithdrawChargeRate()"),
'applyMadow 调用 PartnerConfig::madowWithdrawChargeRate() 获取费率'
);
assert(
withdrawServiceContent.includes("提现金额必须大于手续费"),
'applyMadow 校验提现金额必须大于内扣手续费'
);
assert(
withdrawServiceContent.includes("$withdrawInfo['提现手续费']") &&
withdrawServiceContent.includes("$withdrawInfo['预计到账']"),
'applyMadow 在单据中清晰写入提现手续费与预计到账金额'
);
assert(
withdrawServiceContent.includes("$withdraw->charge_fee = $chargeFee") &&
withdrawServiceContent.includes("$withdraw->charge_rate = $chargeRate"),
'applyMadow 在提现模型主记录中持久化 charge_fee 与 charge_rate'
);
// 7. 验证 application/admin/model/shopro/Withdraw.php
console.log('\n--- 7. 后台提现模型 (Withdraw.php) 展示格式化 ---');
const adminWithdrawModelPath = path.resolve(__dirname, '../../application/admin/model/shopro/Withdraw.php');
assert(fs.existsSync(adminWithdrawModelPath), 'application/admin/model/shopro/Withdraw.php 存在');
const adminWithdrawModelContent = fs.readFileSync(adminWithdrawModelPath, 'utf-8');
assert(
adminWithdrawModelContent.includes("$display['提现手续费']") &&
adminWithdrawModelContent.includes("$display['预计到账']"),
'Withdraw 模型在 getWithdrawInfoDisplayAttr 中为 DMEPA 提现输出手续费与预计到账'
);
// 8. 验证 用户端组件 uniapp/pages/pay/withdraw.vue
console.log('\n--- 8. 用户端提现页 (withdraw.vue) 实时动态计算与展示 ---');
const withdrawVuePath = path.resolve(__dirname, '../../uniapp/pages/pay/withdraw.vue');
assert(fs.existsSync(withdrawVuePath), 'withdraw.vue 存在');
const withdrawVueContent = fs.readFileSync(withdrawVuePath, 'utf-8');
assert(
withdrawVueContent.includes("estimatedMadowChargeFee") &&
withdrawVueContent.includes("estimatedMadowArrivalMoney"),
'withdraw.vue 具备 DMEPA 专属预计内扣手续费与预计到账计算属性'
);
assert(
withdrawVueContent.includes("预计到账:¥{{ estimatedMadowArrivalMoney }}(手续费内扣 ¥{{ estimatedMadowChargeFee }})"),
'withdraw.vue 在 DMEPA 模式下输入金额时实时展示预计到账与手续费'
);
assert(
withdrawVueContent.includes("提现金额须大于手续费"),
'withdraw.vue 提交前严格校验提现金额必须大于内扣手续费'
);
assert(
withdrawVueContent.includes("madow_withdraw_charge_rate"),
'withdraw.vue 从接口解析服务端下发的 madow_withdraw_charge_rate'
);
// 9. 验证 API 输出
console.log('\n--- 9. API 接口 (Withdraw.php::rules 与 partner/Index.php::madow) ---');
const apiRulesPath = path.resolve(__dirname, '../../addons/shopro/controller/Withdraw.php');
const apiRulesContent = fs.readFileSync(apiRulesPath, 'utf-8');
assert(
apiRulesContent.includes("madow_charge_rate"),
'Withdraw.php rules() 下发 madow_charge_rate'
);
const partnerIndexPath = path.resolve(__dirname, '../../addons/shopro/controller/partner/Index.php');
const partnerIndexContent = fs.readFileSync(partnerIndexPath, 'utf-8');
assert(
partnerIndexContent.includes("'madow_withdraw_charge_rate' => PartnerConfig::madowWithdrawChargeRate()"),
'partner/Index.php madow() 接口返回 madow_withdraw_charge_rate'
);
console.log('--------------------------------------------------------');
console.log(`测试执行结果: 通过 ${passCount} 项, 失败 ${failCount} 项`);
console.log('========================================================\n');
if (failCount > 0) {
process.exit(1);
} else {
process.exit(0);
}

查看文件

@@ -0,0 +1,248 @@
/**
* 后台提现管理(shopro/withdraw)导出功能全链路规格与算法模拟测试
* 覆盖范围:
* 1. application/admin/controller/shopro/Withdraw.php 控制器 export 方法、权限配置、save_type 配置注入
* 2. application/admin/model/shopro/Withdraw.php 模型 user 关联包含 mobile 字段
* 3. application/admin/view/shopro/withdraw/index.html 视图导出按钮与权限指令
* 4. public/assets/js/backend/shopro/withdraw.js 前端脚本 onExport 方法与 Vue ref/exportLoading 响应式变量
* 5. 提现数据导出映射逻辑(普通余额提现 vs DMEPA数字资产提现)单元与格式算法模拟
* 6. 大数防科学计数法(\t 前缀)、汇总行金额精确累加逻辑模拟断言
*/
const fs = require('fs');
const path = require('path');
const assert = require('assert');
let passCount = 0;
let failCount = 0;
function testAssert(condition, message) {
if (condition) {
console.log(` \x1b[32m[PASS]\x1b[0m ${message}`);
passCount++;
} else {
console.error(` \x1b[31m[FAIL]\x1b[0m ${message}`);
failCount++;
}
}
console.log('========================================================');
console.log(' 后台提现管理订单导出功能全链路专项测试');
console.log('========================================================\n');
// ----------------------------------------------------
// 1. Withdraw.php 控制器层导出功能验证
// ----------------------------------------------------
console.log('--- 1. application/admin/controller/shopro/Withdraw.php 控制器导出逻辑 ---');
const controllerPath = path.resolve(__dirname, '../application/admin/controller/shopro/Withdraw.php');
testAssert(fs.existsSync(controllerPath), 'Withdraw.php 控制器文件存在');
if (fs.existsSync(controllerPath)) {
const content = fs.readFileSync(controllerPath, 'utf-8');
testAssert(content.includes("'export'"), '$noNeedRight 包含 export 白名单');
testAssert(content.includes('$this->assignconfig("save_type"'), 'index() 注入 save_type 配置');
testAssert(content.includes('public function export()'), '控制器包含 public function export() 方法');
testAssert(content.includes("$this->model->sheepFilter()"), 'export 使用 $this->model->sheepFilter() 进行数据过滤');
testAssert(content.includes("addons\\shopro\\library\\Export"), '引入 Shopro 统一导出库 Export');
testAssert(content.includes("'商户单号'"), '导出表头包含 商户单号');
testAssert(content.includes("'申请用户'"), '导出表头包含 申请用户');
testAssert(content.includes("'用户手机号'"), '导出表头包含 用户手机号');
testAssert(content.includes("'提现资产'"), '导出表头包含 提现资产');
testAssert(content.includes("提现金额/换算市值"), '导出表头包含 提现金额/换算市值');
testAssert(content.includes("实际到账"), '导出表头包含 实际到账');
testAssert(content.includes("'提现方式'"), '导出表头包含 提现方式');
testAssert(content.includes("银行卡号"), '导出表头包含 银行卡号');
testAssert(content.includes("'资产折算明细'"), '导出表头包含 资产折算明细');
testAssert(content.includes('"\\t"'), '导出对长订单号、卡号等使用制表符防止科学计数法');
testAssert(content.includes("is_madow_withdraw"), '正确区分 DMEPA 资产提现与普通余额提现');
testAssert(content.includes("'id' => '合计: 共 ' . $total"), '生成底部汇总统计行');
}
// ----------------------------------------------------
// 2. Withdraw.php 模型层用户信息关联验证
// ----------------------------------------------------
console.log('\n--- 2. application/admin/model/shopro/Withdraw.php 模型层验证 ---');
const modelPath = path.resolve(__dirname, '../application/admin/model/shopro/Withdraw.php');
testAssert(fs.existsSync(modelPath), 'Withdraw.php 模型文件存在');
if (fs.existsSync(modelPath)) {
const content = fs.readFileSync(modelPath, 'utf-8');
testAssert(content.includes("field('id, nickname, mobile, avatar, total_consume')"), 'user 关联字段包含 mobile');
}
// ----------------------------------------------------
// 3. index.html 视图层导出按钮与权限验证
// ----------------------------------------------------
console.log('\n--- 3. application/admin/view/shopro/withdraw/index.html 视图层验证 ---');
const viewPath = path.resolve(__dirname, '../application/admin/view/shopro/withdraw/index.html');
testAssert(fs.existsSync(viewPath), 'index.html 视图文件存在');
if (fs.existsSync(viewPath)) {
const content = fs.readFileSync(viewPath, 'utf-8');
testAssert(content.includes("onExport('export')"), '页面包含 onExport 导出点击事件');
testAssert(content.includes(":loading=\"exportLoading\""), '按钮绑定 exportLoading 加载状态');
testAssert(content.includes("$auth->check('shopro/withdraw/export')"), '导出按钮包裹权限校验');
}
// ----------------------------------------------------
// 4. withdraw.js 前端脚本层验证
// ----------------------------------------------------
console.log('\n--- 4. public/assets/js/backend/shopro/withdraw.js 前端脚本验证 ---');
const jsPath = path.resolve(__dirname, '../public/assets/js/backend/shopro/withdraw.js');
testAssert(fs.existsSync(jsPath), 'withdraw.js 前端脚本文件存在');
if (fs.existsSync(jsPath)) {
const content = fs.readFileSync(jsPath, 'utf-8');
testAssert(content.includes("const { reactive, onMounted, ref } = Vue"), '从 Vue 中解构引入 ref');
testAssert(content.includes("const exportLoading = ref(false)"), '定义 exportLoading 响应式变量');
testAssert(content.includes("function onExport(type = 'export')"), '实现 onExport 导出处理方法');
testAssert(content.includes("Config.save_type == 'download'"), '支持直接浏览器下载模式 (Config.save_type == download)');
testAssert(content.includes("Fast.api.ajax"), '支持异步导出模式 (Fast.api.ajax)');
testAssert(content.includes("exportLoading,") && content.includes("onExport,"), 'setup() return 暴露 exportLoading 与 onExport');
}
// ----------------------------------------------------
// 5. 提现数据导出格式化算法与业务模拟
// ----------------------------------------------------
console.log('\n--- 5. 提现数据导出格式化算法与业务模拟 ---');
// 模拟导出映射函数(与控制器逻辑对齐)
function formatExportRow(row) {
const isMadow = Boolean(row.is_madow_withdraw);
const withdrawInfo = row.withdraw_info || {};
const withdrawType = row.withdraw_type || '';
const withdrawTypeMap = {
'wechat': '微信零钱',
'alipay': '支付宝',
'bank': '银行卡',
};
const statusMap = {
'0': '待审核',
'1': '处理中',
'2': '已处理',
'-1': '已拒绝',
};
let realName = '';
let bankName = '';
let accountNo = '';
if (withdrawType === 'bank') {
realName = withdrawInfo.real_name || '';
bankName = withdrawInfo.bank_name || '';
accountNo = withdrawInfo.card_no ? `\t${withdrawInfo.card_no}` : '';
} else if (withdrawType === 'alipay') {
realName = withdrawInfo.real_name || '';
accountNo = withdrawInfo.account ? `\t${withdrawInfo.account}` : '';
} else if (withdrawType === 'wechat') {
realName = withdrawInfo.real_name || '';
accountNo = withdrawInfo.openid ? `\t${withdrawInfo.openid}` : '';
}
let assetName = '商城余额';
let assetAmountDetail = '-';
let amountDisplay = Number(row.money || 0).toFixed(2);
let actualAmountDisplay = Number(row.actual_money || 0).toFixed(2);
let chargeMoneyDisplay = Number(row.charge_money || 0).toFixed(2);
let serviceFeeRateDisplay = `${row.service_fee || 0}%`;
if (isMadow) {
assetName = 'DMEPA数字资产';
const madowAmount = Number(withdrawInfo.madow_amount || 0);
const madowRate = Number(withdrawInfo.madow_rate || 0);
assetAmountDetail = `DMEPA: ${madowAmount.toFixed(4)}, 汇率: ¥${madowRate.toFixed(4)}`;
amountDisplay = `¥${Number(row.money || 0).toFixed(2)}`;
actualAmountDisplay = `¥${Number(row.actual_money || 0).toFixed(2)}`;
}
return {
id: row.id,
sn: `\t${row.sn}`,
user: `[${row.user_id}] ${row.user ? row.user.nickname : ''}`,
mobile: row.user && row.user.mobile ? `\t${row.user.mobile}` : '-',
asset_name: assetName,
money: amountDisplay,
actual_money: actualAmountDisplay,
service_fee: serviceFeeRateDisplay,
charge_money: chargeMoneyDisplay,
withdraw_type_text: withdrawTypeMap[withdrawType] || withdrawType,
real_name: realName,
bank_name: bankName,
card_no: accountNo,
asset_detail: assetAmountDetail,
status_text: statusMap[String(row.status)] || String(row.status),
status_msg: row.status_msg || '-',
createtime: '2026-09-21 14:00:00',
updatetime: '2026-09-21 14:10:00',
};
}
// 模拟场景 A:普通商城余额提现至银行卡
const mockRowA = {
id: 101,
sn: 'TX202609210001',
user_id: 8888,
user: { id: 8888, nickname: '创客小张', mobile: '13800138000' },
is_madow_withdraw: false,
money: 1000.00,
actual_money: 990.00,
charge_money: 10.00,
service_fee: 1.0,
withdraw_type: 'bank',
withdraw_info: {
real_name: '张三',
bank_name: '中国工商银行',
card_no: '6222021000112233445'
},
status: 2,
status_msg: '打款成功',
};
const formattedA = formatExportRow(mockRowA);
testAssert(formattedA.asset_name === '商城余额', '普通余额提现资产显示为 商城余额');
testAssert(formattedA.mobile === '\t13800138000', '手机号添加制表符防失真');
testAssert(formattedA.card_no === '\t6222021000112233445', '银行卡号添加制表符防科学计数法');
testAssert(formattedA.money === '1000.00', '提现金额精准显示 1000.00');
testAssert(formattedA.actual_money === '990.00', '实际到账金额精准显示 990.00');
testAssert(formattedA.charge_money === '10.00', '手续费精准显示 10.00');
testAssert(formattedA.status_text === '已处理', '状态正确转为 已处理');
// 模拟场景 B:DMEPA 数字资产提现
const mockRowB = {
id: 102,
sn: 'TX202609210002',
user_id: 9999,
user: { id: 9999, nickname: '合伙人李四', mobile: '13900139000' },
is_madow_withdraw: true,
money: 5000.00,
actual_money: 4950.00,
charge_money: 50.00,
service_fee: 1.0,
withdraw_type: 'bank',
withdraw_info: {
real_name: '李四',
bank_name: '招商银行',
card_no: '6214830123456789',
madow_amount: 1000.0000,
madow_rate: 5.0000,
},
status: 0,
status_msg: '',
};
const formattedB = formatExportRow(mockRowB);
testAssert(formattedB.asset_name === 'DMEPA数字资产', 'DMEPA提现资产显示为 DMEPA数字资产');
testAssert(formattedB.asset_detail.includes('DMEPA: 1000.0000'), '资产折算明细包含 DMEPA 数量');
testAssert(formattedB.asset_detail.includes('汇率: ¥5.0000'), '资产折算明细包含汇率');
testAssert(formattedB.status_text === '待审核', '状态正确转为 待审核');
// 模拟场景 C:合计行计算验证
const totalMoney = mockRowA.money + mockRowB.money;
const totalActualMoney = mockRowA.actual_money + mockRowB.actual_money;
const totalChargeMoney = mockRowA.charge_money + mockRowB.charge_money;
testAssert(totalMoney === 6000.00, '提现总额合计为 6000.00 元');
testAssert(totalActualMoney === 5940.00, '实际到账总额合计为 5940.00 元');
testAssert(totalChargeMoney === 60.00, '手续费总额合计为 60.00 元');
console.log('\n========================================================');
console.log(`测试完成!通过: ${passCount}, 失败: ${failCount}`);
console.log('========================================================\n');
if (failCount > 0) {
process.exit(1);
}

查看文件

@@ -73,9 +73,14 @@
</view>
<view
v-if="state.walletType === 'madow' && Number(state.amount) > 0"
style="padding: 12rpx 0 16rpx; font-size: 22rpx; color: #999;"
style="padding: 12rpx 0 16rpx; font-size: 22rpx;"
>
预计扣除约 {{ estimatedDeductMadow }} 枚(实时汇率 1 dmepa ≈ ¥{{ state.madowInfo.madow_rate || '0' }})
<view style="color: #999;">
预计扣除约 {{ estimatedDeductMadow }} 枚 DMEPA(实时汇率 1 DMEPA ≈ ¥{{ state.madowInfo.madow_rate || '0' }})
</view>
<view style="color: #ff6000; margin-top: 6rpx;">
预计到账:¥{{ estimatedMadowArrivalMoney }}(手续费内扣 ¥{{ estimatedMadowChargeFee }})
</view>
</view>
<view
v-else-if="state.walletType !== 'madow' && Number(state.amount) > 0"
@@ -118,7 +123,7 @@
💡 提示:提现金额须为100的整数倍(如100、200、1200等)。商城收益分成已全额直接入账商城余额,支持随时申请提现至银行卡/微信/支付宝,也可用于下单直接抵扣。历史存量佣金可转入商城余额消费。
</view>
<view class="tip-banner ss-m-b-20" v-else style="background: #fdfbf7; color: #b78328; font-size: 22rpx; padding: 16rpx 20rpx; border-radius: 12rpx; line-height: 1.5;">
💡 提示:dmepa 按大盘实时汇率折算为现金扣除持仓,提现渠道限定为银行卡转账。审核驳回后对应 dmepa 将全额原路退回。
💡 提示:DMEPA 按大盘实时汇率折算为现金并内扣手续费打款,提现渠道限定为银行卡转账。审核驳回后对应 DMEPA 将全额原路退回。
</view>
<view class="draw-list" v-for="(rule, index) in state.rulesList" :key="index">
{{ index + 1 }}.{{ rule }}
@@ -199,6 +204,7 @@
madow_value: '0.00',
can_withdraw_madow: 1,
madow_withdraw_min: 1,
madow_withdraw_charge_rate: 0,
},
rules: {
min_amount: 0,
@@ -219,6 +225,19 @@
return deduct.toFixed(4);
});
const estimatedMadowChargeFee = computed(() => {
if (!state.amount || Number(state.amount) <= 0 || !state.madowInfo.madow_withdraw_charge_rate) return '0.00';
const charge = Number(state.amount) * (Number(state.madowInfo.madow_withdraw_charge_rate) * 0.01);
return charge.toFixed(2);
});
const estimatedMadowArrivalMoney = computed(() => {
if (!state.amount || Number(state.amount) <= 0) return '0.00';
const charge = Number(state.amount) * (Number(state.madowInfo.madow_withdraw_charge_rate || 0) * 0.01);
const arrival = Math.max(0, Number(state.amount) - charge);
return arrival.toFixed(2);
});
const estimatedChargeFee = computed(() => {
if (!state.amount || Number(state.amount) <= 0 || !state.rules.charge_rate) return '0.00';
const charge = Number(state.amount) * Number(state.rules.charge_rate);
@@ -286,18 +305,23 @@
madow_value: res.data.madow_value || '0.00',
can_withdraw_madow: res.data.can_withdraw_madow ?? 1,
madow_withdraw_min: res.data.madow_withdraw_min ?? 1,
madow_withdraw_charge_rate: res.data.madow_withdraw_charge_rate ?? 0,
};
const isOpen = state.madowInfo.can_withdraw_madow !== 0 && state.madowInfo.can_withdraw_madow !== false;
state.isWithdrawOpen = Boolean(isOpen);
if (!state.isWithdrawOpen) {
sheep.$helper.toast('dmepa提现暂未开启');
sheep.$helper.toast('DMEPA提现暂未开启');
}
state.rulesList = [
`dmepa按大盘实时市值换算扣除(当前 1 dmepa ≈ ¥${state.madowInfo.madow_rate});`,
let rulesArr = [
`DMEPA按大盘实时市值换算扣除持仓(当前 1 DMEPA ≈ ¥${state.madowInfo.madow_rate});`,
`单次提现金额最低 ${state.madowInfo.madow_withdraw_min || 1} 元;`,
'提现仅支持打款至银行卡,申请提交后由财务人工审核;',
'审核驳回或打款失败后,对应dmepa将全额原路退还至账户持仓。',
];
if (Number(state.madowInfo.madow_withdraw_charge_rate) > 0) {
rulesArr.push(`提现手续费率 ${state.madowInfo.madow_withdraw_charge_rate}%(从到账金额中内扣);`);
}
rulesArr.push('提现仅支持打款至银行卡,申请提交后由财务人工审核;');
rulesArr.push('审核驳回或打款失败后,对应DMEPA将全额原路退还至账户持仓。');
state.rulesList = rulesArr;
}
} catch (e) {}
}
@@ -312,7 +336,7 @@
const onConfirm = async () => {
if (!state.isWithdrawOpen) {
sheep.$helper.toast(state.walletType === 'madow' ? 'dmepa提现暂未开启' : '系统提现功能暂未开启');
sheep.$helper.toast(state.walletType === 'madow' ? 'DMEPA提现暂未开启' : '系统提现功能暂未开启');
return;
}
@@ -336,9 +360,16 @@
return;
}
if (Number(payload.money) > Number(state.madowInfo.madow_value || 0)) {
sheep.$helper.toast('提现金额超出持有dmepa可用市值');
sheep.$helper.toast('提现金额超出持有DMEPA可用市值');
return;
}
if (Number(state.madowInfo.madow_withdraw_charge_rate) > 0) {
const fee = Number(payload.money) * (Number(state.madowInfo.madow_withdraw_charge_rate) * 0.01);
if (Number(payload.money) <= fee) {
sheep.$helper.toast('提现金额须大于手续费');
return;
}
}
} else if (state.walletType === 'commission') {
payload.wallet_type = 'commission';
const moneyVal = Number(payload.money);