欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java-微信小程序支付代码分享

程序员文章站 2024-03-09 08:15:53
...
按照微信支付的流程来进行:
1.小程序调用后台接口生成预支付订单:
package com.stonedt.controller;


import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;


import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;


import com.alibaba.fastjson.JSON;
import com.stonedt.entity.HosOrder;
import com.stonedt.util.DateKit;
import com.stonedt.util.HttpClientsKit;
import com.stonedt.util.ResultUtil;
import com.stonedt.util.WxUtils;


public class Test {


	// 小程序appid
	public static final String appid = "";
	// app **
	public static final String secret = "";
	// 微信支付的商户id
	public static final String mch_id = "";
	// 微信支付的商户**
	public static final String key = "";
	// 支付成功后的服务器回调url
	public static final String notify_url = "";
	// 签名方式,固定值
	public static final String SIGNTYPE = "MD5";
	// 交易类型,小程序支付的固定值为JSAPI
	public static final String trade_type = "JSAPI";
	// 微信统一下单接口地址
	public static final String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
	// 微信统一下单接口地址
	public static final String body = "";
	// 交易类型 : 这里是微信小程序支付
	public static final String JSAPI = "JSAPI";


	/**
	 * 支付接口
	 * 
	 * @param openid  用户唯一标识
	 * @return
	 */
	@RequestMapping(value = "pay", produces = "text/json;charset=UTF-8")
	@ResponseBody
	public String pay(HttpServletRequest request,String openid) {


		// 商户订单号--->订单号唯一  
		//这里用时间戳模拟  商户订单号
		String out_trade_no = System.currentTimeMillis()+"";
		// 生成商户订单类      预支付信息组装    用于存入数据库 
		/**
		 * 这里省略 ....个人生成自己的 订单类存入数据
		 */
		// 生成随机串
		String nonce_str = WxUtils.getRandomStringByLength(32);
		// 获取用户后台IP地址(即使用微信小程序的手机IP)
		String spbill_create_ip = WxUtils.getIpAddr(request);


		// 组装参数,用户生成统一下单接口的签名
		Map<String, String> preParams = new HashMap<String, String>();
		
		//attach可以存储你想要在回调中获取的数据
		String attach = out_trade_no;


		preParams.put("appid", appid);
		preParams.put("attach", attach);
		preParams.put("mch_id", mch_id);
		preParams.put("nonce_str", nonce_str);
		preParams.put("body", body);
		preParams.put("out_trade_no", out_trade_no);
		preParams.put("total_fee", "1");// hospital.getHosPrice()
		preParams.put("spbill_create_ip", spbill_create_ip);// 用户ip
		preParams.put("notify_url", notify_url);
		preParams.put("trade_type", trade_type);
		preParams.put("openid", openid);


		// 参数按照参数名ASCII码从小到大排序拼接 (即key1=value1&key2=value2…)
		Map<String, String> cleanSignArray = WxUtils.cleanSignArray(preParams);
		String preSign = WxUtils.createLinkString(cleanSignArray);


		System.out.println(preSign);
		// MD5运算生成签名,这里是第一次签名,用于调用统一下单接口 --签名时 需要全部转换编码
		System.out.println(WxUtils.sign(preSign, key, "utf-8"));
		String fistSign = WxUtils.sign(preSign, key, "utf-8").toUpperCase(); // 问题商户**


		// 拼接统一下单接口使用的xml数据,和params存储的数据一样,并且排列顺序安装a.b.c.d...的从小到大顺序排列  要将上一步生成的签名fistSign一起拼接进去
		String xml = "<xml>" + "<appid><![CDATA[" + appid + "]]></appid>" + "<attach><![CDATA[" + attach
				+ "]]></attach>" + "<body><![CDATA[" + body + "]]></body>" + "<mch_id><![CDATA[" + mch_id
				+ "]]></mch_id>" + "<nonce_str><![CDATA[" + nonce_str + "]]></nonce_str>" + "<notify_url><![CDATA["
				+ notify_url + "]]></notify_url>" + "<openid><![CDATA[" + openid + "]]></openid>"
				+ "<out_trade_no><![CDATA[" + out_trade_no + "]]></out_trade_no>" + "<spbill_create_ip><![CDATA["
				+ spbill_create_ip + "]]></spbill_create_ip>" + "<total_fee><![CDATA[1]]></total_fee>"
				+ "<trade_type><![CDATA[" + JSAPI + "]]></trade_type>" + "<sign><![CDATA[" + fistSign + "]]></sign>"
				+ "</xml>";


		System.out.println("调试模式_统一下单接口 请求XML数据:" + xml);


		// 调用统一下单接口,并接受返回的结果 ---》发送请求时 参数需要转换统一编码
		String result = HttpClientsKit.httpRequest(pay_url, "POST", xml);


		System.out.println("调试模式_统一下单接口 返回XML数据:" + result);


		// 这里解析的是xml数据
		Map returnMap = WxUtils.doXMLParse(result);
		// 返回状态码
		String return_code = (String) returnMap.get("return_code");


		System.out.println("返回状态码" + return_code);
		// 返回给小程序端的结果
		Map<String, Object> respone = new HashMap<String, Object>();
		// 返回给小程序端需要的参数
		Map<String, String> data = new HashMap<String, String>();


		// 有返回结果
		if (return_code.equals("SUCCESS")) {
			// 业务结果
			String result_code = (String) returnMap.get("result_code");
			
			
			if (!"SUCCESS".equals(result_code)) {
				//业务结果为fail
				respone.put("code", 1);
				String err_code_des = (String) returnMap.get("err_code_des");
				respone.put("msg", err_code_des);
			}


			String prepay_id = (String) returnMap.get("prepay_id");
			data.put("appId", appid);
			data.put("timeStamp", System.currentTimeMillis() + "");
			data.put("nonceStr", "m9fil9bt27e49ag1jz54vtxffwci7e08");
			data.put("package", "prepay_id=" + prepay_id);
			data.put("signType", "MD5");


			// 拼接签名需要的参数
			// 参数按照参数名ASCII码从小到大排序拼接 (即key1=value1&key2=value2…)
			Map<String, String> signTemp = WxUtils.cleanSignArray(data);
			// 生成签名字符串
			String tempSign = WxUtils.createLinkString(signTemp);
			// 再次签名,这个签名用于小程序端调用wx.requesetPayment方法
			String resSign = WxUtils.sign(tempSign, key, "utf-8");
			data.put("paySign", resSign);


			respone.put("data", data);
			respone.put("code", 0);
			respone.put("msg", "success");
		} else {
			//返回结果失败
			respone.put("code", 1);
			String return_msg = (String) returnMap.get("return_msg");
			respone.put("msg", return_msg);
		}
		//返回的数据用于小 程序  调起支付  或 展示 错误信息
		return JSON.toJSONString(respone);
	}
}

