记录:fastadmin 微信支付(V2)订单实现退款处理,支付金额原路返回

一、在微信商户平台下载证书文件,放在addons\epay\certs 文件夹下,并在epay插件中配置证书位置信息

在这里插入图片描述

二、在 addons\epay\library\Service 中增加申请退款代码

    /**
     * 提交退款订单
     * @param array|float $amount    订单金额
     * @param array|float $refund_money    退款金额
     * @param string      $orderid   订单号
     * @param string      $refund_sn   退款订单号
     * @param string      $type      支付类型,可选alipay或wechat
     * @param string      $remark     退款原因
     * @param string      $notifyurl 通知回调URL
     * @param string      $returnurl 跳转返回URL
     * @param string      $method    支付方式
     * @return Response|RedirectResponse|Collection
     * @throws Exception
     */
    public static function submitRefund($amount=null,$refund_money,$orderid,$refund_sn,$type,$remark = null,$notifyurl = null,$returnurl = null,$method = 'app'){
        if (!is_array($amount)) {
            $params = [
                'amount'    => $amount,
                'refund_money'    => $refund_money,
                'orderid'    => $orderid,
                'refund_sn'    => $refund_sn,
                'type'      => $type,
                'remark'      => $remark,
                'notifyurl' => $notifyurl,
                'returnurl' => $returnurl,
                'method'    => $method,
            ];
        } else {
            $params = $amount;
        }
        $amount = isset($params['amount']) ? $params['amount'] : 0;
        $refund_money = isset($params['refund_money']) ? $params['refund_money'] : 0;
        $orderid = isset($params['orderid']) ? $params['orderid'] : '';
        $refund_sn = isset($params['refund_sn']) ? $params['refund_sn'] : '';
        $type = isset($params['type']) && in_array($params['type'], ['alipay', 'wechat']) ? $params['type'] : 'wechat';
        $remark = isset($params['remark']) ? $params['remark'] : '';
        $request = request();
        $notifyurl = isset($params['notifyurl']) ? $params['notifyurl'] : $request->root(true) . '/addons/epay/index/' . $type . 'notify';
        $returnurl = isset($params['returnurl']) ? $params['returnurl'] : '';
        $config = Service::getConfig($type);
        $config['notify_url'] = $notifyurl;
        $config['return_url'] = $returnurl;
        $result = null;
        //退款参数
        $order_data = [
            'out_trade_no' => $orderid//原订单号
        ];
        if ($type == 'wechat') {
            //创建支付对象
            $pay = Pay::wechat($config);
            $total_fee = $amount * 100;
            $refund_fee = $refund_money * 100;
            $order_data = array_merge($order_data, [
                'out_refund_no' => $refund_sn,//退款订单号
                'total_fee' => $total_fee,//支付金额
                'refund_fee' => $refund_fee,//退款金额
                'refund_desc' => $remark,//退款原因
                'type' => $method  //支付方式
            ]);
        } else {
            $pay = Pay::alipay($config);
            $order_data = array_merge($order_data, [
                'out_request_no' => $refund_sn,//退款订单号
                'refund_amount' => $refund_money,
            ]);
        }

        $result = $pay->refund($order_data);

        //使用重写的Response类、RedirectResponse、Collection类
        if ($result instanceof \Symfony\Component\HttpFoundation\RedirectResponse) {
            $result = RedirectResponse::create($result->getTargetUrl());
        } elseif ($result instanceof \Symfony\Component\HttpFoundation\Response) {
            $result = Response::create($result->getContent());
        } elseif ($result instanceof \Yansongda\Supports\Collection) {
            $result = Collection::make($result->all());
        }
        return $result;
    }
    

三、调用退款接口

	Db::startTrans();
	...
	
    // 调用退款接口
    // 异步接收微信支付退款结果通知的回调地址,通知URL必须为外网可访问的url,不允许带参数,公网域名必须为https
    $notifyurl = cdnurl('/api/paynotify/refundwxsign'.$refundRow['sign'], true);
    try {
        $response = \addons\epay\library\Service::submitRefund($orderRow['total_fee'], $refund_money, $refundRow['trade_no'], $refundRow['refund_order_no'], $pay_type='wechat', $refund_reason='用户退款', $notifyurl, $returnurl='', $method='miniapp');
        // SUCCESS退款申请接收成功    FAIL 提交业务失败
        if($response['return_code'] == 'SUCCESS' && $response['result_code'] == 'SUCCESS'){
            $updataData = [
                'refund_id' => $response['refund_id'],
                'tuikuan_status' => '20',	//真实退款状态:0=无,10=未退款,20=退款中,30=退款成功,40=退款关闭,50=退款异常
                'tuikuan_time' => time(),
            ];
        }else{
            $errorMsg = $response['err_code_des'] ?? $response['return_msg'];
            Db::rollback();
            throw new Exception('退款申请失败:'.$errorMsg);
        }
    } catch (\Throwable $th) {
        Db::rollback();
        throw new Exception('退款申请失败,'.$th);
    }
    
    ...

    // 更新退款状态
    $result = $refundRow->allowField(true)->save($updataData);
    if($result === false){
        Db::rollback();
        throw new Exception("抱歉,更新取货状态失败");
    }

    Db::commit();

