Refund.php 20 KB

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