首页 > 编程语言 >JAVA微信扫码支付模式二功能实现完整例子

JAVA微信扫码支付模式二功能实现完整例子

时间:2023-06-08 19:07:45浏览次数:55  
标签:扫码 JAVA String 微信 new sb import packageParams


概述


本例子实现微信扫码支付模式二的支付功能,应用场景是,web网站微信扫码支付。实现从点击付费按钮、到弹出二维码、到用户用手机微信扫码支付、到手机上用户付费成功、web网页再自动调整到支付成功后的页面,这一个过程。

详细



一、准备工作

先开通微信公众号,再开通微信公众号里面的微信支付功能,这些是前提条件,多说一句,申请开通微信公众号需要等待审核,然后在开通微信支付功能,还得等待审核,前前后后耗时得好几天。


JAVA微信扫码支付模式二功能实现完整例子_JAVA微信扫码支付

关于准备工作,再看看微信官方关于“微信支付”的介绍,官方地址 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1。这个是文档的准备,大概可以理解到微信支付有哪些模式,然后大概是怎样一个东东。

红色花掉的部分(PayConfigUtil类里面),需要根据自己的实际情况填写:


JAVA微信扫码支付模式二功能实现完整例子_API_02

其中APP_ID和APP_SECRET可以在公众平台找着,MCH_ID和API_KEY则在商户平台找到,特别是API_KEY要在商户平台设置好,对于“微信扫码支付模式二”(支付与回调)实际只会用到APP_ID、MCH_ID和API_KEY,其他的都不用。

二、程序实现

这里使用spring mvc做一个购买商品,微信扫码支付的演示。先项目代码截图,

JAVA微信扫码支付模式二功能实现完整例子_java_03


以下摘取重点环节的代码说明下:

1、首先是接入微信接口,获取微信支付二维码。

package com.demodashi;

import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.inject.Named;

import com.demodashi.pay.util.HttpUtil;
import com.demodashi.pay.util.PayToolUtil;
import com.demodashi.pay.util.PayConfigUtil;
import com.demodashi.pay.util.XMLUtil4jdom;

@Named("userService")
public class UserServiceImpl implements UserService {
	
	@Override
	public String weixinPay(String userId, String productId) throws Exception {
		
        String out_trade_no = "" + System.currentTimeMillis(); //订单号 (调整为自己的生产逻辑)
        
        // 账号信息 
        String appid = PayConfigUtil.APP_ID;  // appid  
        //String appsecret = PayConfigUtil.APP_SECRET; // appsecret  
        String mch_id = PayConfigUtil.MCH_ID; // 商业号  
        String key = PayConfigUtil.API_KEY; // key  
        
        String currTime = PayToolUtil.getCurrTime();  
        String strTime = currTime.substring(8, currTime.length());  
        String strRandom = PayToolUtil.buildRandom(4) + "";  
        String nonce_str = strTime + strRandom;  
        
        // 获取发起电脑 ip
        String spbill_create_ip = PayConfigUtil.CREATE_IP;
        // 回调接口   
        String notify_url = PayConfigUtil.NOTIFY_URL;
        String trade_type = "NATIVE";
          
        SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();  
        packageParams.put("appid", appid);  
        packageParams.put("mch_id", mch_id);  
        packageParams.put("nonce_str", nonce_str);  
        packageParams.put("body", "可乐");  //(调整为自己的名称)
        packageParams.put("out_trade_no", out_trade_no);  
        packageParams.put("total_fee", "10"); //价格的单位为分  
        packageParams.put("spbill_create_ip", spbill_create_ip);  
        packageParams.put("notify_url", notify_url);  
        packageParams.put("trade_type", trade_type);  
  
        String sign = PayToolUtil.createSign("UTF-8", packageParams,key);  
        packageParams.put("sign", sign);
          
        String requestXML = PayToolUtil.getRequestXml(packageParams);  
        System.out.println(requestXML);  
   
        String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML);  
  
        Map map = XMLUtil4jdom.doXMLParse(resXml);  
        String urlCode = (String) map.get("code_url");  
        
        return urlCode;  
	}

}

以上代码会按照微信支付的协议,生成类似这样格式的URL:weixin://wxpay/bizpayurl?pr=pIxXXXX

2、根据以上方法所产生的URL生成二维码,这里采用我采用的是google的core.jar包来生成二维码