2.小程序根据后台接口生成预支付订单后返回的数据生成预支付订单:
 
wx.requestPayment({
              timeStamp: e.data.data.timeStamp,
              nonceStr: e.data.data.nonceStr,
              package: e.data.data.package,
              signType: 'MD5',
              paySign: e.data.data.paySign,
              success: function (event) {
                // success     
                console.log(event);

                wx.showToast({
                  title: '支付成功',
                  icon: 'success',
                  duration: 2000
                });
                //处理  业务逻辑
                
              },
              fail: function (error) {
                // fail     
                console.log("支付失败")
                wx.showToast({
                  title: '支付失败',
                  icon: 'none',
                  duration: 2000
                });
              }
            })
3.后台的支付回调:

   
/**
	 * 微信通知后台的接口
	 * 
	 * @param request
	 * @return
	 */
	@RequestMapping(value = "notifi")
	@ResponseBody
	public void notifi(HttpServletRequest request, HttpServletResponse response) {
		/**
		 * 当收到通知进行处理时,应检查对应业务数据的状态,判断该通知是否已经处理过
		 * 涉及查询数据库 这里省略
		 * 直接默认未处理
		 */
		String resXml = "";
		try {
			// 通过ServletInputStream读取http请求传入的数据
			BufferedReader br = new BufferedReader(
					new InputStreamReader((ServletInputStream) request.getInputStream()));
			String temp = null;
			// 获取微信返回的xml
			StringBuffer wxRes = new StringBuffer();
			while ((temp = br.readLine()) != null) {
				wxRes.append(temp);
			}
			br.close();
			// 处理微信返回的xml数据
			String notityXml = wxRes.toString();
			System.out.println("接收到微信的返回值(notityXml):" + notityXml);
			Map map = WxUtils.doXMLParse(notityXml);
			// 判断返回结果
			String returnCode = (String) map.get("return_code");
			System.out.println("微信返回结果returnCode:" + returnCode);
			if ("SUCCESS".equals(returnCode)) {
				// 判断result_code--->交易是否成功需要查看result_code来判断
				String result_code = (String) map.get("result_code");
				System.out.println("微信业务返回结果result_code:" + result_code);
				if ("SUCCESS".equals(result_code)) {
					// 成功
					// 获取签名
					String notitySign = (String) map.get("sign");
					// 去除原sign
					map.remove("sign");

					// 对微信传回的数据进行2次签名验证
					if (WxUtils.verify(WxUtils.createLinkString(map), notitySign, key, "utf-8")) {
						// 修改订单信息 --根据订单号order_id
						/**
						 * 修改 根据out_trade_no(我们自己的订单号-->从微信返回的XML中获取 然后修改数据库的订单记录下面三项作为举例) 
						 * 
						 * 支付时间(pay_time) 
						 * 支付状态(order_status=1,支付成功)
						 * 微信流水号(wexin_serial_num)
						 * 
						 */
						// 获取参数
						// 微信支付订单号
						String wxOrderId = (String) map.get("transaction_id");
						// 商户订单号(我们自己的订单号)
						String out_trade_no = (String) map.get("out_trade_no");
						// 支付时间
						String payTime = (String) map.get("time_end");
						// 生成order类
						HosOrder order = new HosOrder();
						order.setOrderId(out_trade_no);
						order.setPayTime(DateKit.localDateTime2Date(payTime));
						order.setOrderStatus(1);
						order.setWexinSerialNum(wxOrderId);
						// 修改订单信息
						/**
						 * 根据serviceImpl  修改 对应订单数据
						 */

						// 通知微信服务器已经处理成功
						resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
								+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
					} else {
						//签名2次验证失败
						System.out.println("微信签名验证失败------");
						resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
								+ "<return_msg><![CDATA[签名验证失败!]]></return_msg>" + "</xml> ";
					}
				} else {
					//微信业务结果失败	
					String err_code_des = (String) map.get("err_code_des");
					System.out.println("微信业务结果失败,错误信息err_code_des:" + err_code_des);
					resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA["
							+ err_code_des + "]]></return_msg>" + "</xml> ";
				}

			}

			System.out.println("微信支付回调数据结束resXml:" + resXml);
			// 返回结果
			BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
			out.write(resXml.getBytes());
			out.flush();
			out.close();

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();

		}

	}
4.微信小程序支付涉及工具类:
package com.stonedt.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.Security;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
 

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;


/**
 * 微信支付工具类
 * @author spring
 *
 */
public class WxUtils {
	/**
	 * 把字符串最后拼接上 key ,再进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign
	 * @param text  需要签名的字符串
	 * @param key   **
	 * @param inpit_charset  编码格式 
	 * @return
	 */
	public static String sign(String text,String key,String inpit_charset) {
		String sign=text+"&key="+key;
		return DigestUtils.md5Hex(getContentBytes(sign, inpit_charset));
	}
	
	/**
	 * 验证签名是否正确
	 * @param text  需要签名的字符串 
	 * @param sign  微信的签名结果    
	 * @param key   **
	 * @param input_charset  编码格式 
	 * @return 验证结果
	 */
	public static boolean verify(String text, String sign, String key, String input_charset){     
        if(null==text||null==sign||null==key) {
        	return false;  
        }else {
        	text = text +"&key="+ key;     
            String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset)).toUpperCase();
            System.out.println("打印微信返回的数据组装的新的sign--mysign:"+mysign);
            if (mysign.equals(sign)) {     
                return true;     
            } else {     
                return false;     
            }  
        }
    } 
	
	
		
	/**
	 * 把字符串按照一点编码格式转成字节数组
	 * @param content  字符串
	 * @param charset  编码方式
	 * @return
	 */
	 public static byte[] getContentBytes(String content, String charset) {     
	        if (charset == null || "".equals(charset)) {     
	            return content.getBytes();     
	        }     
	        try {     
	            return content.getBytes(charset);     
	        } catch (UnsupportedEncodingException e) {     
	            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);     
	        }     
	    } 
	 
	 	
	 	/**   
	     * 除去数组中的空值和签名参数   
	     * @param sArray 签名参数组   
	     * @return 去掉空值与签名参数后的新签名参数组   
	     */     
	    public static Map<String, String> cleanSignArray(Map<String, String> signArray) {     
	        Map<String, String> result = new HashMap<String, String>();     
	        if (signArray == null || signArray.size() <= 0) {     
	            return result;     
	        }     
	        for (String key : signArray.keySet()) {     
	            String value = signArray.get(key);     
	            if (value == null || value.equals("")) {     
	                continue;     
	            }     
	            result.put(key, value);     
	        }     
	        return result;     
	    } 
	 
	    /**   
	     * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串   
	     * @param params 需要排序并参与字符拼接的参数组   
	     * @return 拼接后字符串   
	     */     
	    public static String createLinkString(Map<String, String> params) {     
	        List<String> keys = new ArrayList<String>(params.keySet());     
	        //Collections是一个工具类,sort是其中的静态方法,是用来对List类型进行排序,规则是从小到大
	        Collections.sort(keys);     
	        String prestr = "";     
	        for (int i = 0; i < keys.size(); i++) {     
	            String key = keys.get(i);     
	            String value = params.get(key);     
	            if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符     
	                prestr = prestr + key + "=" + value;     
	            } else {     
	                prestr = prestr + key + "=" + value + "&";     
	            }     
	        }     
	        return prestr;     
	    }  
	    
	    
	    /** 
	     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 
	     * @param strxml 
	     * @return 
	     * @throws JDOMException 
	     * @throws IOException 
	     */  
	    public static Map doXMLParse(String strxml){ 
	    	Map m = new HashMap();  
	    	try {
	        if(null == strxml || "".equals(strxml)) {  
	            return null;  
	        }  
	       
	        InputStream in = String2Inputstream(strxml);  
	        SAXBuilder builder = new SAXBuilder();  
	        Document doc;
			
				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 = getChildrenText(children);  
	            }  
	              
	            m.put(k, v);  
	        }  
	          
	        //关闭流  
	        in.close();
	    	} catch (JDOMException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} 
	    	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(getChildrenText(list));  
	                }  
	                sb.append(value);  
	                sb.append("</" + name + ">");  
	            }  
	        }  
	          
	        return sb.toString();  
	    }  
	    
	    public static InputStream String2Inputstream(String str) {  
	        return new ByteArrayInputStream(str.getBytes());  
	    }  
	    
	    
	    /** 
	     * 获取真实的ip地址 
	     * @param request 
	     * @return 
	     */  
	    public static String getIpAddr(HttpServletRequest request) {  
	        String ip = request.getHeader("X-Forwarded-For");  
	        if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){  
	             //多次反向代理后会有多个ip值,第一个ip才是真实ip  
	            int index = ip.indexOf(",");  
	            if(index != -1){  
	                return ip.substring(0,index);  
	            }else{  
	                return ip;  
	            }  
	        }  
	        ip = request.getHeader("X-Real-IP");  
	        if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){  
	           return ip;  
	        }  
	        return request.getRemoteAddr();  
	    }
	    
	    /** 
	     * 获取一定长度的随机字符串,范围0-9,a-z 
	     * @param length:指定字符串长度 
	     * @return 一定长度的随机字符串 
	     */  
	    public static String getRandomStringByLength(int length) {  
	        String base = "abcdefghijklmnopqrstuvwxyz0123456789";  
	        Random random = new Random();  
	        StringBuffer sb = new StringBuffer();  
	        for (int i = 0; i < length; i++) {  
	            int number = random.nextInt(base.length());  
	            sb.append(base.charAt(number));  
	        }  
	        return sb.toString();  
	       } 
	   
	    ////////////////下面是解密手机号的方法
	    
	    // 算法名
	    public static final String KEY_NAME = "AES";
	    // 加解密算法/模式/填充方式
	    // ECB模式只用**即可对数据进行加密解密,CBC模式需要添加一个iv
	    public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";

	    /**
	     * 微信 数据解密<br/>
	     * 对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充
	     * 对称解密的目标密文:encrypted=Base64_Decode(encryptData)
	     * 对称解密秘钥:key = Base64_Decode(session_key),aeskey是16字节
	     * 对称解密算法初始向量:iv = Base64_Decode(iv),同样是16字节
	     *
	     * @param encrypted 目标密文
	     * @param session_key 会话ID
	     * @param iv 加密算法的初始向量
	     */
	    public static String wxDecrypt(String encrypted, String session_key, String iv) {
	        String json = null;
	        byte[] encrypted64 = Base64.decodeBase64(encrypted);
	        byte[] key64 = Base64.decodeBase64(session_key);
	        byte[] iv64 = Base64.decodeBase64(iv);
	        byte[] data;
	        try {
	            init();
	            json = new String(decrypt(encrypted64, key64, generateIV(iv64)));
	        } catch (Exception e) {
	            e.printStackTrace();
	        }
	        return json;
	    }

	    /**
	     * 初始化**
	     */
	    public static void init() throws Exception {
	        Security.addProvider(new BouncyCastleProvider());
	        KeyGenerator.getInstance(KEY_NAME).init(128);
	    }

	    /**
	     * 生成iv
	     */
	    public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
	        // iv 为一个 16 字节的数组,这里采用和 iOS 端一样的构造方法,数据全为0
	        // Arrays.fill(iv, (byte) 0x00);
	        AlgorithmParameters params = AlgorithmParameters.getInstance(KEY_NAME);
	        params.init(new IvParameterSpec(iv));
	        return params;
	    }

	    /**
	     * 生成解密
	     */
	    public static byte[] decrypt(byte[] encryptedData, byte[] keyBytes, AlgorithmParameters iv)
	            throws Exception {
	        Key key = new SecretKeySpec(keyBytes, KEY_NAME);
	        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
	        // 设置为解密模式
	        cipher.init(Cipher.DECRYPT_MODE, key, iv);
	        return cipher.doFinal(encryptedData);
	    }
}

