Răsfoiți Sursa

1. 添加达达与滴滴跑腿平台的实现
2. 跑腿询价异常处理--货拉拉平台出现异常
3. 加强代码健壮性

shizhongqi 8 luni în urmă
părinte
comite
2c201d4fab

+ 34 - 7
app-ghs/controllers/DeliveryController.php

@@ -7,7 +7,6 @@ use biz\shop\classes\ShopClass;
 use bizGhs\express\classes\GhsDeliveryOrderClass;
 use bizGhs\express\classes\DeliveryAuthTokenClass;
 use bizGhs\order\classes\OrderClass;
-use common\components\delivery\services\adapter\HuolalaAdapter;
 use common\components\noticeUtil;
 use common\components\util;
 use common\components\delivery\services\DispatchService;
@@ -286,7 +285,7 @@ class DeliveryController extends BaseController
         $ret = $ds->createOrder($ghsOrder, 'xhGhsOrder', $this->shopId, $params);
 
         if (isset($ret['code']) && $ret['code'] == 0) {
-            $gdo = GhsDeliveryOrderClass::getByCondition(['mainId' => $this->mainId, 'orderId' => $ret['data']['orderId']]);
+            $gdo = GhsDeliveryOrderClass::getByCondition(['mainId' => $this->mainId, 'orderId' => $ret['data']['orderId'], 'orderStatus!=' => -1]);
             if (empty($gdo)) {
                 $data = [
                     'mainId' => $this->mainId,
@@ -301,6 +300,13 @@ class DeliveryController extends BaseController
                 GhsDeliveryOrderClass::add($data);
             } else {
                 Yii::info('重复创建订单');
+                noticeUtil::push('重复创建订单: ' . $ret['data']['orderId']);
+                $gdo->orderId = $ret['data']['orderId'];
+                $gdo->deliveryId = $ret['platform'];
+                $gdo->distance = $ret['data']['distance'];
+                $gdo->fee = $ret['data']['fee'];
+                $gdo->orderStatus = 0;
+                $gdo->save();
             }
             //更新订单
             $ghsOrder->sendDistance = $ret['data']['distance'];
@@ -358,16 +364,24 @@ class DeliveryController extends BaseController
         $ghsOrderId = $post['ghsOrderId'];
 
         // 先获取配送订单,确保 $gdo 始终可用
-        $gdo = GhsDeliveryOrderClass::getByCondition(['mainId' => $this->mainId, 'ghsOrderId' => $ghsOrderId, 'orderStatus!=' => -1], true);
-        if (empty($gdo)) {
+        $gdos = GhsDeliveryOrderClass::getAllByCondition(['mainId' => $this->mainId, 'ghsOrderId' => $ghsOrderId, 'orderStatus!=' => -1], 'id DESC', '*', null, true);
+        if (empty($gdos)) {
             util::fail('未找到对应订单: ' . $ghsOrderId);
         }
+        if(count($gdos)>=2){
+            noticeUtil::push("xhGhsDeliveryOrder ghsOrderId={$ghsOrderId} 有两笔以上是在配送中");
+            $gdo = $gdos[0];
+        }else{
+            $gdo = $gdos[0];
+        }
 
         // 如果没有提供平台和订单ID,从配送订单中获取
-        if ($platform == '' || $orderId == 0) {
-            $orderId = $gdo->orderId;
+        if ($platform == ''){
             $platform = $gdo->deliveryId;
         }
+        if($orderId == 0) {
+            $orderId = $gdo->orderId;
+        }
 
         $ds = new DispatchService($this->mainId, $platform);
 
@@ -394,6 +408,11 @@ class DeliveryController extends BaseController
             $order = OrderClass::getById($gdo->ghsOrderId, false, 'id,orderSn');
             $orderId = $order['orderSn'];
         }
+        if ($platform == 'didi') {
+            $order = OrderClass::getById($gdo->ghsOrderId, false, 'id,orderSn');
+            $post['outOrderNo'] = $order['orderSn'];
+            $post['cancelSource'] = 1; // TODO 要增加取消码判断
+        }
 
         $post['order_cancel_role'] = 1; //1:商户取消, 2:用户取消
         $ret = $ds->cancelOrder($orderId, $post);
@@ -600,7 +619,15 @@ class DeliveryController extends BaseController
     public function actionDadaCallback()
     {
         $post = Yii::$app->request->post();
-        Yii::info('达达回调:' . json_encode($post, JSON_UNESCAPED_UNICODE), 'dada');
+        if (empty($post)) {
+            $postStr = file_get_contents('php://input');
+            $post = json_decode($postStr, true);
+        }
+        Yii::info('达达回调:' . json_encode($post, JSON_UNESCAPED_UNICODE), 'dada-callback');
+
+        $handler = new \common\components\delivery\platform\dada\CallBackHandler();
+        $handler->handle($post);
+
         return $this->asJson(['status' => 'ok']);
     }
 

+ 0 - 30
app-ghs/controllers/DeliveryShopController.php

@@ -15,35 +15,6 @@ class DeliveryShopController extends BaseController
 {
     public $guestAccess = [];
 
-//    public function actionList()
-//    {
-//        $platformList = [];
-//        $platformLogos = [
-//            'data' => ['name'=>'达达', 'logo'=>'📦'],
-//            'shunfeng' => ['name'=>'顺丰同城', 'logo'=>'🚚'],
-//            'meituan' => ['name'=>'美团', 'logo'=>'🍱'] ,
-//            'fengniao' => ['name'=>'蜂鸟', 'logo'=>'🍜'],
-//            'huolala' => ['name'=>'货拉拉跑腿', 'logo'=>'🚛'],
-//            'shansong' => ['name'=>'闪送', 'logo'=>'⚡'],
-//            'didi' => ['name'=>'滴滴', 'logo'=>'🚗'],
-//			'uu' => ['name'=>'UU跑腿', 'logo'=>'🛵']
-//        ];
-//        $mainId = $this->mainId;
-//        $allDeliveries = DeliveryAuthTokenClass::getAllByCondition(['mainId'=>$mainId]);
-//        foreach($allDeliveries as $d){
-//            $item = [
-//                'id'=>$d['platform'],
-//                'logo'=>$platformLogos[$d['platform']]['logo'],
-//                'name'=>$platformLogos[$d['platform']]['name'],
-//                'is_authorized' => true,
-//                'balance' => $d['balance']/100.0,
-//                'access_type' => 'oauth'
-//            ];
-//            $platformList[] = $item;
-//        }
-//        util::success(['platformList'=>$platformList]);
-//    }
-
     public function actionBalance()
     {
         $get = Yii::$app->request->get();
@@ -51,7 +22,6 @@ class DeliveryShopController extends BaseController
 
         $ShopService = new ShopService($this->mainId, $platform);
         $balance = $ShopService->getBalance($platform);
-        
 
         $deliveryAuthToken = DeliveryAuthTokenClass::getByCondition(['platform'=>$platform, 'mainId'=>$this->mainId], true);
         if(!$deliveryAuthToken){

+ 1 - 1
app-ghs/models/delivery/OrderDetailForm.php

@@ -18,7 +18,7 @@ class OrderDetailForm extends BaseForm
         return [
             [['platform', 'orderId'], 'required'],
             [['platform', 'orderId'], 'string'],
-            ['platform', 'in', 'range' => ['shansong', 'fengniao', 'huolala', 'dada', 'shunfeng']],
+            ['platform', 'in', 'range' => ['shansong', 'fengniao', 'huolala', 'dada', 'shunfeng', 'didi']],
             ['orderSn', 'string'],
         ];
     }

+ 196 - 3
common/components/delivery/platform/dada/CallBackHandler.php

@@ -1,10 +1,203 @@
 <?php
 
-
 namespace common\components\delivery\platform\dada;
 
+use bizGhs\express\classes\GhsDeliveryOrderClass;
+use bizHd\express\classes\HdDeliveryOrderClass;
+use bizGhs\order\classes\OrderClass;
+use bizHd\order\classes\OrderClass as HdOrderClass;
+use bizHd\purchase\classes\PurchaseClass;
+use common\components\util;
+use Yii;
 
-trait CallBackHandler
+/**
+ * 达达回调处理类
+ */
+class CallBackHandler
 {
+    /**
+     * 处理达达回调
+     *
+     * @param array $data 回调数据
+     * @return bool 处理结果
+     */
+    public function handle($data)
+    {
+        try {
+            // 验证签名
+            if (!$this->verifySignature($data)) {
+                Yii::error('达达回调签名验证失败: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'dada-callback');
+                return false;
+            }
+
+            // 处理订单状态变更
+            return $this->processOrderCallback($data);
+
+        } catch (\Exception $e) {
+            Yii::error('达达回调处理异常: ' . $e->getMessage() . ' | ' . $e->getTraceAsString(), 'dada-callback');
+            return false;
+        }
+    }
+
+    /**
+     * 处理订单回调
+     */
+    private function processOrderCallback($data)
+    {
+        $orderSn = $data['order_id']; // 对应 orderSn
+        $dadaStatus = $data['order_status'];
+        $clientId = isset($data['client_id']) ? $data['client_id'] : '';
+
+        // 查找主订单
+        $order = OrderClass::getByCondition(['orderSn' => $orderSn], true);
+        $isGhs = true;
+
+        if (!$order) {
+            $order = HdOrderClass::getByCondition(['orderSn' => $orderSn], true);
+            $isGhs = false;
+        }
+
+        if (!$order) {
+            Yii::error('达达回调未找到对应订单: ' . $orderSn, 'dada-callback');
+            return false;
+        }
+
+        // 查找配送订单
+        $deliveryOrder = $this->getDeliveryOrder($order, $isGhs);
+        if (!$deliveryOrder) {
+            Yii::error('达达配送订单不存在: orderSn=' . $orderSn . ' ghsOrderId=' . $order->id, 'dada-callback');
+            return false;
+        }
+
+        // 更新 client_id (达达运单号)
+        if (!empty($clientId) && empty($deliveryOrder->orderId)) {
+            $deliveryOrder->orderId = $clientId;
+            $deliveryOrder->save();
+        }
+
+        $sendStatus = $this->convertOrderSendStatus($dadaStatus);
+        $orderStatus = $this->convertOrderStatus($dadaStatus);
+
+        if ($sendStatus === null) {
+            return true;
+        }
+
+        // 更新配送订单状态
+        $deliveryOrder->orderStatus = $sendStatus;
+        if (isset($data['dm_name'])) {
+            $deliveryOrder->riderName = $data['dm_name'];
+        }
+        if (isset($data['dm_mobile'])) {
+            $deliveryOrder->riderPhone = $data['dm_mobile'];
+        }
+        $deliveryOrder->save();
+
+        // 更新主订单
+        if ($isGhs) {
+            util::checkRepeatCommit('ghs_pt_' . $order->id . '_' . $sendStatus, 2);
+            $order = OrderClass::getById($order->id, true, 'id,sendType,sendStatus,deliveryId,purchaseId,customId,status');
+            
+            if ($order) {
+                // 已送达
+                if ($sendStatus == 5) {
+                     OrderClass::confirmReach($order, ['staffId' => 0, 'staffName' => '']);
+                }
+                // 取消/异常 (且当前状态未取消)
+                if (!in_array($order->sendStatus, [-2, -1]) && in_array($sendStatus, [-1, -2])) {
+                    OrderClass::cancelOrderSend($order, ['staffId' => 0, 'staffName' => '']);
+                }
+
+                $cg = PurchaseClass::getById($order->purchaseId, true, 'id,sendStatus');
+                if ($cg) {
+                    $cg->sendStatus = $sendStatus;
+                    $cg->save();
+                }
+
+                $order->sendStatus = $sendStatus;
+                if ($orderStatus !== null) {
+                    $order->status = $orderStatus;
+                }
+                $order->save();
+            }
+        } else {
+             // 零售订单处理
+            util::checkRepeatCommit('hd_pt_' . $order->id . '_' . $sendStatus, 2);
+            $order = HdOrderClass::getById($order->id, true, 'id,sendType,sendStatus,deliveryId,customId,status');
+            if ($order) {
+                $order->sendStatus = $sendStatus;
+                if ($orderStatus !== null) {
+                    $order->status = $orderStatus;
+                }
+                $order->save();
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * 验证签名
+     */
+    private function verifySignature($data)
+    {
+        if (!isset($data['signature'])) {
+            return false;
+        }
+
+        // client_id, order_id, update_time
+        // 值为 null 时转为空字符串
+        $clientId = isset($data['client_id']) ? $data['client_id'] : '';
+        $orderId = isset($data['order_id']) ? $data['order_id'] : '';
+        $updateTime = isset($data['update_time']) ? $data['update_time'] : '';
+
+        $list = [$clientId, $orderId, $updateTime];
+        sort($list, SORT_STRING);
+        $str = implode('', $list);
+        $sign = md5($str);
+
+        return $sign === $data['signature'];
+    }
+
+    private function getDeliveryOrder($order, $isGhs)
+    {
+        if ($isGhs) {
+             return GhsDeliveryOrderClass::getByCondition([
+                'deliveryId' => 'dada',
+                'ghsOrderId' => $order->id
+            ], true);
+        } else {
+            return HdDeliveryOrderClass::getByCondition([
+                'deliveryId' => 'dada',
+                'hdOrderId' => $order->id
+            ], true);
+        }
+    }
+
+    private function convertOrderSendStatus($dadaStatus)
+    {
+        $map = [
+            1 => 2,   // 待接单 -> 派单中
+            2 => 20,  // 待取货 -> 已分配骑手
+            3 => 4,   // 配送中 -> 配送中
+            4 => 5,   // 已完成 -> 已送达
+            5 => -1,  // 已取消 -> 已取消
+            8 => 2,   // 已追加待接单 -> 派单中
+            9 => -2,  // 异常
+            10 => -2, // 异常
+            100 => 30,// 骑士到店 -> 骑手已到店
+            1000 => -2,// 创建失败 -> 异常
+        ];
+        return $map[$dadaStatus] ?? null;
+    }
 
-}
+    private function convertOrderStatus($dadaStatus)
+    {
+         $map = [
+            2 => 3,   // 待取货 -> 配送中
+            3 => 3,   // 配送中 -> 配送中
+            4 => 4,   // 已完成 -> 已完成
+            100 => 3, // 到店 -> 配送中
+         ];
+         return $map[$dadaStatus] ?? null;
+    }
+}

+ 1 - 1
common/components/delivery/platform/dada/Dada.php

@@ -23,7 +23,7 @@ class Dada
      */
     public function __construct($token='')
     {
-        $isProduction = getenv('YII_ENV') == 'dev'; // TODO  如果是 dev 请修改为 production
+        $isProduction = getenv('YII_ENV') == 'production'; // TODO  如果是 dev 请修改为 production
 
         if ($isProduction) {
             // 生产环境配置

+ 0 - 9
common/components/delivery/platform/didi/Didi.php

@@ -9,7 +9,6 @@ class Didi
     protected $appSecret; // 签名用的secret
     protected $accessToken;
     protected $apiVersion = '1.0.0';
-    protected $isSandbox;
 
     public function __construct($accessToken = '')
     {
@@ -21,16 +20,13 @@ class Didi
                 'app_id' => '', 
                 'secret' => '',
             ];
-            $this->isSandbox = false;
         } else {
             $cfg = [
                 'base_url' => 'http://pinzhi.didichuxing.com/kop_osim',
                 'app_key' => '4329d266f40144829ca5fd47a025e106',
-                // 请填入测试环境的 appId 和 secret
                 'app_id' => 'MDdovjIS',
                 'secret' => 'b61603625ebcf69a6364fdc30ae47944630c738c',
             ];
-            $this->isSandbox = true;
         }
         $this->baseUrl = $cfg['base_url'];
         $this->appKey = $cfg['app_key'];
@@ -106,9 +102,4 @@ class Didi
         
         return $this->baseUrl . '/gateway?' . http_build_query($queryParams);
     }
-
-    public function getIsSandbox()
-    {
-        return $this->isSandbox;
-    }
 }

+ 5 - 5
common/components/delivery/platform/fengniao/Fengniao.php

@@ -106,9 +106,9 @@ class Fengniao
     public function generateSignature(array $params)
     {
         // Step 1: 过滤空值  --  不能清除,不然测试环境 code 就被排除了
-//        $params = array_filter($params, function ($v) {
-//            return $v !== null && $v !== '';
-//        });
+        // $params = array_filter($params, function ($v) {
+        //     return $v !== null && $v !== '';
+        // });
 
         // Step 2: 按字典序排序
         ksort($params);
@@ -122,11 +122,11 @@ class Fengniao
 
         // Step 4: 拼接 appSecret
         $signBefore = $this->appSecret . $paramStr;
-        //$signBefore = '1111app_id=222&code=4444&grant_type=authorization_code&merchant_id=333&timestamp=1719297100558';
+      
         // Step 5: 使用SHA-256加密
         $signature = hash('sha256', $signBefore);
 
-        Yii::info("[FengniaAuth] Sign Before: {$signBefore}, Signature: {$signature}");
+        // Yii::info("[FengniaAuth] Sign Before: {$signBefore}, Signature: {$signature}");
         return $signature;
     }
 

+ 25 - 0
common/components/delivery/platform/fengniao/Shop.php

@@ -151,4 +151,29 @@ class Shop extends Fengniao
         $deliveryAuthToken->save();
         return true;
     }
+
+    /**
+     * 查询商户余额接口
+     */
+    public function getShopAccountBalance()
+    {
+        $businessData = [
+            'merchant_id' => $this->merchantId,
+        ];
+        $payload = $this->buildRequestPayload('getAmount', $businessData);
+        $url = $this->baseUrl . '/getAmount';
+        $resp = HttpClient::post($url, $payload);
+        if(isset($resp['code'])&&$resp['code']==200){
+            $data = json_decode($resp['business_data'], true);
+            return [
+                'code' => 1,
+                'balance' => $data['balance_amount_cent']
+            ];
+        }
+
+        return [
+            'code' => 0,
+            'balance' => 0
+        ];
+    }
 }

+ 107 - 9
common/components/delivery/services/DispatchService.php

@@ -8,8 +8,8 @@ use bizGhs\order\classes\OrderClass;
 use bizGhs\order\classes\OrderItemClass;
 use bizHd\express\classes\HdDeliveryOrderClass;
 use common\components\delivery\helpers\HttpClient;
-use common\components\delivery\services\adapter\{
-    DadaAdapter,
+use common\components\delivery\services\adapter\{DadaAdapter,
+    DidiAdapter,
     Functions,
     ShansongAdapter,
     HuolalaAdapter,
@@ -55,6 +55,9 @@ class DispatchService
                         $this->adapters['dada']->setShopNo($pt['shopId']);
                         $this->adapters['dada']->setSourceId($pt['merchantId']); // 用 merchanId 来保存 sourceId
                         break;
+                    case 'didi':
+                        $this->adapters['didi'] = new DidiAdapter($pt['accessToken']);
+                        break;
                 }
 
                 //检测 token 是否即将过期
@@ -89,6 +92,9 @@ class DispatchService
                     $this->adapters['dada']->setShopNo($authPlatform['shopId']);
                     $this->adapters['dada']->setSourceId($authPlatform['merchantId']); // 用 merchanId 来保存 sourceId
                     break;
+                case 'didi':
+                    $this->adapters['didi'] = new DidiAdapter($authPlatform['accessToken']);
+                    break;
             }
             $this->platformName = $platform;
             if($authPlatform['expiresAt'] < time() + 86400 * 5) {
@@ -160,6 +166,11 @@ class DispatchService
             return $adapter->selectOrder($ghsOrder['orderSn']);
         }
 
+        if ($adapter instanceof DidiAdapter) {
+            $deliveryOrder = $this->getDeliveryOrder($orderId, 'didi');
+            return $adapter->selectOrder($deliveryOrder['orderId'], $thirdOrderNo);
+        }
+
         return $adapter->selectOrder($orderId);
     }
 
@@ -308,6 +319,15 @@ class DispatchService
             }
         }
 
+        // 准备滴滴订单数据
+        if (isset($this->adapters['didi'])) {
+            try {
+                $preparedData['didi'] = $this->prepareDidiData($order, $shop);
+            } catch (\Exception $e) {
+                Yii::warning("Didi 数据准备失败: {$e->getMessage()}");
+            }
+        }
+
         return $preparedData;
     }
 
@@ -585,6 +605,36 @@ class DispatchService
         ];
     }
 