@ResponseBody
@RequestMapping("/qrcode.do")
public void qrcode(HttpServletRequest request, HttpServletResponse response,
	ModelMap modelMap) {
	try {
       String productId = request.getParameter("productId");
       String userId = "user01";
       String text = userApplication.weixinPay(userId, productId); 
       //根据url来生成生成二维码
       int width = 300; 
       int height = 300; 
       //二维码的图片格式 
       String format = "gif"; 
       Hashtable hints = new Hashtable(); 
       //内容所使用编码 
       hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
       BitMatrix bitMatrix;
	    try {
		bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);
		QRUtil.writeToStream(bitMatrix, format, response.getOutputStream());
	    } catch (WriterException e) {
		e.printStackTrace();
	    }
		
	} catch (Exception e) {
	}
}

上面代码中涉及到几个工具类:PayConfigUtil、PayCommonUtil、HttpUtil和XMLUtil,其中PayConfigUtil放的就是上面提到一些配置及路径,PayCommonUtil涉及到了获取当前事件、产生随机字符串、获取参数签名和拼接xml几个方法,代码如下:

package com.demodashi.pay.util;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

public class PayToolUtil {

	/** 
     * 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。 
     * @return boolean 
     */  
    public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {  
        StringBuffer sb = new StringBuffer();  
        Set es = packageParams.entrySet();  
        Iterator it = es.iterator();  
        while(it.hasNext()) {  
            Map.Entry entry = (Map.Entry)it.next();  
            String k = (String)entry.getKey();  
            String v = (String)entry.getValue();  
            if(!"sign".equals(k) && null != v && !"".equals(v)) {  
                sb.append(k + "=" + v + "&");  
            }  
        }  
          
        sb.append("key=" + API_KEY);  
          
        //算出摘要  
        String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();  
        String tenpaySign = ((String)packageParams.get("sign")).toLowerCase();  
        
        //System.out.println(tenpaySign + "    " + mysign);  
        return tenpaySign.equals(mysign);  
    }  
  
    /** 
     * @author 
     * @date 2016-4-22 
     * @Description:sign签名 
     * @param characterEncoding 
     *            编码格式 
     * @param parameters 
     *            请求参数 
     * @return 
     */  
    public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {  
        StringBuffer sb = new StringBuffer();  
        Set es = packageParams.entrySet();  
        Iterator it = es.iterator();  
        while (it.hasNext()) {  
            Map.Entry entry = (Map.Entry) it.next();  
            String k = (String) entry.getKey();  
            String v = (String) entry.getValue();  
            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {  
                sb.append(k + "=" + v + "&");  
            }  
        }  
        sb.append("key=" + API_KEY);  
        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();  
        return sign;  
    }  
  
    /** 
     * @author 
     * @date 2016-4-22 
     * @Description:将请求参数转换为xml格式的string 
     * @param parameters 
     *            请求参数 
     * @return 
     */  
    public static String getRequestXml(SortedMap<Object, Object> parameters) {  
        StringBuffer sb = new StringBuffer();  
        sb.append("<xml>");  
        Set es = parameters.entrySet();  
        Iterator it = es.iterator();  
        while (it.hasNext()) {  
            Map.Entry entry = (Map.Entry) it.next();  
            String k = (String) entry.getKey();  
            String v = (String) entry.getValue();  
            if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {  
                sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");  
            } else {  
                sb.append("<" + k + ">" + v + "</" + k + ">");  
            }  
        }  
        sb.append("</xml>");  
        return sb.toString();  
    }  
  
    /** 
     * 取出一个指定长度大小的随机正整数. 
     *  
     * @param length 
     *            int 设定所取出随机数的长度。length小于11 
     * @return int 返回生成的随机数。 
     */  
    public static int buildRandom(int length) {  
        int num = 1;  
        double random = Math.random();  
        if (random < 0.1) {  
            random = random + 0.1;  
        }  
        for (int i = 0; i < length; i++) {  
            num = num * 10;  
        }  
        return (int) ((random * num));  
    }  
  
    /** 
     * 获取当前时间 yyyyMMddHHmmss 
     *  
     * @return String 
     */  
    public static String getCurrTime() {  
        Date now = new Date();  
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");  
        String s = outFormat.format(now);  
        return s;  
    }  
	
}

HttpUtil类如下:

package com.demodashi.pay.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;

/**
 * http工具类,负责发起post请求并获取的返回
 */
public class HttpUtil {