微信返回的时间的格式问题(yyyyMMddHHmmss类型)

 /** 
     * yyyyMMddHHmmss 转换为Date 
     * @param localDateTime 
     */  
	public static Date localDateTime2Date( String datetime){ 
		//先把符合格式的字符串时间  转换成LocalDateTime
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");  
        LocalDateTime ldt = LocalDateTime.parse(datetime,dtf);  
        //把LocalDateTime 转换成Date
        ZoneId zoneId = ZoneId.systemDefault();  
        ZonedDateTime zdt = ldt.atZone(zoneId);//Combines this date-time with a time-zone to create a  ZonedDateTime.  
        Date date = Date.from(zdt.toInstant());  
        
        return date;
    }  

发送和接收 统一下单接口内容

 

/**   
     *   
     * @param requestUrl 请求地址   
     * @param requestMethod 请求方法   
     * @param outputStr 参数   
     */     
    public static String httpRequest(String requestUrl,String requestMethod,String outputStr){     
          
        StringBuffer buffer = null;     
        try{     
            URL url = new URL(requestUrl);     
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();     
            conn.setRequestMethod(requestMethod); 
            conn.setRequestProperty("Content-Type","text/xml;charset=UTF-8");
            conn.setDoOutput(true);     
            conn.setDoInput(true);     
            conn.connect();     
            //往服务器端写内容     
            if(null !=outputStr){     
                OutputStream os=conn.getOutputStream();     
                os.write(outputStr.getBytes("utf-8"));     
                os.close();     
            }     
            // 读取服务器端返回的内容     
            InputStream is = conn.getInputStream();     
            InputStreamReader isr = new InputStreamReader(is, "utf-8");     
            BufferedReader br = new BufferedReader(isr);     
            buffer = new StringBuffer();     
            String line = null;     
            while ((line = br.readLine()) != null) {     
                buffer.append(line);     
            }     

                br.close();  
        }catch(Exception e){     
            e.printStackTrace();     
        }  
        return buffer.toString();  
    }  

最后感觉一位博主:https://blog.csdn.net/zhourenfei17/article/details/77765585

借鉴了不少,虽然有些地方出现了问题。。。。感谢!

相关标签: z