+    /**
+     * 准备滴滴订单数据
+     */
+    private function prepareDidiData($order, $shop)
+    {
+        return [
+            'platform' => 'didi',
+            'sender' => [
+                'fromSenderName' => $shop['merchantName'],
+                'fromMobile' => $shop['mobile'],
+                'fromAddress' => $shop['address'],
+                'fromAddressDetail' => $shop['floor'],
+                'fromLatitude' => (float)$shop['lat'],
+                'fromLongitude' => (float)$shop['long'],
+            ],
+            'receiverList' => [
+                [
+                    'toReceiverName' => $order['customName'],
+                    'toMobile' => Functions::getMobile($order),
+                    'toAddress' => $order['address'],
+                    'toAddressDetail' => $order['floor'],
+                    'toLatitude' => (float)$order['lat'],
+                    'toLongitude' => (float)$order['long'],
+                    'weight' => isset($order['weight']) ? (float)$order['weight'] : 1.0,
+                    'goodType' => 8,
+                ]
+            ]
+        ];
+    }
+
     /**
      * 并发获取各平台报价(核心实现)
      * 
@@ -775,6 +825,11 @@ class DispatchService
         }
 
         foreach ($platformQuotes['quotes'] as $item) {
+            if(!isset($item['platform'])){
+                Yii::info(json_encode($item));
+                continue;
+            }
+
             switch ($item['platform']) {
                 case 'shunfeng':
                     $deliveryList[] = [
@@ -834,15 +889,35 @@ class DispatchService
                     }
                     break;
                 case 'huolala':
-                    foreach($item['price_info_list'] as $arr) {
+                    foreach($item['price_info_list'] as $key => $arr) {
                         $priceInfo = $arr['calculate_price_info'];
+                        $priceConditions = $priceInfo['price_conditions'] ?? []; //价格明细
+                        $distanceInfo = $priceInfo['distance_info'] ?? []; //距离信息
                         $vehicleType = $arr['vehicle_type'];
+
+                        if(empty($priceConditions)){
+                            Yii::error('货拉拉 报价数据 priceConditions 为空:' . json_encode($item) . ' key: ' . $key);
+                            continue;
+                        }
+                        if(empty($distanceInfo)){
+                            Yii::warning('货拉拉 报价数据 distanceInfo 为空:' . json_encode($item) . ' key: ' . $key);
+                        }
+                        $priceCalculateId = $priceInfo['price_calculate_id'] ?? 0;
+                        if(!isset($priceInfo['price_calculate_id'])){
+                            Yii::warning('货拉拉 报价数据 priceInfo 缺少 price_calculate_id:' . json_encode($priceInfo) . ' key: ' . $key);
+                        }
+                        //车型信息
+                        $vehicleAttr = $priceInfo['vehicle_info']['vehicle_attr'] ?? [];
+                        if(empty($vehicleAttr)){
+                            Yii::info('货拉拉 报价数据 vehicleAttr 为空:' . json_encode($priceInfo) . ' key: ' . $key);
+                        }
+
                         $deliveryList[] = [
                             'name' => '货拉拉' . ' (' . $vehicleType['_meta']['vehicle_type'] . ')',
                             'vehicle_type' => $vehicleType['_meta']['vehicle_type'],
                             'en_name' => 'huolala',
-                            'price' => $priceInfo['price_conditions'][0]['price_info']['total_price'],
-                            'distance' => $priceInfo['distance_info']['distance_total'],
+                            'price' => !empty($priceConditions) ? $priceConditions[0]['price_info']['total_price'] : 0,
+                            'distance' => !empty($distanceInfo) ? $distanceInfo['distance_total'] : 0,
                             'type' => $vehicleType['_meta']['vehicle_type'],
                             'isAble' => true,
                             // ---------------------------------------------
@@ -852,10 +927,10 @@ class DispatchService
                             // 下单需要透传的数据
                             'vehicle_std' => $vehicleType['vehicle_std'],
                             'spec_req' => $vehicleType['spec_req'],
-                            'price_calculate_id' => $priceInfo['price_calculate_id'],
-                            'price_item_encryption' => $priceInfo['price_conditions'][0]['price_item_encryption'],
-                            'vehicle_attr' => $priceInfo['vehicle_info']['vehicle_attr'],
-                            'commodity_info' => $priceInfo['price_conditions'][0]['commodity_item'],
+                            'price_calculate_id' => $priceCalculateId,
+                            'price_item_encryption' => !empty($priceConditions) ? $priceConditions[0]['price_item_encryption'] : '',
+                            'vehicle_attr' => $vehicleAttr,
+                            'commodity_info' => !empty($priceConditions) ? $priceConditions[0]['commodity_item'] : [],
                         ];
                     }
                     break;
@@ -874,6 +949,29 @@ class DispatchService
                         'couponFee' => $item['couponFee'],
                     ];
                     break;
+
+                case 'didi':
+                    if(!isset($item['estimateList'])){
+                        Yii::error('didi 报价数据为空:' . json_encode($item));
+                        continue;
+                    }
+
+                    foreach($item['estimateList'] as $arr) {
+                        $deliveryList[] = [
+                            'name' => '滴滴' . ' (' . $arr['carType'] . ')',
+                            'en_name' => 'didi',
+                            'price' => $arr['fee'],
+                            'distance' => $arr['distance'],
+                            'type' => $arr['estimateId'],
+                            'isAble' => true,
+                            // ---------------------------------------------
+                            'estimateId' => $arr['estimateId'],
+                            'bizType' => $arr['bizType'],
+                            'discountFee' => $arr['discountFee'],
+                            'icon' => $arr['icon']
+                        ];
+                    }
+                    break;
             }
         }
 

+ 2 - 3
common/components/delivery/services/ShopService.php

@@ -85,9 +85,8 @@ class ShopService
                 $ret = $adapter->queryWalletBalance();
                 break;
             case 'fengniao':
-                $ret['success'] = false;
-                $ret['balance'] = 0;
-                $ret['message'] = '蜂鸟平台不支持查询余额';
+                $adapter = $this->adapters[$platform];
+                $ret = $adapter->getShopAccountBalance();
                 break;
             case 'shunfeng':
                 $adapter = $this->adapters[$platform];

+ 1 - 1
common/components/delivery/services/adapter/DadaAdapter.php

@@ -580,7 +580,7 @@ class DadaAdapter extends Dada implements Adapter
 
     public function processPriceResponse($resp)
     {
-        Yii::info("[DadaAdapter] processPriceResponse: " . json_encode($resp));
+        //Yii::info("[DadaAdapter] processPriceResponse: " . json_encode($resp));
         if (isset($resp['code']) && $resp['code'] == 0 && isset($resp['result'])) {
             $result = $resp['result'];
 

+ 103 - 39
common/components/delivery/services/adapter/DidiAdapter.php

@@ -1,6 +1,9 @@
 <?php
 namespace common\components\delivery\services\adapter;
 
+use bizGhs\express\classes\DeliveryOrderClass;
+use bizGhs\express\classes\GhsDeliveryOrderClass;
+use bizGhs\order\classes\OrderClass;
 use common\components\delivery\helpers\HttpClient;
 use common\components\delivery\platform\didi\Didi;
 use Yii;
@@ -138,11 +141,16 @@ class DidiAdapter extends Didi implements Adapter
      */
     public function processPriceResponse($resp)
     {
-        if (isset($resp['code']) && $resp['code'] == 0 && isset($resp['data'])) {
+        if (isset($resp['code']) && $resp['code'] == 200 && isset($resp['data'])) {
             $data = $resp['data'];
             $estimateList = $data['estimateList'] ?? [];
             if (empty($estimateList)) {
-                return null;
+                return [
+                    'code' => -1,
+                    'platform' => 'didi',
+                    'msg' => '没有预估结果列表',
+                    'data' => null
+                ];
             }
 
             // 选取第一个结果,或者根据策略选取(比如最便宜) TODO
@@ -151,6 +159,7 @@ class DidiAdapter extends Didi implements Adapter
 
             return [
                 'platform' => 'didi',
+                'estimateList' => $estimateList,
                 'estimate_id' => $estimate['estimateId'], // 预估ID,发单用
                 'total_distance' => $estimate['distance'] ?? 0,
                 'total_amount' => $estimate['fee'] ?? 0, // 分
@@ -161,7 +170,54 @@ class DidiAdapter extends Didi implements Adapter
                 'total_weight' => 0, // 响应里没返回重量
             ];
         }
-        return null;
+
+        return [
+            'code' => $resp['code'] ?? -1,
+            'platform' => 'didi',
+            'msg' => $resp['msg'] ?? 'Unknown error',
+            'data' => null
+        ];
+    }
+
+    /**
+     * 转化订单数据
+     * @param $order
+     * @param $orderType
+     * @param $shop
+     * @param $params
+     * @return array
+     * @throws \Exception
+     */
+    public function formatOrderData($order, $orderType, $shop, $params)
+    {
+        $sender = [
+            'fromSenderName' => $shop['merchantName'],
+            'fromMobile' => $shop['mobile'],
+            'fromAddress' => $shop['fullAddress'],
+            'fromAddressDetail' => $shop['floor'],
+            'fromLatitude' => (float)$shop['lat'],
+            'fromLongitude' => (float)$shop['long'],
+        ];
+
+        $receiver = [
+            'toReceiverName' => empty($order['showAddress']) ? $order['customName'] : $order['showAddress'],
+            'toMobile' => Functions::getMobile($order),
+            'toAddress' => $order['fullAddress'],
+            'toAddressDetail' => $order['floor'],
+            'toLatitude' => (float)$order['lat'],
+            'toLongitude' => (float)$order['long'],
+            'weight' => $order['weight'] ?? 1,
+            'goodType' => $order['goodType'] ?? '8',
+        ];
+
+        $apiData = [
+            'issOrderNo' => $order['orderSn'],
+            'estimate_id' => $params['estimateId'] ?? null,
+            'sender' => $sender,
+            'receiverList' => [$receiver],
+            'remark' => $params['remark'] ?? $order['remark'] ?? '',
+        ];
+        return $apiData;
     }
 
     /**
@@ -232,13 +288,15 @@ class DidiAdapter extends Didi implements Adapter
 
         $resp = HttpClient::post($url, $payload);
 
-        if (isset($resp['code']) && $resp['code'] == 0 && isset($resp['data'])) {
+        if (isset($resp['code']) && $resp['code'] == 200 && isset($resp['data'])) {
             $respData = $resp['data'];
             return [
                 'code' => 0,
                 'platform' => 'didi',
                 'data' => [
                     'orderId' => $respData['didiOrderNo'], // 滴滴订单号
+                    'distance' => 0,
+                    'fee' => 0,
                     // 可以附带其他信息
                 ]
             ];
@@ -248,7 +306,6 @@ class DidiAdapter extends Didi implements Adapter
         if (isset($resp['code']) && isset($resp['subCode'])) {
              $msg .= " ({$resp['code']}-{$resp['subCode']})";
         }
-
         Yii::error('[DidiAdapter] createOrder failed: ' . json_encode($resp, JSON_UNESCAPED_UNICODE));
         
         return [
@@ -305,42 +362,17 @@ class DidiAdapter extends Didi implements Adapter
 
     /**
      * 取消订单
+     *
+     * @param int $didiOrderNo 滴滴订单号
+     * @param int $post
      */
-    public function cancelOrder($orderId)
+    public function cancelOrder($didiOrderNo, $post)
     {
-        // $orderId 应该是 outOrderNo (接入方订单号)
-        // Didi API 需要 outOrderNo 和 didiOrderNo。
-        // 如果我们只有 one of them,可能需要先查询?
-        // 接口文档说: outOrderNo 是必须, didiOrderNo 是必须.
-        // 这有点麻烦。通常 Adapter 的 input 只包含一个 ID。
-        // 假设 $orderId 是 outOrderNo。
-        // 此时我们可能没有 didiOrderNo,或者它需要作为第二个参数传入?
-        // 参考 ShansongAdapter,它只传了一个 $orderId (issOrderNo).
-        // 如果必须两个都传,可能需要修改调用方,或者我们只传 outOrderNo 试试,或者 look up mapping.
-        // 文档: outOrderNo (String, 是), didiOrderNo (Long, 是).
-        // 如果无法提供 didiOrderNo,可能无法取消。
-        // 但通常业务系统会保存 didiOrderNo。
-        // 这里假设调用方会在参数里传,或者 $orderId 是一个包含多个ID的数组?
-        // 按照 ShansongAdapter: cancelOrder($orderId).
-        // 只能假设 $orderId 结构可能包含 didiOrderNo,或者我们尝试只传 outOrderNo 看是否可行 (文档说是必须)。
-        // 为了兼容性,如果 $orderId 是数组,则取值;如果是字符串,视为 outOrderNo,且 didiOrderNo 填 0 或空?
-        // 让我们看看能不能只传 outOrderNo. "接口根据接入方订单号幂等...".
-        // 还是严格按照文档吧。如果调用方没传 didiOrderNo,这步会失败。
-        
-        // 临时处理:检查 $orderId 是否是数组
-        $didiOrderNo = 0;
-        $outOrderNo = $orderId;
-        
-        if (is_array($orderId)) {
-            $outOrderNo = $orderId['order_no'] ?? '';
-            $didiOrderNo = $orderId['platform_order_no'] ?? 0;
-        }
-
         $businessParams = [
-            'outOrderNo' => (string)$outOrderNo,
+            'outOrderNo' => (string)$post['outOrderNo'],
             'didiOrderNo' => (int)$didiOrderNo,
-            'cancelSource' => 1, // 用户取消
-            'cancelReason' => '暂不需要',
+            'cancelSource' => $post['cancelSource'],
+            //'cancelReason' => '暂不需要',
         ];
 
         $payload = $this->buildRequestPayload($businessParams);
@@ -348,13 +380,14 @@ class DidiAdapter extends Didi implements Adapter
 
         $resp = HttpClient::post($url, $payload);
 
-        if (isset($resp['code']) && $resp['code'] == 0 && isset($resp['data'])) {
+        if (isset($resp['code']) && $resp['code'] == 200 && isset($resp['data'])) {
             $data = $resp['data'];
             return [
                 'code' => 0,
                 'platform' => 'didi',
                 'data' => [
-                    'deductionFee' => $data['cancelFee'] ?? 0, // 分
+                    'deductionFee' => $data['cancelFee'], // 分
+                    'cancelFee' => $data['cancelFee'],
                     'cancelResult' => $data['cancelResult'],
                 ]
             ];
@@ -367,4 +400,35 @@ class DidiAdapter extends Didi implements Adapter
             'data' => null
         ];
     }
+
+    /**
+     * 查询订单详情
+     */
+    public function selectOrder($orderId, $thirdOrderNo = '')
+    {
+        $businessParams = [
+            'didiOrderNo' => (int)$orderId,
+            'outOrderNo' => (string)$thirdOrderNo,
+        ];
+
+        $payload = $this->buildRequestPayload($businessParams);
+        $url = $this->getApiUrl('freight.open.platform.channel.standard.orderInfo');
+
+        $resp = HttpClient::post($url, $payload);
+
+        if (isset($resp['code']) && $resp['code'] == 200 && isset($resp['data'])) {
+            return [
+                'code' => 0,
+                'platform' => 'didi',
+                'data' => $resp['data']
+            ];
+        }
+
+        return [
+            'code' => $resp['code'] ?? -1,
+            'platform' => 'didi',
+            'msg' => $resp['msg'] ?? 'Unknown error',
+            'data' => null
+        ];
+    }
 }

+ 1 - 1
common/components/delivery/services/adapter/FengniaoAdapter.php

@@ -550,7 +550,7 @@ class FengniaoAdapter extends Fengniao implements Adapter
      */
     public function processPriceResponse($resp)
     {
-        Yii::info("[FengniaoAdapter] preCreateOrder Response: " . json_encode($resp));
+        //Yii::info("[FengniaoAdapter] preCreateOrder Response: " . json_encode($resp));
 
         // 处理响应
         if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) {