之前通过微信支付算是完成了在线支付网站的基本流程了,但是如果要有在线退款还需要进行进一步配置
参考资料
功能设计
首先,我们逻辑上设计下退款功能,退款是针对订单下的每笔子订单(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、运费的处理,我这个案子用的方法是:每个订单详细可以退 (订单详细+运费) 的总额,如果 (订单总额-已退金额 < 订单详细+运费), 那只能退 (订单总额-已退金额),感觉还是比较混乱,而且中间写起来很麻烦,感觉可以运费单独处理逻辑更清晰。