Просмотр исходного кода

fix(wechatpay):修复转账单状态刷新逻辑并完善回调处理

- 修复 `refreshTransferBill` 方法返回类型和状态判断逻辑
- 新增对转账完成事件 `MCHTRANSFER.BILL.FINISHED` 的处理支持
- 更正方法名拼写错误 `tranceferBill` 为 `transferBill`
- 标记旧方法 `transferBills` 为废弃,并推荐使用 `transferBill`
- 补充 `queryTransferBill` 查询接口实现及测试用例
- 在转账失败或取消时触发 `TransferBillFailedEvent` 事件
- 完善微信支付通知回调中对不同类型事件的区分处理
runphp 4 месяцев назад
Родитель
Сommit
13e1a07e97
4 измененных файлов с 82 добавлено и 34 удалено
  1. 27 4
      src/Entity/WechatpayTransferBillEntity.php
  2. 36 29
      src/PaymentProvider.php
  3. 14 1
      src/Trait/ApiTrait.php
  4. 5 0
      test/WechatPayBuilderTest.php

+ 27 - 4
src/Entity/WechatpayTransferBillEntity.php

@@ -37,10 +37,33 @@ class WechatpayTransferBillEntity extends BaseEntity
             ->toArray();
             ->toArray();
     }
     }
 
 
-    public function refreshTransferBill(int $id): array
+    public function refreshTransferBill(int $id): self
     {
     {
-        // TODO 刷新转账单状态
-        return $this->find($id)->toArray();
+        $transferBill = $this->findOrEmpty($id);
+        if ($transferBill->isEmpty()) {
+            throw_logic_exception('提现单不存在');
+        }
+        $result = match ($transferBill->state) {
+            TransferBillStatusEnum::SUCCESS,
+            TransferBillStatusEnum::FAIL,
+            TransferBillStatusEnum::CANCELLED => throw_logic_exception('提现单已是最终状态不允许刷新'),
+            TransferBillStatusEnum::APPLYING => throw_logic_exception('提现单申请中不允许刷新'),
+            default => $this->queryTransferBill($transferBill->out_bill_no),
+        };
+        $transferBill->state = TransferBillStatusEnum::from($result->state);
+        $transferBill->fail_reason = match ($state) {
+            TransferBillStatusEnum::FAIL,
+            TransferBillStatusEnum::CANCELLED => $result->fail_reason,
+            default => '',
+        };
+        Db::transaction(function () use ($transferBill) {
+            if ($transferBill->state === TransferBillStatusEnum::FAIL
+                || $transferBill->state === TransferBillStatusEnum::CANCELLED) {
+                Event::trigger(new TransferBillFailedEvent($transferBill->id));
+            }
+            $transferBill->save();
+        });
+        return $transferBill;
     }
     }
 
 
     public function approveTransferBill(int $id): self
     public function approveTransferBill(int $id): self
@@ -55,7 +78,7 @@ class WechatpayTransferBillEntity extends BaseEntity
         // 转账场景报备信息
         // 转账场景报备信息
         $event = new TransferSceneReportEvent($transferBill);
         $event = new TransferSceneReportEvent($transferBill);
         Event::trigger($event);
         Event::trigger($event);
