Sfoglia il codice sorgente

feat(wechatpay): 实现微信转账场景报备及处理逻辑

- 新增转账场景ID、用户收款感知、受理时间等字段到转账账单表
- 添加转账场景报备事件 TrasferSceneReportEvent 及相关处理逻辑
- 实现 approveTransferBill 方法以支持转账单审批与报备信息处理
- 引入 ApiTrait 提供 tranceferBill 方法用于发起转账请求
- 增加对转账场景报备信息和 package 信息的存储支持
runphp 4 mesi fa
parent
commit
c50e6b7478

+ 30 - 0
database/migrations/20251104122614_wechat_transfer_bill_package_info.php

@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+use Phinx\Migration\AbstractMigration;
+
+final class WechatTransferBillPackageInfo extends AbstractMigration
+{
+    /**
+     * Change Method.
+     *
+     * Write your reversible migrations using this method.
+     *
+     * More information on writing migrations is available here:
+     * https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
+     *
+     * Remember to call "create()" or "update()" and NOT "save()" when working
+     * with the Table class.
+     */
+    public function change(): void
+    {
+        $table = $this->table('wechatpay_transfer_bill');
+        $table->addColumn('transfer_scene_id', 'string', ['limit' => 64, 'comment' => '转账场景ID', 'after' => 'user_name'])
+            ->addColumn('user_recv_perception', 'string', ['limit' => 255, 'comment' => '用户收款感知', 'after' => 'transfer_scene_id'])
+            ->addColumn('accept_time', 'timestamp', ['comment' => '受理时间', 'after' => 'user_recv_perception'])
+            ->addColumn('transfer_scene_report_infos', 'json', ['comment' => '转账场景ID', 'after' => 'accept_time'])
+            ->addColumn('package_info', 'json', ['comment' => '跳转领取页面的package信息', 'after' => 'transfer_scene_report_infos'])
+            ->update();
+    }
+}

+ 35 - 2
src/Entity/WechatpayTransferBillEntity.php

@@ -6,8 +6,11 @@ namespace SixShop\WechatPay\Entity;
 use SixShop\Core\Entity\BaseEntity;
 use SixShop\WechatPay\Enum\TransferBillStatusEnum;
 use SixShop\WechatPay\Event\TransferBillFailedEvent;
+use SixShop\WechatPay\Event\TrasferSceneReportEvent;
+use SixShop\WechatPay\Trait\ApiTrait;
 use think\facade\Db;
 use think\facade\Event;
+use think\Model;
 use think\Paginator;
 use function SixShop\Core\throw_logic_exception;
 
