Kaynağa Gözat

feat(refund): 添加1688订单售后功能

- 新增1688订单提交售后接口 refund/submitAli
- 实现168售后原因查询功能
- 集成1688退款状态变更通知处理
- 添加1688订单售后API服务方法
- 实现售后申请提交及状态同步逻辑
- 创建售后记录存储表结构支持
shichen 2 ay önce
ebeveyn
işleme
38d9cc88ad

+ 87 - 0
app/controller/admin/order/RefundOrder.php

@@ -90,6 +90,93 @@ class RefundOrder extends BaseController
90 90
         return app('json')->success($result);
91 91
     }
92 92
 
93
+    /**
94
+     * 提交1688售后申请
95
+     *
96
+     * 接收前端传入的1688售后参数,直接调用 alibaba.trade.createRefund 接口提交,
97
+     * 并将提交记录保存到 store_refund_alibaba 表
98
+     *
99
+     * @return \think\Response
100
+     */
101
+    public function submitAli()
102
+    {
103
+        $data = $this->request->params([
104
+            'orderId',
105
+            'orderEntryIds',
106
+            'disputeRequest',
107
+            'description',
108
+            'applyPayment',
109
+            'applyCarriage',
110
+            'applyReasonId',
111
+            'goodsStatus',
112
+            'refund_order_id',
113
+        ]);
114
+
115
+        // 参数校验(所有参数均为必填)
116
+        $requiredFields = ['orderId', 'orderEntryIds', 'disputeRequest', 'description', 'applyPayment', 'applyCarriage', 'applyReasonId', 'goodsStatus'];
117
+        foreach ($requiredFields as $field) {
118
+            if (empty($data[$field])) {
119
+                return app('json')->fail('缺少必填参数: ' . $field);
120
+            }
121
+        }
122
+
123
+        $orderService = new OrderService();
124
+
125
+        // 构建1688接口参数(前端 disputeRequest 映射为 1688 接口的 disputeReasonId)
126
+        $params = [
127
+            'orderId'         => (string)$data['orderId'],
128
+            'orderEntryIds'   => $data['orderEntryIds'],
129
+            'disputeReasonId' => (string)$data['disputeRequest'],
130
+            'description'     => (string)$data['description'],
131
+            'applyPayment'    => (string)$data['applyPayment'] * 100,
132
+            'applyCarriage'   => (string)$data['applyCarriage'] * 100,
133
+            'applyReasonId'   => (string)$data['applyReasonId'],
134
+            'goodsStatus'     => (string)$data['goodsStatus'],
135
+        ];
136
+
137
+        // 调用1688 API提交售后申请
138
+        $result = $orderService->createRefund($params);
139
+
140
+        // 记录到 store_refund_alibaba 表(不管成功与否都保存原始数据)
141
+        $insertData = [
142
+            'refund_order_id'   => (int)($data['refund_order_id'] ?? 0),
143
+            'alibaba_order_id'  => (string)$data['orderId'],
144
+            'order_entry_ids'   => $data['orderEntryIds'],
145
+            'dispute_reason_id' => (string)$data['disputeRequest'],
146
+            'description'       => (string)$data['description'],
147
+            'goods_status'      => (string)$data['goodsStatus'],
148
+            'apply_payment'     => (string)$data['applyPayment'],
149
+            'apply_carriage'    => (string)$data['applyCarriage'],
150
+            'result_data'       => json_encode($result['rawData'] ?? [], JSON_UNESCAPED_UNICODE),
151
+        ];
152
+
153
+        if ($result['success']) {
154
+            // 提交成功
155
+            $insertData['status'] = 1;
156
+            $insertData['alibaba_refund_id'] = $result['refundId'] ?? '';
157
+            $insertData['alibaba_status'] = $result['status'] ?? '';
158
+            Db::name('store_refund_alibaba')->insert($insertData);
159
+
160
+            // 更新退款单的 after_sn 字段为1688售后单ID
161
+            if (!empty($data['refund_order_id']) && !empty($result['refundId'])) {
162
+                Db::name('store_refund_order')
163
+                    ->where('refund_order_id', (int)$data['refund_order_id'])
164
+                    ->update(['after_sn' => $result['refundId']]);
165
+            }
166
+
167
+            return app('json')->success([
168
+                'alibaba_refund_id' => $result['refundId'] ?? '',
169
+                'alibaba_status'    => $result['status'] ?? '',
170
+            ], '提交1688售后申请成功');
171
+        } else {
172
+            // 提交失败
173
+            $insertData['status'] = 2;
174
+            $insertData['fail_message'] = $result['status'] ?? '1688接口调用失败';
175
+            Db::name('store_refund_alibaba')->insert($insertData);
176
+            return app('json')->fail($result['status'] ?? '提交1688售后申请失败');
177
+        }
178
+    }
179
+
93 180
     public function detail($id)
