buildRequestPayload('u-city-list', []); $resp = HttpClient::post($this->baseUrl, $payload); $citiesArr = $resp['data']['city_list']; $newCitiesArr = [ 'expire' => $resp['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 $resp; } /** * 获取城市可选车型信息 * * 通过此接口可获取城市可选车型信息,包括车型详情、计价规则、车型附加要求和额外需求等 * * @param int $cityId 城市ID * @param bool $forceRefresh 是否强制刷新缓存 * @return array|null 包含城市车型信息的响应数据 */ public function getCityVehicleList($cityId, $forceRefresh = false) { // 参数验证 if (empty($cityId) || !is_numeric($cityId)) { return null; } $cityId = (int)$cityId; $env = $this->isSandbox ? 'dev' : 'production'; // 避免切环境后,使用了不同环境的缓存数据 $cacheKey = sprintf(self::CITY_VEHICLE_CACHE_KEY_PATTERN, $cityId, $env); $redis = Yii::$app->has('redis') ? Yii::$app->redis : null; if ($redis !== null) { if ($forceRefresh) { $redis->executeCommand('DEL', [$cacheKey]); } else { $cached = $redis->executeCommand('GET', [$cacheKey]); if ($cached !== false && $cached !== null) { $decoded = json_decode($cached, true); if (is_array($decoded)) { return $decoded; } } } } // 构建请求参数,发送请求 $payload = $this->buildRequestPayload('u-city-info', ['city_id' => $cityId]); $resp = HttpClient::post($this->baseUrl, $payload); // 处理响应 if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) { $data = $resp['data']; // 提取车型列表 $vehicleList = []; if (!empty($data['vehicle_list']) && is_array($data['vehicle_list'])) { foreach ($data['vehicle_list'] as $vehicle) { // 处理超里程价格分段 $exceedSegmentPrice = []; if (!empty($vehicle['price_text_item']['exceed_segment_price']) && is_array($vehicle['price_text_item']['exceed_segment_price'])) { foreach ($vehicle['price_text_item']['exceed_segment_price'] as $segment) { $exceedSegmentPrice[] = [ 'start_exdistancekm' => $segment['start_exdistancekm'] ?? 0, 'end_exdistancekm' => $segment['end_exdistancekm'] ?? 0, 'price_extra_fen' => $segment['price_extra_fen'] ?? 0, ]; } } // 处理车型附加要求 $vehicleStdItem = []; if (!empty($vehicle['vehicle_std_item']) && is_array($vehicle['vehicle_std_item'])) { foreach ($vehicle['vehicle_std_item'] as $std) { $vehicleStdItem[] = [ 'name' => $std['name'] ?? '', 'desc' => $std['desc'] ?? '', 'price_fen' => $std['price_fen'] ?? 0, ]; } } $vehicleList[] = [ 'order_vehicle_id' => $vehicle['order_vehicle_id'] ?? 0, 'vehicle_name' => $vehicle['vehicle_name'] ?? '', 'img_url_high_light' => $vehicle['img_url_high_light'] ?? '', 'vehicle_volume' => $vehicle['vehicle_volume'] ?? '', 'vehicle_weight' => $vehicle['vehicle_weight'] ?? '', 'vehicle_size' => $vehicle['vehicle_size'] ?? '', 'standard_order_vehicle_id' => $vehicle['standard_order_vehicle_id'] ?? 0, 'text_desc' => $vehicle['text_desc'] ?? '', 'vehicle_attr' => $vehicle['vehicle_attr'] ?? 0, 'price_text_item' => [ 'base_distancekm' => $vehicle['price_text_item']['base_distancekm'] ?? 0, 'base_price_fen' => $vehicle['price_text_item']['base_price_fen'] ?? 0, 'exceed_segment_price' => $exceedSegmentPrice, ], 'vehicle_std_item' => $vehicleStdItem, ]; } } // 处理城市额外需求列表 $specReqItem = []; if (!empty($data['spec_req_item']) && is_array($data['spec_req_item'])) { foreach ($data['spec_req_item'] as $specReq) { $specReqItem[] = [ 'type' => (int)($specReq['type'] ?? 0), 'name' => $specReq['name'] ?? '', 'desc' => $specReq['desc'] ?? '', 'price_type' => (int)($specReq['price_type'] ?? 0), 'price_value_fen' => (int)($specReq['price_value_fen'] ?? 0), ]; } } $result = [ 'city_id' => $data['city_id'], 'name' => $data['name'], 'name_en' => $data['name_en'], 'city_info_revision' => $data['city_info_revision'], 'vehicle_list' => $vehicleList, 'spec_req_item' => $specReqItem, ]; if ($redis !== null) { $redis->executeCommand('SETEX', [ $cacheKey, self::CITY_VEHICLE_CACHE_TTL, json_encode($result, JSON_UNESCAPED_UNICODE) ]); } return $result; } return null; } /** * 转化订单数据 * @param $order * @param $shop * @param $params * @return array */ public function formatOrderData($order, $shop, $params) { //用车时间 -- 用车时间(unix时间戳:秒)。 取值范围:(当前时间+10分钟)<=用车时间<=(当前时间+5天) $orderTime = strtotime($params['pickupTime']); if($orderTime < (time()+600)) { $orderTime = time() + 600; } $cityId = $params['city_id']; $apiData = [ 'city_id' => $cityId, 'city_info_revision' => $params['city_info_revision'], 'order_vehicle_id' => $params['order_vehicle_id'], 'addr_info' => [ [ 'name' => $shop['merchantName'], //地址名称 'addr' => $shop['fullAddress'], 'lat_lon' => ['lat' => (float)$shop['lat'], 'lon' => (float)$shop['long']], 'city_id' => $cityId, 'city_name' => $shop['city'], 'district_name' => $shop['dist'], 'house_number' => $shop['floor'], 'contacts_name' => $shop['mobile'], 'contacts_phone_no' => $shop['mobile'], ], [ 'name' => empty($order['showAddress']) ? $order['customName'] : $order['showAddress'], 'addr' => $order['fullAddress'], 'lat_lon' => ['lat' => (float)$order['lat'], 'lon' => (float)$order['long']], 'city_id' => $cityId, 'city_name' => $order['city'], 'district_name' => $order['dist'], 'house_number' => $order['floor'], 'contacts_name' => $order['customName'], 'contacts_phone_no' => $order['customMobile'], ] ], 'vehicle_std' => $params['vehicle_std'], 'spec_req' => $params['spec_req'], 'order_time' => $orderTime, 'contact_name' => $shop['merchantName'], 'contact_phone_no' => $shop['mobile'], 'out_user_id' => $params['mainId'], //下单人在服务商平台侧的唯一ID,如果没有,可以传0 'ip' => $params['ip'], 'remark' => $params['remark'], //string(300) 'total_price_fen' => $params['total_price_fen'], 'pay_type' => 4, 'price_calculate_id' => $params['price_calculate_id'], //计价id,从计价接口返回中取值 'price_item_encryption' => $params['price_item_encryption'], //估价返回的费用项,从计价接口返回中取值 'vehicle_attr' => $params['vehicle_attr'], //大小车属性,从计价接口返回中取值 'commodity_info' => $params['commodity_info'] //商品信息(直接透传u-price-multiCalculate返回的commodity_item字段) ]; // 添加订单消息回调地址 $apiData['notify_url'] = $this->notifyUrl; return $apiData; } /** * 创建订单(需用户授权) * * 根据货拉拉官方文档实现下单功能 * * @param array $data 订单数据 * @return array API响应结果 */ public function createOrder($order) { // 构建地址信息 $addrInfo = []; if (!empty($order['addr_info']) && is_array($order['addr_info'])) { foreach ($order['addr_info'] as $addr) { $addrInfo[] = [ 'name' => $addr['name'] ?? '', 'addr' => $addr['addr'] ?? '', 'city_id' => (int)($addr['city_id'] ?? 0), 'city_name' => $addr['city_name'] ?? '', 'district_name' => $addr['district_name'] ?? '', 'house_number' => $addr['house_number'] ?? '', 'contacts_name' => $addr['contacts_name'] ?? '', 'contacts_phone_no' => $addr['contacts_phone_no'] ?? '', 'lat_lon' => [ 'lat' => (double)($addr['lat_lon']['lat'] ?? 0), 'lon' => (double)($addr['lat_lon']['lon'] ?? 0), ] ]; } } // 构建业务数据 $apiData = [ 'city_id' => (int)($order['city_id'] ?? 0), 'city_info_revision' => (int)($order['city_info_revision'] ?? 0), 'order_vehicle_id' => (int)($order['order_vehicle_id'] ?? 0), 'addr_info' => $addrInfo, 'contact_name' => $order['contact_name'] ?? '', 'contact_phone_no' => $order['contact_phone_no'] ?? '', 'total_price_fen' => (int)($order['total_price_fen'] ?? 0), 'pay_type' => (int)($order['pay_type'] ?? 0), 'price_calculate_id' => $order['price_calculate_id'] ?? '', 'price_item_encryption' => $order['price_item_encryption'] ?? '', 'vehicle_attr' => (int)($order['vehicle_attr'] ?? 0), 'order_time' => (int)($order['order_time'] ?? time()), ]; // 可选参数 if (!empty($order['vehicle_std'])) { $apiData['vehicle_std'] = is_array($order['vehicle_std']) ? $order['vehicle_std'] : [$order['vehicle_std']]; } if (!empty($order['spec_req'])) { $apiData['spec_req'] = is_array($order['spec_req']) ? $order['spec_req'] : [$order['spec_req']]; } if (!empty($order['remark'])) { $apiData['remark'] = $order['remark']; } if (!empty($order['out_user_id'])) { $apiData['out_user_id'] = $order['out_user_id']; } if (!empty($order['ip'])) { $apiData['ip'] = $order['ip']; } if (!empty($order['notify_url'])) { $apiData['notify_url'] = $order['notify_url']; } if (isset($order['coupon_id'])) { $apiData['coupon_id'] = $order['coupon_id']; } if (isset($order['invoice_type'])) { $apiData['invoice_type'] = (int)$order['invoice_type']; } if (isset($order['order_service_type'])) { $apiData['order_service_type'] = (int)$order['order_service_type']; } if (!empty($order['ref'])) { $apiData['ref'] = $order['ref']; } if (!empty($order['themis_data'])) { $apiData['themis_data'] = $order['themis_data']; } if (!empty($order['commodity_info'])) { $apiData['commodity_info'] = $order['commodity_info']; } // 构建请求参数 $payload = $this->buildRequestPayload( 'u-order-request', $apiData, $this->accessToken ); // 发送请求 $resp = HttpClient::post($this->baseUrl, $payload, ['Content-type'=>'application/json']); // 处理响应 if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) { $respData = $resp['data']; return [ 'code' => 0, 'platform' => 'huolala', 'data' => [ 'order_id' => $respData['order_uuid'], // 订单UUID 'fee' => $respData['price_fen'] ?? 0, // 订单金额(分) 'distance' => 0, // -------- 'order_display_id' => $respData['order_display_id'] ?? '', // 订单号 'pay_notify_url' => $respData['pay_notify_url'] ?? null, // 支付回调地址 'pay_mch_id' => $respData['pay_mch_id'] ?? null, // 银联商户号 'pay_serv_mch_id' => $respData['pay_serv_mch_id'] ?? null, // 银联服务商号 'pay_no' => $respData['pay_no'] ?? null, // 银联支付单号 'pay_time_expire' => $respData['pay_time_expire'] ?? null, // 支付超时时间 ] ]; } if(in_array($resp['ret'], [30010, 30011, 31006])){ // 错误状态码 $this->cityList(); // 刷新城市列表信息 } return [ 'code' => $resp['ret'] ?? -1, 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } public function cancelOrder($orderUuid, $reason) { $res = $this->selectOrder($orderUuid); if ($res['ret'] != 0) { new \Exception('查询订单详情失败, code=' . $res['ret'] . ', msg=' . $res['msg']); } $status = $res['data']['order_status']; $apiData = [ 'order_display_id' => $orderUuid, 'reason' => $reason, 'order_status' => $status ]; // 构建请求参数 $payload = $this->buildRequestPayload( 'u-order-cancel', $apiData, $this->accessToken ); // 发送请求 $resp = HttpClient::post($this->baseUrl, $payload); if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) { $respData = $resp['data']; return [ 'code' => 0, 'platform' => 'huolala', 'data' => [ 'deductionFee' => 0, //取消收费金额(单位:分) 'order_status' => $respData['order_status'] ] ]; } return [ 'code' => $resp['ret'] ?? -1, 'platform' => 'huolala', 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } /** * 查询订单详情 * @param string $orderUuid 货拉拉订单号 -- 文档上写的是 order_display_id,实则要传 order_uuid * @return array */ public function selectOrder($orderUuid) { $apiData = [ 'order_display_id' => $orderUuid ]; // 构建请求参数 $payload = $this->buildRequestPayload( 'u-order-detail', $apiData, $this->accessToken ); // 发送请求 $resp = HttpClient::post($this->baseUrl, $payload); return $resp; } /** * 构建多车型报价请求信息列表(供并发请求使用) * * @param array $data 包含 vehicle_type_list 的数据 * @param string $orderTime 配送时间 * @return array 请求信息列表,以车型ID为键 */ public function buildPriceRequest($data, $orderTime) { //用车时间 -- 用车时间(unix时间戳:秒)。 取值范围:(当前时间+10分钟)<=用车时间<=(当前时间+5天) $orderTime = strtotime($orderTime); if($orderTime < (time()+600)) { $orderTime = time() + 600; } $requests = []; foreach($data['vehicle_type_list'] as $vehicle) { // 构建地址信息 $addrInfo = []; if (!empty($vehicle['addr_info']) && is_array($vehicle['addr_info'])) { foreach ($vehicle['addr_info'] as $addr) { $addrInfo[] = [ 'name' => $addr['name'] ?? '', 'addr' => $addr['addr'] ?? '', 'city_id' => (int)($addr['city_id'] ?? 0), 'city_name' => $addr['city_name'] ?? '', 'district_name' => $addr['district_name'] ?? '', 'house_number' => $addr['house_number'] ?? '', 'contacts_name' => $addr['contacts_name'] ?? '', 'contacts_phone_no' => $addr['contacts_phone_no'] ?? '', 'lat_lon' => [ 'lat' => (double)($addr['lat_lon']['lat'] ?? 0), 'lon' => (double)($addr['lat_lon']['lon'] ?? 0), ] ]; } } $apiData = [ 'city_id' => (int)($data['city_id'] ?? 0), 'order_vehicle_id' => (int)($vehicle['order_vehicle_id'] ?? 0), 'city_info_revision' => (int)($vehicle['city_info_revision'] ?? 0), 'order_time' => $orderTime, 'addr_info' => $addrInfo, ]; if (!empty($vehicle['vehicle_std'])) { $apiData['vehicle_std'] = is_array($vehicle['vehicle_std']) ? $vehicle['vehicle_std'] : [$vehicle['vehicle_std']]; } if (!empty($vehicle['spec_req'])) { $apiData['spec_req'] = is_array($vehicle['spec_req']) ? $vehicle['spec_req'] : [$vehicle['spec_req']]; } if (isset($vehicle['coupon_id']) && !empty($vehicle['coupon_id'])) { $apiData['coupon_id'] = $vehicle['coupon_id']; } if (isset($vehicle['invoice_type'])) { $apiData['invoice_type'] = (int)$vehicle['invoice_type']; } if (isset($vehicle['order_service_type'])) { $apiData['order_service_type'] = (int)$vehicle['order_service_type']; } $payload = $this->buildRequestPayload( 'u-price-multiCalculate', $apiData, $this->accessToken ); // 使用车型ID作为请求的唯一标识 $vehicleId = (int)($vehicle['order_vehicle_id'] ?? 0); $requests["huolala_vehicle_{$vehicleId}"] = [ 'url' => $this->baseUrl, 'data' => $payload, 'headers' => [], 'timeout' => 5.0, ]; } return $requests; } /** * 处理货拉拉报价响应 * * @param array $resp API 响应 * @return array|null 解析后的报价结果 */ public function processPriceResponse($resp) { if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) { return $resp['data']; } return null; } /** * @deprecated * 获取运费报价(多品类)- 需用户授权 * * 根据货拉拉官方文档 u-price-multiCalculate 实现多品类估价功能 * * @param array $data 估价参数 * @param string $orderTime 配送时间 * @return array|null 包含多个计价方案的报价结果 * @deprecated 使用 buildPriceRequest 和 processPriceResponse 替代 */ public function getPrice($data, $orderTime) { $requests = $this->buildPriceRequest($data, $orderTime); $priceList = []; foreach ($requests as $requestInfo) { $resp = HttpClient::post($requestInfo['url'], $requestInfo['data']); $processed = $this->processPriceResponse($resp); if ($processed) { $priceList[] = $processed; } } return !empty($priceList) ? ['price_list' => $priceList] : null; } /** * 获取可选取消原因 * * 通过此接口可获取可选择的取消原因,包括主原因和子原因 * * @param array $data 业务参数(当前接口无必需参数,可传空数组) * @return array|null 包含取消原因列表的响应数据 */ public function getCancelReasonList($data) { // 构建请求参数(无业务数据) $payload = $this->buildRequestPayload('u-order-cancelReason', []); // 发送请求 $resp = HttpClient::post($this->baseUrl, $payload); // 处理响应 if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) { $respData = $resp['data']; // 提取取消原因列表 $reasonList = []; if (!empty($respData['reason']) && is_array($respData['reason'])) { foreach ($respData['reason'] as $reason) { if (!empty($reason['sub_reason']) && is_array($reason['sub_reason'])) { foreach ($reason['sub_reason'] as $subReason) { $reasonList[] = [ 'order_cancel_code' => (int)($subReason['id'] ?? 0), 'order_cancel_desc' => $subReason['name'] ?? '', ]; } } } } return [ 'code' => 0, 'data' => [ 'reasonList' => $reasonList, ] ]; } return [ 'code' => $resp['ret'] ?? -1, 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } public function addTip($data) { $orderDisplayId = $data['orderId'] ?? null; $totalTipsFen = $data['tips'] ?? null; if (empty($orderDisplayId)) { return [ 'code' => -1, 'platform' => 'huolala', 'msg' => 'order_display_id不能为空', 'data' => null, ]; } if (!isset($totalTipsFen) || (int)$totalTipsFen <= 0) { return [ 'code' => -1, 'platform' => 'huolala', 'msg' => 'total_tips_fen必须为正整数(单位:分)', 'data' => null, ]; } $apiData = [ 'order_display_id' => $orderDisplayId, 'total_tips_fen' => (int)$totalTipsFen, ]; $payload = $this->buildRequestPayload( 'u-order-addTips', $apiData, $this->accessToken ); $resp = HttpClient::post($this->baseUrl, $payload, ['Content-type' => 'application/json']); if (isset($resp['ret']) && $resp['ret'] == 0) { return [ 'code' => 0, 'platform' => 'huolala', 'data' => $resp['data'] ?? [], ]; } return [ 'code' => $resp['ret'] ?? -1, 'platform' => 'huolala', 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null, ]; } }