['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 actionGetAuthUrl() { $platform = Yii::$app->request->get('platformId'); $Auth = new \common\components\delivery\services\AuthService(); $mainId = $this->mainId; switch($platform){ case 'shansong': $Auth->shansongAuth($mainId); break; case 'huolala': $Auth->huolalaAuth($mainId); break; case 'fengniao': $Auth->fengniaoAuth($mainId); break; case 'dada': $Auth->dadaAuth($mainId); break; case 'shunfeng': $Auth->shunfengAuth($mainId); break; case 'didi': $Auth->didiAuth($mainId, $this->shopId); break; case 'dada': $Auth->dadaAuth($mainId); break; default: util::error(-1, $platform . '不存在'); break; } } public function actionCancelAuth() { // 有平台有对应的取消授权接口,有的没有。 // 有的就对接下,然后再删除数据。没有的直接删除数据就行。 $platform = Yii::$app->request->get('platform'); $delivery = DeliveryAuthTokenClass::getByCondition(['mainId'=>$this->mainId, 'platform'=>$platform]); if($delivery) { if($platform == 'shansong') { $auth = new \common\components\delivery\platform\shansong\Auth(); $result = $auth->cancelAuthorization($delivery['accessToken']); if($result['success']) { // 授权已取消 }else{ if($result['message'] == '商户未授权,请授权后再次尝试'){ Yii::info($this->mainId . '--商户未授权,请授权后再次尝试'); }else{ util::fail('取消授权失败:'.$result['message']); } } } }else{ util::fail('取消授权失败,数据未找到'); } $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; } } 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'], ]; } return $newCitiesArr; } 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']; $weight = $post['weight']; $remark = $post['remark'] ?? ''; $adminId = $this->adminId; util::checkRepeatCommit($adminId, 3); $orderId = intval($post['orderId']); $order = OrderClass::getById($orderId); $shop = ShopClass::getById($this->shopId); $order['weight'] = $weight; $order['remark'] = $remark; $ds = new DispatchService($this->mainId); $platformQuotes = $ds->getAllPlatformPrice($order, $shop, $orderTime); if(isset($platformQuotes['error'])){ util::fail($platformQuotes['error']); } // 格式化各平台报价为前端展示格式 $deliveryList = $ds->formatPlatformQuotesForDisplay($platformQuotes); // 按 price 从低到高排序 ArrayHelper::multisort($deliveryList, 'price', SORT_ASC); $ret['deliveryList'] = $deliveryList; util::success($ret, "success"); } // 创建订单(真实下单) public function actionCreateOrder() { $form = new CreateOrderForm(); $form->loadAndValidate(); $post = $form->getAttributes(); $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['pickupTime'] = $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(); OrderClass::hasSend($ghsOrder);// 更新 status 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']; //取消实际需金额(单位:分) } if($platform == 'shunfeng') { //请求订单预取消接口,获取是否可取消。如果运单状态不允许取消,则直接返回 $cancelParams = ['order_id'=>$orderId, 'order_cancel_code'=>$post['order_cancel_code']]; $res = $ds->getAdapter()->preCancelOrder($cancelParams); if($res['code'] != 0){ util::fail('运单状态不允许取消'); } $post['order_type'] = 1; //查询订单ID类型 1、顺丰订单号 2、商家订单号 } $post['order_cancel_role'] = 1; //1:商户取消, 2:用户取消 $ret = $ds->cancelOrder($orderId, $post); if($ret['code'] == 0) { // 更新跑腿订单与批发订单状态 $gdo->orderStatus = -1; $gdo->cancelCost = $ret['platform'] !== 'fengniao' ? $ret['data']['deductionFee'] : $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 = ''; $ghsOrder->sendTip = 0; 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); if($ret['code'] == 0){ util::success($ret['data']); }else{ Yii::error('获取订单取消原因:'.json_encode($ret)); util::fail($ret['msg']); } } /** * 添加小费 */ public function actionAddTip() { $post = Yii::$app->request->post(); $ghsOrderId = $post['ghsOrderId']; $platform = $post['platform']; $tips = intval($post['tips']); if($tips < 0) { util::fail('小费金额至少为1元'); } $tips *= 100; $gdo = GhsDeliveryOrderClass::getByCondition(['mainId'=>$this->mainId, 'ghsOrderId'=>$ghsOrderId, 'orderStatus'=>['in',[0,1,2,10]]], true); if($gdo == null){ util::fail('当前配送状态无法加小费'); } $ds = new DispatchService($this->mainId, $platform); $ret = $ds->addTip(['orderId'=>$gdo->orderId, 'tips'=>$tips]); if($ret['code'] == 0){ $gdo->tip = $tips + $gdo->tip; $gdo->save(); $orderObj = OrderClass::getById($gdo->ghsOrderId, true, 'id,sendTip'); $orderObj->sendTip = $tips/100 + $orderObj->sendTip; $orderObj->save(); util::success($ret['data']); }else{ Yii::error('添加小费请求失败,请求数据是:'.json_encode($ret)); util::fail($ret['msg']); } } // ---------------------------------------------------- 普通回调接口 ---------------------------------------------------------- // 第三方回调入口 /** * 回调接口 */ 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'); $action = $post['action'] ?? ''; if($action != 'order_update') { Yii::error('当前接口只处理货拉拉订单状态变更事件'); return $this->asJson(['return_code' => 0, 'msg' => '当前接口只处理货拉拉订单状态变更事件']); } $param = $post; unset($param['signature']); $huolala = new \common\components\delivery\platform\huolala\Huolala(); $paramSign = $huolala->generateSignatureWithoutAppSecret($param); //注意:不用拼接secret,所有的参数都参与计算,包含空值。 if($paramSign == $post['signature']){ $cbh = new \common\components\delivery\platform\huolala\CallBackHandler(); $ret = $cbh->handler($post); if($ret) { return $this->asJson(['serial_no' => $post['serial_no']]); } else { Yii::error('货拉拉回调失败: ' . json_encode($post, JSON_UNESCAPED_UNICODE)); return $this->asJson(['return_code' => -1, 'return_msg' => 'failed']); } }else{ Yii::error('蜂鸟回调接口签名出错'); return $this->asJson(['return_code' => -1, 'return_msg' => 'signature error']); } } /** * 顺丰回调接口 */ public function actionShunfengCallback() { $post = Yii::$app->request->post(); $cbh = new \common\components\delivery\platform\shunfeng\CallBackHandler(); $ret = $cbh->handle($post);// 各回调处理 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']); $templatePath = Yii::getAlias('@common/components/delivery/html/auth-success-template.html'); $template = ''; if (is_file($templatePath) && is_readable($templatePath)) { $template = file_get_contents($templatePath); } if ($template === false || $template === '') { throw new \yii\web\HttpException(500, '返回h5页面不存在'); } $content = strtr($template, [ '{{title}}' => '授权成功', '{{heading}}' => '授权成功', '{{message}}' => '您的闪送账号已成功完成授权,现在可以返回系统继续使用聚合配送服务。', ]); $response = Yii::$app->response; $response->format = Response::FORMAT_HTML; $response->content = $content; return $response; } /** * 货拉拉授权回调接口 */ 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); $templatePath = Yii::getAlias('@common/components/delivery/html/auth-success-template.html'); $template = ''; if (is_file($templatePath) && is_readable($templatePath)) { $template = file_get_contents($templatePath); } if ($template === false || $template === '') { $template = '
{{message}}
'; } $content = strtr($template, [ '{{title}}' => '授权成功', '{{heading}}' => '授权成功', '{{message}}' => '您的货拉拉账号已成功完成授权,现在可以返回系统继续使用聚合配送服务。', ]); $response = Yii::$app->response; $response->format = Response::FORMAT_HTML; $response->content = $content; return $response; } /** * 蜂鸟授权回调接口 */ public function actionFengniaoAuthCallback() { $callbackData = Yii::$app->request->post(); if (empty($callbackData)) { Yii::error('回调请求的数据为空'); } Yii::info('蜂鸟授权回调:' . json_encode($callbackData, JSON_UNESCAPED_UNICODE), 'fengniao'); $get = Yii::$app->request->get(); if(!isset($get['main_id'])){ Yii::error('蜂鸟授权回调的回调地址 authCallbackUrl 中没有带 main_id'); noticeUtil::push('蜂鸟授权回调的回调地址 authCallbackUrl 中没有带 main_id,回调数据:' . json_encode($callbackData, JSON_UNESCAPED_UNICODE), 'fengniao'); return; } $mainId = $get['main_id']; $code = $callbackData['code']; $merchantId = $callbackData['merchant_id']; $scope = $callbackData['scope']; $state = $callbackData['state']; // 获取AccessToken $auth = new \common\components\delivery\platform\fengniao\Auth(); $result = $auth->getAccessToken($code, $merchantId); if (!$result['success']) { noticeUtil::push("mainId=" . $mainId . ' 授权时,获取token失败,请让其重新发起授权'); Yii::error(json_encode($result)); throw new \yii\web\HttpException(500, '获取授权失败:' . $result['error']); } $result = $result['data']; $exist = DeliveryAuthTokenClass::exists(['mainId' => $mainId, 'platform' => 'fengniao']); if($exist){ noticeUtil::push("mainId=" . $mainId . ' 已成功授权了。如果蜂鸟不能正确使用,请查询数据库进行检查。'); }else{ // 保存授权信息到数据库 $data = [ 'mainId' => $mainId, 'merchantId' => $merchantId, 'shopId' => '', 'accessToken' => $result['access_token'], 'refreshToken' => $result['refresh_token'], 'expiresAt' => $result['expire_in'] + time(), 'authType' => 'all_store', //即 scope='all' '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 actionDadaCallback() { $post = Yii::$app->request->post(); Yii::info('达达回调:' . json_encode($post, JSON_UNESCAPED_UNICODE), 'dada'); return $this->asJson(['status' => 'ok']); } /** * 顺丰授权与取消授权回调接口 */ 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'); } }