回调处理


    protected $key = '';
    protected $model;
    
    public function refundwxsign10()
    {
        return $this->refundwx('10');
    }

    // 微信退款异步回调
    protected function refundwx($sign)
    {
		
        //写记事本
		$year = date('Y');
		$month = date('m');
		$day = date('d');
		$route = dirname(dirname(dirname(__FILE__)));
		$path = "$route/notify/$year/$month";
		// 获取xml
		$xml = file_get_contents('php://input', 'r');
        
		if (!file_exists($path)) {
			mkdir($path, 0777, true);
			$textlog = '01:' . date('Y-m-d H:i:s') . ':微信售后退款' . $xml;
			file_put_contents("$path/$day.txt", "$textlog\r\n", FILE_APPEND);
		} else {
			$textlog = '01:' . date('Y-m-d H:i:s') . ':微信售后退款' . $xml;
			file_put_contents("$path/$day.txt", "$textlog\r\n", FILE_APPEND);
		}

		// 转成php数组
        $dataArr = $this->toArray($xml);

        $textlog = '02:' . date('Y-m-d H:i:s') . ':微信售后退款' . json_encode($dataArr);
        file_put_contents("$path/$day.txt", "$textlog\r\n", FILE_APPEND);		

        // 1. 验证通信状态
        if ($dataArr['return_code'] != 'SUCCESS') {
            $textlog = '03:' . date('Y-m-d H:i:s') . ':微信售后退款-通信失败: ' . $dataArr['return_msg'];
            file_put_contents("$path/$day.txt", "$textlog\r\n", FILE_APPEND);
            return $this->replyXml('FAIL', '通信失败');
        }
        
        // 2.从数组中获取微信的加密数据
        $req_info = $dataArr['req_info'];  
        $getDecryptData = $this->decrypt($req_info);  //对加密数据进行解密
        $data = $this->toArray($getDecryptData); //将解密的数据由xml数据转化为数据格式
        
        $textlog = '04:' . date('Y-m-d H:i:s') . ':微信售后退款' . json_encode($data);
        file_put_contents("$path/$day.txt", "$textlog\r\n", FILE_APPEND);

        // 3.更新退款状态
        $refundStatusMap = [
            'SUCCESS' => 30, // 退款成功
            'REFUNDCLOSE' => 40, // 退款关闭
            'CHANGE' => 50 // 退款异常
        ];        	
        
        
        $tuikuan_status = $refundStatusMap[$data['refund_status']];
        $order_no = $data['out_trade_no'];  // 商户订单号
        $refund_no = $data['out_refund_no'];  // 商户退款单号
        $refund_id = $data['refund_id'];    // 微信退款单号
        $transaction_id = $data['transaction_id'];  //微信支付订单号		
        $refund_fee = $data['refund_fee'] / 100;
        $success_time = strtotime($data['success_time']);

        if($sign == '10'){
            $this->model = new Litestoreorderrefund();
        }elseif($sign == '20'){
            $this->model = new Litestorewmorderrefund();
        }elseif($sign == '30'){
            $this->model = new Litestorejzorderrefund();
        }

        $order = $this->model->where(['refund_order_no'=>$refund_no])->find();
        if (!$order) {
            return $this->replyXml('FAIL', '未查询到订单');
        }
        if($order['tuikuan_status'] == '30'){
            return $this->replyXml('SUCCESS', 'OK');
        }

        // 保存退款状态
        $order->save([
            'refund_fee'=>$refund_fee,
            'tuikuan_status'=>$tuikuan_status,
            'tuikuan_time' => $success_time
        ]);

        return $this->replyXml('SUCCESS', 'OK');

    }

    /**
     * 将xml转为array
     * @param  string $xml xml字符串
     * @return array       转换得到的数组
     */
    private function toArray($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $result = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $result;
    }
    
    /**
     * 对微信返回的加密回调数据进行解密并将其由xml格式转换为数组格式
     * @param $req_info :微信会掉返回的加密数据
     * @return false|string
     */
    public function decrypt($req_info){
        $APIv2 = $this->key;
        $disposeAPI = strtolower(MD5($APIv2));
        $disposeReq = base64_decode($req_info);
        $decrypt = openssl_decrypt($disposeReq,'AES-256-ECB',$disposeAPI,OPENSSL_RAW_DATA);
        file_put_contents('./returnData.xml',$decrypt."\n\r",FILE_APPEND);
        return $decrypt;
    }
    

    /**
     * 返回XML格式响应
     */
    private function replyXml($code, $msg) {
        $xml = "<xml>
            <return_code><![CDATA[{$code}]]></return_code>
            <return_msg><![CDATA[{$msg}]]></return_msg>
        </xml>";
        exit($xml);
    }
Logo

电影级数字人,免显卡端渲染SDK,十行代码即可调用,工业级demo免费开源下载!

更多推荐