Kaynağa Gözat

feat(lakala): 实现拉卡拉支付通知与退款功能

- 新增 NotifyService 处理拉卡拉支付通知逻辑
- 实现交易退款接口,支持聚合扫码退款
- 完善支付通知校验与状态更新机制
- 添加退款测试用例验证功能正确性
- 优化支付查询逻辑,记录支付结果数据
- 调整控制器依赖注入,去除冗余参数传递
runphp 4 ay önce
ebeveyn
işleme
670cd3937a

+ 7 - 3
src/Controller/Api/IndexController.php

@@ -3,13 +3,17 @@ declare(strict_types=1);
 namespace SixShop\Lakala\Controller\Api;
 
 use SixShop\Core\Request;
+use SixShop\Lakala\Service\NotifyService;
 use think\facade\Log;
+use think\response\Json;
+use function SixShop\Core\error_response;
+use function SixShop\Core\success_response;
 
 class IndexController
 {
-    public function notify(Request $request)
+    public function notify(NotifyService $notifyService): Json
     {
-        // todo
-        Log::debug('lakala notify'.json_encode($request->param()));
+        $notifyService->notify();
+        return json(['code' => 'SUCCESS', 'message' => '执行成功']);
     }
 }

+ 1 - 0
src/PaymentProvider.php

