config = [ 'app_id' => $this->payConfig['app_id'], 'merchant_private_key' => $this->getPrivateKey(), 'alipay_public_key' => $this->getPublicKey(), 'notify_url' => $this->payConfig['notify_url'] ?: url('/api/invitation/payment/alipayNotify', [], true, true)->build(), 'charset' => 'UTF-8', 'sign_type' => 'RSA2', 'gateway_url' => $this->apiUrl, ]; if (empty($this->config['app_id']) || empty($this->config['merchant_private_key'])) { throw new \Exception('支付宝支付配置不完整'); } } /** * 获取私钥 - 修复版本 */ protected function getPrivateKey() { // 从文件路径读取或直接使用配置中的私钥 $privateKey = ''; if (!empty($this->payConfig['private_key_path']) && file_exists($this->payConfig['private_key_path'])) { $privateKey = file_get_contents($this->payConfig['private_key_path']); } elseif (!empty($this->payConfig['key'])) { // 如果没有文件路径,可能私钥直接存储在配置中 $privateKey = $this->payConfig['key']; } if (empty($privateKey)) { throw new \Exception('私钥内容为空'); } // 清理私钥格式 $privateKey = $this->formatPrivateKey($privateKey); return $privateKey; } /** * 格式化私钥 */ protected function formatPrivateKey($privateKey) { // 移除可能的开头和结尾标记 $privateKey = str_replace([ '-----BEGIN PRIVATE KEY-----', '-----END PRIVATE KEY-----', '-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----', "\r", "\n" ], '', $privateKey); // 确保私钥是完整的行(每行64字符) $privateKey = chunk_split($privateKey, 64, "\n"); // 添加 PKCS#1 格式的标记(支付宝推荐格式) $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" . $privateKey . "-----END RSA PRIVATE KEY-----\n"; return $privateKey; } /** * 获取公钥 */ protected function getPublicKey() { if (!empty($this->payConfig['public_key_path']) && file_exists($this->payConfig['public_key_path'])) { return file_get_contents($this->payConfig['public_key_path']); } return ''; } /** * 生成支付二维码 */ public function createQrCode($orderNo, $amount, $subject, $body = '') { try { // 业务参数 $bizContent = [ 'out_trade_no' => $orderNo, 'total_amount' => number_format($amount, 2, '.', ''), 'subject' => $subject, 'body' => $body ?: $subject, 'product_code' => 'FAST_INSTANT_TRADE_PAY', ]; // 公共参数 $params = [ 'app_id' => $this->config['app_id'], 'method' => 'alipay.trade.precreate', 'charset' => $this->config['charset'], 'sign_type' => $this->config['sign_type'], 'timestamp' => date('Y-m-d H:i:s'), 'version' => '1.0', 'notify_url' => $this->config['notify_url'], 'biz_content' => json_encode($bizContent, JSON_UNESCAPED_UNICODE), ]; // 生成签名 $params['sign'] = $this->generateSign($params); // 发送请求 $result = $this->execute($params); $response = $result['alipay_trade_precreate_response']; if ($response['code'] == '10000') { return [ 'qr_code_url' => $response['qr_code'], 'qr_code_content' => $response['qr_code'], 'out_request_no' => $response['out_trade_no'], ]; } else { $errorMsg = $response['sub_msg'] ?? $response['msg'] ?? '支付宝下单失败'; throw new \Exception($errorMsg); } } catch (\Throwable $e) { $this->log('生成支付宝支付二维码失败', [ 'order_no' => $orderNo, 'error' => $e->getMessage() ], 'error'); throw $e; } } /** * 处理支付回调 */ public function handleNotify($input) { try { // 验证签名 if (!$this->verifySign($input)) { throw new \Exception('签名验证失败'); } // 检查交易状态 $tradeStatus = $input['trade_status']; if (!in_array($tradeStatus, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) { throw new \Exception('支付未成功: ' . $tradeStatus); } return [ 'order_no' => $input['out_trade_no'], 'trade_no' => $input['trade_no'], 'pay_amount' => $input['total_amount'], 'pay_time' => $input['gmt_payment'] ?? date('Y-m-d H:i:s'), 'raw_data' => $input, ]; } catch (\Throwable $e) { $this->log('处理支付宝支付回调失败', [ 'input' => $input, 'error' => $e->getMessage() ], 'error'); throw $e; } } /** * 查询支付订单 */ public function queryOrder($orderNo) { try { // 业务参数 $bizContent = [ 'out_trade_no' => $orderNo, ]; // 公共参数 $params = [ 'app_id' => $this->config['app_id'], 'method' => 'alipay.trade.query', 'charset' => $this->config['charset'], 'sign_type' => $this->config['sign_type'], 'timestamp' => date('Y-m-d H:i:s'), 'version' => '1.0', 'biz_content' => json_encode($bizContent, JSON_UNESCAPED_UNICODE), ]; $params['sign'] = $this->generateSign($params); $result = $this->execute($params); $response = $result['alipay_trade_query_response']; if ($response['code'] == '10000') { $tradeStatus = $response['trade_status']; $payStatus = 0; // 待支付 if (in_array($tradeStatus, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) { $payStatus = 1; // 已支付 } elseif ($tradeStatus == 'TRADE_CLOSED') { $payStatus = 2; // 支付失败 } return [ 'order_no' => $response['out_trade_no'], 'trade_no' => $response['trade_no'], 'pay_status' => $payStatus, 'pay_amount' => $response['total_amount'], 'pay_time' => $response['send_pay_date'] ?? null, 'trade_status' => $tradeStatus, ]; } elseif ($response['code'] == '40004') { // 订单不存在,可能是未支付 return [ 'order_no' => $orderNo, 'pay_status' => 0, 'trade_status' => 'WAIT_BUYER_PAY', ]; } throw new \Exception('查询订单失败: ' . ($response['sub_msg'] ?? $response['msg'] ?? '未知错误')); } catch (\Throwable $e) { $this->log('查询支付宝支付订单失败', [ 'order_no' => $orderNo, 'error' => $e->getMessage() ], 'error'); throw $e; } } /** * 验证签名 */ protected function verifySign($data, $sign = null) { $sign = $data['sign']; $signType = $data['sign_type']; // 移除sign和sign_type参数 unset($data['sign'], $data['sign_type']); // 参数排序 ksort($data); // 拼接待签名字符串 $signContent = ''; foreach ($data as $key => $value) { if ($value !== '' && $value !== null && $key != 'sign') { $signContent .= $key . '=' . $value . '&'; } } $signContent = rtrim($signContent, '&'); // 验证签名 $publicKey = $this->config['alipay_public_key']; $res = openssl_get_publickey($publicKey); $result = openssl_verify($signContent, base64_decode($sign), $res, OPENSSL_ALGO_SHA256); // openssl_free_key($res); return $result === 1; } /** * 生成签名 */ protected function generateSign($data) { // 参数排序 ksort($data); // 拼接待签名字符串 $signContent = ''; foreach ($data as $key => $value) { if ($value !== '' && $value !== null && $key != 'sign') { $signContent .= $key . '=' . $value . '&'; } } $signContent = rtrim($signContent, '&'); // 使用私钥生成签名 $privateKey = $this->config['merchant_private_key']; $res = openssl_get_privatekey($privateKey); if (!$res) { throw new \Exception('私钥格式错误'); } if ($this->config['sign_type'] == 'RSA2') { openssl_sign($signContent, $sign, $res, OPENSSL_ALGO_SHA256); } else { openssl_sign($signContent, $sign, $res); } // openssl_free_key($res); return base64_encode($sign); } /** * 执行请求 - 简化修复版本 */ private function execute($params) { $url = $this->config['gateway_url']; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $response = curl_exec($ch); if (curl_errno($ch)) { $error = curl_error($ch); curl_close($ch); throw new \Exception('CURL错误: ' . $error); } curl_close($ch); // 清理响应数据 $response = trim($response); // 移除可能的BOM头 if (substr($response, 0, 3) == "\xEF\xBB\xBF") { $response = substr($response, 3); } // 转换编码确保是UTF-8 $encoding = mb_detect_encoding($response, ['UTF-8', 'GBK', 'GB2312', 'ASCII'], true); if ($encoding && $encoding != 'UTF-8') { $response = mb_convert_encoding($response, 'UTF-8', $encoding); } // 移除非法字符 $response = iconv('UTF-8', 'UTF-8//IGNORE', $response); // 尝试解析 $result = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { // 记录详细的调试信息 $errorDetails = [ 'error' => json_last_error_msg(), 'response_length' => strlen($response), 'response_preview' => substr($response, 0, 200), 'response_end' => substr($response, -200) ]; $this->log('支付宝API响应JSON解析失败', $errorDetails, 'error'); // 尝试手动解析支付宝格式 if (preg_match('/\{.*\}/s', $response, $matches)) { $jsonPart = $matches[0]; $result = json_decode($jsonPart, true); if (json_last_error() === JSON_ERROR_NONE) { return $result; } } throw new \Exception('JSON解析失败: ' . json_last_error_msg() . ',响应长度:' . strlen($response)); } return $result; } }