-        $result = $this->tranceferBill(
+        $result = $this->transferBill(
             outBillNo: $transferBill->out_bill_no,
             outBillNo: $transferBill->out_bill_no,
             transferSceneId:$event->getTransferSceneId(),
             transferSceneId:$event->getTransferSceneId(),
             openid: $transferBill->openid,
             openid: $transferBill->openid,

+ 36 - 29
src/PaymentProvider.php

@@ -20,6 +20,7 @@ use SixShop\Payment\Enum\RefundStatusEnum;
 use SixShop\Payment\Event\PaymentSuccessEvent;
 use SixShop\Payment\Event\PaymentSuccessEvent;
 use SixShop\Payment\Event\RefundSuccessEvent;
 use SixShop\Payment\Event\RefundSuccessEvent;
 use SixShop\Wechat\Facade\WechatUser;
 use SixShop\Wechat\Facade\WechatUser;
+use SixShop\WechatPay\Entity\WechatpayTransferBillEntity;
 use SixShop\WechatPay\Job\QueryRefundJob;
 use SixShop\WechatPay\Job\QueryRefundJob;
 use SixShop\WechatPay\Service\NotifyService;
 use SixShop\WechatPay\Service\NotifyService;
 use SixShop\WechatPay\Trait\ApiTrait;
 use SixShop\WechatPay\Trait\ApiTrait;
@@ -43,6 +44,7 @@ class PaymentProvider implements PaymentProviderInterface
     public function __construct(
     public function __construct(
         private readonly ExtensionPaymentEntity $extensionPaymentEntity,
         private readonly ExtensionPaymentEntity $extensionPaymentEntity,
         private readonly ExtensionRefundEntity  $extensionRefundEntity,
         private readonly ExtensionRefundEntity  $extensionRefundEntity,
+        private readonly WechatpayTransferBillEntity $wechatpayTransferBillEntity,
         private readonly NotifyService $notifyService,
         private readonly NotifyService $notifyService,
     )
     )
     {
     {
@@ -107,36 +109,41 @@ class PaymentProvider implements PaymentProviderInterface
     {
     {
         $inBody = $request['inBody'];
         $inBody = $request['inBody'];
         $data = $this->notifyService->transactionSuccess($request['headers'], json_encode($request['inBody']));
         $data = $this->notifyService->transactionSuccess($request['headers'], json_encode($request['inBody']));
-        if ($inBody['event_type'] !== 'TRANSACTION.SUCCESS') {
-            Log::warning('Not implemented: ' . $inBody['event_type']. ' ' . json_encode($data));
-            throw_logic_exception('Not implemented: ' . $inBody['event_type']);
-        }
-        Log::debug(__METHOD__ . json_encode($data));
-        $payment = $this->extensionPaymentEntity->where([
-            'out_trade_no' => $data['out_trade_no'],
-        ])->findOrEmpty();
-        if ($payment->isEmpty()) {
-            throw new \RuntimeException('订单不存在或已结束');
+        if ($inBody['event_type'] == 'TRANSACTION.SUCCESS') {
+            // 交易成功
+            Log::debug(__METHOD__ . json_encode($data));
+            $payment = $this->extensionPaymentEntity->where([
+                'out_trade_no' => $data['out_trade_no'],
+            ])->findOrEmpty();
+            if ($payment->isEmpty()) {
+                throw new \RuntimeException('订单不存在或已结束');
+            }
+            // 验签有问题,暂时叫个支付查询处理 todo 待完善
+            $queryResult = $this->query($payment['id']);
+            return new PaymentNotifyResult(
+                orderNo: $queryResult->orderNo,
+                transactionId: $data['transaction_id'],
+                amount: $queryResult->amount,
+                status: $queryResult->status,
+                raw: $data
+            );
+        } else if ($inBody['event_type'] == 'MCHTRANSFER.BILL.FINISHED') {
+            // 转账完成
+            $transferBill = $this->wechatpayTransferBillEntity->where('out_bill_no', $data['out_bill_no'])->findOrEmpty();
+            if ($transferBill->isEmpty()) {
+                throw new \RuntimeException('转账单不存在');
+            }
+            $this->wechatpayTransferBillEntity->refreshTransferBill($transferBill->id);
+            return new PaymentNotifyResult(
+                orderNo: $transferBill['out_bill_no'],
+                transactionId: $transferBill['transfer_bill_no'],
+                amount: round($transferBill['transfer_amount'] / 100, 2),
+                status: PaymentStatusEnum::SUCCESS,
+                raw: $data
+            );
         }
         }
-        // 验签有问题,暂时叫个支付查询处理 todo 待完善
-        $queryResult = $this->query($payment['id']);
-        return new PaymentNotifyResult(
-            orderNo: $queryResult->orderNo,
-            transactionId: $data['transaction_id'],
-            amount: $queryResult->amount,
-            status: $queryResult->status,
-            raw: $data
-        );
-        // data 数据
-        //{
-        //"mchid":"1730970813",
-        //"appid":"wx2c4a7d3d5d5aef0b",
-        //"out_trade_no":"20251030094337050576",
-        //"transaction_id":"4200002881202510302655663850","trade_type":"JSAPI",
-        //"trade_state":"SUCCESS","trade_state_desc":"\u652f\u4ed8\u6210\u529f","bank_type":"OTHERS","attach":"actor",
-        //"success_time":"2025-10-30T09:43:50+08:00","payer":{"openid":"oPuRO19YQD4Tc2w2Vak4U83-Liys"},"amount":{"total":100,"payer_total":100,"currency":"CNY","payer_currency":"CNY"}
-        //}
-
+        Log::warning('Not implemented: ' . $inBody['event_type']. ' ' . json_encode($data));
+        throw_logic_exception('Not implemented: ' . $inBody['event_type']);
     }
     }
 
 
     public function query(int $recordID): PaymentQueryResult
     public function query(int $recordID): PaymentQueryResult

+ 14 - 1
src/Trait/ApiTrait.php

@@ -119,6 +119,8 @@ trait ApiTrait
      * @param string $orderSn 订单号
      * @param string $orderSn 订单号
      * @param string $amount 转账金额
      * @param string $amount 转账金额
      * @return mixed
      * @return mixed
+     * @deprecated 这个只能支持佣金报酬,请用transferBill
+     * @see transferBill
      */
      */
     private function transferBills(int $openid, string $orderSn, string $amount)
     private function transferBills(int $openid, string $orderSn, string $amount)
     {
     {
@@ -152,7 +154,7 @@ trait ApiTrait
     /**
     /**
      * 发起转账
      * 发起转账
      */
      */
-    private function tranceferBill(
+    private function transferBill(
         string $outBillNo,
         string $outBillNo,
         string $transferSceneId,
         string $transferSceneId,
         String $openid,
         String $openid,
@@ -198,4 +200,15 @@ trait ApiTrait
             ]);
             ]);
         }, $data);
         }, $data);
     }
     }
+
+    private function queryTransferBill(string $outBillNo)
+    {
+        // https://pay.weixin.qq.com/doc/v3/merchant/4012716437
+        //【GET】/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}
+        return $this->handleAsyncRequest(function (BuilderChainable $builder, Config $config, string $outBillNo): PromiseInterface {
+            return $builder->v3->fundApp->mchTransfer->transferBills->outBillNo->_out_bill_no_->getAsync([
+                'out_bill_no' => $outBillNo,
+            ]);
+        }, $outBillNo);
+    }
 }
 }

+ 5 - 0
test/WechatPayBuilderTest.php

@@ -46,4 +46,9 @@ class WechatPayBuilderTest extends TestCase
         $result = $this->queryRefund('20250909135117037302');
         $result = $this->queryRefund('20250909135117037302');
         dump($result);
         dump($result);
     }
     }
+    public function testQueryTransferBill()
+    {
+        $result = $this->queryTransferBill('20251105151948049869');
+        dump($result);
+    }
 }
 }