laravel 微信小程序支付处理 - 退款

Posted by 昆山吴彦祖 on 2019.04.29

之前通过微信支付算是完成了在线支付网站的基本流程了,但是如果要有在线退款还需要进行进一步配置


参考资料

laravel-wechat 退款

微信支付接口文档


功能设计

首先,我们逻辑上设计下退款功能,退款是针对订单下的每笔子订单(order_items)的(模仿淘宝),针对每笔自订单的退款金额不能超过该 子订单总金额+订单(orders)运费

ok,数据表(refunds)出来了

最后的code字段是用来存储 微信退款单号(网站唯一)的,有退款单号才能进行一笔订单多次退款



微信端表单提交和js



小程序js代码

formSubmit(e) {
    var that = this
    if (!e.detail.value.type) {
      wx.showToast({
        title: '请选择退款类型',
        icon: 'none',
        duration: 2000
      })
      return false
    }
    if (!e.detail.value.good_type) {
      wx.showToast({
        title: '请选择货物状态',
        icon: 'none',
        duration: 2000
      })
      return false
    }

    if (!e.detail.value.refund_reason_id) {
      wx.showToast({
        title: '请选择退款原因',
        icon: 'none',
        duration: 2000
      })
      return false
    }
    if (!e.detail.value.money) {
      wx.showToast({
        title: '请输入退款金额',
        icon: 'none',
        duration: 2000
      })
      return false
    }
    if (e.detail.value.money>this.data.price_total) {
      wx.showToast({
        title: '退款金额不能高于' + this.data.price_total,
        icon: 'none',
        duration: 2000
      })
      return false
    }
    wx.request({
      url: app.globalData.baseUrl + '/api/refund',
      data: {
        customer_id: wx.getStorageSync('customer').id,
        session: wx.getStorageSync('customer').session,
        order_item_id: order_item_id,
        type: e.detail.value.type,
        good_type: e.detail.value.good_type,
        refund_reason_id: e.detail.value.refund_reason_id,
        money: e.detail.value.money,
        remark: e.detail.value.remark,
      },
      method: 'post',
      success(result) {
        if (result.data.info) {
          wx.showToast({
            title: '申请成功!',
            icon: 'success',
            duration: 2000,
            success:function() {
              setTimeout(function () {
                //要延时执行的代码
                wx.navigateBack({
                  delta: 1
                })
              }, 1000) 
            }
          })
         
        } else {
          wx.showToast({
            title: result.data.errmsg,
            image: '/images/error.png'
          })
        }

      }
    })
  },


退款提交接口

public function index(Request $request){
	
	$order_item = OrderItem::find($request->order_item_id);
	
	if(!$order_item|| !$order_item->order || $order_item->order->customer_id != $request->customer_id || $request->money<=0)
	{
		return response()->json([
			'info' => false,
			'errmsg' => '参数错误',
		]);
	}

	//获取已退/申请金额,防止用户连续点击多次重复提交数据(也能防止下恶意提交)
	$refund_money_sum = Refund::where('order_item_id',$request->order_item_id)->whereIn('status',['1','2','4'])->get();
	$refund_money_sum = $refund_money_sum->sum('money');
	if($request->money+$refund_money_sum >  ($order_item->price*$order_item->num + $order_item->order->price_express)){
		return response()->json([
			'info' => false,
			'errmsg' => '请勿重复提交',
		]);
	}
	$refund = new Refund();
	$refund->money = ($request->money > ($order_item->price*$order_item->num + $order_item->order->price_express))?($order_item->price + $order_item->order->price_express):$request->money;			
	$refund->order_item_id = $request->order_item_id;
	$refund->customer_id = $request->customer_id;
	$refund->type = $request->type;
	$refund->good_type = $request->good_type;
	$refund->refund_reason_id = $request->refund_reason_id;
	$refund->remark = $request->remark;
	$refund->status = 1;
	$refund->save();
	
        //微信退款如果退款单失败,下次退款需要沿用上次的单号,文档上这么写的,可能是退款单失败并不会被微信删除,不沿用的话也会占用退款额度
	//判断上一个退款单号是否失败。如果有,沿用下去,其实这么写不是很严谨,时间有限
	$refund_old = Refund::where('order_item_id',$request->order_item_id)->orderBy('id','desc')->first();
	if($refund_old->status==5){
		$refund->code = $refund_old->code;
	}else{
		$now = now();
                //创建一个网站唯一的退款单号
		$refund->code = $now->year.$now->month.$now->day.$now->hour.$now->minute.$now->second.$request->customer_id.$refund->id;
	}
	
	$refund->save();
	
	
	return response()->json([
		'info' => true,
	]);
}


