Refund.php 18 KB

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