浏览代码

Rewrite Alipay Notify logic

BrettonYe 1 年之前
父节点
当前提交
2c09bd9fba
共有 2 个文件被更改,包括 67 次插入28 次删除
  1. 31 13
      app/Utils/Library/AlipayF2F.php
  2. 36 15
      app/Utils/Payments/F2Fpay.php

+ 31 - 13
app/Utils/Library/AlipayF2F.php

@@ -1,8 +1,8 @@
 <?php
 /*
  * 作者:BrettonYe
- * 功能:ProxyPanel 支付宝面对面 【收单线下交易预创建】 【收单交易查询】 接口实现库
- * 时间:2022/12/28
+ * 功能:ProxyPanel 支付宝面对面【收单线下交易预创建】【收单交易查询】接口实现库
+ * 更新时间:2023/10/29
  * 参考资料:https://opendocs.alipay.com/open/02ekfg?scene=19 riverslei/payment
  */
 
@@ -13,7 +13,7 @@ use RuntimeException;
 
 class AlipayF2F
 {
-    private static string $gatewayUrl = 'https://openapi.alipay.com/gateway.do';
+    private static string $gatewayUrl = 'https://openapi.alipay.com/gateway.do'; //https://openapi-sandbox.dl.alipaydev.com/gateway.do
 
     private array $config;
 
@@ -98,7 +98,7 @@ class AlipayF2F
             throw new RuntimeException('请求错误-看起来是请求失败');
         }
 
