FengniaoAdapter.php 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382
  1. <?php
  2. namespace common\components\delivery\services\adapter;
  3. use bizGhs\order\classes\OrderItemClass;
  4. use common\components\delivery\helpers\HttpClient;
  5. use common\components\delivery\platform\fengniao\Fengniao;
  6. use Yii;
  7. /**
  8. * 蜂鸟适配器
  9. *
  10. * 基于蜂鸟开放平台API进行封装
  11. * 文档参考:蜂鸟业务接口调用说明
  12. *
  13. * 接口规范:
  14. * - 调用方式:HTTPS/POST
  15. * - 编码格式:UTF-8
  16. * - Content-Type:application/json
  17. * - 正式环境:https://open-anubis.ele.me/anubis-webapi/v3/invoke/{method}
  18. * - 沙箱环境:https://exam-anubis.ele.me/anubis-webapi/v3/invoke/{method}
  19. */
  20. class FengniaoAdapter extends Fengniao implements Adapter
  21. {
  22. /**
  23. * 获取已开通城市列表
  24. *
  25. * 通过此接口可获取到蜂鸟所有已开通的城市列表信息
  26. *
  27. * @return array 包含城市列表的响应数据
  28. */
  29. public function cityList()
  30. {
  31. $businessData = [];
  32. // 构建请求参数
  33. $payload = $this->buildRequestPayload('cityList', $businessData);
  34. // 发送请求
  35. $url = $this->baseUrl . '/cityList';
  36. $resp = HttpClient::post($url, $payload, [
  37. 'Content-Type' => 'application/json',
  38. ]);
  39. Yii::info("[FengniaoAdapter] cityList Response: " . json_encode($resp));
  40. return $resp;
  41. }
  42. /**
  43. * 转化订单数据
  44. * @param $order
  45. * @param $shop
  46. * @param $params
  47. * @return array
  48. */
  49. public function formatOrderData($order, $shop, $params)
  50. {
  51. // 订单类型为预约单时必传,如需要送达时间 – 推单时间 < 60min,则蜂鸟配送开放平台自动将订单类型置为即时单
  52. $timeDiff = strtotime($params['pickupTime']) - time();
  53. $orderType = 1;
  54. if ($timeDiff > 3600) {
  55. $orderType = 3;
  56. }
  57. $sn = $order['orderSn'];
  58. $itemInfos = OrderItemClass::getAllByCondition(['orderSn' => $sn], null, 'id, name, unitPrice, num, unitWeight');
  59. $goodsWeight = 0;
  60. $goodsItemList = [];
  61. foreach ($itemInfos as $item){
  62. $goodsItemList[] = [
  63. 'item_actual_amount_cent' => $item['unitPrice'] * 100 * $item['num'],
  64. 'item_amount_cent' => $item['unitPrice'] * 100,
  65. 'item_id' => $item['id'],
  66. 'item_name' => $item['name'],
  67. 'item_quantity' => $item['num'],
  68. ];
  69. $goodsWeight += $item['unitWeight'];
  70. }
  71. $goodsCount = count($goodsItemList);
  72. // TODO: 需要实现 goods_item_list 的商品信息补充
  73. $orderData = [
  74. //'out_shop_code' => $params['mainId'], // 当使用门店发单 out_shop_code 和 chain_store_id 必填1个
  75. 'partner_order_code' => $order['orderSn'],
  76. 'receiver_primary_phone' => $order['customMobile'],
  77. 'receiver_name' => $order['customName'],
  78. 'receiver_latitude' => (float)$order['lat'],
  79. 'receiver_longitude' => (float)$order['long'],
  80. 'receiver_address' => $order['fullAddress'],
  81. 'position_source' => 3, // 坐标经纬度来源 -- 3 高德地图
  82. 'goods_count' => $goodsCount,
  83. 'goods_weight' => (float)$goodsWeight,
  84. 'goods_total_amount_cent' => (int)($order['prePrice'] * 100), //订单商品总金额
  85. 'goods_actual_amount_cent' => (int)($order['actPrice'] * 100), //订单货物实付金额
  86. 'goods_item_list' => $goodsItemList,
  87. 'order_type' => $orderType,
  88. 'chain_store_id' => 467788524, // TODO: 后续修改为动态的门店号 -- (点对点不传)
  89. 'order_remark' => $order['remark'],
  90. ];
  91. if($orderType == 3) {
  92. $orderData['require_receive_time'] = strtotime($params['order_time']) * 1000;
  93. }
  94. return $orderData;
  95. }
  96. /**
  97. * 创建订单(需用户授权)
  98. *
  99. * 根据蜂鸟官方文档实现下单功能。
  100. * 若需校验配送费价格,需先走预下单接口,并在下单时填上预下单信息。
  101. *
  102. * @param array $data 订单数据,结构参考:
  103. * [
  104. * // ========== 必填参数 ==========
  105. * 'partner_order_code' => '外部订单号(用于幂等)',
  106. * 'receiver_primary_phone' => '收货人主要联系方式(手机号、400电话、座机等)',
  107. * 'receiver_name' => '收货人姓名',
  108. * 'receiver_latitude' => '收货人纬度',
  109. * 'receiver_longitude' => '收货人经度',
  110. * 'receiver_address' => '收货人地址',
  111. * 'position_source' => '经纬度来源(1:腾讯, 2:百度, 3:高德,推荐3)',
  112. * 'goods_count' => '货物件数',
  113. * 'goods_weight' => '货物总重量(kg)',
  114. * 'goods_total_amount_cent' => '订单商品总金额(分)',
  115. * 'goods_actual_amount_cent' => '订单货物实付金额(分)',
  116. * 'goods_item_list' => '货物明细列表',
  117. * 'order_type' => '订单类型(1:即时单, 3:预约单)',
  118. *
  119. * // ========== 可选参数 ==========
  120. * // 门店信息(二选一)
  121. * 'chain_store_id' => '门店ID',
  122. * 'out_shop_code' => '外部门店ID',
  123. *
  124. * // 预下单关联
  125. * 'pre_create_order_t_index_id' => '预下单标识(用于校验配送费价格)',
  126. * 'actual_delivery_amount_cent' => '优惠后配送费总价(分,不为null则校验价格)',
  127. *
  128. * // 取货点信息(点对点特殊场景)
  129. * 'transport_address' => '取货点地址',
  130. * 'transport_name' => '取货点名称',
  131. * 'transport_latitude' => '取货点纬度',
  132. * 'transport_longitude' => '取货点经度',
  133. * 'transport_tel' => '取货点联系人电话',
  134. *
  135. * // 时间相关
  136. * 'order_add_time' => '下单时间(毫秒)',
  137. * 'require_receive_time' => '需要送达时间(毫秒,预约单时重要)',
  138. * 'expect_fetch_time' => '预计出餐时间(毫秒,预约单必填)',
  139. *
  140. * // 订单备注和来源
  141. * 'order_remark' => '用户备注',
  142. * 'order_source' => '商户订单来源(0:手发单, 2:美团, 4:口碑, 6:饿了么等)',
  143. * 'order_source_order_id' => '订单来源单号',
  144. * 'serial_number' => '商家订单流水号(长度<=8)',
  145. *
  146. * // 骑手交互
  147. * 'fetch_code' => '取餐码',
  148. * 'write_off_code' => '核销码',
  149. * 'cancel_code' => '取消码',
  150. * 'customer_ext_tel' => '分机号',
  151. *
  152. * // 收货人备用信息
  153. * 'receiver_second_phone' => '收货人备用联系方式',
  154. *
  155. * // 小费和优惠
  156. * 'order_tip_amount_cent' => '订单小费金额(分)',
  157. * 'use_coupon' => '是否使用优惠券(0:否, 1:是,默认使用)',
  158. * 'warehouse_id' => '优惠券记录ID',
  159. *
  160. * // 商品选择
  161. * 'base_goods_id' => '基础商品ID',
  162. * 'service_goods_id' => '服务商品ID',
  163. * 'appoint_extra_goods_ids' => '指定增值商品ID列表',
  164. *
  165. * 'goods_item_list' => [ // 货物明细(必填)
  166. * [
  167. * 'item_id' => '商品编号',
  168. * 'item_name' => '商品名称',
  169. * 'item_amount_cent' => '商品原价(分,必填)',
  170. * 'item_actual_amount_cent' => '商品实际支付金额(分,必填)',
  171. * 'item_size' => '商品尺寸(1:小, 2:中, 3:大)',
  172. * 'item_quantity' => '商品数量',
  173. * 'item_remark' => '商品备注',
  174. * ],
  175. * ],
  176. * ]
  177. * @return array API响应结果
  178. * 成功:['code' => 0, 'data' => ['order_id' => 订单ID]]
  179. * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null]
  180. */
  181. public function createOrder($data)
  182. {
  183. // 构建货物明细列表
  184. $goodsItemList = [];
  185. if (!empty($data['goods_item_list']) && is_array($data['goods_item_list'])) {
  186. foreach ($data['goods_item_list'] as $item) {
  187. $goodsItem = [
  188. 'item_actual_amount_cent' => isset($item['item_actual_amount_cent']) ? (int)$item['item_actual_amount_cent'] : 0,
  189. 'item_amount_cent' => isset($item['item_amount_cent']) ? (int)$item['item_amount_cent'] : 0,
  190. ];
  191. if (!empty($item['item_id'])) {
  192. $goodsItem['item_id'] = $item['item_id'];
  193. }
  194. if (!empty($item['item_name'])) {
  195. $goodsItem['item_name'] = $item['item_name'];
  196. }
  197. if (isset($item['item_size'])) {
  198. $goodsItem['item_size'] = (int)$item['item_size'];
  199. }
  200. if (isset($item['item_quantity'])) {
  201. $goodsItem['item_quantity'] = (int)$item['item_quantity'];
  202. }
  203. if (!empty($item['item_remark'])) {
  204. $goodsItem['item_remark'] = $item['item_remark'];
  205. }
  206. $goodsItemList[] = $goodsItem;
  207. }
  208. }
  209. // 构建业务数据 - 必填字段
  210. $businessData = [
  211. 'partner_order_code' => $data['partner_order_code'],
  212. 'receiver_primary_phone' => $data['receiver_primary_phone'],
  213. 'receiver_name' => $data['receiver_name'],
  214. 'receiver_latitude' => (double)($data['receiver_latitude']),
  215. 'receiver_longitude' => (double)($data['receiver_longitude']),
  216. 'receiver_address' => $data['receiver_address'],
  217. 'position_source' => $data['position_source'],
  218. 'goods_count' => (int)$data['goods_count'],
  219. 'goods_weight' => (float)$data['goods_weight'],
  220. 'goods_total_amount_cent' => $data['goods_total_amount_cent'],
  221. 'goods_actual_amount_cent' => $data['goods_actual_amount_cent'],
  222. 'goods_item_list' => $goodsItemList,
  223. 'order_type' => $data['order_type'],
  224. ];
  225. // 可选参数 - 门店信息(二选一:chain_store_id 或 out_shop_code)
  226. if (!empty($data['chain_store_id'])) {
  227. $businessData['chain_store_id'] = (int)$data['chain_store_id'];
  228. }
  229. if (!empty($data['out_shop_code'])) {
  230. $businessData['out_shop_code'] = $data['out_shop_code'];
  231. }
  232. // 可选参数 - 预下单关联(用于价格校验)
  233. if (!empty($data['pre_create_order_t_index_id'])) {
  234. $businessData['pre_create_order_t_index_id'] = $data['pre_create_order_t_index_id'];
  235. }
  236. if (isset($data['actual_delivery_amount_cent']) && $data['actual_delivery_amount_cent'] !== null) {
  237. $businessData['actual_delivery_amount_cent'] = (int)$data['actual_delivery_amount_cent'];
  238. }
  239. // 可选参数 - 取货点信息(点对点特殊场景)
  240. if (!empty($data['transport_address'])) {
  241. $businessData['transport_address'] = $data['transport_address'];
  242. }
  243. if (!empty($data['transport_name'])) {
  244. $businessData['transport_name'] = $data['transport_name'];
  245. }
  246. if (isset($data['transport_latitude']) && $data['transport_latitude'] !== '') {
  247. $businessData['transport_latitude'] = (double)$data['transport_latitude'];
  248. }
  249. if (isset($data['transport_longitude']) && $data['transport_longitude'] !== '') {
  250. $businessData['transport_longitude'] = (double)$data['transport_longitude'];
  251. }
  252. if (!empty($data['transport_tel'])) {
  253. $businessData['transport_tel'] = $data['transport_tel'];
  254. }
  255. // 可选参数 - 时间相关
  256. if (!empty($data['order_add_time'])) { // 单位:毫秒
  257. $businessData['order_add_time'] = (int)$data['order_add_time'];
  258. }
  259. if (!empty($data['require_receive_time'])) {
  260. $businessData['require_receive_time'] = $data['require_receive_time'];
  261. }
  262. if (!empty($data['expect_fetch_time'])) {
  263. $businessData['expect_fetch_time'] = (int)$data['expect_fetch_time'];
  264. }
  265. // 可选参数 - 订单备注和来源
  266. if (!empty($data['order_remark'])) {
  267. $businessData['order_remark'] = $data['order_remark'];
  268. }
  269. if (!empty($data['order_source'])) {
  270. $businessData['order_source'] = $data['order_source'];
  271. }
  272. if (!empty($data['order_source_order_id'])) {
  273. $businessData['order_source_order_id'] = $data['order_source_order_id'];
  274. }
  275. if (!empty($data['serial_number'])) {
  276. $businessData['serial_number'] = $data['serial_number'];
  277. }
  278. // 可选参数 - 骑手交互
  279. if (isset($data['fetch_code'])) {
  280. $businessData['fetch_code'] = (int)$data['fetch_code'];
  281. }
  282. if (isset($data['write_off_code'])) {
  283. $businessData['write_off_code'] = (int)$data['write_off_code'];
  284. }
  285. if (isset($data['cancel_code'])) {
  286. $businessData['cancel_code'] = (int)$data['cancel_code'];
  287. }
  288. if (!empty($data['customer_ext_tel'])) {
  289. $businessData['customer_ext_tel'] = $data['customer_ext_tel'];
  290. }
  291. // 可选参数 - 收货人备用信息
  292. if (!empty($data['receiver_second_phone'])) {
  293. $businessData['receiver_second_phone'] = $data['receiver_second_phone'];
  294. }
  295. // 可选参数 - 小费和优惠
  296. if (isset($data['order_tip_amount_cent'])) {
  297. $businessData['order_tip_amount_cent'] = (int)$data['order_tip_amount_cent'];
  298. }
  299. if (isset($data['use_coupon'])) {
  300. $businessData['use_coupon'] = (int)$data['use_coupon'];
  301. }
  302. if (!empty($data['warehouse_id'])) {
  303. $businessData['warehouse_id'] = (int)$data['warehouse_id'];
  304. }
  305. // 可选参数 - 商品选择
  306. // 注意:基础商品ID和服务商品ID不能传0(0有意义),应传null或不传
  307. if (isset($data['base_goods_id']) && $data['base_goods_id'] !== null && $data['base_goods_id'] != 0) {
  308. $businessData['base_goods_id'] = (int)$data['base_goods_id'];
  309. }
  310. if (isset($data['service_goods_id']) && $data['service_goods_id'] !== null && $data['service_goods_id'] != 0) {
  311. $businessData['service_goods_id'] = (int)$data['service_goods_id'];
  312. }
  313. if (!empty($data['appoint_extra_goods_ids']) && is_array($data['appoint_extra_goods_ids'])) {
  314. $businessData['appoint_extra_goods_ids'] = array_map('intval', $data['appoint_extra_goods_ids']);
  315. }
  316. // 构建请求参数
  317. $payload = $this->buildRequestPayload('createOrder', $businessData);
  318. // 发送请求
  319. $url = $this->baseUrl . '/createOrder';
  320. $resp = HttpClient::post($url, $payload, [
  321. 'Content-Type' => 'application/json',
  322. ]);
  323. Yii::info("[FengniaoAdapter] createOrder Response: " . json_encode($resp));
  324. // 处理响应
  325. if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) {
  326. // business_data 是 JSON 字符串,需要解析
  327. $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data'];
  328. if (is_null($respData)) {
  329. Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']);
  330. return [
  331. 'code' => -1,
  332. 'msg' => 'Failed to parse response data',
  333. 'data' => null
  334. ];
  335. }
  336. if (isset($respData['order_id'])) {
  337. return [
  338. 'code' => 0,
  339. 'platform' => 'fengniao',
  340. 'data' => [
  341. 'order_id' => $respData['order_id'],
  342. 'fee' => 0, // 请从"查询订单详情"接口获取 -- order_actual_amount_cent
  343. 'distance' => 0, // 请从"查询订单详情"接口获取 -- order_distance
  344. ]
  345. ];
  346. }
  347. }
  348. return [
  349. 'code' => (int)($resp['code'] ?? -1),
  350. 'msg' => $resp['msg'] ?? 'Unknown error',
  351. 'data' => null
  352. ];
  353. }
  354. /**
  355. * 构建运费报价请求信息(供并发请求使用)
  356. *
  357. * @param array $data 报价参数
  358. * @param string $orderTime 配送时间
  359. * @return array|null 包含 url, payload, headers, timeout 的请求信息
  360. */
  361. public function buildPriceRequest($data, $orderTime)
  362. {
  363. // 订单类型为预约单时必传,如需要送达时间 – 推单时间 < 60min,则蜂鸟配送开放平台自动将订单类型置为即时单
  364. $timeDiff = strtotime($orderTime) - time();
  365. $orderType = 1;
  366. if ($timeDiff > 3600 ) {
  367. $orderType = 3;
  368. }
  369. // 构建货物明细列表
  370. $goodsItemList = [];
  371. if (!empty($data['goods_item_list']) && is_array($data['goods_item_list'])) {
  372. foreach ($data['goods_item_list'] as $item) {
  373. $goodsItem = [
  374. 'item_actual_amount_cent' => isset($item['item_actual_amount_cent']) ? (int)$item['item_actual_amount_cent'] : 0,
  375. 'item_amount_cent' => isset($item['item_amount_cent']) ? (int)$item['item_amount_cent'] : 0,
  376. ];
  377. if (!empty($item['item_id'])) {
  378. $goodsItem['item_id'] = $item['item_id'];
  379. }
  380. if (!empty($item['item_name'])) {
  381. $goodsItem['item_name'] = $item['item_name'];
  382. }
  383. if (isset($item['item_size'])) {
  384. $goodsItem['item_size'] = (int)$item['item_size'];
  385. }
  386. if (isset($item['item_quantity'])) {
  387. $goodsItem['item_quantity'] = (int)$item['item_quantity'];
  388. }
  389. if (!empty($item['item_remark'])) {
  390. $goodsItem['item_remark'] = $item['item_remark'];
  391. }
  392. $goodsItemList[] = $goodsItem;
  393. }
  394. }
  395. // 构建业务数据 - 必填字段
  396. $businessData = [
  397. 'partner_order_code' => $data['partner_order_code'],
  398. 'position_source' => isset($data['position_source']) ? (int)$data['position_source'] : 3, // 默认高德地图
  399. 'receiver_address' => $data['receiver_address'],
  400. 'receiver_longitude' => (string)($data['receiver_longitude']),
  401. 'receiver_latitude' => (string)($data['receiver_latitude']),
  402. 'goods_total_amount_cent' => $data['goods_total_amount_cent'],
  403. 'goods_actual_amount_cent' => $data['goods_actual_amount_cent'],
  404. 'goods_weight' => (float)$data['goods_weight'],
  405. 'goods_count' => (int)$data['goods_count'],
  406. 'goods_item_list' => $goodsItemList,
  407. 'order_type' => $orderType,
  408. ];
  409. // 可选参数 - 门店信息(二选一:chain_store_id 或 out_shop_code)
  410. if (!empty($data['chain_store_id'])) {
  411. $businessData['chain_store_id'] = (int)$data['chain_store_id'];
  412. }
  413. if (!empty($data['out_shop_code'])) {
  414. $businessData['out_shop_code'] = $data['out_shop_code'];
  415. }
  416. // 可选参数 - 取货点信息
  417. if (!empty($data['transport_address'])) {
  418. $businessData['transport_address'] = $data['transport_address'];
  419. }
  420. if (isset($data['transport_longitude']) && $data['transport_longitude'] !== '') {
  421. $businessData['transport_longitude'] = (double)$data['transport_longitude'];
  422. }
  423. if (isset($data['transport_latitude']) && $data['transport_latitude'] !== '') {
  424. $businessData['transport_latitude'] = (double)$data['transport_latitude'];
  425. }
  426. if (!empty($data['transport_tel'])) {
  427. $businessData['transport_tel'] = $data['transport_tel'];
  428. }
  429. // 可选参数 - 时间相关
  430. if (!empty($data['order_add_time'])) {
  431. $businessData['order_add_time'] = (int)$data['order_add_time'];
  432. }
  433. if ($orderType == 3) { // 订单类型为预约单时必传,如需要送达时间 –--- 推单时间 < 60min,则蜂鸟配送开放平台自动将订单类型置为即时单
  434. $businessData['require_receive_time'] = strtotime($orderTime)*1000;
  435. }
  436. // 可选参数 - 订单相关
  437. if (isset($data['order_tip_amount_cent'])) {
  438. $businessData['order_tip_amount_cent'] = (int)$data['order_tip_amount_cent'];
  439. }
  440. if (!empty($data['order_source'])) {
  441. $businessData['order_source'] = $data['order_source'];
  442. }
  443. if (!empty($data['order_source_order_id'])) {
  444. $businessData['order_source_order_id'] = $data['order_source_order_id'];
  445. }
  446. // 可选参数 - 优惠券及商品选择
  447. if (isset($data['use_coupon'])) {
  448. $businessData['use_coupon'] = (int)$data['use_coupon'];
  449. }
  450. // 注意:基础商品ID和服务商品ID不能传0(0有意义),应传null或不传
  451. if (isset($data['base_goods_id']) && $data['base_goods_id'] !== null && $data['base_goods_id'] != 0) {
  452. $businessData['base_goods_id'] = (int)$data['base_goods_id'];
  453. }
  454. if (isset($data['service_goods_id']) && $data['service_goods_id'] !== null && $data['service_goods_id'] != 0) {
  455. $businessData['service_goods_id'] = (int)$data['service_goods_id'];
  456. }
  457. if (!empty($data['appoint_extra_goods_ids']) && is_array($data['appoint_extra_goods_ids'])) {
  458. $businessData['appoint_extra_goods_ids'] = array_map('intval', $data['appoint_extra_goods_ids']);
  459. }
  460. // 构建请求参数
  461. $payload = $this->buildRequestPayload('preCreateOrder', $businessData);
  462. $url = $this->baseUrl . '/preCreateOrder';
  463. return [
  464. 'url' => $url,
  465. 'data' => $payload,
  466. 'headers' => ['Content-Type' => 'application/json'],
  467. 'timeout' => 5.0,
  468. ];
  469. }
  470. /**
  471. * 处理运费报价响应
  472. *
  473. * @param array $resp API 响应
  474. * @return array|null 解析后的报价结果
  475. */
  476. public function processPriceResponse($resp)
  477. {
  478. Yii::info("[FengniaoAdapter] getPrice Response: " . json_encode($resp));
  479. // 处理响应
  480. if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) {
  481. // business_data 是 JSON 字符串,需要解析
  482. $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data'];
  483. if (is_null($respData)) {
  484. Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']);
  485. return null;
  486. }
  487. // 解析商品信息列表
  488. $goodsInfos = [];
  489. if (!empty($respData['goods_infos']) && is_array($respData['goods_infos'])) {
  490. foreach ($respData['goods_infos'] as $goodsInfo) {
  491. // 解析价格明细
  492. $priceDetail = [];
  493. if (!empty($goodsInfo['price_detail']) && is_array($goodsInfo['price_detail'])) {
  494. $pd = $goodsInfo['price_detail'];
  495. $priceDetail = [
  496. 'start_price_cent' => (int)($pd['start_price_cent'] ?? 0),
  497. 'distance_price_cent' => (int)($pd['distance_price_cent'] ?? 0),
  498. 'weight_price_cent' => (int)($pd['weight_price_cent'] ?? 0),
  499. 'pressure_surcharge_cent' => (int)($pd['pressure_surcharge_cent'] ?? 0),
  500. 'time_period_surcharge_cent' => (int)($pd['time_period_surcharge_cent'] ?? 0),
  501. 'river_crossing_surcharge_cent' => (int)($pd['river_crossing_surcharge_cent'] ?? 0),
  502. 'category_surcharge_cent' => (int)($pd['category_surcharge_cent'] ?? 0),
  503. 'temporary_surcharge_cent' => (int)($pd['temporary_surcharge_cent'] ?? 0),
  504. 'order_price_surcharge_cent' => (int)($pd['order_price_surcharge_cent'] ?? 0),
  505. ];
  506. }
  507. $goodsInfos[] = [
  508. 'base_goods_id' => (int)($goodsInfo['base_goods_id'] ?? 0),
  509. 'service_goods_id' => (int)($goodsInfo['service_goods_id'] ?? 0),
  510. 't_index_id' => $goodsInfo['t_index_id'] ?? '',
  511. 'is_valid' => (int)($goodsInfo['is_valid'] ?? 0),
  512. 'disable_reason' => $goodsInfo['disable_reason'] ?? '',
  513. 'slogan' => $goodsInfo['slogan'] ?? '',
  514. 'actual_delivery_amount_cent' => (int)($goodsInfo['actual_delivery_amount_cent'] ?? 0),
  515. 'total_delivery_amount_cent' => (int)($goodsInfo['total_delivery_amount_cent'] ?? 0),
  516. 'predict_delivery_minutes' => (int)($goodsInfo['predict_delivery_minutes'] ?? 0),
  517. 'predict_delivery_time' => (int)($goodsInfo['predict_delivery_time'] ?? 0),
  518. 'can_add_tip' => (int)($goodsInfo['can_add_tip'] ?? 0),
  519. 'warehouse_id' => $goodsInfo['warehouse_id'] ?? null,
  520. 'price_detail' => $priceDetail,
  521. ];
  522. }
  523. }
  524. return [
  525. 'platform' => 'fengniao',
  526. 'distance' => (int)($respData['distance'] ?? 0),
  527. 'time' => (int)($respData['time'] ?? 0),
  528. 'goods_infos' => $goodsInfos,
  529. ];
  530. }
  531. return null;
  532. }
  533. /**
  534. * @deprecated
  535. * 获取运费报价(预下单接口)
  536. *
  537. * 根据蜂鸟官方文档实现报价功能,返回所有可选的商品列表
  538. * 每项包含当前使用该商品下单时对应的价格等信息
  539. *
  540. * @param array $data 报价参数
  541. * @param string $orderTime 配送时间
  542. * @return array|null 包含可用商品列表及报价信息的结果
  543. * @deprecated 使用 buildPriceRequest 和 processPriceResponse 替代
  544. */
  545. public function getPrice($data, $orderTime)
  546. {
  547. // $request = $this->buildPriceRequest($data, $orderTime);
  548. // $resp = HttpClient::post($request['url'], $request['data'], $request['headers']);
  549. // return $this->processPriceResponse($resp);
  550. }
  551. /**
  552. * 获取可用订单取消原因列表
  553. *
  554. * 根据蜂鸟官方文档获取该订单可用的取消原因列表。
  555. * 用户取消订单时需先调用此接口获取可选的取消原因。
  556. *
  557. * @param array $data 查询参数,结构参考:
  558. * [
  559. * // ========== 必填参数(二选一)==========
  560. * 'order_id' => '订单ID',
  561. * 'partner_order_code' => '外部订单号',
  562. * // 注意:order_id 和 partner_order_code 必填一个
  563. * ]
  564. * @return array API响应结果
  565. * 成功:[
  566. * 'code' => 0,
  567. * 'data' => [
  568. * 'cancel_reason_list' => [
  569. * [
  570. * 'order_cancel_code' => 取消原因code,
  571. * 'order_cancel_desc' => '取消原因描述',
  572. * ],
  573. * // ... 更多原因
  574. * ]
  575. * ]
  576. * ]
  577. * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null]
  578. */
  579. public function getCancelReasonList($data)
  580. {
  581. // 验证 order_id 和 partner_order_code 必填一个
  582. $orderId = $data['order_id'] ?? $data;
  583. $partnerOrderCode = null;
  584. // 如果 $orderId 是 XSD_ 开头,则给 partnerOrderCode 赋值为 $orderId
  585. if (strpos($orderId, 'XSD_') === 0) {
  586. $partnerOrderCode = $orderId;
  587. $orderId = null;
  588. }
  589. if (empty($orderId) && empty($partnerOrderCode)) {
  590. Yii::warning("[FengniaoAdapter] getCancelReasonList failed: order_id and partner_order_code cannot both be empty");
  591. return [
  592. 'code' => -1,
  593. 'msg' => 'order_id and partner_order_code must have one',
  594. 'data' => null
  595. ];
  596. }
  597. // 构建业务数据
  598. $businessData = [];
  599. // 订单标识(二选一)
  600. if (!empty($orderId)) {
  601. $businessData['order_id'] = (int)$orderId;
  602. }
  603. if (!empty($partnerOrderCode)) {
  604. $businessData['partner_order_code'] = $partnerOrderCode;
  605. }
  606. // 构建请求参数
  607. $payload = $this->buildRequestPayload('getCancelReasonList', $businessData);
  608. // 发送请求
  609. $url = $this->baseUrl . '/getCancelReasonList';
  610. $resp = HttpClient::post($url, $payload, [
  611. 'Content-Type' => 'application/json',
  612. ]);
  613. Yii::info("[FengniaoAdapter] getCancelReasonList Response: " . json_encode($resp));
  614. // 处理响应
  615. if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) {
  616. // business_data 是 JSON 字符串,需要解析
  617. $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data'];
  618. if (is_null($respData)) {
  619. Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']);
  620. return [
  621. 'code' => -1,
  622. 'msg' => 'Failed to parse response data',
  623. 'data' => null
  624. ];
  625. }
  626. // 解析取消原因列表
  627. $cancelReasonList = [];
  628. if (!empty($respData['cancel_reason_list']) && is_array($respData['cancel_reason_list'])) {
  629. foreach ($respData['cancel_reason_list'] as $reason) {
  630. $cancelReasonList[] = [
  631. 'order_cancel_code' => (int)($reason['order_cancel_code'] ?? 0),
  632. 'order_cancel_desc' => $reason['order_cancel_desc'] ?? '',
  633. ];
  634. }
  635. }
  636. return [
  637. 'code' => 0,
  638. 'data' => [
  639. 'reasonList' => $cancelReasonList,
  640. ]
  641. ];
  642. }
  643. return [
  644. 'code' => (int)($resp['code'] ?? -1),
  645. 'msg' => $resp['msg'] ?? 'Unknown error',
  646. 'data' => null
  647. ];
  648. }
  649. /**
  650. * 订单取消
  651. *
  652. * 根据蜂鸟官方文档实现订单取消功能。
  653. * 支持商户取消和用户取消两种场景。
  654. *
  655. * @param array $data 取消参数,结构参考:
  656. * [
  657. * // ========== 必填参数(二选一)==========
  658. * 'order_id' => '订单ID',
  659. * 'partner_order_code' => '外部订单号',
  660. * // 注意:order_id 和 partner_order_code 必填一个
  661. *
  662. * // ========== 必填参数 ==========
  663. * 'order_cancel_role' => '取消的角色(1:商户取消, 2:用户取消)',
  664. * 'order_cancel_code' => '取消原因code',
  665. * 'order_cancel_other_reason' => '取消原因补充(20字以内)',
  666. *
  667. * // ========== 可选参数 ==========
  668. * 'need_reverse_order' => '是否期望发起售中逆向(Boolean)',
  669. * 'actual_cancel_cost_cent' => '取消价格(分)',
  670. * ]
  671. * @return array API响应结果
  672. * 成功:['code' => 0, 'data' => ['result' => true]]
  673. * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null]
  674. */
  675. public function cancelOrder($orderId, $params)
  676. {
  677. // 验证 order_id 和 partner_order_code 必填一个
  678. $partnerOrderCode = null;
  679. // 如果 $orderId 是 XSD_ 开头,则给 partnerOrderCode 赋值为 $orderId
  680. if (strpos($orderId, 'XSD_') === 0) {
  681. $partnerOrderCode = $orderId;
  682. $orderId = null;
  683. }
  684. if (empty($orderId) && empty($partnerOrderCode)) {
  685. Yii::warning("[FengniaoAdapter] cancelOrder failed: order_id and partner_order_code cannot both be empty");
  686. return [
  687. 'code' => -1,
  688. 'msg' => 'order_id and partner_order_code must have one',
  689. 'data' => null
  690. ];
  691. }
  692. // 构建业务数据
  693. $businessData = [];
  694. // 必填参数 - 订单标识(二选一)
  695. if (!empty($orderId)) {
  696. $businessData['order_id'] = $orderId;
  697. }
  698. if (!empty($partnerOrderCode)) {
  699. $businessData['partner_order_code'] = $partnerOrderCode;
  700. }
  701. // 必填参数 - 取消角色
  702. if (isset($params['order_cancel_role'])) {
  703. $businessData['order_cancel_role'] = (int)$params['order_cancel_role'];
  704. } else {
  705. Yii::warning("[FengniaoAdapter] cancelOrder failed: order_cancel_role is required");
  706. return [
  707. 'code' => -1,
  708. 'msg' => 'order_cancel_role is required (1:商户取消, 2:用户取消)',
  709. 'data' => null
  710. ];
  711. }
  712. // 必填参数 - 取消原因code
  713. if (isset($params['order_cancel_code'])) {
  714. $businessData['order_cancel_code'] = (int)$params['order_cancel_code'];
  715. } else {
  716. Yii::warning("[FengniaoAdapter] cancelOrder failed: order_cancel_code is required");
  717. return [
  718. 'code' => -1,
  719. 'msg' => 'order_cancel_code is required',
  720. 'data' => null
  721. ];
  722. }
  723. // 必填参数 - 取消原因补充
  724. if (!empty($params['order_cancel_reason'])) {
  725. $businessData['order_cancel_other_reason'] = $params['order_cancel_reason'];
  726. } else {
  727. Yii::warning("[FengniaoAdapter] cancelOrder failed: order_cancel_other_reason is required");
  728. return [
  729. 'code' => -1,
  730. 'msg' => 'order_cancel_other_reason is required (20字以内)',
  731. 'data' => null
  732. ];
  733. }
  734. // 可选参数 - 是否期望发起售中逆向
  735. if (isset($params['need_reverse_order']) && $params['need_reverse_order'] !== null) {
  736. $businessData['need_reverse_order'] = (bool)$params['need_reverse_order'];
  737. }
  738. // 可选参数 - 取消价格
  739. if (isset($params['actual_cancel_cost_cent']) && $params['actual_cancel_cost_cent'] !== null) {
  740. $businessData['actual_cancel_cost_cent'] = (int)$params['actual_cancel_cost_cent'];
  741. }
  742. // 构建请求参数
  743. $payload = $this->buildRequestPayload('cancelOrder', $businessData);
  744. // 发送请求
  745. $url = $this->baseUrl . '/cancelOrder';
  746. $resp = HttpClient::post($url, $payload, [
  747. 'Content-Type' => 'application/json',
  748. ]);
  749. Yii::info("[FengniaoAdapter] cancelOrder Response: " . json_encode($resp));
  750. // 处理响应
  751. if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) {
  752. // business_data 是 JSON 字符串,需要解析
  753. $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data'];
  754. if (is_null($respData)) {
  755. Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']);
  756. return [
  757. 'code' => -1,
  758. 'platform' => 'fengniao',
  759. 'msg' => 'Failed to parse response data',
  760. 'data' => null
  761. ];
  762. }
  763. return [
  764. 'code' => 0,
  765. 'platform' => 'fengniao',
  766. 'data' => [
  767. 'result' => (bool)($respData['result'] ?? false),
  768. 'reverse_order_ok' => (bool)($respData['reverse_order_ok'] ?? false),
  769. ]
  770. ];
  771. }
  772. return [
  773. 'code' => (int)($resp['code'] ?? -1),
  774. 'platform' => 'fengniao',
  775. 'msg' => $resp['msg'] ?? 'Unknown error',
  776. 'data' => null
  777. ];
  778. }
  779. /**
  780. * 订单预取消接口
  781. *
  782. * 根据蜂鸟官方文档获取取消价格。
  783. * 需要查询并校验取消价格时使用,若强校验价格需在正式取消接口中把出参价格传过去。
  784. *
  785. * @param array $data 预取消查询参数,结构参考:
  786. * [
  787. * // ========== 必填参数(二选一)==========
  788. * 'order_id' => '订单ID',
  789. * 'partner_order_code' => '外部订单号',
  790. * // 注意:order_id 和 partner_order_code 必填一个
  791. *
  792. * // ========== 必填参数 ==========
  793. * 'order_cancel_code' => '取消原因code(从可用取消原因列表接口返回结果选择)',
  794. * ]
  795. * @return array API响应结果
  796. * 成功:[
  797. * 'code' => 0,
  798. * 'data' => [
  799. * 'actual_cancel_cost_cent' => '取消实际需金额(分,默认使用已有免责取消权益使用后金额为0)',
  800. * ]
  801. * ]
  802. * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null]
  803. *
  804. * @note
  805. * - 获取取消价格,用于查询并校验取消价格
  806. * - actual_cancel_cost_cent 为取消实际需要支付的金额(分),通常为 0(使用免责取消权益)
  807. * - 若需强校验价格,应在正式取消接口(cancelOrder)中把此出参价格传过去
  808. * - 流程:先调用此方法获取取消价格 → 校验价格 → 调用 cancelOrder 正式取消
  809. */
  810. public function preCancelOrder($data)
  811. {
  812. // 验证 order_id 和 partner_order_code 必填一个
  813. $orderId = $data['order_id'];
  814. $partnerOrderCode = null;
  815. // 如果 $orderId 是 XSD_ 开头,则给 partnerOrderCode 赋值为 $orderId
  816. if (strpos($orderId, 'XSD_') === 0) {
  817. $partnerOrderCode = $orderId;
  818. $orderId = null;
  819. }
  820. if (empty($orderId) && empty($partnerOrderCode)) {
  821. Yii::warning("[FengniaoAdapter] preCancelOrder failed: order_id and partner_order_code cannot both be empty");
  822. return [
  823. 'code' => -1,
  824. 'msg' => 'order_id and partner_order_code must have one',
  825. 'data' => null
  826. ];
  827. }
  828. // 验证 order_cancel_code(取消原因code)必填
  829. if (!isset($data['order_cancel_code'])) {
  830. Yii::warning("[FengniaoAdapter] preCancelOrder failed: order_cancel_code is required");
  831. return [
  832. 'code' => -1,
  833. 'msg' => 'order_cancel_code is required (from getCancelReasonList)',
  834. 'data' => null
  835. ];
  836. }
  837. // 构建业务数据
  838. $businessData = [];
  839. // 订单标识(二选一)
  840. if (!empty($orderId)) {
  841. $businessData['order_id'] = (int)$orderId;
  842. }
  843. if (!empty($partnerOrderCode)) {
  844. $businessData['partner_order_code'] = $partnerOrderCode;
  845. }
  846. // 必填参数 - 取消原因code
  847. $businessData['order_cancel_code'] = (int)$data['order_cancel_code'];
  848. // 构建请求参数
  849. $payload = $this->buildRequestPayload('preCancelOrder', $businessData);
  850. // 发送请求
  851. $url = $this->baseUrl . '/preCancelOrder';
  852. $resp = HttpClient::post($url, $payload, [
  853. 'Content-Type' => 'application/json',
  854. ]);
  855. Yii::info("[FengniaoAdapter] preCancelOrder Response: " . json_encode($resp));
  856. // 处理响应
  857. if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) {
  858. // business_data 是 JSON 字符串,需要解析
  859. $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data'];
  860. if (is_null($respData)) {
  861. Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']);
  862. return [
  863. 'code' => -1,
  864. 'msg' => 'Failed to parse response data',
  865. 'data' => null
  866. ];
  867. }
  868. return [
  869. 'code' => 0,
  870. 'data' => [
  871. 'actual_cancel_cost_cent' => (int)($respData['actual_cancel_cost_cent'] ?? 0),
  872. ]
  873. ];
  874. }
  875. return [
  876. 'code' => (int)($resp['code'] ?? -1),
  877. 'msg' => $resp['msg'] ?? 'Unknown error',
  878. 'data' => null
  879. ];
  880. }
  881. /**
  882. * 追加小费
  883. *
  884. * 根据蜂鸟官方文档为订单追加小费。
  885. * 订单小费总金额为每次累加。
  886. *
  887. * @param array $data 加小费参数,结构参考:
  888. * [
  889. * // ========== 必填参数(二选一)==========
  890. * 'order_id' => '订单ID',
  891. * 'partner_order_code' => '外部订单号',
  892. * // 注意:order_id 和 partner_order_code 必填一个
  893. *
  894. * // ========== 必填参数 ==========
  895. * 'third_index_id' => '本次加小费唯一标识(Long类型)',
  896. * 'add_tip_amount_cent' => '加小费金额(分)',
  897. * ]
  898. * @return array API响应结果
  899. * 成功:['code' => 0, 'data' => ['result' => true]]
  900. * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null]
  901. *
  902. * @note
  903. * - 追加小费,订单小费总金额为每次累加
  904. * - 不是所有订单都支持加小费,受合同服务 & 接单运力 & 当时订单状态等多个因素影响
  905. * - 订单详情接口有返回是否可加小费字段(if_can_add_tip)
  906. * - 联调环境使用加小费接口需先使用联调工具把订单状态改为1,否则订单状态会不支持加小费
  907. * - third_index_id用于幂等,相同third_index_id的后续请求会被忽略
  908. */
  909. public function addTip($data = [])
  910. {
  911. // 验证 order_id 和 partner_order_code 必填一个
  912. $orderId = $data['orderId'] ?? null;
  913. $partnerOrderCode = $data['partner_order_code'] ?? null;
  914. $tips = $data['tips'] ?? null;
  915. if($tips != null){
  916. $data['add_tip_amount_cent'] = $tips;
  917. }
  918. if (empty($orderId) && empty($partnerOrderCode)) {
  919. Yii::warning("[FengniaoAdapter] addTip failed: order_id and partner_order_code cannot both be empty");
  920. return [
  921. 'code' => -1,
  922. 'msg' => 'order_id and partner_order_code must have one',
  923. 'data' => null
  924. ];
  925. }
  926. // 验证 third_index_id(幂等标识)
  927. if (!isset($data['third_index_id'])) {
  928. Yii::warning("[FengniaoAdapter] addTip failed: third_index_id is required");
  929. return [
  930. 'code' => -1,
  931. 'msg' => 'third_index_id is required for idempotency',
  932. 'data' => null
  933. ];
  934. }
  935. // 验证 add_tip_amount_cent(加小费金额)
  936. if (!isset($data['add_tip_amount_cent']) || $data['add_tip_amount_cent'] <= 0) {
  937. Yii::warning("[FengniaoAdapter] addTip failed: add_tip_amount_cent must be greater than 0");
  938. return [
  939. 'code' => -1,
  940. 'msg' => 'add_tip_amount_cent is required and must be greater than 0',
  941. 'data' => null
  942. ];
  943. }
  944. // 构建业务数据
  945. $businessData = [];
  946. // 订单标识(二选一)
  947. if (!empty($orderId)) {
  948. $businessData['order_id'] = (int)$orderId;
  949. }
  950. if (!empty($partnerOrderCode)) {
  951. $businessData['partner_order_code'] = $partnerOrderCode;
  952. }
  953. // 必填参数
  954. $businessData['third_index_id'] = (int)$data['third_index_id'];
  955. $businessData['add_tip_amount_cent'] = (int)$data['add_tip_amount_cent'];
  956. // 构建请求参数
  957. $payload = $this->buildRequestPayload('addTip', $businessData);
  958. // 发送请求
  959. $url = $this->baseUrl . '/addTip';
  960. $resp = HttpClient::post($url, $payload, [
  961. 'Content-Type' => 'application/json',
  962. ]);
  963. Yii::info("[FengniaoAdapter] addTip Response: " . json_encode($resp));
  964. // 处理响应
  965. if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) {
  966. // business_data 是 JSON 字符串,需要解析
  967. $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data'];
  968. if (is_null($respData)) {
  969. Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']);
  970. return [
  971. 'code' => -1,
  972. 'msg' => 'Failed to parse response data',
  973. 'data' => null
  974. ];
  975. }
  976. return [
  977. 'code' => 0,
  978. 'data' => [
  979. 'result' => (bool)($respData['result'] ?? false),
  980. ]
  981. ];
  982. }
  983. return [
  984. 'code' => (int)($resp['code'] ?? -1),
  985. 'msg' => $resp['msg'] ?? 'Unknown error',
  986. 'data' => null
  987. ];
  988. }
  989. /**
  990. * 查询订单详情
  991. *
  992. * 根据蜂鸟官方文档获取订单的详细信息。
  993. * 包括订单状态、配送员信息、事件节点、价格明细等完整信息。
  994. *
  995. * @param array $data 查询参数,结构参考:
  996. * [
  997. * // ========== 必填参数(二选一)==========
  998. * 'order_id' => '订单ID',
  999. * 'partner_order_code' => '外部订单号',
  1000. * // 注意:order_id 和 partner_order_code 必填一个
  1001. * ]
  1002. * @return array API响应结果
  1003. */
  1004. //public function getOrderDetail($data = [])
  1005. public function selectOrder($data)
  1006. {
  1007. // 验证 order_id 和 partner_order_code 必填一个
  1008. $orderId = $data['order_id'] ?? $data;
  1009. $partnerOrderCode = null;
  1010. // 如果 $orderId 是 XSD_ 开头,则给 partnerOrderCode 赋值为 $orderId
  1011. if (strpos($orderId, 'XSD_') === 0) {
  1012. $partnerOrderCode = $orderId;
  1013. $orderId = null;
  1014. }
  1015. if (empty($orderId) && empty($partnerOrderCode)) {
  1016. Yii::warning("[FengniaoAdapter] getOrderDetail failed: order_id and partner_order_code cannot both be empty");
  1017. return [
  1018. 'code' => -1,
  1019. 'msg' => 'order_id and partner_order_code must have one',
  1020. 'data' => null
  1021. ];
  1022. }
  1023. // 构建业务数据
  1024. $businessData = [];
  1025. // 订单标识(二选一)
  1026. if (!empty($orderId)) {
  1027. $businessData['order_id'] = $orderId;
  1028. }
  1029. if (!empty($partnerOrderCode)) {
  1030. $businessData['partner_order_code'] = $partnerOrderCode;
  1031. }
  1032. // 构建请求参数
  1033. $payload = $this->buildRequestPayload('getOrderDetail', $businessData);
  1034. // 发送请求
  1035. $url = $this->baseUrl . '/getOrderDetail';
  1036. $resp = HttpClient::post($url, $payload, [
  1037. 'Content-Type' => 'application/json',
  1038. ]);
  1039. Yii::info("[FengniaoAdapter] getOrderDetail Response: " . json_encode($resp));
  1040. // 处理响应
  1041. if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) {
  1042. // business_data 是 JSON 字符串,需要解析
  1043. $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data'];
  1044. if (is_null($respData)) {
  1045. Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']);
  1046. return [
  1047. 'code' => -1,
  1048. 'msg' => 'Failed to parse response data',
  1049. 'data' => null
  1050. ];
  1051. }
  1052. // 解析价格明细
  1053. $priceDetail = [];
  1054. if (!empty($respData['price_detail']) && is_array($respData['price_detail'])) {
  1055. $pd = $respData['price_detail'];
  1056. $priceDetail = [
  1057. 'start_price_cent' => (int)($pd['start_price_cent'] ?? 0),
  1058. 'distance_price_cent' => (int)($pd['distance_price_cent'] ?? 0),
  1059. 'weight_price_cent' => (int)($pd['weight_price_cent'] ?? 0),
  1060. 'pressure_surcharge_cent' => (int)($pd['pressure_surcharge_cent'] ?? 0),
  1061. 'time_period_surcharge_cent' => (int)($pd['time_period_surcharge_cent'] ?? 0),
  1062. 'river_crossing_surcharge_cent' => (int)($pd['river_crossing_surcharge_cent'] ?? 0),
  1063. 'category_surcharge_cent' => (int)($pd['category_surcharge_cent'] ?? 0),
  1064. 'temporary_surcharge_cent' => (int)($pd['temporary_surcharge_cent'] ?? 0),
  1065. 'order_price_surcharge_cent' => (int)($pd['order_price_surcharge_cent'] ?? 0),
  1066. ];
  1067. }
  1068. // 解析事件日志列表
  1069. $eventLogDetails = [];
  1070. if (!empty($respData['event_log_details']) && is_array($respData['event_log_details'])) {
  1071. foreach ($respData['event_log_details'] as $log) {
  1072. $eventLogDetails[] = [
  1073. 'order_status' => (int)($log['order_status'] ?? 0),
  1074. 'occur_time' => (int)($log['occur_time'] ?? 0),
  1075. 'carrier_driver_name' => $log['carrier_driver_name'] ?? '',
  1076. 'carrier_driver_phone' => $log['carrier_driver_phone'] ?? '',
  1077. ];
  1078. }
  1079. }
  1080. // 解析逆向单运单事件节点
  1081. $reverseEventLogDetails = [];
  1082. if (!empty($respData['reverse_event_log_details']) && is_array($respData['reverse_event_log_details'])) {
  1083. $reverse = $respData['reverse_event_log_details'];
  1084. $reverseEventLogDetails = [
  1085. 'order_status' => (int)($reverse['order_status'] ?? 0),
  1086. 'occur_time' => (int)($reverse['occur_time'] ?? 0),
  1087. 'carrier_driver_name' => $reverse['carrier_driver_name'] ?? '',
  1088. 'carrier_driver_phone' => $reverse['carrier_driver_phone'] ?? '',
  1089. ];
  1090. }
  1091. // 解析天气信息
  1092. $weatherInfo = [];
  1093. if (!empty($respData['weather_info']) && is_array($respData['weather_info'])) {
  1094. $weather = $respData['weather_info'];
  1095. $weatherInfo = [
  1096. 'weatherRate' => (int)($weather['weatherRate'] ?? 0),
  1097. 'weatherDesc' => $weather['weatherDesc'] ?? '',
  1098. ];
  1099. }
  1100. // 解析取货照片列表
  1101. $deliveryFetchPhotos = [];
  1102. if (!empty($respData['delivery_fetch_photos']) && is_array($respData['delivery_fetch_photos'])) {
  1103. $deliveryFetchPhotos = $respData['delivery_fetch_photos'];
  1104. }
  1105. return [
  1106. 'code' => 0,
  1107. 'data' => [
  1108. // 基本信息
  1109. 'order_id' => (int)($respData['order_id'] ?? 0),
  1110. 'partner_order_code' => $respData['partner_order_code'] ?? '',
  1111. 'tracking_id' => (int)($respData['tracking_id'] ?? 0),
  1112. 'order_status' => (int)($respData['order_status'] ?? 0),
  1113. 'serial_number' => $respData['serial_number'] ?? '',
  1114. 'remark' => $respData['remark'] ?? '',
  1115. // 配送员信息
  1116. 'carrier_driver_id' => $respData['carrier_driver_id'] ?? '',
  1117. 'carrier_driver_name' => $respData['carrier_driver_name'] ?? '',
  1118. 'carrier_driver_phone' => $respData['carrier_driver_phone'] ?? '',
  1119. 'temperature' => $respData['temperature'] ?? '',
  1120. // 金额信息(单位:分)
  1121. 'order_total_amount_cent' => (int)($respData['order_total_amount_cent'] ?? 0),
  1122. 'order_actual_amount_cent' => (int)($respData['order_actual_amount_cent'] ?? 0),
  1123. 'order_tip_amount_cent' => (int)($respData['order_tip_amount_cent'] ?? 0),
  1124. 'overtime_compensation_cost_cent' => (int)($respData['overtime_compensation_cost_cent'] ?? 0),
  1125. // 配送信息
  1126. 'order_distance' => (double)($respData['order_distance'] ?? 0),
  1127. 'estimate_arrive_time' => (int)($respData['estimate_arrive_time'] ?? 0),
  1128. 'if_can_add_tip' => (int)($respData['if_can_add_tip'] ?? 0),
  1129. 'fetch_code' => $respData['fetch_code'] ?? '',
  1130. 'write_off_code' => $respData['write_off_code'] ?? '',
  1131. 'cancel_code' => $respData['cancel_code'] ?? '',
  1132. // 价格明细
  1133. 'price_detail' => $priceDetail,
  1134. // 事件日志
  1135. 'event_log_details' => $eventLogDetails,
  1136. 'reverse_event_log_details' => $reverseEventLogDetails,
  1137. // 异常信息
  1138. 'abnormal_code' => $respData['abnormal_code'] ?? '',
  1139. 'abnormal_desc' => $respData['abnormal_desc'] ?? '',
  1140. // 索赔信息
  1141. 'claim_id' => (int)($respData['claim_id'] ?? 0),
  1142. 'claim_status' => (int)($respData['claim_status'] ?? 0),
  1143. 'claim_status_desc' => $respData['claim_status_desc'] ?? '',
  1144. 'claim_reason_desc' => $respData['claim_reason_desc'] ?? '',
  1145. // 投诉信息
  1146. 'complaint_id' => (int)($respData['complaint_id'] ?? 0),
  1147. 'complaint_status' => (int)($respData['complaint_status'] ?? 0),
  1148. 'complaint_response_desc' => $respData['complaint_response_desc'] ?? '',
  1149. 'complaint_reason_desc' => $respData['complaint_reason_desc'] ?? '',
  1150. // 其他
  1151. 'delivery_fetch_photos' => $deliveryFetchPhotos,
  1152. 'weather_info' => $weatherInfo,
  1153. ]
  1154. ];
  1155. }
  1156. return [
  1157. 'code' => (int)($resp['code'] ?? -1),
  1158. 'msg' => $resp['msg'] ?? 'Unknown error',
  1159. 'data' => null
  1160. ];
  1161. }
  1162. /**
  1163. * 查询骑手信息
  1164. *
  1165. * 根据蜂鸟官方文档获取订单对应的骑手(配送员)的实时位置和联系信息。
  1166. * 返回骑手的ID、姓名、电话以及实时位置坐标(高德地图坐标系)。
  1167. *
  1168. * @param array $data 查询参数,结构参考:
  1169. * [
  1170. * // ========== 必填参数(二选一)==========
  1171. * 'order_id' => '订单ID',
  1172. * 'partner_order_code' => '外部订单号',
  1173. * // 注意:order_id 和 partner_order_code 必填一个
  1174. * ]
  1175. * @return array API响应结果
  1176. * 成功:[
  1177. * 'code' => 0,
  1178. * 'data' => [
  1179. * 'carrier_driver_id' => '骑手ID',
  1180. * 'carrier_driver_name' => '骑手姓名',
  1181. * 'carrier_driver_phone' => '骑手电话',
  1182. * 'carrier_driver_latitude' => '骑手位置纬度',
  1183. * 'carrier_driver_longitude' => '骑手位置经度',
  1184. * // 返回的是高德地图的经纬度
  1185. * ]
  1186. * ]
  1187. * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null]
  1188. */
  1189. public function getKnightInfo($data = [])
  1190. {
  1191. // 验证 order_id 和 partner_order_code 必填一个
  1192. $orderId = $data['order_id'] ?? null;
  1193. $partnerOrderCode = $data['partner_order_code'] ?? null;
  1194. if (empty($orderId) && empty($partnerOrderCode)) {
  1195. Yii::warning("[FengniaoAdapter] getKnightInfo failed: order_id and partner_order_code cannot both be empty");
  1196. return [
  1197. 'code' => -1,
  1198. 'msg' => 'order_id and partner_order_code must have one',
  1199. 'data' => null
  1200. ];
  1201. }
  1202. // 构建业务数据
  1203. $businessData = [];
  1204. // 订单标识(二选一)
  1205. if (!empty($orderId)) {
  1206. $businessData['order_id'] = (int)$orderId;
  1207. }
  1208. if (!empty($partnerOrderCode)) {
  1209. $businessData['partner_order_code'] = $partnerOrderCode;
  1210. }
  1211. // 构建请求参数
  1212. $payload = $this->buildRequestPayload('getKnightInfo', $businessData);
  1213. // 发送请求
  1214. $url = $this->baseUrl . '/getKnightInfo';
  1215. $resp = HttpClient::post($url, $payload, [
  1216. 'Content-Type' => 'application/json',
  1217. ]);
  1218. Yii::info("[FengniaoAdapter] getKnightInfo Response: " . json_encode($resp));
  1219. // 处理响应
  1220. if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) {
  1221. // business_data 是 JSON 字符串,需要解析
  1222. $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data'];
  1223. if (is_null($respData)) {
  1224. Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']);
  1225. return [
  1226. 'code' => -1,
  1227. 'msg' => 'Failed to parse response data',
  1228. 'data' => null
  1229. ];
  1230. }
  1231. return [
  1232. 'code' => 0,
  1233. 'data' => [
  1234. 'carrier_driver_id' => $respData['carrier_driver_id'] ?? '',
  1235. 'carrier_driver_name' => $respData['carrier_driver_name'] ?? '',
  1236. 'carrier_driver_phone' => $respData['carrier_driver_phone'] ?? '',
  1237. 'carrier_driver_latitude' => $respData['carrier_driver_latitude'] ?? '',
  1238. 'carrier_driver_longitude' => $respData['carrier_driver_longitude'] ?? '',
  1239. ]
  1240. ];
  1241. }
  1242. return [
  1243. 'code' => (int)($resp['code'] ?? -1),
  1244. 'msg' => $resp['msg'] ?? 'Unknown error',
  1245. 'data' => null
  1246. ];
  1247. }
  1248. }