OrderService.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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. * 预下单
  23. *
  24. * API: com.alibaba.trade:alibaba.createOrder.preview-1
  25. *
  26. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.createOrder.preview-1
  27. *
  28. * 请求参数格式:
  29. * - flow: 流程标识(saleproxy=分销采购, general=普通下单)
  30. * - productList: 商品列表JSON数组,每个商品包含 offerId, quantity, specId
  31. * - addressParam: 地址参数JSON对象,包含 fullName, mobile, provinceText, cityText, areaText, address
  32. * - totalAmount: 总金额(单位:分,可选)
  33. * - message: 买家留言(可选)
  34. *
  35. * @param array $params 预下单参数
  36. * @return array|false
  37. */
  38. public function previewOrder(array $params = [])
  39. {
  40. $businessParams = [];
  41. // --- productList(商品列表,JSON字符串) ---
  42. // 直接传入已格式化的数组,由方法转为JSON
  43. if (!empty($params['cargoParamList'])) {
  44. $businessParams['cargoParamList'] = is_array($params['cargoParamList'])
  45. ? json_encode($params['cargoParamList'], JSON_UNESCAPED_UNICODE)
  46. : $params['cargoParamList'];
  47. }
  48. // --- addressParam(地址参数,JSON字符串) ---
  49. // 直接传入已格式化的数组,由方法转为JSON
  50. if (!empty($params['addressParam'])) {
  51. $businessParams['addressParam'] = is_array($params['addressParam'])
  52. ? json_encode($params['addressParam'], JSON_UNESCAPED_UNICODE)
  53. : $params['addressParam'];
  54. }
  55. // --- 调用API ---
  56. $result = $this->executeParam2(
  57. self::NAMESPACE,
  58. 'alibaba.createOrder.preview',
  59. 1,
  60. $businessParams
  61. );
  62. if ($result === false) {
  63. Log::channel('alibaba')->error('[预下单] API请求失败', $businessParams);
  64. return false;
  65. }
  66. Log::channel('alibaba')->info('[预下单] API请求成功', ['result' => json_encode($result)]);
  67. return $this->formatPreviewResult($result);
  68. }
  69. /**
  70. * 格式化预下单返回结果
  71. *
  72. * 实际返回结构:
  73. * {
  74. * "orderPreviewResuslt": [{
  75. * "tradeModeNameList": ["assureTrade"],
  76. * "status": true,
  77. * "sumPayment": 370, // 总支付金额(单位:分)
  78. * "sumCarriage": 300, // 总运费(单位:分)
  79. * "sumPaymentNoCarriage": 70, // 商品总金额(不含运费,单位:分)
  80. * "flowFlag": "fenxiaonew",
  81. * "cargoList": [{ // 商品列表
  82. * "amount": 0.7,
  83. * "finalUnitPrice": 0.7,
  84. * "specId": "...",
  85. * "skuId": 6148579878582,
  86. * "offerId": 678279922176,
  87. * "openOfferId": "...",
  88. * "cargoPromotionList": []
  89. * }],
  90. * "tradeModelList": [{"name":"担保交易","tradeType":"assureTrade","opSupport":true}],
  91. * "payChannelInfos": [{"name":"alipay"}],
  92. * "orderGroup": "79d4f82d...",
  93. * "canUseOfficialSolution": false
  94. * }],
  95. * "success": true,
  96. * "unsupportedCrossBorderPayOfferList": []
  97. * }
  98. *
  99. * @param array $result API原始返回数据
  100. * @return array
  101. */
  102. protected function formatPreviewResult(array $result): array
  103. {
  104. $success = $result['success'] ?? false;
  105. $previewList = $result['orderPreviewResuslt'] ?? [];
  106. if (empty($previewList) || !is_array($previewList)) {
  107. return [
  108. 'success' => $success,
  109. 'preview' => null,
  110. 'list' => [],
  111. ];
  112. }
  113. $formattedList = [];
  114. foreach ($previewList as $item) {
  115. $formattedList[] = [
  116. 'sumPayment' => $item['sumPayment'] ?? 0,
  117. 'sumCarriage' => $item['sumCarriage'] ?? 0,
  118. 'sumPaymentNoCarriage' => $item['sumPaymentNoCarriage'] ?? 0,
  119. 'flowFlag' => $item['flowFlag'] ?? '',
  120. // 'orderGroup' => $item['orderGroup'] ?? '',
  121. // 'status' => $item['status'] ?? false,
  122. // 'cargoList' => $item['cargoList'] ?? [],
  123. // 'tradeModeNameList' => $item['tradeModeNameList'] ?? [],
  124. // 'tradeModelList' => $item['tradeModelList'] ?? [],
  125. // 'payChannelInfos' => $item['payChannelInfos'] ?? [],
  126. // 'shopPromotionList' => $item['shopPromotionList'] ?? [],
  127. // 'tradeServiceList' => $item['tradeServiceList'] ?? [],
  128. // 'taoSampleSinglePromotion' => $item['taoSampleSinglePromotion'] ?? false,
  129. // 'canUseOfficialSolution' => $item['canUseOfficialSolution'] ?? false,
  130. ];
  131. }
  132. return [
  133. // 'success' => $success,
  134. 'preview' => $formattedList[0] ?? null,
  135. // 'list' => $formattedList,
  136. // 'unsupportedCrossBorderPayOfferList' => $result['unsupportedCrossBorderPayOfferList'] ?? [],
  137. ];
  138. }
  139. /**
  140. * 快速创建订单
  141. *
  142. * API: com.alibaba.trade:alibaba.trade.fastCreateOrder-1
  143. *
  144. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.fastCreateOrder-1
  145. *
  146. * 请求参数:
  147. * - flow: 流程标识(saleproxy=分销采购, general=普通下单)
  148. * - cargoParamList: 商品列表JSON数组(alibaba.trade.fast.cargo[]),每个商品包含 offerId, quantity, specId
  149. * - addressParam: 地址参数JSON对象,包含 fullName, mobile, provinceText, cityText, areaText, address
  150. * - totalAmount: 总金额(单位:分,可选)
  151. * - message: 买家留言(可选)
  152. * - orderGroup: 订单分组标识(从预下单返回的 orderGroup 字段获取,可选)
  153. *
  154. * @param array $params 创建订单参数
  155. * @return array|false
  156. */
  157. public function fastCreateOrder(array $params = [])
  158. {
  159. $businessParams = [];
  160. // --- flow(流程标识) ---
  161. $businessParams['flow'] = $params['flow'] ?? 'general';
  162. // --- cargoParamList(商品列表,JSON字符串) ---
  163. if (!empty($params['cargoParamList'])) {
  164. $businessParams['cargoParamList'] = is_array($params['cargoParamList'])
  165. ? json_encode($params['cargoParamList'], JSON_UNESCAPED_UNICODE)
  166. : $params['cargoParamList'];
  167. }
  168. // --- addressParam(地址参数,JSON字符串) ---
  169. if (!empty($params['addressParam'])) {
  170. $businessParams['addressParam'] = is_array($params['addressParam'])
  171. ? json_encode($params['addressParam'], JSON_UNESCAPED_UNICODE)
  172. : $params['addressParam'];
  173. }
  174. // --- 调用API ---
  175. $result = $this->executeParam2(
  176. self::NAMESPACE,
  177. 'alibaba.trade.fastCreateOrder',
  178. 1,
  179. $businessParams
  180. );
  181. if ($result === false) {
  182. Log::channel('alibaba')->error('[创建订单] API请求失败', $businessParams);
  183. return false;
  184. }
  185. Log::channel('alibaba')->info('[创建订单] API请求成功', ['result' => json_encode($result)]);
  186. return $this->formatCreateOrderResult($result);
  187. }
  188. /**
  189. * @param StoreOrderEntity[] $storeOrderEntityList
  190. * @return bool
  191. * @author 史晨
  192. * @date 2026/4/30 09:44
  193. * 支付成功通知1688
  194. */
  195. public function paySuccess(array $storeOrderEntityList): bool
  196. {
  197. foreach ($storeOrderEntityList as $storeOrderEntity) {
  198. $alibabaOrderId = $storeOrderEntity->getAlibabaOrderId();
  199. if (empty($alibabaOrderId)) {
  200. Log::channel('alibaba')->error($storeOrderEntity->getOrderSn() . '不是1688订单,不需要调取1688支付成功接口');
  201. continue;
  202. }
  203. $res = $this->prepareProtocolPay(['orderId' => $alibabaOrderId]);
  204. if ($res['success'] === false) {
  205. return false;
  206. }
  207. }
  208. return true;
  209. }
  210. /**
  211. * 免密支付准备
  212. *
  213. * API: com.alibaba.trade:alibaba.trade.pay.protocolPay.preparePay-1
  214. *
  215. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.pay.protocolPay.preparePay-1
  216. *
  217. * 请求参数说明(接口文档定义):
  218. * - tradeWithholdPreparePayParam: 必填,类型 message:alibaba.trade.pay.withhold.preparePayParam
  219. * preparePayParam 结构:
  220. * - orderId: 订单ID(Long,必填)
  221. *
  222. * 调用示例:
  223. * $orderService->prepareProtocolPay([
  224. * 'orderId' => 123456789,
  225. * ]);
  226. *
  227. * @param array $params 免密支付参数(orderId)
  228. * @return array|false
  229. */
  230. public function prepareProtocolPay(array $params = [])
  231. {
  232. // --- 参数校验 ---
  233. if (empty($params['orderId'])) {
  234. Log::channel('alibaba')->error('[免密支付] orderId不能为空');
  235. return false;
  236. }
  237. // --- 构建 tradeWithholdPreparePayParam(message类型需序列化为JSON字符串) ---
  238. $businessParams = [
  239. 'tradeWithholdPreparePayParam' => json_encode([
  240. 'orderId' => (int)$params['orderId'],
  241. ], JSON_UNESCAPED_UNICODE),
  242. ];
  243. // --- 调用API ---
  244. $result = $this->executeParam2(
  245. self::NAMESPACE,
  246. 'alibaba.trade.pay.protocolPay.preparePay',
  247. 1,
  248. $businessParams
  249. );
  250. if ($result === false) {
  251. Log::channel('alibaba')->error('[免密支付] API请求失败', $businessParams);
  252. return false;
  253. }
  254. Log::channel('alibaba')->info('[免密支付] API请求成功', ['result' => json_encode($result)]);
  255. return $this->formatProtocolPayResult($result);
  256. }
  257. /**
  258. * 格式化免密支付准备返回结果
  259. *
  260. * @param array $result API原始返回数据
  261. * @return array
  262. */
  263. protected function formatProtocolPayResult(array $result): array
  264. {
  265. // 返回结构: { "result": { ... }, "success": true }
  266. $data = $result['result'] ?? $result;
  267. $success = $result['success'] ?? false;
  268. Log::channel('alibaba')->info('[免密支付] 返回结果', [
  269. 'success' => $success,
  270. 'data' => $data,
  271. ]);
  272. return [
  273. 'success' => $success,
  274. 'data' => $data,
  275. 'rawData' => $result,
  276. ];
  277. }
  278. /**
  279. * 格式化创建订单返回结果
  280. *
  281. * @param array $result API原始返回数据
  282. * @return array
  283. */
  284. protected function formatCreateOrderResult(array $result): array
  285. {
  286. // 创建订单成功返回: { "result": { "orderId": "123456", "totalAmount": 370 }, "success": true }
  287. $data = $result['result'] ?? $result;
  288. $success = $result['success'] ?? false;
  289. return [
  290. 'success' => $success,
  291. 'orderId' => $data['orderId'] ?? 0,
  292. 'totalAmount' => $data['totalAmount'] ?? 0,
  293. 'rawData' => $data,
  294. ];
  295. }
  296. /**
  297. * 构建商品列表参数(便捷方法)
  298. *
  299. * @param array $items 商品列表 [['offerId' => 123, 'quantity' => 2, 'skuId' => 456], ...]
  300. * @return array
  301. */
  302. public function buildProductList(array $items): array
  303. {
  304. $productList = [];
  305. foreach ($items as $item) {
  306. $product = [
  307. 'offerId' => (int)($item['offerId'] ?? $item['productId'] ?? 0),
  308. 'quantity' => (int)($item['quantity'] ?? 1),
  309. ];
  310. if (!empty($item['skuId'])) {
  311. $product['skuId'] = (int)$item['skuId'];
  312. }
  313. if (!empty($item['price'])) {
  314. $product['price'] = (int)$item['price']; // 单位:分
  315. }
  316. if (!empty($item['productUnit'])) {
  317. $product['productUnit'] = $item['productUnit'];
  318. }
  319. $productList[] = $product;
  320. }
  321. return $productList;
  322. }
  323. /**
  324. * 构建地址参数(便捷方法)
  325. *
  326. * @param int $addressId 地址ID
  327. * @return array
  328. */
  329. public function buildAddressParam(int $addressId): array
  330. {
  331. return [
  332. 'addressId' => $addressId,
  333. ];
  334. }
  335. /**
  336. * 构建完整地址参数(使用系统地址数据格式)
  337. *
  338. * 适配系统 user_address 表字段:
  339. * address_id, real_name, phone, province, city, district, detail, post_code, code_list
  340. *
  341. * @param array $address 地址信息(系统user_address表数据)
  342. * @return array
  343. */
  344. public function buildFullAddressParam(array $address): array
  345. {
  346. // 解析地区编码列表: "1,67449,35,493"
  347. $codeList = $address['code_list'] ?? '';
  348. $codes = explode(',', $codeList);
  349. $districtCode = $codes[1] ?? $codes[0] ?? '';
  350. return [
  351. 'fullName' => $address['real_name'] ?? $address['fullName'] ?? '',
  352. 'mobile' => $address['phone'] ?? $address['mobile'] ?? '',
  353. 'phone' => $address['phone'] ?? $address['phone'] ?? '',
  354. 'postCode' => $address['post_code'] ?? $address['postCode'] ?? '',
  355. 'cityText' => $address['city'] ?? $address['cityText'] ?? '',
  356. 'provinceText' => $address['province'] ?? $address['provinceText'] ?? '',
  357. 'areaText' => $address['district'] ?? $address['areaText'] ?? '',
  358. 'townText' => $address['village'] ?? $address['townText'] ?? '',
  359. 'address' => $address['detail'] ?? $address['address'] ?? '',
  360. 'districtCode' => $districtCode,
  361. ];
  362. }
  363. /**
  364. * 获取交易订单的物流信息(买家视角)
  365. *
  366. * API: com.alibaba.trade:alibaba.trade.get.logisticsInfos.buyerView-1
  367. *
  368. * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.get.logisticsInfos.buyerView-1
  369. *
  370. * 请求参数:
  371. * - orderId: 1688订单ID(必填)
  372. * - webSite: 1688站点(可选,默认"1688")
  373. *
  374. * 返回示例:
  375. * {
  376. * "result": {
  377. * "logisticsItems": [{
  378. * "logisticsId": "LP001",
  379. * "logisticsCode": "SF1234567890", // 物流单号
  380. * "logisticsName": "顺丰速运", // 物流公司名称
  381. * "logisticsCompany": "shunfeng", // 物流公司编码
  382. * "status": "accept", // 物流状态
  383. * "gmtCreate": "2018-05-30 19:34:27"
  384. * }]
  385. * },
  386. * "success": true
  387. * }
  388. *
  389. * @param array $params 请求参数(orderId 必填)
  390. * @return array|false
  391. */
  392. public function getLogisticsInfosBuyerView(array $params = [])
  393. {
  394. // --- 参数校验 ---
  395. if (empty($params['orderId'])) {
  396. Log::channel('alibaba')->error('[获取物流信息] orderId不能为空');
  397. return false;
  398. }
  399. $businessParams = [
  400. 'orderId' => (string)$params['orderId'],
  401. 'webSite' => $params['webSite'] ?? '1688',
  402. ];
  403. // --- 调用API ---
  404. $result = $this->executeParam2(
  405. self::NAMESPACE,
  406. 'alibaba.trade.getLogisticsInfos.buyerView',
  407. 1,
  408. $businessParams
  409. );
  410. if ($result === false) {
  411. Log::channel('alibaba')->error('[获取物流信息] API请求失败, params:' . json_encode($businessParams, JSON_UNESCAPED_UNICODE));
  412. return false;
  413. }
  414. Log::channel('alibaba')->info('[获取物流信息] API请求成功, result:' . json_encode($result, JSON_UNESCAPED_UNICODE));
  415. return $this->formatLogisticsInfosResult($result);
  416. }
  417. /**
  418. * 格式化物流信息返回结果
  419. *
  420. * @param array $result API原始返回数据
  421. * @return array
  422. */
  423. protected function formatLogisticsInfosResult(array $result): array
  424. {
  425. $success = $result['success'] ?? false;
  426. $logisticsItems = [];
  427. if (isset($result['result']['logisticsItems']) && is_array($result['result']['logisticsItems'])) {
  428. foreach ($result['result']['logisticsItems'] as $item) {
  429. $logisticsItems[] = [
  430. 'logisticsId' => $item['logisticsId'] ?? '',
  431. 'logisticsCode' => $item['logisticsCode'] ?? '', // 物流单号
  432. 'logisticsName' => $item['logisticsName'] ?? '', // 物流公司名称
  433. 'logisticsCompany' => $item['logisticsCompany'] ?? '', // 物流公司编码
  434. 'status' => $item['status'] ?? '',
  435. 'gmtCreate' => $item['gmtCreate'] ?? '',
  436. ];
  437. }
  438. }
  439. return [
  440. 'success' => $success,
  441. 'logisticsItems' => $logisticsItems,
  442. 'rawData' => $result,
  443. ];
  444. }
  445. }