| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512 |
- <?php
- namespace common\components\delivery\services\adapter;
- use Yii;
- use common\components\delivery\helpers\HttpClient;
- use common\components\delivery\platform\huolala\Huolala;
- /**
- * 货拉拉适配器
- *
- * 基于货拉拉开放平台API进行封装
- * 文档参考:货拉拉API调用协议与签名算法
- */
- class HuolalaAdapter extends Huolala implements Adapter
- {
- /**
- * 获取已开通城市列表
- *
- * 通过此接口可获取到货拉拉货运业务所有已开通的城市列表信息
- *
- * @return array 包含城市列表的响应数据
- */
- public function cityList()
- {
- // 构建请求参数(无业务数据)
- $payload = $this->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, '<?php namespace common\components\delivery\platform\huolala; return []; ?>');
- }
- // 文件内容要包含命名空间
- $content = '<?php namespace common\components\delivery\platform\huolala; return ' . var_export($newCitiesArr, true) . '; ?>';
- 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,
- ];
- }
- }
|