['label' => '三斤支付', 'directions' => [1], 'signature' => 'MD5'], 'qianbao' => ['label' => '钱宝支付', 'directions' => [2], 'signature' => 'MD5'], 'jd' => ['label' => 'JD支付', 'directions' => [1, 2], 'signature' => 'MD5'], 'no' => ['label' => 'NO支付', 'directions' => [1, 2], 'signature' => 'SHA256'], 'zimu' => ['label' => '808支付', 'directions' => [1, 2], 'signature' => 'MD5'], ]; // data_type => 通道 type => [支付商代码, 支付方式名称] private const METHODS = [ 1 => [ 'wxsm' => ['sanjin', '微信扫码'], 'ylsm' => ['sanjin', '银联扫码'], 'szrmb' => ['sanjin', '数字人民币'], 'zfbsm' => ['sanjin', '支付宝扫码'], 'zfbge' => ['sanjin', '支付宝固额'], 'zfbzk' => ['sanjin', '支付宝转卡'], 'sdjt' => ['sanjin', '手动金条'], 'JDpay' => ['jd', 'JD钱包'], 'NOpay12' => ['no', 'NO扫码支付'], 'NOpay13' => ['no', 'NO余额支付'], 'ZIMUpay' => ['zimu', '808充值'], ], 2 => [ 'DF001' => ['qianbao', '银行卡'], 'DF002' => ['qianbao', '支付宝'], 'DF005' => ['qianbao', '数字人民币'], 'JDpay' => ['jd', 'JD钱包'], 'NOwithdraw' => ['no', 'NO快捷提现'], 'ZIMUwithdraw' => ['zimu', '808账户提现'], 'ZIMUcash' => ['zimu', '808人民币代付'], ], ]; public static function codes(): array { return array_keys(self::PROVIDERS); } public static function definitions(): array { return self::PROVIDERS; } public static function label(string $code): string { return self::PROVIDERS[$code]['label'] ?? $code; } public static function channelTypesForProvider(string $provider, ?int $direction = null): array { $types = []; foreach (self::METHODS as $dataType => $methods) { if ($direction !== null && $direction !== $dataType) continue; foreach ($methods as $type => [$code]) { if ($code === $provider) $types[] = $type; } } return array_values(array_unique($types)); } /** Definitions for creating channels, intentionally independent of existing channels/groups. */ public static function channelFormOptions(int $dataType, ?array $statuses = null): array { if (!in_array($dataType, [1, 2, 3], true)) throw new InvalidArgumentException('通道方向错误'); $companies = []; $types = []; foreach (self::PROVIDERS as $code => $provider) { if (in_array($dataType, $provider['directions'], true)) { $enabled = $statuses === null || ($statuses[$code] ?? 0) === 1; $companies[] = ['value' => $code, 'label' => $provider['label'], 'status' => $enabled ? 1 : 0, 'disabled' => !$enabled, 'disabled_reason' => $enabled ? '' : '支付公司已禁用']; } } foreach (self::METHODS[$dataType] ?? [] as $type => [$company, $label]) { $enabled = $statuses === null || ($statuses[$company] ?? 0) === 1; $types[] = ['value' => $type, 'label' => $label, 'from' => 1, 'payment_company' => $company, 'payment_company_label' => self::label($company), 'disabled' => !$enabled, 'disabled_reason' => $enabled ? '' : '支付公司已禁用']; } $local = match ($dataType) { 1 => ['usdt' => [2, 'USDT充值'], 'rgcz' => [3, '人工充值']], 2 => ['usdt' => [2, 'USDT提现'], 'rgtx' => [3, '人工提现']], 3 => ['recharge' => [1, '即充即送'], 'old_user' => [1, '老用户回归'], 'yuebao' => [1, '余额宝']], }; foreach ($local as $type => [$from, $label]) { $types[] = ['value' => $type, 'label' => $label, 'from' => $from, 'payment_company' => null, 'payment_company_label' => '', 'disabled' => false, 'disabled_reason' => '']; } return [ 'data_type' => $dataType, 'from_options' => $dataType === 3 ? [['value' => 1, 'label' => '活动']] : [ ['value' => 1, 'label' => '第三方支付'], ['value' => 2, 'label' => 'USDT'], ['value' => 3, 'label' => '人工'], ], 'payment_companies' => $companies, 'types' => $types, ]; } public static function normalizeChannelSelection(array $data): array { $definitions = array_column(self::channelFormOptions((int)$data['data_type'])['types'], null, 'value'); $definition = $definitions[(string)$data['type']] ?? null; if (!$definition) throw new InvalidArgumentException('请选择已接入的通道类型'); $company = (string)($data['payment_company'] ?? ''); if ($company !== '' && $company !== $definition['payment_company']) { throw new InvalidArgumentException('支付公司与通道类型不匹配,请重新选择类型'); } if (isset($data['from']) && (int)$data['from'] !== $definition['from']) { throw new InvalidArgumentException('通道来源与类型不匹配'); } $data['from'] = $definition['from']; unset($data['payment_company']); return $data; } public static function channelDisplay(array $channel, ?array $statuses = null): array { $identity = self::channelIdentity($channel); $label = $identity ? self::label($identity['provider_code']) : ''; $companyStatus = $identity ? ($statuses === null ? 1 : (int)($statuses[$identity['provider_code']] ?? 0)) : null; return [ 'payment_company' => $identity['provider_code'] ?? null, 'payment_company_label' => $label, 'display_name' => ($label === '' ? '' : $label . ' / ') . (string)$channel['name'], 'company_status' => $companyStatus, 'effective_status' => (int)($channel['status'] ?? 1) === 1 && $companyStatus !== 0 && empty($channel['deleted_at']) ? 1 : 0, ]; } public static function direction(?string $kind): ?int { return match ($kind) { null, '' => null, 'deposit', 'collection_scan', 'collection_third_party' => 1, 'withdraw' => 2, default => throw new InvalidArgumentException('不支持的支付配置类型'), }; } public static function channelIdentity(array $channel): ?array { if ((int)($channel['from'] ?? 0) !== 1) return null; $type = (string)($channel['type'] ?? ''); $method = self::METHODS[(int)($channel['data_type'] ?? 0)][$type] ?? null; if (!$method) return null; return ['provider_code' => $method[0], 'method_code' => $type, 'method_label' => $method[1]]; } public static function validateSelection(array $channel, string $provider, string $method, int $direction): void { $identity = self::channelIdentity($channel); if ((int)($channel['data_type'] ?? 0) !== $direction || !$identity) { throw new InvalidArgumentException('该通道不属于已接入的第三方支付通道'); } if ($identity['provider_code'] !== $provider) { throw new InvalidArgumentException('支付公司与所选通道不匹配'); } if ($identity['method_code'] !== $method) { throw new InvalidArgumentException('支付方式与所选通道不匹配'); } } public function options(array $channels, array $groups, array $filters = []): array { $direction = self::direction($filters['kind'] ?? null); $isCollection = in_array($filters['kind'] ?? '', ['collection_scan', 'collection_third_party'], true); $company = (string)($filters['payment_company'] ?? ''); $collector = (string)($filters['provider_name'] ?? ''); $selectedProvider = $isCollection ? $collector : $company; $selectedMethod = (string)($isCollection ? ($filters['collection_method'] ?? '') : ($filters['payment_method'] ?? '')); $groups = array_map(function ($group) { foreach (['recharge_type', 'withdraw_type', 'activity_type'] as $field) { $group[$field] = self::types($group[$field] ?? []); } return ['id' => (int)$group['id'], 'name' => (string)$group['name']] + $group; }, $groups); $formatted = []; foreach ($channels as $channel) { $dataType = (int)$channel['data_type']; $groupField = [1 => 'recharge_type', 2 => 'withdraw_type', 3 => 'activity_type'][$dataType] ?? null; $ids = []; foreach ($groups as $group) { if ($groupField && in_array((string)$channel['type'], $group[$groupField], true)) $ids[] = $group['id']; } $identity = self::channelIdentity($channel); $enabled = (int)$channel['status'] === 1; $companyEnabled = !$identity || $this->companyEnabled($identity['provider_code']); $formatted[] = [ 'id' => (int)$channel['id'], 'data_type' => $dataType, 'name' => (string)$channel['name'], 'key' => (string)$channel['key'], 'type' => (string)$channel['type'], 'rate' => bcadd((string)$channel['rate'], '0', 4), 'fee_rate' => bcmul((string)$channel['rate'], '100', 4), 'min_amount' => $channel['min'] ?? null, 'max_amount' => $channel['max'] ?? null, 'fixed_amounts' => self::types($channel['fixed'] ?? []), 'sort' => (int)$channel['sort'], 'status' => (int)$channel['status'], 'company_status' => $identity ? ($companyEnabled ? 1 : 0) : null, 'available_group_ids' => $ids, 'provider_code' => $identity['provider_code'] ?? null, 'payment_method' => $identity['method_code'] ?? null, 'payment_method_label' => $identity['method_label'] ?? null, 'selectable' => $identity && $companyEnabled && $enabled && $ids !== [], 'unavailable_reason' => !$identity ? '不属于第三方支付通道' : (!$companyEnabled ? '支付公司已禁用' : (!$enabled ? '通道已停用' : ($ids === [] ? '没有包含此通道的组合' : ''))), ]; } $selectedChannels = array_values(array_filter($formatted, static function ($channel) use ($direction, $selectedProvider, $selectedMethod) { if ($direction !== null && ($channel['data_type'] !== $direction || !$channel['provider_code'])) return false; if ($selectedProvider !== '' && $channel['provider_code'] !== $selectedProvider) return false; return $selectedMethod === '' || $channel['payment_method'] === $selectedMethod; })); return [ 'payment_companies' => $this->companies($formatted, $direction), 'payment_methods' => $this->methods($formatted, $direction, $company), 'collection_providers' => $this->companies($formatted, 1), 'collection_methods' => $this->methods($formatted, 1, $collector), 'recharge_channels' => $selectedChannels, 'channel_groups' => $groups, 'levels' => array_map(static fn ($group) => ['value' => $group['id'], 'label' => $group['name']], $groups), 'credential_source' => 'environment', ]; } private function companies(array $channels, ?int $direction): array { $result = []; foreach (self::PROVIDERS as $code => $provider) { if ($direction !== null && !in_array($direction, $provider['directions'], true)) continue; $matching = array_filter($channels, static fn ($channel) => $channel['provider_code'] === $code && ($direction === null || $channel['data_type'] === $direction)); $result[] = [ 'value' => $code, 'label' => $provider['label'], 'signature_algorithm' => $provider['signature'], 'secret_field' => $direction === null ? null : ($direction === 2 ? 'withdrawal_secret' : 'deposit_secret'), ] + $this->companyAvailability($code, $matching); } return $result; } private function methods(array $channels, ?int $direction, string $provider): array { $result = []; foreach (self::METHODS as $dataType => $methods) { if ($direction !== null && $direction !== $dataType) continue; foreach ($methods as $code => [$company, $label]) { if ($provider !== '' && $provider !== $company) continue; $matching = array_filter($channels, static fn ($channel) => $channel['provider_code'] === $company && $channel['data_type'] === $dataType && $channel['payment_method'] === $code); $selectable = array_filter($matching, static fn ($channel) => $channel['selectable']); $result[] = [ 'value' => $code, 'label' => $label, 'provider_code' => $company, 'data_type' => $dataType, 'channel_ids' => array_values(array_column($matching, 'id')), 'selectable_channel_ids' => array_values(array_column($selectable, 'id')), ] + $this->companyAvailability($company, $matching); } } return $result; } private function companyEnabled(string $code): bool { return $this->providerStatuses === null || ($this->providerStatuses[$code] ?? 0) === 1; } private function companyAvailability(string $code, array $channels): array { if (!$this->companyEnabled($code)) { return ['disabled' => true, 'disabled_reason_code' => 'provider_disabled', 'disabled_reason' => '支付公司已禁用']; } return $this->availability($channels); } private function availability(array $channels): array { if (array_filter($channels, static fn ($channel) => $channel['selectable']) !== []) { return ['disabled' => false, 'disabled_reason_code' => '', 'disabled_reason' => '']; } if ($channels === []) { $code = 'channel_missing'; $reason = '未配置匹配的通道'; } elseif (array_filter($channels, static fn ($channel) => $channel['status'] === 1) === []) { $code = 'channel_disabled'; $reason = '匹配通道均已停用'; } else { $code = 'group_missing'; $reason = '已启用通道尚未加入通道组合'; } return ['disabled' => true, 'disabled_reason_code' => $code, 'disabled_reason' => $reason]; } private static function types($value): array { $values = is_array($value) ? $value : explode(',', (string)$value); return array_values(array_filter(array_map(static fn ($item) => trim((string)$item), $values), static fn ($item) => $item !== '')); } }