shichen месяцев назад: 3
Родитель
Сommit
5e9874a1dc

+ 2 - 2
app/common/dao/store/order/StoreCartDao.php

@@ -88,7 +88,7 @@ class StoreCartDao extends BaseDao
88 88
     {
89 89
         return StoreCart::getDb()->where('uid', $uid)->with([
90 90
             'product' => function (Relation $query) use ($cityId) {
91
-            $query->field('pension_is,share_is,product_id,image,store_name,is_show,status,is_del,unit_name,price,mer_status,temp_id,give_coupon_ids,is_gift_bag,is_used,product_type,old_product_id');
91
+            $query->field('pension_is,share_is,product_id,image,store_name,is_show,status,is_del,unit_name,price,mer_status,temp_id,give_coupon_ids,is_gift_bag,is_used,product_type,old_product_id,is_alibaba,spu_id');
92 92
             if ($cityId) {
93 93
                 $query->with(['temp' => ['regionAddress' => function ($query) use ($cityId) {
94 94
                     $query->where(function ($query) use ($cityId) {
@@ -102,7 +102,7 @@ class StoreCartDao extends BaseDao
102 102
             }
103 103
         }
104 104
         , 'productAttr' => function (Relation $query) use ($confirm) {
105
-            $query->field('pension_num,share_num,stock_num,shares_num,gong_mer_profit,gong_pension,gong_market_price,cost_price,product_id,stock,price,unique,sku,volume,weight,ot_price,dacang_price,gong_sku_id' . ($confirm ? '' : ',cost'))
105
+            $query->field('sku_id,spec_id,pension_num,share_num,stock_num,shares_num,gong_mer_profit,gong_pension,gong_market_price,cost_price,product_id,stock,price,unique,sku,volume,weight,ot_price,dacang_price,gong_sku_id' . ($confirm ? '' : ',cost'))
106 106
                 ->append(['bc_extension_one', 'bc_extension_two']);
107 107
         }, 'merchant' => function (Relation $query) use ($uid) {
108 108
             $query->field('mer_id,mer_name,mer_state,mer_avatar')->with(['coupon' => function ($query) use ($uid) {

+ 37 - 2
app/common/repositories/store/order/StoreGroupOrderRepository.php

@@ -42,6 +42,7 @@ use app\entity\data\user\UserEntity;
42 42
 use app\entity\monomer\store\FzonePayTypeEntity;
43 43
 use app\entity\monomer\store\StoreOrderPensionJsonEntity;
44 44
 use app\entity\request\api\store\order\StoreOrderCreateRequestEntity;
45
+use app\services\ThirdParty\AlibabaAgent\OrderService;
45 46
 use think\db\exception\DbException;
46 47
 use think\exception\ValidateException;
47 48
 use think\facade\Db;
@@ -741,6 +742,35 @@ class StoreGroupOrderRepository extends BaseRepository
741 742
             //     $addressEntity->getCityId()
742 743
             // );
743 744
             $postage = 0.00; // 默认全都包邮s
745
+            $alibabaOrderId = null;
746
+            // 1688商品要获取运费
747
+            if ($productEntity->getIsAlibaba() == 1) {
748
+                // 地址数据
749
+                $addressParam = [
750
+                    'fullName' => $addressEntity->getRealName(),
751
+                    'mobile' => $addressEntity->getPhone(),
752
+                    'provinceText' => $addressEntity->getProvince(),
753
+                    'cityText' => $addressEntity->getCity(),
754
+                    'areaText' => $addressEntity->getDistrict(),
755
+                    'address' => $addressEntity->getDetail(),
756
+                ];
757
+                // 商品数据
758
+                $cargoParamList = [
759
+                    'offerId' => $productEntity->getSpuId(),
760
+                    'specId' => $productAttrValueEntity->getSpecId(),
761
+                    'quantity' => $cartEntity->getCartNum()
762
+                ];
763
+                $orderService = new OrderService();
764
+                $alibabaOrder = $orderService->previewOrder(['cargoParamList' => $cargoParamList, 'addressParam' => $addressParam]);
765
+                $postage = $alibabaOrder['preview']['sumCarriage'] / 100;
766
+
767
+                // 创建1688订单
768
+                $flow = $alibabaOrder['preview']['flowFlag'] ?? 'general';
769
+                $createOrderRes = $orderService->fastCreateOrder(['cargoParamList' => $cargoParamList, 'addressParam' => $addressParam,'flow'=>$flow]);
770
+                if ($createOrderRes['success']) $alibabaOrderId = $createOrderRes['orderId'];
771
+            }
772
+
773
+
744 774
 
745 775
             // 汇付手续费 (订单价格 + 邮费) * 汇付手续费比例
746 776
             $commissionRate = bcmul(bcadd($price, $postage, CommonEnum::DECIMAL_PLACES_OF_THE_AMOUNT), CommonEnum::HUIFU_COMMISSION_CHARGE, CommonEnum::DECIMAL_PLACES_OF_THE_AMOUNT);
@@ -782,7 +812,7 @@ class StoreGroupOrderRepository extends BaseRepository
782 812
                 ->setTotalPrice(bcadd($price, $postage, CommonEnum::DECIMAL_PLACES_OF_THE_AMOUNT))
783 813
                 ->setTotalPostage($postage)
784 814
                 ->setPayPostage($postage)
785
-                ->setPayPrice($price)
815
+                ->setPayPrice(bcadd($price, $postage, CommonEnum::DECIMAL_PLACES_OF_THE_AMOUNT))
786 816
                 ->setMerId($cartEntity->getMerId())
787 817
                 ->setCost($cost)
788 818
                 ->setMerMoney($merMoney)
@@ -807,6 +837,7 @@ class StoreGroupOrderRepository extends BaseRepository
807 837
                         ->setAttrValuePensionNum($productAttrValueEntity->getPensionNum())
808 838
                         ->toArray()
809 839
                 )->setPickUpId($storeOrderCreateRequestEntity->getPickUpId())
840
+                ->setAlibabaOrderId($alibabaOrderId)
810 841
                 ->setMark($storeOrderCreateRequestEntity->getMark());
811 842
             $storeOrderEntityList[] = $storeOrderEntity;
812 843
         }
@@ -822,7 +853,7 @@ class StoreGroupOrderRepository extends BaseRepository
822 853
             ->setRealName($addressEntity->getRealName())
823 854
             ->setUserPhone($addressEntity->getPhone())
824 855
             ->setUserAddress($addressEntity->getProvince() . $addressEntity->getCity() . $addressEntity->getDistrict() . $addressEntity->getDetail())
825
-            ->setPayPrice($totalPrice)
856
+            ->setPayPrice(bcadd($totalPrice, $totalPostage, CommonEnum::DECIMAL_PLACES_OF_THE_AMOUNT))
826 857
             ->setPayPostage($totalPostage)
827 858
             ->setCost($totalCost)
828 859
             ->setPayType($storeOrderCreateRequestEntity->getPayType())
@@ -1006,6 +1037,10 @@ class StoreGroupOrderRepository extends BaseRepository
1006 1037
 
1007 1038
         // 9、订单 分账 将收入的现金 商户部分 发放给 商户
1008 1039
         $storeOrderRepository->independentAccountByOrderIdList(array_column($storeOrderEntityList, 'orderId'));
1040
+
1041
+        // 10、 1688订单调用免密支付接口
1042
+        $aliOrderService = new OrderService();
1043
+        $aliOrderService->paySuccess($storeOrderEntityList);
1009 1044
     }
1010 1045
 
1011 1046
     /**

+ 24 - 2
app/common/repositories/store/order/StoreOrderRepository.php

@@ -110,6 +110,7 @@ use app\entity\request\api\store\order\CalculatePostageRequestEntity;
110 110
 use app\entity\request\api\store\order\StoreOrderCreateRequestEntity;
111 111
 use app\model\common\JdOrderModel;
112 112
 use app\model\common\TbOrderModel;
113
+use app\services\ThirdParty\AlibabaAgent\OrderService;
113 114
 use app\traits\GongApiRequest;
114 115
 use app\traits\HuiPay;
115 116
 use app\traits\ShouxinyiApiRequest;
@@ -2615,6 +2616,28 @@ class StoreOrderRepository extends BaseRepository
2615 2616
             }
2616 2617
 
2617 2618
 
2619
+            // 1688商品获取运费
2620
+            if($cart['product']['is_alibaba'] == 1) {
2621
+                $addressParam = [
2622
+                    'fullName' => $address['real_name'],
2623
+                    'mobile' => $address['phone'],
2624
+                    'provinceText' => $address['province'],
2625
+                    'cityText' => $address['city'],
2626
+                    'areaText' => $address['district'],
2627
+                    'address' => $address['detail'],
2628
+                ];
2629
+                $cargoParamList = [
2630
+                    'offerId' => $cart['product']['spu_id'],
2631
+                    'specId' => $cart['productAttr']['spec_id'],
2632
+                    'quantity' => $cart['cart_num']
2633
+                ];
2634
+                $orderService = new OrderService();
2635
+                $ailOrder = $orderService->previewOrder(['cargoParamList' => $cargoParamList, 'addressParam' => $addressParam]);
2636
+                if ($ailOrder['preview']['sumCarriage'] > 0) {
2637
+                    $postage_price = bcadd($postage_price, $ailOrder['preview']['sumCarriage'] / 100, 2);
2638
+                }
2639
+            }
2640
+
2618 2641
             $take = $merchantTakeRepository->get($cartInfo['mer_id']);
2619 2642
             $org_price = bcadd(bcsub($total_price, $valid_total_price, 2), max($_pay_price, 0), 2);
2620 2643
             $coupon_price = set_price_rtrim(min($coupon_price, $total_price));
@@ -2622,7 +2645,7 @@ class StoreOrderRepository extends BaseRepository
2622 2645
             $org_price = bcsub($org_price, $youhui_price, 2);
2623 2646
 
2624 2647
             $pay_price = bcadd($postage_price, $org_price, 2);
2625
-
2648
+            $total_price = bcadd($total_price, $postage_price, 2);
2626 2649
 
2627 2650
             $postage_price = set_price_rtrim($postage_price);
2628 2651
             $org_price = set_price_rtrim($org_price);
@@ -2632,7 +2655,6 @@ class StoreOrderRepository extends BaseRepository
2632 2655
 
2633 2656
             //var_dump( $cartInfo['list'][$_k]);
2634 2657
 
2635
-
2636 2658
             $cartInfo['take'] = $take['mer_take_status'] == '1' ? $take : ['mer_take_status' => 0];
2637 2659
             $cartInfo['coupon'] = array_values($cartInfo['coupon']);
2638 2660
             $cartInfo['order'] = compact('valid_total_price', 'product_cart', 'product_price', 'postage_price', 'org_price', 'total_price', 'total_num', 'pay_price', 'coupon_price', 'use_coupon_product', 'use_store_coupon', 'youhui_price');

+ 5 - 0
app/common/repositories/store/product/ProductAttrValueRepository.php

@@ -96,6 +96,11 @@ class ProductAttrValueRepository extends BaseRepository
96 96
         if($debug){
97 97
             return true;
98 98
         }
99
+        $is_alibaba = Db::name('store_product') -> where('product_id',$product_id)->value('is_alibaba');
100
+        if ($is_alibaba) {
101
+            return true;
102
+        }
103
+
99 104
         $spu_id = Db::name('store_product') -> where('product_id',$product_id)->value('spu_id');
100 105
 
101 106
         if($spu_id == ''){

+ 7 - 5
app/common/repositories/store/product/ProductRepository.php

@@ -425,7 +425,7 @@ class ProductRepository extends BaseRepository
425 425
                     $value['detail'] = ['默认'];
426 426
                     $sku = '默认';
427 427
                 }
428
-                $product_data = Db::name('store_product')->where('product_id', $id)->value('spu_id');
428
+                $product_data = Db::name('store_product')->where('product_id', $id)->where('is_alibaba',0)->value('spu_id');
429 429
                 if ($product_data) {
430 430
                     if (!in_array($merId, ['319', '320', '321', '322', '323', '324', '325', '326', '327', '328'])) {
431 431
                         if ($value['price'] < $value['cost']) {
@@ -463,7 +463,7 @@ class ProductRepository extends BaseRepository
463 463
                         "sku"           => $sku,
464 464
                         "unique"        => $unique,
465 465
                         "dacang_price"  => $value['dacang_price'],
466
-                        "cost_price"    => $value['cost_price'],
466
+                        "cost_price"    => $value['cost_price'] ?? 0.00,
467 467
                         "gong_sku_id"   => $value['gong_sku_id'],
468 468
                         "pension_num"   => 0,
469 469
                         "share_num"     => 0,
@@ -473,6 +473,8 @@ class ProductRepository extends BaseRepository
473 473
                         'gong_mer_profit' => $value['gong_mer_profit'] ?? 0.00,
474 474
                         'gong_market_price' => $value['gong_market_price'] ?? 0.00,
475 475
                         'plate_mer_profit' => $value['plate_mer_profit'] ?? 0.00,
476
+                        'sku_id' => $value['sku_id'] ?? null,
477
+                        'spec_id' => $value['spec_id'] ?? null,
476 478
                     ];
477 479
                 } else {
478 480
                     $result['attrValue'][] = [
@@ -655,7 +657,7 @@ class ProductRepository extends BaseRepository
655 657
         $result[] = [
656 658
             'type'  => 2,
657 659
             'name'  => '仓库中' . $name,
658
-            'count' => $this->dao->search($merId, $this->switchType(2, $merId, $productType))->whereNull('spu_id')->count()
660
+            'count' => $this->dao->search($merId, $this->switchType(2, $merId, $productType))->count()
659 661
         ];
660 662
         if ($merId) {
661 663
             $result[] = [
@@ -677,7 +679,7 @@ class ProductRepository extends BaseRepository
677 679
         $result[] = [
678 680
             'type'  => 6,
679 681
             'name'  => '待审核' . $name,
680
-            'count' => $this->dao->search($merId, $this->switchType(6, $merId, $productType, $is_gift_bag))->whereNull('spu_id')->count()
682
+            'count' => $this->dao->search($merId, $this->switchType(6, $merId, $productType, $is_gift_bag))->count()
681 683
         ];
682 684
         $result[] = [
683 685
             'type'  => 7,
@@ -733,7 +735,7 @@ class ProductRepository extends BaseRepository
733 735
      */
734 736
     public function getList(?int $merId, array $where, int $page, int $limit)
735 737
     {
736
-        $query = $this->dao->search($merId, $where)->whereNull('spu_id')->with(['merCateId.category', 'storeCategory', 'brand']);
738
+        $query = $this->dao->search($merId, $where)->with(['merCateId.category', 'storeCategory', 'brand']);
737 739
         $count = $query->count($this->dao->getPk());
738 740
         $list = $query->page($page, $limit)->setOption('field', [])->field($this->filed)->select();
739 741
         $list->append(['max_extension', 'min_extension']);

+ 1 - 0
app/common/repositories/system/merchant/MerchantRepository.php

@@ -216,6 +216,7 @@ class MerchantRepository extends BaseRepository
216 216
             Elm::switches('is_audit', '产品审核', 1)->activeValue(1)->inactiveValue(0)->inactiveText('关闭')->activeText('开启')->col(12),
217 217
             //Elm::switches('is_bro_goods', '直播间商品审核', $config['broadcast_goods_type'] == 1 ? 0 : 1)->activeValue(1)->inactiveValue(0)->inactiveText('关闭')->activeText('开启')->col(12),
218 218
             Elm::switches('is_best', '是否推荐')->activeValue(1)->inactiveValue(0)->inactiveText('关闭')->activeText('开启'),
219
+            Elm::switches('is_show_alibaba', '阿里巴巴')->activeValue(1)->inactiveValue(0)->inactiveText('关闭')->activeText('开启'),
219 220
         ];
220 221
 
221 222
         $form->setRule($rule);

+ 154 - 0
app/controller/admin/alibaba/Goods.php

@@ -0,0 +1,154 @@
1
+<?php
2
+
3
+namespace app\controller\admin\alibaba;
4
+
5
+use app\services\ThirdParty\AlibabaAgent\ProductService;
6
+
7
+/**
8
+ * 1688分销严选 - 商品管理
9
+ *
10
+ * API文档:
11
+ *   商品列表: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.fenxiao%3Ajxhy.product.getPageList-1
12
+ *   商品详情: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.fenxiao%3Aalibaba.pifatuan.product.detail.list-2
13
+ */
14
+class Goods
15
+{
16
+    /**
17
+     * 获取1688分销商品分页列表
18
+     *
19
+     * @param ProductService $productService
20
+     * @return \think\Response
21
+     *
22
+     * @api {GET} /admin/alibaba/goods/lst 获取商品列表
23
+     * @apiParam {string}  [access_token]  1688 access_token(不传则使用配置中的默认token)
24
+     * @apiParam {int}     [page=1]        页码
25
+     * @apiParam {int}     [limit=20]      每页条数(最大50)
26
+     * @apiParam {string}  [keyword]       关键词搜索
27
+     * @apiParam {int}     [categoryId]    类目ID
28
+     * @apiParam {number}  [minPrice]      最低价格
29
+     * @apiParam {number}  [maxPrice]      最高价格
30
+     * @apiParam {string}  [sort]          排序方式
31
+     */
32
+    public function lst(ProductService $productService)
33
+    {
34
+        $request = app('request');
35
+
36
+        $page = (int)$request->param('page', 1);
37
+        $limit = (int)$request->param('limit', 20);
38
+        $keyword = $request->param('keyword', '');
39
+        $categoryId = $request->param('categoryId', '');
40
+        $minPrice = $request->param('minPrice', '');
41
+        $maxPrice = $request->param('maxPrice', '');
42
+        $sort = $request->param('sort', '');
43
+
44
+        $params = [
45
+            'page' => max(1, $page),
46
+            'limit' => min(max(1, $limit), 50),
47
+        ];
48
+
49
+        if (!empty($keyword)) {
50
+            $params['keyword'] = $keyword;
51
+        }
52
+        if (!empty($categoryId)) {
53
+            $params['categoryId'] = (int)$categoryId;
54
+        }
55
+        if ($minPrice !== '' && $minPrice > 0) {
56
+            $params['minPrice'] = (float)$minPrice;
57
+        }
58
+        if ($maxPrice !== '' && $maxPrice > 0) {
59
+            $params['maxPrice'] = (float)$maxPrice;
60
+        }
61
+        if (!empty($sort)) {
62
+            $params['sort'] = $sort;
63
+        }
64
+
65
+        $result = $productService->searchProducts($params);
66
+
67
+        if ($result === false) {
68
+            return app('json')->fail('获取1688商品列表失败,请查看日志');
69
+        }
70
+
71
+        return app('json')->success($result);
72
+    }
73
+
74
+    /**
75
+     * 获取1688分销商品详情
76
+     *
77
+     * @param ProductService $productService
78
+     * @return mixed
79
+     *
80
+     * @api {GET} /admin/alibaba/goods/detail 获取商品详情
81
+     * @apiParam {string}  [access_token]  1688 access_token(不传则使用配置中的默认token)
82
+     * @apiParam {int}     productId       1688商品ID(必填)
83
+     */
84
+    public function detail(ProductService $productService)
85
+    {
86
+        $request = app('request');
87
+
88
+        $productId = $request->param('productId', '');
89
+
90
+        if (empty($productId)) {
91
+            return app('json')->fail('参数错误:productId不能为空');
92
+        }
93
+
94
+        $result = $productService->getProductDetail($productId);
95
+
96
+        if ($result === false) {
97
+            return app('json')->fail('获取1688商品详情失败,请查看日志');
98
+        }
99
+
100
+        return app('json')->success($result);
101
+    }
102
+
103
+    /**
104
+     * 批量导入1688商品到 alibaba_import_goods 表
105
+     *
106
+     * 业务场景:从1688商品列表中选择多个商品,一键入库
107
+     * 商品详情以JSON格式完整存储到 alibaba_import_goods 表
108
+     *
109
+     * @param ProductService $productService
110
+     * @return mixed
111
+     *
112
+     * @api {POST} /admin/alibaba/goods/import 批量入库
113
+     * @apiParam {string}  productIds  1688商品ID(必填),多个用逗号分隔,如:787533862434 或 787533862434,787533862435
114
+     */
115
+    public function import(ProductService $productService)
116
+    {
117
+        $request = app('request');
118
+
119
+        $raw = $request->param('productIds', '');
120
+
121
+        // 参数校验
122
+        if (empty($raw)) {
123
+            return app('json')->fail('参数错误:productIds不能为空');
124
+        }
125
+
126
+        // 解析productIds:支持 "787533862434" 或 "787533862434,787533862435"
127
+        $productIds = explode(',', str_replace(' ', '', $raw));
128
+        $productIds = array_unique(array_filter($productIds, function ($v) {
129
+            return $v !== '' && $v !== null;
130
+        }));
131
+
132
+        if (empty($productIds)) {
133
+            return app('json')->fail('参数错误:productIds格式不正确');
134
+        }
135
+
136
+        // 执行批量入库
137
+        $result = $productService->batchImport($productIds, 0);
138
+
139
+        $successCount = count($result['success']);
140
+        $failCount = count($result['fail']);
141
+
142
+        if ($successCount > 0) {
143
+            return app('json')->success([
144
+                'success'       => $result['success'],
145
+                'fail'          => $result['fail'],
146
+                'success_count' => $successCount,
147
+                'fail_count'    => $failCount,
148
+                'message'       => "成功入库 {$successCount} 个商品" . ($failCount > 0 ? ",{$failCount} 个失败" : ''),
149
+            ]);
150
+        }
151
+
152
+        return app('json')->fail('入库失败:' . implode('; ', array_values($result['fail'])));
153
+    }
154
+}

+ 14 - 7
app/controller/admin/system/auth/Menu.php

@@ -206,23 +206,30 @@ class Menu extends BaseController
206 206
             $menus[$k] = $menu;
207 207
         }
208 208
 
209
-        if($this->merchant){
210
-
211
-            foreach($menus as $key => $value){
212
-                if($value['menu_id'] == '1112' && $this->request->merId() != 439){
209
+        if ($this->merchant) {
210
+            // 获取是否拥有阿里巴巴选品库权限
211
+            $show = 0;
212
+            $isShowAlibaba = Db::name('merchant')->where('mer_id', $this->request->merId())->value('is_show_alibaba');
213
+            if ($isShowAlibaba) $show = 1;
214
+            foreach ($menus as $key => $value) {
215
+                if ($value['menu_id'] == '1112' && $this->request->merId() != 439) {
213 216
                     unset($menus[$key]);
214 217
                 }
215
-                if($value['menu_id'] == '1127' && $this->request->merId() != 439){
218
+                if ($value['menu_id'] == '1127' && $this->request->merId() != 439) {
216 219
                     unset($menus[$key]);
217 220
                 }
218
-                if($value['menu_id'] == '1103'){
221
+                if ($value['menu_id'] == '1103') {
219 222
                     unset($menus[$key]);
220 223
                 }
224
+                if ($value['route'] == '/merchant/product/alibaba') {
225
+                    if ($show == 0) {
226
+                        unset($menus[$key]);
227
+                    }
228
+                }
221 229
             }
222 230
         }
223 231
 
224 232
 
225
-
226 233
         return app('json')->success(array_values(array_filter(formatCategory($menus, 'menu_id'), function ($v) {
227 234
             return 0 == $v['pid'];
228 235
         })));

+ 1 - 1
app/controller/admin/system/merchant/Merchant.php

@@ -179,7 +179,7 @@ class Merchant extends BaseController
179 179
      */
180 180
     public function checkParam(MerchantValidate $validate, $isUpdate = false)
181 181
     {
182
-        $data = $this->request->params([['category_id', 0], 'mer_type','mer_name', 'commission_rate', 'real_name', 'mer_phone', 'mer_keyword', 'mer_address', 'mark', ['sort', 0], ['status', 0], ['is_audit', 0], ['is_best', 0], ['is_bro_goods', 0], ['is_bro_room', 0]]);
182
+        $data = $this->request->params([['category_id', 0], 'mer_type','mer_name', 'commission_rate', 'real_name', 'mer_phone', 'mer_keyword', 'mer_address', 'mark', ['sort', 0], ['status', 0], ['is_audit', 0], ['is_best', 0], ['is_bro_goods', 0], ['is_bro_room', 0],['is_show_alibaba',0]]);
183 183
         if (!$isUpdate)
184 184
             $data += $this->request->params(['mer_account', 'mer_password']);
185 185
         else

+ 59 - 0
app/controller/api/store/order/AlibabaOrder.php

@@ -0,0 +1,59 @@
1
+<?php
2
+
3
+namespace app\controller\api\store\order;
4
+
5
+use app\common\repositories\store\order\StoreCartRepository;
6
+use app\services\ThirdParty\AlibabaAgent\OrderService;
7
+use think\facade\Db;
8
+
9
+class AlibabaOrder
10
+{
11
+    public function checkOrder(StoreCartRepository $cartRepository)
12
+    {
13
+        $cartId = request()->param('cart_id', []);
14
+        $addressId = request()->param('address_id');
15
+
16
+        if (empty($addressId)) {
17
+            return app('json')->fail('请选择收货地址');
18
+        }
19
+
20
+        $postagePrice = 0;
21
+
22
+        // 判断是否是阿里巴巴商品
23
+        $cartInfo = Db::name('store_cart')->whereIn('cart_id', $cartId)->select()->toArray();
24
+        $productInfo = Db::name('store_product')->whereIn('product_id', array_column($cartInfo, 'product_id'))->find();
25
+        if (!$productInfo['is_alibaba']) {
26
+            return app('json')->success(['postagePrice' => $postagePrice]);
27
+        }
28
+
29
+        // 获取地址
30
+        $addressInfo = Db::name('user_address')->where('address_id', $addressId)->find();
31
+        $addressParam = [
32
+            'fullName' => $addressInfo['real_name'],
33
+            'mobile' => $addressInfo['phone'],
34
+            'provinceText' => $addressInfo['province'],
35
+            'cityText' => $addressInfo['city'],
36
+            'areaText' => $addressInfo['district'],
37
+            'address' => $addressInfo['detail'],
38
+        ];
39
+
40
+
41
+        // 获取商品规格
42
+        $quantity = array_column($cartInfo, 'cart_num')[0];
43
+        $offerId = $productInfo['spu_id'];
44
+        $specId = Db::name('store_product_attr_value')->where('product_id', $productInfo['product_id'])->whereIn('unique', array_column($cartInfo, 'product_attr_unique'))->value('spec_id');
45
+        $cargoParamList = [
46
+            'offerId' => $offerId,
47
+            'specId' => $specId,
48
+            'quantity' => $quantity
49
+        ];
50
+
51
+        $orderService = new OrderService();
52
+
53
+        $res = $orderService->previewOrder(['cargoParamList' => $cargoParamList, 'addressParam' => $addressParam]);
54
+        if (!$res) {
55
+            return app('json')->fail('预下单失败');
56
+        }
57
+        return app('json')->success(['postagePrice' => $res['preview']['sumCarriage'] / 100, 'flow' => $res['preview']['flowFlag']]);
58
+    }
59
+}

+ 14 - 1
app/controller/api/user/User.php

@@ -3323,6 +3323,7 @@ class User extends BaseController
3323 3323
     public function new_sign_info()
3324 3324
     {
3325 3325
         $uid = $this->request->uid();
3326
+
3326 3327
         // 查询今天是否已经签到
3327 3328
         $today_signin_info = Db::name('user_sign_score')->where(array('uid' => $uid))->whereDay('signin_time')->find();
3328 3329
         // 查询昨天是否已经签到了
@@ -3331,7 +3332,8 @@ class User extends BaseController
3331 3332
 
3332 3333
         $data = [
3333 3334
             'today' => 0,
3334
-            'day'   => 0
3335
+            'day'   => 0,
3336
+            'show' => 1
3335 3337
         ];
3336 3338
 
3337 3339
         if ($yes_signin_info) $data['day'] = (int)$yes_signin_info['continuity_day'];
@@ -3340,6 +3342,17 @@ class User extends BaseController
3340 3342
             $data['today'] = 1;
3341 3343
             $data['day'] = $data['day'] + 1;
3342 3344
         }
3345
+
3346
+        $key = 'user_sign_score_' . $uid;
3347
+        if (Cache::get($key)) {
3348
+            $data['show'] = 0;
3349
+        }
3350
+
3351
+        $now = time();
3352
+        $todayEnd = strtotime('today 23:59:59');
3353
+        $remainingSeconds = $todayEnd - $now + rand(1, 100);
3354
+        Cache::set($key, 1, $remainingSeconds);
3355
+
3343 3356
         return app('json')->success($data);
3344 3357
     }
3345 3358
 

+ 233 - 0
app/controller/merchant/alibaba/Product.php

@@ -0,0 +1,233 @@
1
+<?php
2
+
3
+namespace app\controller\merchant\alibaba;
4
+
5
+use think\facade\Db;
6
+use app\services\ThirdParty\AlibabaAgent\ProductService;
7
+use crmeb\basic\BaseController;
8
+
9
+class Product extends BaseController
10
+{
11
+    /**
12
+     * 已入库的1688商品列表
13
+     *
14
+     * 展示 rrx_alibaba_import_goods 表中的商品数据
15
+     * GET /merchant/alibaba/product/lst?page=1&limit=20&keyword=&status=
16
+     *
17
+     * @return \think\Response
18
+     */
19
+    public function lst()
20
+    {
21
+        $request = app('request');
22
+        $page    = (int)($request->param('page', 1));
23
+        $limit   = (int)($request->param('limit', 20));
24
+        $keyword = $request->param('keyword', '');
25
+        $status  = $request->param('status', '');
26
+        $merId = $this->request->merId();
27
+        // $merId = 3447;
28
+
29
+        $query = Db::name('alibaba_import_goods');
30
+
31
+        // 关键词搜索(商品名称)
32
+        if (!empty($keyword)) {
33
+            $query->where('product_name', 'like', '%' . $keyword . '%');
34
+        }
35
+
36
+        // 状态筛选
37
+        if ($status !== '') {
38
+            $query->where('status', (int)$status);
39
+        }
40
+
41
+        $count = $query->count('id');
42
+
43
+        $list = $query
44
+            ->order('create_time', 'DESC')
45
+            ->page($page, $limit)
46
+            ->select()
47
+            ->toArray();
48
+
49
+        // 格式化返回数据
50
+        foreach ($list as &$item) {
51
+            $item['id']          = (int)$item['id'];
52
+            $item['product_id']  = (int)$item['product_id'];
53
+            $item['status']      = (int)$item['status'];
54
+            $item['create_time'] = (string)$item['create_time'];
55
+
56
+            // 解析 product_info JSON
57
+            $productInfo = [];
58
+            if (!empty($item['product_info'])) {
59
+                $productInfo = json_decode($item['product_info'], true) ?: [];
60
+            }
61
+            $item['product_info'] = $productInfo;
62
+
63
+            // 提取关键展示字段
64
+            $item['price']       = $productInfo['price'] ?? 0;
65
+            $item['minPrice']    = $productInfo['minPrice'] ?? 0;
66
+            $item['maxPrice']    = $productInfo['maxPrice'] ?? 0;
67
+            $item['stock']       = $productInfo['stock'] ?? 0;
68
+            $item['unit']        = $productInfo['unit'] ?? '件';
69
+            $item['sku_count']   = isset($productInfo['skuList']) ? count($productInfo['skuList']) : 0;
70
+            $item['image_list']  = $productInfo['imageList'] ?? [];
71
+            $item['shop_name']   = $productInfo['shopName'] ?? '';
72
+            $item['category_name'] = $productInfo['categoryName'] ?? '';
73
+
74
+            // 加价标记
75
+            $item['markup_rate'] = $productInfo['markupRate'] ?? 0;
76
+            $item['original_min_price'] = $productInfo['originalMinPrice'] ?? 0;
77
+            $item['original_max_price'] = $productInfo['originalMaxPrice'] ?? 0;
78
+
79
+            // 是否已入库
80
+            $item['isStoreProduct'] = 0;
81
+            $storeProduct = Db::name('store_product')->where(['spu_id' => $item['product_id'], 'mer_id' => $merId])->find();
82
+            if ($storeProduct) $item['isStoreProduct'] = 1;
83
+        }
84
+        unset($item);
85
+
86
+        return json([
87
+            'code' => 200,
88
+            'message' => 'success',
89
+            'data' => [
90
+                'list'  => $list,
91
+                'count' => $count,
92
+                'page'  => $page,
93
+                'limit' => $limit,
94
+            ],
95
+        ]);
96
+    }
97
+
98
+    /**
99
+     * 获取单个入库商品的详细信息
100
+     *
101
+     * GET /merchant/alibaba/product/detail?id=123
102
+     *
103
+     * @return \think\Response
104
+     */
105
+    public function detail()
106
+    {
107
+        $request = app('request');
108
+        $id = (int)$request->param('id', 0);
109
+        if ($id <= 0) {
110
+            return json(['code' => 400, 'message' => '参数错误:id不能为空']);
111
+        }
112
+
113
+        $item = Db::name('alibaba_import_goods')
114
+            ->where('id', $id)
115
+            ->find();
116
+
117
+        if (!$item) {
118
+            return json(['code' => 404, 'message' => '商品不存在']);
119
+        }
120
+
121
+        $item['id']         = (int)$item['id'];
122
+        $item['product_id'] = (int)$item['product_id'];
123
+        $item['status']     = (int)$item['status'];
124
+
125
+        // 解析 product_info JSON
126
+        if (!empty($item['product_info'])) {
127
+            $item['product_info'] = json_decode($item['product_info'], true) ?: [];
128
+        }
129
+
130
+        return json([
131
+            'code' => 200,
132
+            'message' => 'success',
133
+            'data' => $item,
134
+        ]);
135
+    }
136
+
137
+    /**
138
+     * 删除入库商品(软删除:修改status为0)
139
+     *
140
+     * POST /merchant/alibaba/product/delete
141
+     * Body: id=123
142
+     *
143
+     * @return \think\Response
144
+     */
145
+    public function delete()
146
+    {
147
+        $request = app('request');
148
+        $id = (int)$request->param('id', 0);
149
+        if ($id <= 0) {
150
+            return json(['code' => 400, 'message' => '参数错误:id不能为空']);
151
+        }
152
+
153
+        $exists = Db::name('alibaba_import_goods')->where('id', $id)->find();
154
+        if (!$exists) {
155
+            return json(['code' => 404, 'message' => '商品不存在']);
156
+        }
157
+
158
+        Db::name('alibaba_import_goods')->where('id', $id)->update(['status' => 0]);
159
+
160
+        return json(['code' => 200, 'message' => '删除成功']);
161
+    }
162
+
163
+    /**
164
+     * 正式入库:将 alibaba_import_goods 表中的商品写入 store_product 系列表
165
+     *
166
+     * 业务场景:从 alibaba_import_goods 表中选择已导入的1688商品,
167
+     * 正式写入到 rrx_store_product、rrx_store_product_attr、rrx_store_product_attr_value 表中
168
+     *
169
+     * POST /merchant/alibaba/product/importStore
170
+     * Body: id=123   (alibaba_import_goods 表的主键ID)
171
+     * 或 Body: ids=123,456  (批量入库,逗号分隔)
172
+     *
173
+     * @param ProductService $productService
174
+     * @return \think\Response
175
+     */
176
+    public function importStore(ProductService $productService)
177
+    {
178
+        $request = app('request');
179
+        $idStr = $request->param('id', '');
180
+
181
+        if (empty($idStr)) {
182
+            return json(['code' => 400, 'message' => '参数错误:请传入id']);
183
+        }
184
+
185
+        // 解析要入库的ID列表(支持单个id或逗号分隔的多个id)
186
+        $importIds = [];
187
+        $parts = explode(',', $idStr);
188
+        foreach ($parts as $part) {
189
+            $part = (int)trim($part);
190
+            if ($part > 0) $importIds[] = $part;
191
+        }
192
+
193
+        if (empty($importIds)) {
194
+            return json(['code' => 400, 'message' => '参数错误:id格式不正确']);
195
+        }
196
+
197
+        $merId = $this->request->merId();
198
+        // $merId = 3447;
199
+
200
+        $successList = [];
201
+        $failList    = [];
202
+
203
+        foreach ($importIds as $importId) {
204
+            $result = $productService->importToStoreProduct($importId, $merId);
205
+            if ($result['code'] === 200) {
206
+                $successList[] = [
207
+                    'import_goods_id' => $importId,
208
+                    'product_id'      => $result['product_id'],
209
+                    'msg'             => $result['msg'],
210
+                ];
211
+            } else {
212
+                $failList[] = [
213
+                    'import_goods_id' => $importId,
214
+                    'msg'             => $result['msg'],
215
+                ];
216
+            }
217
+        }
218
+
219
+        $message = count($successList) . '个商品入库成功';
220
+        if (!empty($failList)) {
221
+            $message .= ',' . count($failList) . '个商品入库失败';
222
+        }
223
+
224
+        return json([
225
+            'code'    => 200,
226
+            'message' => $message,
227
+            'data'    => [
228
+                'success' => $successList,
229
+                'fail'    => $failList,
230
+            ],
231
+        ]);
232
+    }
233
+}

+ 13 - 0
app/entity/data/store/StoreOrderEntity.php

@@ -103,6 +103,8 @@ class StoreOrderEntity extends DataEntity
103 103
 
104 104
     public $pickUpId = null;
105 105
 
106
+    public $alibabaOrderId = null;
107
+
106 108
     /**
107 109
      * @return mixed
108 110
      */
@@ -1784,6 +1786,17 @@ class StoreOrderEntity extends DataEntity
1784 1786
         return $this;
1785 1787
     }
1786 1788
 
1789
+    public function getAlibabaOrderId()
1790
+    {
1791
+        return $this->alibabaOrderId;
1792
+    }
1793
+
1794
+    public function setAlibabaOrderId($alibabaOrderId): StoreOrderEntity
1795
+    {
1796
+        $this->alibabaOrderId = $alibabaOrderId;
1797
+        return $this;
1798
+    }
1799
+
1787 1800
     public function deconstructionFzonePaytype()
1788 1801
     {
1789 1802
         if (!empty($this->getFzonePayType()) && is_array(json_decode($this->getFzonePayType(), true))) {

+ 13 - 0
app/entity/data/store/StoreProductAttrValueEntity.php

@@ -34,6 +34,8 @@ class StoreProductAttrValueEntity extends DataEntity
34 34
     public $stockNum = null;  // 股权%
35 35
     public $sharesNum = null;  // 股票
36 36
 
37
+    public $specId = null; // 阿里巴巴规格ID
38
+
37 39
     /**
38 40
      * @return mixed
39 41
      */
@@ -519,4 +521,15 @@ class StoreProductAttrValueEntity extends DataEntity
519 521
         $this->sharesNum = $sharesNum;
520 522
         return $this;
521 523
     }
524
+
525
+    public function getSpecId()
526
+    {
527
+        return $this->specId;
528
+    }
529
+
530
+    public function setSpecId($specId): StoreProductAttrValueEntity
531
+    {
532
+        $this->specId = $specId;
533
+        return $this;
534
+    }
522 535
 }

+ 13 - 0
app/entity/data/store/StoreProductEntity.php

@@ -69,6 +69,8 @@ class StoreProductEntity extends DataEntity
69 69
     public $argument = null;  // 下架说明理由
70 70
     public $isStatus = null;  // 下架状态
71 71
 
72
+    public $isAlibaba = null; // 是否是阿里商品
73
+
72 74
     /**
73 75
      * @return mixed
74 76
      */
@@ -1184,4 +1186,15 @@ class StoreProductEntity extends DataEntity
1184 1186
         $this->isStatus = $isStatus;
1185 1187
         return $this;
1186 1188
     }
1189
+
1190
+    public function getIsAlibaba()
1191
+    {
1192
+        return $this->isAlibaba;
1193
+    }
1194
+
1195
+    public function setIsAlibaba($isAlibaba): StoreProductEntity
1196
+    {
1197
+        $this->isAlibaba = $isAlibaba;
1198
+        return $this;
1199
+    }
1187 1200
 }

+ 449 - 0
app/services/ThirdParty/AlibabaAgent/AlibabaAgentBaseService.php

@@ -0,0 +1,449 @@
1
+<?php
2
+/**
3
+ * 1688分销严选采购解决方案(分销买家版) - 基础服务类
4
+ *
5
+ * 提供OAuth 2.0认证、API请求签名、HTTP请求等基础能力
6
+ *
7
+ * @author: yourname
8
+ * @day: 2026/04/27
9
+ */
10
+
11
+namespace app\services\ThirdParty\AlibabaAgent;
12
+
13
+use crmeb\services\HttpService;
14
+use think\facade\Config;
15
+use think\facade\Log;
16
+
17
+class AlibabaAgentBaseService
18
+{
19
+    /** @var array 配置 */
20
+    protected $config;
21
+
22
+    /** @var string App Key */
23
+    protected $appKey;
24
+
25
+    /** @var string App Secret */
26
+    protected $appSecret;
27
+
28
+    /** @var string 网关地址 */
29
+    protected $gatewayUrl;
30
+
31
+    /** @var string 访问令牌 */
32
+    protected $accessToken = '';
33
+
34
+    /** @var string 刷新令牌 */
35
+    protected $refreshToken = '';
36
+
37
+    /**
38
+     * 构造函数
39
+     */
40
+    public function __construct()
41
+    {
42
+        $this->config = Config::get('third_party.alibaba_agent', []);
43
+        $this->appKey = $this->config['appKey'] ?? '';
44
+        $this->appSecret = $this->config['appSecret'] ?? '';
45
+        $this->accessToken = $this->config['accessToken'] ?? '';
46
+        $this->gatewayUrl = $this->config['gatewayUrl'] ?? 'https://gw.open.1688.com/openapi/';
47
+    }
48
+
49
+    /**
50
+     * 设置访问令牌
51
+     *
52
+     * @param string $accessToken
53
+     * @return $this
54
+     */
55
+    public function setAccessToken(string $accessToken): self
56
+    {
57
+        $this->accessToken = $accessToken;
58
+        return $this;
59
+    }
60
+
61
+    /**
62
+     * 设置刷新令牌
63
+     *
64
+     * @param string $refreshToken
65
+     * @return $this
66
+     */
67
+    public function setRefreshToken(string $refreshToken): self
68
+    {
69
+        $this->refreshToken = $refreshToken;
70
+        return $this;
71
+    }
72
+
73
+    /**
74
+     * 获取访问令牌
75
+     *
76
+     * @return string
77
+     */
78
+    public function getAccessToken(): string
79
+    {
80
+        return $this->accessToken;
81
+    }
82
+
83
+    /**
84
+     * 获取OAuth 2.0授权URL
85
+     *
86
+     * @param string $state 防CSRF状态码
87
+     * @return string
88
+     */
89
+    public function getAuthUrl(string $state = ''): string
90
+    {
91
+        $params = [
92
+            'client_id' => $this->appKey,
93
+            'redirect_uri' => $this->config['redirectUri'] ?? '',
94
+            'response_type' => 'code',
95
+            'state' => $state,
96
+        ];
97
+        return 'https://auth.1688.com/oauth/authorize?' . http_build_query($params);
98
+    }
99
+
100
+    /**
101
+     * 通过授权码获取访问令牌
102
+     *
103
+     * @param string $code 授权码
104
+     * @return array|false
105
+     */
106
+    public function getAccessTokenByCode(string $code)
107
+    {
108
+        $url = 'https://gw.open.1688.com/openapi/http/1/system.oauth2/getAccessTokenByCode/' . $this->appKey;
109
+
110
+        $params = [
111
+            'code' => $code,
112
+            'appKey' => $this->appKey,
113
+            'appSecret' => $this->appSecret,
114
+            'redirect_uri' => $this->config['redirectUri'] ?? '',
115
+            'grant_type' => 'authorization_code',
116
+        ];
117
+
118
+        return $this->parseResponse(
119
+            HttpService::postRequest($url, $params)
120
+        );
121
+    }
122
+
123
+    /**
124
+     * 刷新访问令牌
125
+     *
126
+     * @param string $refreshToken
127
+     * @return array|false
128
+     */
129
+    public function refreshAccessToken(string $refreshToken)
130
+    {
131
+        $url = 'https://gw.open.1688.com/openapi/http/1/system.oauth2/getAccessTokenByCode/' . $this->appKey;
132
+
133
+        $params = [
134
+            'refreshToken' => $refreshToken,
135
+            'appKey' => $this->appKey,
136
+            'appSecret' => $this->appSecret,
137
+            'grant_type' => 'refresh_token',
138
+        ];
139
+
140
+        $result = $this->parseResponse(
141
+            HttpService::postRequest($url, $params)
142
+        );
143
+
144
+        if ($result && isset($result['access_token'])) {
145
+            $this->setAccessToken($result['access_token']);
146
+            if (isset($result['refresh_token'])) {
147
+                $this->setRefreshToken($result['refresh_token']);
148
+            }
149
+        }
150
+
151
+        return $result;
152
+    }
153
+
154
+    /**
155
+     * 生成1688 API签名(param2方式)
156
+     *
157
+     * 参考可调通的代码:
158
+     *   $aliParams = array();
159
+     *   foreach ($code_arr as $key => $val) {
160
+     *       if (is_array($val)) { $val = json_encode($val); }
161
+     *       $aliParams[] = $key . $val;
162
+     *   }
163
+     *   sort($aliParams);
164
+     *   $sign_str = join('', $aliParams);
165
+     *   $sign_str = $param . $apiInfo . $sign_str;
166
+     *   return strtoupper(bin2hex(hash_hmac("sha1", $sign_str, $appSecret, true)));
167
+     *
168
+     * @param string $param 协议前缀,如 "param2/1/"
169
+     * @param string $apiInfo API路径(含appKey),如 "com.alibaba.fenxiao/jxhy.product.getPageList/6512262"
170
+     * @param array $params 所有请求参数(含access_token,不含_aop_signature)
171
+     * @return string
172
+     */
173
+    protected function generateSignature(string $param, string $apiInfo, array $params): string
174
+    {
175
+        $aliParams = array();
176
+        foreach ($params as $key => $val) {
177
+            // 数组参数需要json_encode
178
+            if (is_array($val)) {
179
+                $val = json_encode($val);
180
+            }
181
+            $aliParams[] = $key . $val;
182
+        }
183
+        sort($aliParams);
184
+        $sign_str = join('', $aliParams);
185
+        // 注意顺序: param + apiInfo + 参数拼装
186
+        $sign_str = $param . $apiInfo . $sign_str;
187
+        
188
+        return strtoupper(bin2hex(hash_hmac("sha1", $sign_str, $this->appSecret, true)));
189
+    }
190
+
191
+    /**
192
+     * 执行API请求(param2方式 - RESTful风格)
193
+     *
194
+     * @param string $namespace API命名空间
195
+     * @param string $name API名称
196
+     * @param int $version API版本号
197
+     * @param array $businessParams 业务参数
198
+     * @return array|false
199
+     */
200
+    protected function executeParam2(
201
+        string $namespace,
202
+        string $name,
203
+        int $version = 1,
204
+        array $businessParams = []
205
+    ) {
206
+        // param2方式URL格式: https://gw.open.1688.com/openapi/param2/{version}/{namespace}/{name}/{appKey}
207
+        // 注意: URL路径最后一部分是 appKey,不是API版本号
208
+        $appKey = $this->getConfig('appKey', '');
209
+        
210
+        // 协议前缀: param2/{version}/
211
+        $param = 'param2/' . $version . '/';
212
+        
213
+        // API路径(含appKey): {namespace}/{name}/{appKey}
214
+        $apiInfo = $namespace . '/' . $name . '/' . $appKey;
215
+        
216
+        // 完整URL(不含query参数,参数通过POST body发送)
217
+        $url = rtrim($this->gatewayUrl, '/') . '/' . $param . $apiInfo;
218
+
219
+        // 所有参数(包括access_token和业务参数)
220
+        $allParams = array_merge(
221
+            ['access_token' => $this->accessToken],
222
+            $businessParams
223
+        );
224
+
225
+        // 生成签名
226
+        // 注意: generateSignature($param, $apiInfo, $params)
227
+        // sign_str = $param . $apiInfo . 参数拼装
228
+        $signature = $this->generateSignature($param, $apiInfo, $allParams);
229
+        $allParams['_aop_signature'] = $signature;
230
+
231
+        $this->logRequest($url, $allParams);
232
+
233
+        try {
234
+            // 使用POST方式发送请求,参数放在POST body中(参考可调通的代码)
235
+            $ch = curl_init();
236
+            curl_setopt($ch, CURLOPT_URL, $url);
237
+            curl_setopt($ch, CURLOPT_POST, true);
238
+            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($allParams));
239
+            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
240
+            curl_setopt($ch, CURLOPT_TIMEOUT, 15);
241
+            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
242
+            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
243
+            curl_setopt($ch, CURLOPT_FAILONERROR, false);
244
+            $response = curl_exec($ch);
245
+            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
246
+            $curlError = curl_error($ch);
247
+            curl_close($ch);
248
+
249
+            if ($response === false) {
250
+                $this->logError('HTTP请求失败', $curlError ?: '未知curl错误');
251
+                return false;
252
+            }
253
+
254
+            $this->logResponse($url, $response);
255
+
256
+            return $this->parseResponse($response, $httpCode);
257
+        } catch (\Exception $e) {
258
+            $this->logError('API请求异常', $e->getMessage());
259
+            return false;
260
+        }
261
+    }
262
+
263
+    /**
264
+     * 执行API请求(http方式 - 传统POST方式)
265
+     *
266
+     * @param string $namespace API命名空间
267
+     * @param string $name API名称
268
+     * @param int $version API版本号
269
+     * @param array $businessParams 业务参数
270
+     * @return array|false
271
+     */
272
+    protected function executeHttp(
273
+        string $namespace,
274
+        string $name,
275
+        int $version = 1,
276
+        array $businessParams = []
277
+    ) {
278
+        // 构建URL: https://gw.open.1688.com/openapi/http/1/cn.alibaba.open/offer.get/1
279
+        $url = rtrim($this->gatewayUrl, '/') . '/http/' . $version . '/' . $namespace . '/' . $name . '/' . $version;
280
+
281
+        // 构建请求参数
282
+        $params = [
283
+            'access_token' => $this->accessToken,
284
+            'appKey' => $this->appKey,
285
+            'appSecret' => $this->appSecret,
286
+        ];
287
+
288
+        // 业务参数
289
+        if (!empty($businessParams)) {
290
+            $params = array_merge($params, $businessParams);
291
+        }
292
+
293
+        $this->logRequest($url, $params);
294
+
295
+        try {
296
+            $response = HttpService::postRequest($url, $params);
297
+
298
+            $this->logResponse($url, $response);
299
+
300
+            return $this->parseResponse($response);
301
+        } catch (\Exception $e) {
302
+            $this->logError('API请求异常', $e->getMessage());
303
+            return false;
304
+        }
305
+    }
306
+
307
+    /**
308
+     * 解析响应
309
+     *
310
+     * @param string|false $response 响应体
311
+     * @param int $httpCode HTTP状态码
312
+     * @return array|false
313
+     */
314
+    protected function parseResponse($response, int $httpCode = 200)
315
+    {
316
+        if ($response === false || empty($response)) {
317
+            $this->logError('HTTP请求失败', '响应为空或请求失败');
318
+            return false;
319
+        }
320
+
321
+        $result = json_decode($response, true);
322
+
323
+        if (json_last_error() !== JSON_ERROR_NONE) {
324
+            $this->logError('JSON解析失败', json_last_error_msg() . ' | 原始响应: ' . substr($response, 0, 500));
325
+            return false;
326
+        }
327
+
328
+        // HTTP状态码非200,记录错误并返回false
329
+        if ($httpCode !== 200) {
330
+            $errorMsg = json_encode($result, JSON_UNESCAPED_UNICODE);
331
+            $this->logError("HTTP请求失败(status:{$httpCode})", $errorMsg);
332
+            return false;
333
+        }
334
+
335
+        // 检查1688 API标准错误格式
336
+        if (isset($result['error_code']) || isset($result['errorMessage'])) {
337
+            $errorMsg = $result['errorMessage'] ?? ($result['error_message'] ?? '未知错误');
338
+            $errorCode = $result['error_code'] ?? ($result['code'] ?? 0);
339
+            $this->logError('1688 API错误', "[{$errorCode}] {$errorMsg}");
340
+            return false;
341
+        }
342
+
343
+        // 检查result中的错误
344
+        if (isset($result['result']) && isset($result['result']['errorCode'])) {
345
+            $errorMsg = $result['result']['errorMessage'] ?? '未知错误';
346
+            $errorCode = $result['result']['errorCode'];
347
+            $this->logError('1688 API业务错误', "[{$errorCode}] {$errorMsg}");
348
+            return false;
349
+        }
350
+
351
+        // 检查alibaba标准错误响应格式
352
+        if (isset($result['errorResponse']) && isset($result['errorResponse']['code'])) {
353
+            $errorMsg = $result['errorResponse']['message'] ?? '未知错误';
354
+            $errorCode = $result['errorResponse']['code'];
355
+            $this->logError('1688 API错误响应', "[{$errorCode}] {$errorMsg}");
356
+            return false;
357
+        }
358
+
359
+        return $result;
360
+    }
361
+
362
+    /**
363
+     * 记录请求日志
364
+     *
365
+     * @param string $url
366
+     * @param array $params
367
+     */
368
+    protected function logRequest(string $url, array $params): void
369
+    {
370
+        if (!($this->config['logEnabled'] ?? false)) {
371
+            return;
372
+        }
373
+
374
+        $logData = [
375
+            'url' => $url,
376
+            'params' => $this->maskSensitiveData($params),
377
+        ];
378
+
379
+        Log::channel('alibaba')->info('[1688分销采购] 请求: ' . json_encode($logData, JSON_UNESCAPED_UNICODE));
380
+    }
381
+
382
+    /**
383
+     * 记录响应日志
384
+     *
385
+     * @param string $url
386
+     * @param string|false $response
387
+     */
388
+    protected function logResponse(string $url, $response): void
389
+    {
390
+        if (!($this->config['logEnabled'] ?? false)) {
391
+            return;
392
+        }
393
+
394
+        Log::channel('alibaba')->info('[1688分销采购] 响应: ' . substr((string)$response, 0, 2000));
395
+    }
396
+
397
+    /**
398
+     * 记录错误日志
399
+     *
400
+     * @param string $title
401
+     * @param string $message
402
+     */
403
+    protected function logError(string $title, string $message): void
404
+    {
405
+        Log::channel('alibaba')->error("[1688分销采购] {$title}: {$message}");
406
+
407
+        // 同时写入文件日志(兼容旧的文件日志方式)
408
+        $logPath = $this->config['logPath'] ?? runtime_path() . 'alibaba/';
409
+        $logFile = $logPath . 'error_' . date('Y-m-d') . '.log';
410
+        $logDir = dirname($logFile);
411
+        if (!is_dir($logDir)) {
412
+            @mkdir($logDir, 0755, true);
413
+        }
414
+        @file_put_contents(
415
+            $logFile,
416
+            '[' . date('Y-m-d H:i:s') . "] {$title}: {$message}\n",
417
+            FILE_APPEND
418
+        );
419
+    }
420
+
421
+    /**
422
+     * 脱敏敏感数据
423
+     *
424
+     * @param array $data
425
+     * @return array
426
+     */
427
+    protected function maskSensitiveData(array $data): array
428
+    {
429
+        $sensitiveKeys = ['appSecret', 'access_token', 'refreshToken'];
430
+        foreach ($data as $key => $value) {
431
+            if (in_array($key, $sensitiveKeys) && is_string($value)) {
432
+                $data[$key] = substr($value, 0, 4) . '****' . substr($value, -4);
433
+            }
434
+        }
435
+        return $data;
436
+    }
437
+
438
+    /**
439
+     * 获取配置项
440
+     *
441
+     * @param string $key
442
+     * @param mixed $default
443
+     * @return mixed
444
+     */
445
+    protected function getConfig(string $key, $default = null)
446
+    {
447
+        return $this->config[$key] ?? $default;
448
+    }
449
+}

+ 400 - 0
app/services/ThirdParty/AlibabaAgent/OrderService.php

@@ -0,0 +1,400 @@
1
+<?php
2
+/**
3
+ * 1688分销严选采购解决方案 - 订单服务类
4
+ *
5
+ * 提供预下单、下单、订单查询等功能
6
+ *
7
+ * @author: yourname
8
+ * @day: 2026/04/28
9
+ */
10
+
11
+namespace app\services\ThirdParty\AlibabaAgent;
12
+
13
+use app\entity\data\store\StoreOrderEntity;
14
+use app\entity\data\store\StoreOrderStatusEntity;
15
+use think\facade\Db;
16
+use think\facade\Log;
17
+
18
+class OrderService extends AlibabaAgentBaseService
19
+{
20
+    /**
21
+     * API命名空间
22
+     */
23
+    const NAMESPACE = 'com.alibaba.trade';
24
+
25
+    /**
26
+     * 预下单
27
+     *
28
+     * API: com.alibaba.trade:alibaba.createOrder.preview-1
29
+     *
30
+     * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.createOrder.preview-1
31
+     *
32
+     * 请求参数格式:
33
+     *   - flow: 流程标识(saleproxy=分销采购, general=普通下单)
34
+     *   - productList: 商品列表JSON数组,每个商品包含 offerId, quantity, specId
35
+     *   - addressParam: 地址参数JSON对象,包含 fullName, mobile, provinceText, cityText, areaText, address
36
+     *   - totalAmount: 总金额(单位:分,可选)
37
+     *   - message: 买家留言(可选)
38
+     *
39
+     * @param array $params 预下单参数
40
+     * @return array|false
41
+     */
42
+    public function previewOrder(array $params = [])
43
+    {
44
+        $businessParams = [];
45
+
46
+        // --- productList(商品列表,JSON字符串) ---
47
+        // 直接传入已格式化的数组,由方法转为JSON
48
+        if (!empty($params['cargoParamList'])) {
49
+            $businessParams['cargoParamList'] = is_array($params['cargoParamList'])
50
+                ? json_encode($params['cargoParamList'], JSON_UNESCAPED_UNICODE)
51
+                : $params['cargoParamList'];
52
+        }
53
+
54
+        // --- addressParam(地址参数,JSON字符串) ---
55
+        // 直接传入已格式化的数组,由方法转为JSON
56
+        if (!empty($params['addressParam'])) {
57
+            $businessParams['addressParam'] = is_array($params['addressParam'])
58
+                ? json_encode($params['addressParam'], JSON_UNESCAPED_UNICODE)
59
+                : $params['addressParam'];
60
+        }
61
+        // --- 调用API ---
62
+        $result = $this->executeParam2(
63
+            self::NAMESPACE,
64
+            'alibaba.createOrder.preview',
65
+            1,
66
+            $businessParams
67
+        );
68
+
69
+        if ($result === false) {
70
+            Log::channel('alibaba')->error('[预下单] API请求失败', $businessParams);
71
+            return false;
72
+        }
73
+
74
+        Log::channel('alibaba')->info('[预下单] API请求成功', ['result' => json_encode($result)]);
75
+
76
+        return $this->formatPreviewResult($result);
77
+    }
78
+
79
+    /**
80
+     * 格式化预下单返回结果
81
+     *
82
+     * 实际返回结构:
83
+     * {
84
+     *   "orderPreviewResuslt": [{
85
+     *     "tradeModeNameList": ["assureTrade"],
86
+     *     "status": true,
87
+     *     "sumPayment": 370,           // 总支付金额(单位:分)
88
+     *     "sumCarriage": 300,          // 总运费(单位:分)
89
+     *     "sumPaymentNoCarriage": 70,  // 商品总金额(不含运费,单位:分)
90
+     *     "flowFlag": "fenxiaonew",
91
+     *     "cargoList": [{              // 商品列表
92
+     *       "amount": 0.7,
93
+     *       "finalUnitPrice": 0.7,
94
+     *       "specId": "...",
95
+     *       "skuId": 6148579878582,
96
+     *       "offerId": 678279922176,
97
+     *       "openOfferId": "...",
98
+     *       "cargoPromotionList": []
99
+     *     }],
100
+     *     "tradeModelList": [{"name":"担保交易","tradeType":"assureTrade","opSupport":true}],
101
+     *     "payChannelInfos": [{"name":"alipay"}],
102
+     *     "orderGroup": "79d4f82d...",
103
+     *     "canUseOfficialSolution": false
104
+     *   }],
105
+     *   "success": true,
106
+     *   "unsupportedCrossBorderPayOfferList": []
107
+     * }
108
+     *
109
+     * @param array $result API原始返回数据
110
+     * @return array
111
+     */
112
+    protected function formatPreviewResult(array $result): array
113
+    {
114
+        $success = $result['success'] ?? false;
115
+        $previewList = $result['orderPreviewResuslt'] ?? [];
116
+
117
+        if (empty($previewList) || !is_array($previewList)) {
118
+            return [
119
+                'success' => $success,
120
+                'preview' => null,
121
+                'list'    => [],
122
+            ];
123
+        }
124
+
125
+        $formattedList = [];
126
+        foreach ($previewList as $item) {
127
+            $formattedList[] = [
128
+                'sumPayment'           => $item['sumPayment'] ?? 0,
129
+                'sumCarriage'          => $item['sumCarriage'] ?? 0,
130
+                'sumPaymentNoCarriage' => $item['sumPaymentNoCarriage'] ?? 0,
131
+                'flowFlag'             => $item['flowFlag'] ?? '',
132
+                // 'orderGroup'           => $item['orderGroup'] ?? '',
133
+                // 'status'               => $item['status'] ?? false,
134
+                // 'cargoList'            => $item['cargoList'] ?? [],
135
+                // 'tradeModeNameList'    => $item['tradeModeNameList'] ?? [],
136
+                // 'tradeModelList'       => $item['tradeModelList'] ?? [],
137
+                // 'payChannelInfos'      => $item['payChannelInfos'] ?? [],
138
+                // 'shopPromotionList'    => $item['shopPromotionList'] ?? [],
139
+                // 'tradeServiceList'     => $item['tradeServiceList'] ?? [],
140
+                // 'taoSampleSinglePromotion' => $item['taoSampleSinglePromotion'] ?? false,
141
+                // 'canUseOfficialSolution'   => $item['canUseOfficialSolution'] ?? false,
142
+            ];
143
+        }
144
+
145
+        return [
146
+            // 'success'    => $success,
147
+            'preview'    => $formattedList[0] ?? null,
148
+            // 'list'       => $formattedList,
149
+            // 'unsupportedCrossBorderPayOfferList' => $result['unsupportedCrossBorderPayOfferList'] ?? [],
150
+        ];
151
+    }
152
+
153
+    /**
154
+     * 快速创建订单
155
+     *
156
+     * API: com.alibaba.trade:alibaba.trade.fastCreateOrder-1
157
+     *
158
+     * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.fastCreateOrder-1
159
+     *
160
+     * 请求参数:
161
+     *   - flow: 流程标识(saleproxy=分销采购, general=普通下单)
162
+     *   - cargoParamList: 商品列表JSON数组(alibaba.trade.fast.cargo[]),每个商品包含 offerId, quantity, specId
163
+     *   - addressParam: 地址参数JSON对象,包含 fullName, mobile, provinceText, cityText, areaText, address
164
+     *   - totalAmount: 总金额(单位:分,可选)
165
+     *   - message: 买家留言(可选)
166
+     *   - orderGroup: 订单分组标识(从预下单返回的 orderGroup 字段获取,可选)
167
+     *
168
+     * @param array $params 创建订单参数
169
+     * @return array|false
170
+     */
171
+    public function fastCreateOrder(array $params = [])
172
+    {
173
+        $businessParams = [];
174
+
175
+        // --- flow(流程标识) ---
176
+        $businessParams['flow'] = $params['flow'] ?? 'general';
177
+
178
+        // --- cargoParamList(商品列表,JSON字符串) ---
179
+        if (!empty($params['cargoParamList'])) {
180
+            $businessParams['cargoParamList'] = is_array($params['cargoParamList'])
181
+                ? json_encode($params['cargoParamList'], JSON_UNESCAPED_UNICODE)
182
+                : $params['cargoParamList'];
183
+        }
184
+
185
+        // --- addressParam(地址参数,JSON字符串) ---
186
+        if (!empty($params['addressParam'])) {
187
+            $businessParams['addressParam'] = is_array($params['addressParam'])
188
+                ? json_encode($params['addressParam'], JSON_UNESCAPED_UNICODE)
189
+                : $params['addressParam'];
190
+        }
191
+
192
+        // --- 调用API ---
193
+        $result = $this->executeParam2(
194
+            self::NAMESPACE,
195
+            'alibaba.trade.fastCreateOrder',
196
+            1,
197
+            $businessParams
198
+        );
199
+
200
+        if ($result === false) {
201
+            Log::channel('alibaba')->error('[创建订单] API请求失败', $businessParams);
202
+            return false;
203
+        }
204
+
205
+        Log::channel('alibaba')->info('[创建订单] API请求成功', ['result' => json_encode($result)]);
206
+
207
+        return $this->formatCreateOrderResult($result);
208
+    }
209
+
210
+    /**
211
+     * @param StoreOrderEntity[] $storeOrderEntityList
212
+     * @return bool
213
+     * @author 史晨
214
+     * @date 2026/4/30 09:44
215
+     * 支付成功通知1688
216
+     */
217
+    public function paySuccess(array $storeOrderEntityList): bool
218
+    {
219
+        foreach ($storeOrderEntityList as $storeOrderEntity) {
220
+            $alibabaOrderId = $storeOrderEntity->getAlibabaOrderId();
221
+            if (empty($alibabaOrderId)) {
222
+                Log::channel('alibaba')->error($storeOrderEntity->getOrderSn() . '不是1688订单,不需要调取1688支付成功接口');
223
+                continue;
224
+            }
225
+            $res = $this->prepareProtocolPay(['orderId' => $alibabaOrderId]);
226
+            if ($res['success'] === false) {
227
+                return false;
228
+            }
229
+        }
230
+        return true;
231
+    }
232
+
233
+    /**
234
+     * 免密支付准备
235
+     *
236
+     * API: com.alibaba.trade:alibaba.trade.pay.protocolPay.preparePay-1
237
+     *
238
+     * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.pay.protocolPay.preparePay-1
239
+     *
240
+     * 请求参数说明(接口文档定义):
241
+     *   - tradeWithholdPreparePayParam: 必填,类型 message:alibaba.trade.pay.withhold.preparePayParam
242
+     *     preparePayParam 结构:
243
+     *       - orderId: 订单ID(Long,必填)
244
+     *
245
+     * 调用示例:
246
+     *   $orderService->prepareProtocolPay([
247
+     *     'orderId' => 123456789,
248
+     *   ]);
249
+     *
250
+     * @param array $params 免密支付参数(orderId)
251
+     * @return array|false
252
+     */
253
+    public function prepareProtocolPay(array $params = [])
254
+    {
255
+        // --- 参数校验 ---
256
+        if (empty($params['orderId'])) {
257
+            Log::channel('alibaba')->error('[免密支付] orderId不能为空');
258
+            return false;
259
+        }
260
+
261
+        // --- 构建 tradeWithholdPreparePayParam(message类型需序列化为JSON字符串) ---
262
+        $businessParams = [
263
+            'tradeWithholdPreparePayParam' => json_encode([
264
+                'orderId' => (int)$params['orderId'],
265
+            ], JSON_UNESCAPED_UNICODE),
266
+        ];
267
+
268
+        // --- 调用API ---
269
+        $result = $this->executeParam2(
270
+            self::NAMESPACE,
271
+            'alibaba.trade.pay.protocolPay.preparePay',
272
+            1,
273
+            $businessParams
274
+        );
275
+
276
+        if ($result === false) {
277
+            Log::channel('alibaba')->error('[免密支付] API请求失败', $businessParams);
278
+            return false;
279
+        }
280
+
281
+        Log::channel('alibaba')->info('[免密支付] API请求成功', ['result' => json_encode($result)]);
282
+
283
+        return $this->formatProtocolPayResult($result);
284
+    }
285
+
286
+    /**
287
+     * 格式化免密支付准备返回结果
288
+     *
289
+     * @param array $result API原始返回数据
290
+     * @return array
291
+     */
292
+    protected function formatProtocolPayResult(array $result): array
293
+    {
294
+        // 返回结构: { "result": { ... }, "success": true }
295
+        $data = $result['result'] ?? $result;
296
+        $success = $result['success'] ?? false;
297
+
298
+        Log::channel('alibaba')->info('[免密支付] 返回结果', [
299
+            'success' => $success,
300
+            'data'    => $data,
301
+        ]);
302
+
303
+        return [
304
+            'success' => $success,
305
+            'data'    => $data,
306
+            'rawData' => $result,
307
+        ];
308
+    }
309
+
310
+    /**
311
+     * 格式化创建订单返回结果
312
+     *
313
+     * @param array $result API原始返回数据
314
+     * @return array
315
+     */
316
+    protected function formatCreateOrderResult(array $result): array
317
+    {
318
+        // 创建订单成功返回: { "result": { "orderId": "123456", "totalAmount": 370 }, "success": true }
319
+        $data = $result['result'] ?? $result;
320
+        $success = $result['success'] ?? false;
321
+
322
+        return [
323
+            'success'     => $success,
324
+            'orderId'     => $data['orderId'] ?? 0,
325
+            'totalAmount' => $data['totalAmount'] ?? 0,
326
+            'rawData'     => $data,
327
+        ];
328
+    }
329
+
330
+    /**
331
+     * 构建商品列表参数(便捷方法)
332
+     *
333
+     * @param array $items 商品列表 [['offerId' => 123, 'quantity' => 2, 'skuId' => 456], ...]
334
+     * @return array
335
+     */
336
+    public function buildProductList(array $items): array
337
+    {
338
+        $productList = [];
339
+        foreach ($items as $item) {
340
+            $product = [
341
+                'offerId'  => (int)($item['offerId'] ?? $item['productId'] ?? 0),
342
+                'quantity' => (int)($item['quantity'] ?? 1),
343
+            ];
344
+            if (!empty($item['skuId'])) {
345
+                $product['skuId'] = (int)$item['skuId'];
346
+            }
347
+            if (!empty($item['price'])) {
348
+                $product['price'] = (int)$item['price']; // 单位:分
349
+            }
350
+            if (!empty($item['productUnit'])) {
351
+                $product['productUnit'] = $item['productUnit'];
352
+            }
353
+            $productList[] = $product;
354
+        }
355
+        return $productList;
356
+    }
357
+
358
+    /**
359
+     * 构建地址参数(便捷方法)
360
+     *
361
+     * @param int $addressId 地址ID
362
+     * @return array
363
+     */
364
+    public function buildAddressParam(int $addressId): array
365
+    {
366
+        return [
367
+            'addressId' => $addressId,
368
+        ];
369
+    }
370
+
371
+    /**
372
+     * 构建完整地址参数(使用系统地址数据格式)
373
+     *
374
+     * 适配系统 user_address 表字段:
375
+     *   address_id, real_name, phone, province, city, district, detail, post_code, code_list
376
+     *
377
+     * @param array $address 地址信息(系统user_address表数据)
378
+     * @return array
379
+     */
380
+    public function buildFullAddressParam(array $address): array
381
+    {
382
+        // 解析地区编码列表: "1,67449,35,493"
383
+        $codeList = $address['code_list'] ?? '';
384
+        $codes = explode(',', $codeList);
385
+        $districtCode = $codes[1] ?? $codes[0] ?? '';
386
+
387
+        return [
388
+            'fullName'       => $address['real_name'] ?? $address['fullName'] ?? '',
389
+            'mobile'         => $address['phone'] ?? $address['mobile'] ?? '',
390
+            'phone'          => $address['phone'] ?? $address['phone'] ?? '',
391
+            'postCode'       => $address['post_code'] ?? $address['postCode'] ?? '',
392
+            'cityText'       => $address['city'] ?? $address['cityText'] ?? '',
393
+            'provinceText'   => $address['province'] ?? $address['provinceText'] ?? '',
394
+            'areaText'       => $address['district'] ?? $address['areaText'] ?? '',
395
+            'townText'       => $address['village'] ?? $address['townText'] ?? '',
396
+            'address'        => $address['detail'] ?? $address['address'] ?? '',
397
+            'districtCode'   => $districtCode,
398
+        ];
399
+    }
400
+}

+ 738 - 0
app/services/ThirdParty/AlibabaAgent/ProductService.php

@@ -0,0 +1,738 @@
1
+<?php
2
+/**
3
+ * 1688分销严选采购解决方案 - 商品服务类
4
+ *
5
+ * 提供商品搜索/列表查询、详情获取、批量入库等功能
6
+ *
7
+ * @author: yourname
8
+ * @day: 2026/04/27
9
+ */
10
+
11
+namespace app\services\ThirdParty\AlibabaAgent;
12
+
13
+use think\facade\Db;
14
+use think\facade\Log;
15
+
16
+class ProductService extends AlibabaAgentBaseService
17
+{
18
+    /**
19
+     * 获取分销商品分页列表
20
+     *
21
+     * API: com.alibaba.fenxiao:jxhy.product.getPageList-1
22
+     *
23
+     * @param array $params 搜索参数
24
+     * @return array|false
25
+     */
26
+    public function searchProducts(array $params = [])
27
+    {
28
+        $businessParams = [];
29
+
30
+        if (!empty($params['keyword'])) {
31
+            $businessParams['keyword'] = $params['keyword'];
32
+        }
33
+        if (!empty($params['categoryId'])) {
34
+            $businessParams['categoryId'] = $params['categoryId'];
35
+        }
36
+        $businessParams['pageNum'] = $params['page'] ?? 1;
37
+        $businessParams['pageSize'] = min($params['limit'] ?? 20, 50);
38
+        if (!empty($params['sort'])) {
39
+            $businessParams['sort'] = $params['sort'];
40
+        }
41
+        if (!empty($params['minPrice']) && $params['minPrice'] > 0) {
42
+            $businessParams['priceStart'] = $params['minPrice'];
43
+        }
44
+        if (!empty($params['maxPrice']) && $params['maxPrice'] > 0) {
45
+            $businessParams['priceEnd'] = $params['maxPrice'];
46
+        }
47
+
48
+        $result = $this->executeParam2(
49
+            'com.alibaba.fenxiao',
50
+            'jxhy.product.getPageList',
51
+            1,
52
+            $businessParams
53
+        );
54
+
55
+        if ($result === false) {
56
+            return false;
57
+        }
58
+
59
+        return $this->formatSearchResult($result);
60
+    }
61
+
62
+    /**
63
+     * 格式化搜索结果
64
+     *
65
+     * @param array $result
66
+     * @return array
67
+     */
68
+    protected function formatSearchResult(array $result): array
69
+    {
70
+        $productList = [];
71
+        $data = $result['result'] ?? $result;
72
+        $products = $data['result'] ?? [];
73
+        $pageInfo = $data['pageInfo'] ?? [];
74
+
75
+        if (empty($products)) {
76
+            return [
77
+                'total'    => 0,
78
+                'page'     => (int)($pageInfo['currentPage'] ?? 1),
79
+                'pageSize' => (int)($pageInfo['pageSize'] ?? 20),
80
+                'list'     => [],
81
+            ];
82
+        }
83
+
84
+        foreach ($products as $product) {
85
+            $productList[] = $this->formatProduct($product);
86
+        }
87
+
88
+        return [
89
+            'total'    => (int)($pageInfo['totalRecords'] ?? count($productList)),
90
+            'page'     => (int)($pageInfo['currentPage'] ?? 1),
91
+            'pageSize' => (int)($pageInfo['pageSize'] ?? 20),
92
+            'list'     => $productList,
93
+        ];
94
+    }
95
+
96
+    /**
97
+     * 格式化商品数据为统一格式
98
+     *
99
+     * @param array $product
100
+     * @return array
101
+     */
102
+    public function formatProduct(array $product): array
103
+    {
104
+        $mainImage = $product['imgUrl'] ?? $product['mainImage'] ?? $product['mainPicture'] ?? '';
105
+
106
+        $serviceTags = [];
107
+        if (!empty($product['serviceList'])) {
108
+            foreach ($product['serviceList'] as $service) {
109
+                $serviceTags[] = $service['name'] ?? '';
110
+            }
111
+        }
112
+
113
+        // 判断是否加入商品池
114
+        $product['importStatus'] = 0;
115
+        $import = Db::name('alibaba_import_goods')->where('product_id', $product['itemId'])->find();
116
+        if ($import) {
117
+            $product['importStatus'] = 1;
118
+        }
119
+        return [
120
+            'productId'        => $product['itemId'] ?? $product['productId'] ?? $product['offerId'] ?? $product['id'] ?? 0,
121
+            'productType'      => $product['productType'] ?? '',
122
+            'categoryId'       => $product['categoryId'] ?? $product['catId'] ?? 0,
123
+            'categoryName'     => $product['categoryName'] ?? '',
124
+            'title'            => $product['title'] ?? $product['subject'] ?? $product['name'] ?? '',
125
+            'description'      => $product['description'] ?? $product['detail'] ?? '',
126
+            'price'            => $product['minPrice'] ?? $product['price'] ?? $product['offerPrice'] ?? 0,
127
+            'salePrice'        => $product['maxPrice'] ?? $product['salePrice'] ?? $product['price'] ?? 0,
128
+            'minPrice'         => $product['minPrice'] ?? $product['price'] ?? 0,
129
+            'maxPrice'         => $product['maxPrice'] ?? $product['price'] ?? 0,
130
+            'stock'            => $product['stock'] ?? $product['totalAvailableStock'] ?? 0,
131
+            'unit'             => $product['unit'] ?? '件',
132
+            'mainImage'        => $mainImage,
133
+            'imageList'        => [$mainImage],
134
+            'skuCnt'           => $product['skuCnt'] ?? 0,
135
+            'monthSales'       => $product['monthSales'] ?? $product['salesCnt90d'] ?? 0,
136
+            'totalSales'       => $product['totalSales'] ?? 0,
137
+            'serviceTags'      => $serviceTags,
138
+            'isDistribution'   => $product['isDistribution'] ?? false,
139
+            'isStrictSelected' => $product['isStrictSelected'] ?? false,
140
+            'distributorPrice' => $product['distributorPrice'] ?? 0,
141
+            'shopName'         => $product['shopName'] ?? $product['companyName'] ?? '',
142
+            'shopId'           => $product['shopId'] ?? $product['memberId'] ?? '',
143
+            'detailUrl'        => $product['detailUrl'] ?? $product['offerUrl'] ?? '',
144
+            'importStatus'     => $product['importStatus'],
145
+        ];
146
+    }
147
+
148
+    // ================================================================
149
+    //  商品详情
150
+    // ================================================================
151
+
152
+    /**
153
+     * 获取分销商品详情(含SKU、图片等完整信息)
154
+     *
155
+     * API: com.alibaba.fenxiao:alibaba.pifatuan.product.detail.list-2
156
+     *
157
+     * @param int|string $productId 1688商品ID
158
+     * @return array|false 格式化后的商品详情数据
159
+     */
160
+    public function getProductDetail($productId)
161
+    {
162
+        if (empty($productId)) {
163
+            $this->logError('参数错误', 'productId不能为空');
164
+            return false;
165
+        }
166
+
167
+        $businessParams = [
168
+            'offerIds' => '[' . $productId . ']',
169
+        ];
170
+
171
+        $result = $this->executeParam2(
172
+            'com.alibaba.fenxiao',
173
+            'alibaba.pifatuan.product.detail.list',
174
+            2,
175
+            $businessParams
176
+        );
177
+
178
+        if ($result === false) {
179
+            return false;
180
+        }
181
+
182
+        return $this->formatDetailResult($result);
183
+    }
184
+
185
+    /**
186
+     * 格式化商品详情结果
187
+     *
188
+     * 实际API返回结构:
189
+     * {
190
+     *   "result": {
191
+     *     "success": true,
192
+     *     "result": [
193
+     *       {
194
+     *         "wangwangAccount": "...",
195
+     *         "productInfo": {
196
+     *           "productID": 635835395997,
197
+     *           "subject": "商品标题",
198
+     *           "image": { "images": ["img/ibank/..."] },
199
+     *           "saleInfo": { "amountOnSale": 536989, "priceRanges": [...], "unit": "PCS" },
200
+     *           "skuInfos": [{ "skuId": 5580608617712, "consignPrice": 4.14, "pifaPrice": 0.14, ... }],
201
+     *           "categoryID": 202060914,
202
+     *           "categoryName": "晶体振荡器",
203
+     *           "description": "<div>..."
204
+     *         },
205
+     *         "tagInfoList": ["一件代发", "一件代发包邮"],
206
+     *         "sellerOpenUid": "BBBAAvFPtiSWOJWNj2-MpLo4w"
207
+     *       }
208
+     *     ]
209
+     *   }
210
+     * }
211
+     *
212
+     * @param array $result
213
+     * @return array
214
+     */
215
+    protected function formatDetailResult(array $result): array
216
+    {
217
+        $data = $result['result'] ?? $result;
218
+        $items = $data['result'] ?? [];
219
+
220
+        if (empty($items) || !is_array($items)) {
221
+            return [];
222
+        }
223
+
224
+        // 取第一个商品(API按offerIds查询单个商品)
225
+        $item = $items[0] ?? [];
226
+        if (empty($item)) {
227
+            return [];
228
+        }
229
+
230
+        $productInfo = $item['productInfo'] ?? [];
231
+        $tagInfoList = $item['tagInfoList'] ?? [];
232
+        $wangwangAccount = $item['wangwangAccount'] ?? '';
233
+
234
+        // --- 图片处理 ---
235
+        $imageData = $productInfo['image'] ?? [];
236
+        $rawImages = $imageData['images'] ?? [];
237
+        $imageList = [];
238
+        $mainImage = '';
239
+        if (!empty($rawImages)) {
240
+            foreach ($rawImages as $img) {
241
+                $fullUrl = $this->buildImageUrl($img);
242
+                $imageList[] = $fullUrl;
243
+                if (empty($mainImage)) {
244
+                    $mainImage = $fullUrl;
245
+                }
246
+            }
247
+        }
248
+
249
+        // --- 价格处理(取SKU规格价格) ---
250
+        $saleInfo = $productInfo['saleInfo'] ?? [];
251
+        $skuInfos = $productInfo['skuInfos'] ?? [];
252
+        $minPrice = 0;
253
+        $maxPrice = 0;
254
+        if (!empty($skuInfos)) {
255
+            $skuPrices = [];
256
+            foreach ($skuInfos as $sku) {
257
+                $skuPrice = (float)($sku['consignPrice'] ?? $sku['pifaPrice'] ?? 0);
258
+                if ($skuPrice > 0) {
259
+                    $skuPrices[] = $skuPrice;
260
+                }
261
+            }
262
+            if (!empty($skuPrices)) {
263
+                $minPrice = (float)min($skuPrices);
264
+                $maxPrice = (float)max($skuPrices);
265
+            }
266
+        }
267
+
268
+        // --- 商品属性(如品牌、货号、规格等) ---
269
+        $attributes = $productInfo['attributes'] ?? [];
270
+
271
+        // --- SKU处理(提取规格名称、规格值、成本价) ---
272
+        $skuInfos = $productInfo['skuInfos'] ?? [];
273
+        $formattedSkus = [];
274
+        if (!empty($skuInfos)) {
275
+            foreach ($skuInfos as $sku) {
276
+                // 提取规格属性(规则名称 + 规格值名称)
277
+                $skuAttrs = $sku['attributes'] ?? [];
278
+                $specNames = [];
279
+                $specValues = [];
280
+                $skuAttrList = [];
281
+                foreach ($skuAttrs as $attr) {
282
+                    $attrName = $attr['attributeName'] ?? '';
283
+                    $attrValue = $attr['attributeValue'] ?? '';
284
+                    if ($attrName !== '') {
285
+                        $specNames[] = $attrName;
286
+                        $specValues[] = $attrValue;
287
+                        $skuAttrList[] = [
288
+                            'name'  => $attrName,
289
+                            'value' => $attrValue,
290
+                        ];
291
+                    }
292
+                }
293
+
294
+                // 成本价(consignPrice 是分销价/代发价)
295
+                $costPrice = (float)($sku['consignPrice'] ?? $sku['pifaPrice'] ?? 0);
296
+
297
+                $formattedSkus[] = [
298
+                    'skuId'        => $sku['skuId'] ?? '',
299
+                    'specId'       => $sku['specId'] ?? '',
300
+                    'consignPrice' => $costPrice,
301
+                    'pifaPrice'    => (float)($sku['pifaPrice'] ?? 0),
302
+                    'costPrice'    => $costPrice,
303
+                    'stock'        => $sku['amountOnSale'] ?? 0,
304
+                    'cargoNumber'  => $sku['cargoNumber'] ?? '',
305
+                    // 规格信息
306
+                    'specNames'    => $specNames,
307
+                    'specValues'   => $specValues,
308
+                    'specInfo'     => !empty($skuAttrList) ? json_encode($skuAttrList, JSON_UNESCAPED_UNICODE) : ($sku['specInfo'] ?? ''),
309
+                    'skuAttrs'     => $skuAttrList,
310
+                    // 规格图片
311
+                    'image'        => !empty($skuAttrs[0]['skuImageUrl'] ?? '') ? $this->buildImageUrl($skuAttrs[0]['skuImageUrl']) : '',
312
+                ];
313
+            }
314
+        }else{
315
+
316
+        }
317
+
318
+        // --- 描述处理 ---
319
+        $description = $productInfo['description'] ?? '';
320
+
321
+        return [
322
+            'productId'     => $productInfo['productID'] ?? $productInfo['productId'] ?? 0,
323
+            'categoryId'    => $productInfo['categoryID'] ?? $productInfo['categoryId'] ?? 0,
324
+            'categoryName'  => $productInfo['categoryName'] ?? '',
325
+            'title'         => $productInfo['subject'] ?? '',
326
+            'description'   => $description,
327
+            'price'         => $minPrice,
328
+            'minPrice'      => $minPrice,
329
+            'maxPrice'      => $maxPrice,
330
+            'sale'          => $saleInfo,
331
+            'stock'         => $saleInfo['amountOnSale'] ?? 0,
332
+            'unit'          => $saleInfo['unit'] ?? '件',
333
+            'mainImage'     => $mainImage,
334
+            'imageList'     => $imageList,
335
+            'attributes'    => $attributes,
336
+            'skuList'       => $formattedSkus,
337
+            'shopName'      => $wangwangAccount,
338
+            'sellerOpenUid' => $item['sellerOpenUid'] ?? '',
339
+            'serviceTags'   => $tagInfoList,
340
+        ];
341
+    }
342
+
343
+    /**
344
+     * 构建图片完整URL
345
+     *
346
+     * @param string $path 图片路径
347
+     * @return string
348
+     */
349
+    protected function buildImageUrl(string $path): string
350
+    {
351
+        if (empty($path)) {
352
+            return '';
353
+        }
354
+        if (strpos($path, 'http://') === 0 || strpos($path, 'https://') === 0) {
355
+            return $path;
356
+        }
357
+        // 1688 图片路径为相对路径如 img/ibank/O1CN01...,需拼接阿里云CDN域名
358
+        return 'https://cbu01.alicdn.com/' . ltrim($path, '/');
359
+    }
360
+
361
+    // ================================================================
362
+    //  批量入库
363
+    // ================================================================
364
+
365
+    /**
366
+     * 对价格进行加价处理(加价50%)
367
+     *
368
+     * @param float $price 原价
369
+     * @return float 加价后的价格
370
+     */
371
+    protected function applyMarkup(float $price): float
372
+    {
373
+        return round($price * 1.5, 2);
374
+    }
375
+
376
+    // ================================================================
377
+    //  商品入库到 store_product 表
378
+    // ================================================================
379
+
380
+    /**
381
+     * 将 alibaba_import_goods 表中的商品数据写入 store_product 系列表
382
+     *
383
+     * 业务场景:从 alibaba_import_goods 表中选择已入库的1688商品,正式写入到
384
+     * rrx_store_product、rrx_store_product_attr、rrx_store_product_attr_value 表中
385
+     *
386
+     * @param int $importGoodsId alibaba_import_goods 表的主键ID
387
+     * @param int $merId         商户ID(目前写死3447)
388
+     * @return array ['code' => 200|400, 'msg' => '...', 'product_id' => int]
389
+     */
390
+    public function importToStoreProduct(int $importGoodsId, int $merId): array
391
+    {
392
+        try {
393
+            // 1. 读取 alibaba_import_goods 记录
394
+            $importGoods = Db::name('alibaba_import_goods')
395
+                ->where('id', $importGoodsId)
396
+                ->where('status', 1)
397
+                ->find();
398
+
399
+            if (empty($importGoods)) {
400
+                return ['code' => 400, 'msg' => '商品不存在或已下架', 'product_id' => 0];
401
+            }
402
+
403
+            // 2. 检查是否已入库(按 product_id 去重)
404
+            $exists = Db::name('store_product')
405
+                ->where('mer_id', $merId)
406
+                ->where('spu_id', (string)$importGoods['product_id'])
407
+                ->where('is_del', 0)
408
+                ->value('product_id');
409
+
410
+            if ($exists) {
411
+                return ['code' => 400, 'msg' => '商品已入库,product_id=' . $exists, 'product_id' => $exists];
412
+            }
413
+
414
+            // 3. 解析 product_info JSON
415
+            $productInfo = json_decode($importGoods['product_info'], true) ?: [];
416
+            if (empty($productInfo)) {
417
+                return ['code' => 400, 'msg' => '商品数据解析失败', 'product_id' => 0];
418
+            }
419
+
420
+            $skuList = $productInfo['skuList'] ?? [];
421
+            $imageList = $productInfo['imageList'] ?? [];
422
+            $mainImage = $productInfo['mainImage'] ?? $importGoods['image'] ?? '';
423
+            $title = $productInfo['title'] ?? $importGoods['product_name'] ?? '';
424
+            $description = $productInfo['description'] ?? '';
425
+
426
+            // 4. 判断是否为多规格
427
+            $specType = count($skuList) > 1 ? 1 : 0;
428
+
429
+            // 5. 轮播图处理
430
+            $sliderImage = !empty($imageList) ? implode(',', $imageList) : $mainImage;
431
+
432
+            // 6. 取最低价和最高价作为 price 和 ot_price
433
+            $prices = array_column($skuList, 'consignPrice');
434
+            $minPrice = !empty($prices) ? (float)min($prices) : 0;
435
+            $maxPrice = !empty($prices) ? (float)max($prices) : 0;
436
+            // 成本价取第一个SKU的成本价
437
+            $costPrice = $skuList[0]['costPrice'] ?? 0;
438
+            if (empty($skuList)) {
439
+                $prices = $productInfo['price'];
440
+                $minPrice = $prices;
441
+                $maxPrice = $prices;
442
+                $costPrice = $prices;
443
+            }
444
+
445
+            // 7. 写入 store_product 表
446
+            $productData = [
447
+                'mer_id'         => $merId,
448
+                'image'          => $mainImage,
449
+                'slider_image'   => $sliderImage,
450
+                'store_name'     => $title,
451
+                'store_info'     => mb_substr($title, 0, 100),
452
+                'keyword'        => mb_substr($title, 0, 10),
453
+                'is_show'        => 0,
454
+                'status'         => 1,
455
+                'cate_id'        => 0,
456
+                'unit_name'      => $productInfo['unit'] ?? '件',
457
+                'price'          => $minPrice,
458
+                'cost'           => $costPrice,
459
+                'ot_price'       => $maxPrice,
460
+                'stock'          => 9999,
461
+                'spec_type'      => $specType,
462
+                'extension_type' => 0,
463
+                'mer_status'     => 1,
464
+                'is_used'        => 1,
465
+                'volunteer'      => 0,
466
+                'type'           => 1,
467
+                'pension'        => '0.00',
468
+                'commission'     => '',
469
+                'spu_id'         => (string)$importGoods['product_id'],
470
+                'temp_id'        => 105,
471
+                'concession_pri' => '0.00',
472
+                'cost_price'     => $costPrice,
473
+                'is_alibaba'     => 1
474
+            ];
475
+            $productId = Db::name('store_product')->insertGetId($productData);
476
+
477
+            if (!$productId) {
478
+                return ['code' => 400, 'msg' => '写入store_product表失败', 'product_id' => 0];
479
+            }
480
+
481
+            // 8. 写入 store_product_content 表(商品详情)
482
+            $contentHtml = $description;
483
+            Db::name('store_product_content')->insert([
484
+                'product_id' => $productId,
485
+                'content'    => $contentHtml,
486
+                'type'       => 0,
487
+            ]);
488
+
489
+            // 9. 写入 store_product_attr 和 store_product_attr_value
490
+            $this->insertStoreSkus($productId, $skuList, $merId, $costPrice,$mainImage);
491
+
492
+            Log::channel('alibaba')->info("[1688正式入库1] 商品{$importGoods['product_id']}入库成功,product_id={$productId},mer_id={$merId}");
493
+
494
+            return [
495
+                'code'       => 200,
496
+                'msg'        => '入库成功',
497
+                'product_id' => $productId,
498
+            ];
499
+        } catch (\Throwable $e) {
500
+            Log::channel('alibaba')->error("[1688正式入库2] 异常: " . $e->getMessage());
501
+            return ['code' => 400, 'msg' => '入库异常: ' . $e->getMessage(), 'product_id' => 0];
502
+        }
503
+    }
504
+
505
+    /**
506
+     * 写入 store_product_attr 和 store_product_attr_value
507
+     *
508
+     * @param int   $productId
509
+     * @param array $skuList    formatDetailResult 返回的 skuList
510
+     * @param int   $merId
511
+     */
512
+    protected function insertStoreSkus(int $productId, array $skuList, int $merId, $costPrice, $mainImage): void
513
+    {
514
+        if (empty($skuList)) {
515
+            // 单规格:写入默认规格
516
+            Db::name('store_product_attr')->insert([
517
+                'product_id'  => $productId,
518
+                'attr_name'   => '规格',
519
+                'attr_values' => '',
520
+                'type'        => 0,
521
+            ]);
522
+
523
+            $unique = $this->generateUnique($productId, 'default');
524
+            Db::name('store_product_attr_value')->insert([
525
+                'product_id'  => $productId,
526
+                'detail'      => json_encode(['规格' => '默认'], JSON_UNESCAPED_UNICODE),
527
+                'sku'         => '默认',
528
+                'image'       => $mainImage,
529
+                'cost'        => $costPrice,
530
+                'ot_price'    => $costPrice,
531
+                'price'       => $costPrice,
532
+                'unique'      => $unique,
533
+                'stock'       => 9999,
534
+                'cost_price'  => 0,
535
+                'extension_one' => 5,
536
+                'gong_sku_id' => '',
537
+                'gong_mer_profit' => 0,
538
+                'gong_pension'    => 0,
539
+                'gong_market_price' => 0,
540
+                'plate_mer_profit' => 0,
541
+                'volume' => 0,
542
+                'weight' => 0,
543
+                'dacang_price' => 0
544
+            ]);
545
+            return;
546
+        }
547
+
548
+        // 多规格处理
549
+        // 收集所有规格名称和值
550
+        $attrNames = [];    // ['颜色', '尺寸']
551
+        $attrValueMap = []; // ['颜色' => ['红', '蓝'], '尺寸' => ['S', 'M']]
552
+
553
+        foreach ($skuList as $sku) {
554
+            $skuAttrs = $sku['skuAttrs'] ?? [];
555
+            foreach ($skuAttrs as $attr) {
556
+                $name = $attr['name'] ?? '';
557
+                $value = $attr['value'] ?? '';
558
+                if ($name === '') continue;
559
+                if (!in_array($name, $attrNames)) {
560
+                    $attrNames[] = $name;
561
+                }
562
+                if (!isset($attrValueMap[$name])) {
563
+                    $attrValueMap[$name] = [];
564
+                }
565
+                if (!in_array($value, $attrValueMap[$name])) {
566
+                    $attrValueMap[$name][] = $value;
567
+                }
568
+            }
569
+        }
570
+
571
+        // 写入 store_product_attr
572
+        foreach ($attrNames as $attrName) {
573
+            $attrValues = $attrValueMap[$attrName] ?? [];
574
+            Db::name('store_product_attr')->insert([
575
+                'product_id'  => $productId,
576
+                'attr_name'   => $attrName,
577
+                'attr_values' => implode('-!-', $attrValues),
578
+                'type'        => 0,
579
+            ]);
580
+        }
581
+
582
+        // 写入 store_product_attr_value
583
+        foreach ($skuList as $sku) {
584
+            $skuAttrs = $sku['skuAttrs'] ?? [];
585
+            $detailArr = [];
586
+            $skuKey = '';
587
+            foreach ($skuAttrs as $attr) {
588
+                $name = $attr['name'] ?? '';
589
+                $value = $attr['value'] ?? '';
590
+                if ($name !== '') {
591
+                    $detailArr[$name] = $value;
592
+                    $skuKey .= $value;
593
+                }
594
+            }
595
+            if (empty($detailArr)) {
596
+                $detailArr = ['规格' => '默认'];
597
+                $skuKey = '默认';
598
+            }
599
+
600
+            $skuId = $sku['skuId'] ?? '';
601
+            $specId = $sku['specId'] ?? '';
602
+            $unique = $this->generateUnique($productId, $skuId ?: $skuKey);
603
+
604
+            Db::name('store_product_attr_value')->insert([
605
+                'product_id'  => $productId,
606
+                'detail'      => json_encode($detailArr, JSON_UNESCAPED_UNICODE),
607
+                'sku'         => $skuKey,
608
+                'image'       => $sku['image'] ?? '',
609
+                'cost'        => $sku['costPrice'] ?? 0,
610
+                'ot_price'    => $sku['consignPrice'] ?? 0,
611
+                'price'       => $sku['costPrice'] ?? 0,
612
+                'unique'      => $unique,
613
+                'stock'       => $sku['stock'] ?? 9999,
614
+                'cost_price'  => $sku['costPrice'] ?? 0,
615
+                'extension_one' => 5,
616
+                'gong_sku_id' => $skuId,
617
+                'gong_mer_profit' => 0,
618
+                'gong_pension'    => 0,
619
+                'gong_market_price' => $sku['consignPrice'] ?? 0,
620
+                'plate_mer_profit' => 0,
621
+                'volume' => 0,
622
+                'weight' => 0,
623
+                'dacang_price' => 0,
624
+                'sku_id' => $skuId,
625
+                'spec_id' => $specId
626
+            ]);
627
+        }
628
+    }
629
+
630
+    /**
631
+     * 生成唯一值 unique
632
+     *
633
+     * @param int    $productId
634
+     * @param string $sku
635
+     * @return string
636
+     */
637
+    protected function generateUnique(int $productId, string $sku): string
638
+    {
639
+        return substr(md5($sku . $productId), 12, 11) . '0';
640
+    }
641
+
642
+    /**
643
+     * 批量导入1688商品到 alibaba_import_goods 表
644
+     *
645
+     * 业务场景:从1688商品列表中选择多个商品,一键入库
646
+     * 将商品详情数据以JSON格式完整存储到 alibaba_import_goods 表
647
+     * 入库时自动对所有价格加价50%
648
+     *
649
+     * @param array $productIds 1688商品ID数组,如 [635835395997, 635835395998]
650
+     * @param int   $merId      商户ID
651
+     * @param array $extra      额外参数(预留)
652
+     * @return array ['success' => [productId => localId], 'fail' => [productId => reason]]
653
+     */
654
+    public function batchImport(array $productIds, int $merId, array $extra = []): array
655
+    {
656
+        $success = [];
657
+        $fail = [];
658
+        $now = date('Y-m-d H:i:s');
659
+
660
+        foreach ($productIds as $productId) {
661
+            try {
662
+                // 检查是否已入库(product_id唯一)
663
+                $exists = Db::name('alibaba_import_goods')
664
+                    ->where('product_id', (int)$productId)
665
+                    ->where('status', 1)
666
+                    ->find();
667
+
668
+                if ($exists) {
669
+                    $success[$productId] = $exists['id'];
670
+                    Log::channel('alibaba')->info("[1688入库] 商品{$productId}已存在,跳过入库,本地ID:{$exists['id']}");
671
+                    continue;
672
+                }
673
+
674
+                // 调用详情API获取完整数据
675
+                $detail = $this->getProductDetail($productId);
676
+                if ($detail === false || empty($detail)) {
677
+                    $fail[$productId] = '获取1688商品详情失败';
678
+                    continue;
679
+                }
680
+
681
+                // ===== 加价处理:所有价格加价50% =====
682
+                // 1. SKU级别成本价加价
683
+                $skuList = $detail['skuList'] ?? [];
684
+                foreach ($skuList as &$sku) {
685
+                    $sku['originalCostPrice'] = $sku['costPrice'] ?? 0;
686
+                    $sku['costPrice'] = $this->applyMarkup((float)$sku['costPrice']);
687
+                    $sku['originalConsignPrice'] = $sku['consignPrice'] ?? 0;
688
+                    $sku['consignPrice'] = $this->applyMarkup((float)$sku['consignPrice']);
689
+                    $sku['originalPifaPrice'] = $sku['pifaPrice'] ?? 0;
690
+                    $sku['pifaPrice'] = $this->applyMarkup((float)$sku['pifaPrice']);
691
+                }
692
+                unset($sku);
693
+                $detail['skuList'] = $skuList;
694
+
695
+                // 2. 商品级别价格加价
696
+                if ($detail['minPrice'] == 0) $detail['minPrice'] = $detail['sale']['consignPrice'];
697
+                if ($detail['maxPrice'] == 0) $detail['maxPrice'] = $detail['sale']['consignPrice'];
698
+                if ($detail['price'] == 0) $detail['price'] = $detail['sale']['consignPrice'];
699
+                $detail['originalMinPrice'] = $detail['minPrice'] ?? 0;
700
+                $detail['originalMaxPrice'] = $detail['maxPrice'] ?? 0;
701
+                $detail['originalPrice'] = $detail['price'] ?? 0;
702
+                $detail['minPrice'] = $this->applyMarkup((float)$detail['minPrice']);
703
+                $detail['maxPrice'] = $this->applyMarkup((float)$detail['maxPrice']);
704
+                $detail['price'] = $this->applyMarkup((float)$detail['price']);
705
+
706
+                // 3. 标记加价比例
707
+                $detail['markupRate'] = 50;
708
+                $detail['markupMultiplier'] = 1.5;
709
+
710
+                // 写入 alibaba_import_goods 表(完整JSON存储,含加价后价格)
711
+                $insertData = [
712
+                    'product_id'   => (int)$productId,
713
+                    'product_name' => $detail['title'] ?? '',
714
+                    'image'        => $detail['mainImage'] ?? '',
715
+                    'product_info' => json_encode($detail, JSON_UNESCAPED_UNICODE),
716
+                    'status'       => 1,
717
+                    'create_time'  => $now,
718
+                ];
719
+                $localId = Db::name('alibaba_import_goods')->insertGetId($insertData);
720
+
721
+                if ($localId) {
722
+                    $success[$productId] = $localId;
723
+                    Log::channel('alibaba')->info("[1688入库] 商品{$productId}入库成功,本地ID:{$localId},已加价50%");
724
+                } else {
725
+                    $fail[$productId] = '写入数据库失败';
726
+                }
727
+            } catch (\Throwable $e) {
728
+                $fail[$productId] = '异常: ' . $e->getMessage();
729
+                Log::channel('alibaba')->error("[1688入库] 商品{$productId}入库异常: " . $e->getMessage());
730
+            }
731
+        }
732
+
733
+        return [
734
+            'success' => $success,
735
+            'fail'    => $fail,
736
+        ];
737
+    }
738
+}

+ 6 - 0
config/log.php

@@ -47,6 +47,12 @@ return [
47 47
             'path' => app()->getRootPath().'runtime/gong',
48 48
             'time_format' => 'Y-m-d H:i:s',
49 49
             'format' => '[%s][%s]:%s'
50
+        ],
51
+        'alibaba' => [
52
+            'type' => 'File',
53
+            'path' => app()->getRootPath().'runtime/alibaba',
54
+            'time_format' => 'Y-m-d H:i:s',
55
+            'format' => '[%s][%s]:%s'
50 56
         ]
51 57
     ],
52 58
 

+ 18 - 0
config/third_party.php

@@ -8,5 +8,23 @@ return [
8 8
         'adzoneId' => 116174700455,
9 9
         // 商品筛选-后台类目ID。用,分割,最大10个
10 10
         'cat' => '50016422,50012486,50008164,50002766,50050359,50022703,50000436,50011130,50011277,50020579'
11
+    ],
12
+    // 1688分销严选采购解决方案(分销买家版)
13
+    'alibaba_agent' => [
14
+        'appKey' => '6512262',
15
+        'appSecret' => 'uNPRZE7895',
16
+        'accessToken' => 'dc8eab20-f10f-4688-9e45-9ebfa182c7c2',
17
+        // 授权回调地址
18
+        // 'redirectUri' => 'https://yourdomain.com/api/alibaba/callback',
19
+        // 1688 API网关地址
20
+        'gatewayUrl' => 'https://gw.open.1688.com/openapi/',
21
+        // 协议版本
22
+        'version' => '1',
23
+        // 分销买家ID(在1688分销平台获取)
24
+        'distributorId' => '',
25
+        // 日志开关
26
+        'logEnabled' => true,
27
+        // 日志目录
28
+        'logPath' => runtime_path() . 'alibaba/',
11 29
     ]
12 30
 ];

+ 7 - 7
extend/applet/WXBizDataCrypt.php

@@ -38,20 +38,20 @@ class WXBizDataCrypt
38 38
 	public function decryptData( $encryptedData, $iv, &$data )
39 39
 	{
40 40
 	    Db::name('log')->insert(['data'=>'进来了']);
41
-		if (strlen($this->sessionKey) != 24) {
42
-			return ErrorCode::$IllegalAesKey;
43
-		}
41
+		// if (strlen($this->sessionKey) != 24) {
42
+		// 	return ErrorCode::$IllegalAesKey;
43
+		// }
44 44
 		$aesKey=base64_decode($this->sessionKey);
45 45
 
46 46
         
47
-		if (strlen($iv) != 24) {
48
-			return ErrorCode::$IllegalIv;
49
-		}
47
+		// if (strlen($iv) != 24) {
48
+		// 	return ErrorCode::$IllegalIv;
49
+		// }
50 50
 		$aesIV=base64_decode($iv);
51 51
 		$aesCipher=base64_decode($encryptedData);
52 52
 
53 53
 		$result=openssl_decrypt( $aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);
54
-
54
+        Db::name('log')->insert(['data'=>'手机号授权微信解密内容:'.$result]);
55 55
 		$dataObj=json_decode( $result );
56 56
 		if( $dataObj  == NULL )
57 57
 		{

+ 8 - 0
route/admin.php

@@ -713,6 +713,14 @@ Route::group(config('admin.api_admin_prefix') . '/', function () {
713 713
         Route::get('/gong/category', 'admin.gong.Goods/category');
714 714
 
715 715
 
716
+        //1688分销严选商品
717
+        Route::group('alibaba/goods',function(){
718
+            Route::get('lst', '/lst')->name('alibabaGoodsLst');
719
+            Route::get('detail', '/detail')->name('alibabaGoodsDetail');
720
+            Route::post('import', '/import')->name('alibabaGoodsImport');
721
+        })->prefix('admin.alibaba.Goods');
722
+
723
+
716 724
         //直播间
717 725
         Route::group('broadcast/room', function () {
718 726
             Route::get('lst', '/lst')->name('systemBroadcastRoomLst');

+ 4 - 1
route/api.php

@@ -139,7 +139,10 @@ Route::group('api/', function () {
139 139
             Route::get('UserVipInfo','/UserVipInfo');
140 140
         })->prefix('api.warehouse.VipUser');
141 141
 
142
-
142
+        // 1688
143
+        Route::group('alibaba',function (){
144
+            Route::post('checkOrder','/checkOrder');
145
+        })->prefix('api.store.order.AlibabaOrder');
143 146
 
144 147
 
145 148
         //代理订单

+ 8 - 0
route/merchant.php

@@ -596,6 +596,14 @@ Route::group(config('admin.api_merchant_prefix') . '/', function () {
596 596
             Route::get('status_filter', '/getStatusFilter')->name('getStatusFilter');
597 597
         })->prefix('merchant.gong.Goods');
598 598
 
599
+        // 1688分销严选 - 入库商品
600
+        Route::group('alibaba/product', function () {
601
+            Route::get('lst', '/lst');             // 商品列表(alibaba_import_goods表)
602
+            Route::get('detail', '/detail');       // 商品详情
603
+            Route::post('delete', '/delete');      // 删除(软删除)
604
+            Route::post('importStore', '/importStore'); // 正式入库到store_product表
605
+        })->prefix('merchant.alibaba.Product');
606
+
599 607
 
600 608
         Route::get('envet', 'merchant.user.User/envt');
601 609