Skip to content
关注公众号,获取新课通知

拒绝or同意退款


控制器 app/controller/admin/Order.php

php
// 拒绝/同意 申请退款
   public function handleRefund(){
       // 获取当前订单
       $order = request()->Model;
       // 判断订单状态是否正确
       if ($order->refund_status !== 'applied') {
           ApiException('订单状态不正确');
       }
       $param = request()->param();
       // 是否同意退款
       if ($param['agree']) {
           // 同意
           $this->__refundOrder($order);
       } else {
           // 拒绝退款
           $order->extra = [
               'refund_reason'=> isset($order->extra->refund_reason) ? $order->extra->refund_reason: null,
               'refund_disagree_reason'=>$param['disagree_reason']
           ];
           $order->refund_status = 'pending';
           $order->save();
       }
       return showSuccess($order);
   }

// 退款逻辑
   public function __refundOrder($order){
       switch ($order->payment_method) {
           case 'alipay':
               // 生成退款单号
               $refundNo = \app\model\Order::setRefundOrderNo();
               // 调用支付宝退款
               $obj = new \app\controller\common\AliPay();
               $alipay = $obj->alipay;
               $res = $alipay->refund([
                   'out_trade_no' => $order->no, // 之前的订单流水号
                   'refund_amount' => $order->total_price, // 退款金额,单位元
                   'out_request_no' => $refundNo, // 退款订单号
               ]);
               // 根据支付宝的文档,如果返回值里有 sub_code 字段说明退款失败
               if ($res->sub_code) {
                   // 将订单的退款状态标记为退款失败
                   $order->refund_no = $refundNo;
                   $order->refund_status ='failed';
                   $order->extra =[
                       'refund_failed_code'=>$res->sub_code
                   ];
                   $order->save();
               } else {
                   // 将订单的退款状态标记为退款成功并保存退款订单号
                   $order->refund_no = $refundNo;
                   $order->refund_status ='success';
                   $order->save();
               }
               break;
           case 'wechat':
               // 生成退款单号
               $refundNo = \app\model\Order::setRefundOrderNo();
               // 调用微信退款
               $obj = new \app\controller\common\wechatPay();
               $wechat = $obj->wechat;
               $res = $wechat->refund([
               	'type' => 'app',
               	'out_trade_no' => $order->no, // 之前的订单流水号
			    'out_refund_no' => $refundNo, // 退款订单号
			    'total_fee' => strval($order->total_price*100), // 退款金额,单位元
			    'refund_fee' => strval($order->total_price*100), // 退款金额,单位元
			    'refund_desc' => $order->extra->refund_reason,
               ]);
               if ($res->return_code !== 'SUCCESS') {
                   // 将订单的退款状态标记为退款失败
                   $order->refund_no = $refundNo;
                   $order->refund_status ='failed';
                   $order->extra =[
                       'refund_failed_code'=>$res->return_msg
                   ];
                   $order->save();
               } else {
                   // 将订单的退款状态标记为退款成功并保存退款订单号
                   $order->refund_no = $refundNo;
                   $order->refund_status ='success';
                   $order->save();
               }
               break;
       }
   }

模型 app/model/Order.php

php
// 生成退款单号
   public static function setRefundOrderNo(){
       do {
           $prefix = 'refund'.date('YmdHis');
           // 随机生成 6 位的数字
           $no = $prefix.str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
       } while (self::where('refund_no',$no)->find());
       return $no;
   }

安装支付扩展包 文档地址:https://pay.yanda.net.cn/docs/2.x/overview

php
composer require yansongda/pay

支付宝支付 app/controller/common/AliPay.php

php
<?php

namespace app\controller\common;

use think\Request;
use Yansongda\Pay\Pay;
class AliPay
{
    public $alipay;
    // 初始化
    public function __construct(){
        $config = config('cms.payment.alipay');
        $this->alipay = Pay::alipay(config('cms.payment.alipay'));
    }