@@ -87,6 +87,7 @@ class PaymentProvider implements PaymentProviderInterface
             };
             if ($payment->status !== PaymentStatusEnum::PENDING) {
                 $payment->status_desc = $response->trade_state_desc;
+                $payment->payment_result = $response;
                 if ($payment->status === PaymentStatusEnum::SUCCESS) {
                     $payment->payment_time = time();
                     $payment->save();

+ 41 - 0
src/Service/NotifyService.php

@@ -0,0 +1,41 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\Lakala\Service;
+
+use SixShop\Lakala\Config;
+use SixShop\Lakala\OpenAPISDK\V3\Api\LakalaNotifyApi;
+use SixShop\Lakala\OpenAPISDK\V3\Model\ModelTradeNotify;
+use SixShop\Lakala\PaymentProvider;
+use SixShop\Payment\Enum\PaymentStatusEnum;
+use SixShop\Payment\Model\ExtensionPaymentModel;
+use think\facade\Log;
+use function SixShop\Core\error_response;
+use function SixShop\Core\throw_logic_exception;
+
+class NotifyService
+{
+    public function __construct(private Config $config, private PaymentProvider $paymentProvider)
+    {
+        $this->notifyApi = new LakalaNotifyApi($this->config->getV3Config());
+    }
+
+    public function notify(): void
+    {
+        $notify = $this->notifyApi->notiApi();
+        Log::debug('lakala notify', json_encode($notify));
+        $inBody = $notify->getOriginalText();
+        if (!json_validate($inBody)) {
+            throw_logic_exception('lakala notify json error');
+        }
+        $bodyArr = json_decode($inBody, true);
+        $payment = ExtensionPaymentModel::where(['transaction_id' => $bodyArr['trade_no']])->findOrEmpty();
+        if ($payment->isEmpty()) {
+            throw_logic_exception('lakala notify transaction_id error');
+        }
+        if ($payment->status != PaymentStatusEnum::PENDING) {
+            throw_logic_exception('lakala notify transaction_id status error');
+        }
+        $this->paymentProvider->query($payment->id);
+        Log::debug('lakala notified success {transaction_id}', ['transaction_id' => $payment->transaction_id]);
+    }
+}

+ 51 - 0
src/Service/TransactionService.php

@@ -2,9 +2,11 @@
 declare(strict_types=1);
 namespace SixShop\Lakala\Service;
 
+use SixShop\Lakala\OpenAPISDK\V3\Api\LakalaApi;
 use SixShop\Lakala\OpenAPISDK\V3\Api\QueryTradequeryApi;
 use SixShop\Lakala\OpenAPISDK\V3\Api\TransPreorderApi;
 use SixShop\Lakala\OpenAPISDK\V3\Configuration;
+use SixShop\Lakala\OpenAPISDK\V3\Model\ModelRequest;
 use SixShop\Lakala\OpenAPISDK\V3\Model\QueryTradequeryRequest;
 use SixShop\Lakala\OpenAPISDK\V3\Model\TradeAccBusiFields;
 use SixShop\Lakala\OpenAPISDK\V3\Model\TradePreorderWechaAccBusiFields;
@@ -23,10 +25,13 @@ class TransactionService
     private TransPreorderApi $transPreorderApi;
 
     private QueryTradequeryApi $queryTradequeryApi;
+
+    private LakalaApi $lakalaApi;
     public function __construct(private Config $config)
     {
         $this->transPreorderApi = new TransPreorderApi($config->getV3Config());
         $this->queryTradequeryApi = new QueryTradequeryApi($config->getV3Config());
+        $this->lakalaApi = new LakalaApi($config->getV3Config());
     }
 
     /**
@@ -119,4 +124,50 @@ class TransactionService
             );
         }
     }
+
+    /**
+     * 聚合扫码-交易退款
+     *
+     * @param string $refundTradeNo 退款订单号
+     * @param string $refundAmount 退款金额
+     * @param LocationInfo $locationInfo 地理位置信息
+     * @param string $originTradeNo 原交易拉卡拉交易订单号
+     * @param string $originOutTradeNo 原商户订单号
+     * @param string $refundReason 退款原因
+     * @link https://o.lakala.com/#/home/document/detail?id=892
+     */
+    public function refund(
+        string $refundTradeNo,
+        float $refundAmount,
+        LocationInfo $locationInfo,
+        string $originTradeNo = '',
+        string $originOutTradeNo = '',
+        string $refundReason = '',
+    )
+    {
+        $request = new ModelRequest();
+        $reqData = [
+            'merchant_no' => $this->config->merchant_no,
+            'term_no' => $this->config->term_no,
+            'out_trade_no' => $refundTradeNo,
+            'refund_amount' => round($refundAmount*100),
+            'refund_reason' => $refundReason,
+            'origin_out_trade_no' => $originOutTradeNo,
+            'origin_trade_no' => $originTradeNo,
+            'location_info' => $locationInfo->jsonSerialize(),
+            'notify_url' => $this->config->notify_url,
+            'refund_amt_sts' => '00'
+        ];
+        $request->setReqData($reqData);
+        $response = $this->lakalaApi->tradeApi('/api/v3/rfd/refund_front/refund', $request);
+        if ($response->getCode() == '000000') {
+            return $response->getRespData();
+        } else {
+            throw_logic_exception(
+                msg:$response->getMsg(),
+                status: $response->getCode(),
+                data: $response->getRespData(),
+            );
+        }
+    }
 }

+ 16 - 2
tests/Service/TransactionServiceTest.php

@@ -13,6 +13,7 @@ use SixShop\Payment\Enum\NumberBizEnum;
 class TransactionServiceTest extends TestCase
 {
     private TransactionService $transactionService;
+
     protected function setUp(): void
     {
         $this->transactionService = app(TransactionService::class);
@@ -37,7 +38,7 @@ class TransactionServiceTest extends TestCase
         $goodsDetail->setQuantity(1);
         $goodsDetail->setPrice($totalAmount);
         $detail->setGoodsDetail([$goodsDetail]);
-       // $accBusiFields->setDetail($detail);
+        // $accBusiFields->setDetail($detail);
         $response = $this->transactionService->preOrder(
             outTradeNo: generate_number(NumberBizEnum::ORDER_PAY),
             totalAmount: $totalAmount,
@@ -54,9 +55,22 @@ class TransactionServiceTest extends TestCase
     public function queryTrade()
     {
         $response = $this->transactionService->queryTrade(
-           // tradeNo: '20251119110113130266202252424725',
+        // tradeNo: '20251119110113130266202252424725',
             outTradeNo: '20251119145747020294',
         );
         dump($response);
     }
+
+    #[Test]
+    public function refund()
+    {
+           $response = $this->transactionService->refund(
+               refundTradeNo: '20251119215114025260',
+               refundAmount: .01,
+               locationInfo: new LocationInfo(requestIP: '183.214.109.50'),
+               originOutTradeNo: '20251119215114025260',
+               refundReason: '测试退款',
+           );
+           dump($response);
+    }
 }