['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' => 0, 'access_type' => 'oauth' ]; $platformList[] = $item; } util::success(['platformList'=>$platformList]); } public function actionGetAuthUrl() { $platform = Yii::$app->request->get('platformId'); switch($platform){ case 'shansong': return $this->actionShansongAuth(); break; case 'huolala': return $this->actionHuolalaAuth(); break; case 'fengniao': return $this->actionFengniaoAuth(); break; case 'dada': return $this->actionDadaAuth(); break; case 'shunfeng': return $this->actionShunfengAuth(); break; case 'didi': break; default: util::error(-1, $platform . '不存在'); break; } } // ------------------ 授权(账号绑定) --------------------- //授权 public function actionShansongAuth() { //闪送授权(商户授权:授权后能为商户下所有门店发单) $auth = new \common\components\delivery\platform\shansong\Auth(); $redirectUrl = 'https://api.shop.hzghd.com/delivery/shansong-auth-callback'; $authUrl = $auth->generateMerchantAuthUrl($this->mainId, $redirectUrl); // 重定向用户 //header('Location: ' . $authUrl); util::success(['url'=>$authUrl]); } public function actionHuolalaAuth() { $huoLalaAuth = new \common\components\delivery\platform\huolala\Auth(); $mainId = $this->mainId; $redirectUrl = 'https://api.shop.hzghd.com/delivery/huolala-auth-callback' . '?mainId=' . $mainId; $authUrl = $huoLalaAuth->generateAuthUrl($redirectUrl); //header('Location: ' . $authUrl); util::success(['url'=>$authUrl]); } public function actionFengniaoAuth() { $fengnaioAuth = new \common\components\delivery\platform\fengniao\Auth(); $mainId = $this->mainId; $redirectUrl = 'https://api.shop.hzghd.com/delivery/fengniao-auth-callback' . '?mainId=' . $mainId; $authUrl = $fengnaioAuth->generateAuthUrl($redirectUrl); util::success(['url'=>$authUrl]); } public function actionDadaAuth() { $dadaAuth = new \common\components\delivery\platform\dada\Auth(); $ticket = $dadaAuth->getTicket(); $mainId = $this->mainId; $redirectUrl = 'https://api.shop.hzghd.com/delivery/dada-auth-callback' . '?mainId=' . $mainId; $shopNumber = '40d8391f9b05477b'; $state = 'huidiao_biaoshi'; $authUrl = $dadaAuth->generateAuthUrl($ticket, $state, $shopNumber); //header('Location: ' . $authUrl); util::success(['url'=>$authUrl]); } public function actionShunfengAuth() { $shunfengAuth = new \common\components\delivery\platform\shunfeng\Auth(); $mainId = $this->mainId; $authUrl = $shunfengAuth->generateAuthUrl($mainId); util::success(['url'=>$authUrl]); } public function actionCancelAuth() { // 有平台有对应的取消授权接口,有的没有。 // 有的就对接下,然后再删除数据。没有的直接删除数据就行。 $platform = Yii::$app->request->get('platform'); $ret = DeliveryAuthTokenClass::deleteByCondition(['mainId'=>$this->mainId, 'platform'=>$platform]); if($ret > 0) { util::success(['message'=>'取消授权成功']); } else { util::fail('取消授权失败'); } } // -------------------------------------------------------------------------------------------------------------- public function actionHuolalaVehicle() { $mainId = $this->mainId; $authPlatforms = DeliveryAuthTokenClass::getByCondition(['mainId'=>$mainId, 'platform'=>'huolala']); $huolalaAdapter = new HuolalaAdapter($authPlatforms['access_token']); $cities = $huolalaAdapter->getCityVehicleList(1006); return $this->asJson($cities); } // 查询开通城市 public function actionOpenCitiesLists() { $platform = Yii::$app->request->get('platform'); $ds = new DispatchService($this->mainId); $cities = $ds->openCitiesLists($platform); if ($platform == 'shansong') { $newCitiesArr = []; $citiesArr = $cities['data']; foreach($citiesArr as $block) { foreach ($block['cities'] as $city) { $newCitiesArr[$city['name']] = $city; } } // 把newCitiesArr保存到 platform/huolala/cities.php 文件中 $citiesFile = Yii::getAlias('@common/components/delivery/platform/shansong/cities.php'); // 如果文件不存在,则创建文件 if (!file_exists($citiesFile)) { file_put_contents($citiesFile, ''); } // 文件内容要包含命名空间 $content = ''; file_put_contents($citiesFile, $content); return $newCitiesArr; } else if ($platform == 'huolala') { $citiesArr = $cities['data']['city_list']; $newCitiesArr = [ 'expire' => $cities['data']['expire'], ]; foreach($citiesArr as $city) { $newCitiesArr[$city['name']] = [ 'city_id' => $city['city_id'], 'city_name' => $city['name'], ]; } // 把newCitiesArr保存到 platform/huolala/cities.php 文件中 $citiesFile = Yii::getAlias('@common/components/delivery/platform/huolala/cities.php'); // 如果文件不存在,则创建文件 if (!file_exists($citiesFile)) { file_put_contents($citiesFile, ''); } // 文件内容要包含命名空间 $content = ''; file_put_contents($citiesFile, $content); return $newCitiesArr; } else { return $cities; } return $cities; } public function actionAddress() { $post = Yii::$app->request->post(); $adminId = $this->adminId; util::checkRepeatCommit($adminId, 4); $orderId = intval($post['orderId']); $order = OrderClass::getById($orderId, false, 'fullAddress'); $shop = ShopClass::getById($this->shopId, false, 'fullAddress'); $ret = [ "send_address" => $shop['fullAddress'], "receive_address" => $order['fullAddress'] ]; util::success($ret, "success"); } // 获取多个平台报价 public function actionAllDeliveryQuotes() { $post = Yii::$app->request->post(); $orderTime = $post['pickupTime']; $adminId = $this->adminId; util::checkRepeatCommit($adminId, 4); $orderId = intval($post['orderId']); $order = OrderClass::getById($orderId); $shop = ShopClass::getById($this->shopId); $ds = new DispatchService($this->mainId); $platforms = $ds->getAllPlatformPrice($order, $shop, $orderTime); $deliveryList = []; foreach ($platforms['quotes'] as $item) { switch ($item['platform']) { case 'shunfeng': $deliveryList[] = [ 'name' => '顺丰', 'en_name' => 'shunfeng', 'price' => $item['real_pay_money'], 'distance' => $item['distance'], 'type' => '', 'isAble' => true ]; break; case 'shansong': $deliveryList[] = [ 'name' => '闪送', 'en_name' => 'shansong', 'price' => $item['total_amount'], 'distance' => $item['total_distance'], 'type' => '', 'issOrderNo' => $item['order_number'], 'isAble' => true ]; break; case 'fengniao': // 循环遍历 goods_infos 数组,提取有效项的信息 $validGoods = []; if (!empty($item['goods_infos']) && is_array($item['goods_infos'])) { foreach ($item['goods_infos'] as $good) { if (!empty($good['is_valid']) && $good['is_valid'] == 1) { $validGoods[] = [ 'actual_delivery_amount_cent' => $good['actual_delivery_amount_cent'] ?? 0, 'service_goods_id' => $good['service_goods_id'] ?? '' ]; $deliveryList[] = [ 'name' => '蜂鸟'. ' (' .$good['base_goods_id'] . ')', 'en_name' => 'fengniao', 'price' => $good['actual_delivery_amount_cent'], 'distance' => $item['distance'], 'valid_goods' => $validGoods, 'type' => '', 'isAble' => true ]; } else { $deliveryList[] = [ 'name' => '蜂鸟'. ' (' .$good['base_goods_id'] . ')', 'en_name' => 'fengniao', 'price' => 0, 'distance' => $item['distance'], 'valid_goods' => $good, 'type' => $good['disable_reason'], 'isAble' => false ]; } } } break; case 'huolala': $deliveryList = []; foreach($item['price_info_list'] as $arr) { $priceInfo = $arr['calculate_price_info']; $vehicleType = $arr['vehicle_type']; $deliveryList[] = [ 'name' => '货拉拉' . ' (' . $vehicleType['_meta']['vehicle_type'] . ')', 'en_name' => 'huolala', 'price' => $priceInfo['price_conditions'][0]['price_info']['total_price'], 'distance' => $priceInfo['distance_info']['distance_total'], 'type' => $vehicleType['_meta']['vehicle_type'], 'isAble' => true, // --------------------------------------------- 'order_vehicle_id' => $vehicleType['order_vehicle_id'], 'city_id' => $item['city_id'], 'city_info_revision' => $vehicleType['city_info_revision'], // // 下单需要透传的数据 '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'], ]; } break; } } $ret['deliveryList'] = $deliveryList; util::success($ret, "success"); } // 创建订单(真实下单) public function actionCreateOrder() { $form = new CreateOrderForm(); $form->loadAndValidate(); $post = $form->getAttributes(); //$post = Yii::$app->request->post(); $orderId = $post['orderId']; // xhGhsOrder.id $params = $post['allParams']; $platform = $params['en_name'];// $post['platform'] 是中文 $pickupTime = $post['pickupTime']; $params['mainId'] = $this->mainId; $params['ip'] = Yii::$app->request->userIP; $params['remark'] = $post['remark']; $params['total_price_fen'] = $post['price']; $params['order_time'] = $pickupTime['value']; $shopId = $this->shopId; $ghsOrder = OrderClass::getById($orderId, true); // if ($ghsOrder['status'] != 2) { // util::fail('订单状态不是待配送'); // // } if(!in_array($ghsOrder['sendStatus'], [-4, -1])) { Yii::error('订单已经发过跑腿'); util::fail('订单已经发过跑腿'); } $ds = new DispatchService($this->mainId, $platform); $ret = $ds->createOrder($ghsOrder, $shopId, $params); if (isset($ret['code']) && $ret['code'] == 0) { $gdo = GhsDeliveryOrderClass::getByCondition(['mainId'=>$this->mainId, 'orderId'=>$ret['data']['order_id']]); if (empty($gdo)) { $data = [ 'mainId' => $this->mainId, 'shopId' => $this->shopId, 'deliveryId' => $ret['platform'], 'ghsOrderId' => $orderId, 'orderId' => $ret['data']['order_id'], 'distance' => $ret['data']['distance'], 'fee' => $ret['data']['fee'], 'orderStatus' => 0 ]; GhsDeliveryOrderClass::add($data); } else { Yii::info('重复创建订单'); } //更新订单 $ghsOrder->sendDistance = $ret['data']['distance']; $ghsOrder->sendStatus = 0; $ghsOrder->deliveryId = $ret['platform']; $ghsOrder->sendType = 2;// 2指跑腿 $ghsOrder->save(); util::success($ret, "success"); } else { if(isset($ret['msg'])){ util::fail($ret['msg']); } util::fail('创建跑腿失败'); } } /** * 查询订单详情 * * 1. 蜂鸟平台 orderId 可以使用 "XSD_CS26322799" (即:表 xhGhsOrder.orderSn) */ public function actionOrderDetail() { $form = new OrderDetailForm(); $form->loadAndValidate(); $post = $form->getAttributes(); $platform = $post['platform']; $orderId = $post['orderId']; $orderSn = isset($post['orderSn']) ? $post['orderSn'] : ''; $ds = new DispatchService($this->mainId, $platform); $ret = $ds->selectOrder($orderId, $orderSn); $return = isset($ret['ret']) ? $ret['ret'] : $ret['code']; // 各平台兼容处理 if($return == 0) { util::success($ret, "success"); } util::fail('查询失败'); } // 取消订单 public function actionCancelOrder() { $form = new CancelOrderForm(); $form->loadAndValidate(); $post = $form->getAttributes(); $orderId = $post['orderId'] ?? 0; // 平台的orderId, $platform = $post['platform'] ?? ''; $ghsOrderId = $post['ghsOrderId']; // 先获取配送订单,确保 $gdo 始终可用 $gdo = GhsDeliveryOrderClass::getByCondition(['mainId'=>$this->mainId, 'ghsOrderId'=>$ghsOrderId, 'orderStatus!='=> -1], true); if(empty($gdo)) { util::fail('未找到对应订单: ' . $ghsOrderId); } // 如果没有提供平台和订单ID,从配送订单中获取 if($platform == '' || $orderId == 0) { $orderId = $gdo->orderId; $platform = $gdo->deliveryId; } $ds = new DispatchService($this->mainId, $platform); $cancelCostCent = 0; if($platform == 'fengniao') { //请求订单预取消接口,获取是否可取消。如果运单状态不允许取消,则直接返回 $cancelParams = ['order_id'=>$orderId, 'order_cancel_code'=>$post['order_cancel_code']]; $res = $ds->getAdapter()->preCancelOrder($cancelParams); if($res['code'] != 0){ util::fail('运单状态不允许取消'); } $cancelCostCent = $res['data']['actual_cancel_cost_cent']; //取消实际需金额(单位:分) } $post['order_cancel_role'] = 1; //1:商户取消, 2:用户取消 $ret = $ds->cancelOrder($orderId, $post); if($ret['code'] == 0) { // 更新跑腿订单与批发订单状态 $gdo->orderStatus = -1; $gdo->cancelCost = $cancelCostCent; if(!$gdo->save()) { Yii::error('保存配送订单失败: ' . json_encode($gdo->errors)); util::fail('保存配送订单失败'); } $ghsOrder = OrderClass::getById($ghsOrderId, true, 'id, sendDistance, sendStatus, deliveryId'); $ghsOrder->sendDistance = 0; $ghsOrder->sendStatus = -4; $ghsOrder->deliveryId = ''; if(!$ghsOrder->save()) { Yii::error('保存批发订单失败: ' . json_encode($ghsOrder->errors)); util::fail('保存批发订单失败'); } util::success($ret['data'], "success"); } util::fail('取消失败'); } /** * 获取订单取消原因 */ public function actionCancelReason() { $post = Yii::$app->request->post(); $ghsOrderId = $post['ghsOrderId']; $platform = $post['platform']; $gdo = GhsDeliveryOrderClass::getByCondition(['mainId'=>$this->mainId, 'ghsOrderId'=>$ghsOrderId, 'orderStatus!='=>-1], true); if(!empty($gdo)) { $orderId = $gdo->orderId; //$platform = $gdo->deliveryId; } else { util::fail('未找到对应订单: ' . $ghsOrderId); } $ds = new DispatchService($this->mainId, $platform); $ret = $ds->getCancelReasonList($orderId); util::success($ret); } // ---------------------------------------------------- 普通回调接口 ---------------------------------------------------------- // 第三方回调入口 /** * 回调接口 */ public function actionCallback() { $callbackData = Yii::$app->request->post(); if (empty($callbackData)) { $postStr = file_get_contents('php://input'); $callbackData = json_decode($postStr, true); if (empty($callbackData)) { util::fail('回调请求的数据为空'); } } // 从配置中获取安全token // $token = Yii::$app->params['wx_intracity_token'] ?? 'your_token_here'; // $result = IntraCityExpress::handleOrderCallback($callbackData, $token); // 记录回调日志 Yii::info('聚合配送回调:' . json_encode($callbackData, JSON_UNESCAPED_UNICODE), 'delivery'); return $this->asJson(['return_code' => 0, 'return_msg' => 'OK']); } /** * 闪送回调接口 * 回调返回的格式必须是{"status":200,"msg":"","data":null}格式,当status为200表示回调成功,否则,回调失败。当回调失败时,间隔一分钟重试一次,最多重试五次。 */ public function actionShansongCallback() { $callbackData = Yii::$app->request->post(); if (empty($callbackData)) { $postStr = file_get_contents('php://input'); $callbackData = json_decode($postStr, true); } Yii::info('shansong 回调 -- post: ' . json_encode($callbackData, JSON_UNESCAPED_UNICODE)); $obj = new \common\components\delivery\platform\shansong\OrderStatusCallBack(); $ret = $obj->handle($callbackData); if($ret) { return $this->asJson(['status' => 200, 'msg' => 'OK', "data"=> null]); } else { Yii::error('闪送回调失败: ' . json_encode($callbackData, JSON_UNESCAPED_UNICODE)); return $this->asJson(['status' => 200, 'msg' => '回调失败', "data"=> null]); } } /** * 蜂鸟回调接口 */ public function actionFengniaoCallback() { $callbackData = Yii::$app->request->post(); // 逆向回调接口结果的加签 $param = [ 'app_id' => $callbackData['app_id'], 'timestamp' => $callbackData['timestamp'], 'business_data' => $callbackData['business_data'] ]; $fa = new \common\components\delivery\services\adapter\FengniaoAdapter(); $paramSign = $fa->generateSignature($param); if($paramSign == $callbackData['signature']){ $businessData = isset($callbackData['business_data']) ? $callbackData['business_data'] : ''; if(empty($businessData)){ $this->asJson(['return_code' => -1, 'return_msg' => 'business data is empty']); } $businessData = json_decode($businessData, true); $cbnObj = new \common\components\delivery\platform\fengniao\CallBackNotify(); $ret = $cbnObj->handle($businessData['callback_business_type'], $businessData['param']); return $this->asJson(['return_code' => 0, 'return_msg' => 'OK']); }else{ Yii::error('蜂鸟回调接口签名出错'); } } /** * 货拉拉回调接口 */ public function actionHuolalaCallback() { $post = Yii::$app->request->post(); Yii::info('货拉拉回调接口post:' . json_encode($post, JSON_UNESCAPED_UNICODE), 'delivery'); $get = Yii::$app->request->get(); Yii::info('货拉拉回调接口get:' . json_encode($get, JSON_UNESCAPED_UNICODE), 'delivery'); return $this->asJson(['return_code' => 0, 'return_msg' => 'OK']); } /** * 顺丰回调接口 */ public function actionShunfengCallback() { $post = Yii::$app->request->post(); $cbh = new \common\components\delivery\platform\shunfeng\CallBackHandler(); $ret = $cbh->handle($post);// 1.配送状态更改 2.订单完成 3.顺丰原因取消 4.订单配送异常 5.骑士撤单 6.自动建店回调 if($ret) { return $this->asJson(['error_code' => 0, 'error_msg' => 'success']); } else { Yii::error('顺丰回调失败: ' . json_encode($post, JSON_UNESCAPED_UNICODE)); return $this->asJson(['error_code' => -1, 'error_msg' => 'failed']); } } // ---------------------------------------------------- 授权回调接口 ---------------------------------------------------------- /** * 闪送授权回调接口 */ public function actionShansongAuthCallback() { $code = Yii::$app->request->get('code'); $state = Yii::$app->request->get('state'); $shopId = Yii::$app->request->get('shopId'); $isAllStoreAuth = Yii::$app->request->get('isAllStoreAuth'); $thirdStoreId = Yii::$app->request->get('thirdStoreId'); $storeId = Yii::$app->request->get('storeId'); // 验证state参数(防止CSRF) //if ($state != $yourUserId) { //throw new \yii\web\BadRequestHttpException('Invalid state parameter'); //} // 获取AccessToken $auth = new \common\components\delivery\platform\shansong\Auth(); $result = $auth->getAccessToken($code); if (!$result['success']) { throw new \yii\web\HttpException(500, '获取授权失败:' . $result['error']); } // 保存授权信息到数据库 $data = [ 'mainId' => $state, 'shopId' => $shopId, //闪送商户ID 'accessToken' => $result['access_token'], 'refreshToken' => $result['refresh_token'], 'expiresAt' => time() + $result['expires_in'], 'authType' => $isAllStoreAuth ? 'all_store' : 'single_store', 'platform' => 'shansong' ]; if (!$isAllStoreAuth) { $data['third_store_id'] = $thirdStoreId; $data['shans_store_id'] = $storeId; } DeliveryAuthTokenClass::add($data); if (false) { throw new \yii\web\HttpException(500, '保存授权信息失败'); } return $this->asJson(['return_code' => 0, 'return_msg' => 'OK']); } /** * 货拉拉授权回调接口 */ public function actionHuolalaAuthCallback() { $get = Yii::$app->request->get(); $getData = json_encode($get, JSON_UNESCAPED_UNICODE); Yii::info($getData); $code = $get['code']; $mainId = $get['mainId']; // 获取AccessToken $auth = new \common\components\delivery\platform\huolala\Auth(); $result = $auth->getAccessToken($code); if (!$result['success']) { throw new \yii\web\HttpException(500, '获取授权失败:' . $result['error']); } // 保存授权信息到数据库 $data = [ 'mainId' => $mainId, 'shopId' => '', 'accessToken' => $result['access_token'], 'refreshToken' => $result['refresh_token'], 'expiresAt' => strtotime($result['auth_end_time']), 'authType' => 'all_store', 'platform' => 'huolala' ]; DeliveryAuthTokenClass::add($data); return $this->asJson(['return_code' => 0, 'return_msg' => 'OK']); } /** * 蜂鸟授权回调接口 */ public function actionFengniaoAuthCallback() { $get = Yii::$app->request->get(); $getData = json_encode($get, JSON_UNESCAPED_UNICODE); Yii::info($getData); $callbackData = Yii::$app->request->post(); if (empty($callbackData)) { $postStr = file_get_contents('php://input'); $callbackData = json_decode($postStr, true); if (empty($callbackData)) { Yii::info('回调请求的数据为空'); } } Yii::info('蜂鸟授权回调:' . json_encode($callbackData, JSON_UNESCAPED_UNICODE), 'fengniao'); $mainId = $get['mainId']; $code = $get['code']; $merchantId = $get['merchant_id']; $scope = $get['scope']; $state = $get['state']; // 获取AccessToken $auth = new \common\components\delivery\platform\fengniao\Auth(); $result = $auth->getAccessToken($code, $merchantId); if (!$result['success']) { throw new \yii\web\HttpException(500, '获取授权失败:' . $result['error']); } $result = $result['data']; // 保存授权信息到数据库 $data = [ 'mainId' => $mainId, 'shopId' => '', 'accessToken' => $result['access_token'], 'refreshToken' => $result['refresh_token'], 'expiresAt' => $result['expire_in'], 'authType' => 'all_store', 'platform' => 'fengniao' ]; DeliveryAuthTokenClass::add($data); return $this->asJson(['return_code' => 0, 'return_msg' => 'OK']); } /** * 达达授权回调接口 */ public function actionDadaAuthCallback() { $get = Yii::$app->request->get(); $getData = json_encode($get, JSON_UNESCAPED_UNICODE); Yii::info($getData); $callbackData = Yii::$app->request->post(); if (empty($callbackData)) { $postStr = file_get_contents('php://input'); $callbackData = json_decode($postStr, true); if (empty($callbackData)) { Yii::info('回调请求的数据为空'); } } Yii::info('达达授权回调:' . json_encode($callbackData, JSON_UNESCAPED_UNICODE), 'dada'); } /** * 顺丰授权与取消授权回调接口 */ public function actionShunfengAuthCallback() { $get = Yii::$app->request->get(); $getData = json_encode($get, JSON_UNESCAPED_UNICODE); Yii::info($getData); $callbackData = Yii::$app->request->post(); if (empty($callbackData)) { $postStr = file_get_contents('php://input'); $callbackData = json_decode($postStr, true); if (empty($callbackData)) { Yii::info('回调请求的数据为空'); } } Yii::info('顺丰授权与取消授权回调接口:' . json_encode($callbackData, JSON_UNESCAPED_UNICODE), 'dada'); } }