OrderRefund.php 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. <?php
  2. namespace common\models\order;
  3. use addons\Chanjet\common\global_func\Request;
  4. use addons\Chanjet\common\models\AddonsChanjetRequest;
  5. use addons\Store\common\models\StoreGoods;
  6. use addons\Store\common\models\StoreOrder;
  7. use addons\Store\common\models\StoreOrderDetail;
  8. use common\components\export\CsvTool;
  9. use common\enums\AddonsChanjetRequestEnum;
  10. use common\enums\AddonsEnum;
  11. use common\enums\DownloadEnum;
  12. use common\enums\EventNameEnum;
  13. use common\enums\GoodsTypeEnum;
  14. use common\enums\OrderSourceEnum;
  15. use common\enums\OrderStatusEnum;
  16. use common\enums\RabbitMqEnum;
  17. use common\enums\RefundStatusEnum;
  18. use common\enums\RefundTypeEnum;
  19. use common\enums\ShippingTypeEnum;
  20. use common\events\order\OrderRefundEvent;
  21. use common\helpers\LockHelper;
  22. use common\logic\order\OrderRefundLogic;
  23. use common\logic\statistics\StatisticsHandle;
  24. use common\models\common\Download;
  25. use common\models\goods\Goods;
  26. use common\models\mall\MallAddons;
  27. use common\models\user\User;
  28. use common\enums\StatusEnum;
  29. use common\events\order\OrderRefundCancelEvent;
  30. use common\globals\GlobalInvoke;
  31. use common\models\common\PaymentTrade;
  32. use common\models\common\PaymentTradeOrder;
  33. use Yii;
  34. use yii\db\Exception;
  35. use yii\helpers\Json;
  36. /**
  37. * This is the model class for table "{{%order_refund}}".
  38. *
  39. *
  40. * @property int $id
  41. * @property int $mall_id
  42. * @property int $mch_id
  43. * @property int $store_id 门店id
  44. * @property int $user_id 用户id
  45. * @property int $order_id 订单id
  46. * @property int $order_detail_id 关联订单详情id
  47. * @property string $order_no 售后单号
  48. * @property int $type 售后类型:1=仅退款,2=退货退款,3=换货
  49. * @property string $reason 退款原因
  50. * @property string $remark 用户退款备注、说明
  51. * @property string $pic_list 用户上传图片凭证
  52. * @property string $refuse_remark 商家拒绝理由
  53. * @property string $express 快递公司
  54. * @property string $express_no 快递单号
  55. * @property string $saler_express 商家快递公司
  56. * @property string $customer_name 京东物流
  57. * @property string $saler_express_no 商家快递单号
  58. * @property string $saler_address 买家收货地址(json存储)
  59. * @property int $is_refund 是否打款,【0否,1是】
  60. * @property int $refund_at 打款时间
  61. * @property float $refund_price 退款金额
  62. * @property float $reality_refund_price 实际退款金额
  63. * @property int $express_at 买家发货时间
  64. * @property int $saler_express_at 卖家重新发货时间
  65. * @property int $step_status 售后步骤状态,对应qimall_order_refund_step.step_status最新状态
  66. * @property int $refund_status 售后状态
  67. * @property int $status 状态【1启用 0禁用 -1删除】
  68. * @property int $created_at 创建时间
  69. * @property int $updated_at 修改时间
  70. * @property int $goods_type 修改时间
  71. * @property int $num 修改时间
  72. * @property string $order_source 订单来源
  73. */
  74. class OrderRefund extends \common\models\base\BaseActiveRecord
  75. {
  76. /**
  77. * {@inheritdoc}
  78. */
  79. public static function tableName()
  80. {
  81. return '{{%order_refund}}';
  82. }
  83. /**
  84. * {@inheritdoc}
  85. */
  86. public function rules()
  87. {
  88. return [
  89. [['mall_id', 'mch_id', 'store_id', 'user_id', 'order_id', 'order_detail_id', 'type', 'is_refund', 'refund_at', 'express_at',
  90. 'saler_express_at', 'step_status', 'refund_status', 'status', 'created_at', 'updated_at', 'goods_type', 'num'], 'integer'],
  91. [['pic_list'], 'string'],
  92. [['refund_price', 'reality_refund_price','old_reality_refund_price'], 'number'],
  93. [['order_no', 'reason', 'remark', 'refuse_remark', 'express_no', 'saler_express_no', 'customer_name', 'refund_remark'], 'string', 'max' => 255],
  94. [['express', 'saler_express'], 'string', 'max' => 65],
  95. [['order_source'],'string'],
  96. [['saler_address'],'string']
  97. ];
  98. }
  99. /**
  100. * {@inheritdoc}
  101. */
  102. public function attributeLabels()
  103. {
  104. return [
  105. 'id' => 'ID',
  106. 'mall_id' => 'Mall ID',
  107. 'mch_id' => 'Mch ID',
  108. 'store_id' => '门店id',
  109. 'user_id' => '用户id',
  110. 'order_id' => '订单id',
  111. 'order_detail_id' => '关联订单详情id',
  112. 'order_no' => '售后单号',
  113. 'type' => '售后类型:1=仅退款,2=退货退款,3=换货',
  114. 'reason' => '退款原因',
  115. 'remark' => '用户退款备注、说明',
  116. 'pic_list' => '用户上传图片凭证',
  117. 'refuse_remark' => '商家拒绝理由',
  118. 'express' => '快递公司',
  119. 'express_no' => '快递单号',
  120. 'saler_express' => '商家快递公司',
  121. 'customer_name' => '京东物流',
  122. 'saler_express_no' => '商家快递单号',
  123. 'saler_address' => '买家收货地址(json存储)',
  124. 'is_refund' => '是否打款,【0否,1是】',
  125. 'refund_at' => '打款时间',
  126. 'refund_price' => '退款金额',
  127. 'reality_refund_price' => '实际退款金额',
  128. 'express_at' => '买家发货时间',
  129. 'saler_express_at' => '卖家重新发货时间',
  130. 'step_status' => '售后步骤状态',
  131. 'refund_status' => '售后状态',
  132. 'status' => '状态【1启用 0禁用 -1删除】',
  133. 'created_at' => '创建时间',
  134. 'updated_at' => '修改时间',
  135. ];
  136. }
  137. public function getStep()
  138. {
  139. return $this->hasMany(OrderRefundStep::class, ['order_refund_id' => 'id'])->where(['status' => StatusEnum::ENABLED])->orderBy('id desc');
  140. }
  141. public function getOrderDetail()
  142. {
  143. return $this->hasOne(OrderDetail::class, ['id' => 'order_detail_id']);
  144. }
  145. public function getBase_user()
  146. {
  147. return $this->hasOne(User::class, ['id' => 'user_id'])->select(['id', 'username','nickname', 'avatar_url','mobile']);
  148. }
  149. public function getOrder()
  150. {
  151. return $this->hasOne(Order::class, ['id' => 'order_id'])->select(['id', 'store_id', 'province', 'mch_id', 'order_no','out_trade_no','payment_type','mobile','receiver_name','region_name','address','order_status', 'marketing_type', 'order_source','extra_info','shipping_money']);
  152. }
  153. public function getMarketing()
  154. {
  155. return $this->hasOne(OrderMarketing::class, ['order_detail_id' => 'order_detail_id'])->select(['order_detail_id', 'deduct_score','deduct_currency','user_coupon_id']);
  156. }
  157. /**
  158. * 获取最新的一条售后步骤
  159. *
  160. * @return ActiveQuery
  161. */
  162. public function getLastStep()
  163. {
  164. return $this->hasOne(OrderRefundStep::class, ['order_refund_id' => 'id'])
  165. ->where(['status' => StatusEnum::ENABLED])
  166. ->orderBy('id desc');
  167. }
  168. /**
  169. * 畅捷通请求
  170. *
  171. * @return \yii\db\ActiveQuery
  172. */
  173. public function getAddonsChanjetRequest()
  174. {
  175. return $this
  176. ->hasOne(AddonsChanjetRequest::class, ['table_id' => 'id'])
  177. ->orderBy('updated_at desc')
  178. ->select('id, table_id, request_status, updated_at')
  179. ->where([
  180. 'table' => self::tableName(),
  181. 'path' => [Request::PATHS['SALE_DELIVERY_CREATE_BATCH']]
  182. ])
  183. ->andWhere(['>', 'request_status', 0]);
  184. }
  185. /**
  186. * @Author: hua
  187. * @DateTime: 2021/10/8 17:53
  188. * @Copyright: copyright (c) 2021 广东七件事集团
  189. * @param array $params
  190. * @return bool|OrderRefund
  191. */
  192. public static function setData(array $params)
  193. {
  194. try {
  195. if (!empty($params['id'])) {
  196. $model = self::findOne(['mall_id' => $params['mall_id'],'user_id'=>$params['user_id'], 'id' => $params['id'], 'status' => [StatusEnum::ENABLED]]);
  197. if (empty($model)) throw new Exception('订单详情不存在或已删除');
  198. } else {
  199. $model = new self();
  200. $model->loadDefaultValues();
  201. }
  202. if (isset($params['pic_list'])) $params['pic_list'] = Json::encode($params['pic_list']);
  203. if (!$model->load($params, '') || !$model->save()) {
  204. throw new Exception($model->getErrorMessage());
  205. }
  206. return $model;
  207. } catch (Exception $e) {
  208. self::$static_error = $e->getMessage();
  209. return false;
  210. }
  211. }
  212. /**
  213. * 根据订单号获取订单id
  214. * @Author: lun
  215. * @DateTime: 2021/10/9 0009 10:33
  216. * @Copyright: copyright (c) 2021 广东七件事集团
  217. * @param $mall_id
  218. * @param $order_no
  219. * @return false|int|string|null
  220. */
  221. public static function getOidByOrderNo($mall_id, $order_no)
  222. {
  223. return Order::find()->select('id')->where(['mall_id' => $mall_id, 'order_no' => $order_no])->asArray()->scalar();
  224. }
  225. public static function getVagueOrderNo($mall_id, $order_no)
  226. {
  227. return Order::find()->select("id")->where(['mall_id' => $mall_id])->where(['like', 'order_no' ,$order_no])->asArray()->column();
  228. }
  229. public static function searchByKeyword($mall_id, $type, $keyword)
  230. {
  231. switch ($type) {
  232. case 'mobile':
  233. $user = User::getOne([['mall_id' => $mall_id, 'mobile' => $keyword]], true, [], false, 'id');
  234. $data = ['=', 'user_id', $user['id']];
  235. break;
  236. case 'goods_name':
  237. $goodsId = Goods::find()->select('id')->where(['mall_id' => $mall_id])->andWhere(['like', 'goods_name', $keyword])->asArray()->column();
  238. if ($goodsId) {
  239. $orderDetailIds = OrderDetail::find()->select('id')->where(['mall_id' => $mall_id])->andWhere(['in', 'goods_id', $goodsId])->asArray()->column();
  240. }
  241. $data = empty($orderDetailIds) ? ['=', 'order_detail_id',""] : ['in', 'order_detail_id', $orderDetailIds];
  242. break;
  243. // 交易单号查询
  244. case 'trade_no':
  245. $ids = PaymentTrade::find()->where(['mall_id' => $mall_id, 'out_trade_no' => $keyword])->select('id')->column();
  246. $order_nos = PaymentTradeOrder::find()->where(['trade_id' => $ids])->select('order_no')->column();
  247. $order_ids = Order::find()->where(['order_no' => $order_nos])->select('id')->column();
  248. $data = ['order_id' => $order_ids];
  249. break;
  250. }
  251. return $data;
  252. }
  253. /**
  254. * 申请售后
  255. * @Author: hua
  256. * @DateTime: 2021/10/7 10:26
  257. * @Copyright: copyright (c) 2021 广东七件事集团
  258. * @param $params
  259. * @return object|bool
  260. */
  261. public static function feedback($params){
  262. $t = Yii::$app->db->beginTransaction();
  263. try{
  264. //订单详情表
  265. $order_detail = OrderDetail::findOne(['id' => $params['order_detail_id'], 'user_id' => $params['user_id']]);
  266. if (empty($order_detail)) throw new Exception('订单不存在');
  267. if (!isset($params['num'])) $params['num'] = intval($order_detail->num);
  268. if (empty($order_detail->store_id)) {
  269. $goods = Goods::findOne(['id' => $order_detail->goods_id]);
  270. if (empty($goods->can_feedback)) throw new Exception('当前商品不支持售后');
  271. }
  272. $order = Order::findOne(['id'=>$order_detail['order_id']]);
  273. $original_goods_price = $order_detail->goods_price;// 记录此商品的原实付金额
  274. if (!empty($goods) && $goods->goods_type == GoodsTypeEnum::GoodsTypeVirtual) {
  275. if ($order_detail->is_feedback != StatusEnum::DISABLED) {
  276. throw new Exception('订单已过售后时间,无法申请售后');
  277. }
  278. } else {
  279. if ($order_detail->order_status == OrderStatusEnum::ACCOMPLISH) throw new Exception('订单已过售后时间,无法申请售后');
  280. /* 自提订单 待提货是否允许退款 */
  281. $communityGoodsSetting = GlobalInvoke::exec(GlobalInvoke::INVOKE_ORDER_DETAILS_ITEM, $order_detail->mall_id, [
  282. 'order_detail' => ['goods_id' => $order_detail->goods_id, 'order_id' => $order_detail->order_id],
  283. 'mall_id' => $order_detail->mall_id,
  284. ]);
  285. // 不在允许退款的状态里面,或者在允许退款的状态里面,但是是社区团购自提订单并且不允许申请退款
  286. if (
  287. ($order->order_source == OrderSourceEnum::SOURCE_REGISTRATION && $order->order_status != OrderStatusEnum::TAKE) &&
  288. (!in_array($order_detail->order_status, [OrderStatusEnum::PAY, OrderStatusEnum::SHIPMENTS, OrderStatusEnum::SING]) ||
  289. (!empty($communityGoodsSetting['is_community_order']) && empty($communityGoodsSetting['community_goods_setting']['enable_refund'])))
  290. ){
  291. throw new Exception('该订单状态下,无法申请售后');
  292. } else if (
  293. !in_array($order_detail->order_status, [OrderStatusEnum::PAY, OrderStatusEnum::SHIPMENTS, OrderStatusEnum::SING, OrderStatusEnum::TAKE, OrderStatusEnum::TAKE_FINISH]) ||
  294. (!empty($communityGoodsSetting['is_community_order']) && empty($communityGoodsSetting['community_goods_setting']['enable_refund']))
  295. ){
  296. throw new Exception('该订单状态下,无法申请售后');
  297. }
  298. // 社区团购订单只有待提货时能申请仅退款
  299. if (!empty($communityGoodsSetting['is_community_order']) && $params['type'] != RefundTypeEnum::MONEY) {
  300. throw new Exception('该订单状态下,无法申请售后');
  301. }
  302. }
  303. // 考虑部分发货的情况,如果就部分发货了,则不允许申请售后
  304. if ($order_detail->shipped_num != $order_detail->num && $order_detail->shipped_num > 0) {
  305. throw new Exception('该订单未发货完成,无法申请售后');
  306. }
  307. $order = Order::findOne(['id'=>$order_detail['order_id']]);
  308. // 是否允许申请售后事件
  309. $event = new \common\events\mall\IsAllowFeedbackEvent($order_detail->mall_id);
  310. Yii::$app->services->events->dispatch($event, ['mall_id' => $order_detail->mall_id, 'order' => $order, 'order_detail' => $order_detail], $event->listeners);
  311. if (!$event->getIsAllow()) {
  312. throw new Exception($event->getMessage());
  313. }
  314. // 公共调用
  315. $order_detail = GlobalInvoke::exec(GlobalInvoke::ORDER_REFUND_SUBMIT, $order_detail->mall_id, [
  316. 'order_detail' => $order_detail,
  317. 'mall_id' => $order_detail->mall_id,
  318. ]) ?: $order_detail;
  319. // if ($params['type'] <= RefundTypeEnum::MONEY && empty($params['refund_price'])) throw new Exception('退款金额需大于0');
  320. $shipping_money = OrderRefund::isRefundShippingMoney($order_detail['order_id'],$order['shipping_money']);
  321. $params['shipping_money'] = $shipping_money;
  322. $total_money = $order_detail->goods_price + $order_detail->adjust_money;
  323. // 这里累加了运费金额, 总金额累加了运费,那么提交的退款金额也需要累加运费,因为这个运费不是客户提交过来的,而是通过系统计算出来的,所以都需要累加运费
  324. if (bcadd($params['refund_price'], $shipping_money, 2) > bcadd($total_money,$shipping_money,2)){
  325. throw new Exception('最多可退款金额¥' . number_format($total_money, 2,'.', ''));
  326. }
  327. $order_detail->is_feedback = OrderStatusEnum::FEEDBACKING;
  328. $order_detail->goods_price = $original_goods_price;// 防止随机减减扣的金额覆盖掉原实付金额,此金额下单后就不可改变
  329. if(!$order_detail->save()) throw new Exception($order_detail->getErrorMessage());
  330. // 处理售后步骤
  331. if (!empty($params['pic_list'])) {
  332. $params['pic_list'] = json_decode($params['pic_list'], true);
  333. } else {
  334. $params['pic_list'] = [];
  335. }
  336. self::stepHandle($params);
  337. //退款主表 - 如果退款金额大于支付金额那么就智能退支付金额
  338. if (bcadd($params['refund_price'], $shipping_money, 2) > bcmul($order->pay_money, 1, 2)) {
  339. $params['refund_price'] = bcmul($order->pay_money, 1, 2);
  340. } else {
  341. $params['refund_price']+=$shipping_money;
  342. }
  343. $params['is_feedback'] = OrderStatusEnum::FEEDBACKING;
  344. $params['order_id'] = $order_detail->order_id;
  345. $params['store_id'] = $order_detail->store_id;
  346. $params['mch_id'] = $order_detail->mch_id;
  347. $params['order_source'] = $order_detail->order_source;
  348. if (empty($params['is_full'])) {
  349. $params['is_full'] = "{$order_detail->order_id}_{$order_detail->id}";
  350. }
  351. if ($order->store_id) {
  352. $params['goods_type'] = StoreGoods::find()->where(['id' => $order_detail->goods_id])->select('goods_type')->scalar();
  353. } else {
  354. $params['goods_type'] = Goods::find()->where(['id' => $order_detail->goods_id])->select('goods_type')->scalar();
  355. }
  356. if ($order->marketing_type == AddonsEnum::ADDONS_NAME_LAO_DA_FU) {
  357. $params['order_source'] = AddonsEnum::ADDONS_NAME_LAO_DA_FU;
  358. $params['goods_type'] = \addons\LaoDaFu\common\models\StoreGoods::find()->where(['id' => $order_detail->goods_id])->select('goods_type')
  359. ->scalar();
  360. }
  361. $params['order_no'] = Order::generateOrderNo('r');
  362. $order_refund_model = OrderRefund::setData($params);
  363. if(!$order_refund_model) throw new Exception(OrderRefund::getStaticError());
  364. $params['order_refund_id'] = $order_refund_model->id;
  365. $res = OrderRefundStep::setData($params);
  366. if(!$res) throw new Exception(OrderRefundStep::getStaticError());
  367. $trade = PaymentTrade::findOne(['out_trade_no' => $order->out_trade_no]);
  368. $trade->addRefundItem($order_refund_model, $order);
  369. //自动维护订单主表
  370. $order = Order::findOne($params['order_id']);
  371. $res = Order::autoUpdateStatus($order);
  372. $t->commit();
  373. //统计订单待评价消息数量
  374. $orderStatis['user_id'] = $order->user_id;
  375. $orderStatis['mall_id'] = $order->mall_id;
  376. switch($order->order_status){
  377. case OrderStatusEnum::PAY:
  378. $orderStatis['order_goods_num'] = -1;
  379. break;
  380. case OrderStatusEnum::SHIPMENTS:
  381. $orderStatis['order_received_num'] = -1;
  382. break;
  383. case OrderStatusEnum::SING:
  384. $orderStatis['order_evaluate_num'] = -1;
  385. break;
  386. }
  387. $orderStatis['order_return_num'] = 1;
  388. Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_ORDER, RabbitMqEnum::ORDER_QUEUE, $orderStatis, OrderUnreadMsg::class, 'orderStastics');
  389. // 触发售后统计
  390. $refund_event = ['id' => $order_refund_model->id, 'type' => RefundTypeEnum::EVENT_APPLY];
  391. Yii::$app->services->rabbitMq->push(
  392. RabbitMqEnum::EXCHANGE_TASK, RabbitMqEnum::TASK_QUEUE, [
  393. 'type' => EventNameEnum::ORDER_REFUND,
  394. 'params' => $refund_event,
  395. ], StatisticsHandle::class, 'handleSync'
  396. );
  397. return $order_refund_model;
  398. }catch(Exception $e){
  399. $t->rollBack();
  400. self::$static_error = $e->getMessage();
  401. return false;
  402. }
  403. }
  404. /**
  405. * 步骤处理
  406. * @Author bing
  407. * @DateTime 2021-10-21 16:32:05
  408. * @copyright: Copyright (c) 2020 广东七件事集团
  409. * @param array $params
  410. * @return void
  411. */
  412. public static function stepHandle(&$params){
  413. //步骤表
  414. $content = [];
  415. $refund_type = RefundTypeEnum::getMap();
  416. $content[] = '售后方式:' . $refund_type[$params['type']];
  417. switch($params['type']){
  418. case RefundTypeEnum::MONEY:
  419. $step = RefundStatusEnum::STEP_5;
  420. break;
  421. case RefundTypeEnum::MONEY_AND_PRODUCT:
  422. $step = RefundStatusEnum::STEP_1;
  423. break;
  424. case RefundTypeEnum::EXCHANGE_GOODS:
  425. $step = RefundStatusEnum::STEP_7;
  426. break;
  427. default:
  428. throw new Exception('售后方式错误');
  429. }
  430. $status_text_arr = RefundStatusEnum::getStepStatusText($step);
  431. $content[] = $status_text_arr['desc'] ?? '';
  432. $content[] = '选项:' . $params['reason'];
  433. $params['role'] = $data['role'] ?? RefundStatusEnum::BUYERS;
  434. $content[] = '售后说明:' . $params['remark'];
  435. $content[] = $params['pic_list'];
  436. if(in_array($params['type'],[RefundStatusEnum::STEP_5,RefundStatusEnum::STEP_1])){
  437. if(!empty($params['shipping_money'])) $content[] = '运费:' . $params['shipping_money'];
  438. $content[] = '退款金额:' . $params['refund_price'];
  439. }
  440. // 返还优惠内容
  441. $marketing = OrderMarketing::find()->select("deduct_score, deduct_currency")->where(['order_detail_id' => $params['order_detail_id']])->asArray()->one();
  442. if (!empty($marketing)) {
  443. $discount = [];
  444. if (!empty($marketing['deduct_score'])) {// 返回积分
  445. $deduct_currency = json_decode($marketing['deduct_currency'], true);
  446. if(isset($deduct_currency['midou_cy'])){
  447. $discount[] = floatval($deduct_currency['midou_cy']['midou_cy']) . $deduct_currency['midou_cy']['name'];
  448. }else{
  449. $score_name = Yii::$app->services->setting->mall->get($params['mall_id'],'score.score_name');
  450. $discount[] = floatval($marketing['deduct_score']) . $score_name;
  451. }
  452. }
  453. !empty($discount) && $content[] = '需返优惠:' . implode('+', $discount);
  454. }
  455. $params['content'] = $content;
  456. // 获取当前可显示的步数
  457. $params['step_num'] = RefundStatusEnum::getStepNumByStatus($step);
  458. $params['step_status'] = $step;
  459. }
  460. /**
  461. * 撤销申请
  462. * @Author: hua
  463. * @DateTime: 2021/10/9 9:15
  464. * @Copyright: copyright (c) 2021 广东七件事集团
  465. * @param $params
  466. * @return bool
  467. */
  468. public static function cancelRefund($params){
  469. $t = Yii::$app->db->beginTransaction();
  470. try{
  471. //退款主表
  472. $params['refund_status'] = RefundStatusEnum::REVOKE;
  473. if (!empty($params['id'])) {
  474. $where = ['id' => $params['id'], 'user_id' => $params['user_id']];
  475. } else {
  476. $where = ['order_detail_id' => $params['order_detail_id'], 'user_id' => $params['user_id']];
  477. }
  478. $order_refund_model = OrderRefund::find()->where($where)->orderBy('id desc')->one();
  479. if(empty($order_refund_model)) throw new Exception('售后订单不存在或已删除');
  480. if($order_refund_model->refund_status==RefundStatusEnum::FINISH) throw new Exception('售后完成');
  481. $order_refund_model->refund_status = $params['refund_status'];
  482. $res = $order_refund_model->save();
  483. if(!$res) throw new Exception(OrderRefund::getStaticError());
  484. //订单详情表
  485. $order_detail_model = OrderDetail::findOne($order_refund_model->order_detail_id);
  486. $order_detail_model->is_feedback = OrderStatusEnum::NOT_FEEDBACK;
  487. $res = $order_detail_model->save();
  488. if(!$res) throw new Exception(OrderDetail::getStaticError());
  489. //自动维护订单主表
  490. $order = Order::findOne($order_detail_model->order_id);
  491. Order::autoUpdateStatus($order);
  492. $is_install = MallAddons::isInstall($params['mall_id'], AddonsEnum::ADDONS_NAME_STORE);
  493. if($is_install){
  494. $store_order = StoreOrder::findOne(['order_no'=>$order['order_no']]);
  495. if(!empty($store_order)){
  496. StoreOrder::autoUpdateStatus($store_order);
  497. StoreOrderDetail::updateAll(['is_feedback' => OrderStatusEnum::NOT_FEEDBACK, 'updated_at' => time()], ['main_order_detail_id' => $order_refund_model->order_detail_id]);
  498. }
  499. }
  500. // 取消售后订单
  501. $event = new OrderRefundCancelEvent($order_refund_model['mall_id']);
  502. $data['id'] = $order_refund_model->id;
  503. $data['mall_id'] = $order_refund_model->mall_id;
  504. $data['operator_id'] = $params['operator_id'] ?? 0;
  505. $data['is_admin'] = (int)!empty($params['is_admin']);
  506. Yii::$app->services->events->dispatch($event, $data, $event->listeners);
  507. //统计订单待评价消息数量
  508. $orderStatis['user_id'] = $order->user_id;
  509. $orderStatis['mall_id'] = $order->mall_id;
  510. switch($order->order_status){
  511. case OrderStatusEnum::PAY:
  512. $orderStatis['order_goods_num'] = 1;
  513. break;
  514. case OrderStatusEnum::SHIPMENTS:
  515. $orderStatis['order_received_num'] = 1;
  516. break;
  517. case OrderStatusEnum::SING:
  518. $orderStatis['order_evaluate_num'] = 1;
  519. break;
  520. }
  521. //$orderStatis['order_return_num'] = -1;
  522. Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_ORDER, RabbitMqEnum::ORDER_QUEUE, $orderStatis, OrderUnreadMsg::class, 'orderStastics');
  523. $t->commit();
  524. return true;
  525. }catch(Exception $e){
  526. $t->rollBack();
  527. self::$static_error = $e->getMessage();
  528. return false;
  529. }
  530. }
  531. //是否退回运费
  532. public static function isRefundShippingMoney($order_id,$shipping_money){
  533. $order_detail_list = OrderDetail::lists([
  534. 'where'=>[
  535. ['order_id'=>$order_id],
  536. ['status'=>StatusEnum::ENABLED]
  537. ],
  538. 'select'=>'is_feedback,shipping_status']);
  539. $counts = 0;
  540. foreach ($order_detail_list as $val){
  541. if(!empty($val['shipping_status'])){
  542. $counts = 0;
  543. break;
  544. }
  545. if(empty($val['is_feedback'])) $counts++;
  546. }
  547. if($counts==1) return $shipping_money;
  548. return 0;
  549. }
  550. public static function exportHandle(array $data) {
  551. $download_id = $data['download_id'] ?? 0;
  552. $download = Download::findOne(['id' => $download_id]);
  553. if ($download) {
  554. try {
  555. $params = json_decode($download->params,true);
  556. $isChanjetExport = !empty($params['is_chanjet_addons']);
  557. $filename = $download->name;
  558. $type = $download->type;
  559. $order_detail_model = OrderRefund::find();
  560. if(!empty($params['mch_id'])) $order_detail_model->where(['mch_id'=>$params['mch_id']]);
  561. $order_detail_model->where(['mall_id'=>$params['mall_id']]);
  562. $order_detail_model->andWhere(['>=','status',StatusEnum::DISABLED]);
  563. !empty($get['order_no']) && $order_detail_model->andWhere(['in', 'order_id', self::getOidByOrderNo($params['mall_id'], $get['order_no'])]);
  564. if (isset($get['refund_status'])) {
  565. $order_detail_model->andWhere(['=', 'refund_status', $get['refund_status']]);
  566. } else {
  567. $order_detail_model->andWhere(['in', 'refund_status', [RefundStatusEnum::APPLY,RefundStatusEnum::HANDEL,RefundStatusEnum::FINISH,RefundStatusEnum::RETURN_GOODS,RefundStatusEnum::EXCHANGE_GOODS,RefundStatusEnum::REFUSE]]);
  568. }
  569. if (!empty($get['type']) && !empty($get['keyword'])) $order_detail_model->andWhere(self::searchByKeyword($params['mall_id'], $get['type'], $get['keyword']));
  570. $order_detail_model->with(['base_user','orderDetail','order']);
  571. if ($isChanjetExport) {
  572. $order_detail_model->with(['addonsChanjetRequest']);
  573. }
  574. $fields = $params['fields'];
  575. $fields_arr = self::fieldsList(null, $isChanjetExport);
  576. $have_select_field_arr = [];
  577. foreach ($fields as $value){
  578. $have_select_field_arr[]=$fields_arr[$value];
  579. }
  580. $csv = new CsvTool();
  581. $csv->filename = $filename;
  582. $csv->file_dirname = DownloadEnum::getTypeStorageDirMap($type);
  583. $csv->export_mode = CsvTool::EXPORT_MODE_ASYNC;
  584. $csv->header = $have_select_field_arr;
  585. $refund_status_list = RefundStatusEnum::getMap();
  586. $order_status_list = OrderStatusEnum::getMap();
  587. $refund_type_list = RefundTypeEnum::getMap();
  588. foreach ($order_detail_model->batch() as $order_details) {
  589. $csv->data = array_map(function($order_detail)use($fields,$refund_status_list,$refund_type_list,$order_status_list){
  590. //var_dump($order_detail);die;
  591. $row = [];
  592. foreach ($fields as $field){
  593. switch (true){
  594. case (in_array($field,['created_at'])):
  595. $row[] = !empty($order_detail[$field])?date('Y-m-d H:i:s',$order_detail[$field]):'';
  596. break;
  597. case ($field=='order_id'):
  598. $row[] = $order_detail['order_id'] ?:'';
  599. break;
  600. case ($field=='order_no_a'):
  601. $row[] = $order_detail['order']['order_no'] ?:'';
  602. break;
  603. case ($field=='user_id'):
  604. $row[] = $order_detail['base_user']['id'] ?:'';
  605. break;
  606. case ($field=='username'):
  607. $row[] = $order_detail['base_user']['username'] ?:'';
  608. break;
  609. case ($field=='nickname'):
  610. $row[] = $order_detail['base_user']['nickname'] ?:'';
  611. break;
  612. case ($field=='user_mobile'):
  613. $row[] = $order_detail['base_user']['mobile'] ?:'';
  614. break;
  615. case ($field=='order_no'):
  616. $row[] = $order_detail['order_id'] ?:'';
  617. break;
  618. case ($field=='goods_name'):
  619. $row[] = $order_detail['orderDetail']['goods_name'] ?:'';
  620. break;
  621. case ($field == 'goods_price'):
  622. $row[] = $order_detail['orderDetail']['price'] ?:'';
  623. break;
  624. case ($field=='num'):
  625. $row[] = $order_detail['orderDetail']['num'] ?:'';
  626. break;
  627. case ($field=='refund_status'):
  628. $row[] = $refund_status_list[$order_detail['refund_status']] ?:'';
  629. break;
  630. case ($field=='order_status'):
  631. $row[] = $order_status_list[$order_detail['order']['order_status']] ?:'';
  632. break;
  633. case ($field=='price'):
  634. $row[] = $order_detail['orderDetail']['price']*$order_detail['orderDetail']['num'] ?:'';
  635. break;
  636. case ($field=='type'):
  637. $row[] = $refund_type_list[$order_detail['type']] ?:'';
  638. break;
  639. case ($field == 'refund_price'):
  640. $refund_price = $order_detail['refund_price'] ?:'';
  641. if($order_detail['refund_status'] == RefundStatusEnum::FINISH){
  642. $refund_price = $order_detail['reality_refund_price'] ?:'';
  643. }
  644. $row[] = $refund_price;
  645. break;
  646. case ($field == 'reason'):
  647. $row[] = $order_detail['reason'] ?:'';
  648. break;
  649. case ($field == 'refuse_remark'):
  650. $row[] = $order_detail['remark'] ?:'';
  651. break;
  652. case ($field == 'refuse_address'):
  653. $row[] = $order_detail['order_id'] ?:'';
  654. break;
  655. case ($field == 'chanjet_updated_at'):
  656. if (isset($order_detail['addonsChanjetRequest']['updated_at'])) {
  657. $row[] = date('Y-m-d H:i:s', $order_detail['addonsChanjetRequest']['updated_at']);
  658. }else {
  659. $row[] = '';
  660. }
  661. break;
  662. case ($field == 'chanjet_request_status'):
  663. if (isset($order_detail['addonsChanjetRequest']['request_status'])) {
  664. $row[] = AddonsChanjetRequestEnum::getMap()[$order_detail['addonsChanjetRequest']['request_status']] ?? '';
  665. }else {
  666. $row[] = '';
  667. }
  668. break;
  669. default:
  670. $row[] = !empty($order_detail[$field])?$order_detail[$field]:'';
  671. }
  672. }
  673. //var_dump($row);
  674. return $row;
  675. },$order_details);
  676. $csv->export();
  677. }
  678. $csv->updateDown($download,$params['mall_id']);
  679. return true;
  680. } catch (\Exception $e) {
  681. $download->status = 3;
  682. $res = $download->save(); //var_dump($e->getMessage());
  683. self::setStaticError($e->getMessage());
  684. return $e->getMessage();
  685. }
  686. }
  687. }
  688. public static function fieldsList($field = null, $isChanjetAddons = false){
  689. $fields = [
  690. 'order_id' => '订单ID',
  691. 'order_no_a' => '订单编号',
  692. 'user_id' => '用户ID',
  693. 'username' => '用户编号',
  694. 'nickname' => '用户昵称',
  695. 'user_mobile' => '用户手机号',
  696. 'order_no' => '售后编号',
  697. 'goods_name'=>'商品名称',
  698. 'goods_price' => '价格',
  699. 'num' => '数量',
  700. 'order_status' => '订单状态',
  701. 'refund_status' => '售后状态',
  702. 'price' => '商品价格',
  703. 'type' => '售后方式',
  704. 'refund_price' => '退款金额',
  705. 'reason' => '售后原因',
  706. 'refuse_remark' => '售后说明',
  707. 'refuse_address' => '退货地址',
  708. 'created_at'=>'申请时间'
  709. ];
  710. if ($isChanjetAddons) {
  711. $fields['chanjet_updated_at'] = '最后推送时间';
  712. $fields['chanjet_request_status'] = '推送状态';
  713. }
  714. if ($field === null) return $fields;
  715. $temp = [];
  716. foreach ($field as $val) {
  717. if (isset($fields[$val])) $temp[$val] = $fields[$val];
  718. }
  719. return $temp;
  720. }
  721. /**
  722. * 批量退款
  723. * @param $params
  724. * @return false|void
  725. */
  726. public static function batchRefundSync($params)
  727. {
  728. try {
  729. Yii::$app->services->rabbitMq->push(RabbitMqEnum::EXCHANGE_TASK_IMPORT_USER, RabbitMqEnum::IMPORT_USER, $params, self::class, 'batchRefundSyncExecute');
  730. } catch (\Exception $e) {
  731. return false;
  732. }
  733. }
  734. /**
  735. * 异步请求批量回款
  736. * @param $params
  737. */
  738. public static function batchRefundSyncExecute($params)
  739. {
  740. try {
  741. if ($params['data']) {
  742. foreach ($params['data'] as $item) {
  743. $logic = new OrderRefundLogic();
  744. $logic->execute($params['mall_id'], $item);
  745. }
  746. }
  747. } catch (\Exception $e) {
  748. }
  749. LockHelper::unFLock($params['redis_key']);
  750. }
  751. /**
  752. * 当前订单是否已完成售后
  753. *
  754. * @return boolean
  755. */
  756. public function isRefundFinish()
  757. {
  758. return in_array($this->refund_status, [RefundStatusEnum::FINISH, RefundStatusEnum::REFUSE, RefundStatusEnum::REVOKE])
  759. ? true
  760. : false;
  761. }
  762. /**
  763. * 是否退回商品超时
  764. *
  765. * @return boolean
  766. */
  767. public function isReturnTimeoutOfRefund()
  768. {
  769. $step = $this->step[0];
  770. $can_after_sale_time = Yii::$app->services->setting->mall->get($this->mall_id, 'order.can_after_sale_time') ?? 7;
  771. if (
  772. $step
  773. &&
  774. in_array($step->step_status, [RefundStatusEnum::STEP_2, RefundStatusEnum::STEP_8])
  775. && ($step->created_at + $can_after_sale_time * 86400) < time()
  776. ) {
  777. return true;
  778. }
  779. return false;
  780. }
  781. /**
  782. * 获取真实退款金额
  783. *
  784. * @param PaymentTrade $trade
  785. * @param array $params 退款外部参数(包含自定义退款金额)
  786. * @return float
  787. */
  788. public function getRealityRefundPrice(PaymentTrade $trade, array $params = [])
  789. {
  790. return $trade->isMixedPay()
  791. ? $this->refund_price
  792. : (
  793. // 自定义退款金额
  794. !empty($params['refund_price'])
  795. ? $params['refund_price']
  796. : $this->refund_price
  797. );
  798. }
  799. }