RefundService.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. <?php
  2. namespace addons\QiShangTong\common\service;
  3. use addons\QiShangTong\common\expand\sdk\qishangtong\src\model\RefundCancelModel;
  4. use addons\QiShangTong\common\expand\sdk\qishangtong\src\model\RefundCreateModel;
  5. use addons\QiShangTong\common\expand\sdk\qishangtong\src\model\RefundDeliveryModel;
  6. use addons\QiShangTong\common\expand\sdk\qishangtong\src\model\RefundReasonListModel;
  7. use addons\QiShangTong\common\expand\sdk\qishangtong\src\request\RefundCancelRequest;
  8. use addons\QiShangTong\common\expand\sdk\qishangtong\src\request\RefundCreateRequest;
  9. use addons\QiShangTong\common\expand\sdk\qishangtong\src\request\RefundDeliveryRequest;
  10. use addons\QiShangTong\common\expand\sdk\qishangtong\src\request\RefundDetailRequest;
  11. use addons\QiShangTong\common\expand\sdk\qishangtong\src\request\RefundReasonListRequest;
  12. use addons\QiShangTong\common\expand\sdk\qishangtong\src\model\RefundDetailModel;
  13. use addons\QiShangTong\common\helper\RequestHelper;
  14. use addons\QiShangTong\common\models\QiShangTongEvent;
  15. use addons\QiShangTong\common\models\QiShangTongGoods;
  16. use addons\QiShangTong\common\models\QiShangTongGoodsAttr;
  17. use addons\QiShangTong\common\models\QiShangTongOrder;
  18. use addons\QiShangTong\common\models\QiShangTongRefund;
  19. use common\enums\RabbitMqEnum;
  20. use common\enums\RefundStatusEnum;
  21. use common\enums\RefundTypeEnum;
  22. use common\enums\StatusEnum;
  23. use common\helpers\FormatHelper;
  24. use common\logic\order\OrderRefundLogic;
  25. use common\models\order\Order;
  26. use common\models\order\OrderDetail;
  27. use common\models\order\OrderRefund;
  28. use Yii;
  29. class RefundService
  30. {
  31. public function page(int $mall_id, array $params)
  32. {
  33. $where = [
  34. ['mall_id' => $mall_id],
  35. ['status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]]
  36. ];
  37. if (!empty($params['refund_no'])) {
  38. $where[] = ['like', 'refund_no', $params['refund_no']];
  39. }
  40. if (!empty($params['order_no'])) {
  41. $where[] = ['like', 'order_no', $params['order_no']];
  42. }
  43. if (!empty($params['third_refund_no'])) {
  44. $where[] = ['like', 'third_order_no', $params['third_refund_no']];
  45. }
  46. if (!empty($params['third_refund_id'])) {
  47. $where[] = ['like', 'third_refund_id', $params['third_refund_id']];
  48. }
  49. $ret = QiShangTongRefund::listPage([
  50. 'where' => $where,
  51. 'order' => 'id desc',
  52. 'limit' => $params['limit'] ?? '',
  53. 'with' => ['refund' => function($query) {
  54. $query->with(['orderDetail']);
  55. }]
  56. ]);
  57. if (!empty(QiShangTongRefund::getStaticError())) return QiShangTongRefund::getStaticError();
  58. return $ret;
  59. }
  60. /**
  61. * @param array $refund
  62. * @return true
  63. * @throws \Exception
  64. */
  65. public function create(array $refund)
  66. {
  67. // 只有退货退款/换货维修才需要过来这里?
  68. // 未收到货的无需处理
  69. $aOrder = QiShangTongOrder::findOne(['order_id' => $refund['order_id']]);
  70. if (empty($aOrder)) return true;
  71. if (in_array($refund['order_detail_id'], $aOrder->order_detail_ids)) {
  72. // 可以执行退款操作
  73. $model = new QiShangTongRefund();
  74. $model->mall_id = $refund['mall_id'];
  75. $model->order_id = $aOrder->order_id;
  76. $model->order_no = $aOrder->order_no;
  77. $model->refund_id = $refund['id'];
  78. $model->refund_no = $refund['order_no'];
  79. $model->third_order_no = $aOrder->third_order_no;
  80. $model->order_detail_id = $refund['order_detail_id'];
  81. $model->user_id = $refund['user_id'];
  82. if (!$model->save()) throw new \Exception($model->getErrorMessage());
  83. }
  84. return true;
  85. }
  86. /**
  87. * 同步
  88. * @param int $mall_id
  89. * @param int $id
  90. * @return string|true
  91. */
  92. public function async(int $mall_id, int $id)
  93. {
  94. // 执行同步
  95. $aRefund = QiShangTongRefund::findOne(['id' => $id, 'mall_id' => $mall_id]);
  96. if (empty($aRefund)) return true;
  97. try {
  98. $refund = OrderRefund::findOne(['id' => $aRefund->refund_id]);
  99. $order = Order::findOne(['id' => $aRefund->order_id]);
  100. $detail = OrderDetail::findOne(['id' => $aRefund->order_detail_id]);
  101. $aGoods = QiShangTongGoods::findOne(['goods_id' => $detail->goods_id]);
  102. $aOrder = QiShangTongOrder::findOne(['order_id' => $order->id]);
  103. $contentModel = new RefundCreateModel();
  104. $specItem = QiShangTongGoodsAttr::findOne(['goods_attr_id' => $detail->goods_attr_id]);
  105. $order_sn_map = array_column($aOrder->content['create']['goods'], "order_sn", "spec_id");
  106. $next_req = true;
  107. if ($refund->type != RefundTypeEnum::MONEY) {
  108. $rContentModel = new RefundReasonListModel();
  109. $rContentModel->order_sn = $order_sn_map[$specItem->third_spec_id]; // 订单创建时返回的order_sn 在goods里面的单号
  110. $rContentModel->apply_type = $refund->type; // 售后类型 1退款 2退货 3换货
  111. $res = RequestHelper::send(new RefundReasonListRequest(), $rContentModel, $mall_id);
  112. $aRefund->req_success = 100;
  113. if (!is_array($res)) {
  114. // throw new \Exception($rRes);
  115. $aRefund->req_success = StatusEnum::ENABLED;
  116. $next_req = false;
  117. }
  118. }
  119. if ($next_req) {
  120. $contentModel->goods_id = $aGoods->third_goods_id; // 商品ID
  121. $contentModel->spec_id = $specItem->third_spec_id; // 商品规格ID
  122. $contentModel->order_sn = $order_sn_map[$specItem->third_spec_id]; // 客户平台订单单号
  123. $contentModel->apply_type = $refund->type; // 售后类型 1退款 2退货 3换货
  124. $contentModel->apply_num = $detail->num; // 售后数量
  125. $contentModel->image = $refund->pic_list; // 售后图片
  126. $contentModel->video = ""; // 售后视频
  127. // 售后原因(可通过售后原因列表获取) 售后原因列表的type的值 退款情况下传0即可
  128. $contentModel->apply_case = $refund->type == RefundTypeEnum::MONEY ? StatusEnum::DISABLED : $res[0]['type'] ?? 0;
  129. $contentModel->send_back_name = $order->receiver_name; // 联系人姓名
  130. $contentModel->send_back_phone = $order->mobile; // 联系人电话
  131. $contentModel->caused = $refund->type == RefundTypeEnum::MONEY ? '' : $refund->reason; // 售后理由 原因列表的type_txt的值 退款情况下传空即可
  132. $contentModel->order_parent_sn = $aOrder->third_order_no; // 支付单号
  133. $res = RequestHelper::send(new RefundCreateRequest(), $contentModel, $mall_id);
  134. $aRefund->req_success = 100;
  135. if (!is_array($res)) {
  136. // throw new \Exception($res);
  137. $aRefund->req_success = StatusEnum::ENABLED;
  138. } else {
  139. $aRefund->third_refund_id = $res['apply_sn'] ?? '';
  140. }
  141. }
  142. // 更新数据
  143. $aRefund->post = ['create' => $contentModel->toArray()];
  144. $aRefund->content = ['create' => $res ?? []];
  145. $aRefund->last_req = 'async';
  146. $aRefund->last_resp = var_export($res ?? [], true);
  147. if (!$aRefund->save()) throw new \Exception($aRefund->getErrorMessage());
  148. } catch (\Exception $e) {
  149. \Yii::error(FormatHelper::exception($e, __METHOD__));
  150. return $e->getMessage();
  151. }
  152. return true;
  153. }
  154. /**
  155. * 取消售后
  156. * @param array $refund
  157. * @return true
  158. * @throws \Exception
  159. */
  160. public function cancel(array $refund)
  161. {
  162. // 执行同步
  163. $mall_id = $refund['mall_id'];
  164. $aRefund = QiShangTongRefund::findOne(['id' => $refund['id'], 'mall_id' => $mall_id]);
  165. if (empty($aRefund)) return true;
  166. $contentModel = new RefundCancelModel();
  167. $contentModel->apply_sn = $aRefund->third_refund_id;
  168. $res = RequestHelper::send(new RefundCancelRequest(), $contentModel, $mall_id);
  169. $aRefund->req_success = 100;
  170. if (!is_array($res)) {
  171. // throw new \Exception($res);
  172. $aRefund->req_success = StatusEnum::ENABLED;
  173. } else {
  174. $aRefund->refund_status = RefundStatusEnum::REVOKE;
  175. }
  176. // 更新数据
  177. $aRefund->post = array_merge($aRefund->post, ['cancel' => $contentModel->toArray()]);
  178. $aRefund->content = array_merge($aRefund->content, ['cancel' => $res]);
  179. $aRefund->third_refund_id = $res['apply_sn'];
  180. $aRefund->last_resp = var_export($res, true);
  181. $aRefund->last_req = 'cancel';
  182. if (!$aRefund->save()) throw new \Exception($aRefund->getErrorMessage());
  183. return true;
  184. }
  185. public function task()
  186. {
  187. foreach (QiShangTongRefund::find()->select('id, mall_id')->where(['refund_status' => [0, 1, 2]])->each() as $item) {
  188. $data = [
  189. 'mall_id' => $item->mall_id,
  190. 'id' => $item->id
  191. ];
  192. \Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_ORDER, RabbitMqEnum::ORDER_QUEUE, $data,
  193. self::class, 'taskSync');
  194. // $this->taskSync($data);
  195. }
  196. }
  197. /**
  198. * 更新退款状态
  199. * @param QiShangTongEvent $event
  200. * @return true
  201. * @throws \Exception
  202. */
  203. public function eventUpdate(QiShangTongEvent &$event)
  204. {
  205. $t = Yii::$app->db->beginTransaction();
  206. try {
  207. $aRefund = QiShangTongRefund::findOne(['third_refund_id' => $event->content['apply_sn']]);
  208. if (empty($aRefund)) throw new \Exception("退款记录不存在");
  209. $event->mall_id = $aRefund->mall_id;
  210. $is_update = true;
  211. switch ($event->content['process_step']) {
  212. case 1:
  213. // $step_status = RefundStatusEnum::REFUSE;
  214. // 驳回
  215. break;
  216. case 2:
  217. /**
  218. * address_type: 1
  219. consignee: 林锦豪
  220. mobile: 13246824705
  221. address: 广州市白云区永平街永泰学山塘街81号A2栋618
  222. express_id:
  223. saler_express:
  224. saler_express_no:
  225. customer_name:
  226. id: 377
  227. step_status: 8
  228. */
  229. //去企叮咚第三方详情
  230. // 退货退款 2
  231. // 换货 8
  232. $refund = OrderRefund::findOne(['id' => $aRefund->refund_id]);
  233. // 确认收货并退款
  234. $step_status = $refund->type == RefundTypeEnum::MONEY_AND_PRODUCT ? RefundStatusEnum::STEP_2 : RefundStatusEnum::STEP_8;
  235. $contentModel = new RefundDetailModel();
  236. $contentModel->apply_sn = $aRefund->third_refund_id;
  237. $res = RequestHelper::send(new RefundDetailRequest(), $contentModel, $aRefund->mall_id);
  238. $aRefund->req_success = 100;
  239. $aRefund->last_resp = var_export($res, true);
  240. // $aRefund->last_req = '';
  241. if (!is_array($res)) {
  242. // throw new \Exception($res);
  243. $aRefund->req_success = StatusEnum::ENABLED;
  244. $is_update = false;
  245. } else {
  246. $address = $res['consignee_address'];
  247. $mobile = $res['consignee_phone'];
  248. $consignee = $res['consignee_name'];
  249. }
  250. // 需要完善物流信息
  251. break;
  252. case 3:
  253. // $step_status = RefundStatusEnum::REVOKE;
  254. $params['mall_id'] = $aRefund->mall_id;
  255. $params['user_id'] = $aRefund->user_id;
  256. $params['id'] = $aRefund->refund_id;
  257. $res = OrderRefund::cancelRefund($params);
  258. if (!$res) throw new \Exception($res);
  259. // 取消
  260. break;
  261. case 4:
  262. $refund = OrderRefund::findOne(['id' => $aRefund->refund_id]);
  263. $step_status = $refund->type == RefundTypeEnum::MONEY ? RefundStatusEnum::STEP_6 : RefundStatusEnum::STEP_4;
  264. $aRefund->refund_status = RefundStatusEnum::FINISH;
  265. // 完成
  266. break;
  267. }
  268. if (!$aRefund->save()) throw new \Exception($aRefund->getErrorMessage());
  269. if ($is_update) {
  270. if (empty($step_status)) throw new \Exception("未匹配到退款步骤");
  271. // 退货地址
  272. $address = ['consignee' => $consignee ?? '','mobile' => $mobile ?? '','address' => $address ?? ''];
  273. // 换货物流信息
  274. $express = ['saler_express' => '','saler_express_no' => '','customer_name' => ''];
  275. $data = [
  276. 'id' => $aRefund->refund_id,
  277. 'step_status' => $step_status,
  278. 'address' => $address,
  279. 'express' => $express,
  280. 'refund_reason' => '',
  281. ];
  282. $logic = new OrderRefundLogic();
  283. $res = $logic->execute($event->mall_id, $data);
  284. if (!$res) throw new \Exception($logic->getError());
  285. }
  286. $t->commit();
  287. } catch (\Exception $e) {
  288. $t->rollBack();
  289. throw new \Exception($e->getMessage());
  290. }
  291. return true;
  292. }
  293. /**
  294. * 拒绝售后
  295. * @param int $mall_id
  296. * @param int $id
  297. * @return string|true
  298. */
  299. public function reject(int $mall_id, int $id, array $otherData = [])
  300. {
  301. $aRefund = QiShangTongRefund::findOne(['id' => $id, 'mall_id' => $mall_id]);
  302. if (empty($aRefund)) return '售后记录不存在';
  303. // 退货地址
  304. $address = ['consignee' => '','mobile' => '','address' => ''];
  305. // 换货物流信息
  306. $express = ['saler_express' => '','saler_express_no' => '','customer_name' => ''];
  307. $data = [
  308. 'id' => $aRefund->refund_id,
  309. 'step_status' => RefundStatusEnum::REFUSE,
  310. 'address' => $address,
  311. 'express' => $express,
  312. 'refund_reason' => '暂无拒绝理由',
  313. 'is_admin' => $otherData['is_admin'] ?? 0,
  314. 'operator_id' => $otherData['operator_id'] ?? 0,
  315. ];
  316. $logic = new OrderRefundLogic();
  317. $res = $logic->execute($mall_id, $data);
  318. if (!$res) return $logic->getError();
  319. // 执行拒绝动作
  320. return true;
  321. }
  322. public function compensate(int $mall_id, $id)
  323. {
  324. $aRefund = QiShangTongRefund::findOne(['id' => $id, 'mall_id' => $mall_id]);
  325. if (empty($aRefund)) return '售后记录不存在';
  326. if ($aRefund->req_success == 100) return '当前状态无效补偿';
  327. try {
  328. switch ($aRefund->last_req) {
  329. case 'cancel':
  330. $refund = OrderRefund::getOne([
  331. ['id' => $aRefund->refund_id]
  332. ], true, ['orderDetail', 'base_user', 'order']);
  333. return $this->cancel($refund);
  334. case 'async':
  335. return $this->async($aRefund->mall_id, $aRefund->id);
  336. case 'buyerShip':
  337. $refund = OrderRefund::getOne([
  338. ['id' => $aRefund->refund_id]
  339. ], true, ['orderDetail', 'base_user', 'order']);
  340. return $this->buyerShip($refund);
  341. }
  342. } catch (\Exception $e) {
  343. return $e->getMessage();
  344. }
  345. return true;
  346. }
  347. /**
  348. * @param array $refund
  349. * @return true
  350. * @throws \Exception
  351. */
  352. public function buyerShip(array $refund)
  353. {
  354. $aRefund = QiShangTongRefund::findOne(['refund_id' => $refund['id'], 'mall_id' => $refund['mall_id']]);
  355. if (empty($aRefund)) {
  356. return true;
  357. }
  358. // 存在 那么就需要邮寄过去
  359. $contentModel = new RefundDetailModel();
  360. $contentModel->apply_sn = $aRefund->third_refund_id;
  361. $res = RequestHelper::send(new RefundDetailRequest(), $contentModel, $aRefund->mall_id);
  362. $aRefund->last_resp = var_export($res, true);
  363. $aRefund->last_req = 'buyerShip';
  364. $aRefund->req_success = 100;
  365. if (!is_array($res)) {
  366. $aRefund->req_success = 1;
  367. } else {
  368. // 处理
  369. if ($res['status'] == 7 && $res['is_send_back'] == StatusEnum::ENABLED) {
  370. $order = Order::findOne(['id' => $refund['order_id']]);
  371. $contentModel = new RefundDeliveryModel();
  372. $contentModel->apply_sn = $aRefund->third_refund_id;
  373. $contentModel->back_ship_name = $refund['express']; // 快递名称
  374. $contentModel->back_ship_sn = $refund['express_no']; // 快递单号
  375. $contentModel->back_ship_image = $refund['pic_list']; // 快递凭证
  376. $contentModel->send_back_name = $order->receiver_name; // 寄回人姓名
  377. $contentModel->send_back_phone = $order->mobile; // 寄回人电话
  378. $contentModel->back_ship_notes = '这边系统无需上传快递凭证';
  379. $res = RequestHelper::send(new RefundDeliveryRequest(), $contentModel, $aRefund->mall_id);
  380. $aRefund->last_resp = var_export($res, true);
  381. if (!is_array($res)) {
  382. $aRefund->req_success = 1;
  383. }
  384. }
  385. }
  386. $aRefund->save();
  387. return true;
  388. }
  389. }