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

306 行
13 KiB
PHP

<?php
namespace addons\shopro\controller\partner;
use addons\shopro\controller\commission\Commission;
use addons\shopro\service\partner\IdentityService;
use addons\shopro\service\partner\PartnerConfig;
use addons\shopro\service\partner\RateService;
use app\admin\model\shopro\partner\Reward as RewardModel;
use app\admin\model\shopro\partner\VoucherPlan;
class Index extends Commission
{
protected $noNeedLogin = [];
protected $noNeedRight = ['*'];
public function index()
{
$userId = $this->service->user->id;
$monthStart = strtotime(date('Y-m-01 00:00:00'));
$monthIncome = RewardModel::where('user_id', $userId)
->where('status', 1)
->where('createtime', '>=', $monthStart)
->whereIn('reward_type', array_merge(PartnerConfig::instantTypes(), ['venue_share', 'county_share']))
->sum('amount');
$skipCount = RewardModel::where('user_id', $userId)
->where('status', -1)
->where('createtime', '>=', $monthStart)
->count();
$agent = IdentityService::attachAgent($this->service->agent);
$totalIncome = $agent['total_income'] ?? '0.00';
$this->success('ok', [
'total_income' => $totalIncome,
'month_income' => IdentityService::formatMoney($monthIncome),
'skip_count' => intval($skipCount),
'active_30d' => $agent['active_30d'] ?? true,
]);
}
public function rewards()
{
$userId = $this->service->user->id;
$tab = $this->request->param('tab', 'all');
$buyerId = intval($this->request->param('buyer_id', 0));
$query = RewardModel::where('user_id', $userId)->with(['buyer', 'order']);
// 仅查询真金白银的现金奖励类型,马豆 (madow_share) 及兑换券有专属页面,绝不混入现金明细
$cashTypes = array_merge(PartnerConfig::instantTypes(), ['venue_share', 'county_share']);
$tabMap = [
'push3' => ['push3'],
'maker' => ['maker_diff', 'maker_peer', 'maker_overtaken'],
'venue' => ['team_venue', 'venue_peer', 'venue_share'],
'county' => ['team_county', 'county_peer', 'county_share'],
'share' => ['venue_share', 'county_share'],
];
if ($tab !== 'all' && isset($tabMap[$tab])) {
$query->whereIn('reward_type', $tabMap[$tab]);
} else {
$query->whereIn('reward_type', $cashTypes);
}
// 排除已入账且金额为0的无效非现金记录,只展示有效现金收益或明确未达成的拦截记录
$query->where(function ($q) {
$q->where('amount', '>', 0)
->whereOr('status', -1);
});
if ($buyerId > 0) {
$query->where('buyer_id', $buyerId);
}
$list = $query->order('id', 'desc')->paginate($this->request->param('list_rows', 10));
$rows = [];
foreach ($list as $item) {
$reason = $item->skip_reason;
$buyerNick = $item->buyer ? ($item->buyer->nickname ?? '') : '';
$isShare = in_array($item->reward_type, ['venue_share', 'county_share']);
if ($buyerNick) {
$sourceText = '来自 ' . $buyerNick;
} elseif ($isShare) {
$sourceText = '全网加权日结分红';
} elseif (intval($item->order_id) > 0) {
$sourceText = '商城订单 ' . $item->order_id;
} else {
$sourceText = '平台分润奖励';
}
$rows[] = [
'id' => intval($item->id),
'reward_type' => $item->reward_type,
'reward_type_text' => $item->reward_type_text,
'amount' => IdentityService::formatMoney($item->amount),
'madow' => IdentityService::formatMadow($item->madow),
'status' => intval($item->status),
'status_text' => $item->status_text,
'skip_reason' => $reason,
'skip_reason_text' => $reason ? PartnerConfig::skipReasonText($reason, true) : '',
'buyer_nickname' => $buyerNick,
'source_text' => $sourceText,
'order_id' => intval($item->order_id) > 0 ? intval($item->order_id) : 0,
'createtime' => $item->createtime,
'formula' => $item->formula,
];
}
$buyerInfo = null;
if ($buyerId > 0) {
$buyerUser = \app\admin\model\shopro\user\User::get($buyerId);
if ($buyerUser) {
$rawMobile = (string) ($buyerUser->mobile ?? '');
$buyerInfo = [
'id' => $buyerUser->id,
'nickname' => $buyerUser->nickname,
'avatar' => $buyerUser->avatar,
'mobile' => $rawMobile ? (strlen($rawMobile) == 11 ? substr_replace($rawMobile, '****', 3, 4) : $rawMobile) : '',
];
}
}
$monthStart = strtotime(date('Y-m-01 00:00:00'));
$monthIncome = RewardModel::where('user_id', $userId)
->where('status', 1)
->where('createtime', '>=', $monthStart)
->whereIn('reward_type', array_merge(PartnerConfig::instantTypes(), ['venue_share', 'county_share']))
->sum('amount');
$agent = IdentityService::attachAgent($this->service->agent);
$totalIncome = $agent['total_income'] ?? '0.00';
$this->success('ok', [
'total' => $list->total(),
'per_page' => $list->listRows(),
'current_page' => $list->currentPage(),
'total_income' => $totalIncome,
'month_income' => IdentityService::formatMoney($monthIncome),
'data' => $rows,
'buyer_info' => $buyerInfo,
]);
}
public function madow()
{
$userId = $this->service->user->id;
$agent = IdentityService::attachAgent($this->service->agent);
$logs = RewardModel::where('user_id', $userId)
->where('reward_type', 'madow_share')
->where('status', 1)
->order('id', 'desc')
->paginate($this->request->param('list_rows', 10));
$list = [];
foreach ($logs as $item) {
$list[] = [
'id' => $item->id,
'settle_date' => $item->settle_date,
'madow' => IdentityService::formatMadow($item->madow),
'gmv' => $item->ext['gmv'] ?? '',
'share_user_count' => $item->ext['share_user_count'] ?? 0,
'createtime' => $item->createtime,
];
}
$this->success('ok', [
'madow' => $agent['madow'] ?? '0.00000000',
'madow_rate' => $agent['madow_rate'] ?? IdentityService::formatRate(null),
'madow_value' => $agent['madow_value'] ?? '0.00',
'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,
]);
}
public function voucher()
{
$userId = $this->service->user->id;
$agent = IdentityService::attachAgent($this->service->agent);
$plans = VoucherPlan::with(['order'])->where('user_id', $userId)->order('id', 'desc')->select();
$rows = [];
foreach ($plans as $plan) {
$rows[] = [
'id' => $plan->id,
'order_id' => $plan->order_id,
'order_sn' => $plan->order ? (string) $plan->order->order_sn : (string) $plan->order_id,
'total_amount' => IdentityService::formatMoney($plan->total_amount),
'daily_amount' => IdentityService::formatMoney($plan->daily_amount),
'released_amount' => IdentityService::formatMoney($plan->released_amount),
'released_days' => intval($plan->released_days),
'total_days' => intval($plan->total_days),
'start_date' => $plan->start_date,
'status' => intval($plan->status),
'status_text' => $plan->status_text,
];
}
$this->success('ok', [
'voucher_total' => $agent['voucher_total'] ?? '0.00',
'voucher_released' => $agent['voucher_released'] ?? '0.00',
'voucher_used' => $agent['voucher_used'] ?? '0.00',
'voucher_available' => $agent['voucher_available'] ?? '0.00',
'rule_title' => PartnerConfig::voucherRuleTitle(),
'rules' => PartnerConfig::voucherRules(),
'plans' => $rows,
]);
}
/**
* 获取指定兑换券释放计划的按日释放明细
*
* @param int id 计划ID
*/
public function voucherLogs()
{
$userId = $this->service->user->id;
$planId = intval($this->request->param('id', $this->request->param('plan_id', 0)));
if ($planId <= 0) {
$this->error('请提供有效的计划ID');
}
$plan = VoucherPlan::with(['order'])->where('id', $planId)->where('user_id', $userId)->find();
if (!$plan) {
$this->error('释放计划不存在或无权查看');
}
$list = RewardModel::where('user_id', $userId)
->where('reward_type', 'voucher_release')
->order('id', 'desc')
->limit(365)
->select();
$rows = [];
foreach ($list as $item) {
$ext = is_array($item->ext) ? $item->ext : (json_decode($item->ext ?: '{}', true) ?: []);
$planIds = $ext['plan_ids'] ?? [];
if (!$planIds && !empty($ext['plan_id'])) {
$planIds = [$ext['plan_id']];
}
$planIds = array_map('intval', (array) $planIds);
if ($planIds && !in_array(intval($plan->id), $planIds, true)) {
continue;
}
if (empty($planIds) && !empty($ext['order_id']) && intval($ext['order_id']) != intval($plan->order_id)) {
continue;
}
$dateText = $item->settle_date ?: ($item->createtime ? date('Y-m-d', is_numeric($item->createtime) ? $item->createtime : strtotime($item->createtime)) : '--');
$timeText = $item->createtime ? (is_numeric($item->createtime) ? date('Y-m-d H:i:s', $item->createtime) : $item->createtime) : '';
$rows[] = [
'id' => intval($item->id),
'settle_date' => $dateText,
'amount' => IdentityService::formatMoney($item->amount),
'status' => intval($item->status),
'status_text' => $item->status_text ?: '已到账',
'formula' => $item->formula ?: ("兑换券释放 " . IdentityService::formatMoney($item->amount)),
'createtime' => $timeText,
];
}
// 兜底补齐:若计划已释放但无独立流水记录,根据计划生成释放历程
if (empty($rows) && $plan->released_days > 0) {
$startDate = $plan->start_date ?: date('Y-m-d', $plan->createtime ?: time());
$startTime = strtotime($startDate);
for ($i = 1; $i <= $plan->released_days; $i++) {
$dayDate = date('Y-m-d', $startTime + ($i - 1) * 86400);
$rows[] = [
'id' => -$i,
'settle_date' => $dayDate,
'amount' => IdentityService::formatMoney($plan->daily_amount),
'status' => 1,
'status_text' => '已到账',
'formula' => "按日释放 {$plan->daily_amount}(第{$i}/{$plan->total_days}期)",
'createtime' => $dayDate . ' 00:30:00',
];
}
$rows = array_reverse($rows);
}
$orderSn = $plan->order ? (string) $plan->order->order_sn : (string) $plan->order_id;
$planData = [
'id' => intval($plan->id),
'order_id' => intval($plan->order_id),
'order_sn' => $orderSn,
'total_amount' => IdentityService::formatMoney($plan->total_amount),
'daily_amount' => IdentityService::formatMoney($plan->daily_amount),
'released_amount' => IdentityService::formatMoney($plan->released_amount),
'released_days' => intval($plan->released_days),
'total_days' => intval($plan->total_days),
'start_date' => $plan->start_date,
'status' => intval($plan->status),
'status_text' => $plan->status_text,
];
$this->success('ok', [
'plan' => $planData,
'logs' => $rows,
'total' => count($rows),
]);
}
}