RefundService.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. <?php
  2. namespace addons\StarChain\common\services;
  3. use addons\StarChain\common\enums\OrderRefundEnum;
  4. use addons\StarChain\common\enums\RefundStatusEnum;
  5. use addons\StarChain\common\enums\RefundTypeEnum;
  6. use addons\StarChain\common\enums\StarChainEnum;
  7. use addons\StarChain\common\models\Goods;
  8. use addons\StarChain\common\models\MallOrder;
  9. use addons\StarChain\common\models\Order;
  10. use addons\StarChain\common\models\OrderItem;
  11. use addons\StarChain\common\models\OrderRefund;
  12. use addons\StarChain\common\models\Setting;
  13. use common\enums\EventNameEnum;
  14. use common\enums\OrderStatusEnum;
  15. use common\enums\RabbitMqEnum;
  16. use common\enums\RefundTypeEnum as EnumsRefundTypeEnum;
  17. use common\enums\ShippingStatusEnum;
  18. use common\globals\GlobalInvoke;
  19. use common\logic\order\OrderRefundLogic;
  20. use common\logic\statistics\StatisticsHandle;
  21. use common\models\order\OrderDetail;
  22. use common\models\order\OrderRefund as MallOrderRefund;
  23. use common\models\order\OrderRefundStep;
  24. use Exception;
  25. use Yii;
  26. use yii\data\Pagination;
  27. use yii\helpers\Json;
  28. /**
  29. * 售后服务类
  30. */
  31. class RefundService extends BaseService
  32. {
  33. /**
  34. * 获取售后订单列表
  35. *
  36. * @param array $params
  37. * @return array
  38. */
  39. public function getRefundList($params = [])
  40. {
  41. $limit = $params['limit'] ?? 10;
  42. $model = OrderRefund::find();
  43. $model->where(['mall_id' => $this->mall_id]);
  44. $model->with([
  45. 'goods' => function ($query) {
  46. $query->select('id, name, spu_id, carousel_img_list, status');
  47. },
  48. 'detail' => function ($query) {
  49. $query->select('id, order_id, goods_attr_name, price, goods_price');
  50. },
  51. 'order' => function ($query) {
  52. $query->select('id, region_name, address, receiver_name, shipping_money, goods_price, mobile');
  53. },
  54. 'refund' => function ($query) {
  55. $query->select('id, order_no, reason, refund_price, pic_list, type, remark');
  56. },
  57. ]);
  58. // 查询
  59. if (!empty($params['name'])) {
  60. $model->andWhere([
  61. 'spu_id' => Goods::find()->where(['like', 'name', $params['name']])->select('spu_id'),
  62. ]);
  63. }
  64. if (!empty($params['order_no'])) {
  65. $model->andWhere([
  66. 'order_refund_id' => MallOrderRefund::find()->where(['like', 'order_no', $params['order_no']])->select('id')->column(),
  67. ]);
  68. }
  69. if (!empty($params['data'])) {
  70. list($start, $end) = $params['data'];
  71. $start = strtotime($start);
  72. $end = strtotime($end);
  73. $model->andFilterWhere(['between', 'created_at', $start, $end]);
  74. }
  75. $pagination = new Pagination(['totalCount' => $model->count(), 'pageSize' => $limit]);
  76. $model->limit($pagination->limit)->offset($pagination->offset);
  77. $list = $model->orderBy('id desc')
  78. ->asArray()
  79. ->all();
  80. foreach ($list as $k => $v) {
  81. $list[$k]['goods']['carousel_img_list'] = Json::decode($v['goods']['carousel_img_list']);
  82. $list[$k]['data'] = Json::decode($v['data']);
  83. $list[$k]['refund']['pic_list'] = Json::decode($v['refund']['pic_list']);
  84. $list[$k]['refund']['type_text'] = RefundTypeEnum::getValue($v['refund']['type']);
  85. $list[$k]['refund_status_text'] = RefundStatusEnum::getValue($v['refund_status']);
  86. $list[$k]['status_text'] = OrderRefundEnum::getValue($v['status']);
  87. }
  88. $data['list'] = $list;
  89. $data['status_arr'] = OrderRefundEnum::getMap();
  90. $data['refund_status_arr'] = RefundStatusEnum::getMap();
  91. $data['page_count'] = $pagination->pageCount;
  92. return $data;
  93. }
  94. /**
  95. * 添加售后
  96. *
  97. * @param array $params
  98. * @return MallOrderRefund
  99. */
  100. public function doRefund($params)
  101. {
  102. $params2 = $params;
  103. $db = Yii::$app->db->beginTransaction();
  104. try {
  105. //订单详情表
  106. $order_detail = OrderDetail::findOne(['id' => $params['order_detail_id'], 'user_id' => $params['user_id']]);
  107. if (empty($order_detail)) throw new Exception('订单不存在');
  108. if ($order_detail->order_status == OrderStatusEnum::ACCOMPLISH) throw new Exception('订单已过售后时间,无法申请售后');
  109. if (!in_array($order_detail->order_status, [OrderStatusEnum::PAY, OrderStatusEnum::SHIPMENTS, OrderStatusEnum::SING])) {
  110. throw new Exception('该订单状态下,无法申请售后');
  111. }
  112. // 订单是否聚合
  113. $order = MallOrder::find()->where(['id' => $order_detail['order_id']])->select('id, order_no, mall_id, shipping_money, order_source')->one();
  114. if ($order->order_source != StarChainEnum::PLUGIN_SIGN) throw new Exception('该订单不是聚合供应链订单');
  115. // 供应链订单信息
  116. $chain_order = OrderItem::find()->where(['mall_order_id' => $order_detail['order_id']])->select('sku_id, spu_id, num')->one();
  117. if (empty($chain_order)) throw new Exception('该订单不是聚合供应链订单');
  118. // 公共调用
  119. $order_detail = GlobalInvoke::exec(GlobalInvoke::ORDER_REFUND_SUBMIT, $this->mall_id, [
  120. 'order_detail' => $order_detail,
  121. 'mall_id' => $this->mall_id,
  122. ]) ?: $order_detail;
  123. // 一共可以退款
  124. $refund_total_money = bcadd($order_detail->goods_price, $order_detail->adjust_money, 2);
  125. // 未发货添加邮费
  126. if ($order_detail['shipping_status'] == ShippingStatusEnum::UN_SHIPPED) {
  127. $refund_total_money = bcadd($refund_total_money, $order->shipping_money, 2);
  128. }
  129. if ($params['refund_price'] > $refund_total_money) {
  130. throw new Exception('最多可退款金额¥' . $refund_total_money);
  131. }
  132. // 替换成商城状态
  133. $params['type'] = RefundTypeEnum::typeChange($params['type']);
  134. if (empty($params['type'])) {
  135. throw new Exception('选择退款状态有误');
  136. }
  137. $order_detail->is_feedback = OrderStatusEnum::FEEDBACKING;
  138. if(!$order_detail->save()) throw new Exception(OrderDetail::getStaticError());
  139. // 处理售后步骤
  140. !empty($params['pic_list']) && $params['pic_list'] = Json::decode($params['pic_list']);
  141. MallOrderRefund::stepHandle($params);
  142. // 退款主表
  143. $params2['is_feedback'] = $params['is_feedback'] = OrderStatusEnum::FEEDBACKING;
  144. $params2['order_id'] = $params['order_id'] = $order_detail->order_id;
  145. $params2['order_no'] = $params['order_no'] = MallOrder::generateOrderNo('r');
  146. $order_refund_model = MallOrderRefund::setData($params);
  147. if(!$order_refund_model) throw new Exception(MallOrderRefund::getStaticError());
  148. $params2['order_refund_id'] = $params['order_refund_id'] = $order_refund_model->id;
  149. $res = OrderRefundStep::setData($params);
  150. if(!$res) throw new Exception(OrderRefundStep::getStaticError());
  151. //自动维护订单主表
  152. $order = MallOrder::findOne($params['order_id']);
  153. $res = MallOrder::autoUpdateStatus($order);
  154. // 供应链订单详情
  155. $order_item = OrderItem::getOrderItemByDetailId($order_detail->id);
  156. if (empty($order_item)) throw new Exception('售后订单详情不存在');
  157. // 添加聚合售后
  158. $refund = OrderRefund::add([
  159. 'user_id' => $params['user_id'],
  160. 'mall_id' => $order->mall_id,
  161. 'order_id' => $order_detail['order_id'],
  162. 'order_detail_id' => $order_detail['id'],
  163. 'order_refund_id' => $order_refund_model->id,
  164. 'spu_id' => $order_item->spu_id,
  165. 'sku_id' => $order_item->sku_id,
  166. 'num' => $order_item->num,
  167. 'data' => $params2,
  168. ]);
  169. $db->commit();
  170. // 触发售后统计
  171. $refund_event = ['id' => $order_refund_model->id, 'type' => EnumsRefundTypeEnum::EVENT_APPLY];
  172. Yii::$app->services->rabbitMq->push(
  173. RabbitMqEnum::EXCHANGE_TASK, RabbitMqEnum::TASK_QUEUE, [
  174. 'type' => EventNameEnum::ORDER_REFUND,
  175. 'params' => $refund_event,
  176. ], StatisticsHandle::class, 'handleSync'
  177. );
  178. // 自动推送售后
  179. Yii::$app->services->rabbitMq->push(
  180. RabbitMqEnum::EXCHANGE_TASK, RabbitMqEnum::TASK_QUEUE, [
  181. 'mall_id' => $refund->mall_id,
  182. 'id' => $refund->id,
  183. ],
  184. self::class, 'autoPush'
  185. );
  186. return $order_refund_model;
  187. } catch (Exception $e) {
  188. $db->rollback();
  189. return $this->error($e->getMessage());
  190. }
  191. }
  192. /**
  193. * 驳回申请
  194. *
  195. * @param array $ids
  196. * @return integer
  197. */
  198. public function reject($ids)
  199. {
  200. $list = OrderRefund::find()->where(['id' => $ids, 'status' => OrderRefundEnum::NOT_PUSHED])->all();
  201. $num = 0;
  202. foreach ($list as $v) {
  203. $db = Yii::$app->db->beginTransaction();
  204. try {
  205. self::refuse($v);
  206. $db->commit();
  207. $num++;
  208. } catch (Exception $e) {
  209. $db->rollback();
  210. $v->error_msg = $e->getMessage();
  211. $v->save();
  212. }
  213. }
  214. return $num;
  215. }
  216. /**
  217. * 同意申请
  218. *
  219. * @param array $ids [1,2,3]
  220. * @return void
  221. */
  222. public function agree($ids)
  223. {
  224. $list = OrderRefund::find()->where(['id' => $ids, 'status' => OrderRefundEnum::NOT_PUSHED])->all();
  225. $num = 0;
  226. /**
  227. * @var OrderRefund
  228. */
  229. foreach ($list as $v) {
  230. $db = Yii::$app->db->beginTransaction();
  231. try {
  232. $res = $this->push($v);
  233. $v->pushOk($res);
  234. $num++;
  235. $db->commit();
  236. } catch (Exception $e) {
  237. $db->rollback();
  238. $v->error_msg = $e->getMessage();
  239. $v->save();
  240. }
  241. }
  242. return $num;
  243. }
  244. /**
  245. * 推送申请
  246. *
  247. * @param OrderRefund $model
  248. * @return void
  249. */
  250. public function push(OrderRefund $model)
  251. {
  252. // 聚合订单
  253. $chain_order = OrderItem::find()
  254. ->where(['mall_order_id' => $model->order_id, 'sku_id' => $model->sku_id])
  255. ->one();
  256. // 商城退款订单
  257. $mall_refund_order = MallOrderRefund::find()->where(['id' => $model->order_refund_id])->one();
  258. // 提交到聚合供应链
  259. $params = $this->getParams($model, $chain_order, $mall_refund_order);
  260. \Yii::error('-------- 推送售后订单 --------');
  261. \Yii::error(Json::encode($params));
  262. return $this->api->applyRefund($params);
  263. }
  264. /**
  265. * 获取供应链售后参数
  266. *
  267. * @param OrderRefund $model
  268. * @param OrderItem $chain_order
  269. * @param MallOrderRefund $mall_refund_order
  270. * @return array
  271. */
  272. private function getParams($model, $chain_order, $mall_refund_order)
  273. {
  274. $data = Json::decode($model->data);
  275. $params = [
  276. 'reason' => $mall_refund_order->reason,
  277. 'orderSn' => $chain_order->chain_order_sn,
  278. 'refundReasonId' => $data['reason_id'],
  279. 'goodsList' => [
  280. [
  281. 'goodsQuantity' => $model->num,
  282. 'goodsSpecId' => $model->sku_id,
  283. ]
  284. ],
  285. 'returnType' => /* RefundTypeEnum::typeChange */($data['type']),
  286. ];
  287. $mall_refund_order->remark && $params['memo'] = $mall_refund_order->remark;
  288. Json::decode($mall_refund_order->pic_list) && $params['pictureurlList'] = Json::decode($mall_refund_order->pic_list);
  289. return $params;
  290. }
  291. /**
  292. * 驳回
  293. *
  294. * @param OrderRefund $model
  295. * @return void
  296. */
  297. public static function refuse(OrderRefund $model)
  298. {
  299. $model->status = OrderRefundEnum::REFUSE;
  300. $res = $model->save();
  301. if ($res === false) throw new Exception($model->getError());
  302. $res = OrderRefundLogic::refuseRefundForChain($model->mall_id, $model->order_refund_id);
  303. if ($res === false) throw new Exception(MallOrderRefund::$static_error);
  304. }
  305. /**
  306. * 售后状态变更
  307. *
  308. * @param string $return_sn
  309. * @param integer $status
  310. * @return boolean
  311. */
  312. public function statusChange($return_sn, $status)
  313. {
  314. try {
  315. /**
  316. * @var OrderRefund|null
  317. */
  318. $refund_order = OrderRefund::getOrderByReturnSn($this->mall_id, $return_sn);
  319. if (empty($refund_order)) {
  320. throw new Exception("售后订单{$return_sn}不存在");
  321. }
  322. switch ($status) {
  323. // 审核中
  324. case '1':
  325. $refund_order->review();
  326. break;
  327. // 卖家拒绝退款
  328. case '2':
  329. $refund_order->refuseMoney();
  330. break;
  331. // 退款成功
  332. case '3':
  333. $refund_order->success();
  334. break;
  335. // 卖家拒绝退货退款
  336. case '4':
  337. $refund_order->refuseMoneyAndProduct();
  338. break;
  339. // 待买家退货
  340. case '5':
  341. $refund_detail = $this->api->orderReturnGoodsDetail($return_sn);
  342. $refund_order->waitBuyerSend($refund_detail);
  343. break;
  344. // 买家已退货,待卖家收货
  345. case '6':
  346. $refund_order->waitSellerReceive();
  347. break;
  348. // 买家已退货,卖家拒绝收货
  349. case '7':
  350. $refund_order->sellerRefuseReceive();
  351. break;
  352. // 卖家已收货,待确认退款
  353. case '8':
  354. $refund_order->sellerReceiveOfWait();
  355. break;
  356. // 退货退款成功
  357. case '9':
  358. $refund_order->successMoneyAndProduct();
  359. break;
  360. // 卖家拒绝换货
  361. case '10':
  362. $refund_order->refuseExchange();
  363. break;
  364. // 买家已退货,待卖家换货
  365. case '11':
  366. $refund_order->waitSellerSend();
  367. break;
  368. // 卖家已换货,待买家收货
  369. case '12':
  370. $refund_detail = $this->api->orderReturnGoodsDetail($return_sn);
  371. $refund_order->waitBuyerReceive($refund_detail);
  372. break;
  373. // 换货成功
  374. case '13':
  375. $refund_order->successExchange();
  376. break;
  377. // 已关闭
  378. case '14':
  379. $refund_order->close();
  380. break;
  381. // 卖家同意退款
  382. case '15':
  383. $refund_order->adoptMoney();
  384. break;
  385. // 卖家同意仅退款
  386. case '16':
  387. $refund_order->adoptOnlyMoney();
  388. break;
  389. // 卖家拒绝仅退款
  390. case '17':
  391. $refund_order->refuseOnlyMoney();
  392. break;
  393. // 仅退款成功
  394. case '18':
  395. $refund_order->successOnlyMoney();
  396. break;
  397. default:
  398. break;
  399. }
  400. } catch (Exception $e) {
  401. return $this->error("售后订单{$return_sn}, 售后失败, 状态为{$status}, 失败原因:" . $e->getMessage());
  402. }
  403. return true;
  404. }
  405. /**
  406. * 售后处理
  407. *
  408. * @param integer $refund_id
  409. * @return void
  410. */
  411. public function buyerDelivery($refund_id)
  412. {
  413. $chain_refund_sn = OrderRefund::find()->where(['order_refund_id' => $refund_id])->select('chain_refund_sn')->scalar();
  414. if ($chain_refund_sn) {
  415. $refund_order = MallOrderRefund::find()->where(['id' => $refund_id])->select('id, express, express_no')->one();
  416. try {
  417. // 都不为空的时候调用
  418. !empty($refund_order)
  419. && $refund_order->express_no
  420. && $refund_order->express
  421. && $this->api->sendReturnGoods(
  422. $chain_refund_sn,
  423. $refund_order->express_no,
  424. $refund_order->express
  425. );
  426. } catch (Exception $e) {
  427. Yii::error("");
  428. }
  429. }
  430. }
  431. /**
  432. * 取消订单
  433. *
  434. * @param integer $id
  435. * @return boolean
  436. */
  437. public function cancel($id)
  438. {
  439. $model = OrderRefund::find()->where(['order_refund_id' => $id, 'status' => OrderRefundEnum::NOT_PUSHED])->one();
  440. return self::refuse($model);
  441. }
  442. /**
  443. * 售后订单收货完成
  444. *
  445. * @param integer $id
  446. * @return boolean
  447. */
  448. public function finish($id)
  449. {
  450. $model = OrderRefund::find()->where(['order_refund_id' => $id])->one();
  451. if (empty($model)) return false;
  452. $this->api->receiptRefund($model->chain_refund_sn);
  453. }
  454. /**
  455. * 自动推送售后
  456. *
  457. * @param array $params
  458. * @return boolean
  459. */
  460. public static function autoPush($params = [])
  461. {
  462. if (empty($mall_id = $params['mall_id']) || empty($id = $params['id'])) return true;
  463. // 获取设置
  464. $setting = Setting::getSetting($mall_id);
  465. if (empty($setting) || empty($setting['refund_auto_push'])) return true;
  466. try {
  467. /**
  468. * @var OrderRefund
  469. */
  470. $model = OrderRefund::find()->where(['id' => $id])->one();
  471. $res = (new static(['mall_id' => $mall_id]))->push($model);
  472. $model->pushOk($res);
  473. } catch (Exception $e) {
  474. echo '文件: ' . $e->getFile() . ', 错误行数: ' . $e->getLine() . ', 错误信息: ' . $e->getMessage();
  475. }
  476. return true;
  477. }
  478. }