OrderService.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. <?php
  2. /**
  3. * 1688分销严选采购解决方案 - 订单服务类
  4. *
  5. * 提供预下单、下单、订单查询等功能
  6. *
  7. * @author: yourname
  8. * @day: 2026/04/28
  9. */
  10. namespace app\services\ThirdParty\AlibabaAgent;
  11. use app\entity\data\store\StoreOrderEntity;
  12. use app\entity\data\store\StoreOrderStatusEntity;
  13. use think\facade\Db;
  14. use think\facade\Log;
  15. class OrderService extends AlibabaAgentBaseService
  16. {
  17. /**
  18. * API命名空间
  19. */
  20. const NAMESPACE = 'com.alibaba.trade';
  21. /**
  22. * 物流API命名空间
  23. */
  24. const LOGISTICS_NAMESPACE = 'com.alibaba.logistics';
  25. /**
  26. * 预下单
  27. *
  28. * API: com.alibaba.trade:alibaba.createOrder.preview-1
  29. *
  30. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.createOrder.preview-1
  31. *
  32. * 请求参数格式:
  33. * - flow: 流程标识(saleproxy=分销采购, general=普通下单)
  34. * - productList: 商品列表JSON数组,每个商品包含 offerId, quantity, specId
  35. * - addressParam: 地址参数JSON对象,包含 fullName, mobile, provinceText, cityText, areaText, address
  36. * - totalAmount: 总金额(单位:分,可选)
  37. * - message: 买家留言(可选)
  38. *
  39. * @param array $params 预下单参数
  40. * @return array|false
  41. */
  42. public function previewOrder(array $params = [])
  43. {
  44. $businessParams = [];
  45. // --- productList(商品列表,JSON字符串) ---
  46. // 直接传入已格式化的数组,由方法转为JSON
  47. if (!empty($params['cargoParamList'])) {
  48. $businessParams['cargoParamList'] = is_array($params['cargoParamList'])
  49. ? json_encode($params['cargoParamList'], JSON_UNESCAPED_UNICODE)
  50. : $params['cargoParamList'];
  51. }
  52. // --- addressParam(地址参数,JSON字符串) ---
  53. // 直接传入已格式化的数组,由方法转为JSON
  54. if (!empty($params['addressParam'])) {
  55. $businessParams['addressParam'] = is_array($params['addressParam'])
  56. ? json_encode($params['addressParam'], JSON_UNESCAPED_UNICODE)
  57. : $params['addressParam'];
  58. }
  59. // --- 调用API ---
  60. $result = $this->executeParam2(
  61. self::NAMESPACE,
  62. 'alibaba.createOrder.preview',
  63. 1,
  64. $businessParams
  65. );
  66. if ($result === false) {
  67. Log::channel('alibaba')->error('[预下单] API请求失败', $businessParams);
  68. return false;
  69. }
  70. Log::channel('alibaba')->info('[预下单] API请求成功', ['result' => json_encode($result)]);
  71. return $this->formatPreviewResult($result);
  72. }
  73. /**
  74. * 格式化预下单返回结果
  75. *
  76. * 实际返回结构:
  77. * {
  78. * "orderPreviewResuslt": [{
  79. * "tradeModeNameList": ["assureTrade"],
  80. * "status": true,
  81. * "sumPayment": 370, // 总支付金额(单位:分)
  82. * "sumCarriage": 300, // 总运费(单位:分)
  83. * "sumPaymentNoCarriage": 70, // 商品总金额(不含运费,单位:分)
  84. * "flowFlag": "fenxiaonew",
  85. * "cargoList": [{ // 商品列表
  86. * "amount": 0.7,
  87. * "finalUnitPrice": 0.7,
  88. * "specId": "...",
  89. * "skuId": 6148579878582,
  90. * "offerId": 678279922176,
  91. * "openOfferId": "...",
  92. * "cargoPromotionList": []
  93. * }],
  94. * "tradeModelList": [{"name":"担保交易","tradeType":"assureTrade","opSupport":true}],
  95. * "payChannelInfos": [{"name":"alipay"}],
  96. * "orderGroup": "79d4f82d...",
  97. * "canUseOfficialSolution": false
  98. * }],
  99. * "success": true,
  100. * "unsupportedCrossBorderPayOfferList": []
  101. * }
  102. *
  103. * @param array $result API原始返回数据
  104. * @return array
  105. */
  106. protected function formatPreviewResult(array $result): array
  107. {
  108. $success = $result['success'] ?? false;
  109. $previewList = $result['orderPreviewResuslt'] ?? [];
  110. if (empty($previewList) || !is_array($previewList)) {
  111. return [
  112. 'success' => $success,
  113. 'preview' => null,
  114. 'list' => [],
  115. ];
  116. }
  117. $formattedList = [];
  118. foreach ($previewList as $item) {
  119. $formattedList[] = [
  120. 'sumPayment' => $item['sumPayment'] ?? 0,
  121. 'sumCarriage' => $item['sumCarriage'] ?? 0,
  122. 'sumPaymentNoCarriage' => $item['sumPaymentNoCarriage'] ?? 0,
  123. 'flowFlag' => $item['flowFlag'] ?? '',
  124. // 'orderGroup' => $item['orderGroup'] ?? '',
  125. // 'status' => $item['status'] ?? false,
  126. // 'cargoList' => $item['cargoList'] ?? [],
  127. // 'tradeModeNameList' => $item['tradeModeNameList'] ?? [],
  128. // 'tradeModelList' => $item['tradeModelList'] ?? [],
  129. // 'payChannelInfos' => $item['payChannelInfos'] ?? [],
  130. // 'shopPromotionList' => $item['shopPromotionList'] ?? [],
  131. // 'tradeServiceList' => $item['tradeServiceList'] ?? [],
  132. // 'taoSampleSinglePromotion' => $item['taoSampleSinglePromotion'] ?? false,
  133. // 'canUseOfficialSolution' => $item['canUseOfficialSolution'] ?? false,
  134. ];
  135. }
  136. return [
  137. // 'success' => $success,
  138. 'preview' => $formattedList[0] ?? null,
  139. // 'list' => $formattedList,
  140. // 'unsupportedCrossBorderPayOfferList' => $result['unsupportedCrossBorderPayOfferList'] ?? [],
  141. ];
  142. }
  143. /**
  144. * 快速创建订单
  145. *
  146. * API: com.alibaba.trade:alibaba.trade.fastCreateOrder-1
  147. *
  148. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.fastCreateOrder-1
  149. *
  150. * 请求参数:
  151. * - flow: 流程标识(saleproxy=分销采购, general=普通下单)
  152. * - cargoParamList: 商品列表JSON数组(alibaba.trade.fast.cargo[]),每个商品包含 offerId, quantity, specId
  153. * - addressParam: 地址参数JSON对象,包含 fullName, mobile, provinceText, cityText, areaText, address
  154. * - totalAmount: 总金额(单位:分,可选)
  155. * - message: 买家留言(可选)
  156. * - orderGroup: 订单分组标识(从预下单返回的 orderGroup 字段获取,可选)
  157. *
  158. * @param array $params 创建订单参数
  159. * @return array|false
  160. */
  161. public function fastCreateOrder(array $params = [])
  162. {
  163. $businessParams = [];
  164. // --- flow(流程标识) ---
  165. $businessParams['flow'] = $params['flow'] ?? 'general';
  166. // --- cargoParamList(商品列表,JSON字符串) ---
  167. if (!empty($params['cargoParamList'])) {
  168. $businessParams['cargoParamList'] = is_array($params['cargoParamList'])
  169. ? json_encode($params['cargoParamList'], JSON_UNESCAPED_UNICODE)
  170. : $params['cargoParamList'];
  171. }
  172. // --- addressParam(地址参数,JSON字符串) ---
  173. if (!empty($params['addressParam'])) {
  174. $businessParams['addressParam'] = is_array($params['addressParam'])
  175. ? json_encode($params['addressParam'], JSON_UNESCAPED_UNICODE)
  176. : $params['addressParam'];
  177. }
  178. // --- 调用API ---
  179. $result = $this->executeParam2(
  180. self::NAMESPACE,
  181. 'alibaba.trade.fastCreateOrder',
  182. 1,
  183. $businessParams
  184. );
  185. if ($result === false) {
  186. Log::channel('alibaba')->error('[创建订单] API请求失败', $businessParams);
  187. return false;
  188. }
  189. Log::channel('alibaba')->info('[创建订单] API请求成功', ['result' => json_encode($result)]);
  190. return $this->formatCreateOrderResult($result);
  191. }
  192. /**
  193. * @param StoreOrderEntity[] $storeOrderEntityList
  194. * @return bool
  195. * @author 史晨
  196. * @date 2026/4/30 09:44
  197. * 支付成功通知1688
  198. */
  199. public function paySuccess(array $storeOrderEntityList): bool
  200. {
  201. foreach ($storeOrderEntityList as $storeOrderEntity) {
  202. $alibabaOrderId = $storeOrderEntity->getAlibabaOrderId();
  203. if (empty($alibabaOrderId)) {
  204. Log::channel('alibaba')->error($storeOrderEntity->getOrderSn() . '不是1688订单,不需要调取1688支付成功接口');
  205. continue;
  206. }
  207. $res = $this->prepareProtocolPay(['orderId' => $alibabaOrderId]);
  208. if ($res['success'] === false) {
  209. return false;
  210. }
  211. }
  212. return true;
  213. }
  214. /**
  215. * 免密支付准备
  216. *
  217. * API: com.alibaba.trade:alibaba.trade.pay.protocolPay.preparePay-1
  218. *
  219. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.pay.protocolPay.preparePay-1
  220. *
  221. * 请求参数说明(接口文档定义):
  222. * - tradeWithholdPreparePayParam: 必填,类型 message:alibaba.trade.pay.withhold.preparePayParam
  223. * preparePayParam 结构:
  224. * - orderId: 订单ID(Long,必填)
  225. *
  226. * 调用示例:
  227. * $orderService->prepareProtocolPay([
  228. * 'orderId' => 123456789,
  229. * ]);
  230. *
  231. * @param array $params 免密支付参数(orderId)
  232. * @return array|false
  233. */
  234. public function prepareProtocolPay(array $params = [])
  235. {
  236. // --- 参数校验 ---
  237. if (empty($params['orderId'])) {
  238. Log::channel('alibaba')->error('[免密支付] orderId不能为空');
  239. return false;
  240. }
  241. // --- 构建 tradeWithholdPreparePayParam(message类型需序列化为JSON字符串) ---
  242. $businessParams = [
  243. 'tradeWithholdPreparePayParam' => json_encode([
  244. 'orderId' => (int)$params['orderId'],
  245. ], JSON_UNESCAPED_UNICODE),
  246. ];
  247. // --- 调用API ---
  248. $result = $this->executeParam2(
  249. self::NAMESPACE,
  250. 'alibaba.trade.pay.protocolPay.preparePay',
  251. 1,
  252. $businessParams
  253. );
  254. if ($result === false) {
  255. Log::channel('alibaba')->error('[免密支付] API请求失败', $businessParams);
  256. return false;
  257. }
  258. Log::channel('alibaba')->info('[免密支付] API请求成功', ['result' => json_encode($result)]);
  259. return $this->formatProtocolPayResult($result);
  260. }
  261. /**
  262. * 格式化免密支付准备返回结果
  263. *
  264. * @param array $result API原始返回数据
  265. * @return array
  266. */
  267. protected function formatProtocolPayResult(array $result): array
  268. {
  269. // 返回结构: { "result": { ... }, "success": true }
  270. $data = $result['result'] ?? $result;
  271. $success = $result['success'] ?? false;
  272. Log::channel('alibaba')->info('[免密支付] 返回结果', [
  273. 'success' => $success,
  274. 'data' => $data,
  275. ]);
  276. return [
  277. 'success' => $success,
  278. 'data' => $data,
  279. 'rawData' => $result,
  280. ];
  281. }
  282. /**
  283. * 格式化创建订单返回结果
  284. *
  285. * @param array $result API原始返回数据
  286. * @return array
  287. */
  288. protected function formatCreateOrderResult(array $result): array
  289. {
  290. // 创建订单成功返回: { "result": { "orderId": "123456", "totalAmount": 370 }, "success": true }
  291. $data = $result['result'] ?? $result;
  292. $success = $result['success'] ?? false;
  293. return [
  294. 'success' => $success,
  295. 'orderId' => $data['orderId'] ?? 0,
  296. 'totalAmount' => $data['totalAmount'] ?? 0,
  297. 'rawData' => $data,
  298. ];
  299. }
  300. /**
  301. * 构建商品列表参数(便捷方法)
  302. *
  303. * @param array $items 商品列表 [['offerId' => 123, 'quantity' => 2, 'skuId' => 456], ...]
  304. * @return array
  305. */
  306. public function buildProductList(array $items): array
  307. {
  308. $productList = [];
  309. foreach ($items as $item) {
  310. $product = [
  311. 'offerId' => (int)($item['offerId'] ?? $item['productId'] ?? 0),
  312. 'quantity' => (int)($item['quantity'] ?? 1),
  313. ];
  314. if (!empty($item['skuId'])) {
  315. $product['skuId'] = (int)$item['skuId'];
  316. }
  317. if (!empty($item['price'])) {
  318. $product['price'] = (int)$item['price']; // 单位:分
  319. }
  320. if (!empty($item['productUnit'])) {
  321. $product['productUnit'] = $item['productUnit'];
  322. }
  323. $productList[] = $product;
  324. }
  325. return $productList;
  326. }
  327. /**
  328. * 构建地址参数(便捷方法)
  329. *
  330. * @param int $addressId 地址ID
  331. * @return array
  332. */
  333. public function buildAddressParam(int $addressId): array
  334. {
  335. return [
  336. 'addressId' => $addressId,
  337. ];
  338. }
  339. /**
  340. * 构建完整地址参数(使用系统地址数据格式)
  341. *
  342. * 适配系统 user_address 表字段:
  343. * address_id, real_name, phone, province, city, district, detail, post_code, code_list
  344. *
  345. * @param array $address 地址信息(系统user_address表数据)
  346. * @return array
  347. */
  348. public function buildFullAddressParam(array $address): array
  349. {
  350. // 解析地区编码列表: "1,67449,35,493"
  351. $codeList = $address['code_list'] ?? '';
  352. $codes = explode(',', $codeList);
  353. $districtCode = $codes[1] ?? $codes[0] ?? '';
  354. return [
  355. 'fullName' => $address['real_name'] ?? $address['fullName'] ?? '',
  356. 'mobile' => $address['phone'] ?? $address['mobile'] ?? '',
  357. 'phone' => $address['phone'] ?? $address['phone'] ?? '',
  358. 'postCode' => $address['post_code'] ?? $address['postCode'] ?? '',
  359. 'cityText' => $address['city'] ?? $address['cityText'] ?? '',
  360. 'provinceText' => $address['province'] ?? $address['provinceText'] ?? '',
  361. 'areaText' => $address['district'] ?? $address['areaText'] ?? '',
  362. 'townText' => $address['village'] ?? $address['townText'] ?? '',
  363. 'address' => $address['detail'] ?? $address['address'] ?? '',
  364. 'districtCode' => $districtCode,
  365. ];
  366. }
  367. /**
  368. * 获取交易订单的物流信息(买家视角)
  369. *
  370. * API: com.alibaba.trade:alibaba.trade.get.logisticsInfos.buyerView-1
  371. *
  372. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.get.logisticsInfos.buyerView-1
  373. *
  374. * 请求参数:
  375. * - orderId: 1688订单ID(必填)
  376. * - webSite: 1688站点(可选,默认"1688")
  377. *
  378. * 返回示例:
  379. * {
  380. * "result": {
  381. * "logisticsItems": [{
  382. * "logisticsId": "LP001",
  383. * "logisticsCode": "SF1234567890", // 物流单号
  384. * "logisticsName": "顺丰速运", // 物流公司名称
  385. * "logisticsCompany": "shunfeng", // 物流公司编码
  386. * "status": "accept", // 物流状态
  387. * "gmtCreate": "2018-05-30 19:34:27"
  388. * }]
  389. * },
  390. * "success": true
  391. * }
  392. *
  393. * @param array $params 请求参数(orderId 必填)
  394. * @return array|false
  395. */
  396. public function getLogisticsInfosBuyerView(array $params = [])
  397. {
  398. // --- 参数校验 ---
  399. if (empty($params['orderId'])) {
  400. Log::channel('alibaba')->error('[获取物流信息] orderId不能为空');
  401. return false;
  402. }
  403. $businessParams = [
  404. 'orderId' => (string)$params['orderId'],
  405. 'webSite' => $params['webSite'] ?? '1688',
  406. ];
  407. // --- 调用API ---
  408. $result = $this->executeParam2(
  409. self::LOGISTICS_NAMESPACE,
  410. 'alibaba.trade.getLogisticsInfos.buyerView',
  411. 1,
  412. $businessParams
  413. );
  414. if ($result === false) {
  415. Log::channel('alibaba')->error('[获取物流信息] API请求失败, params:' . json_encode($businessParams, JSON_UNESCAPED_UNICODE));
  416. return false;
  417. }
  418. Log::channel('alibaba')->info('[获取物流信息] API请求成功, result:' . json_encode($result, JSON_UNESCAPED_UNICODE));
  419. return $this->formatLogisticsInfosResult($result);
  420. }
  421. /**
  422. * 格式化物流信息返回结果
  423. *
  424. * 实际返回数据结构(result为数组):
  425. * {
  426. * "result": [{
  427. * "logisticsId": "LP00815917141701",
  428. * "logisticsBillNo": "YT7619307479079", // 物流单号
  429. * "logisticsCompanyName": "圆通速递(YTO)", // 物流公司名称
  430. * "logisticsCompanyNo": "YTO", // 物流公司编码
  431. * "status": "SIGN",
  432. * ...
  433. * }],
  434. * "success": true
  435. * }
  436. *
  437. * @param array $result API原始返回数据
  438. * @return array
  439. */
  440. protected function formatLogisticsInfosResult(array $result): array
  441. {
  442. $success = $result['success'] ?? false;
  443. $logisticsItems = [];
  444. // result 是数组,每个元素是一条物流信息
  445. if (isset($result['result']) && is_array($result['result'])) {
  446. foreach ($result['result'] as $item) {
  447. $logisticsItems[] = [
  448. 'logisticsId' => $item['logisticsId'] ?? '',
  449. 'logisticsBillNo' => $item['logisticsBillNo'] ?? '', // 物流单号
  450. 'logisticsName' => $item['logisticsCompanyName'] ?? '', // 物流公司名称
  451. 'logisticsCompany' => $item['logisticsCompanyNo'] ?? '', // 物流公司编码
  452. 'status' => $item['status'] ?? '',
  453. 'orderEntryIds' => $item['orderEntryIds'] ?? '',
  454. ];
  455. }
  456. }
  457. return [
  458. 'success' => $success,
  459. 'logisticsItems' => $logisticsItems,
  460. 'rawData' => $result,
  461. ];
  462. }
  463. /**
  464. * 查询退款退货原因列表
  465. *
  466. * API: com.alibaba.trade:alibaba.trade.getRefundReasonList-1
  467. *
  468. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.getRefundReasonList-1
  469. *
  470. * 请求参数:
  471. * - orderId: 订单号(必填)
  472. * - orderEntryIds: 子订单号(可选,多个用逗号分隔)
  473. *
  474. * 返回示例:
  475. * {
  476. * "result": [
  477. * {
  478. * "refundId": "REFUND123456",
  479. * "refundType": "退货退款",
  480. * "refundStatus": "waitSellerAgree",
  481. * "refundReasonList": [
  482. * {
  483. * "reasonId": 1,
  484. * "reasonText": "质量问题"
  485. * }
  486. * ]
  487. * }
  488. * ],
  489. * "success": true
  490. * }
  491. *
  492. * @param array $params 请求参数(orderId 必填)
  493. * @return array|false
  494. */
  495. public function getRefundReasonList(array $params = [])
  496. {
  497. // --- 参数校验 ---
  498. if (empty($params['orderId'])) {
  499. Log::channel('alibaba')->error('[获取退款原因] orderId不能为空');
  500. return false;
  501. }
  502. $businessParams = [
  503. 'orderId' => (string)$params['orderId'],
  504. ];
  505. // 可选参数:子订单号
  506. if (!empty($params['orderEntryIds'])) {
  507. $businessParams['orderEntryIds'] = (string)$params['orderEntryIds'];
  508. }
  509. // --- 调用API ---
  510. $result = $this->executeParam2(
  511. self::NAMESPACE,
  512. 'alibaba.trade.getRefundReasonList',
  513. 1,
  514. $businessParams
  515. );
  516. if ($result === false) {
  517. Log::channel('alibaba')->error('[获取退款原因] API请求失败, params:' . json_encode($businessParams, JSON_UNESCAPED_UNICODE));
  518. return false;
  519. }
  520. Log::channel('alibaba')->info('[获取退款原因] API请求成功, result:' . json_encode($result, JSON_UNESCAPED_UNICODE));
  521. return $this->formatRefundReasonListResult($result);
  522. }
  523. /**
  524. * 格式化退款退货原因列表返回结果
  525. *
  526. * @param array $result API原始返回数据
  527. * @return array
  528. */
  529. protected function formatRefundReasonListResult(array $result): array
  530. {
  531. $success = $result['success'] ?? false;
  532. $refundList = [];
  533. if (isset($result['result']) && is_array($result['result'])) {
  534. foreach ($result['result'] as $item) {
  535. $reasons = [];
  536. if (isset($item['refundReasonList']) && is_array($item['refundReasonList'])) {
  537. foreach ($item['refundReasonList'] as $reason) {
  538. $reasons[] = [
  539. 'reasonId' => $reason['reasonId'] ?? '',
  540. 'reasonText' => $reason['reasonText'] ?? '',
  541. ];
  542. }
  543. }
  544. $refundList[] = [
  545. 'refundId' => $item['refundId'] ?? '',
  546. 'refundType' => $item['refundType'] ?? '',
  547. 'refundStatus' => $item['refundStatus'] ?? '',
  548. 'refundReasonList' => $reasons,
  549. ];
  550. }
  551. }
  552. return [
  553. 'success' => $success,
  554. 'refundList' => $refundList,
  555. 'rawData' => $result,
  556. ];
  557. }
  558. }