'待发单', self::DELIVERY_STATUS_WAIT_HOLD => '待抢单', self::DELIVERY_STATUS_DELIVERY => '待接单', self::DELIVERY_STATUS_PICKUP => '取单中', self::DELIVERY_STATUS_DELIVERING => '送单中', self::DELIVERY_STATUS_DONE => '已送达', self::DELIVERY_STATUS_CANCEL => '已撤销', self::DELIVERY_STATUS_IN_STORE => '骑手到店', ]; public static function callbackCommand($command, array $data){ debug_log([__FUNCTION__, __LINE__, $command, $data], __CLASS__ . '.log'); $command = 'cb_' . $command; if($data['data']){ $data = json_decode($data['data'], true); } $res = call_user_func_array([new KeloopCallback(), $command], [$data]); if($res['code'] === 0){ $res['code'] = 200; } debug_log([__FUNCTION__, __LINE__, $command, $res], __CLASS__ . '.log'); return $res; } public static function saasConf(){ $conf = json_decode(Option::get(OptionSetting::KELOOP, 0, 'saas', '{}')['value'], true); if(!$conf){ $conf = [ 'dev_key' => '', 'dev_secret' => '', 'team_token' => '', ]; } $url_callback = ''; if (\Yii::$app instanceof \yii\web\Application){ $url_callback = \Yii::$app->request->hostInfo . '/index.php/keloop/callback/index'; } return [ 'code'=>0, 'msg'=>'ok', 'data' => $conf, 'url_callback' => $url_callback, 'openLink' => 'https://open.keloop.cn/', 'adminLink' => 'https://admin.keloop.cn/', 'docLink' => 'https://apifox.com/apidoc/shared-a4e1709f-0728-4224-908e-35eb12bc2959/doc-1988267', ]; } public static function saasConfSave($conf){ if(!is_array($conf)){ $conf = json_decode($conf, true); } Option::set(OptionSetting::KELOOP, json_encode($conf), 0, 'saas'); return [ 'code'=>0, 'msg'=>'保存成功' ]; } public static function isSaasOpen() { $conf = self::saasConf(); return empty($conf['data']['dev_key']) ? 0 : 1; } public static function conf($store_id = 0, $refresh = 0) { $cacheK = $store_id; if(isset(self::$confs[$cacheK]) && !$refresh){ return self::$confs[$cacheK]; } $conf = json_decode(Option::get(OptionSetting::KELOOP_TOKEN, $store_id, 'store', '{}')['value'], true); if(!$conf){ $conf = [ "shop_name" => "", //店铺名称 "shop_tel" => "", //店铺电话 "shop_address" => "", //店铺地址 "shop_tag" => "", //店铺坐标,火星坐标,如:116.459226,40.007126 'meal_assessment_time' => '10', //出餐时间(分钟) ]; } self::$confs[$cacheK] = $conf; return $conf; } public static function saveConf($store_id = 0, $config = [], &$createMerchant = []) { $oldConf = self::conf($store_id, 1); $conf = array_merge($oldConf, $config); $createMerchant = self::createMerchant($store_id, $conf); if($createMerchant['code']){ return $createMerchant; } $set = Option::set(OptionSetting::KELOOP_TOKEN, json_encode($conf), $store_id, 'store'); self::conf($store_id, 1); return $set; } public static function getConf($store_id = 0, $key = '') { $config = self::conf($store_id); return $config[$key]; } public static function setConf($store_id = 0, $key = '', $val = '') { $config = self::conf($store_id); $config[$key] = $val; return self::saveConf($store_id, $config); } public static function apiConf($store_id = 0) { $conf = self::saasConf()['data']; return $conf; } public static function apiRes($apiRes) { $res = [ 'code' => 1, 'msg' => '操作失败。', ]; if($apiRes['code'] == 200){ $res = [ 'code' => 0, 'msg' => '操作成功.', ]; } $res['msg'] = $apiRes['message']; $res['data'] = $apiRes['data']; // $res['_data'] = $apiRes; //api_err字符串可用于筛选错误 $res['code'] && debug_log([__FUNCTION__, 'api_err', $apiRes], __CLASS__ . '.log'); return $res; } public static function apiErrs($n = 200) { $err = []; try{ $file_path = \Yii::$app->runtimePath . '/logs/app_modules_admin_models_keloop_KeloopForm.log'; if(file_exists($file_path)){ $fp = fopen($file_path,"r"); $pos=-2; $eof=""; while($n>0){ while($eof != "\n"){ if(!fseek($fp, $pos, SEEK_END)){ $eof = fgetc($fp); $pos--; }else{ break; } } $line = fgets($fp, 1000); if(strstr($line, 'api_err')){ $err[] = $line; } $eof=""; $n--; } } } catch (\Exception $ex) { $err[] = $ex->getMessage(); } return $err; } public static function sdk($store_id = 0) { return Keloop::app(self::apiConf($store_id)); } public static function getTeamInfo($store_id) { try{ return self::apiRes(self::sdk($store_id)->getTeamInfo()); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function getCouriers($store_id) { try{ return self::apiRes(self::sdk($store_id)->getCouriers()); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function getMerchants($store_id) { try{ return self::apiRes(self::sdk($store_id)->getMerchants()); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function getCourierTag($store_id, $trade_no) { try{ return self::apiRes(self::sdk($store_id)->getCourierTag(['trade_no' => $trade_no])); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function getOrderInfo($store_id, $trade_no, $change_data = 1) { try{ $res = self::apiRes(self::sdk($store_id)->getOrderInfo(['trade_no' => $trade_no])); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } $change_data && self::data_delivery_change($store_id, $trade_no, [], $res); return $res; } public static function getOrderLog($store_id, $trade_no) { try{ return self::apiRes(self::sdk($store_id)->getOrderLog(['trade_no' => $trade_no])); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function mealOutOrder($store_id, $trade_no, $time = 0) { $conf = self::conf($store_id); $param = [ 'trade_no' => $trade_no, 'meal_assessment_time' => time() + 60 * $conf['meal_assessment_time'], ]; if($time){ $param['meal_assessment_time'] = $time; } try{ return self::apiRes(self::sdk($store_id)->mealOutOrder($param)); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function createMerchant($store_id, $conf = []) { $cancelMerchantRelate = self::cancelMerchantRelate($store_id); if($cancelMerchantRelate['code']){ return $cancelMerchantRelate; } empty($conf) && $conf = self::conf($store_id); $params = [ "shop_id" => $store_id, "shop_name" => $conf['shop_name'], //店铺名称 "shop_tel" => $conf['shop_tel'], //店铺电话 "shop_address" => $conf['shop_address'], //店铺地址 "shop_tag" => $conf['shop_tag'], //店铺坐标,火星坐标,如:116.459226,40.007126 ]; try{ return self::apiRes(self::sdk($store_id)->createMerchant($params)); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function getFundUrl($store_id) { $params = [ "shop_id" => $store_id, ]; try{ return self::apiRes(self::sdk($store_id)->getFundUrl($params)); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function cancelMerchantRelate($store_id) { $params = [ "shop_id" => $store_id, ]; try{ return self::apiRes(self::sdk($store_id)->cancelMerchantRelate($params)); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function getFee($store_id, $lng, $lat) { $customer_tag = implode(',', [$lng, $lat]); $conf = self::conf($store_id); $params = [ "shop_id" => $store_id, "get_tag" => $conf['shop_tag'], "customer_tag" => $customer_tag, "order_price" => 10, //$order['pay_price'], ]; try{ return self::apiRes(self::sdk($store_id)->getFee($params)); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function preAddOrder($store_id, $order_no, $lng, $lat) { if (!$order_no) { return [ 'code' => 1, 'msg' => '缺少订单号参数' ]; } $fee = self::getFee($store_id, $lng, $lat); if ($fee['code']) { return $fee; } $freight = $fee['data']['pay_fee']; DeliveryInfo::deleteAll(['order_no' => $order_no, 'store_id' => $store_id, 'status' => 0]); $delivery_info = new DeliveryInfo(); $delivery_info->store_id = $store_id; $delivery_info->order_no = $order_no; $delivery_info->delivery_type = ''; $delivery_info->fee = $freight; $delivery_info->created_at = time(); $save = $delivery_info->save(); if(!$save){ return [ 'code' => 1, 'msg' => array_shift($delivery_info->getFirstErrors()), ]; } return [ 'code' => 0, 'msg' => 'ok', 'data' => [ "resultcode" => 0, 'fee' => (float)$delivery_info->fee, 'fee_data' => $fee, ], ]; } public static function order_created($store_id, $order_id = 0, $saveErr = 1){ try{ $order = Order::findOne($order_id); if(!$order){ return [ 'code' => 1, 'msg' => '订单信息错误', ]; } $delivery = DeliveryKeloop::findOne(['order_no' => $order->order_no, 'store_id' => $store_id]); if($delivery){ return [ 'code' => 1, 'msg' => '订单信息已存在', ]; } $params = self::getOrder($store_id, $order); try{ $res = self::apiRes(self::sdk($store_id)->createOrder($params)); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } if($res['code']){ if($saveErr){ DeliveryKeloopErr::saveErr($store_id, $order->id, $order->order_no, $res); } return $res; }else{ DeliveryKeloopErr::updateAll(['is_delete' => 1], ['order_no' => $order->order_no, 'store_id' => $store_id]); } self::data_order_created($store_id, $order, $res['data']); self::mealOutOrder($store_id, $res['data']['trade_no']); return $res; } catch (\Exception $ex) { \Yii::error([__METHOD__, $ex]); return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function data_order_created($store_id, $order, $data){ $delivery = new DeliveryKeloop(); $delivery->store_id = $store_id; $delivery->order_no = $order->order_no; $delivery->trade_no = $data['trade_no']; $delivery->fee = $data['pay_fee']; $delivery->delivery_status = self::DELIVERY_STATUS_DELIVERY; $save = $delivery->save(); if(!$save){ return [ 'code' => 1, 'msg' => array_shift($delivery->getFirstErrors()), ]; } if($order->trade_status < Order::ORDER_FLOW_SEND){ $order->trade_status = Order::ORDER_FLOW_SEND; $order->send_time = time(); if (!$order->save()) { debug_log([__FUNCTION__, __LINE__, '订单发货失败', array_shift($order->getFirstErrors())], __CLASS__ . '.log'); return [ 'code' => 1, 'msg' => '订单发货失败', ]; } } return [ 'code' => 0, 'msg' => 'ok', ]; } public static function getOrder($store_id, $order) { $address_data = json_decode($order->address_data, true); $conf = self::conf($store_id); $data = [ "shop_id" => $store_id, "shop_name" => $conf['shop_name'], //店铺名称 "shop_tel" => $conf['shop_tel'], //店铺电话 "shop_address" => $conf['shop_address'], //店铺地址 "shop_tag" => $conf['shop_tag'], //店铺坐标,火星坐标,如:116.459226,40.007126 // "store_id" => $md_id, 'order_mark' => $order->food_code, 'order_time' => date('Y-m-d H:i:s', $order->created_at), 'customer_tel' => $order->mobile, 'customer_address' => $order->address, 'customer_name' => $order->name, 'customer_tag' => implode(',', [$address_data['longitude'] ?? '', $address_data['latitude'] ?? '']), "order_no" => $order->order_no, 'pay_status' => 0, // 'pre_times' => $delivery_time, // 'pre_deliver_times' => $delivery_time, ]; if(!empty($order->delivery_time)){ $delivery_time = $order->delivery_time; $data['pre_times'] = $delivery_time; $data['pre_deliver_times'] = $delivery_time; } return $data; } /** * 订单取消 * @param type $store_id * @param type $order_id * @param type $reason * @param type $reason_code //取消类型 1用户 2商户 3客服 4系统 5其他 */ public static function order_canceled($store_id, $order_id = 0, $reason = '取消', $reason_code = 1){ try{ $order = Order::findOne($order_id); if(!$order){ return [ 'code' => 1, 'msg' => '订单信息错误', ]; } $delivery = DeliveryKeloop::findOne(['order_no' => $order['order_no'], 'store_id' => $store_id]); if(!$delivery){ return [ 'code' => 1, 'msg' => '配送单信息错误', ]; } $params = [ 'reason' => (string)$reason, 'trade_no' => $delivery['trade_no'], ]; try{ $res = self::apiRes(self::sdk($store_id)->cancelOrder($params)); } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } if($res['code'] != 0){ return $res; } self::data_order_canceled($store_id, $order->order_no, $params); return $res; } catch (\Exception $ex) { return [ 'code' => 1, 'msg' => $ex->getMessage(), ]; } } public static function data_order_canceled($store_id, $order_no = '', $params = []){ $delivery = DeliveryKeloop::findOne(['order_no' => $order_no, 'store_id' => $store_id]); $delivery->is_cancel = 1; $params['reason'] && $delivery->cancel_reason = $params['reason']; $save = $delivery->save(); if(!$save){ return [ 'code' => 1, 'msg' => array_shift($delivery->getFirstErrors()), ]; } return [ 'code' => 0, 'msg' => 'ok', ]; } public static function afterOrderSave($insert, $changedAttributes, $order){ try{ $local_type = Option::get(OptionSetting::STORE_LOCAL_TYPE, $order->store_id, 'store')['value']; $local_type = Option::get(OptionSetting::STORE_LOCAL_TYPE, $order->store_id, 'pay', $local_type)['value']; \Yii::error($local_type); if($local_type != 'keloop'){ return; } //取消订单 if (($order->is_delivery == 1) && ($order->trade_status == Order::ORDER_FLOW_CANCEL) && ($changedAttributes['trade_status'] != Order::ORDER_FLOW_CANCEL)) { $delivery = DeliveryKeloop::findOne(['order_no' => $order->order_no, 'store_id' => $order->store_id]); if($delivery){ $order_canceled = self::order_canceled($order->store_id, $order->id); if($order_canceled['code'] !== 0){ throw new \Exception($order_canceled['msg']); } } } } catch (\Exception $ex) { \Yii::error($ex); debug_log([__FUNCTION__, __LINE__, $ex->getMessage(), $ex->getTrace()], __CLASS__ . '.log'); } } public static function data_delivery_change($store_id, $trade_no = '', $callback_data = [], $orderInfo = []){ if($callback_data['sign'] && cache_lock(['keloop_callback', $callback_data['sign']], 86400)){ return; } $delivery = DeliveryKeloop::findOne(['trade_no' => $trade_no, 'store_id' => $store_id]); if(!$delivery){ return; } $orderInfo || $orderInfo = self::getOrderInfo($store_id, $trade_no, 0); if($orderInfo['code']){ return; } $delivery->delivery_status = $orderInfo['data']['status']; $delivery->rider_name = $orderInfo['data']['courier_name']; $delivery->rider_mobile = $orderInfo['data']['courier_tel']; $ctag = self::getCourierTag($store_id, $trade_no); if(!$ctag['code']){ $delivery->lng = $ctag['data']['longitude']; $delivery->lat = $ctag['data']['latitude']; } $save = $delivery->save(); if(!$save){ return [ 'code' => 1, 'msg' => array_shift($delivery->getFirstErrors()), ]; } if($delivery->delivery_status == self::DELIVERY_STATUS_DONE){ debug_log([__FUNCTION__, __LINE__, '确认收货', $delivery->delivery_status], __CLASS__ . '.log'); //确认收货 $order = Order::find()->where(['order_no' => $delivery->order_no])->andWhere(['trade_status' => Order::ORDER_FLOW_SEND])->one(); if(!$order){ return [ 'code' => 1, 'msg' => '确认收货订单状态信息错误', ]; } $order->trade_status = Order::ORDER_FLOW_CONFIRM; $order->confirm_time = time(); if (!$order->save()) { debug_log([__FUNCTION__, __LINE__, '确认收货失败', array_shift($order->getFirstErrors())], __CLASS__ . '.log'); return [ 'code' => 1, 'msg' => '确认收货失败', ]; } } return [ 'code' => 0, 'msg' => 'ok', ]; } public static function orderList($store_id, $params){ $form = new OrderListForm(); if($params['dateEnd']){ $params['dateEnd'] .= ' 23:59:59'; } $form->attributes = $params; $form->store_id = $store_id; $form->keloop = 1; $form->mch = -1; $form->is_delivery = true; $list = $form->search(); unset($list['data']['export_list']); unset($list['data']['express_list']); foreach($list['data']['data'] as &$item){ $deliveryModel = DeliveryKeloop::findOne(['order_no' => $item['order_no']]); $delivery = $deliveryModel->attributes; $delivery['delivery_status_name'] = self::$order_status_list[$delivery['delivery_status']] ?? ''; $keloopOrderInfo = self::getOrderInfo($store_id, $delivery['trade_no']); if(!$keloopOrderInfo['code']){ $delivery['delivery_status_name'] = self::$order_status_list[$keloopOrderInfo['data']['status']] ?? ''; } $item['delivery'] = $delivery; $item['keloopOrderInfo'] = $keloopOrderInfo; } $list['keloop'] = [ 'delivery_status_list' => self::$order_status_list, ]; return $list; } public static function orderErrList($store_id){ $list = DeliveryKeloopErr::find()->where(['store_id' => $store_id, 'is_delete' => 0])->orderBy('id DESC')->all(); return [ 'code' => 0, 'data' => $list, ]; } public static function orderErrRecreate($store_id, $id){ $err = DeliveryKeloopErr::findOne(['store_id' => $store_id, 'id' => $id, 'is_delete' => 0]); if(!$err){ return [ 'code' => 1, 'msg' => '订单不存在,或配送单正常' ]; } $delivery_res = KeloopForm::order_created($store_id, $err['order_id'], 0); return $delivery_res; } public static function orderFee($store_id, $order_id){ $order = Order::findOne(['store_id' => $store_id, 'id' => $order_id]); $addr = json_decode($order->address_data, true); $getFee = KeloopForm::getFee($store_id, $addr['longitude'], $addr['latitude']); return $getFee; } }