Files
webman-buildadmin/app/common/library/game/DepositChannel.php
2026-04-23 10:15:01 +08:00

415 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\common\library\game;
use InvalidArgumentException;
use support\think\Db;
/**
* 充值支付渠道:优先读取 game_config.finance_cashier.channels无此键时回退 game_config.deposit_channel迁移期镜像
*
* 每项code须在代码/环境注册表内、sort、status(0/1)。
*
* 渠道展示名以代码注册表为准;运营只配置开关与排序,默认兼容全部充值档位。
*/
final class DepositChannel
{
public const CONFIG_KEY = 'deposit_channel';
/**
* @return array<string, array{name: string, name_en: string, sort: int}>
*/
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<string, mixed>
*/
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<array{code: string, sort: int, status: int, tier_ids: list<string>}>
*/
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<array{code: string, sort: int, status: int, tier_ids: list<string>}>
*/
public static function effectiveRowsFromDb(): array
{
$stored = self::parseStoredOverridesFromDb();
return self::effectiveOverrides(self::expandRowsForAdmin($stored));
}
/**
* @param list<mixed> $items
*
* @return list<array{code: string, sort: int, status: int, tier_ids: list<string>}>
*/
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;
$out[] = [
'code' => $code,
'sort' => $sort,
'status' => $status,
'tier_ids' => [],
];
}
return $out;
}
/**
* 合并注册表与运营覆盖;若库中无覆盖则对注册表内全部渠道启用默认行
*
* @param list<array{code: string, sort: int, status: int, tier_ids: list<string>}> $overrides
*
* @return list<array{code: string, sort: int, status: int, tier_ids: list<string>}>
*/
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<string>} $overrideRow
*/
public static function isTierAllowed(array $overrideRow, string $tierId): bool
{
// 渠道不再配置档位白名单:默认兼容全部充值档位。
return true;
}
/**
* @param list<array<string, mixed>> $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<array{code: string, sort: int, status: int, tier_ids: list<string>}> $overrideRows
*
* @return list<array{code: string, name: string, sort: int}>
*/
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;
}
$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<string, string> $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<array{code: string, sort: int, status: int, tier_ids: list<string>}> $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<array{code: string, sort: int, status: int, tier_ids: list<string>}> $effectiveRows
*
* @return list<string>
*/
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<array<string, mixed>> $items
*
* @return list<array{code: string, sort: int, status: int, tier_ids: list<string>}>
*/
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<array{code: string, sort: int, status: int, tier_ids: list<string>}> $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<array{code: string, sort: int, status: int, tier_ids: list<string>}> $storedOverrides
*
* @return list<array{code: string, sort: int, status: int, tier_ids: list<string>}>
*/
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;
}
}