java微信小程序支付完整流程

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u013661953/article/details/81189634

api支付流程必读:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3

用到的jar包就不贴了

 

1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API

public String getOpenId(String code) throws BusinessException {
		
		try {
			//登录凭证校验
			String loginValidataUrl = "https://api.weixin.qq.com/sns/jscode2session?appid="+appid+"&secret="+appsecret+"&js_code="+code+"&grant_type=authorization_code";

			Connection conn = Jsoup.connect(loginValidataUrl);
			conn.method(Method.GET).execute();
			int status = conn.response().statusCode();//200成功
			if (status!=200) {
				//抛异常
				throw new Exception(EWxError.WX_GET_OPENID_ERROR.getName());
			}
			String body = conn.response().body();
			
			return body;
		} catch (Exception e) {
			log.error(EWxError.WX_GET_OPENID_ERROR.getName(),e);
			throw new BusinessException(EWxError.WX_GET_OPENID_ERROR.getName());
		}
	}


 

用到的实体类PaymentPo.java

package com.wdwl.changfa.po;

import java.io.Serializable;

import lombok.Data;

/**
 * 小程序支付
 * @author lhy
 *	
 */
@Data
public class PaymentPo  implements Serializable{ 
    /**
	 * 
	 */
	private static final long serialVersionUID = 1712467669291115101L;
	private String appid;//小程序ID 
    private String mch_id;//商户号 
    private String device_info;//设备号 
    private String nonce_str;//随机字符串 
    private String sign;//签名 
    private String body;//商品描述  
    private String detail;//商品详情    
    private String attach;//附加数据 
    private String out_trade_no;//商户订单号 
    private String fee_type;//货币类型 
    private String spbill_create_ip;//终端IP 
    private String time_start;//交易起始时间 
    private String time_expire;//交易结束时间 
    private String goods_tag;//商品标记 
    private String total_fee;//总金额 
    private String notify_url;//通知地址    
    private String trade_type;//交易类型    
    private String limit_pay;//指定支付方式 
    private String openid;//用户标识 
    private String code;//用户标识 
    private String placeId;//用户标识 
    private Integer carSum;//用户购买了多少次
    private Integer type;//用户购卡类型
} 

 

2、商户server调用支付统一下单,api参见公共api【统一下单API

3、商户server调用再次签名,api参见公共api【再次签名

2和3写在一起了

注意:小程序金额不是元,单位是分(如:100=1元)

