133 lines
3.9 KiB
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;
|
|
}
|
|
}
|