CaiWuXiTong/app/Services/Esign/EsignConfiguration.php

173 lines
5.8 KiB
PHP

<?php
namespace App\Services\Esign;
use JsonException;
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;
private readonly string $initiatorAccounts;
private readonly string $defaultInitiator;
public function __construct(
string $environment,
string $baseUrl,
string $appId,
string $appSecret,
string $notifyUrl,
int $callbackToleranceSeconds,
int $timeout,
string $initiatorAccounts = '{}',
string $defaultInitiator = '',
) {
$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);
$this->initiatorAccounts = trim($initiatorAccounts);
$this->defaultInitiator = trim($defaultInitiator);
}
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)),
initiatorAccounts: (string) config('services.esign.initiator_accounts', '{}'),
defaultInitiator: (string) config('services.esign.default_initiator', ''),
);
}
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;
}
/** Resolve the selected legal entity without changing credentials used by historical flows. */
public function signFlowInitiator(?string $organizationName = null): ?array
{
try {
$accounts = json_decode($this->initiatorAccounts ?: '{}', true, 32, JSON_THROW_ON_ERROR);
} catch (JsonException) {
throw new RuntimeException('E 签宝发起身份配置格式无效,请联系管理员。');
}
if (! is_array($accounts)) {
throw new RuntimeException('E 签宝发起身份配置格式无效,请联系管理员。');
}
if ($accounts === [] && $this->defaultInitiator === '') {
return null;
}
$name = trim((string) $organizationName) ?: $this->defaultInitiator;
$account = $accounts[$name] ?? null;
if (! is_array($account) || ! is_string($account['org_id'] ?? null) || ! is_string($account['psn_id'] ?? null)
|| trim($account['org_id']) === '' || trim($account['psn_id']) === '') {
throw new RuntimeException('所选签署主体未配置完整的 E 签宝发起企业和经办人身份,请联系管理员。');
}
return ['orgInitiator' => [
'orgId' => trim($account['org_id']),
'transactor' => ['psnId' => trim($account['psn_id'])],
]];
}
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;
}
}