    private final static int CONNECT_TIMEOUT = 5000; // in milliseconds  
    private final static String DEFAULT_ENCODING = "UTF-8";  
      
    public static String postData(String urlStr, String data){  
        return postData(urlStr, data, null);
    }
      
    public static String postData(String urlStr, String data, String contentType){  
        BufferedReader reader = null;  
        try {  
            URL url = new URL(urlStr);  
            URLConnection conn = url.openConnection();  
            conn.setDoOutput(true);  
            conn.setConnectTimeout(CONNECT_TIMEOUT);  
            conn.setReadTimeout(CONNECT_TIMEOUT);  
            if(contentType != null)  
                conn.setRequestProperty("content-type", contentType);  
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);  
            if(data == null)  
                data = "";  
            writer.write(data);   
            writer.flush();  
            writer.close();    
  
            reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));  
            StringBuilder sb = new StringBuilder();  
            String line = null;  
            while ((line = reader.readLine()) != null) {  
                sb.append(line);  
                sb.append("\r\n");  
            }  
            return sb.toString();  
        } catch (IOException e) {  
            //logger.error("Error connecting to " + urlStr + ": " + e.getMessage());  
        } finally {  
            try {  
                if (reader != null)  
                    reader.close();  
            } catch (IOException e) {  
            }  
        }  
        return null;  
    }  

}

XMLUtil4jdom类如下:

package com.demodashi.pay.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;


public class XMLUtil4jdom {

    /** 
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 
     * @param strxml 
     * @return 
     * @throws JDOMException 
     * @throws IOException 
     */  
    public static Map doXMLParse(String strxml) throws JDOMException, IOException {  
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");  
  
        if(null == strxml || "".equals(strxml)) {
            return null;  
        }
          
        Map<String, String> m = new HashMap<String, String>(); 
        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));  
        SAXBuilder builder = new SAXBuilder();  
        Document doc = builder.build(in);  
        Element root = doc.getRootElement();  
        List list = root.getChildren();  
        Iterator it = list.iterator();  
        while(it.hasNext()) {  
            Element e = (Element) it.next();  
            String k = e.getName();  
            String v = "";  
            List children = e.getChildren();  
            if(children.isEmpty()) {  
                v = e.getTextNormalize();  
            } else {  
                v = XMLUtil4jdom.getChildrenText(children);  
            }  
              
            m.put(k, v);  
        }  
          
        //关闭流  
        in.close();  
          
        return m;  
    }  
      
    /** 
     * 获取子结点的xml 
     * @param children 
     * @return String 
     */  
    public static String getChildrenText(List children) {  
        StringBuffer sb = new StringBuffer();  
        if(!children.isEmpty()) {  
            Iterator it = children.iterator();  
            while(it.hasNext()) {  
                Element e = (Element) it.next();  
                String name = e.getName();  
                String value = e.getTextNormalize();  
                List list = e.getChildren();  
                sb.append("<" + name + ">");  
                if(!list.isEmpty()) {  
                    sb.append(XMLUtil4jdom.getChildrenText(list));  
                }  
                sb.append(value);  
                sb.append("</" + name + ">");  
            }  
        }  
          
        return sb.toString();  
    }  

}

2、支付回调

支付完成后,微信会把相关支付结果和用户信息发送到我们上面指定的那个回调地址,我们需要接收处理,并返回应答。对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)

关于支付回调接口,我们首先要对于支付结果通知的内容进行签名验证,然后根据支付结果进行相应的处理流程即可。

支付回调需要在微信公众号的微信支付里面设置回调地址:

JAVA微信扫码支付模式二功能实现完整例子_API_04

/**
 * 微信平台发起的回调方法,
 * 调用我们这个系统的这个方法接口,将扫描支付的处理结果告知我们系统
 * @throws JDOMException
 * @throws Exception
 */
