浏览代码

feat(lakala): 新增分账接收方功能及相关配置

- 数据库迁移文件中修改 `acct_open_bank_code` 和 `acct_clear_bank_code` 字段为可空
- 新增 `fail_reason` 和 `delete_time` 字段用于记录失败原因与软删除时间- 添加索引 `idx_user_id` 和唯一索引 `idx_order_no` 提升查询性能
- 新增 API 路由 `/api/lakala/profit_share_receiver` 并应用认证中间件
- 创建控制器 `ProfitShareReceiverController` 处理保存分账接收方请求
- 实现实体类方法 `saveReceiver` 完成数据校验、事务处理及开户行信息获取- 引入门面类 `MMSService` 封装拉卡拉接口调用逻辑
- 更新模型配置支持自动生成 `order_no`且设为只读字段
- 在服务层实现卡BIN查询并增强错误处理机制
- 配置文件中新增环境变量 `org_code` 支持动态设置机构代码
- 单元测试覆盖分账接收方申请与详情查询场景,并增加 MMSService 测试用例- 前端配置表单添加“机构代码”输入项以适配新业务需求
runphp 4 月之前
父节点
当前提交
d56b737717

+ 16 - 1
config.php

@@ -215,7 +215,22 @@ return json_decode(<<<'JSON'
     "_fc_drag_tag": "input",
     "display": true,
     "hidden": false
-  }
+  },
+   {
+   "type": "input",
+    "title": "机构代码",
+    "field": "org_code",
+    "value":"1",
+    "props": {
+      "placeholder": "请输入机构代码"
+    },
+    "$required": true,
+    "_fc_id": "id_F0j5md1fz7stawc",
+    "name": "ref_Ffipmd1fz7staxc",
+    "_fc_drag_tag": "input",
+    "display": true,
+    "hidden": false
+   }
     ],
     "_fc_drag_tag": "elCard",
     "_fc_id": "id_Fpcxmh2zkilk14fc",

+ 6 - 2
database/migrations/20251109121623_profit_share_receiver.php

@@ -30,15 +30,19 @@ final class ProfitShareReceiver extends AbstractMigration
             ->addColumn('acct_type_code', 'string', ['null' => false, 'limit' => 32, 'default' => '58', 'comment' => '收款账户账户类型(57:对公 58:对私)'])
             ->addColumn('acct_certificate_type', 'string', ['null' => false, 'limit' => 32, 'default' => '17', 'comment' => '收款账户证件类型 17 身份证,18 护照,19 港澳居民来往内地通行证 20 台湾居民来往内地通行证'])
             ->addColumn('acct_certificate_no', 'string', ['null' => false, 'limit' => 32, 'comment' => '收款账户证件号'])
-            ->addColumn('acct_open_bank_code', 'string', ['null' => false, 'limit' => 32, 'comment' => '收款账户开户行号'])
+            ->addColumn('acct_open_bank_code', 'string', ['null' => true, 'limit' => 32, 'comment' => '收款账户开户行号'])
             ->addColumn('acct_open_bank_name', 'string', ['null' => false, 'limit' => 64, 'comment' => '收款账户开户行名称'])
-            ->addColumn('acct_clear_bank_code', 'string', ['null' => false, 'limit' => 32, 'comment' => '收款账户清算行行号'])
+            ->addColumn('acct_clear_bank_code', 'string', ['null' => true, 'limit' => 32, 'comment' => '收款账户清算行行号'])
             ->addColumn('settle_type', 'string', ['null' => false, 'limit' => 32, 'default' => '01', 'comment' => '提款类型 01:主动提款 03:交易自动结算'])
             ->addColumn('org_id', 'string', ['null' => true, 'limit' => 32,  'comment' => '接收方所属机构'])
             ->addColumn('org_name', 'string', ['null' => true, 'limit' => 32, 'comment' => '接收方所属机构名称'])
             ->addColumn('receiver_no', 'string', ['null' => true, 'limit' => 32, 'comment' => '接收方编号'])
             ->addColumn('status', 'integer', ['signed' => false, 'default' => 1, 'comment' => '状态 1: 待审核 2:提交中 3: 验证通过 4: 验证失败 5:绑定中 6: 绑定成功 7: 绑定失败'])
+            ->addColumn('fail_reason', 'string', ['null' => true, 'limit' => 255, 'comment' => '失败原因'])
             ->addTimestamps('create_time', 'update_time')
