Browse Source

feat(wechatpay): 实现微信支付回调通知处理

- 新增 NotifyService服务类处理微信支付回调验证与解密
- 修改 Notify.php 控制器传递请求头和请求体到支付提供者
- 在 PaymentProvider 中注入 NotifyService 并实现 notify 方法
- 添加日志记录支付回调数据
- 增加对 TRANSACTION.SUCCESS 事件类型的处理逻辑
runphp 5 months ago
parent
commit
6c9179169a
2 changed files with 74 additions and 4 deletions
  1. 17 4
      src/PaymentProvider.php
  2. 57 0
      src/Service/NotifyService.php

+ 17 - 4
src/PaymentProvider.php

@@ -20,14 +20,14 @@ use SixShop\Payment\Enum\RefundStatusEnum;
 use SixShop\Payment\Event\PaymentSuccessEvent;
 use SixShop\Payment\Event\RefundSuccessEvent;
 use SixShop\Wechat\Facade\WechatUser;
-use SixShop\Wechat\Service\MiniApp;
 use SixShop\WechatPay\Job\QueryRefundJob;
+use SixShop\WechatPay\Service\NotifyService;
 use SixShop\WechatPay\Trait\ApiTrait;
 use SixShop\WechatPay\Trait\MiniAppTrait;
 use SixShop\WechatPay\Trait\PaymentParamsTrait;
-use SixShop\WechatPay\Trait\UploadShippingInfoTrait;
 use think\facade\Db;
 use think\facade\Event;
+use think\facade\Log;
 use function SixShop\Core\throw_logic_exception;
 
 class PaymentProvider implements PaymentProviderInterface
@@ -42,7 +42,8 @@ class PaymentProvider implements PaymentProviderInterface
 
     public function __construct(
         private readonly ExtensionPaymentEntity $extensionPaymentEntity,
-        private readonly ExtensionRefundEntity  $extensionRefundEntity
+        private readonly ExtensionRefundEntity  $extensionRefundEntity,
+        private readonly NotifyService $notifyService,
     )
     {
     }
@@ -95,9 +96,21 @@ class PaymentProvider implements PaymentProviderInterface
         return new PaymentResponse(orderNo: $payment->out_trade_no, type: self::PAYMENT_TYPE, raw: $payment->toArray());
     }
 
+    /**
+     * 支付成功通知
+     *
+     * @param array{headers: array<string, string>, inBody: array<string, mixed>} $request
+     * @return PaymentNotifyResult
+     * @throws \Exception]
+     */
     public function notify(array $request): PaymentNotifyResult
     {
-        throw new \Exception('Not implemented');
+        if ($request['event_type'] === 'TRANSACTION.SUCCESS') {
+            $data = $this->notifyService->transactionSuccess($request['headers'], $request['inBody']);
+            Log::info(__METHOD__ . json_encode($data));
+            // todo
+        }
+        throw new \Exception('Not implemented');        
     }
 
     public function query(int $recordID): PaymentQueryResult

+ 57 - 0
src/Service/NotifyService.php

@@ -0,0 +1,57 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\WechatPay\Service;
+
+use SixShop\WechatPay\Config;
+use WeChatPay\Crypto\AesGcm;
+use WeChatPay\Crypto\Rsa;
+use WeChatPay\Formatter;
+
+class NotifyService
+{
+    public function __construct(private Config $config)
+    {
+    }
+
+    /**
+     * 微信支付成功回调
+     * @param array $headers 请求头
+     * @param array $inBody 请求体
+     */
+    public function transactionSuccess(array $headers, array $inBody):array
+    {
+        $signature = $headers['wechatpay-signature'] ?? '';// 请根据实际情况获取
+        $timestamp = $headers['wechatpay-timestamp'] ?? '';// 请根据实际情况获取
+        $serial = $headers['wechatpay-serial'] ?? ''; // 请根据实际情况获取
+        $nonce = $headers['wechatpay-nonce'] ?? ''; // 请根据实际情况获取
+        
+        $apiv3Key = $this->config->api_v3_key;
+        $platformPublicKeyInstance = $this->config->public_key;
+
+        $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$timestamp);
+        if (!$timeOffsetStatus) {
+            throw new \RuntimeException('The timestamp is out of range.');
+        }
+        $verifiedStatus = Rsa::verify(
+        // 构造验签名串
+            Formatter::joinedByLineFeed($timestamp, $nonce, $body),
+            $signature,
+            $platformPublicKeyInstance
+        );
+        if (!$verifiedStatus) {
+            throw new \RuntimeException('The signature is invalid.');
+        }
+        // 转换通知的JSON文本消息为PHP Array数组
+        $inBodyArray = (array)json_decode($body, true);
+        // 使用PHP7的数据解构语法,从Array中解构并赋值变量
+        ['resource' => [
+            'ciphertext' => $ciphertext,
+            'nonce' => $nonce,
+            'associated_data' => $aad
+        ]] = $inBodyArray;
+        // 加密文本消息解密
+        $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
+        return json_decode($inBodyResource);
+        return [];
+    }
+}