RefundController.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <?php
  2. /**
  3. * 用途:商城顾客售后申请接口
  4. * 谁用:mallApp 申请售后页、退款售后列表/详情
  5. * 解决:顾客提交/查看/撤销售后申请;列表展示店铺与商品快照;真正退款由商家在 hdApp 审核通过后执行
  6. */
  7. namespace mall\controllers;
  8. use bizHd\distribution\classes\DistributionOrderClass;
  9. use bizHd\order\classes\OrderClass as HdOrderClass;
  10. use bizHd\refund\classes\MallRefundApplyClass;
  11. use bizHd\refund\services\MallRefundApplyService;
  12. use bizMall\order\classes\OrderClass;
  13. use common\components\util;
  14. use Yii;
  15. class RefundController extends BaseController
  16. {
  17. /**
  18. * 申请页初始化数据:订单商品 + 可退金额 + 已有申请进度
  19. */
  20. public function actionGetRefundData()
  21. {
  22. $id = (int)Yii::$app->request->get('id', 0);
  23. $order = OrderClass::getOrderById($id);
  24. if (empty($order)) {
  25. util::fail('没有找到订单');
  26. }
  27. $this->assertOrderOwner($order);
  28. $orderPrice = (float)($order['orderPrice'] ?? 0);
  29. $tkPrice = (float)($order['tkPrice'] ?? 0);
  30. $couldRefundPrice = round(max(0, $orderPrice - $tkPrice), 2);
  31. $apply = MallRefundApplyClass::getLatestByOrderId($id);
  32. if (!empty($apply) && is_array($apply)) {
  33. $apply['statusText'] = MallRefundApplyClass::statusText($apply['status'] ?? 0);
  34. if (!empty($apply['product']) && is_string($apply['product'])) {
  35. $apply['productList'] = json_decode($apply['product'], true) ?: [];
  36. }
  37. }
  38. // 分销售后截止提示(仅提示,真正拦截在提交时)
  39. $refundDeadlineTip = '';
  40. if (DistributionOrderClass::isDistOrderSettled($id)) {
  41. $refundDeadlineTip = '该订单分销佣金已结算,无法申请售后';
  42. } else {
  43. $distOrder = DistributionOrderClass::getByCondition(['orderId' => $id]);
  44. if (!empty($distOrder)) {
  45. $finishTime = $distOrder['finishTime'] ?? '';
  46. if (!empty($finishTime) && $finishTime !== '0000-00-00 00:00:00') {
  47. $rule = \bizHd\distribution\classes\DistributionRuleClass::getRule(
  48. (int)($order['shopId'] ?? 0),
  49. (int)($order['mainId'] ?? $this->mainId)
  50. );
  51. $settleDays = max(0, (int)($rule['settleDays'] ?? 0));
  52. $deadline = date('Y-m-d H:i', strtotime($finishTime) + $settleDays * 86400);
  53. $refundDeadlineTip = "分销订单完成后{$settleDays}天内可申请售后,截止:{$deadline}";
  54. }
  55. }
  56. }
  57. util::success([
  58. 'orderInfo' => $order,
  59. 'goodsInfoList' => $order['goodsInfoList'] ?? [],
  60. 'itemInfoList' => $order['itemList'] ?? [],
  61. 'couldRefundPrice' => $couldRefundPrice,
  62. 'apply' => $apply,
  63. 'refundDeadlineTip' => $refundDeadlineTip,
  64. 'canApply' => $this->canApplyRefund($order, $apply),
  65. ]);
  66. }
  67. /**
  68. * 顾客提交售后申请(仅落待审核记录)
  69. * 若该订单最新申请为已取消,则把原记录状态改回待审核,不新开一条
  70. */
  71. public function actionCreateOrder()
  72. {
  73. $post = Yii::$app->request->post();
  74. $id = (int)($post['id'] ?? 0);
  75. util::checkRepeatCommit('mall_refund_' . $id, 5);
  76. $order = HdOrderClass::getById($id, true);
  77. if (empty($order)) {
  78. util::fail('没有找到订单');
  79. }
  80. $this->assertOrderOwner($order);
  81. if ((int)$order->payStatus !== 1) {
  82. util::fail('订单未付款,无法申请售后');
  83. }
  84. if ((int)$order->status === HdOrderClass::ORDER_STATUS_UN_PAY) {
  85. util::fail('待付款订单无法申请售后');
  86. }
  87. if ((int)$order->status === HdOrderClass::ORDER_STATUS_CANCEL) {
  88. util::fail('已取消订单无法申请售后');
  89. }
  90. // 前端 ORDER_STATUS 含已退款=6,全额退完后可能落此状态
  91. if ((int)$order->status === 6) {
  92. util::fail('已退款订单无法申请售后');
  93. }
  94. if ((int)($order->forward ?? 0) === 1) {
  95. util::fail('售后收入单无法申请售后');
  96. }
  97. if (MallRefundApplyClass::hasPendingByOrderId($id)) {
  98. util::fail('已有待审核的售后申请,请勿重复提交');
  99. }
  100. // 分销关联订单:完成后超过 settleDays 不可再申请
  101. DistributionOrderClass::assertRefundNotExpired(
  102. $id,
  103. (int)$order->shopId,
  104. (int)($order->mainId ?? $this->mainId)
  105. );
  106. $connection = Yii::$app->db;
  107. $transaction = $connection->beginTransaction();
  108. try {
  109. $extra = [
  110. 'userId' => (int)$this->userId,
  111. 'hdId' => (int)$this->hdId,
  112. 'customId' => (int)$this->customId,
  113. 'shopId' => (int)$order->shopId,
  114. 'mainId' => (int)($order->mainId ?? $this->mainId),
  115. ];
  116. $apply = MallRefundApplyService::createApply($post, $order, $extra);
  117. $transaction->commit();
  118. util::success([
  119. 'id' => (int)$apply->id,
  120. 'status' => (int)$apply->status,
  121. 'statusText' => MallRefundApplyClass::statusText($apply->status),
  122. ], '提交成功,请等待商家审核');
  123. } catch (\Exception $e) {
  124. $transaction->rollBack();
  125. Yii::info('商城售后申请失败:' . $e->getMessage());
  126. // util::fail 已输出时不会走到这里;其它异常统一提示
  127. if (strpos($e->getMessage(), '操作失败') === false) {
  128. util::fail($e->getMessage() ?: '申请失败');
  129. }
  130. util::fail('申请失败');
  131. }
  132. }
  133. /**
  134. * 顾客售后申请列表(mallApp 退款售后 Tab)
  135. * status:all 全部;0/pending 处理中;1/refunded 已退款;closed 已关闭(驳回+取消)
  136. */
  137. public function actionList()
  138. {
  139. $tab = Yii::$app->request->get('status', 'all');
  140. if ($tab === '' || $tab === null) {
  141. $tab = 'all';
  142. }
  143. $list = MallRefundApplyClass::getCustomApplyList((int)$this->userId, (string)$tab);
  144. util::success($list);
  145. }
  146. /**
  147. * 顾客售后申请详情(列表「查看详情」)
  148. */
  149. public function actionDetail()
  150. {
  151. $id = (int)Yii::$app->request->get('id', 0);
  152. $apply = MallRefundApplyClass::getById($id, true);
  153. MallRefundApplyClass::validByCustom($apply, (int)$this->customId, (int)$this->userId);
  154. $data = MallRefundApplyClass::formatCustomDetail($apply->toArray());
  155. util::success($data);
  156. }
  157. /**
  158. * 顾客撤销待审核申请
  159. */
  160. public function actionCancelOrder()
  161. {
  162. $post = Yii::$app->request->post();
  163. $id = (int)($post['id'] ?? 0);
  164. $apply = MallRefundApplyClass::getById($id, true);
  165. MallRefundApplyClass::validByCustom($apply, (int)$this->customId, (int)$this->userId);
  166. $connection = Yii::$app->db;
  167. $transaction = $connection->beginTransaction();
  168. try {
  169. MallRefundApplyService::cancel($apply);
  170. $transaction->commit();
  171. util::complete('已撤销申请');
  172. } catch (\Exception $e) {
  173. $transaction->rollBack();
  174. Yii::info('撤销售后申请失败:' . $e->getMessage());
  175. util::fail($e->getMessage() ?: '撤销失败');
  176. }
  177. }
  178. /**
  179. * 校验订单归属当前登录顾客
  180. * @param array|object $order
  181. */
  182. protected function assertOrderOwner($order)
  183. {
  184. $customId = is_object($order) ? (int)($order->customId ?? 0) : (int)($order['customId'] ?? 0);
  185. $userId = is_object($order) ? (int)($order->userId ?? 0) : (int)($order['userId'] ?? 0);
  186. if ($customId > 0 && (int)$this->customId > 0 && $customId === (int)$this->customId) {
  187. return;
  188. }
  189. // 兼容早期仅绑 userId 的订单
  190. if ($userId > 0 && $userId === (int)$this->userId) {
  191. return;
  192. }
  193. util::fail('不是你的订单,无法操作');
  194. }
  195. /**
  196. * 是否允许新开售后申请
  197. * @param array $order
  198. * @param array|null $apply
  199. * @return int 1可申请 0不可
  200. */
  201. protected function canApplyRefund($order, $apply)
  202. {
  203. if ((int)($order['payStatus'] ?? 0) !== 1) {
  204. return 0;
  205. }
  206. $status = (int)($order['status'] ?? 0);
  207. if (in_array($status, [HdOrderClass::ORDER_STATUS_UN_PAY, HdOrderClass::ORDER_STATUS_CANCEL, 6], true)) {
  208. return 0;
  209. }
  210. if ((int)($order['forward'] ?? 0) === 1) {
  211. return 0;
  212. }
  213. if (!empty($apply) && (int)($apply['status'] ?? -1) === MallRefundApplyClass::STATUS_PENDING) {
  214. return 0;
  215. }
  216. if (DistributionOrderClass::isDistOrderSettled((int)($order['id'] ?? 0))) {
  217. return 0;
  218. }
  219. $could = bcsub((string)($order['orderPrice'] ?? 0), (string)($order['tkPrice'] ?? 0), 2);
  220. if (bccomp($could, '0', 2) <= 0) {
  221. return 0;
  222. }
  223. return 1;
  224. }
  225. }