Refund.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. <?php
  2. namespace app\common\model;
  3. use app\common\service\OrderRefundService;
  4. use app\common\service\RefundService;
  5. use Carbon\Carbon;
  6. use think\db\Query;
  7. use think\Model;
  8. use Yansongda\Supports\Arr;
  9. /**
  10. * 短信验证码
  11. * @property Orders orders
  12. * @property User user
  13. * @property LogisticsCompany com
  14. * @property UserAddress address
  15. * @property int refund_status
  16. * @property int refund_type
  17. * @property int create_time
  18. * @property int audit_time
  19. * @property int over_time
  20. * @property int rm_time
  21. * @property int user_send_time
  22. * @property int user_trans_com_id
  23. * @property int refund_by
  24. * @property string user_trans_no
  25. * @property bool is_wait_audit
  26. * @property bool has_money
  27. * @property bool need_rec
  28. * @property bool need_tk
  29. * @property bool need_complete
  30. * @property bool can_view_logistics
  31. * @property bool self_back_goods
  32. * @property string audit_remark
  33. * @property string order_no
  34. * @property string fix_order_no
  35. * @property string refund_type_text
  36. * @property string refund_by_text
  37. * @property float amount_last
  38. * @method static static|Query FilterRefund($status=null)
  39. * @method static static|Query FilterTs()
  40. */
  41. class Refund Extends Model
  42. {
  43. protected $name='order_info_refund';
  44. protected $append=[
  45. 'timeline',
  46. 'refund_status_text',
  47. 'need_rec',
  48. 'need_tk',
  49. 'is_pass',
  50. 'need_complete',
  51. 'refund_type_text',
  52. 'refund_by_text',
  53. 'reason_text',
  54. 'can_view_logistics',
  55. ];
  56. const REFUND_ING=100;
  57. const REFUND_PASS=200;
  58. const REFUND_JI=250;
  59. const REFUND_RUNNING=270;
  60. const REFUND_REFUNDING=280;
  61. const REFUND_REJECT=300;
  62. const REFUND_CANCEL=400;
  63. const REFUND_OVER=500;
  64. public static $refundStatus=[
  65. self::REFUND_ING=>'申请审核中',
  66. self::REFUND_PASS=>'申请通过',
  67. self::REFUND_JI=>'用户寄回',
  68. self::REFUND_RUNNING=>'售后中',
  69. self::REFUND_REFUNDING=>'退款中',
  70. self::REFUND_REJECT=>'申请驳回',
  71. self::REFUND_CANCEL=>'已取消',
  72. self::REFUND_OVER=>'处理完成',
  73. ];
  74. protected $type=[
  75. 'address'=>'json',
  76. ];
  77. /**
  78. * @return string[]
  79. */
  80. public static function getRefundStatus(): array
  81. {
  82. return self::$refundStatus;
  83. }
  84. const REFUND_TYPE_MONEY=1;
  85. const REFUND_TYPE_ALL=2;
  86. const REFUND_TYPE_HHBX=5;
  87. public static $refundTypes=[
  88. self::REFUND_TYPE_MONEY=>'取消订单',
  89. self::REFUND_TYPE_ALL=>'退款退货',
  90. self::REFUND_TYPE_HHBX=>'换货/维修',
  91. ];
  92. public static $refundTypeGoods=[self::REFUND_TYPE_ALL,self::REFUND_TYPE_HHBX];
  93. public static $refundTypeMoney=[self::REFUND_TYPE_MONEY,self::REFUND_TYPE_ALL];
  94. const REASON_QU=1;
  95. public static $reasons=[
  96. self::REASON_QU=>'质量问题',
  97. 2=>'7天无理由退货(退回运费需客户承担)',
  98. 3=>'协商一致退款',
  99. 4=>'快递运输破损',
  100. 5=>'其他',
  101. ];
  102. const TH_TYPE_NONE=0;
  103. const TH_TYPE_SELF_SEND=1;
  104. const TH_TYPE_SELF_FEE=2;
  105. const TH_TYPE_FREE=3;
  106. const TH_TYPE_SENDER=4;
  107. public static $goodsTypes=[
  108. //self::TH_TYPE_NONE=>'无需退货',
  109. self::TH_TYPE_SELF_SEND=>'自行邮寄',
  110. //self::TH_TYPE_SELF_FEE=>'拍维修费',
  111. //self::TH_TYPE_FREE=>'一年以内免费保修',
  112. self::TH_TYPE_SENDER=>'上门取件',
  113. ];
  114. protected $autoWriteTimestamp=true;
  115. public function orderInfo(){
  116. return $this->belongsTo(OrderInfo::class);
  117. }
  118. public function orders(){
  119. return $this->belongsTo(Orders::class,'order_id');
  120. }
  121. public function user(){
  122. return $this->belongsTo(User::class);
  123. }
  124. public function com(){
  125. return $this->belongsTo(LogisticsCompany::class,'user_trans_com_id');
  126. }
  127. public function allowCancel(){
  128. return in_array($this['refund_status'],[
  129. self::REFUND_ING,
  130. ]);
  131. }
  132. public function allowApply(){
  133. return in_array($this->refund_status,[
  134. self::REFUND_REJECT,
  135. self::REFUND_CANCEL,
  136. self::REFUND_OVER,
  137. ]);
  138. }
  139. public function allowEdit(){
  140. return in_array($this->refund_status,[
  141. self::REFUND_ING,
  142. ]);
  143. }
  144. public function makeCancel(){
  145. $this['refund_status']=self::REFUND_CANCEL;
  146. $this->save();
  147. }
  148. #根据订单状态选择售后
  149. public static function makeRefundConfig(OrderInfo $orderInfo,$inject=true,$delKeys=false){
  150. $order=$orderInfo->orders;
  151. $refundConfig=[];
  152. if(!$orderInfo->refund || $orderInfo->refund->allowApply() || $orderInfo->refund->allowEdit()) {
  153. #未发货
  154. if (in_array($order['status'], [$order::S_WAIT_SEND])) {
  155. $refundConfig['refund_type'] = Arr::only(self::getRefundTypes(), [
  156. self::REFUND_TYPE_MONEY,
  157. ]);
  158. $refundConfig['type'] = [
  159. self::REFUND_TYPE_MONEY => null,
  160. ];
  161. $refundConfig['req_order'] = [
  162. self::REFUND_TYPE_MONEY=>false,
  163. ];
  164. $refundConfig['req_address'] = null;
  165. $refundConfig['reason'] = array_values(Refund::getReasons());
  166. list($amount_single, $amount_install_single) = RefundService::setOrderInfo($orderInfo, $orderInfo['num'], $orderInfo['num_install'])->amount();
  167. $refundConfig['amount_single'] = $amount_single;
  168. $refundConfig['amount_install_single'] = $amount_install_single;
  169. $refundConfig['num'] = $orderInfo['num'];
  170. $refundConfig['num_install'] = $orderInfo['num_install'];
  171. }
  172. #已发货
  173. /*elseif(in_array($order['status'],[$order::S_WAIT_REC,$order::S_OVER])){
  174. #七天内可以退货退款
  175. if(time()<$order['send_time']+7*86400){
  176. $refundConfig['refund_type']=[
  177. self::REFUND_TYPE_ALL=>'退款退货',
  178. ];
  179. $refundConfig['type']=[
  180. self::TH_TYPE_FREE=>'自行邮寄',
  181. ];
  182. $refundConfig['reason']=array_values(Refund::getReasons());
  183. $refundConfig['req_amount']=1;
  184. $refundConfig['amount']=RefundService::setOrderInfo($orderInfo,$orderInfo['num'],$orderInfo['num_install'])->amount(true);
  185. $refundConfig['req_order']=0;
  186. $refundConfig['num']=$orderInfo['num'];
  187. $refundConfig['num_install']=$orderInfo['num_install'];
  188. }
  189. #只能换货保修
  190. else{
  191. $refundConfig['refund_type']=[
  192. self::REFUND_TYPE_HHBX=>'换货保修',
  193. ];
  194. $refundConfig['req_order']=0;
  195. #一年内
  196. if(time()<strtotime('+1year',$order['send_time'])){
  197. $refundConfig['type']=[
  198. self::TH_TYPE_FREE=>'一年以内免费保修',
  199. ];
  200. }else{
  201. $refundConfig['type']=[
  202. self::TH_TYPE_SELF_FEE=>'拍维修费',
  203. ];
  204. $refundConfig['req_order']=1;
  205. $refundConfig['req_order_goods']=Goods::getFixGoods();
  206. }
  207. $refundConfig['reason']=array_values(Refund::getReasons());
  208. $refundConfig['req_amount']=0;
  209. }
  210. }*/
  211. elseif (in_array($order['status'], [$order::S_WAIT_REC,$order::S_OVER])) {
  212. $gtYear=Carbon::now()->gt(Carbon::createFromTimestamp($order->send_time)->addYear());
  213. if($gtYear){
  214. $refundConfig['refund_type'] = Arr::only(self::getRefundTypes(), [
  215. self::REFUND_TYPE_HHBX,
  216. ]);
  217. $refundConfig['type'] = [
  218. self::REFUND_TYPE_HHBX => Arr::only(self::getGoodsTypes(), [
  219. self::TH_TYPE_SELF_SEND,
  220. self::TH_TYPE_SENDER,
  221. ]),
  222. ];
  223. $refundConfig['req_order'] = [
  224. self::REFUND_TYPE_HHBX=>$gtYear,
  225. ];
  226. }else{
  227. $refundConfig['refund_type'] = Arr::only(self::getRefundTypes(), [
  228. self::REFUND_TYPE_ALL,
  229. self::REFUND_TYPE_HHBX,
  230. ]);
  231. $refundConfig['type'] = [
  232. self::REFUND_TYPE_ALL => Arr::only(self::getGoodsTypes(), [
  233. self::TH_TYPE_SELF_SEND,
  234. self::TH_TYPE_SENDER,
  235. ]),
  236. self::REFUND_TYPE_HHBX => Arr::only(self::getGoodsTypes(), [
  237. self::TH_TYPE_SELF_SEND,
  238. self::TH_TYPE_SENDER,
  239. ]),
  240. ];
  241. $refundConfig['req_order'] = [
  242. self::REFUND_TYPE_ALL=>false,
  243. self::REFUND_TYPE_HHBX=>false,
  244. ];
  245. }
  246. $refundConfig['req_address'] = [
  247. self::TH_TYPE_SELF_SEND=>false,
  248. self::TH_TYPE_SENDER=>true,
  249. ];
  250. $refundConfig['reason'] = array_values(Refund::getReasons());
  251. list($amount_single, $amount_install_single) = RefundService::setOrderInfo($orderInfo, $orderInfo['num'], $orderInfo['num_install'])->amount();
  252. $refundConfig['amount_single'] = $amount_single;
  253. $refundConfig['amount_install_single'] = $amount_install_single;
  254. $refundConfig['num'] = $orderInfo['num'];
  255. $refundConfig['num_install'] = $orderInfo['num_install'];
  256. $refundConfig['req_order_goods']=Goods::getFixGoods();
  257. }
  258. }
  259. if(empty($refundConfig)){
  260. $refundConfig=null;
  261. }
  262. if($delKeys && !empty($refundConfig['refund_type'])){
  263. $newArr=[];
  264. foreach ($refundConfig['refund_type'] as $key=>$value){
  265. $newArr[]=compact('key','value');
  266. }
  267. $refundConfig['refund_type']=$newArr;
  268. }
  269. if($inject){
  270. $orderInfo['refund_config']=$refundConfig;
  271. }
  272. return $refundConfig;
  273. }
  274. public function allowAudit(){
  275. return $this['refund_status']==self::REFUND_ING;
  276. }
  277. /**
  278. * @return string[]
  279. */
  280. public static function getGoodsTypes(): array
  281. {
  282. return self::$goodsTypes;
  283. }
  284. #退款相关
  285. /**
  286. * 退款金额
  287. */
  288. public function getRefundAmount(){
  289. return $this['amount_last'];
  290. }
  291. public function refundResult($succ,$remark=''){
  292. if(is_bool($succ)){
  293. $this['pay_status']=$succ?1:2;
  294. }else{
  295. $this['pay_status']=$succ;
  296. }
  297. $this['pay_remark']=$remark;
  298. $this->rm_time=time();
  299. $this->over_time=time();
  300. $this->save();
  301. }
  302. public function isRefundMoney(){
  303. return in_array($this['refund_type'],self::getRefundTypeMoney());
  304. }
  305. #end
  306. public function payToUser(){
  307. if($this->amount_last>0) {
  308. $payment = $this->orders->payment ?? null;
  309. if ($payment) {
  310. $this->order_no=order_no('tk');
  311. $this->save();
  312. $refund = new OrderRefundService();
  313. $refund->setPayment($payment);
  314. $refund->setRefund($this);
  315. $refund->setBody("订单[{$this->orders->order_no}]退款");
  316. $refund->pay();
  317. }
  318. }
  319. }
  320. public function makeAudit($pass,$extend){
  321. $this['refund_status']=$pass?self::REFUND_PASS:self::REFUND_REJECT;
  322. if(!empty($extend['audit_remark'])) {
  323. $this->audit_remark = $extend['audit_remark'];
  324. }
  325. if($this->has_money) {
  326. $this->amount_last = $extend['amount_last'];
  327. }
  328. $this->audit_time=time();
  329. if($pass){
  330. if($this->refund_type==self::REFUND_TYPE_MONEY){
  331. $this->payToUser();
  332. $this->orders->makeCancel();
  333. }elseif($this->refund_by==self::TH_TYPE_SENDER){
  334. $this->makeLabelOrder();
  335. }
  336. }
  337. $this->save();
  338. SiteMsg::sendMsg(
  339. $pass?SiteMsg::TYPE_ORDER_REFUND_PASS:SiteMsg::TYPE_ORDER_REFUND_REJECT,
  340. $this->user
  341. );
  342. #总订单
  343. /*$order=$this->orders;
  344. if($order && $order->status<Orders::S_OVER){
  345. $has=OrderInfo::where('order_id',$this['order_id'])
  346. ->filterHasUnRefund()
  347. ->find();
  348. if(!$has){
  349. $order->makeCancel();
  350. }
  351. }*/
  352. }
  353. public function makeComplete(){
  354. $refund=$this;
  355. $refund->over_time=time();
  356. $refund->refund_status=Refund::REFUND_OVER;
  357. return $refund->save();
  358. }
  359. public function makeLabelOrder(){
  360. }
  361. /**
  362. * @return string[]
  363. */
  364. public static function getReasons(): array
  365. {
  366. $reasons=self::$reasons;
  367. $arr=[];
  368. foreach ($reasons as $key=>$value){
  369. $arr[$key]=compact('key','value');
  370. }
  371. return $arr;
  372. }
  373. /**
  374. * @return string[]
  375. */
  376. public static function getRefundBys(): array
  377. {
  378. $obj=self::$goodsTypes;
  379. $arr=[];
  380. foreach ($obj as $key=>$value){
  381. $arr[$key]=compact('key','value');
  382. }
  383. return $arr;
  384. }
  385. /**
  386. * @return string[]
  387. */
  388. public static function getRefundTypes(): array
  389. {
  390. return self::$refundTypes;
  391. }
  392. /**
  393. * @return int[]
  394. */
  395. public static function getRefundTypeGoods(): array
  396. {
  397. return self::$refundTypeGoods;
  398. }
  399. /**
  400. * @return int[]
  401. */
  402. public static function getRefundTypeMoney(): array
  403. {
  404. return self::$refundTypeMoney;
  405. }
  406. /**
  407. * 售后中及已售后的
  408. * @param Query $query
  409. * @param null $status 1进行中2已完成
  410. */
  411. public function scopeFilterRefund(Query $query,$status=null){
  412. if(is_null($status)) {
  413. $query->whereBetween("{$this->getTable()}.refund_status", [self::REFUND_ING, self::REFUND_PASS]);
  414. }elseif ($status==1){
  415. $query->where("{$this->getTable()}.refund_status", self::REFUND_ING);
  416. }elseif ($status==2){
  417. $query->where("{$this->getTable()}.refund_status", self::REFUND_PASS);
  418. }
  419. }
  420. /**
  421. * 投诉类型的售后
  422. * @param Query $query
  423. */
  424. public function scopeFilterTs(Query $query){
  425. $query->where("{$this->getTable()}.reason1",self::REASON_QU);
  426. }
  427. public function getIsWaitAuditAttr($_,$model){
  428. return $model['refund_status']==self::REFUND_ING;
  429. }
  430. public function gethasMoneyAttr($_,$model){
  431. return $this->isRefundMoney();
  432. }
  433. #是否可查看寄回物流
  434. public function getCanViewLogisticsAttr($_,$model){
  435. return $model['user_trans_no']?true:false;
  436. }
  437. #是否是需要寄回
  438. public function getIsGoodsBackAttr($_,$model){
  439. return $this->refund_status==self::REFUND_PASS && in_array($this->refund_type,self::getRefundTypeGoods());
  440. }
  441. #是否自己寄回
  442. public function getSelfBackGoodsAttr($_,$model){
  443. return $this->refund_status==self::REFUND_PASS && $this->refund_type==self::TH_TYPE_SELF_SEND && !$model['user_send_time'];
  444. }
  445. public function getRefundStatusTextAttr($_,$model){
  446. return Arr::get(self::getRefundStatus(),$model['refund_status']);
  447. }
  448. public function getRefundTypeTextAttr($_,$model){
  449. return Arr::get(self::getRefundTypes(),$model['refund_type']);
  450. }
  451. public function getRefundByTextAttr($_,$model){
  452. if(!$model['refund_by']){
  453. return null;
  454. }
  455. return Arr::get(self::getRefundBys(),$model['refund_by'])['value'];
  456. }
  457. public function getReasonTextAttr($_,$model){
  458. if(!$model['reason1']){
  459. return null;
  460. }
  461. return Arr::get(self::getReasons(),$model['reason1'])['value'];
  462. }
  463. #是否可以已收货
  464. public function getNeedRecAttr($_,$model){
  465. return $this->self_back_goods && $this->user_send_time;
  466. }
  467. #是否可以退款
  468. public function getNeedTkAttr($_,$model){
  469. return $this->refund_status==self::REFUND_REFUNDING;
  470. }
  471. #是否需要完成
  472. public function getNeedCompleteAttr($_,$model){
  473. $con1 = $this->refund_type==self::REFUND_TYPE_HHBX;
  474. $con2 = $model['refund_status']==self::REFUND_RUNNING;
  475. return $con1 && $con2;
  476. }
  477. public function getTimelineAttr(){
  478. $arr=[
  479. [
  480. 'title'=>'提交申请',
  481. 'time' =>self::datetime($this->create_time),
  482. 'status'=>1,
  483. ],
  484. ];
  485. $arr[]=[
  486. 'title'=>'申请审核',
  487. 'time' =>self::datetime($this->audit_time),
  488. 'status'=>100,
  489. ];
  490. switch ($this->refund_type){
  491. case self::REFUND_TYPE_MONEY:
  492. $arr[]=['title'=>'退款中','time' =>self::datetime($this->rm_time),'status'=>200,];
  493. $arr[]=['title'=>'已完成','time' =>self::datetime($this->over_time),'status'=>1000,];
  494. break;
  495. case self::REFUND_TYPE_ALL:
  496. $arr[]=['title'=>'用户寄回','time' =>self::datetime($this->user_send_time),'status'=>300,];
  497. $arr[]=['title'=>'退款中','time' =>self::datetime($this->rm_time),'status'=>200,];
  498. $arr[]=['title'=>'已完成','time' =>self::datetime($this->over_time),'status'=>1000,];
  499. break;
  500. case self::REFUND_TYPE_HHBX:
  501. $arr[]=['title'=>'用户寄回','time' =>self::datetime($this->user_send_time),'status'=>300,];
  502. $arr[]=['title'=>'售后中','time' =>self::datetime($this->rm_time),'status'=>400,];
  503. $arr[]=['title'=>'已完成','time' =>self::datetime($this->over_time),'status'=>1000,];
  504. break;
  505. }
  506. return $arr;
  507. }
  508. #是否已通过
  509. public function getIsPassAttr($_,$model){
  510. return !in_array($model['refund_status'],[
  511. self::REFUND_REJECT,
  512. self::REFUND_CANCEL,
  513. ]);
  514. }
  515. public static function datetime($time){
  516. if($time){
  517. return Carbon::createFromTimestamp($time)->toDateTimeString();
  518. }
  519. return null;
  520. }
  521. protected static function init()
  522. {
  523. self::beforeInsert(function (self $refund){
  524. $refund['refund_status']=self::REFUND_ING;
  525. if($refund->refund_type==self::REFUND_TYPE_HHBX){
  526. $refund->fix_order_no=order_no('wx');
  527. }
  528. });
  529. self::afterUpdate(function (self $refund){
  530. if($refund->refund_status==self::REFUND_PASS && $refund->isRefundMoney()){
  531. Transaction::addTransaction($refund);
  532. }
  533. });
  534. }
  535. }