    // 网页支付
    public function web($order){
        return $this->alipay->web($order)->send();
    }

    // app支付
    public function app($order){
        return $this->alipay->app($order)->send();
    }
}

微信支付 app/controller/common/wechatPay.php

php
<?php

namespace app\controller\common;

use think\Request;
use Yansongda\Pay\Pay;
class wechatPay
{
    public $wechat;

    public function __construct(){
        $config = config('cms.payment.wechat');
        $this->wechat = Pay::wechat($config);
    }

    // 扫码支付
    public function scan($order){
        return $this->wechat->scan($order)->send();
    }

    // app支付
    public function app($order){
        return $this->wechat->app($order)->send();
    }
    
    // 小程序支付
    public function miniapp($order){
    	return $this->wechat->miniapp($order);
    }
    
    // 公众号支付
    public function mp($order){
    	return $this->wechat->mp($order)->send();
    }
}

配置文件 config/cms.php(配置内容换成你的,以下仅供参考,不能使用)

php
	// 支付相关
    'payment'=>[
		// 支付宝支付
        'alipay' => [
            'notify_url' =>"http://ceshi3.dishait.cn/api/payment/alipay/notify",

            'app_id'         => '2018091545059522',
            'ali_public_key' => "MIIBIcANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhysWmVdABTKzpH3qM40zBo2zGVWNhGe4xm/bFWKnmv/KOtmfZNm8tBjQGQ1o9ME56m0fASnnz5lJS2r+AC4qIePCiKFE5uH57WZo2N9yO042y8VZ/MJsWUgog/FrIvjU3pkp5vB7FcZ1uresolt/jTQPe7Bdoqt6GDetKBCXdEbJbL+DOyYNiCPmVaVH9pv8oy58RjWP5JeBOYIDe3+aMyilHt2sH0Vg/KrvKWzFanoltzwHrgMp27gtvTDPmnIjpkKEVfzevxOHe0q/NpsD+4qWd/6xclfcgd70Y+Dc9dwrq30BfoWJtIytTJJ0qqeNkQOLNeOppzintBXFpgQIDAQAB",
            'private_key'    => "MIIEpACBAAKCAQEAqld5rwZvdFQYzIMhfx7mn+YxTQ6o2yhoYfpZnQaDH7yHCqfIN8svMJ2PXVSrfMkX+dlkQzsT5nYiujRZ8WJ/RT5Nbh45nAoj+o9id3weWs0qwXU2a7F36LwuIDmvn0zSVoq4zq0wdaCZio/sa7IZ1NaRu/FYuoYINlYZgWYsggecxKBTR6gGb4Cad/9T759L/2xWc0vETvr14KRb+5cnKOc+avR9BAbgRQNBMZcD6fx5DgZXzQ704GRh4W32hL5WXpzOFPvNf1oOTD4brv4KojPEMcfxBUV/tzDk+dr+HpJpLtGV9aRMqgjZumIpCjmPRADIVpgrmjEXYvF64/UXvwIDAQABAoIBAB/GW4ODnszDVzDnERuGZxzSssWeA2+GNRp5ubep3FHSOBqLu7R0qWPXMEQHpEmNtXQ80hAceBTYWpgDZfe6GOWQvp7Y8oQh/B4kGwkQ1RS5SKL8H5MBb1VBjXoYuW+9RsX+1nhKsfl/6WkZeuoR8HNvjM6Wa/e+zdkudwT2GqYD1yuPhSmtlmpjLh0kMBfK4qFbVxbu3XqViWwJOuLwFQl6N258dfp4uMnKNz0nqNQeUfoDZ35Ixe0orPIffypdfdTqOirC7WYbF/8W5Ygtuj/HTQqAIZ+I1qbmI5GfP4Q1S0kkXvj4aq/5r0u9iJOI7GK9U8mFVNu3xVKKsJkCgYEA0sCQmtpSAldXCXFuXSB6RJUKCIw2jmcVLuMfCYBKyGlNUa2jSIKQisXbSQmqYgL7/L6G82GwKjD6ZXvQFAs+c8L/MEkaF2nT6zmMtDSBdIyFIb+bd8kx97EmQjUyd+vf4vDY1zV1cGZa6iF/EHgMHNK4OM5EiX9zvFDGt/3qz8sCgYEAzunatzRCFhQK0tfAV6c0VOrgZntirooa6onH/0sO6Sm1W4vEJoYHkaPJpaOO4V2nbNcLK1620NkqB1RT+5YzMb7jEpL8DY24ufM1dD+aihWHt8DGCRSZdycDcwIKM+f2vwtsvwjldfudRjh9QXWTfALPkSbqpYABoqqKD8PtcV0CgYEAr1pbtwyKCbqkB45itoesU93yEDShvBCW9oExNNWS43eCRsCDyHQiUeTYVMf/BTfYdG4OmPih/CjuXnwLIHJOj0Ei1Qkt9WcvVVt38ARz5gZ5SyBC+gLkWWQDIjli+Za/nPKqaT3orhHr+TzPnWNVKLJHZ7RwIDt0j65h+XsC4csCgYAi3wyqVZdRqz0Lvaq/2wEZ0p/RBbhi3AmfP2tCXj78Erhq3kpHh80cwXLJhKAe4S7HTBKo04SR/Bd2NsMUooKsPpR8W+M40YqxZAi9N77uyKQf1tBJVXxrtVqCdnSLvOs71UwVggVR9f7Sh9CsSIl7m+mpd9qR29nqtT0hXQ7WTQKBgQDP4NIHuAQOc0xbO58HZgtGB4wFLbSG+PFL+VJxlVvJ6oBHR6axiWTFR+yzS/nJC918BCh7HfsQnfePlrydG2o7NDO25jGyQZvbf5welgCSxWaQ1j9rulng4saBrZNgOmUFBWi+YB3U2NY5HILYuw/2UB8sJyv326iqnKLA7hVVzg==",
	            'log'            => [
	                'file' => './payment/alipay.log',
	                'level' => 'debug',
	                'type' => 'single', // optional, 可选 daily.
	                'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
	            ],
	            //'mode' => 'dev' // 进入沙箱模式 不需要的时候需要注释掉
        ],
    	// 微信支付
        'wechat' => [
            'notify_url' => "http://ceshi3.dishait.cn/api/payment/wxpay/notify",
            
            'app_id' => 'wxf0d08abcc66Vab61', // 公众号 APPID
            'miniapp_id' => 'wxb1fd05dS99c0fe32', // 小程序 APPID
            
            'appid'      => 'wxc55deade7Da3bde', // appid
            'mch_id'      => '1554008681', // 商户号
            'key'         => '8b078a1ec793049f1569793464c7049f', // API 密钥
            'cert_client' => __DIR__.'/payment/wechat2/apiclient_cert.pem',
            'cert_key'    => __DIR__.'/payment/wechat2/apiclient_key.pem',
            
            'log'         => [
                'file' => './payment/wechat_pay.log',
                'level' => 'debug', // 建议生产环境等级调整为 info,开发环境为 debug
		        'type' => 'single', // optional, 可选 daily.
		        'max_file' => 30, 
            ],
            'http' => [ // optional
                'timeout' => 5.0,
                'connect_timeout' => 5.0,
            ],
            //'mode' => 'dev',
        ],
    ],
    // 微信小程序相关(微信小程序支付需要配置)
    'wx'=>[
        'appid'=>'wxb1fd05dS99c0fe32',
        'secret'=>'09f38qdf2b1asf9763e9a6924f9ced'
    ]

