OrderService.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  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. const REFUND_GOOD_STATUS = [
  26. // 售中等待卖家发货
  27. 'waitsellersend' => 'refundWaitSellerSend',
  28. // 售中等待买家收货
  29. 'waitbuyerreceive'=>'refundWaitBuyerReceive',
  30. // // 售中已收货(未确认完成交易)
  31. // 'BuyerReceived'=>'refundBuyerReceived',
  32. // // 售后未收货
  33. // 'saleBuyerNotReceived'=>'aftersaleBuyerNotReceived',
  34. // // 售后已收到货
  35. // 'saleBuyerReceived'=>'aftersaleBuyerReceived',
  36. ];
  37. /**
  38. * 预下单
  39. *
  40. * API: com.alibaba.trade:alibaba.createOrder.preview-1
  41. *
  42. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.createOrder.preview-1
  43. *
  44. * 请求参数格式:
  45. * - flow: 流程标识(saleproxy=分销采购, general=普通下单)
  46. * - productList: 商品列表JSON数组,每个商品包含 offerId, quantity, specId
  47. * - addressParam: 地址参数JSON对象,包含 fullName, mobile, provinceText, cityText, areaText, address
  48. * - totalAmount: 总金额(单位:分,可选)
  49. * - message: 买家留言(可选)
  50. *
  51. * @param array $params 预下单参数
  52. * @return array|false
  53. */
  54. public function previewOrder(array $params = [])
  55. {
  56. $businessParams = [];
  57. // --- productList(商品列表,JSON字符串) ---
  58. // 直接传入已格式化的数组,由方法转为JSON
  59. if (!empty($params['cargoParamList'])) {
  60. $businessParams['cargoParamList'] = is_array($params['cargoParamList'])
  61. ? json_encode($params['cargoParamList'], JSON_UNESCAPED_UNICODE)
  62. : $params['cargoParamList'];
  63. }
  64. // --- addressParam(地址参数,JSON字符串) ---
  65. // 直接传入已格式化的数组,由方法转为JSON
  66. if (!empty($params['addressParam'])) {
  67. $businessParams['addressParam'] = is_array($params['addressParam'])
  68. ? json_encode($params['addressParam'], JSON_UNESCAPED_UNICODE)
  69. : $params['addressParam'];
  70. }
  71. // --- 调用API ---
  72. $result = $this->executeParam2(
  73. self::NAMESPACE,
  74. 'alibaba.createOrder.preview',
  75. 1,
  76. $businessParams
  77. );
  78. if ($result === false) {
  79. Log::channel('alibaba')->error('[预下单] API请求失败', $businessParams);
  80. return false;
  81. }
  82. Log::channel('alibaba')->info('[预下单] API请求成功', ['result' => json_encode($result)]);
  83. return $this->formatPreviewResult($result);
  84. }
  85. /**
  86. * 格式化预下单返回结果
  87. *
  88. * 实际返回结构:
  89. * {
  90. * "orderPreviewResuslt": [{
  91. * "tradeModeNameList": ["assureTrade"],
  92. * "status": true,
  93. * "sumPayment": 370, // 总支付金额(单位:分)
  94. * "sumCarriage": 300, // 总运费(单位:分)
  95. * "sumPaymentNoCarriage": 70, // 商品总金额(不含运费,单位:分)
  96. * "flowFlag": "fenxiaonew",
  97. * "cargoList": [{ // 商品列表
  98. * "amount": 0.7,
  99. * "finalUnitPrice": 0.7,
  100. * "specId": "...",
  101. * "skuId": 6148579878582,
  102. * "offerId": 678279922176,
  103. * "openOfferId": "...",
  104. * "cargoPromotionList": []
  105. * }],
  106. * "tradeModelList": [{"name":"担保交易","tradeType":"assureTrade","opSupport":true}],
  107. * "payChannelInfos": [{"name":"alipay"}],
  108. * "orderGroup": "79d4f82d...",
  109. * "canUseOfficialSolution": false
  110. * }],
  111. * "success": true,
  112. * "unsupportedCrossBorderPayOfferList": []
  113. * }
  114. *
  115. * @param array $result API原始返回数据
  116. * @return array
  117. */
  118. protected function formatPreviewResult(array $result): array
  119. {
  120. $success = $result['success'] ?? false;
  121. $previewList = $result['orderPreviewResuslt'] ?? [];
  122. if (empty($previewList) || !is_array($previewList)) {
  123. return [
  124. 'success' => $success,
  125. 'preview' => null,
  126. 'list' => [],
  127. ];
  128. }
  129. $formattedList = [];
  130. foreach ($previewList as $item) {
  131. $formattedList[] = [
  132. 'sumPayment' => $item['sumPayment'] ?? 0,
  133. 'sumCarriage' => $item['sumCarriage'] ?? 0,
  134. 'sumPaymentNoCarriage' => $item['sumPaymentNoCarriage'] ?? 0,
  135. 'flowFlag' => $item['flowFlag'] ?? '',
  136. // 'orderGroup' => $item['orderGroup'] ?? '',
  137. // 'status' => $item['status'] ?? false,
  138. // 'cargoList' => $item['cargoList'] ?? [],
  139. // 'tradeModeNameList' => $item['tradeModeNameList'] ?? [],
  140. // 'tradeModelList' => $item['tradeModelList'] ?? [],
  141. // 'payChannelInfos' => $item['payChannelInfos'] ?? [],
  142. // 'shopPromotionList' => $item['shopPromotionList'] ?? [],
  143. // 'tradeServiceList' => $item['tradeServiceList'] ?? [],
  144. // 'taoSampleSinglePromotion' => $item['taoSampleSinglePromotion'] ?? false,
  145. // 'canUseOfficialSolution' => $item['canUseOfficialSolution'] ?? false,
  146. ];
  147. }
  148. return [
  149. // 'success' => $success,
  150. 'preview' => $formattedList[0] ?? null,
  151. // 'list' => $formattedList,
  152. // 'unsupportedCrossBorderPayOfferList' => $result['unsupportedCrossBorderPayOfferList'] ?? [],
  153. ];
  154. }
  155. /**
  156. * 快速创建订单
  157. *
  158. * API: com.alibaba.trade:alibaba.trade.fastCreateOrder-1
  159. *
  160. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.fastCreateOrder-1
  161. *
  162. * 请求参数:
  163. * - flow: 流程标识(saleproxy=分销采购, general=普通下单)
  164. * - cargoParamList: 商品列表JSON数组(alibaba.trade.fast.cargo[]),每个商品包含 offerId, quantity, specId
  165. * - addressParam: 地址参数JSON对象,包含 fullName, mobile, provinceText, cityText, areaText, address
  166. * - totalAmount: 总金额(单位:分,可选)
  167. * - message: 买家留言(可选)
  168. * - orderGroup: 订单分组标识(从预下单返回的 orderGroup 字段获取,可选)
  169. *
  170. * @param array $params 创建订单参数
  171. * @return array|false
  172. */
  173. public function fastCreateOrder(array $params = [])
  174. {
  175. $businessParams = [];
  176. // --- flow(流程标识) ---
  177. $businessParams['flow'] = $params['flow'] ?? 'general';
  178. // --- cargoParamList(商品列表,JSON字符串) ---
  179. if (!empty($params['cargoParamList'])) {
  180. $businessParams['cargoParamList'] = is_array($params['cargoParamList'])
  181. ? json_encode($params['cargoParamList'], JSON_UNESCAPED_UNICODE)
  182. : $params['cargoParamList'];
  183. }
  184. // --- addressParam(地址参数,JSON字符串) ---
  185. if (!empty($params['addressParam'])) {
  186. $businessParams['addressParam'] = is_array($params['addressParam'])
  187. ? json_encode($params['addressParam'], JSON_UNESCAPED_UNICODE)
  188. : $params['addressParam'];
  189. }
  190. // --- 调用API ---
  191. $result = $this->executeParam2(
  192. self::NAMESPACE,
  193. 'alibaba.trade.fastCreateOrder',
  194. 1,
  195. $businessParams
  196. );
  197. if ($result === false) {
  198. Log::channel('alibaba')->error('[创建订单] API请求失败', $businessParams);
  199. return false;
  200. }
  201. Log::channel('alibaba')->info('[创建订单] API请求成功', ['result' => json_encode($result)]);
  202. return $this->formatCreateOrderResult($result);
  203. }
  204. /**
  205. * @param StoreOrderEntity[] $storeOrderEntityList
  206. * @return bool
  207. * @author 史晨
  208. * @date 2026/4/30 09:44
  209. * 支付成功通知1688
  210. */
  211. public function paySuccess(array $storeOrderEntityList): bool
  212. {
  213. foreach ($storeOrderEntityList as $storeOrderEntity) {
  214. $alibabaOrderId = $storeOrderEntity->getAlibabaOrderId();
  215. if (empty($alibabaOrderId)) {
  216. Log::channel('alibaba')->error($storeOrderEntity->getOrderSn() . '不是1688订单,不需要调取1688支付成功接口');
  217. continue;
  218. }
  219. $res = $this->prepareProtocolPay(['orderId' => $alibabaOrderId]);
  220. if ($res['success'] === false) {
  221. return false;
  222. }
  223. }
  224. return true;
  225. }
  226. /**
  227. * 免密支付准备
  228. *
  229. * API: com.alibaba.trade:alibaba.trade.pay.protocolPay.preparePay-1
  230. *
  231. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.pay.protocolPay.preparePay-1
  232. *
  233. * 请求参数说明(接口文档定义):
  234. * - tradeWithholdPreparePayParam: 必填,类型 message:alibaba.trade.pay.withhold.preparePayParam
  235. * preparePayParam 结构:
  236. * - orderId: 订单ID(Long,必填)
  237. *
  238. * 调用示例:
  239. * $orderService->prepareProtocolPay([
  240. * 'orderId' => 123456789,
  241. * ]);
  242. *
  243. * @param array $params 免密支付参数(orderId)
  244. * @return array|false
  245. */
  246. public function prepareProtocolPay(array $params = [])
  247. {
  248. // --- 参数校验 ---
  249. if (empty($params['orderId'])) {
  250. Log::channel('alibaba')->error('[免密支付] orderId不能为空');
  251. return false;
  252. }
  253. // --- 构建 tradeWithholdPreparePayParam(message类型需序列化为JSON字符串) ---
  254. $businessParams = [
  255. 'tradeWithholdPreparePayParam' => json_encode([
  256. 'orderId' => (int)$params['orderId'],
  257. ], JSON_UNESCAPED_UNICODE),
  258. ];
  259. // --- 调用API ---
  260. $result = $this->executeParam2(
  261. self::NAMESPACE,
  262. 'alibaba.trade.pay.protocolPay.preparePay',
  263. 1,
  264. $businessParams
  265. );
  266. if ($result === false) {
  267. Log::channel('alibaba')->error('[免密支付] API请求失败', $businessParams);
  268. return false;
  269. }
  270. Log::channel('alibaba')->info('[免密支付] API请求成功', ['result' => json_encode($result)]);
  271. return $this->formatProtocolPayResult($result);
  272. }
  273. /**
  274. * 格式化免密支付准备返回结果
  275. *
  276. * @param array $result API原始返回数据
  277. * @return array
  278. */
  279. protected function formatProtocolPayResult(array $result): array
  280. {
  281. // 返回结构: { "result": { ... }, "success": true }
  282. $data = $result['result'] ?? $result;
  283. $success = $result['success'] ?? false;
  284. Log::channel('alibaba')->info('[免密支付] 返回结果', [
  285. 'success' => $success,
  286. 'data' => $data,
  287. ]);
  288. return [
  289. 'success' => $success,
  290. 'data' => $data,
  291. 'rawData' => $result,
  292. ];
  293. }
  294. /**
  295. * 格式化创建订单返回结果
  296. *
  297. * @param array $result API原始返回数据
  298. * @return array
  299. */
  300. protected function formatCreateOrderResult(array $result): array
  301. {
  302. // 创建订单成功返回: { "result": { "orderId": "123456", "totalAmount": 370 }, "success": true }
  303. $data = $result['result'] ?? $result;
  304. $success = $result['success'] ?? false;
  305. return [
  306. 'success' => $success,
  307. 'orderId' => $data['orderId'] ?? 0,
  308. 'totalAmount' => $data['totalAmount'] ?? 0,
  309. 'rawData' => $data,
  310. ];
  311. }
  312. /**
  313. * 构建商品列表参数(便捷方法)
  314. *
  315. * @param array $items 商品列表 [['offerId' => 123, 'quantity' => 2, 'skuId' => 456], ...]
  316. * @return array
  317. */
  318. public function buildProductList(array $items): array
  319. {
  320. $productList = [];
  321. foreach ($items as $item) {
  322. $product = [
  323. 'offerId' => (int)($item['offerId'] ?? $item['productId'] ?? 0),
  324. 'quantity' => (int)($item['quantity'] ?? 1),
  325. ];
  326. if (!empty($item['skuId'])) {
  327. $product['skuId'] = (int)$item['skuId'];
  328. }
  329. if (!empty($item['price'])) {
  330. $product['price'] = (int)$item['price']; // 单位:分
  331. }
  332. if (!empty($item['productUnit'])) {
  333. $product['productUnit'] = $item['productUnit'];
  334. }
  335. $productList[] = $product;
  336. }
  337. return $productList;
  338. }
  339. /**
  340. * 构建地址参数(便捷方法)
  341. *
  342. * @param int $addressId 地址ID
  343. * @return array
  344. */
  345. public function buildAddressParam(int $addressId): array
  346. {
  347. return [
  348. 'addressId' => $addressId,
  349. ];
  350. }
  351. /**
  352. * 构建完整地址参数(使用系统地址数据格式)
  353. *
  354. * 适配系统 user_address 表字段:
  355. * address_id, real_name, phone, province, city, district, detail, post_code, code_list
  356. *
  357. * @param array $address 地址信息(系统user_address表数据)
  358. * @return array
  359. */
  360. public function buildFullAddressParam(array $address): array
  361. {
  362. // 解析地区编码列表: "1,67449,35,493"
  363. $codeList = $address['code_list'] ?? '';
  364. $codes = explode(',', $codeList);
  365. $districtCode = $codes[1] ?? $codes[0] ?? '';
  366. return [
  367. 'fullName' => $address['real_name'] ?? $address['fullName'] ?? '',
  368. 'mobile' => $address['phone'] ?? $address['mobile'] ?? '',
  369. 'phone' => $address['phone'] ?? $address['phone'] ?? '',
  370. 'postCode' => $address['post_code'] ?? $address['postCode'] ?? '',
  371. 'cityText' => $address['city'] ?? $address['cityText'] ?? '',
  372. 'provinceText' => $address['province'] ?? $address['provinceText'] ?? '',
  373. 'areaText' => $address['district'] ?? $address['areaText'] ?? '',
  374. 'townText' => $address['village'] ?? $address['townText'] ?? '',
  375. 'address' => $address['detail'] ?? $address['address'] ?? '',
  376. 'districtCode' => $districtCode,
  377. ];
  378. }
  379. /**
  380. * 获取交易订单的物流信息(买家视角)
  381. *
  382. * API: com.alibaba.trade:alibaba.trade.get.logisticsInfos.buyerView-1
  383. *
  384. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.get.logisticsInfos.buyerView-1
  385. *
  386. * 请求参数:
  387. * - orderId: 1688订单ID(必填)
  388. * - webSite: 1688站点(可选,默认"1688")
  389. *
  390. * 返回示例:
  391. * {
  392. * "result": {
  393. * "logisticsItems": [{
  394. * "logisticsId": "LP001",
  395. * "logisticsCode": "SF1234567890", // 物流单号
  396. * "logisticsName": "顺丰速运", // 物流公司名称
  397. * "logisticsCompany": "shunfeng", // 物流公司编码
  398. * "status": "accept", // 物流状态
  399. * "gmtCreate": "2018-05-30 19:34:27"
  400. * }]
  401. * },
  402. * "success": true
  403. * }
  404. *
  405. * @param array $params 请求参数(orderId 必填)
  406. * @return array|false
  407. */
  408. public function getLogisticsInfosBuyerView(array $params = [])
  409. {
  410. // --- 参数校验 ---
  411. if (empty($params['orderId'])) {
  412. Log::channel('alibaba')->error('[获取物流信息] orderId不能为空');
  413. return false;
  414. }
  415. $businessParams = [
  416. 'orderId' => (string)$params['orderId'],
  417. 'webSite' => $params['webSite'] ?? '1688',
  418. ];
  419. // --- 调用API ---
  420. $result = $this->executeParam2(
  421. self::LOGISTICS_NAMESPACE,
  422. 'alibaba.trade.getLogisticsInfos.buyerView',
  423. 1,
  424. $businessParams
  425. );
  426. if ($result === false) {
  427. Log::channel('alibaba')->error('[获取物流信息] API请求失败, params:' . json_encode($businessParams, JSON_UNESCAPED_UNICODE));
  428. return false;
  429. }
  430. Log::channel('alibaba')->info('[获取物流信息] API请求成功, result:' . json_encode($result, JSON_UNESCAPED_UNICODE));
  431. return $this->formatLogisticsInfosResult($result);
  432. }
  433. /**
  434. * 格式化物流信息返回结果
  435. *
  436. * 实际返回数据结构(result为数组):
  437. * {
  438. * "result": [{
  439. * "logisticsId": "LP00815917141701",
  440. * "logisticsBillNo": "YT7619307479079", // 物流单号
  441. * "logisticsCompanyName": "圆通速递(YTO)", // 物流公司名称
  442. * "logisticsCompanyNo": "YTO", // 物流公司编码
  443. * "status": "SIGN",
  444. * ...
  445. * }],
  446. * "success": true
  447. * }
  448. *
  449. * @param array $result API原始返回数据
  450. * @return array
  451. */
  452. protected function formatLogisticsInfosResult(array $result): array
  453. {
  454. $success = $result['success'] ?? false;
  455. $logisticsItems = [];
  456. // result 是数组,每个元素是一条物流信息
  457. if (isset($result['result']) && is_array($result['result'])) {
  458. foreach ($result['result'] as $item) {
  459. $logisticsItems[] = [
  460. 'logisticsId' => $item['logisticsId'] ?? '',
  461. 'logisticsBillNo' => $item['logisticsBillNo'] ?? '', // 物流单号
  462. 'logisticsName' => $item['logisticsCompanyName'] ?? '', // 物流公司名称
  463. 'logisticsCompany' => $item['logisticsCompanyNo'] ?? '', // 物流公司编码
  464. 'status' => $item['status'] ?? '',
  465. 'orderEntryIds' => $item['orderEntryIds'] ?? '',
  466. ];
  467. }
  468. }
  469. return [
  470. 'success' => $success,
  471. 'logisticsItems' => $logisticsItems,
  472. 'rawData' => $result,
  473. ];
  474. }
  475. /**
  476. * 获取订单详情(买家视角)
  477. *
  478. * API: com.alibaba.trade:alibaba.trade.get.buyerView-1
  479. *
  480. * @param string $orderId 1688订单号
  481. * @return array|false
  482. */
  483. public function getOrderDetailBuyerView(string $orderId)
  484. {
  485. if (empty($orderId)) {
  486. Log::channel('alibaba')->error('[获取订单详情] orderId不能为空');
  487. return false;
  488. }
  489. $businessParams = [
  490. 'orderId' => $orderId,
  491. 'webSite' => 1688,
  492. ];
  493. $result = $this->executeParam2(
  494. self::NAMESPACE,
  495. 'alibaba.trade.get.buyerView',
  496. 1,
  497. $businessParams
  498. );
  499. if ($result === false) {
  500. Log::channel('alibaba')->error('[获取订单详情] API请求失败, orderId:' . $orderId);
  501. return false;
  502. }
  503. Log::channel('alibaba')->info('[获取订单详情] API请求成功, orderId:' . $orderId . ', result:' . json_encode($result, JSON_UNESCAPED_UNICODE));
  504. return $result;
  505. }
  506. /**
  507. * 查询退款退货原因列表
  508. *
  509. * 流程:
  510. * 1. 先调用 alibaba.trade.get.buyerView 获取订单详情
  511. * 2. 从订单详情中提取子订单号(orderEntryIds)、货物状态、可退款金额、可退运费
  512. * 3. 再调用 alibaba.trade.getRefundReasonList 获取退款原因
  513. *
  514. * API: com.alibaba.trade:alibaba.trade.getRefundReasonList-1
  515. *
  516. * @param array $params 请求参数(orderId 必填)
  517. * @return array|false
  518. */
  519. public function getRefundReasonList(array $params = [])
  520. {
  521. // --- 参数校验 ---
  522. if (empty($params['orderId'])) {
  523. Log::channel('alibaba')->error('[获取退款原因] orderId不能为空');
  524. return false;
  525. }
  526. $orderId = (string)$params['orderId'];
  527. // 1. 先获取订单详情,提取子订单号和货物状态
  528. $orderDetail = $this->getOrderDetailBuyerView($orderId);
  529. if ($orderDetail === false) {
  530. Log::channel('alibaba')->error('[获取退款原因] 获取订单详情失败, orderId:' . $orderId);
  531. return false;
  532. }
  533. // 从订单详情中提取子订单ID列表
  534. $orderEntryIds = $orderDetail['result']['productItems'][0]['subItemID'] ?: '';
  535. $goodsStatus = self::REFUND_GOOD_STATUS[$orderDetail['result']['productItems'][0]['status']] ?: '';
  536. if (!$goodsStatus) {
  537. Log::channel('alibaba')->error('[获取退款原因] 获取货物状态失败, orderId:' . $orderId);
  538. return false;
  539. }
  540. // 提取可退款金额和可退运费(从订单详情中)
  541. // totalSuccessAmount: 实付款(分),carriage: 运费(分)
  542. $applyPayment = (string)$orderDetail['result']['baseInfo']['totalAmount'] ?? 0;
  543. $applyCarriage = (string)$orderDetail['result']['baseInfo']['shippingFee'] ?? 0;
  544. // 2. 调用退款原因查询API
  545. $businessParams = [
  546. 'orderId' => $orderId,
  547. 'orderEntryIds' => '[' . $orderEntryIds . ']',
  548. 'goodsStatus' => $goodsStatus,
  549. ];
  550. Log::channel('alibaba')->info('[获取退款原因] 准备请求, orderId:' . $orderId . ', orderEntryIds:' . json_encode($orderEntryIds) . ', goodsStatus:' . $goodsStatus);
  551. // --- 调用API ---
  552. $result = $this->executeParam2(
  553. self::NAMESPACE,
  554. 'alibaba.trade.getRefundReasonList',
  555. 1,
  556. $businessParams
  557. );
  558. if ($result === false) {
  559. Log::channel('alibaba')->error('[获取退款原因] API请求失败, params:' . json_encode($businessParams, JSON_UNESCAPED_UNICODE));
  560. return false;
  561. }
  562. Log::channel('alibaba')->info('[获取退款原因] API请求成功, result:' . json_encode($result, JSON_UNESCAPED_UNICODE));
  563. return $this->formatRefundReasonListResult($result, $orderId, $orderEntryIds, $goodsStatus, $applyPayment, $applyCarriage);
  564. }
  565. /**
  566. * 格式化退款退货原因列表返回结果
  567. *
  568. * 实际返回数据结构:
  569. * {
  570. * "result": {
  571. * "result": {
  572. * "reasons": [
  573. * {"id": 20006, "name": "不想买了", "needVoucher": false, "noRefundCarriage": false, "tip": ""}
  574. * ]
  575. * },
  576. * "success": true
  577. * }
  578. * }
  579. *
  580. * @param array $result API原始返回数据
  581. * @param string $orderId 1688订单号
  582. * @param string $orderEntryIds 子订单ID列表
  583. * @param string $goodsStatus 货物状态
  584. * @param int $applyPayment 可退款金额(分)
  585. * @param int $applyCarriage 可退运费(分)
  586. * @return array
  587. */
  588. protected function formatRefundReasonListResult(array $result, string $orderId = '', string $orderEntryIds = '', string $goodsStatus = '', string $applyPayment = '0', string $applyCarriage = '0'): array
  589. {
  590. $reasons = [];
  591. if (isset($result['result']['result']['reasons']) && is_array($result['result']['result']['reasons'])) {
  592. foreach ($result['result']['result']['reasons'] as $reason) {
  593. $reasons[] = [
  594. 'id' => $reason['id'] ?? '',
  595. 'name' => $reason['name'] ?? '',
  596. ];
  597. }
  598. }
  599. return [
  600. 'orderId' => $orderId,
  601. 'orderEntryIds' => $orderEntryIds,
  602. 'goodsStatus' => $goodsStatus,
  603. 'applyPayment' => $applyPayment,
  604. 'applyCarriage' => $applyCarriage,
  605. 'reasons' => $reasons,
  606. ];
  607. }
  608. /**
  609. * 提交售后申请
  610. *
  611. * API: com.alibaba.trade:alibaba.trade.createRefund-1
  612. *
  613. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.createRefund-1
  614. *
  615. * 请求参数:
  616. * - orderId: 主订单ID(必填)
  617. * - orderEntryIds: 子订单ID列表,格式 "[123,456]"(必填)
  618. * - disputeReasonId: 退款原因ID(必填,从 getRefundReasonList 获取)
  619. * - description: 退款说明(必填)
  620. * - goodsStatus: 货物状态(必填)
  621. * - applyPayment: 退款金额,单位分(必填)
  622. * - applyCarriage: 退运费,单位分(可选,默认0)
  623. * - voucher: 凭证图片列表,格式 "[{url:'http://...'}]"(可选)
  624. * - applyReason: 退款原因描述(可选)
  625. *
  626. * 返回示例:
  627. * {
  628. * "result": {
  629. * "result": {
  630. * "refundId": "RF123456",
  631. * "status": "refundWaitSellerSend"
  632. * },
  633. * "success": true
  634. * }
  635. * }
  636. *
  637. * @param array $params 请求参数
  638. * @return array|false
  639. */
  640. public function createRefund(array $params = [])
  641. {
  642. // --- 参数校验 ---
  643. $requiredFields = ['orderId', 'orderEntryIds', 'disputeReasonId', 'description', 'goodsStatus', 'applyPayment', 'applyCarriage', 'applyReasonId'];
  644. foreach ($requiredFields as $field) {
  645. if (!isset($params[$field])) {
  646. Log::channel('alibaba')->error('[提交售后申请] 缺少必填参数: ' . $field);
  647. return [
  648. 'success' => false,
  649. 'refundId' => '',
  650. 'status' => 'param_missing',
  651. 'rawData' => ['error' => '缺少必填参数: ' . $field],
  652. ];
  653. }
  654. }
  655. $businessParams = [
  656. 'orderId' => (string)$params['orderId'],
  657. 'orderEntryIds' => $params['orderEntryIds'],
  658. 'disputeRequest' => (string)$params['disputeReasonId'],
  659. 'applyPayment' => (string)$params['applyPayment'],
  660. 'applyCarriage' => (string)$params['applyCarriage'],
  661. 'applyReasonId' => (string)$params['applyReasonId'],
  662. 'description' => (string)$params['description'],
  663. 'goodsStatus' => (string)$params['goodsStatus'],
  664. ];
  665. // 可选参数
  666. if (!empty($params['voucher'])) {
  667. $businessParams['voucher'] = $params['voucher'];
  668. }
  669. Log::channel('alibaba')->info('[提交售后申请] 准备请求, params:' . json_encode($businessParams, JSON_UNESCAPED_UNICODE));
  670. // --- 调用API ---
  671. $result = $this->executeParam2(
  672. self::NAMESPACE,
  673. 'alibaba.trade.createRefund',
  674. 1,
  675. $businessParams
  676. );
  677. // 不管API调用是否成功,都返回格式化的结果(包含原始数据)
  678. if ($result === false) {
  679. Log::channel('alibaba')->error('[提交售后申请] API请求失败, params:' . json_encode($businessParams, JSON_UNESCAPED_UNICODE));
  680. return [
  681. 'success' => false,
  682. 'refundId' => '',
  683. 'status' => 'api_failed',
  684. 'rawData' => ['error' => 'API请求失败'],
  685. ];
  686. }
  687. Log::channel('alibaba')->info('[提交售后申请] API请求成功, result:' . json_encode($result, JSON_UNESCAPED_UNICODE));
  688. return $this->formatCreateRefundResult($result);
  689. }
  690. /**
  691. * 格式化提交售后申请返回结果
  692. *
  693. * 成功返回数据结构:
  694. * {
  695. * "result": {
  696. * "result": {
  697. * "refundId": "RF123456",
  698. * "status": "refundWaitSellerSend"
  699. * },
  700. * "success": true
  701. * }
  702. * }
  703. *
  704. * 失败返回数据结构:
  705. * {
  706. * "result": {
  707. * "code": "5000",
  708. * "message": "错误描述",
  709. * "success": false
  710. * }
  711. * }
  712. *
  713. * @param array $result API原始返回数据
  714. * @return array
  715. */
  716. protected function formatCreateRefundResult(array $result): array
  717. {
  718. // 检查 result 层是否存在
  719. $resultData = $result['result'] ?? $result;
  720. $success = $resultData['success'] ?? false;
  721. if ($success) {
  722. // 成功:result.result 中包含 refundId 和 status
  723. $refundResult = $resultData['result'] ?? [];
  724. return [
  725. 'success' => true,
  726. 'refundId' => $refundResult['refundId'] ?? '',
  727. 'status' => $refundResult['status'] ?? '',
  728. 'rawData' => $result,
  729. ];
  730. }
  731. // 失败:result 中包含 code 和 message
  732. return [
  733. 'success' => false,
  734. 'refundId' => '',
  735. 'status' => $resultData['message'] ?? ($resultData['code'] ?? 'api_error'),
  736. 'rawData' => $result,
  737. ];
  738. }
  739. }