*/ public static function codeRegistry(): array { $base = [ 'directpay' => ['name' => 'DirectPay', 'name_en' => 'DirectPay', 'sort' => 10], ]; $extra = self::registryFromEnv(); foreach ($extra as $code => $meta) { if (!is_string($code) || trim($code) === '' || !is_array($meta)) { continue; } $normCode = strtolower(trim($code)); if (!preg_match('/^[a-z0-9_\-]{1,24}$/', $normCode)) { continue; } $name = isset($meta['name']) && is_string($meta['name']) ? trim($meta['name']) : ''; $nameEn = isset($meta['name_en']) && is_string($meta['name_en']) ? trim($meta['name_en']) : ''; $sort = isset($meta['sort']) && is_numeric($meta['sort']) ? intval($meta['sort']) : 50; if ($name === '') { continue; } $base[$normCode] = [ 'name' => $name, 'name_en' => $nameEn !== '' ? $nameEn : $name, 'sort' => $sort, ]; } return $base; } /** * @return array */ private static function registryFromEnv(): array { $raw = getenv('DEPOSIT_CHANNELS_REGISTRY_JSON'); if (!is_string($raw) || trim($raw) === '') { return []; } $decoded = json_decode($raw, true); if (!is_array($decoded)) { return []; } $out = []; foreach ($decoded as $item) { if (!is_array($item)) { continue; } $code = isset($item['code']) && is_string($item['code']) ? strtolower(trim($item['code'])) : ''; if ($code === '') { continue; } $out[$code] = $item; } return $out; } public static function parseOverridesFromConfigValue($raw): array { if (!is_string($raw) || trim($raw) === '') { return []; } $decoded = json_decode($raw, true); if (!is_array($decoded)) { return []; } $list = isset($decoded['channels']) && is_array($decoded['channels']) ? $decoded['channels'] : $decoded; return self::normalizeOverrides($list); } /** * 库中「运营覆盖」原始列表(未与注册表合并):finance_cashier 含 channels 键时用其值;否则读 deposit_channel * * @return list}> */ public static function parseStoredOverridesFromDb(): array { $fcRow = Db::name('game_config')->where('config_key', FinanceCashierConfig::CONFIG_KEY)->find(); $rawFc = is_array($fcRow) ? ($fcRow['config_value'] ?? null) : null; $decoded = null; if (is_string($rawFc) && trim($rawFc) !== '') { $tmp = json_decode($rawFc, true); if (is_array($tmp)) { $decoded = $tmp; } } $channelsKeyPresent = is_array($decoded) && array_key_exists('channels', $decoded); if ($channelsKeyPresent) { $list = isset($decoded['channels']) && is_array($decoded['channels']) ? $decoded['channels'] : []; return self::normalizeOverrides($list); } $depRow = Db::name('game_config')->where('config_key', self::CONFIG_KEY)->find(); $depRaw = is_array($depRow) ? ($depRow['config_value'] ?? null) : null; return self::parseOverridesFromConfigValue($depRaw); } /** * 与注册表合并并排序后的有效渠道行(业务侧统一入口) * * @return list}> */ public static function effectiveRowsFromDb(): array { $stored = self::parseStoredOverridesFromDb(); return self::effectiveOverrides(self::expandRowsForAdmin($stored)); } /** * @param list $items * * @return list}> */ public static function normalizeOverrides(array $items): array { $out = []; foreach ($items as $row) { if (!is_array($row)) { continue; } $code = isset($row['code']) && is_string($row['code']) ? strtolower(trim($row['code'])) : ''; if ($code === '') { continue; } $sort = isset($row['sort']) && is_numeric($row['sort']) ? intval($row['sort']) : 0; $status = isset($row['status']) && is_numeric($row['status']) ? intval($row['status']) : 1; $status = $status === 1 ? 1 : 0; $tierIds = []; if (isset($row['tier_ids']) && is_array($row['tier_ids'])) { foreach ($row['tier_ids'] as $tid) { if (is_string($tid)) { $t = trim($tid); if ($t !== '' && preg_match('/^[a-zA-Z0-9_\-]{1,32}$/', $t)) { $tierIds[] = $t; } } } $tierIds = array_values(array_unique($tierIds)); } $out[] = [ 'code' => $code, 'sort' => $sort, 'status' => $status, 'tier_ids' => $tierIds, ]; } return $out; } /** * 合并注册表与运营覆盖;若库中无覆盖则对注册表内全部渠道启用默认行 * * @param list}> $overrides * * @return list}> */ public static function effectiveOverrides(array $overrides): array { $registry = self::codeRegistry(); $byCode = []; foreach ($overrides as $row) { if (!isset($registry[$row['code']])) { continue; } $byCode[$row['code']] = $row; } if ($byCode === []) { foreach ($registry as $code => $meta) { $sortMeta = $meta['sort'] ?? 10; $sortVal = is_numeric($sortMeta) ? intval($sortMeta) : 10; $byCode[$code] = [ 'code' => $code, 'sort' => $sortVal, 'status' => 1, 'tier_ids' => [], ]; } } $list = array_values($byCode); usort($list, static function (array $a, array $b): int { if ($a['sort'] !== $b['sort']) { return $a['sort'] <=> $b['sort']; } return strcmp($a['code'], $b['code']); }); return $list; } /** * @param array{code: string, sort: int, status: int, tier_ids: list} $overrideRow */ public static function isTierAllowed(array $overrideRow, string $tierId): bool { $ids = $overrideRow['tier_ids'] ?? []; if (!is_array($ids) || $ids === []) { return true; } foreach ($ids as $id) { if (is_string($id) && $id === $tierId) { return true; } } return false; } /** * @param list> $effectiveRows */ public static function findMergedByCode(array $effectiveRows, string $code): ?array { $norm = strtolower(trim($code)); foreach ($effectiveRows as $row) { if (!is_array($row)) { continue; } $c = isset($row['code']) && is_string($row['code']) ? strtolower(trim($row['code'])) : ''; if ($c === $norm) { return $row; } } return null; } /** * @param list}> $overrideRows * * @return list */ public static function channelsForTier(string $tierId, array $overrideRows, string $lang): array { $registry = self::codeRegistry(); $out = []; foreach ($overrideRows as $row) { if (($row['status'] ?? 0) !== 1) { continue; } $code = $row['code']; if (!isset($registry[$code])) { continue; } if (!self::isTierAllowed($row, $tierId)) { continue; } $meta = $registry[$code]; $name = self::pickLangName($meta, $lang); $sortRaw = $row['sort'] ?? 0; $sortVal = is_numeric($sortRaw) ? intval($sortRaw) : 0; $out[] = [ 'code' => $code, 'name' => $name, 'sort' => $sortVal, ]; } usort($out, static function (array $a, array $b): int { if ($a['sort'] !== $b['sort']) { return $a['sort'] <=> $b['sort']; } return strcmp($a['code'], $b['code']); }); return $out; } /** * @param array $meta */ private static function pickLangName(array $meta, string $lang): string { $name = isset($meta['name']) && is_string($meta['name']) ? $meta['name'] : ''; $nameEn = isset($meta['name_en']) && is_string($meta['name_en']) ? $meta['name_en'] : ''; $normalized = strtolower(str_replace('_', '-', trim($lang))); $isEn = $normalized === 'en' || str_starts_with($normalized, 'en-'); if ($isEn) { return $nameEn !== '' ? $nameEn : $name; } return $name !== '' ? $name : $nameEn; } /** * @param list}> $effectiveRows */ public static function assertChannelAllowsTier(string $channelCode, string $tierId, array $effectiveRows): bool { $row = self::findMergedByCode($effectiveRows, $channelCode); if ($row === null) { return false; } if (($row['status'] ?? 0) !== 1) { return false; } return self::isTierAllowed($row, $tierId); } /** * @param list}> $effectiveRows * * @return list */ public static function enabledPayChannelCodes(array $effectiveRows): array { $registry = self::codeRegistry(); $codes = []; foreach ($effectiveRows as $row) { if (($row['status'] ?? 0) !== 1) { continue; } $c = isset($row['code']) && is_string($row['code']) ? $row['code'] : ''; if ($c !== '' && isset($registry[$c])) { $codes[] = $c; } } return array_values(array_unique($codes)); } /** * @param list> $items * * @return list}> */ public static function prepareOverridesForSave(array $items): array { $registry = self::codeRegistry(); $seen = []; $out = []; foreach ($items as $idx => $row) { if (!is_array($row)) { throw new InvalidArgumentException('第 ' . ($idx + 1) . ' 行格式错误'); } $code = isset($row['code']) && is_string($row['code']) ? strtolower(trim($row['code'])) : ''; if ($code === '' || !isset($registry[$code])) { throw new InvalidArgumentException('第 ' . ($idx + 1) . ' 行渠道 code 未注册'); } if (isset($seen[$code])) { throw new InvalidArgumentException('渠道 code 重复:' . $code); } $seen[$code] = true; $norm = self::normalizeOverrides([array_merge($row, ['code' => $code])]); if ($norm === []) { throw new InvalidArgumentException('第 ' . ($idx + 1) . ' 行渠道数据无效'); } $out[] = $norm[0]; } usort($out, static function (array $a, array $b): int { if ($a['sort'] !== $b['sort']) { return $a['sort'] <=> $b['sort']; } return strcmp($a['code'], $b['code']); }); return $out; } /** * @param list}> $items */ public static function encodeForDb(array $items): string { $wrapped = ['channels' => $items]; $encoded = json_encode($wrapped, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($encoded === false) { throw new InvalidArgumentException('JSON 编码失败'); } return $encoded; } /** * 后台编辑用:为注册表内每个 code 补齐一行(合并库内覆盖) * * @param list}> $storedOverrides * * @return list}> */ public static function expandRowsForAdmin(array $storedOverrides): array { $registry = self::codeRegistry(); $effective = self::effectiveOverrides($storedOverrides); $byCode = []; foreach ($effective as $r) { $byCode[$r['code']] = $r; } $items = []; foreach ($registry as $code => $meta) { $sortMeta = $meta['sort'] ?? 10; $sortDefault = is_numeric($sortMeta) ? intval($sortMeta) : 10; $items[] = $byCode[$code] ?? [ 'code' => $code, 'sort' => $sortDefault, 'status' => 1, 'tier_ids' => [], ]; } usort($items, static function (array $a, array $b): int { if ($a['sort'] !== $b['sort']) { return $a['sort'] <=> $b['sort']; } return strcmp($a['code'], $b['code']); }); return $items; } }