):array{http_status:int,body:array,request_id:string} */ private ?Closure $transport; public function __construct( ?string $baseUrl = null, ?string $appKey = null, ?string $appSecret = null, ?callable $transport = null ) { $this->baseUrl = rtrim($baseUrl ?? (string) Config::get('ej_pharmacy.base_url', ''), '/'); $this->appKey = $appKey ?? (string) Config::get('ej_pharmacy.app_key', ''); $this->appSecret = $appSecret ?? (string) Config::get('ej_pharmacy.app_secret', ''); if ($this->baseUrl === '' || $this->appKey === '' || $this->appSecret === '') { throw new RuntimeException('恩济药房接口未配置完整'); } $this->transport = $transport === null ? null : Closure::fromCallable($transport); } public static function isConfigured(): bool { return (bool) Config::get('ej_pharmacy.enabled', false) && trim((string) Config::get('ej_pharmacy.base_url', '')) !== '' && trim((string) Config::get('ej_pharmacy.app_key', '')) !== '' && trim((string) Config::get('ej_pharmacy.app_secret', '')) !== ''; } /** @return array{http_status:int,body:array,request_id:string} */ public function medicines(int $after = 0, int $limit = 100): array { return $this->request('GET', '/api/openapi/v1/medicines', null, [ 'after' => max($after, 0), 'limit' => min(max($limit, 1), 500), ]); } /** @param array $payload @return array{http_status:int,body:array,request_id:string} */ public function importMedicines(array $payload): array { return $this->request('POST', '/api/openapi/v1/medicine-imports', $payload); } /** @param array $payload @return array{http_status:int,body:array,request_id:string} */ public function createPrescriptionOrder(array $payload): array { return $this->request('POST', '/api/openapi/v1/prescription-orders', $payload); } /** @return array{http_status:int,body:array,request_id:string} */ public function prescriptionOrder(string $sourceOrderNo, int $sourceRevision = 0): array { $query = ['source_system' => 'zyt']; if ($sourceRevision > 0) { $query['source_revision'] = $sourceRevision; } return $this->request( 'GET', '/api/openapi/v1/prescription-orders/' . rawurlencode($sourceOrderNo), null, $query ); } /** @param array|null $payload @param array $query @return array{http_status:int,body:array,request_id:string} */ private function request(string $method, string $path, ?array $payload = null, array $query = []): array { $queryString = $query === [] ? '' : http_build_query($query, '', '&', PHP_QUERY_RFC3986); $pathWithQuery = $path . ($queryString !== '' ? '?' . $queryString : ''); $body = $payload === null ? '' : (string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); $timestamp = (string) time(); $nonce = bin2hex(random_bytes(16)); $requestId = bin2hex(random_bytes(16)); $canonical = EjPharmacySignature::canonical($method, $pathWithQuery, $timestamp, $nonce, $body); $headers = [ 'Accept: application/json', 'Content-Type: application/json; charset=utf-8', 'X-App-Key: ' . $this->appKey, 'X-Timestamp: ' . $timestamp, 'X-Nonce: ' . $nonce, 'X-Signature: ' . EjPharmacySignature::sign($this->appSecret, $canonical), 'X-Request-Id: ' . $requestId, 'Expect:', ]; if ($this->transport !== null) { return ($this->transport)(strtoupper($method), $pathWithQuery, $body, $headers); } $ch = curl_init($this->baseUrl . $pathWithQuery); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => strtoupper($method), CURLOPT_HTTPHEADER => $headers, CURLOPT_CONNECTTIMEOUT => (int) Config::get('ej_pharmacy.connect_timeout', 5), CURLOPT_TIMEOUT => (int) Config::get('ej_pharmacy.request_timeout', 30), CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, ]); if ($payload !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, $body); } $raw = curl_exec($ch); $httpStatus = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($raw === false) { throw new RuntimeException('恩济药房通信失败:' . $error); } $decoded = json_decode((string) $raw, true); if (!is_array($decoded)) { throw new RuntimeException('恩济药房返回了无效 JSON,HTTP ' . $httpStatus); } return ['http_status' => $httpStatus, 'body' => $decoded, 'request_id' => $requestId]; } }