| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- <?php
- namespace app\common\service;
- class AlipayPaymentService extends BasePaymentService
- {
- // 支付宝API地址
- protected $apiUrl = 'https://openapi.alipay.com/gateway.do';
- // 配置参数
- protected $config = [];
- /**
- * 初始化支付宝配置
- */
- protected function initConfig()
- {
- $this->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;
- }
- }
|