HuolalaAdapter.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. <?php
  2. namespace common\components\delivery\services\adapter;
  3. use common\components\delivery\services\DispatchService;
  4. use Yii;
  5. use common\components\delivery\helpers\HttpClient;
  6. use common\components\delivery\platform\huolala\Huolala;
  7. /**
  8. * 货拉拉适配器
  9. *
  10. * 基于货拉拉开放平台API进行封装
  11. * 文档参考:货拉拉API调用协议与签名算法
  12. */
  13. class HuolalaAdapter extends Huolala implements Adapter
  14. {
  15. private const CITY_VEHICLE_CACHE_TTL = 259200; // 3 days
  16. private const CITY_VEHICLE_CACHE_KEY_PATTERN = 'huolala-city-vehicle:%d_%s';
  17. /**
  18. * 获取已开通城市列表
  19. *
  20. * 通过此接口可获取到货拉拉货运业务所有已开通的城市列表信息
  21. *
  22. * @return array 包含城市列表的响应数据
  23. */
  24. public function cityList()
  25. {
  26. // 构建请求参数(无业务数据)
  27. $payload = $this->buildRequestPayload('u-city-list', []);
  28. $resp = HttpClient::post($this->baseUrl, $payload);
  29. $citiesArr = $resp['data']['city_list'];
  30. $newCitiesArr = [
  31. 'expire' => $resp['data']['expire'],
  32. ];
  33. foreach($citiesArr as $city) {
  34. $newCitiesArr[$city['name']] = [
  35. 'city_id' => $city['city_id'],
  36. 'city_name' => $city['name'],
  37. ];
  38. }
  39. // 把newCitiesArr保存到 platform/huolala/cities.php 文件中
  40. $citiesFile = Yii::getAlias('@common/components/delivery/platform/huolala/cities.php');
  41. // 如果文件不存在,则创建文件
  42. if (!file_exists($citiesFile)) {
  43. file_put_contents($citiesFile, '<?php namespace common\components\delivery\platform\huolala; return []; ?>');
  44. }
  45. // 文件内容要包含命名空间
  46. $content = '<?php namespace common\components\delivery\platform\huolala; return ' . var_export($newCitiesArr, true) . '; ?>';
  47. file_put_contents($citiesFile, $content);
  48. return $resp;
  49. }
  50. /**
  51. * 获取城市可选车型信息
  52. *
  53. * 通过此接口可获取城市可选车型信息,包括车型详情、计价规则、车型附加要求和额外需求等
  54. *
  55. * @param int $cityId 城市ID
  56. * @param bool $forceRefresh 是否强制刷新缓存
  57. * @return array|null 包含城市车型信息的响应数据
  58. */
  59. public function getCityVehicleList($cityId, $forceRefresh = false)
  60. {
  61. // 参数验证
  62. if (empty($cityId) || !is_numeric($cityId)) {
  63. return null;
  64. }
  65. $cityId = (int)$cityId;
  66. $env = $this->isSandbox ? 'dev' : 'production'; // 避免切环境后,使用了不同环境的缓存数据
  67. $cacheKey = sprintf(self::CITY_VEHICLE_CACHE_KEY_PATTERN, $cityId, $env);
  68. $redis = Yii::$app->has('redis') ? Yii::$app->redis : null;
  69. if ($redis !== null) {
  70. if ($forceRefresh) {
  71. $redis->executeCommand('DEL', [$cacheKey]);
  72. } else {
  73. $cached = $redis->executeCommand('GET', [$cacheKey]);
  74. if ($cached !== false && $cached !== null) {
  75. $decoded = json_decode($cached, true);
  76. if (is_array($decoded)) {
  77. return $decoded;
  78. }
  79. }
  80. }
  81. }
  82. // 构建请求参数,发送请求
  83. $payload = $this->buildRequestPayload('u-city-info', ['city_id' => $cityId]);
  84. $resp = HttpClient::post($this->baseUrl, $payload);
  85. // 处理响应
  86. if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) {
  87. $data = $resp['data'];
  88. // 提取车型列表
  89. $vehicleList = [];
  90. if (!empty($data['vehicle_list']) && is_array($data['vehicle_list'])) {
  91. foreach ($data['vehicle_list'] as $vehicle) {
  92. // 处理超里程价格分段
  93. $exceedSegmentPrice = [];
  94. if (!empty($vehicle['price_text_item']['exceed_segment_price'])
  95. && is_array($vehicle['price_text_item']['exceed_segment_price'])) {
  96. foreach ($vehicle['price_text_item']['exceed_segment_price'] as $segment) {
  97. $exceedSegmentPrice[] = [
  98. 'start_exdistancekm' => $segment['start_exdistancekm'] ?? 0,
  99. 'end_exdistancekm' => $segment['end_exdistancekm'] ?? 0,
  100. 'price_extra_fen' => $segment['price_extra_fen'] ?? 0,
  101. ];
  102. }
  103. }
  104. // 处理车型附加要求
  105. $vehicleStdItem = [];
  106. if (!empty($vehicle['vehicle_std_item']) && is_array($vehicle['vehicle_std_item'])) {
  107. foreach ($vehicle['vehicle_std_item'] as $std) {
  108. $vehicleStdItem[] = [
  109. 'name' => $std['name'] ?? '',
  110. 'desc' => $std['desc'] ?? '',
  111. 'price_fen' => $std['price_fen'] ?? 0,
  112. ];
  113. }
  114. }
  115. $vehicleList[] = [
  116. 'order_vehicle_id' => $vehicle['order_vehicle_id'] ?? 0,
  117. 'vehicle_name' => $vehicle['vehicle_name'] ?? '',
  118. 'img_url_high_light' => $vehicle['img_url_high_light'] ?? '',
  119. 'vehicle_volume' => $vehicle['vehicle_volume'] ?? '',
  120. 'vehicle_weight' => $vehicle['vehicle_weight'] ?? '',
  121. 'vehicle_size' => $vehicle['vehicle_size'] ?? '',
  122. 'standard_order_vehicle_id' => $vehicle['standard_order_vehicle_id'] ?? 0,
  123. 'text_desc' => $vehicle['text_desc'] ?? '',
  124. 'vehicle_attr' => $vehicle['vehicle_attr'] ?? 0,
  125. 'price_text_item' => [
  126. 'base_distancekm' => $vehicle['price_text_item']['base_distancekm'] ?? 0,
  127. 'base_price_fen' => $vehicle['price_text_item']['base_price_fen'] ?? 0,
  128. 'exceed_segment_price' => $exceedSegmentPrice,
  129. ],
  130. 'vehicle_std_item' => $vehicleStdItem,
  131. ];
  132. }
  133. }
  134. // 处理城市额外需求列表
  135. $specReqItem = [];
  136. if (!empty($data['spec_req_item']) && is_array($data['spec_req_item'])) {
  137. foreach ($data['spec_req_item'] as $specReq) {
  138. $specReqItem[] = [
  139. 'type' => (int)($specReq['type'] ?? 0),
  140. 'name' => $specReq['name'] ?? '',
  141. 'desc' => $specReq['desc'] ?? '',
  142. 'price_type' => (int)($specReq['price_type'] ?? 0),
  143. 'price_value_fen' => (int)($specReq['price_value_fen'] ?? 0),
  144. ];
  145. }
  146. }
  147. $result = [
  148. 'city_id' => $data['city_id'],
  149. 'name' => $data['name'],
  150. 'name_en' => $data['name_en'],
  151. 'city_info_revision' => $data['city_info_revision'],
  152. 'vehicle_list' => $vehicleList,
  153. 'spec_req_item' => $specReqItem,
  154. ];
  155. if ($redis !== null) {
  156. $redis->executeCommand('SETEX', [
  157. $cacheKey,
  158. self::CITY_VEHICLE_CACHE_TTL,
  159. json_encode($result, JSON_UNESCAPED_UNICODE)
  160. ]);
  161. }
  162. return $result;
  163. }
  164. return null;
  165. }
  166. /**
  167. * 转化订单数据
  168. * @param $order
  169. * @param $shop
  170. * @param $params
  171. * @return array
  172. */
  173. public function formatOrderData($order, $shop, $params)
  174. {
  175. //用车时间 -- 用车时间(unix时间戳:秒)。 取值范围:(当前时间+10分钟)<=用车时间<=(当前时间+5天)
  176. $orderTime = strtotime($params['pickupTime']);
  177. if($orderTime < (time()+600)) {
  178. $orderTime = time() + 600;
  179. }
  180. $cityId = $params['city_id'];
  181. $apiData = [
  182. 'city_id' => $cityId,
  183. 'city_info_revision' => $params['city_info_revision'],
  184. 'order_vehicle_id' => $params['order_vehicle_id'],
  185. 'addr_info' => [
  186. [
  187. 'name' => $shop['merchantName'], //地址名称
  188. 'addr' => $shop['fullAddress'],
  189. 'lat_lon' => ['lat' => (float)$shop['lat'], 'lon' => (float)$shop['long']],
  190. 'city_id' => $cityId,
  191. 'city_name' => $shop['city'],
  192. 'district_name' => $shop['dist'],
  193. 'house_number' => $shop['floor'],
  194. 'contacts_name' => $shop['mobile'],
  195. 'contacts_phone_no' => $shop['mobile'],
  196. ],
  197. [
  198. 'name' => empty($order['showAddress']) ? $order['customName'] : $order['showAddress'],
  199. 'addr' => $order['fullAddress'],
  200. 'lat_lon' => ['lat' => (float)$order['lat'], 'lon' => (float)$order['long']],
  201. 'city_id' => $cityId,
  202. 'city_name' => $order['city'],
  203. 'district_name' => $order['dist'],
  204. 'house_number' => $order['floor'],
  205. 'contacts_name' => $order['customName'],
  206. 'contacts_phone_no' => $order['customMobile'],
  207. ]
  208. ],
  209. 'vehicle_std' => $params['vehicle_std'],
  210. 'spec_req' => $params['spec_req'],
  211. 'order_time' => $orderTime,
  212. 'contact_name' => $shop['merchantName'],
  213. 'contact_phone_no' => $shop['mobile'],
  214. 'out_user_id' => $params['mainId'], //下单人在服务商平台侧的唯一ID,如果没有,可以传0
  215. 'ip' => $params['ip'],
  216. 'remark' => $params['remark'], //string(300)
  217. 'total_price_fen' => $params['total_price_fen'],
  218. 'pay_type' => 4,
  219. 'price_calculate_id' => $params['price_calculate_id'], //计价id,从计价接口返回中取值
  220. 'price_item_encryption' => $params['price_item_encryption'], //估价返回的费用项,从计价接口返回中取值
  221. 'vehicle_attr' => $params['vehicle_attr'], //大小车属性,从计价接口返回中取值
  222. 'commodity_info' => $params['commodity_info'] //商品信息(直接透传u-price-multiCalculate返回的commodity_item字段)
  223. ];
  224. // 添加订单消息回调地址
  225. $apiData['notify_url'] = $this->notifyUrl;
  226. return $apiData;
  227. }
  228. /**
  229. * 创建订单(需用户授权)
  230. *
  231. * 根据货拉拉官方文档实现下单功能
  232. *
  233. * @param array $data 订单数据
  234. * @return array API响应结果
  235. */
  236. public function createOrder($order)
  237. {
  238. // 构建地址信息
  239. $addrInfo = [];
  240. if (!empty($order['addr_info']) && is_array($order['addr_info'])) {
  241. foreach ($order['addr_info'] as $addr) {
  242. $addrInfo[] = [
  243. 'name' => $addr['name'] ?? '',
  244. 'addr' => $addr['addr'] ?? '',
  245. 'city_id' => (int)($addr['city_id'] ?? 0),
  246. 'city_name' => $addr['city_name'] ?? '',
  247. 'district_name' => $addr['district_name'] ?? '',
  248. 'house_number' => $addr['house_number'] ?? '',
  249. 'contacts_name' => $addr['contacts_name'] ?? '',
  250. 'contacts_phone_no' => $addr['contacts_phone_no'] ?? '',
  251. 'lat_lon' => [
  252. 'lat' => (double)($addr['lat_lon']['lat'] ?? 0),
  253. 'lon' => (double)($addr['lat_lon']['lon'] ?? 0),
  254. ]
  255. ];
  256. }
  257. }
  258. // 构建业务数据
  259. $apiData = [
  260. 'city_id' => (int)($order['city_id'] ?? 0),
  261. 'city_info_revision' => (int)($order['city_info_revision'] ?? 0),
  262. 'order_vehicle_id' => (int)($order['order_vehicle_id'] ?? 0),
  263. 'addr_info' => $addrInfo,
  264. 'contact_name' => $order['contact_name'] ?? '',
  265. 'contact_phone_no' => $order['contact_phone_no'] ?? '',
  266. 'total_price_fen' => (int)($order['total_price_fen'] ?? 0),
  267. 'pay_type' => (int)($order['pay_type'] ?? 0),
  268. 'price_calculate_id' => $order['price_calculate_id'] ?? '',
  269. 'price_item_encryption' => $order['price_item_encryption'] ?? '',
  270. 'vehicle_attr' => (int)($order['vehicle_attr'] ?? 0),
  271. 'order_time' => (int)($order['order_time'] ?? time()),
  272. ];
  273. // 可选参数
  274. if (!empty($order['vehicle_std'])) {
  275. $apiData['vehicle_std'] = is_array($order['vehicle_std']) ? $order['vehicle_std'] : [$order['vehicle_std']];
  276. }
  277. if (!empty($order['spec_req'])) {
  278. $apiData['spec_req'] = is_array($order['spec_req']) ? $order['spec_req'] : [$order['spec_req']];
  279. }
  280. if (!empty($order['remark'])) {
  281. $apiData['remark'] = $order['remark'];
  282. }
  283. if (!empty($order['out_user_id'])) {
  284. $apiData['out_user_id'] = $order['out_user_id'];
  285. }
  286. if (!empty($order['ip'])) {
  287. $apiData['ip'] = $order['ip'];
  288. }
  289. if (!empty($order['notify_url'])) {
  290. $apiData['notify_url'] = $order['notify_url'];
  291. }
  292. if (isset($order['coupon_id'])) {
  293. $apiData['coupon_id'] = $order['coupon_id'];
  294. }
  295. if (isset($order['invoice_type'])) {
  296. $apiData['invoice_type'] = (int)$order['invoice_type'];
  297. }
  298. if (isset($order['order_service_type'])) {
  299. $apiData['order_service_type'] = (int)$order['order_service_type'];
  300. }
  301. if (!empty($order['ref'])) {
  302. $apiData['ref'] = $order['ref'];
  303. }
  304. if (!empty($order['themis_data'])) {
  305. $apiData['themis_data'] = $order['themis_data'];
  306. }
  307. if (!empty($order['commodity_info'])) {
  308. $apiData['commodity_info'] = $order['commodity_info'];
  309. }
  310. // 构建请求参数
  311. $payload = $this->buildRequestPayload(
  312. 'u-order-request',
  313. $apiData,
  314. $this->accessToken
  315. );
  316. // 发送请求
  317. $resp = HttpClient::post($this->baseUrl, $payload, ['Content-type'=>'application/json']);
  318. // 处理响应
  319. if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) {
  320. $respData = $resp['data'];
  321. return [
  322. 'code' => 0,
  323. 'platform' => 'huolala',
  324. 'data' => [
  325. 'order_id' => $respData['order_uuid'], // 订单UUID
  326. 'fee' => $respData['price_fen'] ?? 0, // 订单金额(分)
  327. 'distance' => 0,
  328. // --------
  329. 'order_display_id' => $respData['order_display_id'] ?? '', // 订单号
  330. 'pay_notify_url' => $respData['pay_notify_url'] ?? null, // 支付回调地址
  331. 'pay_mch_id' => $respData['pay_mch_id'] ?? null, // 银联商户号
  332. 'pay_serv_mch_id' => $respData['pay_serv_mch_id'] ?? null, // 银联服务商号
  333. 'pay_no' => $respData['pay_no'] ?? null, // 银联支付单号
  334. 'pay_time_expire' => $respData['pay_time_expire'] ?? null, // 支付超时时间
  335. ]
  336. ];
  337. }
  338. if(in_array($resp['ret'], [30010, 30011, 31006])){ // 错误状态码
  339. $this->cityList(); // 刷新城市列表信息
  340. }
  341. return [
  342. 'code' => $resp['ret'] ?? -1,
  343. 'msg' => $resp['msg'] ?? 'Unknown error',
  344. 'data' => null
  345. ];
  346. }
  347. public function cancelOrder($orderUuid, $reason)
  348. {
  349. $res = $this->selectOrder($orderUuid);
  350. if ($res['ret'] != 0) {
  351. new \Exception('查询订单详情失败, code=' . $res['ret'] . ', msg=' . $res['msg']);
  352. }
  353. $status = $res['data']['order_status'];
  354. $apiData = [
  355. 'order_display_id' => $orderUuid,
  356. 'reason' => $reason,
  357. 'order_status' => $status
  358. ];
  359. // 构建请求参数
  360. $payload = $this->buildRequestPayload(
  361. 'u-order-cancel',
  362. $apiData,
  363. $this->accessToken
  364. );
  365. // 发送请求
  366. $resp = HttpClient::post($this->baseUrl, $payload);
  367. if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) {
  368. $respData = $resp['data'];
  369. return [
  370. 'code' => 0,
  371. 'platform' => 'huolala',
  372. 'data' => [
  373. 'deductionFee' => 0, //取消收费金额(单位:分)
  374. 'order_status' => $respData['order_status']
  375. ]
  376. ];
  377. }
  378. return [
  379. 'code' => $resp['ret'] ?? -1,
  380. 'platform' => 'huolala',
  381. 'msg' => $resp['msg'] ?? 'Unknown error',
  382. 'data' => null
  383. ];
  384. }
  385. /**
  386. * 查询订单详情
  387. * @param string $orderUuid 货拉拉订单号 -- 文档上写的是 order_display_id,实则要传 order_uuid
  388. * @return array
  389. */
  390. public function selectOrder($orderUuid)
  391. {
  392. $apiData = [
  393. 'order_display_id' => $orderUuid
  394. ];
  395. // 构建请求参数
  396. $payload = $this->buildRequestPayload(
  397. 'u-order-detail',
  398. $apiData,
  399. $this->accessToken
  400. );
  401. // 发送请求
  402. $resp = HttpClient::post($this->baseUrl, $payload);
  403. return $resp;
  404. }
  405. /**
  406. * 构建多车型报价请求信息列表(供并发请求使用)
  407. *
  408. * @param array $data 包含 vehicle_type_list 的数据
  409. * @param string $orderTime 配送时间
  410. * @return array 请求信息列表,以车型ID为键
  411. */
  412. public function buildPriceRequest($data, $orderTime)
  413. {
  414. //用车时间 -- 用车时间(unix时间戳:秒)。 取值范围:(当前时间+10分钟)<=用车时间<=(当前时间+5天)
  415. $orderTime = strtotime($orderTime);
  416. if($orderTime < (time()+600)) {
  417. $orderTime = time() + 600;
  418. }
  419. $requests = [];
  420. foreach($data['vehicle_type_list'] as $vehicle) {
  421. // 构建地址信息
  422. $addrInfo = [];
  423. if (!empty($vehicle['addr_info']) && is_array($vehicle['addr_info'])) {
  424. foreach ($vehicle['addr_info'] as $addr) {
  425. $addrInfo[] = [
  426. 'name' => $addr['name'] ?? '',
  427. 'addr' => $addr['addr'] ?? '',
  428. 'city_id' => (int)($addr['city_id'] ?? 0),
  429. 'city_name' => $addr['city_name'] ?? '',
  430. 'district_name' => $addr['district_name'] ?? '',
  431. 'house_number' => $addr['house_number'] ?? '',
  432. 'contacts_name' => $addr['contacts_name'] ?? '',
  433. 'contacts_phone_no' => $addr['contacts_phone_no'] ?? '',
  434. 'lat_lon' => [
  435. 'lat' => (double)($addr['lat_lon']['lat'] ?? 0),
  436. 'lon' => (double)($addr['lat_lon']['lon'] ?? 0),
  437. ]
  438. ];
  439. }
  440. }
  441. $apiData = [
  442. 'city_id' => (int)($data['city_id'] ?? 0),
  443. 'order_vehicle_id' => (int)($vehicle['order_vehicle_id'] ?? 0),
  444. 'city_info_revision' => (int)($vehicle['city_info_revision'] ?? 0),
  445. 'order_time' => $orderTime,
  446. 'addr_info' => $addrInfo,
  447. ];
  448. if (!empty($vehicle['vehicle_std'])) {
  449. $apiData['vehicle_std'] = is_array($vehicle['vehicle_std']) ? $vehicle['vehicle_std'] : [$vehicle['vehicle_std']];
  450. }
  451. if (!empty($vehicle['spec_req'])) {
  452. $apiData['spec_req'] = is_array($vehicle['spec_req']) ? $vehicle['spec_req'] : [$vehicle['spec_req']];
  453. }
  454. if (isset($vehicle['coupon_id']) && !empty($vehicle['coupon_id'])) {
  455. $apiData['coupon_id'] = $vehicle['coupon_id'];
  456. }
  457. if (isset($vehicle['invoice_type'])) {
  458. $apiData['invoice_type'] = (int)$vehicle['invoice_type'];
  459. }
  460. if (isset($vehicle['order_service_type'])) {
  461. $apiData['order_service_type'] = (int)$vehicle['order_service_type'];
  462. }
  463. $payload = $this->buildRequestPayload(
  464. 'u-price-multiCalculate',
  465. $apiData,
  466. $this->accessToken
  467. );
  468. // 使用车型ID作为请求的唯一标识
  469. $vehicleId = (int)($vehicle['order_vehicle_id'] ?? 0);
  470. $requests["huolala_vehicle_{$vehicleId}"] = [
  471. 'url' => $this->baseUrl,
  472. 'data' => $payload,
  473. 'headers' => [],
  474. 'timeout' => 5.0,
  475. ];
  476. }
  477. return $requests;
  478. }
  479. /**
  480. * 处理货拉拉报价响应
  481. *
  482. * @param array $resp API 响应
  483. * @return array|null 解析后的报价结果
  484. */
  485. public function processPriceResponse($resp)
  486. {
  487. if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) {
  488. return $resp['data'];
  489. }
  490. return null;
  491. }
  492. /**
  493. * @deprecated
  494. * 获取运费报价(多品类)- 需用户授权
  495. *
  496. * 根据货拉拉官方文档 u-price-multiCalculate 实现多品类估价功能
  497. *
  498. * @param array $data 估价参数
  499. * @param string $orderTime 配送时间
  500. * @return array|null 包含多个计价方案的报价结果
  501. * @deprecated 使用 buildPriceRequest 和 processPriceResponse 替代
  502. */
  503. public function getPrice($data, $orderTime)
  504. {
  505. $requests = $this->buildPriceRequest($data, $orderTime);
  506. $priceList = [];
  507. foreach ($requests as $requestInfo) {
  508. $resp = HttpClient::post($requestInfo['url'], $requestInfo['data']);
  509. $processed = $this->processPriceResponse($resp);
  510. if ($processed) {
  511. $priceList[] = $processed;
  512. }
  513. }
  514. return !empty($priceList) ? ['price_list' => $priceList] : null;
  515. }
  516. /**
  517. * 获取可选取消原因
  518. *
  519. * 通过此接口可获取可选择的取消原因,包括主原因和子原因
  520. *
  521. * @param array $data 业务参数(当前接口无必需参数,可传空数组)
  522. * @return array|null 包含取消原因列表的响应数据
  523. */
  524. public function getCancelReasonList($data)
  525. {
  526. // 构建请求参数(无业务数据)
  527. $payload = $this->buildRequestPayload('u-order-cancelReason', []);
  528. // 发送请求
  529. $resp = HttpClient::post($this->baseUrl, $payload);
  530. // 处理响应
  531. if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) {
  532. $respData = $resp['data'];
  533. // 提取取消原因列表
  534. $reasonList = [];
  535. if (!empty($respData['reason']) && is_array($respData['reason'])) {
  536. foreach ($respData['reason'] as $reason) {
  537. if (!empty($reason['sub_reason']) && is_array($reason['sub_reason'])) {
  538. foreach ($reason['sub_reason'] as $subReason) {
  539. $reasonList[] = [
  540. 'order_cancel_code' => (int)($subReason['id'] ?? 0),
  541. 'order_cancel_desc' => $subReason['name'] ?? '',
  542. ];
  543. }
  544. }
  545. }
  546. }
  547. return [
  548. 'code' => 0,
  549. 'data' => [
  550. 'reasonList' => $reasonList,
  551. ]
  552. ];
  553. }
  554. return [
  555. 'code' => $resp['ret'] ?? -1,
  556. 'msg' => $resp['msg'] ?? 'Unknown error',
  557. 'data' => null
  558. ];
  559. }
  560. public function addTip($data)
  561. {
  562. $orderDisplayId = $data['orderId'] ?? null;
  563. $totalTipsFen = $data['tips'] ?? null;
  564. if (empty($orderDisplayId)) {
  565. return [
  566. 'code' => -1,
  567. 'platform' => 'huolala',
  568. 'msg' => 'order_display_id不能为空',
  569. 'data' => null,
  570. ];
  571. }
  572. if (!isset($totalTipsFen) || (int)$totalTipsFen <= 0) {
  573. return [
  574. 'code' => -1,
  575. 'platform' => 'huolala',
  576. 'msg' => 'total_tips_fen必须为正整数(单位:分)',
  577. 'data' => null,
  578. ];
  579. }
  580. $apiData = [
  581. 'order_display_id' => $orderDisplayId,
  582. 'total_tips_fen' => (int)$totalTipsFen,
  583. ];
  584. $payload = $this->buildRequestPayload(
  585. 'u-order-addTips',
  586. $apiData,
  587. $this->accessToken
  588. );
  589. $resp = HttpClient::post($this->baseUrl, $payload, ['Content-type' => 'application/json']);
  590. if (isset($resp['ret']) && $resp['ret'] == 0) {
  591. return [
  592. 'code' => 0,
  593. 'platform' => 'huolala',
  594. 'data' => $resp['data'] ?? [],
  595. ];
  596. }
  597. return [
  598. 'code' => $resp['ret'] ?? -1,
  599. 'platform' => 'huolala',
  600. 'msg' => $resp['msg'] ?? 'Unknown error',
  601. 'data' => null,
  602. ];
  603. }
  604. }