@SuppressWarnings("rawtypes")
	private Map<String, String> goPay(PaymentPo paymentPo) throws Exception {
		//商品名称
		//String body = "测试商品名称";
		//金额元=paymentPo.getTotal_fee()*100
		String total_fee = String.valueOf(new BigDecimal(paymentPo.getTotal_fee()).multiply(new BigDecimal(100)).intValue());
		//组装参数,用户生成统一下单接口的签名
		Map<String, String> packageParams = new HashMap<String, String>();
		packageParams.put("appid", appid);
		packageParams.put("mch_id", mchId);
		packageParams.put("nonce_str", paymentPo.getNonce_str());
		packageParams.put("body", paymentPo.getBody());
		packageParams.put("out_trade_no", paymentPo.getOut_trade_no());//商户订单号
		packageParams.put("total_fee", total_fee);//支付金额,这边需要转成字符串类型,否则后面的签名会失败
		packageParams.put("notify_url", paymentPo.getNotify_url());//支付成功后的回调地址
		packageParams.put("trade_type", tradeType);//支付方式
		packageParams.put("openid", paymentPo.getOpenid());
		   
		String prestr = PayUtil.createLinkString(packageParams); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 
		
		//MD5运算生成签名,这里是第一次签名,用于调用统一下单接口
		String mysign = PayUtil.sign(prestr, key, "utf-8").toUpperCase();
		
		//拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
		String xml = "<xml>" + "<appid>" + appid + "</appid>" 
		        + "<body><![CDATA[" + paymentPo.getBody() + "]]></body>" 
		        + "<mch_id>" + mchId + "</mch_id>" 
		        + "<nonce_str>" + paymentPo.getNonce_str() + "</nonce_str>" 
		        + "<notify_url>" + paymentPo.getNotify_url() + "</notify_url>" 
		        + "<openid>" + paymentPo.getOpenid() + "</openid>" 
		        + "<out_trade_no>" + paymentPo.getOut_trade_no() + "</out_trade_no>" 
		       /* + "<spbill_create_ip>" + paymentPo.getSpbill_create_ip() + "</spbill_create_ip>" */
		        + "<total_fee>" + total_fee + "</total_fee>"
		        + "<trade_type>" + tradeType + "</trade_type>" 
		        + "<sign>" + mysign + "</sign>"
		        + "</xml>";
		
		log.info("调试模式_统一下单接口 请求XML数据:" + xml);

		//调用统一下单接口,并接受返回的结果
		String res = PayUtil.httpRequest(payUrl, "POST", xml);
		
		log.info("调试模式_统一下单接口 返回XML数据:" + res);
		
		// 将解析结果存储在HashMap中   
		Map map = PayUtil.doXMLParse(res);
		
		String return_code = (String) map.get("return_code");//返回状态码
		
		Map<String, String> result = new HashMap<String, String>();//返回给小程序端需要的参数
		String prepay_id = null;
		if(return_code=="SUCCESS"||return_code.equals(return_code)){  
			prepay_id = (String) map.get("prepay_id");//返回的预付单信息   

		    result.put("nonceStr", paymentPo.getNonce_str());
		    result.put("package", "prepay_id=" + prepay_id);
		    Long timeStamp = System.currentTimeMillis() / 1000;   
		    result.put("timeStamp", timeStamp + "");//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
		    //拼接签名需要的参数
		    String stringSignTemp = "appId=" + appid + "&nonceStr=" + paymentPo.getNonce_str() + "&package=prepay_id=" + prepay_id+ "&signType=MD5&timeStamp=" + timeStamp;   
		    //再次签名,这个签名用于小程序端调用wx.requesetPayment方法
		    String paySign = PayUtil.sign(stringSignTemp, key, "utf-8").toUpperCase();
		    
		    result.put("paySign", paySign);
		}
		result.put("appid", appid);
		return result;
	}

 

4、商户server接收支付通知,api参见公共api【支付结果通知API】即支付回调通知

此步骤未测,项目未上线无法测试。有测试成功的可以留个言

/**
	 * 支付回调
	 * @param request
	 * @param response
	 * @throws InterruptedException
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@RequestMapping(value="/pay/notify")
	public synchronized void notify(HttpServletRequest request,HttpServletResponse response) throws InterruptedException{
		
		String orderId = null;
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream()));
	        String line = null;
	        StringBuilder sb = new StringBuilder();
	        while((line = br.readLine()) != null){
	            sb.append(line);
	        }
	        br.close();
	        //sb为微信返回的xml
	        String notityXml = sb.toString();
	        String resXml = "";
	        log.info("接收到的报文:" + notityXml);
	 
	        Map map = PayUtil.doXMLParse(notityXml);
	 
	        String returnCode = (String) map.get("return_code");
	        if("SUCCESS".equals(returnCode)){
	            //验证签名是否正确
	            Map<String, String> validParams = PayUtil.paraFilter(map);  //回调验签时需要去除sign和空值参数
	            String validStr = PayUtil.createLinkString(validParams);//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
	            String sign = PayUtil.sign(validStr, key, "utf-8").toUpperCase();//拼装生成服务器端验证的签名
	            //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等
	            if(sign.equals(map.get("sign"))){
	                /**此处添加自己的业务逻辑代码start**/
	            	//TODO

	                /**此处添加自己的业务逻辑代码end**/
	                //通知微信服务器已经支付成功
	                resXml = this.getXml();
	                
	            }
	            
	        }else{
	            resXml = this.getFailXml();
	        }
	        log.info(resXml);
	        log.info("微信支付回调数据结束");
	 
	 
	        BufferedOutputStream out = new BufferedOutputStream(
	                response.getOutputStream());
	        out.write(resXml.getBytes());
	        out.flush();
	        out.close();
		} catch (Exception e) {
			log.error(EPayError.PAY_NOTIFY_ERROR.getName(),orderId,e);
		}
	}

 

