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 $order * @param $orderType * @param $shop * @param $params * @return array * @throws \Exception */ public function formatOrderData($order, $orderType, $shop, $params) { // 下单时必须优先使用估价时透传的时间,避免货拉拉核价失败。 $orderTime = $params['order_time'] ?? null; if (empty($orderTime)) { $orderTime = strtotime($params['pickupTime']); if($orderTime < (time()+600) ) { $orderTime = time() + 600; } } $cityId = $params['city_id']; // 下单时必须优先使用估价时透传的地址 $addrInfo = $params['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' => Functions::getMobile($order), ] ]; $apiData = [ 'city_id' => $cityId, 'city_info_revision' => $params['city_info_revision'], 'order_vehicle_id' => $params['order_vehicle_id'], 'addr_info' => $addrInfo, 'vehicle_std' => $params['vehicle_std'] ?? [], 'spec_req' => $params['spec_req'], 'order_time' => (int)$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字段) 'invoice_type' => $params['invoice_type'] ]; if (isset($params['coupon_id'])) { $apiData['coupon_id'] = $params['coupon_id']; } // 添加订单消息回调地址 $apiData['notify_url'] = $this->notifyUrl; // 跑腿车型,传这个值会出现"核价失败",所以要去除 if ($params['type'] == '跑腿') { unset($apiData['spec_req']); } 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'], 'addr_info' => $addrInfo, 'contact_name' => $order['contact_name'] ?? '', 'contact_phone_no' => $order['contact_phone_no'] ?? '', 'total_price_fen' => (int)$order['total_price_fen'], 'pay_type' => $order['pay_type'], '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'], ]; // 可选参数 if (!empty($order['vehicle_std'])) { $apiData['vehicle_std'] = is_array($order['vehicle_std']) ? $order['vehicle_std'] : [$order['vehicle_std']]; } if (!empty($order['spec_req']) && is_array($order['spec_req'])) { $apiData['spec_req'] = array_map('intval', $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'] = intval($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' => [ 'orderId' => $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, $post) { if(isset($post['order_cancel_reason']) && $post['order_cancel_reason'] != ''){ $reason = $post['order_cancel_reason']; }else{ return [ 'code' => -1, 'msg' => '取消原因描述参数出错' ]; } $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'], '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'], 'order_vehicle_id' => (int)$vehicle['order_vehicle_id'], 'city_info_revision' => (int)$vehicle['city_info_revision'], '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 = $vehicle['order_vehicle_id']; $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']; } Yii::error('huolala 报价失败:'.json_encode($resp)); return [ 'platform' => 'huolala', 'error' => isset($resp['msg']) ? $resp['msg'] : 'fail' ]; } /** * @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; } 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, ]; } }