控制器 app/controller/common/Payment.php

php
<?php

namespace app\controller\common;

use think\Request;
use app\BaseController;
use Yansongda\Pay\Pay;
class Payment extends BaseController
{
    protected $autoNewModel = false;

    protected $excludeValidateCheck = ['alipayReturn','alipayNotify','wechatNotify'];

    // 支付宝支付
    public function payByAlipay(){
        // 验证当前订单是否为该用户
        $user = request()->UserModel;
        $order = request()->Model;
        if ($user->id !== $order->user_id) {
            ApiException('非法操作');
        }
        // 当前订单是否已经支付过
        if ($order->paid_time || $order->closed) {
            ApiException('订单状态不正确');
        }
        
        $alipay = new AliPay;
        return $alipay->app([
            'out_trade_no' => $order->no, // 订单编号,需保证在商户端不重复
            'total_amount' => $order->total_price, // 订单金额,单位元,支持小数点后两位
            'subject'      => '支付ShopAdmin的订单:'.$order->no, // 订单标题
            'body'      => '支付ShopAdmin的订单:'.$order->no, 
        ]);
        
    }

    // 前端回调
    public function alipayReturn(){
        $obj = new AliPay;
        try {
            $data = $obj->alipay->verify(request()->param());
        } catch (\Exception $e) {
            ApiException('数据不正确');
        }
        return showSuccess('付款成功');
    }
    