94 181
     {
95 182
         $data=$this->repository->getOne($id);

+ 69 - 0
app/controller/api/AlibabaNotify.php

@@ -54,6 +54,9 @@ class AlibabaNotify
54 54
             switch ($type) {
55 55
                 case 'ORDER_BUYER_VIEW_ANNOUNCE_SENDGOODS':
56 56
                     return $this->handleOrderSendGoods($message);
57
+                case 'ORDER_BUYER_VIEW_ORDER_BUYER_REFUND_IN_SALES':
58
+                case 'ORDER_BUYER_VIEW_ORDER_REFUND_AFTER_SALES':
59
+                    return $this->handleRefundInSales($message);
57 60
                 default:
58 61
                     Log::channel('alibaba')->warning('[订阅消息通知] 未知消息类型, type:' . $type);
59 62
                     return response('success: unknown type');
@@ -167,4 +170,70 @@ class AlibabaNotify
167 170
 
168 171
         return response('success');
169 172
     }
173
+
174
+    /**
175
+     * 处理售中退款状态变更通知
176
+     *
177
+     * 订阅消息类型: ORDER_BUYER_VIEW_ORDER_BUYER_REFUND_IN_SALES
178
+     *
179
+     * 消息内容示例:
180
+     * {
181
+     *   "data": {
182
+     *     "buyerMemberId": "b2b-...",
183
+     *     "orderId": 111,
184
+     *     "currentStatus": "refundsuccess",
185
+     *     "refundAction": "SYSTEM_AGREE_REFUND_PROTOCOL",
186
+     *     "sellerMemberId": "b2b-...",
187
+     *     "msgSendTime": "2018-05-30 19:30:13",
188
+     *     "operator": "system",
189
+     *     "refundId": "1234556"
190
+     *   },
191
+     *   "gmtBorn": 1778296872365,
192
+     *   "msgId": 189206085626,
193
+     *   "type": "ORDER_BUYER_VIEW_ORDER_BUYER_REFUND_IN_SALES",
194
+     *   "userInfo": "b2b-..."
195
+     * }
196
+     *
197
+     * 处理流程:
198
+     * 1. 根据 data.refundId 查询 rrx_store_refund_alibaba 表
199
+     * 2. 更新 alibaba_status 为当前状态
200
+     *
201
+     * @param array $message 消息内容
202
+     * @return \think\Response
203
+     */
204
+    protected function handleRefundInSales(array $message)
205
+    {
206
+        $data = $message['data'] ?? [];
207
+        $refundId = $data['refundId'] ?? '';
208
+        $currentStatus = $data['currentStatus'] ?? '';
209
+        $orderId = $data['orderId'] ?? 0;
210
+
211
+        if (empty($refundId)) {
212
+            Log::channel('alibaba')->error('[退款通知] data.refundId为空, message:' . json_encode($message, JSON_UNESCAPED_UNICODE));
213
+            return response('fail: refundId is empty');
214
+        }
215
+
216
+        Log::channel('alibaba')->info('[退款通知] 开始处理, refundId:' . $refundId . ', orderId:' . $orderId . ', currentStatus:' . $currentStatus);
217
+
218
+        // 查询 rrx_store_refund_alibaba 表
219
+        $refundRecord = Db::name('store_refund_alibaba')
220
+            ->where('alibaba_refund_id', (string)$refundId)
221
+            ->find();
222
+
223
+        if (empty($refundRecord)) {
224
+            Log::channel('alibaba')->warning('[退款通知] 未找到匹配的退款记录, refundId:' . $refundId);
225
+            return response('success: refund record not found locally');
226
+        }
227
+
228
+        // 更新 alibaba_status
229
+        Db::name('store_refund_alibaba')
230
+            ->where('id', $refundRecord['id'])
231
+            ->update([
232
+                'alibaba_status' => $currentStatus,
233
+            ]);
234
+
235
+        Log::channel('alibaba')->info('[退款通知] 退款状态更新成功, id:' . $refundRecord['id'] . ', refundId:' . $refundId . ', status:' . $currentStatus);
236
+
237
+        return response('success');
238
+    }
170 239
 }