@@ -16,6 +19,7 @@ use function SixShop\Core\throw_logic_exception;
  */
 class WechatpayTransferBillEntity extends BaseEntity
 {
+    use  ApiTrait;
     public function getTransferBillList(array $params, array $page): Paginator
     {
         $query = $this->withSearch(['state', 'out_bill_no'], $params);
@@ -32,12 +36,41 @@ class WechatpayTransferBillEntity extends BaseEntity
 
     public function refreshTransferBill(int $id): array
     {
-        // todo: 刷新转账单状态
+        // TODO 刷新转账单状态
         return $this->find($id)->toArray();
     }
 
-    public function approveTransferBill(int $id): array
+    public function approveTransferBill(int $id): self
     {
+        $transferBill = $this->findOrEmpty($id);
+        if ($transferBill->isEmpty()) {
+            throw new Exception('提现单不存在');
+        }
+        if ($transferBill->state !== TransferBillStatusEnum::APPLYING) {
+            throw_logic_exception('非法操作');
+        }
+        // 转账场景报备信息
+        $event = new TrasferSceneReportEvent($transferBill);
+        Event::trigger($event);
+        $result = $this->tranceferBill(
+            outBillNo: $transferBill->out_bill_no,
+            transferSceneId:$event->getTransferSceneId(),
+            openid: $transferBill->openid,
+            transferAmount: $transferBill->transfer_amount,
+            transferRemark: $transferBill->transfer_remark,
+            transferScenceReportInfos: $event->getTransferSceneReportInfos(),
+            userName: $transferBill->user_name,
+            userRecvPerception: $event->getUserRecvPerception(),
+        );
+        $transferBill->transfer_scene_id = $event->getTransferSceneId();
+        $transferBill->user_recv_perception = $event->getUserRecvPerception();
+        $transferBill->transfer_bill_no = $result->transfer_bill_no;
+        $datetime = new \DateTime($result->create_time);
+        $transferBill->accept_time = $datetime->format('Y-m-d H:i:s');
+        $transferBill->state = $result->state;
+        $transferBill->package_info = $result->package_info;
+        $transferBill->save();
+        return $transferBill;
     }
 
     public function rejectTransferBill(int $id, string $failReason): self

+ 59 - 0
src/Event/TrasferSceneReportEvent.php

@@ -0,0 +1,59 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\WechatPay\Event;
+
+use SixShop\WechatPay\Entity\WechatpayTransferBillEntity;
+
+/**
+ * 转账场景报备信息
+ */
+class TrasferSceneReportEvent
+{
+    private string $transferSceneId = '';
+
+    private string $userRecvPerception = '';
+
+    private array $transferSceneReportInfos = [];
+    public function __construct(private WechatpayTransferBillEntity $entity)
+    {
+
+    }
+
+    public function getEntity(): WechatpayTransferBillEntity
+    {
+        return $this->entity;
+    }
+
+    public function getTransferSceneId(): string
+    {
+        return $this->transferSceneId;
+    }
+
+    public function setTransferSceneId(string $transferSceneId): self
+    {
+        $this->transferSceneId = $transferSceneId;
+        return $this;
+    }
+
+    public function getUserRecvPerception(): string
+    {
+        return $this->userRecvPerception;
+    }
+
+    public function setUserRecvPerception(string $userRecvPerception): self
+    {
+        $this->userRecvPerception = $userRecvPerception;
+        return $this;
+    }
+
+    public function getTransferSceneReportInfos(): array
+    {
+        return $this->transferSceneReportInfos;
+    }
+
+    public function setTransferSceneReportInfos(array $transferSceneReportInfos): self
+    {
+        $this->transferSceneReportInfos = $transferSceneReportInfos;
+        return $this;
+    }
+}

+ 4 - 0
src/Service/WechatpayTransferBillService.php

@@ -2,10 +2,14 @@
 declare(strict_types=1);
 namespace SixShop\WechatPay\Service;
 
+use GuzzleHttp\Promise\PromiseInterface;
 use SixShop\Wechat\Facade\WechatUser;
 use SixShop\WechatPay\Config;
 use SixShop\WechatPay\Enum\TransferBillStatusEnum;
 use SixShop\WechatPay\Model\WechatpayTransferBillModel;
+use SixShop\WechatPay\Trait\HandleAsyncRequestTrait;
+use WeChatPay\BuilderChainable;
+use WeChatPay\Crypto\Rsa;
 
 class WechatpayTransferBillService
 {

+ 51 - 0
src/Trait/ApiTrait.php

@@ -6,6 +6,7 @@ namespace SixShop\WechatPay\Trait;
 use GuzzleHttp\Promise\PromiseInterface;
 use SixShop\WechatPay\Config;
 use WeChatPay\BuilderChainable;
+use WeChatPay\Crypto\Rsa;
 use function SixShop\Core\throw_logic_exception;
 
 trait ApiTrait
@@ -147,4 +148,54 @@ trait ApiTrait
             ]);
         });
     }
+
+    /**
+     * 发起转账
+     */
+    private function tranceferBill(
+        string $outBillNo,
+        string $transferSceneId,
+        String $openid,
+        int $transferAmount,
+        String $transferRemark,
+        array $transferScenceReportInfos,
+        string $userName = '',
+        string $userRecvPerception = ''
+    ): object
+    {
+        // https://pay.weixin.qq.com/doc/v3/merchant/4012716434
+        // 支持商户:【普通商户】
+        // 请求方式:【POST】/v3/fund-app/mch-transfer/transfer-bills
+        // appid
+        // notify_url
+        $data =  [
+            'out_bill_no' => $outBillNo,
+            'transfer_scene_id' => $transferSceneId,
+            'openid' => $openid,
+            'transfer_amount' => $transferAmount,
+            'transfer_remark' => $transferRemark,
+            'transfer_scence_report_infos' => $transferScenceReportInfos,
+        ];
+        if ($userName) {
+            $encryptor = static function(string $msg): string {
+                return Rsa::encrypt($msg, $this->config->public_key);
+            };
+            $data['user_name'] = $encryptor($userName);
+        }
+        if ($userRecvPerception) {
+            $data['user_recv_perception'] = $userRecvPerception;
+        }
+        return $this->handleAsyncRequest(function (
+            BuilderChainable $builder, Config $config, $data
+        ): PromiseInterface {
+            $data['appid'] = $config->appid;
+            $data['notify_url'] = $config->notify_url;
+            return $builder->v3->fundApp->mchTransfer->transferBills->postAsync([
+                'json' => $data,
+                'headers' => [
+                    'Wechatpay-Serial' => $config->public_key_id,
+                ]
+            ]);
+        }, $data);
+    }
 }