CaiWuXiTong/app/Services/Esign/EsignConfiguration.php

133 lines
3.9 KiB
PHP

<?php
namespace App\Services\Esign;
use RuntimeException;
/**
* Reads the E签宝 settings that Laravel loaded from the private runtime/.env.
*
* Keeping this normalization and endpoint check in one place prevents a
* production credential from silently being sent to the sandbox endpoint (or
* the reverse) when a deployment omits ESIGN_BASE_URL.
*/
final class EsignConfiguration
{
private readonly string $environment;
private readonly string $baseUrl;
private readonly string $appId;
private readonly string $appSecret;
private readonly string $notifyUrl;
private readonly int $callbackToleranceSeconds;
private readonly int $timeout;
public function __construct(
string $environment,
string $baseUrl,
string $appId,
string $appSecret,
string $notifyUrl,
int $callbackToleranceSeconds,
int $timeout,
) {
$this->environment = strtolower(trim($environment));
$this->baseUrl = rtrim(trim($baseUrl), '/');
$this->appId = trim($appId);
$this->appSecret = trim($appSecret);
$this->notifyUrl = trim($notifyUrl);
$this->callbackToleranceSeconds = max(60, $callbackToleranceSeconds);
$this->timeout = max(5, $timeout);
}
public static function fromConfig(): self
{
return new self(
environment: strtolower(trim((string) config('services.esign.environment', 'sandbox'))),
baseUrl: rtrim(trim((string) config('services.esign.base_url')), '/'),
appId: trim((string) config('services.esign.app_id')),
appSecret: trim((string) config('services.esign.app_secret')),
notifyUrl: trim((string) config('services.esign.notify_url')),
callbackToleranceSeconds: max(60, (int) config('services.esign.callback_tolerance_seconds', 900)),
timeout: max(5, (int) config('services.esign.timeout', 20)),
);
}
public function isConfigured(): bool
{
return $this->appId !== '' && $this->appSecret !== '';
}
public function assertReadyForApi(): void
{
if (! $this->isConfigured()) {
$label = $this->environment === 'production' ? '正式' : '沙箱';
throw new RuntimeException("E 签宝{$label}尚未配置 App ID 或 AppSecret。");
}
if (! in_array($this->environment, ['sandbox', 'production'], true)) {
throw new RuntimeException('E 签宝环境只能是 sandbox 或 production。');
}
if ($this->baseUrl === '' || filter_var($this->baseUrl, FILTER_VALIDATE_URL) === false) {
throw new RuntimeException('E 签宝 API 地址无效,请检查 ESIGN_BASE_URL。');
}
$scheme = strtolower((string) parse_url($this->baseUrl, PHP_URL_SCHEME));
$host = strtolower((string) parse_url($this->baseUrl, PHP_URL_HOST));
if ($scheme !== 'https') {
throw new RuntimeException('E 签宝 API 地址必须使用 HTTPS。');
}
if ($this->environment === 'production' && $host === 'smlopenapi.esign.cn') {
throw new RuntimeException('E 签宝正式环境不能使用沙箱 API 地址。');
}
if ($this->environment === 'sandbox' && $host === 'openapi.esign.cn') {
throw new RuntimeException('E 签宝沙箱环境不能使用正式 API 地址。');
}
}
public function environment(): string
{
return $this->environment;
}
public function baseUrl(): string
{
return $this->baseUrl;
}
public function appId(): string
{
return $this->appId;
}
public function appSecret(): string
{
return $this->appSecret;
}
public function notifyUrl(): string
{
return $this->notifyUrl;
}
public function callbackToleranceSeconds(): int
{
return $this->callbackToleranceSeconds;
}
public function timeout(): int
{
return $this->timeout;
}
}