微信扫码支付模式二(springboot)

最近给学校做了一个项目(springboot)里面用到了微信支付,我完成了pc端的支付,使用的是模式二,在这里记录一下,方便以后在工作中回顾

首先,加入相关的pml依赖

说明:-------------------------------------------------------------------------------------------------------------------------------------------
引入lombok是简化javabean中get,set方法的书写,使得属性和这些方法完成解耦
google zxing是第三方生成二维码的工具。它和hutool(第三方工具类)一起使用,生成二维码
还要加入微信sdk(不解释这个)和httpclient(用于和微信支付系统通信)
注意看以上说明------------------------------------------------------------------------------------------------------------------------------


        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
           <!--google zxing 生成二维码-->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.3</version>
        </dependency>
         <!--hutool工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.5.16</version>
        </dependency>
        <!-- 微信支付sdk -->
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>

        <!--http客户端-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.2</version>
        </dependency>

配置文件

说明:我将微信相关的配置抽离在一个properties文件中,方便管理,使得配置信息不与代码耦合

在这里插入图片描述

weixinpay.properties

说明:注意配置的前缀(weixinpay)下面会用到

#公众账号ID
weixinpay.appid=*******************
#商户号
weixinpay.mch_id=********************
#用于生成sign       key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
weixinpay.sign_key=******************
#微信统一下单url
weixinpay.Unified_Order_URL=https://api.mch.weixin.qq.com/pay/unifiedorder
#微信回调通知地址(我们服务器的ip地址和端口号)
weixinpay.notify_url=*************************

WeiXinPayConfig

说明:有了上面的配置信息当然需要读取配置信息为我们所用,使用以下配置类,springboot就可以为我们注入到容器里,在后面的代码中会用到
注意:这里@Data注解就是lombok的注解,使用它就不用写get,set方法了,很方便
这里的@PropertySource就是读取配置文件
这里的prefix 对应上面配置文件中信息

@PropertySource(value = {"classpath:weixinpay.properties"})
@ConfigurationProperties(prefix = "weixinpay")
@Component
@Data
public class WeiXinPayConfig {

    private String appid;

    private String mch_id;

    private String sign_key;

    private String Unified_Order_URL;

    private String notify_url;


}

基本环境搭好了,现在开始开发

首先在页面填写好金额,然后点击立即捐赠按钮

在这里插入图片描述

到了后台我的controller是这样的

说明:我这个项目前台会传要捐赠的项目id,要捐赠的金额,以及支付方式(微信?支付宝?银行卡?)
我们拿到数据后要先在数据库中生成订单,也就是说用户填完金额,只要一点“立即捐赠”按钮,我后端就会生成一个捐赠订单,然后将一些基本数据放在session中,方便后面代码的回显。
这里的代码只是,供大家参考,没要照搬照抄!!!,只要将这个说明中的意思用你自己的代码完成就行了!!!

 /**
     * @Author 张满
     * @Description 去支付页面
     * @Date 2019/8/6  12:41
     * @Param [money, projectId, payType, session]
     * @return java.lang.String
     **/
    @RequestMapping("/toWeiXinPay")
    public String toWeiXinPay(@RequestParam(value = "money",required = true) Double money,
                              @RequestParam(value = "projectId",required = true)Integer projectId,
                              @RequestParam(value = "payType",required = true)Integer payType,
                              HttpSession session
                             ){
        User user = (User) session.getAttribute("user");
        MoneyDonation moneyDonation=new MoneyDonation();
        if(user!=null){
            //如果用户已经登录(实名捐赠)
            moneyDonation.setUserid(user.getId());
        }
        moneyDonation.setProjectid(projectId);
        moneyDonation.setSum(new BigDecimal(money).setScale(2,BigDecimal.ROUND_HALF_UP));
        if(payType  == PayTypeConstants.WEI_XIN){
            moneyDonation.setType(PayTypeConstants.WEI_XIN);
            session.setAttribute("payType","微信支付");
        }
        moneyDonation.setCreatetime(new Date());
        //设置订单状态
        moneyDonation.setOrderStatus(OrderStatusConstants.PAYING);
        //生成订单id-----类似 b17f24ff026d40949c85a24f4f375d42
        String orderId = IdUtil.simpleUUID();
        moneyDonation.setOrderId(orderId);

        //生成订单
        int count = moneyDonationService.generateMoneyDonation(moneyDonation);

        if(count>0){
            //将订单号和金额传递到微信支付页面
            session.setAttribute("orderId",orderId);
            session.setAttribute("money",money);
            //商品描述   规则类似: 腾讯充值中心-QQ会员充值
            Project project = projectService.queryProjectById(projectId);
            session.setAttribute("body","湖科捐赠-"+project.getName());

            return  "redirect:returnProjectPay";
        }else {
            //返回失败页面
            return  "redirect:returnPayFail";
        }
    }