    // 服务端回调
    public function alipayNotify(){
        $obj = new AliPay;
        $alipay = $obj->alipay;
        trace('[支付宝alipayNotify] 拿到支付宝实例','info');
        // 校验输入参数
        $data = $alipay->verify(request()->param());
        trace('[支付宝alipayNotify] 获取到参数'.$data,'info');
        // 如果订单状态不是成功或者结束,则不走后续的逻辑
        // 所有交易状态:https://docs.open.alipay.com/59/103672
        if(!in_array($data->trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
            trace('[支付宝alipayNotify] 订单状态不是成功或者结束'.$data,'error');
            return $alipay->success()->send();
        }
        // $data->out_trade_no 拿到订单流水号,并在数据库中查询
        $order = \app\model\Order::where('no', $data->out_trade_no)->find();

        if (!$order) {
            trace('[支付宝alipayNotify] 订单不存在'.$data,'error');
            return 'fail';
        }

        // 如果这笔订单的状态已经是已支付
        if ($order->paid_time) {
            trace('[支付宝alipayNotify] 这笔订单的状态已经是已支付'.$data,'error');
            // 返回数据给支付宝
            return $alipay->success()->send();
        }
        // 更新订单状态
        $order->paid_time = time();
        $order->payment_method = 'alipay';
        $order->payment_no = $data->trade_no;
        $order->save();

        // 支付成功后的操作
        $this->afterPay($order);

        trace('[支付宝alipayNotify] 成功'.$data,'info');
        return $alipay->success()->send();
    }

	// 微信小程序支付
	public function payByWechatMp(){
		$code = request()-> param('code');
		if(!$code){
			ApiException('code不能为空');
		}
		// 验证当前订单是否为该用户
        $user = request()->UserModel;
        $order = request()->Model;
        if ($user->id !== $order->user_id) {
            ApiException('非法操作');
        }
        // 当前订单是否已经支付过
        if ($order->paid_time || $order->closed) {
            ApiException('订单状态不正确');
        }
        // 获取openid
        $openid = $this->getOpenId($code);
        // 调用支付
        $wechat = new wechatPay;
        return $wechat->miniapp([
        	'out_trade_no' => $order->no, // 订单编号,需保证在商户端不重复
		    'body' => '支付ShopAdmin的订单:'.$order->no, // 订单标题
		    'total_fee' => strval($order->total_price*100), // 订单金额,单位分,支持小数点后两位
		    'openid' => $openid,
        ]);
	}
	
	// 获取openid
	public function getOpenId($code){
        $url = "https://api.weixin.qq.com/sns/jscode2session";
        // 参数
        $params['appid']= config('cms.wx.appid');
        $params['secret']=  config('cms.wx.secret');
        $params['js_code']= $code;
        $params['grant_type']= 'authorization_code';
        // 微信API返回的session_key 和 openid
        $arr = httpWurl($url, $params, 'POST');
        $arr = json_decode($arr,true);
        // 不成功
        if(isset($arr['errcode']) && !empty($arr['errcode'])){
            ApiException($arr['errmsg']);
        }
        // 拿到数据
        return $arr['openid'];
    }

    // 微信支付
    public function payByWechat(){
        // 验证当前订单是否为该用户
        $user = request()->UserModel;
        $order = request()->Model;
        if ($user->id !== $order->user_id) {
            ApiException('非法操作');
        }
        // 当前订单是否已经支付过
        if ($order->paid_time || $order->closed) {
            ApiException('订单状态不正确');
        }
        // 调用支付
        $wechat = new wechatPay;
        return $wechat->app([
        	'out_trade_no' => $order->no, // 订单编号,需保证在商户端不重复
		    'body' => '支付ShopAdmin的订单:'.$order->no, // 订单标题
		    'total_fee' => strval($order->total_price*100), // 订单金额,单位分,支持小数点后两位
        ]); 
    }

    
    /**
     * 微信支付回调
     * @param Request $request
     */
    public function wechatNotify(Request $request){
    	trace('[微信支付回调] 回调开始','info');
        // 此代码 是因为 php7之后的版本中已经弃用 HTTP_RAW_POST_DATA,所以...
        $xml=isset($GLOBALS['HTTP_RAW_POST_DATA'])
            ? $GLOBALS['HTTP_RAW_POST_DATA'] : file_get_contents("php://input");
            
        //将服务器返回的XML数据转化为数组
        $data = self::xml2array($xml);
        if (($data['return_code'] == 'SUCCESS') && ($data['result_code'] == 'SUCCESS')) {
        	trace('[微信支付回调] 支付成功开始处理回调','info');
        	
            // //获取服务器返回的数据
            // $out_trade_no = $data['out_trade_no'];            //订单单号
            // $data['timestamp'] = date("Y-m-d H:i:s");
            // $json_str_notify = json_encode($data);
			$out_trade_no = $data['out_trade_no'];
			$order = \app\model\Order::where('no', $out_trade_no)->find();
		    if ($order) {
		    	// 如果这笔订单的状态已经是已支付
		        if (!$order->paid_time) {
		        	// 更新订单状态
			        $order->paid_time = time();
			        $order->payment_method = 'wechat';
			        $order->payment_no = $out_trade_no;
			        $order->save();
			        trace('[微信支付回调] 成功','info');
		        }
		    } else {
		    	trace('[微信支付回调] 订单不存在'.$out_trade_no,'error');
		    }
        } else {
        	trace('[微信支付回调] 支付失败','info');
        }
    }
    /**
     * 将xml转为array
     * @param  string $xml xml字符串
     * @return array    转换得到的数组
     */
    protected function xml2array($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $result = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $result;
    }


    // 支付成功后操作
    public function afterPay($order){
        // 商品增加销量
        $this->addSaleCount($order);
    }

    // 增加商品销量
    public function addSaleCount($order){
        // 获取当前订单对应商品
        $orderItems = $order->orderItems;
        $orderItems->each(function($item){
            // 需要增加的销量
            $count = $item->num;
            // 获取每个订单的商品
            $goods = $item->skus_type === 1 ? $item->goodsSkus->goods : $item->goods;
            // 更新商品销量
            $goods->sale_count += $count;
            $goods->save();
        });
    }
}

验证器 app/validate/admin/Order.php

php
// 验证规则
protected $rule = [
    // ...
    'id'=>'require|integer|>:0|isExist:Order',
	'agree'=>'require|in:0,1',
    'disagree_reason|拒绝退款理由'=>'requireIf:agree,0',
    // ...
];

protected $scene = [
   // ...
    'handleRefund'=>['id','agree','disagree_reason'],
];

路由 router/admin.php

php
Route::post('order/:id/handle_refund','admin.Order/handleRefund');