PayUtil.java

package com.wdwl.changfa.utils.pay;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SignatureException;
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 org.apache.commons.codec.digest.DigestUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
 
public class PayUtil {
	 /**  
     * 签名字符串  
     * @param text需要签名的字符串  
     * @param key 密钥  
     * @param input_charset编码格式  
     * @return 签名结果  
     */   
    public static String sign(String text, String key, String input_charset) {   
        text = text + "&key=" + key;   
        return DigestUtils.md5Hex(getContentBytes(text, input_charset));   
    }   
    /**  
     * 签名字符串  
     *  @param text需要签名的字符串  
     * @param sign 签名结果  
     * @param key密钥  
     * @param input_charset 编码格式  
     * @return 签名结果  
     */   
    public static boolean verify(String text, String sign, String key, String input_charset) {   
        text = text + key;   
        String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset));   
        if (mysign.equals(sign)) {   
            return true;   
        } else {   
            return false;   
        }   
    }   
    /**  
     * @param content  
     * @param charset  
     * @return  
     * @throws SignatureException  
     * @throws UnsupportedEncodingException  
     */   
    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);   
        }   
    }   
    
    private static boolean isValidChar(char ch) {   
        if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))   
            return true;   
        if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f))   
            return true;// 简体中文汉字编码   
        return false;   
    }   
    /**  
     * 除去数组中的空值和签名参数  
     * @param sArray 签名参数组  
     * @return 去掉空值与签名参数后的新签名参数组  
     */   
    public static Map<String, String> paraFilter(Map<String, String> sArray) {   
        Map<String, String> result = new HashMap<String, String>();   
        if (sArray == null || sArray.size() <= 0) {   
            return result;   
        }   
        for (String key : sArray.keySet()) {   
            String value = sArray.get(key);   
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")   
                    || key.equalsIgnoreCase("sign_type")) {   
                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(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;   
    }   
    /**  
     *  
     * @param requestUrl请求地址  
     * @param requestMethod请求方法  
     * @param outputStr参数  
     */   
    public static String httpRequest(String requestUrl,String requestMethod,String outputStr){   
        // 创建SSLContext   
        StringBuffer buffer = null;   
        try{   
	        URL url = new URL(requestUrl);   
	        HttpURLConnection conn = (HttpURLConnection) url.openConnection();   
	        conn.setRequestMethod(requestMethod);   
	        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();
    }     
    public static String urlEncodeUTF8(String source){   
        String result=source;   
        try {   
            result=java.net.URLEncoder.encode(source, "UTF-8");   
        } catch (UnsupportedEncodingException e) {   
            // TODO Auto-generated catch block   
            e.printStackTrace();   
        }   
        return result;   
    } 
	/**
	 * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
	 * @param strxml
	 * @return
	 * @throws JDOMException
	 * @throws IOException
	 */
	public static Map doXMLParse(String strxml) throws Exception {
		if(null == strxml || "".equals(strxml)) {
			return null;
		}
		
		Map m = new HashMap();
		InputStream in = String2Inputstream(strxml);
		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 = 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(getChildrenText(list));
				}
				sb.append(value);
				sb.append("</" + name + ">");
			}
		}
		
		return sb.toString();
	}
	public static InputStream String2Inputstream(String str) {
		return new ByteArrayInputStream(str.getBytes());
	}
}

 

做个记录。代码有漏掉可以留言。

展开阅读全文

没有更多推荐了,返回首页