OrderRefundLogic.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. <?php
  2. namespace addons\UpgradeGift\common\logic\order;
  3. use addons\Store\common\models\StoreGoodsReserve;
  4. use addons\Store\common\models\StoreOrder;
  5. use addons\Store\common\models\StoreOrderDetail;
  6. use addons\Store\common\models\StoreOrderMarketing;
  7. use addons\ValetOrder\common\service\OrderService;
  8. use addons\WangDianTong\common\service\OrderRefundRejectService;
  9. use addons\CardCoupons\common\models\CardCouponsScoreGive;
  10. use common\enums\AddonsEnum;
  11. use common\enums\EventNameEnum;
  12. use common\enums\OrderSourceEnum;
  13. use common\enums\OrderStatusEnum;
  14. use common\enums\PaymentTradeOrderEnum;
  15. use common\enums\RefundStatusEnum;
  16. use common\enums\RefundTypeEnum;
  17. use common\enums\StatusEnum;
  18. use common\enums\UserWalletEnum;
  19. use common\events\order\OrderRefundChangeEvent;
  20. use common\events\order\OrderRefundFinishEvent;
  21. use common\events\order\OrderRefundUpdateEvent;
  22. use common\logic\statistics\StatisticsHandle;
  23. use common\logic\wechat\WechatMessageLogic;
  24. use common\models\common\PaymentTrade;
  25. use common\models\common\PaymentTradeOrder;
  26. use common\models\goods\GoodsReserve;
  27. use common\models\mall\MallAddons;
  28. use common\models\order\Order;
  29. use common\models\order\OrderDetail;
  30. use common\models\order\OrderMarketing;
  31. use common\models\order\OrderRefund;
  32. use common\models\order\OrderRefundStep;
  33. use common\models\common\Addons;
  34. use common\traits\ErrorTrait;
  35. use Yii;
  36. use Exception;
  37. use services\common\UserWalletService;
  38. use yii\helpers\Json;
  39. use common\enums\PayTypeEnum;
  40. use common\globals\GlobalInvoke;
  41. use services\common\ThirdPartyRefundService;
  42. /**
  43. * 售后订单处理
  44. * Class OrderRefundLogic
  45. * @Author: lun
  46. * @DateTime: 2021/10/11 0011 14:11
  47. * @Copyright: copyright (c) 2021 广东七件事集团
  48. * @package addons\UpgradeGift\common\logic\order
  49. */
  50. class OrderRefundLogic
  51. {
  52. use ErrorTrait;
  53. /**
  54. * 售后处理
  55. * @Author: lun
  56. * @DateTime: 2021/10/11 0011 15:55
  57. * @Copyright: copyright (c) 2021 广东七件事集团
  58. * @param $mallId
  59. * @param $refundId
  60. * @param $stepStatus
  61. * @return string
  62. */
  63. public function execute($mallId, $params)
  64. {
  65. $trans = Yii::$app->db->beginTransaction();
  66. try {
  67. $refund = OrderRefund::findOne(['id' => $params['id'], 'mall_id' => $mallId]);
  68. if(empty($refund)) throw new Exception("售后订单不存在或已删除");
  69. if ($refund->isRefundFinish()) throw new Exception('当前订单已售后');
  70. switch ($params['step_status']) {
  71. case RefundStatusEnum::STEP_2:// 退货退款:已同意退货申请,等待买家处理
  72. $refund->saler_address = Json::encode($params['address']);
  73. $refund->refund_status = RefundStatusEnum::HANDEL;
  74. break;
  75. case RefundStatusEnum::STEP_4:// 退货退款:退货完成
  76. $refund->refund_status = RefundStatusEnum::FINISH;
  77. break;
  78. case RefundStatusEnum::STEP_6:// 仅退款:退款完成
  79. $refund->refund_status = RefundStatusEnum::FINISH;
  80. break;
  81. case RefundStatusEnum::STEP_8:// 换货:已同意换货申请,等待买家处理
  82. $refund->saler_address = Json::encode($params['address']);
  83. $refund->refund_status = RefundStatusEnum::HANDEL;
  84. break;
  85. case RefundStatusEnum::STEP_10:// 换货:已重新发货
  86. $refund->saler_express = $params['express']['saler_express'];
  87. $refund->saler_express_no = $params['express']['saler_express_no'];
  88. $refund->customer_name = $params['express']['customer_name'] ?? '';
  89. $refund->refund_status = RefundStatusEnum::EXCHANGE_GOODS;
  90. $refund->saler_express_at = time();// 重新发货时间
  91. break;
  92. case RefundStatusEnum::STEP_NEGATIVE_1:// 拒绝售后
  93. $refund->refund_status = RefundStatusEnum::FINISH;
  94. break;
  95. case RefundStatusEnum::STEP_NEGATIVE_2:// 拒绝收货
  96. $refund->refund_status = RefundStatusEnum::REFUSE;
  97. break;
  98. }
  99. $refund_price = $refund->refund_price;
  100. // 自动维护主订单
  101. OrderDetail::updateAll(['is_feedback' => OrderStatusEnum::getIsFeedbackStatus($params['step_status']), 'updated_at' => time()], ['id' => $refund->order_detail_id]);
  102. $order = Order::getOne([['id' => $refund->order_id]]);
  103. $order_status = $order->order_status;
  104. $is_install = MallAddons::isInstall($mallId, AddonsEnum::ADDONS_NAME_STORE);
  105. Order::autoUpdateStatus($order);
  106. if($is_install){
  107. $store_order = StoreOrder::findOne(['order_no'=>$order['order_no']]);
  108. if(!empty($store_order)){
  109. $order['out_trade_no'] = $store_order['out_trade_no'];
  110. StoreOrderDetail::updateAll(['is_feedback' => OrderStatusEnum::getIsFeedbackStatus($params['step_status']), 'updated_at' => time()], ['main_order_detail_id' => $refund->order_detail_id]);
  111. StoreOrder::autoUpdateStatus($store_order);
  112. }
  113. }
  114. // 操作退款-退货退款,仅退款
  115. if (in_array($params['step_status'], [RefundStatusEnum::STEP_4, RefundStatusEnum::STEP_6]) &&
  116. $order->marketing_type != AddonsEnum::ADDONS_NAME_WECHAT_VIDEO_SHOP) {
  117. // 判断是否为预约订单
  118. if (in_array($order->order_source, [OrderSourceEnum::SOURCE_RESERVE, OrderSourceEnum::SOURCE_STORE_RESERVE]) && empty($params['not_dec_fee'])) {
  119. // 如果是预约订单那么就需要判断是否需要扣点
  120. $orderDetail = OrderDetail::findOne(['id' => $refund->order_detail_id]);
  121. if ($order->order_source == OrderSourceEnum::SOURCE_STORE_RESERVE) {
  122. $goodsReserve = StoreGoodsReserve::findOne(['goods_id' => $orderDetail->goods_id]);
  123. } else {
  124. $goodsReserve = GoodsReserve::findOne(['goods_id' => $orderDetail->goods_id]);
  125. }
  126. $cancel_rule = !empty($goodsReserve->cancel_rule) ? Json::decode($goodsReserve->cancel_rule) : [];
  127. if (!empty($cancel_rule['cancel_fee_percent']) && in_array($order_status, [OrderStatusEnum::SHIPMENTS, OrderStatusEnum::SING])) {
  128. // 判断是否需要扣点 - 待服务/服务中
  129. $refund->refund_price -= bcmul(bcmul($refund->refund_price, $cancel_rule['cancel_fee_percent'], 2), 0.01, 2);
  130. }
  131. }
  132. $wallet_type = UserWalletService::WALLET_TYPE_BALANCE;
  133. if ($order->payment_type == PayTypeEnum::SCORE) $wallet_type = UserWalletService::WALLET_TYPE_SCORE;
  134. if ($refund->refund_price > 0) {// 只有退款金额大于0的情况下才可以进入
  135. // 是否为代客下单订单
  136. $is_refund_balance = MallAddons::isInstall($refund->mall_id, AddonsEnum::ADDONS_NAME_VALET_ORDER) && (new OrderService())->refund($order->id);
  137. $out_trade_no = $order['out_trade_no'];
  138. //是否为组合支付
  139. $payment_trade = PaymentTrade::findOne(['out_trade_no' => $out_trade_no]);
  140. if(!empty($payment_trade->is_pay_group)){
  141. $payment_group_trade_list = PaymentTrade::find()->where(['is_pay'=>1,'is_pay_group'=>1,'parent_trade_id'=>$payment_trade->id])->orderBy('id asc')->all();
  142. }else{
  143. $payment_group_trade_list[] = $payment_trade;
  144. }
  145. $all_refund_price = $refund->refund_price;
  146. foreach($payment_group_trade_list as $payment_group_trade){
  147. if($all_refund_price > $payment_group_trade->pay_fee){
  148. $refund_price = $payment_group_trade->pay_fee;
  149. }else{
  150. $refund_price = $all_refund_price;
  151. }
  152. if(in_array($payment_group_trade->pay_type, [PayTypeEnum::BALANCE, PayTypeEnum::SCORE]) || $is_refund_balance){
  153. // 本地支付
  154. $wallet_type = $payment_group_trade->pay_type == PayTypeEnum::SCORE ? UserWalletService::WALLET_TYPE_SCORE : UserWalletService::WALLET_TYPE_BALANCE;
  155. // 如果是代付,那么就需要原路退回
  156. $extra_refund_price = 0;//额外抵扣金额
  157. if($wallet_type == UserWalletService::WALLET_TYPE_BALANCE){
  158. //判断是否有卡券插件
  159. if (MallAddons::isInstall($refund->mall_id, AddonsEnum::ADDONS_NAME_CARD_COUPONS)) {
  160. $card_refund_price = $refund->refund_price - $payment_group_trade->pay_fee;
  161. $extra_refund_price = CardCouponsScoreGive::setOrderRefund([
  162. 'refund_price' => $card_refund_price,
  163. 'trade_id' => $payment_group_trade->id,
  164. 'refund_id' => $refund->id
  165. ]);
  166. }
  167. }
  168. $insert_wallet = [];
  169. $insert_wallet[] = [
  170. 'mall_id' => $refund->mall_id,
  171. 'user_id' => $refund->user_id,
  172. 'is_frozen' => false,
  173. 'wallet_type' => $wallet_type,// 暂定退回余额或积分
  174. 'money' => $refund_price,
  175. 'desc' => '订单售后退款',
  176. 'source_table' => OrderRefund::tableName(),
  177. 'source_table_id' => $refund->id,
  178. 'from_type' => UserWalletEnum::REFUND,
  179. 'capital_type' => UserWalletEnum::REFUND_ORDER,
  180. 'order_no' => $order->order_no,
  181. 'not_add_total' => false
  182. ];
  183. $res = UserWalletService::batchHandleByQueue($insert_wallet);
  184. if (empty($res)) throw new \Exception(UserWalletService::getStaticError());
  185. $refund_price += $extra_refund_price;
  186. } elseif(in_array($payment_group_trade->pay_type,[PayTypeEnum::JOINPAY,PayTypeEnum::ALI,PayTypeEnum::JOINPAY_FAST_PAY,PayTypeEnum::WECHAT,PayTypeEnum::SANDPAY_FAST_PAY])){
  187. //原路返回
  188. $res = Yii::$app->services->pay->refund($refund->refund_price,$refund_price, $payment_group_trade->out_trade_no, $order, '订单售后退款');
  189. if ($res === false) throw new \Exception('退款失败');
  190. } elseif ($order->payment_type == PayTypeEnum::OFFLINE) {
  191. // 此支付方式为后台管理员直接支付不做任何处理
  192. } elseif (
  193. // 当res返回为false 说明没有相对于的退款方式
  194. $res = GlobalInvoke::exec(GlobalInvoke::INVOKE_ORDER_REFUND, $mallId, [
  195. 'id' => $refund->id,
  196. 'mall_id' => $refund->mall_id,
  197. 'refund' => $refund,
  198. 'params' => $params,
  199. 'order_no' => $order['order_no'],
  200. 'refund_order_no' => $refund['order_no'],
  201. 'payment_trade_id' => $payment_group_trade->id,
  202. 'refund_price' => $refund_price,
  203. 'pay_type' => $order->payment_type,
  204. ])
  205. ) {
  206. // 当res不为空 进此判断
  207. list($boolean, $msg) = $res;
  208. if ($boolean === false) {
  209. $this->setError($msg);
  210. return false;
  211. }
  212. } else {
  213. }
  214. $all_refund_price -= $refund_price;
  215. $refund->reality_refund_price += $refund_price;
  216. if($all_refund_price <= 0) break;
  217. }
  218. }
  219. $refund->is_refund = StatusEnum::ENABLED;
  220. $refund->refund_at = time();
  221. $refund->refund_price = $refund_price;
  222. //$refund->reality_refund_price = $refund->refund_price;
  223. $order_detail_ids = array_column($order->detail,'id');
  224. // 订单营销扣减返还
  225. if(empty($order['store_id'])){
  226. $res = OrderMarketing::deductBack($order['id'],$order_detail_ids,[$refund['order_detail_id']],$refund->mall_id,$refund->user_id,'订单退款',UserWalletEnum::REFUND,UserWalletEnum::REFUND_ORDER);
  227. if ($res == false) throw new Exception(OrderMarketing::getStaticError());
  228. }else{
  229. if(MallAddons::isInstall($order['mall_id'],AddonsEnum::ADDONS_NAME_STORE)){
  230. $store_order = StoreOrder::findOne(['order_no'=>$order['order_no']]);
  231. $store_order_detail_ids = array_column($store_order->detail,'id');
  232. $store_order_detail_arr = array_column($store_order->detail,'id','main_order_detail_id');
  233. $store_order_detail_id = $store_order_detail_arr[$refund['order_detail_id']]??0;
  234. $res = StoreOrderMarketing::deductBack($store_order,$store_order_detail_ids,[$store_order_detail_id],$refund->mall_id,$refund->user_id,'订单退款',UserWalletEnum::REFUND,UserWalletEnum::REFUND_ORDER);
  235. if ($res == false) throw new Exception(StoreOrderMarketing::getStaticError());
  236. }
  237. }
  238. }
  239. // 保存步骤
  240. $params['payment_type'] = $order->payment_type;
  241. $step_status = self::saveStep($refund, $params);
  242. // 修改售后信息
  243. $refund->step_status = $step_status;
  244. if (!$refund->save()) throw new Exception("保存失败,msg=" . $refund->getErrorMessage());
  245. $trans->commit();
  246. // 触发售后统计
  247. if (in_array($params['step_status'], [RefundStatusEnum::STEP_4, RefundStatusEnum::STEP_6]) && empty($params['is_async'])) {
  248. $refund_event = ['id' =>$refund->id, 'type' => RefundTypeEnum::EVENT_FINISH];
  249. StatisticsHandle::execute(EventNameEnum::ORDER_REFUND, $refund_event);
  250. //触发订单售后完成
  251. $event = new OrderRefundFinishEvent($refund->mall_id);
  252. $data = [];
  253. $data['id'] = $refund->id;
  254. $data['source_table'] = 'order_detail';
  255. $data['source_table_id'] = $refund['order_detail_id'];
  256. $data['step_status'] = $params['step_status'];
  257. Yii::$app->services->events->dispatch($event, $data, $event->listeners);
  258. }
  259. if($params['step_status']==RefundStatusEnum::STEP_2){
  260. Yii::error('退货申请审核通过开始');
  261. try {
  262. $send_data = [
  263. 'mall_id' => $order['mall_id'],
  264. 'user_id' => $order['user_id'],
  265. 'order_id' => $order['id'],
  266. 'refund_id' => $refund->id,
  267. 'created_at' => $order['created_at'],
  268. 'order_no' => $order['order_no'],
  269. 'mobile' => $order['mobile'],
  270. 'address' => $order['region_name'].$order['address'],
  271. 'receiver_name' => $order['receiver_name'],
  272. ];
  273. $obj = new WechatMessageLogic();
  274. $obj->orderRefundAgree($send_data);
  275. }catch (\Exception $e){
  276. Yii::error('退货申请审核通过Exception:'.$e->getMessage());
  277. }
  278. Yii::error('退货申请审核通过结束');
  279. }
  280. // 拒绝 - 需要处理ERP
  281. if ($params['step_status'] == RefundStatusEnum::REFUSE && MallAddons::isInstall($order['mall_id'],AddonsEnum::ADDONS_NAME_WANG_DIAN_TONG)) {
  282. (new OrderRefundRejectService())->sync($order['mall_id'], $refund->id);
  283. }
  284. // 是否需要触发更新事件
  285. if (!empty($params['is_trigger_update_event'])) {
  286. // 退款ID & 退款状态
  287. $event = new OrderRefundUpdateEvent($refund->mall_id);
  288. $data = [];
  289. $data['id'] = $refund->id;
  290. $data['step_status'] = $params['step_status'];
  291. Yii::$app->services->events->dispatch($event, $data, $event->listeners);
  292. }
  293. $event = new OrderRefundChangeEvent($refund->mall_id);
  294. $data = [];
  295. $data['id'] = $refund->id;
  296. $data['step_status'] = $params['step_status'];
  297. Yii::$app->services->events->dispatch($event, $data, $event->listeners);
  298. return true;
  299. } catch (Exception $e) {
  300. $trans->rollBack();
  301. $this->setError($e->getMessage());
  302. return false;
  303. }
  304. }
  305. /**
  306. * 保存步骤
  307. * @Author: lun
  308. * @DateTime: 2021/10/11 0011 15:55
  309. * @Copyright: copyright (c) 2021 广东七件事集团
  310. * @param $refund
  311. * @param $stepStatus
  312. * @param array $params
  313. */
  314. public static function saveStep($refund, $params = [])
  315. {
  316. $return_address = $params['address']['address'] .' '. $params['address']['consignee'] .' '. $params['address']['mobile'];
  317. $step_text = RefundStatusEnum::getStepStatusText($params['step_status']);
  318. $payment_map = PayTypeEnum::getMap();
  319. switch ($params['step_status']) {
  320. case RefundStatusEnum::STEP_2:
  321. $content = [
  322. $step_text['desc'],
  323. '退货地址:' . $return_address
  324. ];
  325. break;
  326. case RefundStatusEnum::STEP_4:
  327. $content = [
  328. $step_text['desc'],
  329. '申请退款金额:¥' . $refund->refund_price,
  330. '实际退款金额:¥' . $refund->reality_refund_price,
  331. '退款方式:' . $payment_map[$params['payment_type']],
  332. ];
  333. break;
  334. case RefundStatusEnum::STEP_6:
  335. $content = [
  336. $step_text['desc'],
  337. '申请退款金额:¥' . $refund->refund_price,
  338. '实际退款金额:¥' . $refund->reality_refund_price,
  339. '退款方式:' . $payment_map[$params['payment_type']],
  340. ];
  341. break;
  342. case RefundStatusEnum::STEP_8:
  343. $content = [
  344. $step_text['desc'],
  345. '退货地址:' . $return_address
  346. ];
  347. break;
  348. case RefundStatusEnum::STEP_10:
  349. $content = [
  350. $step_text['desc'],
  351. '物流公司:' . $refund->saler_express,
  352. '物流编号:' . $refund->saler_express_no,
  353. ];
  354. break;
  355. case RefundStatusEnum::STEP_NEGATIVE_1:
  356. case RefundStatusEnum::STEP_NEGATIVE_2:
  357. $content = [
  358. $step_text['desc'],
  359. '拒绝理由:' . $params['refund_reason'] ?? '',
  360. ];
  361. break;
  362. }
  363. $step = new OrderRefundStep();
  364. $step->order_refund_id = $refund->id;
  365. $step->role = $step_text['role'];
  366. $step->content = Json::encode($content);
  367. $step->step_num = RefundStatusEnum::getStepNumByStatus($params['step_status']);
  368. $step->step_status = $params['step_status'];
  369. if (!$step->save()) throw new Exception("退款步骤保存失败,msg=" . $refund->getErrorMessage());
  370. return $params['step_status'];
  371. }
  372. /**
  373. * 商家拒绝售后(暂供应链订单使用)
  374. *
  375. * @param integer $mall_id
  376. * @param integer $id
  377. * @param string $reason
  378. * @return void
  379. */
  380. public static function refuseRefundForChain(int $mall_id, int $id, string $reason = '拒绝售后')
  381. {
  382. // 售后订单
  383. $refund = OrderRefund::find()->where(['mall_id' => $mall_id, 'id' => $id])->one();
  384. if ($refund->isRefundFinish()) throw new Exception('当前订单已售后');
  385. $refund->refund_status = RefundStatusEnum::FINISH;
  386. // 自动维护主订单
  387. OrderDetail::updateAll(
  388. [
  389. 'is_feedback' => OrderStatusEnum::getIsFeedbackStatus(RefundStatusEnum::STEP_NEGATIVE_1),
  390. 'updated_at' => time()
  391. ],
  392. ['id' => $refund->order_detail_id]
  393. );
  394. $order = Order::getOne([['id' => $refund->order_id]]);
  395. $order->is_feedback = OrderStatusEnum::getIsFeedbackStatus(RefundStatusEnum::STEP_NEGATIVE_1);
  396. $order->save();
  397. // 保存步骤
  398. $step_status = self::saveStep($refund, [
  399. 'payment_type' => $order->payment_type,
  400. 'address' => [
  401. 'address' => '',
  402. 'consignee' => '',
  403. 'mobile' => '',
  404. ],
  405. 'step_status' => RefundStatusEnum::STEP_NEGATIVE_1,
  406. 'refund_reason' => $reason,
  407. ]);
  408. // 修改售后信息
  409. $refund->step_status = $step_status;
  410. if (!$refund->save()) throw new Exception("保存失败,msg=" . $refund->getErrorMessage());
  411. // 拒绝 - 需要处理ERP
  412. if (MallAddons::isInstall($order['mall_id'],AddonsEnum::ADDONS_NAME_WANG_DIAN_TONG)) {
  413. (new OrderRefundRejectService())->sync($order['mall_id'], $refund->id);
  414. }
  415. $event = new OrderRefundChangeEvent($refund->mall_id);
  416. $data = [];
  417. $data['id'] = $refund->id;
  418. $data['step_status'] = RefundStatusEnum::STEP_NEGATIVE_1;
  419. Yii::$app->services->events->dispatch($event, $data, $event->listeners);
  420. }
  421. }