1. 程式人生 > >java微信小程式支付完整流程

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

用到的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());
	}
}

做個記錄。程式碼有漏掉的可以加Q739568808.