-        if (! $this->rsaVerify($response[$resKey], $response['sign'])) {
+        if (! $this->validate_response_sign($response[$resKey], $response['sign'])) {
             throw new RuntimeException('验签错误-'.$response[$resKey]['msg'].' | '.($response[$resKey]['sub_msg'] ?? var_export($response, true)));
         }
 
@@ -113,14 +113,14 @@ class AlipayF2F
     private function buildParams(): array
     {
         $params = [
-            'app_id' => $this->config['app_id'] ?? '',
-            'method' => $this->config['method'] ?? '',
+            'app_id' => $this->config['app_id'],
+            'method' => $this->config['method'],
             'charset' => 'utf-8',
             'sign_type' => 'RSA2',
             'timestamp' => date('Y-m-d H:m:s'),
-            'biz_content' => $this->config['biz_content'] ?? [],
+            'biz_content' => $this->config['biz_content'],
             'version' => '1.0',
-            'notify_url' => $this->config['notify_url'] ?? '',
+            'notify_url' => $this->config['notify_url'],
         ];
         $params = array_filter($params);
         $params['sign'] = $this->encrypt($this->buildQuery($params));
@@ -155,21 +155,39 @@ class AlipayF2F
     }
 
     /**
-     * RSA2验签.
+     * 同步返回验签.
      *
-     * @param  array  $data  待签名数据
+     * @param  array  $body  待签名数据
      *
      * @throws RuntimeException
      */
-    public function rsaVerify(array $data, string $sign): bool
+    public function validate_response_sign(array $body, string $sign): bool
     {
-        unset($data['sign'], $data['sign_type']);
+        unset($body['sign'], $body['sign_type']);
         $publicKey = openssl_pkey_get_public($this->config['public_key']);
         if (empty($publicKey)) {
             throw new RuntimeException('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
         }
 
-        return (bool) openssl_verify(json_encode($data), base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256);
+        return (bool) openssl_verify(json_encode($body), base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256);
+    }
+
+    /**
+     * 异步通知验签.
+     *
+     * @param  array  $body  待签名数据
+     *
+     * @throws RuntimeException
+     */
+    public function validate_notification_sign(array $body, string $sign): bool
+    {
+        unset($body['sign'], $body['sign_type']);
+        $publicKey = openssl_pkey_get_public($this->config['public_key']);
+        if (empty($publicKey)) {
+            throw new RuntimeException('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
+        }
+
+        return (bool) openssl_verify($this->buildQuery($body), base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256);
     }
 
     public function qrCharge(array $content): array

+ 36 - 15
app/Utils/Payments/F2Fpay.php

@@ -2,6 +2,7 @@
 
 namespace App\Utils\Payments;
 
+use App\Models\Payment;
 use App\Services\PaymentService;
 use App\Utils\Library\AlipayF2F;
 use App\Utils\Library\Templates\Gateway;
@@ -14,16 +15,16 @@ use Response;
 
 class F2Fpay extends PaymentService implements Gateway
 {
-    private static array $aliConfig;
+    private static AlipayF2F $aliClient;
 
     public function __construct()
     {
-        self::$aliConfig = [
+        self::$aliClient = new AlipayF2F([
             'app_id' => sysConfig('f2fpay_app_id'),
             'ali_public_key' => sysConfig('f2fpay_public_key'),
             'rsa_private_key' => sysConfig('f2fpay_private_key'),
             'notify_url' => route('payment.notify', ['method' => 'f2fpay']),
-        ];
+        ]);
     }
 
     public function purchase(Request $request): JsonResponse
@@ -37,8 +38,7 @@ class F2Fpay extends PaymentService implements Gateway
         ];
 
         try {
-            $gateWay = new AlipayF2F(self::$aliConfig);
-            $result = $gateWay->qrCharge($data);
+            $result = self::$aliClient->qrCharge($data);
             $payment->update(['qr_code' => 1, 'url' => $result['qr_code']]);
         } catch (Exception $e) {
             Log::alert('【支付宝当面付】支付错误: '.$e->getMessage());
@@ -52,26 +52,47 @@ class F2Fpay extends PaymentService implements Gateway
     public function notify(Request $request): void
     {
         try {
-            $result = (new AlipayF2F(self::$aliConfig))->tradeQuery($request->only('out_trade_no', 'trade_no'));
-            Log::notice('【支付宝当面付】回调验证验证:'.var_export($result, true));
+            if (sysConfig('f2fpay_app_id') === $request->input('app_id') && self::$aliClient->validate_notification_sign($request->except('method'), $request->input('sign'))) {
+                $payment = Payment::whereTradeNo($request->input('out_trade_no'))->with('order')->first();
+                if ($payment && abs($payment->amount - $request->input('total_amount')) < 0.01 && in_array($request->input('trade_status'), ['TRADE_FINISHED', 'TRADE_SUCCESS']) && $this->paymentReceived($request->input('out_trade_no'))) {
+                    exit('success');
+                }
+            }
+            Log::error('【支付宝当面付】异步验证失败,尝试订单查询');
+            if ($this->capture($request->input('out_trade_no'), $request->input('trade_no'))) {
+                exit('success');
+            }
+
+            Log::notice('【支付宝当面付】异步验证失败:'.var_export($request->all(), true));
         } catch (Exception $e) {
             Log::alert('【支付宝当面付】回调信息错误: '.$e->getMessage());
             exit;
         }
 
+        // 返回验证结果
+        exit('fail');
+    }
+
+    public function capture(string $trade_no = null, string $ali_trade_no = null): bool
+    {
+        $result = self::$aliClient->tradeQuery(array_filter([
+            'out_trade_no' => $trade_no,
+            'trade_no' => $ali_trade_no,
+        ]));
+
         if ($result['code'] === '10000' && $result['msg'] === 'Success') {
-            if ($request->has('out_trade_no') && in_array($request->input('trade_status'), ['TRADE_FINISHED', 'TRADE_SUCCESS'])) {
-                if ($this->paymentReceived($request->input('out_trade_no'))) {
-                    exit('success');
+            if ($result['out_trade_no'] && in_array($result['trade_status'], ['TRADE_FINISHED', 'TRADE_SUCCESS'])) {
+                if ($this->paymentReceived($result['out_trade_no'])) {
+                    return true;
                 }
-            } else {
-                Log::error('【支付宝当面付】交易失败:'.var_export($request->all(), true));
+                Log::error('【支付宝当面付】收单交易订单结算失败:'.var_export($result, true));
+
+                return false;
             }
         } else {
-            Log::error('【支付宝当面付】验证失败:'.var_export($result, true));
+            Log::error('【支付宝当面付】收单交易查询失败:'.var_export($result, true));
         }
 
-        // 返回验证结果
-        exit('fail');
+        return false;
     }
 }