fix(core): harden settlement and wallet integration

This commit is contained in:
wchino
2026-07-22 01:40:37 +08:00
parent 150c3e7ebd
commit 35f6e46958
54 changed files with 2564 additions and 359 deletions

View File

@@ -3,9 +3,9 @@
namespace App\Services\Wallet;
use App\Models\Player;
use Illuminate\Support\Facades\Http;
use App\Services\Integration\PartnerSiteConfig;
use App\Support\Integration\WalletApiRequestGuard;
use App\Services\Integration\PartnerSiteConfigResolver;
use App\Support\Integration\WalletApiUrlSanitizer;
/**
* 查询主站钱包余额(供玩家端余额接口填充 main_balance
@@ -14,6 +14,7 @@ final class HttpMainSiteWalletBalanceClient
{
public function __construct(
private readonly PartnerSiteConfigResolver $partnerSiteConfigResolver,
private readonly WalletApiRequestGuard $walletApiRequestGuard,
) {}
public function fetch(Player $player, string $currencyCode): ?int
@@ -52,8 +53,11 @@ final class HttpMainSiteWalletBalanceClient
);
}
$base = WalletApiUrlSanitizer::normalizeAndValidate($config->walletApiUrl);
if ($base === null) {
$endpoint = $this->walletApiRequestGuard->guard(
$config->walletApiUrl,
$config->walletTimeoutSeconds,
);
if ($endpoint === null) {
return new MainSiteWalletBalanceProbeResult(
success: false,
mainBalanceMinor: null,
@@ -66,12 +70,11 @@ final class HttpMainSiteWalletBalanceClient
}
$path = $config->walletBalancePath;
$url = $base.'/'.ltrim($path, '/');
$timeout = $config->walletTimeoutSeconds;
$url = $endpoint->baseUrl.'/'.ltrim($path, '/');
$apiKey = $config->walletApiKey;
if (app()->environment(['production'])
&& $config->source === \App\Services\Integration\PartnerSiteConfig::SOURCE_LEGACY_ENV
&& $config->source === PartnerSiteConfig::SOURCE_LEGACY_ENV
&& (! is_string($apiKey) || trim($apiKey) === '')
) {
return new MainSiteWalletBalanceProbeResult(
@@ -97,8 +100,7 @@ final class HttpMainSiteWalletBalanceClient
];
try {
$response = Http::withHeaders($headers)
->timeout($timeout)
$response = $endpoint->request($headers)
->acceptJson()
->get($url, $query);
} catch (\Throwable $e) {

View File

@@ -3,10 +3,10 @@
namespace App\Services\Wallet;
use App\Models\Player;
use Illuminate\Support\Facades\Http;
use GuzzleHttp\Exception\ConnectException;
use App\Services\Integration\PartnerSiteConfig;
use App\Support\Integration\WalletApiRequestGuard;
use App\Services\Integration\PartnerSiteConfigResolver;
use App\Support\Integration\WalletApiUrlSanitizer;
/**
* 通过 HTTP 调用主站钱包 API路径见 config lottery.main_site.wallet_*_path
@@ -15,6 +15,7 @@ final class HttpMainSiteWalletGateway implements MainSiteWalletGateway
{
public function __construct(
private readonly PartnerSiteConfigResolver $partnerSiteConfigResolver,
private readonly WalletApiRequestGuard $walletApiRequestGuard,
) {}
public function debitMainForLotteryDeposit(
@@ -77,7 +78,7 @@ final class HttpMainSiteWalletGateway implements MainSiteWalletGateway
string $currencyCode,
int $amountMinor,
string $idempotentKey,
\App\Services\Integration\PartnerSiteConfig $config,
PartnerSiteConfig $config,
): MainSiteWalletResult {
if (! $config->hasWalletApi()) {
$requestSnapshot = [
@@ -108,8 +109,11 @@ final class HttpMainSiteWalletGateway implements MainSiteWalletGateway
);
}
$base = WalletApiUrlSanitizer::normalizeAndValidate($config->walletApiUrl);
if ($base === null) {
$endpoint = $this->walletApiRequestGuard->guard(
$config->walletApiUrl,
$config->walletTimeoutSeconds,
);
if ($endpoint === null) {
return MainSiteWalletResult::failure(
'wallet_api_url_invalid',
['reason' => 'invalid_base_url'],
@@ -142,12 +146,11 @@ final class HttpMainSiteWalletGateway implements MainSiteWalletGateway
],
]);
$url = $base.'/'.ltrim($path, '/');
$timeout = $config->walletTimeoutSeconds;
$url = $endpoint->baseUrl.'/'.ltrim($path, '/');
$apiKey = $config->walletApiKey;
if (app()->environment(['production'])
&& $config->source === \App\Services\Integration\PartnerSiteConfig::SOURCE_LEGACY_ENV
&& $config->source === PartnerSiteConfig::SOURCE_LEGACY_ENV
&& (! is_string($apiKey) || trim($apiKey) === '')
) {
return MainSiteWalletResult::failure(
@@ -164,8 +167,7 @@ final class HttpMainSiteWalletGateway implements MainSiteWalletGateway
}
try {
$response = Http::withHeaders($headers)
->timeout($timeout)
$response = $endpoint->request($headers)
->acceptJson()
->asJson()
->post($url, $requestBody);

View File

@@ -3,15 +3,15 @@
namespace App\Services\Wallet;
use App\Models\Player;
use Illuminate\Support\Facades\Http;
use App\Support\Integration\WalletApiRequestGuard;
use App\Services\Integration\PartnerSiteConfigResolver;
use App\Support\Integration\WalletApiUrlSanitizer;
/** 按幂等键查询主站钱包侧是否已有对应划转记录。 */
final class HttpMainSiteWalletIdempotentProbeClient
{
public function __construct(
private readonly PartnerSiteConfigResolver $partnerSiteConfigResolver,
private readonly WalletApiRequestGuard $walletApiRequestGuard,
) {}
public function probe(Player $player, string $idempotentKey): MainSiteWalletIdempotentProbeResult
@@ -32,8 +32,11 @@ final class HttpMainSiteWalletIdempotentProbeClient
);
}
$base = WalletApiUrlSanitizer::normalizeAndValidate($config->walletApiUrl);
if ($base === null) {
$endpoint = $this->walletApiRequestGuard->guard(
$config->walletApiUrl,
$config->walletTimeoutSeconds,
);
if ($endpoint === null) {
return new MainSiteWalletIdempotentProbeResult(
status: MainSiteWalletIdempotentProbeResult::STATUS_UNAVAILABLE,
message: 'wallet_api_url_invalid',
@@ -41,7 +44,7 @@ final class HttpMainSiteWalletIdempotentProbeClient
}
$path = $config->walletLookupIdempotentPath;
$url = $base.'/'.ltrim($path, '/');
$url = $endpoint->baseUrl.'/'.ltrim($path, '/');
$headers = ['Accept' => 'application/json'];
if (is_string($config->walletApiKey) && $config->walletApiKey !== '') {
$headers['Authorization'] = 'Bearer '.$config->walletApiKey;
@@ -54,8 +57,7 @@ final class HttpMainSiteWalletIdempotentProbeClient
];
try {
$response = Http::withHeaders($headers)
->timeout($config->walletTimeoutSeconds)
$response = $endpoint->request($headers)
->acceptJson()
->get($url, $query);
} catch (\Throwable $e) {

View File

@@ -3,7 +3,7 @@
namespace App\Services\Wallet;
/**
* 主站 balance 探测结果(后台联调检测用,含诊断信息)。
* 主站 balance 探测结果(后台联调检测用)。
*/
final readonly class MainSiteWalletBalanceProbeResult
{
@@ -29,7 +29,6 @@ final readonly class MainSiteWalletBalanceProbeResult
'request_url' => $this->requestUrl,
'http_status' => $this->httpStatus,
'message' => $this->message,
'response_preview' => $this->responseBody,
];
}
}