config = $payConfigService->getConfig('SafePay'); $this->merNo = $this->config['mer_no'] ?? ''; $this->apiUrl = $this->config['apiUrl'] ?? 'https://api.safepay.wang'; $this->privateKey = $this->config['private_key'] ?? ''; $this->platformPublicKey = $this->config['platform_public_key'] ?? ''; } /** * 构建待签名数据(ASCII升序、QueryString格式、跳过sign和空值) */ public function buildSignData(array $params): string { // 移除sign字段和空值 $signData = []; foreach ($params as $key => $value) { if ($key === 'sign') { continue; } if ($value === null || $value === '' || (is_array($value) && empty($value))) { continue; } if (is_array($value) || is_object($value)) { $signData[$key] = json_encode($value, JSON_UNESCAPED_UNICODE); } else { $signData[$key] = (string)$value; } } // ASCII升序(字典序) ksort($signData); // 构建 QueryString: key1=value1&key2=value2 $result = ''; foreach ($signData as $key => $value) { $result .= '&' . $key . '=' . $value; } return substr($result, 1); } /** * RSA SHA256 签名(使用商户私钥) * * @param array $params 请求参数 * @return array 添加sign后的参数 */ public function sign(array $params): array { $signData = $this->buildSignData($params); $prvKeyPem = $this->convertPrivateKeyToPem($this->privateKey); $signature = ''; $signed = openssl_sign($signData, $signature, $prvKeyPem, OPENSSL_ALGO_SHA256); if (!$signed) { throw new \Exception('SafePay RSA签名失败'); } $params['sign'] = base64_encode($signature); Util::WriteLog('SafePay_sign', "待签名字符串: " . $signData); Util::WriteLog('SafePay_sign', "签名结果: " . $params['sign']); return $params; } /** * RSA SHA256 验签(使用平台公钥) * * @param array $params 回调参数(含sign) * @return bool */ public function verifySign(array $params): bool { $receivedSign = $params['sign'] ?? ''; if (empty($receivedSign)) { Util::WriteLog('SafePay', '验签失败:sign字段为空'); return false; } $signData = $this->buildSignData($params); $pubKeyPem = $this->convertPublicKeyToPem($this->platformPublicKey); $isVerified = openssl_verify( $signData, base64_decode($receivedSign), $pubKeyPem, OPENSSL_ALGO_SHA256 ); Util::WriteLog('SafePay_verify', "验签数据: " . $signData); Util::WriteLog('SafePay_verify', "验签结果: " . ($isVerified === 1 ? '通过' : '失败')); return $isVerified === 1; } /** * 转换私钥为PEM格式 */ private function convertPrivateKeyToPem($privateKeyString) { if (strpos($privateKeyString, '-----BEGIN PRIVATE KEY-----') !== false) { return $privateKeyString; } $key = trim($privateKeyString); if (!preg_match('/^-----BEGIN/', $key)) { if (base64_decode($key, true)) { $key = base64_decode($key); } $key = "-----BEGIN PRIVATE KEY-----\n" . chunk_split(base64_encode($key), 64, "\n") . "-----END PRIVATE KEY-----"; } return $key; } /** * 转换公钥为PEM格式 */ private function convertPublicKeyToPem($publicKeyString) { if (strpos($publicKeyString, '-----BEGIN PUBLIC KEY-----') !== false) { return $publicKeyString; } $key = trim($publicKeyString); if (!preg_match('/^-----BEGIN/', $key)) { if (base64_decode($key, true)) { $key = base64_decode($key); } $key = "-----BEGIN PUBLIC KEY-----\n" . chunk_split(base64_encode($key), 64, "\n") . "-----END PUBLIC KEY-----"; } return $key; } /** * POST JSON请求 */ public function curlPost($url, $payload) { $timeout = 20; $data = json_encode($payload, JSON_UNESCAPED_UNICODE); $headers = [ 'Content-Type: application/json', ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $result = curl_exec($ch); if (curl_errno($ch)) { $error = curl_error($ch); Util::WriteLog('SafePay_error', 'CURL Error: ' . $error); curl_close($ch); return false; } curl_close($ch); return $result; } }