[ 'original_refund' => 1, 'refund_speed' => 'immediate', 'delay_hours' => 0, 'wallet_as_deposit' => 0, ], 'withdraw_methods' => ['wechat_qrcode'], 'agent_withdraw' => [ 'review_amount' => 200, 'other_fee_mode' => 'fee', 'alipay_fee_mode' => 'tax', 'tax_rate' => 0.6, 'fee' => 0, 'min_amount' => 0.3, 'max_amount' => 500, 'schedule_type' => 'day', 'time_ranges' => [['start' => '00:00', 'end' => '24:00']], ], 'merchant_withdraw' => [ 'review_amount' => 200, 'other_fee_mode' => 'tax', 'alipay_fee_mode' => 'tax', 'tax_rate' => 0.6, 'fee' => 0, 'min_amount' => 0.3, 'max_amount' => 500, 'schedule_type' => 'day', 'time_ranges' => [['start' => '00:00', 'end' => '24:00']], ], 'user_withdraw' => [ 'review_amount' => 100, 'fee_rate' => 0, 'min_amount' => 0.01, 'max_amount' => 100, 'deposit_refund_mode' => 0, ], 'user_settings' => [ 'wechat_phone_authorization' => 0, 'alipay_phone_authorization' => 0, ], 'membership' => [ 'enabled' => 0, 'month_price' => 0, 'quarter_price' => 0, 'year_price' => 0, ], 'order_profit_detail' => 1, 'official_account_login' => 0, 'new_merchant_dd_prefix' => 0, ]; public function __construct() { $this->model = new MiniProgramSettings(); } public function getSettings(): array { $model = $this->model->findOrEmpty(self::CONFIG_ID); if ($model->isEmpty()) { return self::DEFAULTS; } $stored = json_decode((string) $model->getAttr('settings'), true); if (!is_array($stored)) { return self::DEFAULTS; } return $this->normalizeSettings(array_replace_recursive(self::DEFAULTS, $stored)); } public function saveSettings(array $data): bool { $settings = $this->normalizeSettings($data); $json = json_encode($settings, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($json === false) { throw new ApiException('系统设置序列化失败'); } $model = $this->model->findOrEmpty(self::CONFIG_ID); if ($model->isEmpty()) { return (bool) $this->model->create(['id' => self::CONFIG_ID, 'settings' => $json])->getKey(); } return $model->save(['settings' => $json]) !== false; } private function normalizeSettings(array $input): array { $deposit = $this->arrayValue($input, 'deposit_refund'); $userWithdraw = $this->arrayValue($input, 'user_withdraw'); $userSettings = $this->arrayValue($input, 'user_settings'); $membership = $this->arrayValue($input, 'membership'); return [ 'deposit_refund' => [ 'original_refund' => $this->flag($deposit['original_refund'] ?? 1), 'refund_speed' => $this->enum($deposit['refund_speed'] ?? 'immediate', ['immediate', 'delayed'], '押金退回速度'), 'delay_hours' => (int) $this->number($deposit['delay_hours'] ?? 0, 0, 8760, '押金退款延长时间'), 'wallet_as_deposit' => $this->flag($deposit['wallet_as_deposit'] ?? 0), ], 'withdraw_methods' => $this->withdrawMethods($input['withdraw_methods'] ?? []), 'agent_withdraw' => $this->withdrawProfile($this->arrayValue($input, 'agent_withdraw'), '代理'), 'merchant_withdraw' => $this->withdrawProfile($this->arrayValue($input, 'merchant_withdraw'), '商家'), 'user_withdraw' => [ 'review_amount' => $this->number($userWithdraw['review_amount'] ?? 100, 0, 99999999, '用户提现审核金额'), 'fee_rate' => $this->number($userWithdraw['fee_rate'] ?? 0, 0, 100, '用户提现手续费'), 'min_amount' => $this->number($userWithdraw['min_amount'] ?? 0.01, 0, 99999999, '用户提现最小金额'), 'max_amount' => $this->number($userWithdraw['max_amount'] ?? 100, 0, 99999999, '用户提现最大金额'), 'deposit_refund_mode' => $this->flag($userWithdraw['deposit_refund_mode'] ?? 0), ], 'user_settings' => [ 'wechat_phone_authorization' => $this->flag($userSettings['wechat_phone_authorization'] ?? 0), 'alipay_phone_authorization' => $this->flag($userSettings['alipay_phone_authorization'] ?? 0), ], 'membership' => [ 'enabled' => $this->flag($membership['enabled'] ?? 0), 'month_price' => $this->number($membership['month_price'] ?? 0, 0, 99999999, '月卡金额'), 'quarter_price' => $this->number($membership['quarter_price'] ?? 0, 0, 99999999, '季卡金额'), 'year_price' => $this->number($membership['year_price'] ?? 0, 0, 99999999, '年卡金额'), ], 'order_profit_detail' => $this->flag($input['order_profit_detail'] ?? 1), 'official_account_login' => $this->flag($input['official_account_login'] ?? 0), 'new_merchant_dd_prefix' => (int) $this->number($input['new_merchant_dd_prefix'] ?? 0, 0, 999999, '新商户 DD 规则'), ]; } private function withdrawProfile(array $profile, string $label): array { $min = $this->number($profile['min_amount'] ?? 0.3, 0, 99999999, $label . '提现最小金额'); $max = $this->number($profile['max_amount'] ?? 500, 0, 99999999, $label . '提现最大金额'); if ($max < $min) { throw new ApiException($label . '提现最大金额不能小于最小金额'); } return [ 'review_amount' => $this->number($profile['review_amount'] ?? 200, 0, 99999999, $label . '提现审核金额'), 'other_fee_mode' => $this->enum($profile['other_fee_mode'] ?? 'tax', ['tax', 'fee', 'both'], $label . '其他手续费模式'), 'alipay_fee_mode' => $this->enum($profile['alipay_fee_mode'] ?? 'tax', ['tax', 'fee', 'both'], $label . '支付宝手续费模式'), 'tax_rate' => $this->number($profile['tax_rate'] ?? 0.6, 0, 100, $label . '提现税点'), 'fee' => $this->number($profile['fee'] ?? 0, 0, 99999999, $label . '提现手续费'), 'min_amount' => $min, 'max_amount' => $max, 'schedule_type' => $this->enum($profile['schedule_type'] ?? 'day', ['day', 'week', 'month'], $label . '提现时间'), 'time_ranges' => $this->timeRanges($profile['time_ranges'] ?? []), ]; } private function timeRanges(mixed $ranges): array { if (!is_array($ranges) || $ranges === []) { return [['start' => '00:00', 'end' => '24:00']]; } if (count($ranges) > 10) { throw new ApiException('提现时间段不能超过 10 个'); } $result = []; foreach ($ranges as $range) { if (!is_array($range)) { throw new ApiException('提现时间段格式不正确'); } $start = (string) ($range['start'] ?? ''); $end = (string) ($range['end'] ?? ''); if (!$this->validTime($start, false) || !$this->validTime($end, true)) { throw new ApiException('提现时间段必须使用 HH:mm 格式'); } $result[] = ['start' => $start, 'end' => $end]; } return $result; } private function validTime(string $value, bool $allowEndOfDay): bool { if ($allowEndOfDay && $value === '24:00') { return true; } return preg_match('/^(?:[01]\\d|2[0-3]):[0-5]\\d$/', $value) === 1; } private function withdrawMethods(mixed $methods): array { $allowed = ['bank_card', 'wechat', 'alipay', 'wechat_qrcode', 'alipay_qrcode']; if (!is_array($methods)) { return []; } return array_values(array_unique(array_intersect($allowed, array_map('strval', $methods)))); } private function arrayValue(array $input, string $key): array { return isset($input[$key]) && is_array($input[$key]) ? $input[$key] : []; } private function flag(mixed $value): int { return in_array($value, [1, '1', true], true) ? 1 : 0; } private function enum(mixed $value, array $allowed, string $label): string { $value = (string) $value; if (!in_array($value, $allowed, true)) { throw new ApiException($label . '设置不正确'); } return $value; } private function number(mixed $value, float $min, float $max, string $label): float { if (!is_numeric($value)) { throw new ApiException($label . '必须是数字'); } $number = (float) $value; if ($number < $min || $number > $max) { throw new ApiException($label . '超出允许范围'); } return $number; } }