然后重定向到下面的controller,使用这个controller返回捐赠页面

说明:这里session中取的属性就是上面controller中session放的数据,方便回显到页面

  /**
     * @Author 张满
     * @Description 返回支付页面
     * @Date 2019/8/6  13:30
     * @Param []
     * @return java.lang.String
     **/
    @RequestMapping("/returnProjectPay")
    public String returnProjectPay(  HttpSession session,
                                     Model model
                                   ){
        //返回订单号,捐赠金额,支付方式,显示在页面上
        model.addAttribute("payType",session.getAttribute("payType"));
        model.addAttribute("orderId",session.getAttribute("orderId"));
        model.addAttribute("money",session.getAttribute("money"));
        model.addAttribute("body",session.getAttribute("body"));

        return prefix+"/pay/projectPay";
    }

返回的捐赠页面

说明:这里回显的东西就是上面model放的东西
先不要急着问二维码哪来的!!!!!!!!!!!!!!!!!!!!!!!!
先不要急着问二维码哪来的!!!!!!!!!!!!!!!!!!!!!!!!
先不要急着问二维码哪来的!!!!!!!!!!!!!!!!!!!!!!!!马上说!

在这里插入图片描述

二维码怎么来的???

说明:在返回的页面中二维码是个图片,但是src不填任何东西,当页面一加载就用js去后台动态的获取我们需要的二维码
注意:这里的Math.random()是为了防止浏览器有缓存,支付一次后,以后就把这个二维码图片缓存下来,以后不去请求二维码图片了。!

在这里插入图片描述

<script>
    //请求二维码图片
    window.onload=function(){
        var QRcode = document.getElementById("QRcode");
        QRcode.src="/WeiXinPay/generateQRcode?_="+Math.random();
    }
</script>

接下来看看生成二维码的controller