+ 142 - 0
app/services/ThirdParty/AlibabaAgent/OrderService.php

@@ -674,4 +674,146 @@ class OrderService extends AlibabaAgentBaseService
674 674
             'reasons'       => $reasons,
675 675
         ];
676 676
     }
677
+
678
+    /**
679
+     * 提交售后申请
680
+     *
681
+     * API: com.alibaba.trade:alibaba.trade.createRefund-1
682
+     *
683
+     * 接口文档: https://open.1688.com/api/apidocdetail.htm?id=com.alibaba.trade%3Aalibaba.trade.createRefund-1
684
+     *
685
+     * 请求参数:
686
+     *   - orderId:        主订单ID(必填)
687
+     *   - orderEntryIds:  子订单ID列表,格式 "[123,456]"(必填)
688
+     *   - disputeReasonId: 退款原因ID(必填,从 getRefundReasonList 获取)
689
+     *   - description:    退款说明(必填)
690
+     *   - goodsStatus:    货物状态(必填)
691
+     *   - applyPayment:   退款金额,单位分(必填)
692
+     *   - applyCarriage:  退运费,单位分(可选,默认0)
693
+     *   - voucher:        凭证图片列表,格式 "[{url:'http://...'}]"(可选)
694
+     *   - applyReason:    退款原因描述(可选)
695
+     *
696
+     * 返回示例:
697
+     * {
698
+     *   "result": {
699
+     *     "result": {
700
+     *       "refundId": "RF123456",
701
+     *       "status": "refundWaitSellerSend"
702
+     *     },
703
+     *     "success": true
704
+     *   }
705
+     * }
706
+     *
707
+     * @param array $params 请求参数
708
+     * @return array|false
709
+     */
710
+    public function createRefund(array $params = [])
711
+    {
712
+        // --- 参数校验 ---
713
+        $requiredFields = ['orderId', 'orderEntryIds', 'disputeReasonId', 'description', 'goodsStatus', 'applyPayment', 'applyCarriage', 'applyReasonId'];
714
+        foreach ($requiredFields as $field) {
715
+            if (!isset($params[$field])) {
716
+                Log::channel('alibaba')->error('[提交售后申请] 缺少必填参数: ' . $field);
717
+                return [
718
+                    'success'  => false,
719
+                    'refundId' => '',
720
+                    'status'   => 'param_missing',
721
+                    'rawData'  => ['error' => '缺少必填参数: ' . $field],
722
+                ];
723
+            }
724
+        }
725
+
726
+        $businessParams = [
727
+            'orderId'         => (string)$params['orderId'],
728
+            'orderEntryIds'   => $params['orderEntryIds'],
729
+            'disputeRequest'  => (string)$params['disputeReasonId'],
730
+            'applyPayment'    => (string)$params['applyPayment'],
731
+            'applyCarriage'   => (string)$params['applyCarriage'],
732
+            'applyReasonId'   => (string)$params['applyReasonId'],
733
+            'description'     => (string)$params['description'],
734
+            'goodsStatus'     => (string)$params['goodsStatus'],
735
+        ];
736
+
737
+        // 可选参数
738
+        if (!empty($params['voucher'])) {
739
+            $businessParams['voucher'] = $params['voucher'];
740
+        }
741
+
742
+        Log::channel('alibaba')->info('[提交售后申请] 准备请求, params:' . json_encode($businessParams, JSON_UNESCAPED_UNICODE));
743
+
744
+        // --- 调用API ---
745
+        $result = $this->executeParam2(
746
+            self::NAMESPACE,
747
+            'alibaba.trade.createRefund',
748
+            1,
749
+            $businessParams
750
+        );
751
+
752
+        // 不管API调用是否成功,都返回格式化的结果(包含原始数据)
753
+        if ($result === false) {
754
+            Log::channel('alibaba')->error('[提交售后申请] API请求失败, params:' . json_encode($businessParams, JSON_UNESCAPED_UNICODE));
755
+            return [
756
+                'success'  => false,
757
+                'refundId' => '',
758
+                'status'   => 'api_failed',
759
+                'rawData'  => ['error' => 'API请求失败'],
760
+            ];
761
+        }
762
+
763
+        Log::channel('alibaba')->info('[提交售后申请] API请求成功, result:' . json_encode($result, JSON_UNESCAPED_UNICODE));
764
+
765
+        return $this->formatCreateRefundResult($result);
766
+    }
767
+
768
+    /**
769
+     * 格式化提交售后申请返回结果
770
+     *
771
+     * 成功返回数据结构:
772
+     * {
773
+     *   "result": {
774
+     *     "result": {
775
+     *       "refundId": "RF123456",
776
+     *       "status": "refundWaitSellerSend"
777
+     *     },
778
+     *     "success": true
779
+     *   }
780
+     * }
781
+     *
782
+     * 失败返回数据结构:
783
+     * {
784
+     *   "result": {
785
+     *     "code": "5000",
786
+     *     "message": "错误描述",
787
+     *     "success": false
788
+     *   }
789
+     * }
790
+     *
791
+     * @param array $result API原始返回数据
792
+     * @return array
793
+     */
794
+    protected function formatCreateRefundResult(array $result): array
795
+    {
796
+        // 检查 result 层是否存在
797
+        $resultData = $result['result'] ?? $result;
798
+        $success = $resultData['success'] ?? false;
799
+
800
+        if ($success) {
801
+            // 成功:result.result 中包含 refundId 和 status
802
+            $refundResult = $resultData['result'] ?? [];
803
+            return [
804
+                'success'  => true,
805
+                'refundId' => $refundResult['refundId'] ?? '',
806
+                'status'   => $refundResult['status'] ?? '',
807
+                'rawData'  => $result,
808
+            ];
809
+        }
810
+
811
+        // 失败:result 中包含 code 和 message
812
+        return [
813
+            'success'  => false,
814
+            'refundId' => '',
815
+            'status'   => $resultData['message'] ?? ($resultData['code'] ?? 'api_error'),
816
+            'rawData'  => $result,
817
+        ];
818
+    }
677 819
 }

+ 3 - 1
route/admin.php

@@ -1004,8 +1004,10 @@ Route::group(config('admin.api_admin_prefix') . '/', function () {
1004 1004
             Route::get('refund/express/:id', 'RefundOrder/express')->name('systemStoreRefundExpress');//查询用户退货物流
1005 1005
 
1006 1006
 
1007
-            // 1688订单售后
1007
+            // 1688订单售后原因
1008 1008
             Route::get('refundReasonList','RefundOrder/refundReasonList');
1009
+            // 1688订单提交售后
1010
+            Route::post('refund/submitAli', 'RefundOrder/submitAli')->name('systemStoreRefundOrderSubmitAli');
1009 1011
 
1010 1012
             Route::get('dhm_list/lst', 'RefundOrder/dhm_list')->name('systemRefundOrderDhmLst');
1011 1013
             Route::get('dhm_status', 'RefundOrder/dhm_status')->name('systemRefundOrderDhmStatus');