415 lines
14 KiB
PHP
415 lines
14 KiB
PHP
<?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('Row format error');
|
||
}
|
||
$code = isset($row['code']) && is_string($row['code']) ? strtolower(trim($row['code'])) : '';
|
||
if ($code === '' || !isset($registry[$code])) {
|
||
throw new InvalidArgumentException('Channel code is not registered');
|
||
}
|
||
if (isset($seen[$code])) {
|
||
throw new InvalidArgumentException('Duplicate channel code');
|
||
}
|
||
$seen[$code] = true;
|
||
$norm = self::normalizeOverrides([array_merge($row, ['code' => $code])]);
|
||
if ($norm === []) {
|
||
throw new InvalidArgumentException('Invalid channel row data');
|
||
}
|
||
$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 encode failed');
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|