说明:这里代码较多,不要害怕,我慢慢说。
在这个controller我们用@Autowired注入了之前配置的WeiXinPayConfig 对象,所有我们就可以在接下来的代码中,用weiXinPayConfig.get("…")拿到相关的配置信息了。
特别注意:这里的 notify_url是回调url ,是用户支付成功后,微信通知我们用户已经支付了时,它就是掉这里我们设置的接口,对它的相关解释,这里不再多说,请参考微信扫码支付文档之统一下单API。
特别注意:微信要求的订单金额单位是分,在前台我们传的是元,所有这里要做转换成分
里面的HttpClientUtils 类,我接下来会给出,使用它来与微信支付系统通信。
里面你不认识的工具类,如:WXPayUtil,这都是微信sdk中提供的。
特别注意:当我们请求微信成功后,微信会给我们一个code_url,我们就是用这个字符串生成二维码的, 这里的QrCodeUtil.generate(code_url,184,184,“jpg”,response.getOutputStream());就是生成二维码,这个工具类是hutool中的,我们已经在开始加入了zxing和hutool,然后调用这个方法,就自动生成了二维码,然后用流的方式,写回页面,页面上就出现了二维码。


    @Autowired
    private WeiXinPayConfig weiXinPayConfig;

    /**
     * @Author 张满
     * @Description 生成二维码图片
     * @Date 2019/8/6  21:51
     * @Param []
     * @return void
     **/
    @RequestMapping("/generateQRcode")
    public void generateQRcode(HttpSession session, HttpServletResponse response)  {

        try {
            Map map = new HashMap();
            map.put("appid",weiXinPayConfig.getAppid());        //微信支付分配的公众账号ID(企业号corpid即为此appId)
            map.put("mch_id",weiXinPayConfig.getMch_id());          //微信支付分配的商户号
            String nonceStr = WXPayUtil.generateNonceStr();
            map.put("nonce_str",nonceStr);      //随机字符串,长度要求在32位以内。
            map.put("body",session.getAttribute("body"));  //商品简单描述
            map.put("out_trade_no",session.getAttribute("orderId"));  //商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一

            double money= (double)session.getAttribute("money");
            money=money*100;      //转成单位为分

            map.put("total_fee",String.valueOf((int)money));  //订单总金额,单位为分

            InetAddress localHost = InetAddress.getLocalHost();
            String address = localHost.getHostAddress();
            map.put("spbill_create_ip",address);         //终端IP

            map.put("notify_url",weiXinPayConfig.getNotify_url()+"/WeiXinPay/acceptWeiXinNotice");      //异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
            map.put("trade_type","NATIVE");             //交易类型
            map.put("product_id",session.getAttribute("orderId"));  //trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义。

            String signature = WXPayUtil.generateSignature(map, weiXinPayConfig.getSign_key());    //通过签名算法计算得出的签名值  key设置路径见文档,默认MD5加密
            map.put("sign",signature);
            String requestDataXml = WXPayUtil.mapToXml(map);

            //调用微信统一下单api  todo  查询时应该坚持订单状态,只有支付成功的才可以被查询出来
            String returnXml = HttpClientUtils.doPostByXml(weiXinPayConfig.getUnified_Order_URL(), requestDataXml);
            //System.out.println(returnXml);
            //将微信支付系统返回的xml转换成map集合
            Map<String, String> returnMap = WXPayUtil.xmlToMap(returnXml);
            //通讯正常
            if(returnMap.get("return_code").equals(WeiXinPayConstants.SUCCESS)){
                //返回消息正常
                if(returnMap.get("result_code").equals(WeiXinPayConstants.SUCCESS)){
                    //获得code_url生成二维码
                    String code_url = returnMap.get("code_url");
                    QrCodeUtil.generate(code_url,184,184,"jpg",response.getOutputStream());
                }else {
                    //微信支付系统生成订单失败
                    //输出错误代码
                    System.out.println(map.get("err_code"));
                    //输出错误描述
                    System.out.println(map.get("err_code_des"));
                }
            }else{
                //和微信支付系统同通讯失败
                //输出错误消息
                System.out.println(map.get("return_msg"));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }


    }

上面的HttpClientUtils工具类

说明:这里面的方法有好几个,但实际上我们只用了doPostByXml的方法。使用了post方式给微信发送了xml格式的请求参数

package com.iot.donation.util;

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class HttpClientUtils {

	public static String doGet(String url, Map<String, String> param) {

		// 创建Httpclient对象
		CloseableHttpClient httpclient = HttpClients.createDefault();

		String resultString = "";
		CloseableHttpResponse response = null;
		try {
			// 创建uri
			URIBuilder builder = new URIBuilder(url);
			if (param != null) {
				for (String key : param.keySet()) {
					builder.addParameter(key, param.get(key));
				}
			}
			URI uri = builder.build();

			// 创建http GET请求
			HttpGet httpGet = new HttpGet(uri);

			// 执行请求
			response = httpclient.execute(httpGet);
			// 判断返回状态是否为200
			if (response.getStatusLine().getStatusCode() == 200) {
				resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (response != null) {
					response.close();
				}
				httpclient.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return resultString;
	}

	public static String doGet(String url) {
		return doGet(url, null);
	}

	public static String doPost(String url, Map<String, String> param) {
		// 创建Httpclient对象
		CloseableHttpClient httpClient = HttpClients.createDefault();
		CloseableHttpResponse response = null;
		String resultString = "";
		try {
			// 创建Http Post请求
			HttpPost httpPost = new HttpPost(url);
			// 创建参数列表
			if (param != null) {
				List<NameValuePair> paramList = new ArrayList<>();
				for (String key : param.keySet()) {
					paramList.add(new BasicNameValuePair(key, param.get(key)));
				}
				// 模拟表单
				UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
				httpPost.setEntity(entity);
			}
			// 执行http请求
			response = httpClient.execute(httpPost);
			resultString = EntityUtils.toString(response.getEntity(), "utf-8");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				response.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return resultString;
	}

	public static String doPost(String url) {
		return doPost(url, null);
	}
	
	public static String doPostJson(String url, String json) {
		// 创建Httpclient对象
		CloseableHttpClient httpClient = HttpClients.createDefault();
		CloseableHttpResponse response = null;
		String resultString = "";
		try {
			// 创建Http Post请求
			HttpPost httpPost = new HttpPost(url);
			// 创建请求内容
			StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
			httpPost.setEntity(entity);
			// 执行http请求
			response = httpClient.execute(httpPost);
			resultString = EntityUtils.toString(response.getEntity(), "utf-8");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				response.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return resultString;
	}


	/**
	 * @Author 张满
	 * @Description post请求发送xml
	 * @Date 2019/8/7  12:19
	 * @Param [url, requestDataXml]
	 * @return java.lang.String
	 **/
	public static String doPostByXml(String url,String requestDataXml){
		CloseableHttpClient httpClient=null;
		CloseableHttpResponse httpResponse=null;

		//创建httpClient连接对象
		httpClient = HttpClients.createDefault();
		//创建post请求连接对象
		HttpPost httpPost = new HttpPost(url);
		//创建连接请求参数对象,并设置连接参数
		RequestConfig requestConfig = RequestConfig.custom()
				.setConnectTimeout(15000)		//连接服务器主机超时时间
				.setConnectionRequestTimeout(60000)  //连接请求超时时间
				.setSocketTimeout(6000)				//设置读取响应数据超时时间
				.build();

		//为httpPost请求设置参数
		httpPost.setConfig(requestConfig);
		//将上传参数存放到entity属性中
		httpPost.setEntity(new StringEntity(requestDataXml,"UTF-8"));
		//添加头信息
		httpPost.setHeader("Content-Type","text/xml");

		String result="";
		try {
			//发送请求
			httpResponse = httpClient.execute(httpPost);
			//获取返回内容
			HttpEntity httpEntity = httpResponse.getEntity();
			result = EntityUtils.toString(httpEntity, "UTF-8");
		} catch (IOException e) {
			e.printStackTrace();
		}

		return result;
	}






}

通过以上步骤,捐赠页面就生成了一个 可以供用户支付的二维码。你就可以支付了。当用户支付以后,微信那边就会通知你(告诉你用户a捐钱成功了),这时候他就需要调用上面提到的notify_url,就是你和微信通讯时,给他的接口,这个接口要在公网上可以直接访问,具体的要求请看微信文档。

回调接口怎么写?

注意:大家可以参考我这个接口的写法,看懂之后,就可以灵活使用了。
思路:将微信发来的xml格式,利用WXPayUtil(微信sdk提供)转化成map,获取里面的数据,判断微信返回的状态码,如果成功就做一下签名校验(也是sdk提供方法),当校验完成后,开始写我们的业务:主要就是判断这个订单是不是我已经给它的状态改变为已支付(微信会发很多次通知给你),如果改变订单状态,就返回成功给微信,如果没改变就改变了以后发成功。按照自己的业务需求写自己的业务就行了!

/**
     * @Author 张满
     * @Description 接收微信通知(订单支付情况)
     * @Date 2019/8/8  15:44
     * @Param []
     * @return void
     **/
    @RequestMapping("/acceptWeiXinNotice")
    public void acceptWeiXinNotice(HttpServletRequest request,HttpServletResponse response,HttpSession session){

        System.out.println("---------------------回调成功---------------------------");

        PrintWriter writer = null;
        InputStream inStream = null;
        ByteArrayOutputStream outSteam = null;
        try {
            writer = response.getWriter();
            inStream = request.getInputStream();
            outSteam = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = inStream.read(buffer)) != -1) {
                outSteam.write(buffer, 0, len);
            }
            String result = new String(outSteam.toByteArray(), "utf-8");
            System.out.println("-------------------result Xml------------------------------");
            System.out.println(result);
            Map<String, String> map = null;
            // 解析微信通知返回的信息
            map = WXPayUtil.xmlToMap(result);

            if (map.get("return_code").equals(WeiXinPayConstants.SUCCESS)) {
                if (map.get("result_code").equals(WeiXinPayConstants.SUCCESS)) {
                    //进行签名校验
                    if(WXPayUtil.isSignatureValid(map,weiXinPayConfig.getSign_key())){
                            //判断该订单是否已经处理过,(对订单状态的改变要加数据锁,进行并发控制)
                            MoneyDonation moneyDonation = moneyDonationService.findMoneyDonationByOrderId(map.get("out_trade_no"));
                            //如果已经改变过了订单状态,返回成功
                            if(moneyDonation!=null && moneyDonation.getOrderStatus().equals(OrderStatusConstants.PAYED)){
                                Map returnMap = new HashMap();
                                returnMap.put("return_code",WeiXinPayConstants.SUCCESS);
                                returnMap.put("return_msg",WeiXinPayConstants.OK);
                                String returnXml = WXPayUtil.mapToXml(returnMap);
                                writer.write(returnXml);
                                writer.flush();
                            }
                            //改变订单状态为已支付,(对订单状态的改变要加数据锁,进行并发控制)
                            if(moneyDonation!=null && moneyDonation.getOrderStatus().equals(OrderStatusConstants.PAYING)){
                                //改变订单状态为已支付
                                int count = moneyDonationService.changeOrderStatus(moneyDonation.getId(),OrderStatusConstants.PAYED);
                                //  给项目的total加上money
                                Project project = projectService.queryProjectById(moneyDonation.getProjectid());
                                BigDecimal total = project.getTotal();
                                System.out.println("之前的项目收到的金额:"+total);
                                //微信返回的金额单位是分,而数据库中存的是元,所以需要将分转化成元
                                total=total.add( new BigDecimal(Integer.parseInt(map.get("total_fee"))).divide(new BigDecimal(100)) );
                                System.out.println("之前的项目收到的金额:"+total);
                                project.setTotal(total);
                                int count2 = projectService.updateProject(project);
                                //响应成功
                                if(count==1 && count2==1){
                                    Map returnMap = new HashMap();
                                    returnMap.put("return_code",WeiXinPayConstants.SUCCESS);
                                    returnMap.put("return_msg",WeiXinPayConstants.OK);
                                    String returnXml = WXPayUtil.mapToXml(returnMap);
                                    writer.write(returnXml);
                                    writer.flush();
                                }
                            }
                    }

                }

            }else{
                System.out.println(map.get("err_code"));
                System.out.println(map.get("err_code_des"));
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                outSteam.close();
                inStream.close();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

效果:当用户支付后,你的账户就收到了钱,并且数据库的订单状态就更改为已支付

把上面思路仔细看懂,用自己的代码完成逻辑,切忌照搬照抄,那样你就很难完成这么简单地一件事
如果你觉得上面的思路对你有所帮助,你可以评论鼓励一下,最好能关注我一下,毕竟第一场这么用心的写博客,我后续也会好好写博客!谢谢!Thanks♪(・ω・)ノ

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值