RefundCallbackService.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. <?php
  2. namespace addons\WechatVideoShop\common\service;
  3. use addons\WechatVideoShop\common\expand\sdk\weixin\src\enums\RefundEnum;
  4. use addons\WechatVideoShop\common\expand\sdk\weixin\src\model\basic\GetMediaModel;
  5. use addons\WechatVideoShop\common\expand\sdk\weixin\src\model\refund\RefundInfoModel;
  6. use addons\WechatVideoShop\common\expand\sdk\weixin\src\request\basic\GetMediaRequest;
  7. use addons\WechatVideoShop\common\expand\sdk\weixin\src\request\refund\RefundInfoRequest;
  8. use addons\WechatVideoShop\common\helper\WechatRequestHelper;
  9. use addons\WechatVideoShop\common\models\WechatVideoShop;
  10. use addons\WechatVideoShop\common\models\WechatVideoShopExpressCompany;
  11. use addons\WechatVideoShop\common\models\WechatVideoShopOrder;
  12. use addons\WechatVideoShop\common\models\WechatVideoShopRefund;
  13. use common\enums\OrderStatusEnum;
  14. use common\enums\RefundStatusEnum;
  15. use common\events\order\OrderRefundEvent;
  16. use common\helpers\FormatHelper;
  17. use common\logic\order\OrderRefundLogic;
  18. use common\models\goods\Goods;
  19. use common\models\order\Order;
  20. use common\models\order\OrderDetail;
  21. use common\models\order\OrderRefund;
  22. use common\models\order\OrderRefundStep;
  23. use common\models\user\UserInfo;
  24. use Yii;
  25. use yii\helpers\Json;
  26. class RefundCallbackService extends AbstractService
  27. {
  28. /**
  29. * 这边退款不需要退钱
  30. * @param array $event
  31. * @return void
  32. * @throws \Exception
  33. */
  34. public function update(array $event)
  35. {
  36. $resp = $this->getRefundDetail($event['finder_shop_aftersale_status_update']['after_sale_order_id']);
  37. // 判断是否有同步当前退款,没有的话那么就需要记录
  38. $status = $resp['after_sale_order']['status'] ?? null;
  39. switch ($status) {
  40. case RefundEnum::AFTER_SALE_STATUS_USER_CANCELD: // 用户取消申请
  41. $refund = $this->cancel($resp['after_sale_order'] ?? []);
  42. break;
  43. case RefundEnum::AFTER_SALE_STATUS_MERCHANT_PROCESSING: // 商家受理中
  44. // $this->create($resp['after_sale_order'] ?? []);
  45. break;
  46. case RefundEnum::AFTER_SALE_STATUS_MERCHANT_REJECT_REFUND: // 商家拒绝退款
  47. $refund = $this->reject($resp['after_sale_order'] ?? []);
  48. break;
  49. case RefundEnum::AFTER_SALE_STATUS_MERCHANT_REJECT_RETURN: // 商家拒绝退货退款
  50. $refund = $this->reject($resp['after_sale_order'] ?? []);
  51. break;
  52. case RefundEnum::AFTER_SALE_STATUS_USER_WAIT_RETURN: // 待买家退货
  53. $refund = $this->agree($resp['after_sale_order'] ?? []);
  54. break;
  55. case RefundEnum::AFTER_SALE_STATUS_RETURN_CLOSED: // 退货退款关闭
  56. $refund = $this->cancel($resp['after_sale_order'] ?? []);
  57. break;
  58. case RefundEnum::AFTER_SALE_STATUS_MERCHANT_WAIT_RECEIPT: // 待商家收货
  59. $refund = $this->buyerShip($resp['after_sale_order'] ?? []);
  60. break;
  61. case RefundEnum::AFTER_SALE_STATUS_MERCHANT_OVERDUE_REFUND: // 商家逾期未退款
  62. break;
  63. case RefundEnum::AFTER_SALE_STATUS_MERCHANT_REFUND_SUCCESS: // 退款完成
  64. break;
  65. case RefundEnum::AFTER_SALE_STATUS_MERCHANT_RETURN_SUCCESS: // 退货退款完成
  66. break;
  67. case RefundEnum::AFTER_SALE_STATUS_PLATFORM_REFUNDING: // 平台退款中
  68. break;
  69. case RefundEnum::AFTER_SALE_STATUS_PLATFORM_REFUND_FAIL: // 平台退款失败
  70. break;
  71. case RefundEnum::AFTER_SALE_STATUS_USER_WAIT_CONFIRM: // 待用户确认
  72. break;
  73. case RefundEnum::AFTER_SALE_STATUS_MERCHANT_REFUND_RETRY_FAIL: // 商家打款失败,客服关闭售后
  74. $refund = $this->cancel($resp['after_sale_order'] ?? []);
  75. break;
  76. case RefundEnum::AFTER_SALE_STATUS_MERCHANT_FAIL: // 售后关闭
  77. $refund = $this->cancel($resp['after_sale_order'] ?? []);
  78. break;
  79. }
  80. if (!empty($refund)) {
  81. $refund->content = $resp['after_sale_order'];
  82. if (!$refund->save()) { // 修改数据
  83. throw new \Exception($refund->getErrorMessage());
  84. }
  85. }
  86. }
  87. /**
  88. * 申请售后
  89. * @return void
  90. * @throws \Exception
  91. */
  92. protected function create(array $resp)
  93. {
  94. if (empty($resp)) throw new \Exception("售后订单数据为空");
  95. // 取出订单
  96. $user_id = UserInfo::find()->where(['mall_id' => $this->mall_id])
  97. ->andWhere(['openid' => $resp['openid']])->select('user_id')->scalar();
  98. if (empty($user_id)) throw new \Exception("用户不存在: {$resp['openid']}");
  99. // $order_ids = WechatVideoShopOrder::find()
  100. // ->where(['third_order_id' => $resp['order_id']])->select('order_id')->column();
  101. // if (empty($order_ids)) throw new \Exception("订单不存在: {$resp['order_id']}");
  102. $orders = WechatVideoShopOrder::lists([
  103. 'where' => [
  104. ['third_order_id' => $resp['order_id']]
  105. ],
  106. 'index' => 'order_id',
  107. 'select'=> 'order_id, content'
  108. ]);
  109. // SKU ID 不一致
  110. if (empty($orders)) throw new \Exception("订单不存在: {$resp['order_id']}");
  111. $order_id = 0;
  112. $sku_id = 0;
  113. foreach ($orders as $key => $order) {
  114. $content = Json::decode($order['content']);
  115. // 找出包含 $resp['product_info']['sku_id'] 的那一行数据
  116. $product_infos = $content['order_detail']['product_infos'];
  117. $tmp_skus = array_column($product_infos, "out_sku_id", "sku_id");
  118. if (!empty($tmp_skus[$resp['product_info']['sku_id']])) {
  119. $order_id = $key;
  120. $sku_id = $tmp_skus[$resp['product_info']['sku_id']];
  121. }
  122. }
  123. if (empty($order_id)) throw new \Exception("未找到相关SKU: {$resp['product_info']['sku_id']}");
  124. // 取出订单商品ID
  125. $orderDetail = OrderDetail::getOne([
  126. ['order_id' => $order_id],
  127. ['goods_attr_id' => $sku_id],
  128. ['user_id' => $user_id]
  129. ]);
  130. if (empty($orderDetail)) throw new \Exception("订单详情不存在: $sku_id");
  131. // order_detail_id, shipping_status, pic_list, reason, refund_price, remark, type
  132. // 获取media
  133. $pic_list = [];
  134. if (!empty($resp['details']['media_id_list'])) {
  135. foreach ($resp['details']['media_id_list'] as $mediaId) {
  136. $mediaModel = new GetMediaModel();
  137. $mediaModel->media_id = $mediaId;
  138. $pic_list[] = WechatRequestHelper::accessTokenAndToArray(new GetMediaRequest([]), $mediaModel);
  139. }
  140. }
  141. $params['user_id'] = $user_id;
  142. $params['mall_id'] = $this->mall_id;
  143. $params['order_detail_id'] = $orderDetail->id;
  144. $params['pic_list'] = Json::encode($pic_list);
  145. $params['refund_price'] = bcmul($resp['refund_info']['amount'], 0.01, 2);
  146. $params['reason'] = $resp['reason_text'] ?? '未知理由';
  147. $params['shipping_status'] = $orderDetail->order_status == OrderStatusEnum::PAY ? 0 : 1; // 是否已经发货
  148. $params['type'] = $resp['type'] == 'REFUND' ? 1 : 2; // 售后类型:1=仅退款,2=退货退款,3=换货 // 售后类型。REFUND:退款;RETURN:退货退款。
  149. $params['remark'] = "{$resp['details']['desc']}, 联系电话: [{$resp['details']['tel_number']}]";
  150. // 退货数量
  151. // 退款金额
  152. $where = [
  153. ['=', 'order_detail_id', $params['order_detail_id']],
  154. ['=', 'user_id', $user_id],
  155. ['=', 'mall_id', $this->mall_id],
  156. ['>', 'step_status', 0],
  157. ['<>', 'refund_status', RefundStatusEnum::REVOKE]
  158. ];
  159. $order_refund_model = OrderRefund::getOne($where, true);
  160. if ($order_refund_model) throw new \Exception('该订单商品已经申请过售后');
  161. $res = OrderRefund::feedback($params);
  162. if (!$res) throw new \Exception(OrderRefund::getStaticError());
  163. // 这里需要怎么处理呢?
  164. $refundModel = new WechatVideoShopRefund();
  165. $refundModel->mall_id = $this->mall_id;
  166. $refundModel->shop_id = $this->shop_id;
  167. $refundModel->user_id = $user_id;
  168. $refundModel->order_id = $orderDetail->order_id;
  169. $refundModel->third_refund_id = $resp['after_sale_order_id'];
  170. $refundModel->third_order_id = $resp['order_id'];
  171. $refundModel->refund_id = $res->id;
  172. $refundModel->content = $resp;
  173. $refundModel->refund_certificates = !empty($resp['details']['media_id_list']) ? $resp['details']['media_id_list'] : [];
  174. $refundModel->refund_desc = $resp['details']['desc'];
  175. if (!$refundModel->save()) {
  176. throw new \Exception($refundModel->getErrorMessage());
  177. }
  178. // 触发事件
  179. $event = new OrderRefundEvent();
  180. $event->mall_id = $this->mall_id;
  181. $order_detail['order_detail_id'] = $params['order_detail_id'];
  182. $order_detail['id'] = $res->id;
  183. $order_detail['operator_id'] = 0;
  184. $order_detail['operator_name'] = OrderCallbackService::OPERATOR_NAME;
  185. Yii::$app->services->events->dispatch($event, $order_detail, $event->listeners);
  186. }
  187. /**
  188. * @param $third_refund_id
  189. * @return WechatVideoShopRefund
  190. * @throws \Exception
  191. */
  192. protected function getLocalRefund($third_refund_id)
  193. {
  194. $refund = WechatVideoShopRefund::getOne([
  195. ['third_refund_id' => $third_refund_id]
  196. ]);
  197. if (empty($refund)) throw new \Exception("退款单号不存在: {$third_refund_id}");
  198. return $refund;
  199. }
  200. /**
  201. * 取消售后
  202. * @return WechatVideoShopRefund
  203. * @throws \Exception
  204. */
  205. protected function cancel(array $resp)
  206. {
  207. $refund = $this->getLocalRefund($resp['after_sale_order_id']);
  208. $params['mall_id'] = $this->mall_id;
  209. $params['user_id'] = $refund->user_id;
  210. $params['id'] = $refund->refund_id;
  211. $res = OrderRefund::cancelRefund($params);
  212. if ($res === false) throw new \Exception(OrderRefund::getStaticError());
  213. return $refund;
  214. }
  215. /**
  216. * 买家发货
  217. * @return WechatVideoShopRefund
  218. * @throws \Exception
  219. */
  220. protected function buyerShip(array $resp)
  221. {
  222. $refund = $this->getLocalRefund($resp['after_sale_order_id']);
  223. $params['mall_id'] = $this->mall_id;
  224. $params['user_id'] = $refund->user_id;
  225. // 快递公司
  226. $company = WechatVideoShopExpressCompany::getOne([
  227. ['delivery_id' => $resp['return_info']['delivery_id']]
  228. ]);
  229. $params['express'] = !empty($company) ? $company->delivery_name : "其他"; // 默认其他快递
  230. $params['express_id'] = !empty($company) ? $company->company_id : 10000; // 默认其他快递
  231. // 快递单号
  232. $params['express_no'] = $resp['return_info']['waybill_id'];
  233. $params['id'] = $refund->refund_id;
  234. $res = OrderRefundStep::refundSend($params);
  235. if ($res === false) throw new \Exception(OrderRefundStep::getStaticError());
  236. return $refund;
  237. }
  238. /**
  239. * 买家收货 - 买家不需要收货
  240. * @return WechatVideoShopRefund
  241. * @throws \Exception
  242. */
  243. protected function buyerSing(array $resp)
  244. {
  245. $refund = $this->getLocalRefund($resp['after_sale_order_id']);
  246. $params['mall_id'] = $this->mall_id;
  247. $params['user_id'] = $refund->user_id;
  248. $params['id'] = $refund->refund_id;
  249. $res = OrderRefundStep::refundTakeDelivery($params);
  250. if ($res === false) throw new \Exception(OrderRefundStep::getStaticError());
  251. return $refund;
  252. }
  253. /**
  254. * 卖家发货 - 卖家不需要发货
  255. * @return WechatVideoShopRefund
  256. * @throws \Exception
  257. */
  258. protected function sellerShip(array $resp)
  259. {
  260. // 卖家发货
  261. $refund = $this->getLocalRefund($resp['after_sale_order_id']);
  262. $this->handle([
  263. 'id' => $refund->refund_id,
  264. 'step_status' => -1
  265. ]);
  266. return $refund;
  267. }
  268. /**
  269. * 同意售后
  270. * @return WechatVideoShopRefund
  271. * @throws \Exception
  272. */
  273. protected function agree(array $resp)
  274. {
  275. $refund = $this->getLocalRefund($resp['after_sale_order_id']);
  276. $params = [];
  277. switch ($resp['type']) {
  278. case 'REFUND':
  279. // id: 108
  280. // step_status: 6
  281. $params['step_status'] = RefundStatusEnum::STEP_6;
  282. $params['id'] = $refund->refund_id;
  283. break;
  284. case 'RETURN':
  285. // address_type: 1
  286. // consignee: 林锦豪
  287. // mobile: 13246824705
  288. // address: 广州市白云区永平街永泰学山塘街81号A2栋618
  289. // express_id:
  290. // saler_express:
  291. // saler_express_no:
  292. // customer_name:
  293. // id: 105
  294. // step_status: 2
  295. $shop = WechatVideoShop::getOne([
  296. ['id' => $refund->shop_id]
  297. ]);
  298. $params = [
  299. 'address_type' => 2,
  300. 'consignee' => $shop->contact_person,
  301. 'mobile' => $shop->phone,
  302. 'address' => "{$shop->province}{$shop->city}{$shop->district}{$shop->address}",
  303. 'id' => $refund->refund_id,
  304. 'step_status' => RefundStatusEnum::STEP_2, // 第几步,如果是仅退款是第几步,如果是退货退款那么是第几步
  305. ];
  306. break;
  307. }
  308. if (empty($params)) throw new \Exception("退款类型未找到: {$resp['type']}");
  309. $this->handle($params);
  310. return $refund;
  311. }
  312. /**
  313. * 拒绝售后
  314. * @return WechatVideoShopRefund
  315. * @throws \Exception
  316. */
  317. protected function reject(array $resp)
  318. {
  319. // id: 106
  320. // step_status: -1
  321. $refund = $this->getLocalRefund($resp['after_sale_order_id']);
  322. $this->handle([
  323. 'id' => $refund->refund_id,
  324. 'step_status' => RefundStatusEnum::REFUSE
  325. ]);
  326. return $refund;
  327. }
  328. /**
  329. * @param array $params
  330. * @return void
  331. * @throws \Exception
  332. */
  333. protected function handle(array $params)
  334. {
  335. // 退货地址
  336. $address = ['consignee' => $params['consignee'] ?? '','mobile' => $params['mobile'] ?? '','address' => $params['address'] ?? ''];
  337. // 换货物流信息
  338. $express = ['saler_express' => $params['saler_express'] ?? '',
  339. 'saler_express_no' => $params['saler_express_no'] ?? '', 'customer_name' => $params['customer_name'] ?? ''];
  340. $data = [
  341. 'id' => $params['id'],
  342. 'step_status' => $params['step_status'], // 在这里处理
  343. 'address' => $address,
  344. 'express' => $express,
  345. 'is_async' => true, // 不退钱
  346. 'is_trigger_update_event' => true,
  347. ];
  348. $logic = new OrderRefundLogic();
  349. $res = $logic->execute($this->mall_id, $data);
  350. if (!$res) throw new \Exception($logic->getError());
  351. }
  352. /**
  353. * 卖家收货
  354. * @return WechatVideoShopRefund
  355. * @throws \Exception
  356. */
  357. protected function sellerSing(array $resp)
  358. {
  359. $refund = $this->getLocalRefund($resp['after_sale_order_id']);
  360. $this->handle([
  361. 'id' => $refund->refund_id,
  362. 'step_status' => 4
  363. ]);
  364. return $refund;
  365. }
  366. /**
  367. * @param $after_sale_order_id
  368. * @param array $config
  369. * @return array
  370. * @throws \Exception
  371. */
  372. public function getRefundDetail($after_sale_order_id, array $config = [])
  373. {
  374. $refundInfoModel = new RefundInfoModel();
  375. $refundInfoModel->after_sale_order_id = $after_sale_order_id;
  376. $resp = WechatRequestHelper::accessTokenAndToArray(new RefundInfoRequest(!empty($config) ? $config : $this->config), $refundInfoModel);
  377. if (empty($resp['after_sale_order'])) throw new \Exception('获取售后详情失败');
  378. return $resp;
  379. }
  380. /**
  381. * @param array $config
  382. * @return void
  383. * @throws \Exception
  384. */
  385. public function syncCreate(array $config)
  386. {
  387. try {
  388. $this->mall_id = $config['mall_id'];
  389. $this->shop_id = $config['id'];
  390. $resp = $this->getRefundDetail($config['after_sale_order_id'], $config);
  391. $this->create($resp['after_sale_order'] ?? []);
  392. } catch (\Exception $e) {
  393. \Yii::error(FormatHelper::exception($e, __METHOD__));
  394. }
  395. }
  396. }