public void weixinNotify(HttpServletRequest request, HttpServletResponse response) throws JDOMException, Exception{
       //读取参数  
       InputStream inputStream ;  
       StringBuffer sb = new StringBuffer();  
       inputStream = request.getInputStream();  
       String s ;  
       BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));  
       while ((s = in.readLine()) != null){  
           sb.append(s);
       }
       in.close();
       inputStream.close();
 
       //解析xml成map  
       Map<String, String> m = new HashMap<String, String>();  
       m = XMLUtil4jdom.doXMLParse(sb.toString());  
       
       //过滤空 设置 TreeMap  
       SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();        
       Iterator it = m.keySet().iterator();  
       while (it.hasNext()) {  
           String parameter = (String) it.next();
           String parameterValue = m.get(parameter);
           
           String v = "";  
           if(null != parameterValue) {
               v = parameterValue.trim();  
           }  
           packageParams.put(parameter, v);  
       }  
         
       // 账号信息  
       String key = PayConfigUtil.API_KEY; //key  
 
       //判断签名是否正确  
       if(PayToolUtil.isTenpaySign("UTF-8", packageParams,key)) {  
           //------------------------------  
           //处理业务开始  
           //------------------------------  
           String resXml = "";  
           if("SUCCESS".equals((String)packageParams.get("result_code"))){  
               // 这里是支付成功  
               //执行自己的业务逻辑  
               String mch_id = (String)packageParams.get("mch_id");  
               String openid = (String)packageParams.get("openid");  
               String is_subscribe = (String)packageParams.get("is_subscribe");  
               String out_trade_no = (String)packageParams.get("out_trade_no");  
               
               String total_fee = (String)packageParams.get("total_fee");  
               
               //执行自己的业务逻辑 
               //暂时使用最简单的业务逻辑来处理:只是将业务处理结果保存到session中
               //(根据自己的实际业务逻辑来调整,很多时候,我们会操作业务表,将返回成功的状态保留下来)
               request.getSession().setAttribute("_PAY_RESULT", "OK");
               
               System.out.println("支付成功");  
               //通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.  
               resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"  
                       + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";  
                 
           } else {
               resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"  
                       + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";  
           }
           //------------------------------  
           //处理业务完毕  
           //------------------------------  
           BufferedOutputStream out = new BufferedOutputStream(  
                   response.getOutputStream());  
           out.write(resXml.getBytes());  
           out.flush();  
           out.close();  
       } else{  
       	System.out.println("通知签名验证失败");  
       }
         
}

3、支付后网页自动跳转

web页面弹出二维码后,就开启轮询,询问系统后台支付有微信平台的成功支付返回了,如果有,则跳转到支付成功的页面。

<%@ page language="java" pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" charset="utf-8" src="/resource/js/jquery-2.min.js"></script>
<script type="text/javascript" charset="utf-8" src="/resource/js/layer/layer.js"></script>
<title>微信扫码支付例子</title>
</head>
<body>
<form id="pay_form" method="post" >
<h1>可乐特价:0.1元/罐 <input id="pay_submit" name="but" type="button" value="微信支付"/></h1>
</form>
</body>
<script>
$(function(){
	$("#pay_submit").click(function(){
	    buy('001');//传入可乐的ID号
	});
	
});

/**
 * 购买
 */
function buy(productId){
	//打开付费二维码 -- 微信二维码
	layer.open({
		area: ['300px', '300px'],
        type: 2,
        closeBtn: false,
        title: false,
        shift: 2,
        shadeClose: true,
        content:'../user/qrcode.do?productId=' + productId
    });
	
	//重复执行某个方法 
	var t1 = window.setInterval("getPayState('" + productId + "')",1500); 
}

function getPayState(productId){
	var url = '../user/hadPay.do?productId=' + productId;
	//轮询是否已经付费
	$.ajax({
    	type:'post',
   		url:url,
   		data:{productId:productId},
   		cache:false,
   		async:true,
   		success:function(json){
   			if(json.result == 0){
   				location.href = '/result.jsp';
   			}
   		},
   	    error:function(){
   	    	layer.msg("执行错误!", 8);
   	    }
   	});
}
</script> 
</html>

三、运行效果

项目导入eclipse后,发表到tomcat中运行,或者通过jetty运行,跑起来后,访问:

JAVA微信扫码支付模式二功能实现完整例子_API_05

点击微信支付:

这个时候在手机上用微信扫码:

JAVA微信扫码支付模式二功能实现完整例子_JAVA微信扫码支付_06

支付成功后:

JAVA微信扫码支付模式二功能实现完整例子_微信支付_07

然后web网页会跳转到购买成功的页面,这里需要注意,微信支付回调接口,最好部署在公网的服务器上,这样能被回调,我本地使用改hosts的方法来让支付回调,不成功。

JAVA微信扫码支付模式二功能实现完整例子_API_08

四、注意点

本例子为了演示,所以一些业务逻辑特别简单,例如:订单号的生产,这里只是简单的用当前时间long数字来表示:

