DadaAdapter.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. <?php
  2. namespace common\components\delivery\services\adapter;
  3. use bizGhs\express\classes\GhsDeliveryOrderClass;
  4. use bizGhs\order\classes\OrderItemClass;
  5. use bizHd\express\classes\HdDeliveryOrderClass;
  6. use bizHd\order\classes\OrderGoodsClass;
  7. use common\components\delivery\helpers\HttpClient;
  8. use common\components\delivery\platform\dada\Dada;
  9. use common\components\util;
  10. use Yii;
  11. /**
  12. * 达达(DaDa)配送平台适配器
  13. *
  14. * 基于达达开放平台 API 接口规范进行封装
  15. * API 文档参考:达达开放平台 - 接口调用协议与签名算法
  16. */
  17. class DadaAdapter extends Dada implements Adapter
  18. {
  19. /**
  20. * 获取已开通城市列表
  21. */
  22. public function cityList()
  23. {
  24. // 业务参数为空时使用空字符串
  25. $body = '';
  26. // 构建完整的 API 请求参数
  27. $payload = $this->buildRequestPayload('cityList', $body);
  28. // 发送 POST 请求
  29. $resp = HttpClient::post("{$this->baseUrl}/cityList", $payload);
  30. return $resp;
  31. }
  32. /**
  33. * 转化订单数据
  34. * @param $order
  35. * @param string $orderType 订单类型:xhOrder 或 xhGhsOrder
  36. * @param $shop
  37. * @param $params
  38. * @return array
  39. * @throws \Exception
  40. */
  41. public function formatOrderData($order, $orderType, $shop, $params)
  42. {
  43. $hasOldOrder = false;
  44. if($orderType == 'xhOrder'){
  45. $deliveryOrders = HdDeliveryOrderClass::getByCondition([
  46. 'deliveryId' => 'dada',
  47. 'hdOrderId' => $order['id']
  48. ], 'id DESC', 'id,mainId,orderId');
  49. if(is_array($deliveryOrders) && count($deliveryOrders) >= 1) {
  50. $hasOldOrder = true;
  51. }
  52. } elseif($orderType == 'xhGhsOrder') {
  53. // 查找批发配送订单
  54. $deliveryOrders = GhsDeliveryOrderClass::getAllByCondition([
  55. 'deliveryId' => 'dada',
  56. 'ghsOrderId' => $order['id']
  57. ], 'id DESC', 'id,mainId,orderId');
  58. if(is_array($deliveryOrders) && count($deliveryOrders) >= 1) {
  59. $hasOldOrder = true;
  60. }
  61. }
  62. $orderData = [
  63. 'deliveryNo' => $params['deliveryNo'],
  64. 'fee' => $params['price'],
  65. 'distance' => $params['distance'],
  66. 'hasOldOrder' => $hasOldOrder,
  67. 'orderSn' => $order['orderSn'],
  68. 'params' => $hasOldOrder ? $this->formatOrderDataByResourceData($order, $orderType, $shop, $params) : [],
  69. ];
  70. return $orderData;
  71. }
  72. /**
  73. * 转化订单数据
  74. * @param $order
  75. * @param string $orderType 订单类型:xhOrder 或 xhGhsOrder
  76. * @param $shop
  77. * @param $params
  78. * @return array
  79. * @throws \Exception
  80. */
  81. public function formatOrderDataByResourceData($order, $orderType, $shop, $params)
  82. {
  83. $shopNo = $this->shopNo;
  84. if(empty($shopNo)){
  85. util::fail("DadaAdapter formatOrderData shopNo is empty");
  86. }
  87. $productList = [];
  88. if($orderType == 'xhGhsOrder'){
  89. $itemInfos = OrderItemClass::getAllByCondition(['orderSn' => $order['orderSn']], null, 'id, name, unitPrice, num, xhUnitName');
  90. foreach ($itemInfos as $item){
  91. $productList[] = [
  92. 'sku_name' => $item['name'],
  93. 'src_product_no' => (string)$item['id'],
  94. 'count' => (float)$item['num'],
  95. 'unit' => $item['xhUnitName'],
  96. ];
  97. }
  98. } else if($orderType == 'xhOrder'){
  99. $itemInfos = OrderGoodsClass::getAllByCondition(['orderSn' => $order['orderSn']], null, 'id, name, unitPrice, num');
  100. foreach ($itemInfos as $item){
  101. $productList[] = [
  102. 'sku_name' => $item['name'],
  103. 'src_product_no' => (string)$item['id'],
  104. 'count' => (float)$item['num'],
  105. 'unit' => '件',
  106. ];
  107. }
  108. } else {
  109. Yii::error('订单编号出错: '.$order['orderSn']);
  110. util::fail('订单编号出错');
  111. }
  112. // 默认回调地址
  113. $callbackUrl = Yii::$app->params['ghsHost'] . '/delivery/dada-callback';
  114. return [
  115. 'order_id' => $order['orderSn'],
  116. 'origin_id' => $order['orderSn'], // 达达API通常使用 origin_id
  117. 'shop_no' => $shopNo,
  118. 'receiver_name' => $order['customName'],
  119. 'receiver_address' => $order['fullAddress'],
  120. 'receiver_phone' => Functions::getMobile($order),
  121. 'receiver_lat' => (float)$order['lat'],
  122. 'receiver_lng' => (float)$order['long'],
  123. 'cargo_price' => (float)$order['actPrice'], // 单位:元
  124. 'tips' => 0,
  125. 'info' => $order['remark'] ?? '',
  126. 'callback' => $callbackUrl,
  127. 'cargo_weight' => isset($params['weight']) ? (float)$params['weight'] : 1.0,
  128. 'product_list' => $productList,
  129. 'is_prepay' => 0, // 是否需要垫付
  130. 'pickupTime' => $params['pickupTime'],
  131. ];
  132. }
  133. /**
  134. * 查询运费后下单 -- addAfterQuery
  135. * @param array $data 包含 deliveryNo 等参数
  136. * @return array
  137. */
  138. public function createOrder($data)
  139. {
  140. if($data['hasOldOrder']){
  141. $deliveryNo = $data['deliveryNo'];
  142. $orderId = $data['orderSn'];
  143. $params = $data['params'];
  144. $ret = $this->reCreateOrder($deliveryNo, $orderId, $params);
  145. }else{
  146. $ret = $this->firstCreateOrder($data);
  147. }
  148. return $ret;
  149. }
  150. /**
  151. * 查询运费后下单 -- addAfterQuery
  152. * @param array $data 包含 deliveryNo 等参数
  153. * @return array
  154. */
  155. public function firstCreateOrder($data)
  156. {
  157. // 验证必需字段
  158. if (empty($data['deliveryNo'])) {
  159. return [
  160. 'code' => -1,
  161. 'msg' => '缺少必需参数: deliveryNo'
  162. ];
  163. }
  164. $apiData = [
  165. 'deliveryNo' => $data['deliveryNo']
  166. ];
  167. if (isset($data['enableReset'])) {
  168. $apiData['enableReset'] = (bool)$data['enableReset'];
  169. }
  170. if (!empty($data['info'])) {
  171. $apiData['info'] = $data['info'];
  172. }
  173. $body = json_encode($apiData, JSON_UNESCAPED_UNICODE);
  174. $payload = $this->buildRequestPayload('api/order/addAfterQuery', $body);
  175. $resp = HttpClient::post("{$this->baseUrl}/api/order/addAfterQuery", $payload);
  176. if (isset($resp['code']) && $resp['code'] == 0) {
  177. return [
  178. 'code' => 0,
  179. 'platform' => 'dada',
  180. 'data' => [
  181. 'orderId' => $data['deliveryNo'],
  182. 'deliveryNo' => $data['deliveryNo'],
  183. 'fee' => $data['fee'],
  184. 'distance' => $data['distance'],
  185. 'deliverFee' => $data['deliverFee'] ?? 0,
  186. 'couponName' => $data['couponName'] ?? '',
  187. 'couponFee' => $data['couponFee'] ?? 0,
  188. ],
  189. ];
  190. }
  191. return [
  192. 'code' => $resp['code'] ?? -1,
  193. 'msg' => $resp['msg'] ?? 'Unknown error',
  194. 'data' => null
  195. ];
  196. }
  197. /**
  198. * 创建订单(指派订单给配送员)
  199. *
  200. * 根据达达官方文档实现下单功能
  201. *
  202. * @param array $data 订单数据
  203. * @return array API 响应结果
  204. */
  205. public function createOrderByResourceData($data)
  206. {
  207. // 验证必需字段
  208. if (empty($data['order_id']) || empty($data['shop_no'])) {
  209. return [
  210. 'code' => -1,
  211. 'msg' => '缺少必需参数: order_id 和 shop_no'
  212. ];
  213. }
  214. // 构建业务参数(按达达 API 规范)
  215. // 达达新增订单接口(orderAddRequest)主要参数:shop_no, origin_id, ...
  216. $apiData = [
  217. 'shop_no' => $data['shop_no'],
  218. 'origin_id' => $data['origin_id'] ?? $data['order_id'], // 优先使用 origin_id
  219. 'city_code' => '', // 可选
  220. 'cargo_price' => isset($data['cargo_price']) ? (float)$data['cargo_price'] : 0,
  221. 'is_prepay' => isset($data['is_prepay']) ? (int)$data['is_prepay'] : 0,
  222. 'receiver_name' => $data['receiver_name'] ?? '',
  223. 'receiver_address' => $data['receiver_address'] ?? '',
  224. 'receiver_phone' => $data['receiver_phone'] ?? '',
  225. 'receiver_lat' => isset($data['receiver_lat']) ? (double)$data['receiver_lat'] : 0,
  226. 'receiver_lng' => isset($data['receiver_lng']) ? (double)$data['receiver_lng'] : 0,
  227. 'callback' => $data['callback'] ?? '',
  228. ];
  229. // 如果指定了配送员ID,添加到请求
  230. if (isset($data['transporter_id'])) {
  231. $apiData['transporter_id'] = (int)$data['transporter_id'];
  232. }
  233. if (isset($data['tips'])) {
  234. $apiData['tips'] = (float)$data['tips'];
  235. }
  236. if (isset($data['distance'])) {
  237. $apiData['distance'] = (double)$data['distance'];
  238. }
  239. if (isset($data['prepare_time'])) { // 废弃字段,但保留兼容
  240. //$apiData['prepare_time'] = (int)$data['prepare_time'];
  241. }
  242. if (!empty($data['info'])) { // 订单备注
  243. $apiData['info'] = $data['info'];
  244. } elseif (!empty($data['remark'])) {
  245. $apiData['info'] = $data['remark'];
  246. }
  247. if (isset($data['cargo_weight'])) {
  248. $apiData['cargo_weight'] = (double)$data['cargo_weight'];
  249. }
  250. if (isset($data['product_list'])) {
  251. $apiData['product_list'] = $data['product_list'];
  252. }
  253. // 将业务参数转换为 JSON 字符串
  254. $body = json_encode($apiData, JSON_UNESCAPED_UNICODE);
  255. // 构建完整的 API 请求参数
  256. $payload = $this->buildRequestPayload('orderAddRequest', $body);
  257. // 发送 POST 请求
  258. $resp = HttpClient::post("{$this->baseUrl}/orderAddRequest", $payload);
  259. if(isset($resp['code']) && $resp['code'] == 0){
  260. return [
  261. 'code' => 0,
  262. 'platform' => 'dada',
  263. 'data' => [
  264. 'orderId' => $resp['order_id'],
  265. ],
  266. ];
  267. }
  268. return [
  269. 'code' => $resp['code'] ?? -1,
  270. 'msg' => $resp['msg'] ?? 'Unknown error',
  271. 'data' => null
  272. ];
  273. }
  274. /**
  275. * 重新下单 -- /api/order/reAddOrder
  276. * 同一个商户的单号(根据三方单号识别)如果已经在达达侧创建了订单,在该订单被取消(已取消=5)、拒收返还(妥投异常物品返还完成=10)后,可以调用该接口重发订单,达达将根据传入的配送信息重新计算运费并创建达达物流订单。
  277. * @param string $deliveryNo
  278. * @param string $orderId 第三方订单ID
  279. * @param array $params
  280. * @return array
  281. */
  282. public function reCreateOrder($deliveryNo, $orderId, $params)
  283. {
  284. // 验证必需字段
  285. if (empty($orderId) || empty($params['shop_no'])) {
  286. return [
  287. 'code' => -1,
  288. 'msg' => '缺少必需参数: order_id 和 shop_no'
  289. ];
  290. }
  291. // 构建业务参数
  292. $apiData = [
  293. 'shop_no' => $params['shop_no'],
  294. 'origin_id' => $orderId,
  295. 'cargo_price' => isset($params['cargo_price']) ? (float)$params['cargo_price'] : 0,
  296. 'is_prepay' => isset($params['is_prepay']) ? (int)$params['is_prepay'] : 0,
  297. 'receiver_name' => $params['receiver_name'] ?? '',
  298. 'receiver_address' => $params['receiver_address'] ?? '',
  299. 'receiver_lat' => isset($params['receiver_lat']) ? (double)$params['receiver_lat'] : 0,
  300. 'receiver_lng' => isset($params['receiver_lng']) ? (double)$params['receiver_lng'] : 0,
  301. 'callback' => $params['callback'] ?? '',
  302. 'cargo_weight' => isset($params['cargo_weight']) ? (double)$params['cargo_weight'] : 0,
  303. ];
  304. // 可选参数列表
  305. $optionalFields = [
  306. 'receiver_phone', 'receiver_tel', 'tips', 'info', 'cargo_type', 'cargo_num',
  307. 'invoice_title', 'origin_mark', 'origin_mark_no', 'is_use_insurance',
  308. 'is_finish_code_needed', 'delay_publish_time', 'is_expect_finish_order',
  309. 'expect_finish_time_limit', 'is_direct_delivery', 'product_list',
  310. 'pick_up_pos', 'fetch_code', 'business_type',
  311. 'supplier_name', 'supplier_address', 'supplier_phone', 'supplier_lat', 'supplier_lng'
  312. ];
  313. foreach ($optionalFields as $field) {
  314. if (isset($params[$field])) {
  315. $apiData[$field] = $params[$field];
  316. }
  317. }
  318. // 处理预约单
  319. $ts = strtotime($params['pickupTime']);
  320. if ($ts > time() + 30) {
  321. // delay_publish_time
  322. // 预约发单时间(unix时间戳(10位),精确到分),可以支持未来3天内的订单发预约单,如预约发单时间早于当前时间会作为即时单下发。
  323. $apiData['delay_publish_time'] = $ts;
  324. $apiData['is_expect_finish_order'] = 1; // 是否根据期望送达时间预约发单(0-否,即时发单;1-是,预约发单),如传1则期望送达时间必传。
  325. $apiData['expect_finish_time_limit'] = $ts; // 期望送达时间(单位秒,不早于当前时间)
  326. }
  327. // 特殊处理:如果 info 没传但有 remark,尝试使用 remark
  328. if (!isset($apiData['info']) && !empty($params['remark'])) {
  329. $apiData['info'] = $params['remark'];
  330. }
  331. $body = json_encode($apiData, JSON_UNESCAPED_UNICODE);
  332. $payload = $this->buildRequestPayload('api/order/reAddOrder', $body);
  333. $resp = HttpClient::post("{$this->baseUrl}/api/order/reAddOrder", $payload);
  334. if(isset($resp['code']) && $resp['code'] == 0){
  335. $data = $resp['result'];
  336. return [
  337. 'code' => 0,
  338. 'platform' => 'dada',
  339. 'data' => [
  340. 'orderId' => $deliveryNo,
  341. 'deliveryNo' => $deliveryNo,
  342. 'fee' => $data['fee'],
  343. 'distance' => $data['distance'],
  344. 'deliverFee' => $data['deliverFee'],
  345. 'couponName' => $data['couponName'] ?? '',
  346. 'couponFee' => $data['couponFee'] ?? 0,
  347. ],
  348. ];
  349. }
  350. return [
  351. 'code' => $resp['code'] ?? -1,
  352. 'msg' => $resp['msg'] ?? 'Unknown error',
  353. 'data' => null
  354. ];
  355. }
  356. /**
  357. * 取消订单
  358. * 在订单待接单或待取货情况下,调用此接口可取消订单
  359. * @param $orderId
  360. * @param $params
  361. * @return array
  362. */
  363. public function cancelOrder($orderId, $params)
  364. {
  365. // 验证必需参数
  366. if (empty($orderId)) {
  367. return [
  368. 'code' => -1,
  369. 'msg' => '缺少订单号'
  370. ];
  371. }
  372. if (empty($params['order_cancel_code'])) {
  373. return [
  374. 'code' => -1,
  375. 'msg' => '缺少取消原因ID'
  376. ];
  377. }
  378. if ($params['order_cancel_code'] == 10000 && empty($params['order_cancel_reason'])) {
  379. return [
  380. 'code' => -1,
  381. 'msg' => '当取消原因ID为其他时,取消原因必填'
  382. ];
  383. }
  384. // 构建业务参数
  385. $apiData = [
  386. 'order_id' => $orderId,
  387. 'cancel_reason_id' => (int)$params['order_cancel_code'],
  388. ];
  389. if (!empty($params['order_cancel_reason'])) {
  390. $apiData['cancel_reason'] = $params['order_cancel_reason'];
  391. }
  392. $body = json_encode($apiData, JSON_UNESCAPED_UNICODE);
  393. $payload = $this->buildRequestPayload('api/order/formalCancel', $body);
  394. $resp = HttpClient::post("{$this->baseUrl}/api/order/formalCancel", $payload);
  395. // 处理响应
  396. if (isset($resp['status']) && $resp['status'] === 'success' && $resp['code'] == 0) {
  397. $result = $resp['result'] ?? [];
  398. return [
  399. 'code' => 0,
  400. 'platform' => 'dada',
  401. 'msg' => '取消成功',
  402. 'data' => [
  403. 'deductionFee' => $result['deduct_fee'] ?? 0.0, // 扣除的违约金
  404. ],
  405. ];
  406. }
  407. return [
  408. 'code' => $resp['code'] ?? -1,
  409. 'platform' => 'dada',
  410. 'msg' => $resp['msg'] ?? '取消失败',
  411. 'data' => null
  412. ];
  413. }
  414. /**
  415. * 获取订单运费报价
  416. *
  417. * 根据达达官方文档实现订单运费估价功能
  418. *
  419. * @param array $data 运费估价参数,结构参考:
  420. * [
  421. * 'shop_no' => '门店编号' (必需),
  422. * 'origin_id' => '原始订单ID' (必需), // 商户侧订单编号
  423. * 'receiver_address' => '收货地址' (必需),
  424. * 'receiver_phone' => '收货人电话' (必需),
  425. * 'receiver_lng' => 经度 (必需),
  426. * 'receiver_lat' => 纬度 (必需),
  427. * 'cargo_price' => 货物价格(分) (可选), // 商品价格,单位为分
  428. * 'distance' => 距离(米) (可选),
  429. * 'prepare_time' => 商品准备时间 (可选), // 单位:秒
  430. * ]
  431. * @return array|null 运费报价结果
  432. */
  433. public function getPrice($data)
  434. {
  435. // 验证必需字段
  436. if (empty($data['shop_no']) || empty($data['origin_id']) ||
  437. empty($data['receiver_address']) || empty($data['receiver_phone']) ||
  438. !isset($data['receiver_lng']) || !isset($data['receiver_lat'])) {
  439. return [
  440. 'code' => -1,
  441. 'msg' => '缺少必需参数'
  442. ];
  443. }
  444. // 构建业务参数(按达达 API 规范)
  445. $apiData = [
  446. 'shop_no' => $data['shop_no'],
  447. 'origin_id' => $data['origin_id'],
  448. 'receiver_address' => $data['receiver_address'],
  449. 'receiver_phone' => $data['receiver_phone'],
  450. 'receiver_lng' => (double)$data['receiver_lng'],
  451. 'receiver_lat' => (double)$data['receiver_lat'],
  452. ];
  453. // 可选参数
  454. if (isset($data['cargo_price'])) {
  455. $apiData['cargo_price'] = (int)$data['cargo_price'];
  456. }
  457. if (isset($data['distance'])) {
  458. $apiData['distance'] = (int)$data['distance'];
  459. }
  460. if (isset($data['prepare_time'])) {
  461. $apiData['prepare_time'] = (int)$data['prepare_time'];
  462. }
  463. $body = json_encode($apiData, JSON_UNESCAPED_UNICODE);
  464. $payload = $this->buildRequestPayload('queryDeliverFee', $body);
  465. $resp = HttpClient::post("{$this->baseUrl}/queryDeliverFee", $payload);
  466. // 处理响应
  467. if (isset($resp['status']) && $resp['status'] === 'success' && $resp['code'] == 0) {
  468. $result = $resp['result'] ?? [];
  469. return [
  470. 'code' => 0,
  471. 'platform' => 'dada',
  472. 'deliver_fee' => $result['deliver_fee'] ?? 0, // 配送费(分)
  473. 'distance' => $result['distance'] ?? 0, // 距离(米)
  474. 'duration' => $result['duration'] ?? 0, // 配送时间(分钟)
  475. 'tips' => $result['tips'] ?? 0, // 小费(分)
  476. ];
  477. }
  478. return [
  479. 'code' => $resp['code'] ?? -1,
  480. 'msg' => $resp['msg'] ?? 'Unknown error',
  481. 'data' => null
  482. ];
  483. }
  484. /**
  485. * 生成随机数字符串(备用方法,如需要使用)
  486. *
  487. * @param int $length 长度
  488. * @return string 随机字符串
  489. */
  490. protected function generateNonceStr($length = 16)
  491. {
  492. $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  493. $str = '';
  494. for ($i = 0; $i < $length; $i++) {
  495. $str .= $chars[random_int(0, strlen($chars) - 1)];
  496. }
  497. return $str;
  498. }
  499. /**
  500. * 增加小费
  501. * @param array $data 包含 deliveryNo 等参数
  502. * @return array
  503. */
  504. public function addTip($data)
  505. {
  506. // 验证必需字段
  507. if (empty($data['order_id'])) {
  508. return [
  509. 'code' => -1,
  510. 'msg' => '缺少必需参数: order_id'
  511. ];
  512. }
  513. if (!isset($data['tips'])) {
  514. return [
  515. 'code' => -1,
  516. 'msg' => '缺少必需参数: tips'
  517. ];
  518. }
  519. $apiData = [
  520. 'order_id' => $data['order_id'],
  521. 'tips' => (float)$data['tips'],
  522. ];
  523. if (!empty($data['info'])) {
  524. $apiData['info'] = $data['info'];
  525. }
  526. $body = json_encode($apiData, JSON_UNESCAPED_UNICODE);
  527. $payload = $this->buildRequestPayload('api/order/addTip', $body);
  528. $resp = HttpClient::post("{$this->baseUrl}/api/order/addTip", $payload);
  529. return $resp;
  530. }
  531. public function setSourceId($sourceId)
  532. {
  533. $this->sourceId = $sourceId;
  534. }
  535. public function getSourceId($sourceId)
  536. {
  537. return $this->sourceId;
  538. }
  539. /**
  540. * 构建运费报价请求信息
  541. * @param array $order 预处理后的订单数据
  542. * @param string $orderTime 配送时间
  543. * @return array
  544. */
  545. public function buildPriceRequest($order, $orderTime)
  546. {
  547. // 1. 准备业务参数
  548. $apiData = [
  549. 'shop_no' => $order['shop_no'] ?? $this->getShopNo(),
  550. 'origin_id' => $order['origin_id'],
  551. 'cargo_price' => (float)$order['cargo_price'],
  552. 'is_prepay' => (int)$order['is_prepay'],
  553. 'receiver_name' => $order['receiver_name'],
  554. 'receiver_address' => $order['receiver_address'],
  555. 'receiver_lat' => (double)$order['receiver_lat'],
  556. 'receiver_lng' => (double)$order['receiver_lng'],
  557. 'callback' => $order['callback'],
  558. 'cargo_weight' => (double)$order['cargo_weight'],
  559. 'receiver_phone' => $order['receiver_phone'],
  560. ];
  561. // 处理预约单
  562. $ts = strtotime($orderTime);
  563. if ($ts > time() + 30) {
  564. // delay_publish_time
  565. // 预约发单时间(unix时间戳(10位),精确到分),可以支持未来3天内的订单发预约单,如预约发单时间早于当前时间会作为即时单下发。
  566. $apiData['delay_publish_time'] = $ts;
  567. $apiData['is_expect_finish_order'] = 1; // 是否根据期望送达时间预约发单(0-否,即时发单;1-是,预约发单),如传1则期望送达时间必传。
  568. $apiData['expect_finish_time_limit'] = $ts + 3000; // 期望送达时间(单位秒)== 预约发单时间 + 50分钟
  569. }
  570. // 添加可选参数
  571. if (isset($order['tips'])) {
  572. $apiData['tips'] = (float)$order['tips'];
  573. }
  574. if (isset($order['info'])) {
  575. $apiData['info'] = $order['info'];
  576. }
  577. if (isset($order['cargo_type'])) {
  578. $apiData['cargo_type'] = (int)$order['cargo_type'];
  579. }
  580. if (isset($order['cargo_num'])) {
  581. $apiData['cargo_num'] = (int)$order['cargo_num'];
  582. }
  583. if (isset($order['is_use_insurance'])) {
  584. $apiData['is_use_insurance'] = (int)$order['is_use_insurance'];
  585. }
  586. if (isset($order['product_list']) && !empty($order['product_list'])) {
  587. $apiData['product_list'] = $order['product_list'];
  588. }
  589. // 2. 构建完整请求 Payload (含签名)
  590. $body = json_encode($apiData, JSON_UNESCAPED_UNICODE);
  591. $payload = $this->buildRequestPayload('order/queryDeliverFee', $body);
  592. // 3. 返回请求配置 (供 HttpClient::postConcurrent 使用)
  593. return [
  594. 'url' => $this->baseUrl . '/api/order/queryDeliverFee',
  595. 'data' => $payload,
  596. 'headers' => ['Content-Type' => 'application/json'],
  597. 'timeout' => 5.0,
  598. ];
  599. }
  600. public function processPriceResponse($resp)
  601. {
  602. //Yii::info("[DadaAdapter] processPriceResponse: " . json_encode($resp));
  603. if (isset($resp['code']) && $resp['code'] == 0 && isset($resp['result'])) {
  604. $result = $resp['result'];
  605. //"result":{
  606. // "distance":3192,
  607. // "fee":7.99, //实际运费(单位:元),运费减去优惠券费用
  608. // "deliverFee":9.4, //运费(单位:元)
  609. // "couponFee":1.41, //优惠券费用(单位:元)
  610. // "couponName":"\u60ca\u559c\u4f18\u60e0",
  611. // "deliveryNo":"Dadabebc27e1ec78410cbb497897f5f22a88", //平台订单号
  612. // "insuranceFee":0, //保价费(单位:元)
  613. // "tips":0,
  614. // "expiredTime":1765361146 //平台订单号的过期时间
  615. // }
  616. $ret = [
  617. 'platform' => 'dada',
  618. 'fee' => $result['fee'] * 100, // *100 把单位:元 变为 分
  619. 'distance' => $result['distance'],
  620. 'tips' => $result['tips'],
  621. 'deliverFee' => $result['deliverFee'],
  622. 'deliveryNo' => $result['deliveryNo'],
  623. ];
  624. // 只有在有优惠券时,才进行coupon相关设置
  625. if(isset($result['couponName']) && isset($result['couponFee'])){
  626. $ret['couponName'] = $result['couponName'];
  627. $ret['couponFee'] = $result['couponFee'];
  628. }
  629. return $ret;
  630. }
  631. return [
  632. 'code' => $resp['code'] ?? -1,
  633. 'error' => $resp['msg'] ?? 'Unknown error',
  634. 'data' => null
  635. ];
  636. }
  637. /**
  638. * 查询订单详情
  639. * @param $orderId
  640. * @return mixed
  641. */
  642. public function selectOrder($orderId)
  643. {
  644. // 验证必需参数
  645. if (empty($orderId)) {
  646. return [
  647. 'code' => -1,
  648. 'msg' => '缺少必需参数: order_id'
  649. ];
  650. }
  651. // 构建业务参数
  652. $apiData = [
  653. 'order_id' => $orderId,
  654. ];
  655. $body = json_encode($apiData, JSON_UNESCAPED_UNICODE);
  656. $payload = $this->buildRequestPayload('api/order/status/query', $body);
  657. $resp = HttpClient::post("{$this->baseUrl}/api/order/status/query", $payload);
  658. if (isset($resp['code']) && $resp['code'] == 0 && isset($resp['result'])) {
  659. $result = $resp['result'];
  660. return [
  661. 'code' => 0,
  662. 'platform' => 'dada',
  663. 'data' => $result,
  664. ];
  665. }
  666. return [
  667. 'code' => $resp['code'] ?? -1,
  668. 'platform' => 'dada',
  669. 'msg' => $resp['msg'] ?? 'Unknown error',
  670. 'data' => null
  671. ];
  672. }
  673. }