OrderRefund.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <?php
  2. namespace addons\shopro\service\order;
  3. use app\admin\model\shopro\order\Order;
  4. use app\admin\model\shopro\order\OrderItem;
  5. use app\admin\model\shopro\order\Action;
  6. use app\admin\model\shopro\Pay as PayModel;
  7. use app\admin\model\shopro\user\User;
  8. use app\common\model\User as CommonUser;
  9. use addons\shopro\service\pay\PayRefund;
  10. use addons\shopro\service\pay\PayOper;
  11. use addons\shopro\traits\CouponSend;
  12. use addons\shopro\service\StockSale;
  13. use addons\shopro\facade\Activity as ActivityFacade;
  14. class OrderRefund
  15. {
  16. use CouponSend;
  17. protected $order = null;
  18. protected $default_refund_type = 'back';
  19. public function __construct($order)
  20. {
  21. $this->order = $order;
  22. $this->default_refund_type = $order->ext['refund_type'] ?? 'back';
  23. }
  24. /**
  25. * 全额退款(无条件退款, 优惠券积分全退)
  26. *
  27. * @param \think\Model $user
  28. * @param string $remark
  29. * @return void
  30. */
  31. public function fullRefund($user = null, $data = [])
  32. {
  33. $items = OrderItem::where('order_id', $this->order->id)->lock(true)->select();
  34. foreach ($items as $key => $item) {
  35. if (in_array($item['refund_status'], [
  36. OrderItem::REFUND_STATUS_AGREE,
  37. OrderItem::REFUND_STATUS_COMPLETED,
  38. ])) {
  39. error_stop('订单有退款,不能全额退款');
  40. }
  41. }
  42. // 返还库存,减少销量
  43. $stockSale = new StockSale();
  44. $stockSale->backStockSale($this->order, $items);
  45. if ($this->order->apply_refund_status == Order::APPLY_REFUND_STATUS_APPLY) {
  46. // 如果订单申请了全额退款,这里将全额退款状态改为 已完成
  47. $this->order->apply_refund_status = Order::APPLY_REFUND_STATUS_FINISH;
  48. $this->order->save();
  49. }
  50. if ($this->order->coupon_id) {
  51. // 订单使用了优惠券,退回用户优惠券
  52. $this->backUserCoupon($this->order->coupon_id);
  53. }
  54. if ($this->order->activity_id || $this->order->promo_types) {
  55. // 有活动,执行活动失败
  56. ActivityFacade::buyFail($this->order, 'refund');
  57. }
  58. $total_refund_fee = '0'; // 已退金额
  59. foreach ($items as $key => $item) {
  60. $is_last = (count($items) - 1) == $key ? true : false; // 是否最后一个商品
  61. // 计算 refund_fee
  62. $refund_fee = $this->calcRefundFee($item, $total_refund_fee, $is_last);
  63. $item->refund_status = OrderItem::REFUND_STATUS_AGREE; // 同意退款
  64. $item->refund_fee = $refund_fee; // 实时计算,总 退款金额 等于 商品实际支付金额
  65. $item->refund_type = $data['refund_type'] ?? $this->default_refund_type;
  66. $item->ext = array_merge($item->ext, ['refund_time' => time()]); // 退款时间
  67. $item->save();
  68. // 累加已退金额
  69. if ($refund_fee > 0) {
  70. $total_refund_fee = bcadd($total_refund_fee, $refund_fee, 2);
  71. }
  72. Action::add($this->order, $item, $user, ($user ? (($user instanceof User || $user instanceof CommonUser) ? 'user' : 'admin') : 'system'), (isset($data['remark']) && $data['remark'] ? $data['remark'] . ',' : '') . '退款金额:¥' . $item->refund_fee);
  73. // 订单商品退款后
  74. $eventData = ['order' => $this->order, 'item' => $item];
  75. \think\Hook::listen('order_item_refund_after', $eventData);//orderItemRefundAfter ,就只是更新一下订单的退款时间
  76. }
  77. // 退回已支付的所有金额(积分,余额,在线支付原路返回等)
  78. $this->refundAllPays($data);
  79. // 订单商品退款后
  80. //1 将开票申请取消,
  81. //2 扣除退款金额的累计消费金额,
  82. //3 通知用户
  83. $eventData = [
  84. 'order' => $this->order,
  85. 'items' => $items,
  86. 'refund_type' => $data['refund_type'] ?? $this->default_refund_type,
  87. 'refund_method' => 'full_refund'
  88. ];
  89. \think\Hook::listen('order_refund_after', $eventData);//orderRefundAfter
  90. }
  91. /**
  92. * 部分退款 (通过订单商品退款)
  93. *
  94. * @param \think\Model $item
  95. * @param string $refund_money
  96. * @param \think\Model $user
  97. * @param string $remark
  98. * @return void
  99. */
  100. public function refund($item, $refund_money, $user = null, $data = [])
  101. {
  102. $item->refund_status = OrderItem::REFUND_STATUS_AGREE; // 同意退款
  103. $item->refund_fee = $refund_money;
  104. $item->refund_type = $data['refund_type'] ?? $this->default_refund_type;
  105. $item->ext = array_merge($item->ext, ['refund_time' => time()]); // 退款时间
  106. $item->save();
  107. Action::add($this->order, $item, $user, ($user ? (($user instanceof User || $user instanceof CommonUser) ? 'user' : 'admin') : 'system'), (isset($data['remark']) && $data['remark'] ? $data['remark'] . ',' : '') . '退款金额:¥' . $refund_money);
  108. // 订单商品退款后
  109. $eventData = ['order' => $this->order, 'item' => $item];
  110. \think\Hook::listen('order_item_refund_after', $eventData);
  111. // 查找符合条件的 pays 并从中退指定金额
  112. $this->refundPaysByMoney((string)$refund_money, $data);
  113. // 订单商品退款后
  114. $eventData = [
  115. 'order' => $this->order,
  116. 'items' => [$item],
  117. 'refund_type' => $data['refund_type'] ?? $this->default_refund_type,
  118. 'refund_method' => 'item_refund'
  119. ];
  120. \think\Hook::listen('order_refund_after', $eventData);
  121. }
  122. /**
  123. * 退回已支付的所有 pays 记录
  124. *
  125. * @param string $remark
  126. * @return void
  127. */
  128. public function refundAllPays($data = [])
  129. {
  130. // 商城订单,已支付的 pay 记录
  131. $pays = PayModel::typeOrder()->paid()->where('order_id', $this->order->id)->lock(true)->select();
  132. $refund = new PayRefund($this->order->user_id);
  133. foreach ($pays as $key => $pay) {
  134. $refund->fullRefund($pay, [
  135. 'refund_type' => $data['refund_type'] ?? $this->default_refund_type,
  136. 'platform' => $this->order->platform,
  137. 'remark' => $data['remark'] ?? ''
  138. ]);
  139. }
  140. }
  141. /**
  142. * 查找符合条件的 pays 并从中退指定金额 (不退积分,包括积分抵扣的积分)
  143. *
  144. * @param string $refund_money
  145. * @param string $remark
  146. * @return void
  147. */
  148. protected function refundPaysByMoney(string $refund_money, $data = [])
  149. {
  150. $payOper = new PayOper($this->order->user_id);
  151. $pays = $payOper->getCanRefundPays($this->order->id);
  152. $remain_max_refund_money = $payOper->getRemainRefundMoney($pays);
  153. if (bccomp($refund_money, $remain_max_refund_money, 2) === 1) {
  154. // 退款金额超出最大支付金额
  155. error_stop('退款金额超出最大可退款金额');
  156. }
  157. $current_refunded_money = '0'; // 本次退款,已退金额累计
  158. $refund = new PayRefund($this->order->user_id);
  159. foreach ($pays as $key => $pay) {
  160. $current_remain_money = bcsub($refund_money, $current_refunded_money, 2); // 剩余应退款金额
  161. if ($current_remain_money <= 0) {
  162. // 退款完成
  163. break;
  164. }
  165. $current_pay_remain_money = bcsub($pay->pay_fee, $pay->refund_fee, 2); // 当前 pay 记录剩余可退金额
  166. if ($current_pay_remain_money <= 0) {
  167. // 当前 pay 支付的金额已经退完了,循环下一个
  168. continue;
  169. }
  170. $current_refund_money = min($current_remain_money, $current_pay_remain_money); // 取最小值
  171. $refund->refund($pay, $current_refund_money, [
  172. 'refund_type' => $data['refund_type'] ?? $this->default_refund_type,
  173. 'platform' => $this->order->platform,
  174. 'remark' => $data['remark'] ?? ''
  175. ]);
  176. $current_refunded_money = bcadd($current_refunded_money, $current_refund_money, 2);
  177. }
  178. if ($refund_money > $current_refunded_money) {
  179. // 退款金额超出最大支付金额
  180. error_stop('退款金额超出最大可退款金额');
  181. }
  182. }
  183. /**
  184. * 计算 item 应退金额
  185. *
  186. * @param \think\Model $item
  187. * @param string $total_refund_fee
  188. * @param boolean $is_last
  189. * @return string
  190. */
  191. private function calcRefundFee($item, $total_refund_fee, $is_last = false)
  192. {
  193. $pay_fee = $this->order->pay_fee; // 支付总金额
  194. $current_goods_amount = bcmul($item->goods_price, (string)$item->goods_num, 2);
  195. $total_amount = bcadd($current_goods_amount, $item->dispatch_fee, 2);
  196. $refund_fee = bcsub($total_amount, $item->discount_fee, 2); // (商品金额 + 运费金额) - 总优惠(活动,优惠券,包邮优惠)
  197. if ($total_refund_fee >= $pay_fee) {
  198. $refund_fee = 0;
  199. } else {
  200. $remain_fee = bcsub($pay_fee, $total_refund_fee, 2);
  201. $refund_fee = $remain_fee > $refund_fee ? ($is_last ? $remain_fee : $refund_fee) : $remain_fee;
  202. }
  203. return $refund_fee;
  204. }
  205. }