'percent', 'default_commission_rate' => '10.00', 'default_fixed_fee' => '0.00', 'default_min_commission' => '0.00', 'default_max_commission' => '0.00', 'include_diff_in_commission' => '1', // 提现与微信小程序商家转账配置 'withdraw_enabled' => '1', 'withdraw_close_tip' => '系统正在进行财务结算维护,请稍后再发起提现', 'withdraw_channels' => 'wechat', 'withdraw_require_realname' => '1', 'withdraw_min_amount' => '10.00', 'withdraw_max_amount' => '200.00', 'withdraw_daily_amount' => '2000.00', 'withdraw_daily_count' => '3', 'withdraw_allow_weekdays' => '1,2,3,4,5,6,7', 'withdraw_time_start' => '00:00', 'withdraw_time_end' => '23:59', 'withdraw_fee_mode' => 'percent', 'withdraw_fee_rate' => '0.60', 'withdraw_fee_fixed' => '0.00', 'withdraw_fee_min' => '0.00', 'withdraw_fee_max' => '0.00', 'withdraw_transfer_mode' => 'wx_mch_transfer', 'withdraw_audit_mode' => 'manual', 'withdraw_auto_max_amount' => '50.00', 'wx_transfer_scene_id' => '1005', 'wx_recv_perception' => '劳务报酬', 'wx_job_type' => '上门安装维修师傅', 'wx_reward_desc' => '平台工单安装维修劳务报酬', 'wx_transfer_remark' => '工单安装维修服务劳务报酬', ]; } /** * 清空请求内静态缓存 */ public static function clearCache(): void { self::$cache = null; } /** * 获取全部原始配置键值数组 */ public static function getAll(bool $forceRefresh = false): array { if (self::$cache === null || $forceRefresh) { $base = self::defaults(); try { $dbMap = Db::name('finance_config')->column('value', 'name'); if (is_array($dbMap) && !empty($dbMap)) { foreach ($dbMap as $k => $v) { if ($v !== null) { $base[$k] = (string)$v; } } } } catch (\Throwable $e) { // 表未就绪时平滑回退默认字典 } self::$cache = $base; } return self::$cache; } /** * 获取订单默认兜底抽佣配置 */ public static function getCommissionDefaults(): array { $all = self::getAll(); $mode = in_array($all['default_calc_mode'], ['percent', 'fixed', 'mixed'], true) ? $all['default_calc_mode'] : 'percent'; return [ 'calc_mode' => $mode, 'commission_rate' => round(min(100.00, max(0.00, (float)$all['default_commission_rate'])), 2), 'fixed_fee' => round(max(0.00, (float)$all['default_fixed_fee']), 2), 'min_commission' => round(max(0.00, (float)$all['default_min_commission']), 2), 'max_commission' => round(max(0.00, (float)$all['default_max_commission']), 2), 'include_diff_in_commission' => (int)$all['include_diff_in_commission'] === 1 ? 1 : 0, ]; } /** * 获取结构化的提现与微信小程序转账配置 */ public static function getWithdrawConfig(): array { $all = self::getAll(); $rawChannels = array_filter(array_map('trim', explode(',', (string)$all['withdraw_channels']))); $channels = []; foreach ($rawChannels as $ch) { if (in_array($ch, ['wechat', 'bank'], true) && !in_array($ch, $channels, true)) { $channels[] = $ch; } } if (empty($channels)) { $channels = ['wechat']; } $rawDays = array_filter(array_map('intval', explode(',', (string)$all['withdraw_allow_weekdays']))); $weekdays = []; foreach ($rawDays as $d) { if ($d >= 1 && $d <= 7 && !in_array($d, $weekdays, true)) { $weekdays[] = $d; } } sort($weekdays); if (empty($weekdays)) { $weekdays = [1, 2, 3, 4, 5, 6, 7]; } $minAmt = round(max(0.10, (float)$all['withdraw_min_amount']), 2); $maxAmt = round(min(self::WX_TRANSFER_HARD_MAX, max($minAmt, (float)$all['withdraw_max_amount'])), 2); $dailyAmt = round(max(0.00, (float)$all['withdraw_daily_amount']), 2); if ($dailyAmt > 0 && $dailyAmt < $minAmt) { $dailyAmt = $maxAmt; } $feeMode = in_array($all['withdraw_fee_mode'], ['percent', 'fixed', 'free'], true) ? $all['withdraw_fee_mode'] : 'percent'; $transferMode = in_array($all['withdraw_transfer_mode'], ['wx_mch_transfer', 'offline_manual'], true) ? $all['withdraw_transfer_mode'] : 'wx_mch_transfer'; $auditMode = in_array($all['withdraw_audit_mode'], ['manual', 'auto_small'], true) ? $all['withdraw_audit_mode'] : 'manual'; return [ 'enabled' => (int)$all['withdraw_enabled'] === 1 ? 1 : 0, 'close_tip' => trim((string)$all['withdraw_close_tip']) ?: '当前暂未开放提现,请稍后再试', 'channels' => $channels, 'require_realname' => (int)$all['withdraw_require_realname'] === 1 ? 1 : 0, 'min_amount' => $minAmt, 'max_amount' => $maxAmt, 'daily_amount' => $dailyAmt, 'daily_count' => max(0, (int)$all['withdraw_daily_count']), 'allow_weekdays' => $weekdays, 'time_start' => preg_match('/^\d{2}:\d{2}$/', (string)$all['withdraw_time_start']) ? $all['withdraw_time_start'] : '00:00', 'time_end' => preg_match('/^\d{2}:\d{2}$/', (string)$all['withdraw_time_end']) ? $all['withdraw_time_end'] : '23:59', 'fee_mode' => $feeMode, 'fee_rate' => round(min(50.00, max(0.00, (float)$all['withdraw_fee_rate'])), 2), 'fee_fixed' => round(max(0.00, (float)$all['withdraw_fee_fixed']), 2), 'fee_min' => round(max(0.00, (float)$all['withdraw_fee_min']), 2), 'fee_max' => round(max(0.00, (float)$all['withdraw_fee_max']), 2), 'transfer_mode' => $transferMode, 'audit_mode' => $auditMode, 'auto_max_amount' => round(max(0.00, (float)$all['withdraw_auto_max_amount']), 2), 'wx_scene_id' => trim((string)$all['wx_transfer_scene_id']) ?: '1005', 'wx_perception' => mb_substr(trim((string)$all['wx_recv_perception']) ?: '劳务报酬', 0, 32), 'wx_job_type' => mb_substr(trim((string)$all['wx_job_type']) ?: '上门安装维修师傅', 0, 32), 'wx_reward_desc' => mb_substr(trim((string)$all['wx_reward_desc']) ?: '平台工单安装维修劳务报酬', 0, 32), 'wx_remark' => mb_substr(trim((string)$all['wx_transfer_remark']) ?: '工单安装维修服务劳务报酬', 0, 32), ]; } /** * 计算单笔提现的手续费与实际到账净额 */ public static function calcWithdrawFee(float $amount, ?array $cfg = null): array { $cfg = $cfg ?: self::getWithdrawConfig(); $amt = max(0.00, round($amount, 2)); $fee = 0.00; if ($amt > 0) { if ($cfg['fee_mode'] === 'percent') { $fee = round($amt * ((float)$cfg['fee_rate']) / 100, 2); if ($cfg['fee_min'] > 0 && $fee < $cfg['fee_min']) { $fee = $cfg['fee_min']; } if ($cfg['fee_max'] > 0 && $fee > $cfg['fee_max']) { $fee = $cfg['fee_max']; } } elseif ($cfg['fee_mode'] === 'fixed') { $fee = round((float)$cfg['fee_fixed'], 2); } else { $fee = 0.00; } } if ($fee >= $amt && $amt > 0) { $fee = max(0.00, round($amt - self::WX_TRANSFER_MIN_ARRIVE, 2)); } $arrive = round(max(0.00, $amt - $fee), 2); return [ 'amount' => $amt, 'fee' => $fee, 'arrive_amount' => $arrive, 'fee_mode' => $cfg['fee_mode'], 'fee_rate' => $cfg['fee_rate'], 'fee_fixed' => $cfg['fee_fixed'], ]; } /** * 统计师傅今日已申请提现次数与金额(排除已驳回单) */ public static function getMasterTodayWithdrawStats(int $masterId): array { $todayStart = strtotime(date('Y-m-d 00:00:00')); $rows = Db::name('withdraw') ->field('amount') ->where('master_id', $masterId) ->where('status', '<>', 'reject') ->where('createtime', '>=', $todayStart) ->select(); $cnt = count($rows); $sum = 0.00; foreach ($rows as $r) { $sum += (float)$r['amount']; } return [ 'today_count' => $cnt, 'today_amount' => round($sum, 2), ]; } /** * 生成下发给小程序「我的钱包」页面的动态提现配置与规则说明 */ public static function buildMasterWithdrawClientPayload(array $master): array { $cfg = self::getWithdrawConfig(); $stats = self::getMasterTodayWithdrawStats((int)$master['id']); $remainDailyAmount = $cfg['daily_amount'] > 0 ? round(max(0.00, $cfg['daily_amount'] - $stats['today_amount']), 2) : -1; $remainDailyCount = $cfg['daily_count'] > 0 ? max(0, $cfg['daily_count'] - $stats['today_count']) : -1; if ($cfg['fee_mode'] === 'free' || ($cfg['fee_mode'] === 'percent' && $cfg['fee_rate'] <= 0) || ($cfg['fee_mode'] === 'fixed' && $cfg['fee_fixed'] <= 0)) { $feeText = '免通道手续费'; } elseif ($cfg['fee_mode'] === 'fixed') { $feeText = '手续费 ¥' . number_format($cfg['fee_fixed'], 2, '.', '') . '/笔'; } else { $feeText = '通道费率 ' . rtrim(rtrim(number_format($cfg['fee_rate'], 2, '.', ''), '0'), '.') . '%'; if ($cfg['fee_min'] > 0 || $cfg['fee_max'] > 0) { $parts = []; if ($cfg['fee_min'] > 0) { $parts[] = '最低¥' . number_format($cfg['fee_min'], 2, '.', ''); } if ($cfg['fee_max'] > 0) { $parts[] = '封顶¥' . number_format($cfg['fee_max'], 2, '.', ''); } $feeText .= '(' . implode(',', $parts) . ')'; } } $minTxt = rtrim(rtrim(number_format($cfg['min_amount'], 2, '.', ''), '0'), '.'); $maxTxt = rtrim(rtrim(number_format($cfg['max_amount'], 2, '.', ''), '0'), '.'); $ruleTip = "完工核销后工费即刻入账;单笔 ¥{$minTxt}~¥{$maxTxt} 可提现,{$feeText}。"; if (!$cfg['enabled']) { $ruleTip = $cfg['close_tip']; } $arrivalDesc = $cfg['transfer_mode'] === 'wx_mch_transfer' ? ($cfg['audit_mode'] === 'auto_small' ? '微信零钱(小额免审,确认收款即到账)' : '微信零钱(审核后小程序确认收款即到账)') : '微信零钱 / 人工财务打款'; return [ 'enabled' => (bool)$cfg['enabled'], 'close_tip' => $cfg['close_tip'], 'channels' => $cfg['channels'], 'require_realname' => (bool)$cfg['require_realname'], 'min_amount' => $cfg['min_amount'], 'max_amount' => $cfg['max_amount'], 'daily_amount' => $cfg['daily_amount'], 'daily_count' => $cfg['daily_count'], 'today_amount' => $stats['today_amount'], 'today_count' => $stats['today_count'], 'remain_daily_amount' => $remainDailyAmount, 'remain_daily_count' => $remainDailyCount, 'allow_weekdays' => $cfg['allow_weekdays'], 'time_start' => $cfg['time_start'], 'time_end' => $cfg['time_end'], 'fee_mode' => $cfg['fee_mode'], 'fee_rate' => $cfg['fee_rate'], 'fee_fixed' => $cfg['fee_fixed'], 'fee_min' => $cfg['fee_min'], 'fee_max' => $cfg['fee_max'], 'fee_text' => $feeText, 'transfer_mode' => $cfg['transfer_mode'], 'audit_mode' => $cfg['audit_mode'], 'auto_max_amount' => $cfg['auto_max_amount'], 'arrival_desc' => $arrivalDesc, 'rule_tip' => $ruleTip, ]; } /** * 校验师傅发起的提现请求(全量业务规则 + 微信小程序商家转账硬规范) * * @param array $master fa_master 行记录 * @param float $amount 申请提现金额 * @param string $channel 申请提现渠道 wechat | bank * @param string $account 银行卡等收款账号信息 * @return array ['fee' => float, 'arrive_amount' => float, 'cfg' => array, 'rule_snapshot' => string] * @throws \InvalidArgumentException */ public static function validateWithdrawRequest(array $master, float $amount, string $channel, string $account = ''): array { $cfg = self::getWithdrawConfig(); if (!$cfg['enabled']) { throw new \InvalidArgumentException($cfg['close_tip']); } if (!in_array($channel, $cfg['channels'], true)) { $chLabel = $channel === 'bank' ? '银行储蓄卡' : '微信零钱'; throw new \InvalidArgumentException("当前暂未开放【{$chLabel}】提现渠道"); } if ($channel === 'bank' && trim($account) === '') { throw new \InvalidArgumentException('请填写收款银行卡号与开户行信息'); } // 校验允许提现星期与每日时段 $curWeekday = (int)date('N'); // 1(周一) ~ 7(周日) if (!in_array($curWeekday, $cfg['allow_weekdays'], true)) { $dayNames = [1 => '周一', 2 => '周二', 3 => '周三', 4 => '周四', 5 => '周五', 6 => '周六', 7 => '周日']; $allowLabels = array_map(function ($d) use ($dayNames) { return $dayNames[$d] ?? ''; }, $cfg['allow_weekdays']); throw new \InvalidArgumentException('当前不在提现开放日(开放时间:' . implode('、', array_filter($allowLabels)) . ')'); } $nowHm = date('H:i'); if ($nowHm < $cfg['time_start'] || $nowHm > $cfg['time_end']) { throw new \InvalidArgumentException("当前不在每日提现开放时段({$cfg['time_start']} ~ {$cfg['time_end']})"); } // 实名认证合规校验(后台开启强制实名,或微信单笔 >= 2000 元微信官方强制实名) $isRealnameVerified = (($master['verify_status'] ?? '') === 'pass') && trim((string)($master['realname'] ?? '')) !== ''; if ($cfg['require_realname'] && !$isRealnameVerified) { throw new \InvalidArgumentException('根据资金合规要求,请先完成师傅实名认证审核通过后再申请提现'); } $amt = round($amount, 2); if ($amt <= 0) { throw new \InvalidArgumentException('请输入有效的提现金额'); } if ($amt < $cfg['min_amount']) { throw new \InvalidArgumentException('单笔提现金额不能低于 ¥' . number_format($cfg['min_amount'], 2, '.', '')); } if ($amt > $cfg['max_amount']) { throw new \InvalidArgumentException('单笔提现金额不能超过 ¥' . number_format($cfg['max_amount'], 2, '.', '')); } if ($channel === 'wechat' && $amt > self::WX_TRANSFER_HARD_MAX) { throw new \InvalidArgumentException('微信零钱单笔提现不能超过官方上限 ¥20,000.00'); } $feeCalc = self::calcWithdrawFee($amt, $cfg); $arrive = $feeCalc['arrive_amount']; if ($channel === 'wechat') { if ($arrive < self::WX_TRANSFER_MIN_ARRIVE) { throw new \InvalidArgumentException('扣除手续费后实际到账金额不能低于 ¥0.10(微信转账最低限额)'); } if ($arrive >= self::WX_TRANSFER_REALNAME_THRESHOLD && !$isRealnameVerified) { throw new \InvalidArgumentException('微信官方规定单笔转账满 ¥2,000.00 必须校验真实姓名,请先完成实名认证'); } } // 单日次数与累计额度校验 $stats = self::getMasterTodayWithdrawStats((int)$master['id']); if ($cfg['daily_count'] > 0 && $stats['today_count'] >= $cfg['daily_count']) { throw new \InvalidArgumentException("今日提现次数已达上限(每日最多 {$cfg['daily_count']} 次),请明日再试"); } if ($cfg['daily_amount'] > 0 && round($stats['today_amount'] + $amt, 2) > $cfg['daily_amount']) { $remain = max(0.00, round($cfg['daily_amount'] - $stats['today_amount'], 2)); throw new \InvalidArgumentException('超出单日累计提现上限(今日剩余可提额度 ¥' . number_format($remain, 2, '.', '') . ')'); } $snapshot = [ 'fee_mode' => $cfg['fee_mode'], 'fee_rate' => $cfg['fee_rate'], 'fee_fixed' => $cfg['fee_fixed'], 'fee_min' => $cfg['fee_min'], 'fee_max' => $cfg['fee_max'], 'fee' => $feeCalc['fee'], 'arrive_amount' => $arrive, 'transfer_mode' => $cfg['transfer_mode'], 'audit_mode' => $cfg['audit_mode'], 'snapshot_time' => time(), ]; return [ 'fee' => $feeCalc['fee'], 'arrive_amount' => $arrive, 'cfg' => $cfg, 'rule_snapshot' => json_encode($snapshot, JSON_UNESCAPED_UNICODE), ]; } /** * 后台配置中心保存并严格校验全量配置项 * * @param array $input * @return void * @throws \InvalidArgumentException */ public static function saveConfigs(array $input): void { $defaults = self::defaults(); $clean = []; // 1. 校验订单默认抽佣兜底配置 if (isset($input['default_calc_mode'])) { if (!in_array($input['default_calc_mode'], ['percent', 'fixed', 'mixed'], true)) { throw new \InvalidArgumentException('请选择有效的默认抽佣计费方式'); } $clean['default_calc_mode'] = $input['default_calc_mode']; } if (isset($input['default_commission_rate'])) { if (!is_numeric($input['default_commission_rate']) || (float)$input['default_commission_rate'] < 0 || (float)$input['default_commission_rate'] > 100) { throw new \InvalidArgumentException('默认抽佣比例必须在 0% ~ 100% 之间'); } $clean['default_commission_rate'] = number_format(round((float)$input['default_commission_rate'], 2), 2, '.', ''); } foreach (['default_fixed_fee' => '默认单笔固定抽佣', 'default_min_commission' => '默认单笔保底抽佣', 'default_max_commission' => '默认单笔封顶抽佣'] as $k => $label) { if (isset($input[$k])) { if (!is_numeric($input[$k]) || (float)$input[$k] < 0) { throw new \InvalidArgumentException($label . '不能为负数'); } $clean[$k] = number_format(round((float)$input[$k], 2), 2, '.', ''); } } if (isset($clean['default_min_commission'], $clean['default_max_commission'])) { $minC = (float)$clean['default_min_commission']; $maxC = (float)$clean['default_max_commission']; if ($minC > 0 && $maxC > 0 && $minC > $maxC) { throw new \InvalidArgumentException('默认单笔保底抽佣不能大于单笔封顶抽佣'); } } if (isset($input['include_diff_in_commission'])) { $clean['include_diff_in_commission'] = (int)$input['include_diff_in_commission'] === 1 ? '1' : '0'; } // 2. 校验提现与微信小程序商家转账配置 if (isset($input['withdraw_enabled'])) { $clean['withdraw_enabled'] = (int)$input['withdraw_enabled'] === 1 ? '1' : '0'; } if (isset($input['withdraw_close_tip'])) { $clean['withdraw_close_tip'] = mb_substr(trim((string)$input['withdraw_close_tip']), 0, 200); } if (isset($input['withdraw_channels'])) { $chArr = is_array($input['withdraw_channels']) ? $input['withdraw_channels'] : explode(',', (string)$input['withdraw_channels']); $validCh = []; foreach ($chArr as $c) { $c = trim((string)$c); if (in_array($c, ['wechat', 'bank'], true) && !in_array($c, $validCh, true)) { $validCh[] = $c; } } if (empty($validCh)) { throw new \InvalidArgumentException('请至少勾选一种开放提现渠道'); } $clean['withdraw_channels'] = implode(',', $validCh); } if (isset($input['withdraw_require_realname'])) { $clean['withdraw_require_realname'] = (int)$input['withdraw_require_realname'] === 1 ? '1' : '0'; } if (isset($input['withdraw_min_amount'])) { if (!is_numeric($input['withdraw_min_amount']) || (float)$input['withdraw_min_amount'] < 0.10) { throw new \InvalidArgumentException('符合微信小程序商家转账规范,单笔最低提现金额不得低于 ¥0.10'); } $clean['withdraw_min_amount'] = number_format(round((float)$input['withdraw_min_amount'], 2), 2, '.', ''); } if (isset($input['withdraw_max_amount'])) { if (!is_numeric($input['withdraw_max_amount']) || (float)$input['withdraw_max_amount'] > self::WX_TRANSFER_HARD_MAX) { throw new \InvalidArgumentException('微信商家转账官方规定单笔最高金额不得超过 ¥20,000.00'); } $clean['withdraw_max_amount'] = number_format(round((float)$input['withdraw_max_amount'], 2), 2, '.', ''); } if (isset($clean['withdraw_min_amount'], $clean['withdraw_max_amount']) && (float)$clean['withdraw_min_amount'] > (float)$clean['withdraw_max_amount']) { throw new \InvalidArgumentException('单笔最低提现金额不能大于单笔最高提现金额'); } if (isset($input['withdraw_daily_amount'])) { if (!is_numeric($input['withdraw_daily_amount']) || (float)$input['withdraw_daily_amount'] < 0) { throw new \InvalidArgumentException('单人单日累计提现上限不能为负数'); } $clean['withdraw_daily_amount'] = number_format(round((float)$input['withdraw_daily_amount'], 2), 2, '.', ''); if ((float)$clean['withdraw_daily_amount'] > 0 && isset($clean['withdraw_min_amount']) && (float)$clean['withdraw_daily_amount'] < (float)$clean['withdraw_min_amount']) { throw new \InvalidArgumentException('单人单日累计上限不能低于单笔最低提现金额'); } } if (isset($input['withdraw_daily_count'])) { $clean['withdraw_daily_count'] = (string)max(0, (int)$input['withdraw_daily_count']); } if (isset($input['withdraw_allow_weekdays'])) { $dayArr = is_array($input['withdraw_allow_weekdays']) ? $input['withdraw_allow_weekdays'] : explode(',', (string)$input['withdraw_allow_weekdays']); $validDays = []; foreach ($dayArr as $d) { $di = (int)$d; if ($di >= 1 && $di <= 7 && !in_array($di, $validDays, true)) { $validDays[] = $di; } } if (empty($validDays)) { throw new \InvalidArgumentException('请至少选择一天允许提现的周期'); } sort($validDays); $clean['withdraw_allow_weekdays'] = implode(',', $validDays); } if (isset($input['withdraw_time_start'])) { $ts = trim((string)$input['withdraw_time_start']); if (!preg_match('/^([01]\d|2[0-3]):[0-5]\d$/', $ts)) { throw new \InvalidArgumentException('每日开始时间格式须为 HH:mm(如 00:00)'); } $clean['withdraw_time_start'] = $ts; } if (isset($input['withdraw_time_end'])) { $te = trim((string)$input['withdraw_time_end']); if (!preg_match('/^([01]\d|2[0-3]):[0-5]\d$/', $te)) { throw new \InvalidArgumentException('每日结束时间格式须为 HH:mm(如 23:59)'); } $clean['withdraw_time_end'] = $te; } if (isset($clean['withdraw_time_start'], $clean['withdraw_time_end']) && $clean['withdraw_time_start'] >= $clean['withdraw_time_end']) { throw new \InvalidArgumentException('每日允许提现开始时间必须早于结束时间'); } if (isset($input['withdraw_fee_mode'])) { if (!in_array($input['withdraw_fee_mode'], ['percent', 'fixed', 'free'], true)) { throw new \InvalidArgumentException('请选择有效的手续费计收方式'); } $clean['withdraw_fee_mode'] = $input['withdraw_fee_mode']; } if (isset($input['withdraw_fee_rate'])) { if (!is_numeric($input['withdraw_fee_rate']) || (float)$input['withdraw_fee_rate'] < 0 || (float)$input['withdraw_fee_rate'] > 50) { throw new \InvalidArgumentException('提现手续费率须在 0% ~ 50% 之间'); } $clean['withdraw_fee_rate'] = number_format(round((float)$input['withdraw_fee_rate'], 2), 2, '.', ''); } foreach (['withdraw_fee_fixed' => '单笔固定手续费', 'withdraw_fee_min' => '单笔最低保底手续费', 'withdraw_fee_max' => '单笔最高封顶手续费'] as $k => $label) { if (isset($input[$k])) { if (!is_numeric($input[$k]) || (float)$input[$k] < 0) { throw new \InvalidArgumentException($label . '不能为负数'); } $clean[$k] = number_format(round((float)$input[$k], 2), 2, '.', ''); } } if (isset($clean['withdraw_fee_min'], $clean['withdraw_fee_max'])) { $fMin = (float)$clean['withdraw_fee_min']; $fMax = (float)$clean['withdraw_fee_max']; if ($fMin > 0 && $fMax > 0 && $fMin > $fMax) { throw new \InvalidArgumentException('单笔最低保底手续费不能大于最高封顶手续费'); } } // 校验最低提现额扣除手续费后不低于微信官方 0.10 元底线 $mergedPreview = array_merge(self::getAll(), $clean); $minTestAmt = (float)$mergedPreview['withdraw_min_amount']; if (($mergedPreview['withdraw_fee_mode'] ?? 'percent') === 'fixed' && $minTestAmt - (float)$mergedPreview['withdraw_fee_fixed'] < self::WX_TRANSFER_MIN_ARRIVE) { throw new \InvalidArgumentException('单笔最低提现金额扣除固定手续费后实际到账低于 ¥0.10,请调高最低提现额或调低固定手续费'); } if (isset($input['withdraw_transfer_mode'])) { if (!in_array($input['withdraw_transfer_mode'], ['wx_mch_transfer', 'offline_manual'], true)) { throw new \InvalidArgumentException('无效的微信渠道打款执行方式'); } $clean['withdraw_transfer_mode'] = $input['withdraw_transfer_mode']; } if (isset($input['withdraw_audit_mode'])) { if (!in_array($input['withdraw_audit_mode'], ['manual', 'auto_small'], true)) { throw new \InvalidArgumentException('无效的提现审核模式'); } $clean['withdraw_audit_mode'] = $input['withdraw_audit_mode']; } if (isset($input['withdraw_auto_max_amount'])) { if (!is_numeric($input['withdraw_auto_max_amount']) || (float)$input['withdraw_auto_max_amount'] < 0 || (float)$input['withdraw_auto_max_amount'] > self::WX_TRANSFER_HARD_MAX) { throw new \InvalidArgumentException('小额免审自动转账上限须在 ¥0 ~ ¥20,000 之间'); } $clean['withdraw_auto_max_amount'] = number_format(round((float)$input['withdraw_auto_max_amount'], 2), 2, '.', ''); } foreach ([ 'wx_transfer_scene_id' => ['label' => '微信转账场景ID', 'max' => 16], 'wx_recv_perception' => ['label' => '微信收款用户感知', 'max' => 32], 'wx_job_type' => ['label' => '微信场景报备-岗位类型', 'max' => 32], 'wx_reward_desc' => ['label' => '微信场景报备-报酬说明', 'max' => 32], 'wx_transfer_remark' => ['label' => '微信零钱转账备注', 'max' => 32], ] as $k => $meta) { if (isset($input[$k])) { $val = trim((string)$input[$k]); if ($val === '') { throw new \InvalidArgumentException($meta['label'] . '不能为空(微信支付 APIv3 商家转账必填项)'); } if (mb_strlen($val) > $meta['max']) { throw new \InvalidArgumentException($meta['label'] . '不能超过 ' . $meta['max'] . ' 个字符(微信接口长度限制)'); } $clean[$k] = $val; } } $now = time(); Db::startTrans(); try { foreach ($clean as $name => $val) { if (!array_key_exists($name, $defaults)) { continue; } $group = strpos($name, 'default_') === 0 || $name === 'include_diff_in_commission' ? 'commission' : 'withdraw'; $exists = Db::name('finance_config')->where('name', $name)->find(); if ($exists) { Db::name('finance_config')->where('id', (int)$exists['id'])->update([ 'value' => $val, 'updatetime' => $now, ]); } else { Db::name('finance_config')->insert([ 'name' => $name, 'group' => $group, 'title' => $name, 'value' => $val, 'remark' => '', 'updatetime' => $now, ]); } } Db::commit(); } catch (\Throwable $e) { Db::rollback(); throw $e; } self::clearCache(); } }