到这一步。简单的退款申请已经被我们提交到后台和数据库了。接下来就要用laravel-wechat来进行退款操作了

我们希望做的是,用户后台审核退款申请(初始status=1),如果审核通过(status=2),系统自动退钱,退钱成功(status=4)/失败(status=5)(银行卡冻结等原因),退款申请状态变为 '退款成功'/'退款失败’,但是因为退款并不是同步成功/失败的,所以这里还需要用到之前用的 延时队列操作


管理员后台

laravel-admin架设的,不多累赘,只能更改状态为1的退款申请即可




创建并编辑refund的资源监控者

namespace App\Observers;
use App\Refund;
use Carbon\Carbon;
use Log;
use App\Jobs\RefundJob;

class RefundObserver
{
	public function updated(Refund $refund)
	{
	
	//退款申请状态更新为2时候进行微信退款操作
	if($refund->status == 2){
		$app = app('easywechat.payment');
		if($refund->orderItem->order){
			//申请退款
			$result = $app->refund->byOutTradeNumber($refund->orderItem->order->code,$refund->code,($refund->orderItem->order->price_good+$refund->orderItem->order->price_express)*100,($refund->money)*100);
			if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') {
				Log::info('退款申请成功!退款中');
				//创建延时队列3天后执行
				RefundJob::dispatch($refund)->delay(now()->addDays(3));
			} else {
				//$unify['return_code'] = 'FAIL';
				Log::info('退款申请失败');
				Log::info($result);
				$refund->status = 5;
				$refund->save();
				
			}
		}else{
			Log::info("退款失败!订单不存在");
			$refund->status = 5;
			$refund->save();
		}
	}
	
	}
}


创建并编辑 队列任务类

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use EasyWeChat\Factory;
use App\Refund;
use Log;

class RefundJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
	public $tries = 5;//最大尝试次数
	public $timeout = 5;//最大尝试时间
	public $refund;
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(Refund $refund)
    {
        //
		$this->refund = $refund;
		
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
	$app = Factory::payment(config('wechat.payment.default'));
        $result = $app->refund->queryByOutRefundNumber($this->refund->code);
	if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS' && $result['refund_status_0'] === 'SUCCESS') {
		$this->refund->status = 4;  
		$this->refund->save();
	} else {
		Log::info('退款申请'.$this->refund->id.'退款未到帐!');
		Log::info($result);
		$this->refund->status = 5;
		$this->refund->save();			
	}
    }
}


好了,到这里基本的功能就ok了,如果时间充足想做的更完美呢

1、加入微信模板消息提醒退款成功

2、退款失败的原因存储到refund表,不然现在这种状况,退款失败了还得进log档查看失败原因,哈哈

3、因为存在一个订单详细有多个退款的状况,设计表的时候可能要去考在订单详细中加入已退款总额。

4、运费的处理,我这个案子用的方法是:每个订单详细可以退 (订单详细+运费) 的总额,如果 (订单总额-已退金额 < 订单详细+运费), 那只能退 (订单总额-已退金额),感觉还是比较混乱,而且中间写起来很麻烦,感觉可以运费单独处理逻辑更清晰。

easy_wechat 微信支付