Files
lotteryLaravel/app/Services/Wallet/HttpMainSiteWalletGateway.php

242 lines
7.7 KiB
PHP
Raw Permalink 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
namespace App\Services\Wallet;
use App\Models\Player;
use GuzzleHttp\Exception\ConnectException;
use App\Services\Integration\PartnerSiteConfig;
use App\Support\Integration\WalletApiRequestGuard;
use App\Services\Integration\PartnerSiteConfigResolver;
/**
* 通过 HTTP 调用主站钱包 API路径见 config lottery.main_site.wallet_*_path
*/
final class HttpMainSiteWalletGateway implements MainSiteWalletGateway
{
public function __construct(
private readonly PartnerSiteConfigResolver $partnerSiteConfigResolver,
private readonly WalletApiRequestGuard $walletApiRequestGuard,
) {}
public function debitMainForLotteryDeposit(
Player $player,
string $currencyCode,
int $amountMinor,
string $idempotentKey,
): MainSiteWalletResult {
$config = $this->partnerSiteConfigResolver->resolveForPlayer($player);
return $this->post(
$config->walletDebitPath,
$player,
$currencyCode,
$amountMinor,
$idempotentKey,
$config,
);
}
public function creditMainForLotteryWithdraw(
Player $player,
string $currencyCode,
int $amountMinor,
string $idempotentKey,
): MainSiteWalletResult {
$config = $this->partnerSiteConfigResolver->resolveForPlayer($player);
return $this->post(
$config->walletCreditPath,
$player,
$currencyCode,
$amountMinor,
$idempotentKey,
$config,
);
}
public function refundMainForFailedLotteryDeposit(
Player $player,
string $currencyCode,
int $amountMinor,
string $idempotentKey,
): MainSiteWalletResult {
$config = $this->partnerSiteConfigResolver->resolveForPlayer($player);
return $this->post(
$config->walletCreditPath,
$player,
$currencyCode,
$amountMinor,
$idempotentKey,
$config,
);
}
private function post(
string $path,
Player $player,
string $currencyCode,
int $amountMinor,
string $idempotentKey,
PartnerSiteConfig $config,
): MainSiteWalletResult {
if (! $config->hasWalletApi()) {
$requestSnapshot = [
'site_code' => $player->site_code,
'site_player_id' => $player->site_player_id,
'player_id' => $player->id,
'currency_code' => $currencyCode,
'amount_minor' => $amountMinor,
'idempotent_key' => $idempotentKey,
'_meta' => ['stub' => true],
];
// 生产环境不允许把“主站未配置钱包 API”当作成功从而避免资金链路失真。
if (app()->environment(['production'])) {
return MainSiteWalletResult::failure(
'wallet_api_not_configured',
['stub' => false, 'reason' => 'wallet_api_not_configured'],
false,
$requestSnapshot,
);
}
// 本地/测试环境允许 stub 成功,方便联调与自动化用例覆盖资金链路流程。
return MainSiteWalletResult::success(
null,
['stub' => true, 'reason' => 'wallet_api_not_configured'],
$requestSnapshot,
);
}
$endpoint = $this->walletApiRequestGuard->guard(
$config->walletApiUrl,
$config->walletTimeoutSeconds,
);
if ($endpoint === null) {
return MainSiteWalletResult::failure(
'wallet_api_url_invalid',
['reason' => 'invalid_base_url'],
false,
[
'site_code' => $player->site_code,
'site_player_id' => $player->site_player_id,
'player_id' => $player->id,
'currency_code' => $currencyCode,
'amount_minor' => $amountMinor,
'idempotent_key' => $idempotentKey,
'_meta' => ['stub' => true, 'operation' => 'invalid_wallet_api_url'],
],
);
}
$requestBody = [
'site_code' => $player->site_code,
'site_player_id' => $player->site_player_id,
'player_id' => $player->id,
'currency_code' => $currencyCode,
'amount_minor' => $amountMinor,
'idempotent_key' => $idempotentKey,
];
$requestSnapshot = array_merge($requestBody, [
'_meta' => [
'method' => 'POST',
'path' => '/'.ltrim($path, '/'),
],
]);
$url = $endpoint->baseUrl.'/'.ltrim($path, '/');
$apiKey = $config->walletApiKey;
if (app()->environment(['production'])
&& $config->source === PartnerSiteConfig::SOURCE_LEGACY_ENV
&& (! is_string($apiKey) || trim($apiKey) === '')
) {
return MainSiteWalletResult::failure(
'main_site_wallet_api_key_required',
['reason' => 'missing_main_site_wallet_api_key'],
false,
$requestSnapshot,
);
}
$headers = [];
if (is_string($apiKey) && $apiKey !== '') {
$headers['Authorization'] = 'Bearer '.$apiKey;
}
try {
$response = $endpoint->request($headers)
->acceptJson()
->asJson()
->post($url, $requestBody);
} catch (\Throwable $e) {
return MainSiteWalletResult::failure(
$e->getMessage(),
null,
self::isUncertainTransportFailure($e),
$requestSnapshot,
);
}
$payload = $response->json();
if (! is_array($payload)) {
$payload = ['raw' => $response->body()];
}
$status = $response->status();
if ($status === 408 || $status === 504) {
return MainSiteWalletResult::failure(
'HTTP '.$status,
$payload,
true,
$requestSnapshot,
);
}
if (! $response->successful()) {
return MainSiteWalletResult::failure(
is_string(data_get($payload, 'message'))
? (string) data_get($payload, 'message')
: ('HTTP '.$status),
$payload,
false,
$requestSnapshot,
);
}
$ok = (bool) data_get($payload, 'success', true);
if (! $ok) {
return MainSiteWalletResult::failure(
is_string(data_get($payload, 'message'))
? (string) data_get($payload, 'message')
: 'main_site_rejected',
$payload,
false,
$requestSnapshot,
);
}
$ref = data_get($payload, 'external_ref_no')
?? data_get($payload, 'data.external_ref_no')
?? data_get($payload, 'ref');
return MainSiteWalletResult::success(is_string($ref) ? $ref : null, $payload, $requestSnapshot);
}
private static function isUncertainTransportFailure(\Throwable $e): bool
{
if ($e instanceof ConnectException) {
return true;
}
$msg = strtolower($e->getMessage());
return str_contains($msg, 'curl error 28')
|| str_contains($msg, 'connection timed out')
|| str_contains($msg, 'timed out')
|| str_contains($msg, 'operation timed out');
}
}