+            ->addColumn('delete_time', 'timestamp', ['null' => true, 'comment' => '删除时间'])
+            ->addIndex('user_id', ['name' => 'idx_user_id'])
+            ->addIndex('order_no', ['name' => 'idx_order_no', 'unique' => true])
             ->create();
     }
 }

+ 8 - 0
route/api.php

@@ -2,4 +2,12 @@
 declare(strict_types=1);
 
 use think\facade\Route;
+use SixShop\Lakala\Controller\Api\ProfitShareReceiverController;
+// API路由
+// 路由前缀: /api/lakala
+//
+// 如果需要登录请添加认证中间件auth
+// ->middleware(['auth'])
 
+Route::resource('profit_share_receiver', ProfitShareReceiverController::class)
+    ->middleware(['auth']);

+ 2 - 0
src/Config.php

@@ -12,6 +12,8 @@ use SixShop\System\Trait\ConfigTrait;
  * @property string $term_no 终端号
  * @property string $notify_url 商户通知地址
  * @property string $complete_notify_url 收货确认通知地址
+ * @property string $environment 环境
+ * @property string $org_code 机构代码
  */
 class Config
 {

+ 19 - 0
src/Controller/Api/ProfitShareReceiverController.php

@@ -0,0 +1,19 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\Lakala\Controller\Api;
+
+use SixShop\Core\Request;
+use SixShop\Lakala\Config;
+use SixShop\Lakala\Entity\ProfitShareReceiverEntity;
+use function SixShop\Core\success_response;
+
+class ProfitShareReceiverController
+{
+    public function save(Request $request, ProfitShareReceiverEntity $entity, Config $config)
+    {
+        $data = $request->post();
+        $data[ 'user_id'] = $request->userID;
+        $data['org_code'] = $config->org_code;
+        return success_response($entity->saveReceiver($data));
+    }
+}

+ 23 - 0
src/Entity/ProfitShareReceiverEntity.php

@@ -1,14 +1,37 @@
 <?php
 declare(strict_types=1);
+
 namespace SixShop\Lakala\Entity;
 
 use SixShop\Core\Entity\BaseEntity;
+use SixShop\Lakala\Config;
+use SixShop\Lakala\Facade\MMSService;
 use SixShop\Lakala\Model\ProfitShareReceiverModel;
+use think\facade\Db;
+use think\Model;
+use function SixShop\Core\throw_logic_exception;
 
 /**
  * @mixin ProfitShareReceiverModel
  */
 class ProfitShareReceiverEntity extends BaseEntity
 {
+    public function saveReceiver(array $data): self
+    {
+        /* @var self $entity */
+        $entity = $this->where(['user_id' => $data['user_id']])->findOrEmpty();
+        if (!$entity->isEmpty()) {
+            throw_logic_exception('已存在分账接收方申请记录,请勿重复提交!');
+        }
+        $entity->data($data);
+        Db::transaction(function () use ($entity) {
+            $entity->save();
+            $result = MMSService::cardBin($entity->order_no, $entity->acct_no, $entity->org_code);
+            $entity->acct_open_bank_code = $result->bank_code;
+            $entity->acct_clear_bank_code = $result->clearingBankCode;
+            $entity->save();
+        });
 
+        return $entity;
+    }
 }

+ 16 - 0
src/Facade/MMSService.php

@@ -0,0 +1,16 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\Lakala\Facade;
+
+use think\Facade;
+
+/**
+ * @mixin \SixShop\Lakala\Service\MMSService
+ */
+class MMSService extends Facade
+{
+    protected static function getFacadeClass()
+    {
+        return \SixShop\Lakala\Service\MMSService::class;
+    }
+}

+ 3 - 2
src/Model/ProfitShareReceiverModel.php

@@ -8,14 +8,15 @@ use think\Model;
 
 class ProfitShareReceiverModel extends Model
 {
-
     protected function getOptions(): array
     {
         return [
             'name' => 'profit_share_receiver',
             'type' => [
                 'status' => ReceiverStatusEnum::class
-            ]
+            ],
+            'insert' => ['order_no'],
+            'readonly' => ['user_id', 'order_no'],
         ];
     }
 

+ 14 - 11
src/Service/MMSService.php

@@ -7,6 +7,7 @@ use SixShop\Lakala\Config;
 use SixShop\Lakala\Enum\UploadFileTypeEnum;
 use SixShop\Lakala\OpenAPISDK\V2\Api\V2LakalaApi;
 use SixShop\Lakala\OpenAPISDK\V2\Model\V2ModelRequest;
+use function SixShop\Core\throw_logic_exception;
 
 class MMSService
 {
@@ -24,7 +25,7 @@ class MMSService
      * @param string $cardNo 银行卡号
      * @param string $orgCode 机构代码
      * @param string $version 接口版本号 默认1.0
-     * @return void
+     * @link https://o.lakala.com/#/home/document/detail?id=179
      */
     public function cardBin(string $orderNo, string $cardNo,string $orgCode = '1',  string $version = '1.0')
     {
@@ -36,18 +37,20 @@ class MMSService
             'cardNo' => $cardNo,
         ]);
         $response = $this->v2LakalaApi->tradeApi('/api/v2/mms/openApi/cardBin', $request);
-        if ($response->getRespData()) {
-            print_r($response->getRespData());
+        if ($response->getRetCode() == '000000') {
+            $result = $response->getRespData();
+            if ($result->bankCode && $result->clearingBankCode) {
+                return $result;
+            } else {
+                throw_logic_exception('未获取到开户行信息');
+            }
         } else {
-            print_r($response);
+            throw_logic_exception(
+                msg:$response->getRetMsg(),
+                status: $response->getRetCode(),
+                data: $response->getRespData(),
+            );
         }
-        echo $response->getRetCode();
-
-        # 响应头信息
-        print_r($response->getHeaders());
-
-        # 响应原文
-        echo $response->getOriginalText();
     }
 
     /**

+ 44 - 1
tests/Service/LedgerServiceTest.php

@@ -3,6 +3,7 @@ declare(strict_types=1);
 namespace SixShop\Lakala\Service;
 
 use PHPUnit\Framework\TestCase;
+use SixShop\Payment\Enum\NumberBizEnum;
 
 class LedgerServiceTest extends TestCase
 {
@@ -14,6 +15,48 @@ class LedgerServiceTest extends TestCase
 
     public function testApplyLedgerReceiver():void
     {
-        $this->ledgerService->applyLedgerReceiver();
+        $reqData = [
+            // 14位年月日时(24小时制)分秒+8位的随机数(不重复)如:2021020112000012345678
+            'orderNo' => generate_number(NumberBizEnum::WITHDRAWAL, 5),
+            'orgCode' => '1',
+            'receiverName' => '中国',
+            'contactMobile' => '13800138000',
+            'acctNo' => '6222809643395635382',
+            'acctName' => '中国',
+            'acctTypeCode' => '58',
+            'acctCertificateType' => '17',
+            'acctCertificateNo' => '522624201810237551',
+            'acctOpenBankCode' => '01050000', // 收款账户开户行号
+            'acctOpenBankName' => '建设银行',
+            'acctClearBankCode' => '01050000', // 收款账户清算行行号
+        ];
+        $this->ledgerService->applyLedgerReceiver($reqData);
+
+        /**
+         * (
+         * [version] => 1.0
+         * [orderNo] => 2025110820420904005737
+         * [orgCode] => 1
+         * [openAppid] => OP00000003
+         * [orgId] => 1
+         * [orgName] => 总部
+         * [receiverNo] => SR2024021200605
+         * )
+         *
+         * (
+         * [version] => 1.0
+         * [orderNo] => 2025110915361004006104
+         * [orgCode] => 1
+         * [openAppid] => OP00000003
+         * [orgId] => 1
+         * [orgName] => 总部
+         * [receiverNo] => SR2024021200610
+         * )
+         */
+    }
+
+    public function testQueryReceiverDetail():void
+    {
+        $this->ledgerService->queryReceiverDetail('2025110915361004006104', 'SR2024021200610',);
     }
 }

+ 35 - 0
tests/Service/MMSServiceTest.php

@@ -0,0 +1,35 @@
+<?php
+declare(strict_types=1);
+namespace SixShop\Lakala\Service;
+
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\TestCase;
+use SixShop\Lakala\Enum\UploadFileTypeEnum;
+use SixShop\Payment\Enum\NumberBizEnum;
+
+class MMSServiceTest extends TestCase
+{
+    private MMSService $mmsService;
+    protected function setUp(): void
+    {
+        $this->mmsService = app(MMSService::class);
+    }
+
+    #[Test]
+    public function cardBin()
+    {
+        $ret = $this->mmsService->cardBin(generate_number(NumberBizEnum::WITHDRAWAL, 5), '16227003321580073323');
+        dump($ret);
+    }
+
+    #[Test]
+    public function uploadFile()
+    {
+        $this->mmsService->uploadFile(
+            generate_number(NumberBizEnum::WITHDRAWAL, 5),
+            UploadFileTypeEnum::MERCHANT_PHOTO,
+            'pdf',
+            'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png'
+        );
+    }
+}