String out_trade_no = "" + System.currentTimeMillis(); //订单号 (调整为自己的生产逻辑)

实际开发的时候需要考虑并且情况下的订单号的唯一性。

还有,回调接口,考虑很简单:

//执行自己的业务逻辑 
//暂时使用最简单的业务逻辑来处理:只是将业务处理结果保存到session中
//(根据自己的实际业务逻辑来调整,很多时候,我们会操作业务表,将返回成功的状态保留下来)
request.getSession().setAttribute("_PAY_RESULT", "OK");
                
System.out.println("支付成功");

实际开发,要把支付成功DB保存下来,以及回调信息log下来等等



标签:扫码,JAVA,String,微信,new,sb,import,packageParams
From: https://blog.51cto.com/u_7583030/6442228

相关文章

  • iOS微信支付集成
    概述iOS微信支付集成详细支付宝和微信都是业界的老大哥,相信大家都有所觉得文档、SDK都是各种坑吧(纯粹吐槽而已),这是继上篇支付宝支付集成后接着的微信支付集成。一、准备工作1、微信商户申请步骤申请步骤: http://kf.qq.com/faq/120911VrYVrA150906F3qqY3.html2、申......
  • Java基础——深入了解泛型机制
    ......
  • 01-Java基础语法
    day01_Java基础一、课程目标1.【了解】Java语言发展史             2.【理解】Java语言平台版本3.【理解】Java语言特点4.【理解】JRE与JDK5.【掌握】Java开发环境搭建6.【掌握】第一个Java程序7.【掌握】注释8.【理解】关键字/标识符......
  • 2023春招:Javaweb面试锦囊
    cookie和session的区别?(必会)存储位置不同cookie存放在客户端电脑,是一个磁盘文件。Ie浏览器是可以从文件夹中找到。session是存放在服务器内存中的一个对象。chrome浏览器进行安全处理,只能通过浏览器找到。Session是服务器端会话管理技术,并且session就是cookie实现的。......
  • 微信小程序上实现下载pdf功能
    onLookFile(){letthat=this;constfilename='下载附件'constfileExtName=".pdf";constrandfile=filename+fileExtName;//wx.env.USER_DATA_PATH是微信提供了一个用户文件目录给开发者,开发者对这个目录有完全自由的读写权限......
  • JAVA 线程池之Callable返回结果
    JAVA线程池之Callable返回结果原文:https://www.cnblogs.com/hapjin/p/7599189.html本文介绍如何向线程池提交任务,并获得任务的执行结果。然后模拟线程池中的线程在执行任务的过程中抛出异常时,该如何处理。一、执行具体任务的线程类要想获得线程的执行结果,需实现Callable接......
  • Java开发工程师学习日记(十)
    1.谈谈Java线程池使用的优势:(1)Java线程池是一定数量的线程集合,线程的频繁创建与销毁消耗了操作系统与内存的大量资源,使用线程池使得减少了线程创建与销毁的资源浪费。(2)使用线程池可以提高程序的响应速度,通过复用已存在的线程,无需等待新线程的创建便能立即执行。(3)进行线程并发数的......
  • 微信小程序开发(一)基础概念汇总
    大家好,我是千与千寻,最近开始涉及小程序开发了,学一学最后做出来一个项目,最开始进入微信小程序开发的时候,如果学过Vue框架接触微信小程序开发的速度会非常快。我准备顺便开设一个专题,进行讲解微信小程序的开发理论复习。大家一起学习进步~一、微信小程序的开发步骤微信小程序是一种轻......
  • Java语言实现生产者与消费者的消息队列模型(附源码)
    Java构建生产者与消费者之间的生产关系模型,可以理解生产者生产message,存放缓存的容器,同时消费者进行消费需求的消耗,如果做一个合理的比喻的话:生产者消费者问题是线程模型中的经典问题。解决生产者/消费者问题有两种方法:一是采用某种机制保护生产者和消费者之间的同步;二是在生产者和......
  • 一文读懂大厂面试的JAVA基础(集合,面向对象特性,反射,IO,容器)
    整理了操作系统,计算机网络,以及JVM的高频面试题目,对于面试大厂的Android以及后端开发岗位,可以说的是十分必要的部分就是JAVA语言的基础,在整体的内容上我认为有以下的几个部分,我发现任何的学习都是先建立框架体系,再逐个击破,针对Java的基础中包括:(1)Java语言的面向对象的特性(2)Java语言......