微信支付java


直接上代碼:

1、支付配置PayCommonUtil

import com.legendshop.payment.tenpay.util.MD5Util;
import com.legendshop.util.AppUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

public class PayCommonUtil {


    /**
     * @Date: 2018/6/6 11:08
     * @Descript: 因為公司app端與網頁端商戶號不一樣,所以這里需要做一些判斷
     */    
    
    /* 微信公眾號  */
    public static final String appid_wap = "your appid";
    public static final String MCHID_wap = "商戶號";

    /**
     * 微信原生
     * appid 查看方法:登錄微信商戶平台(https://pay.weixin.qq.com/index.php/core/home/login) --> 營銷中心 --> 支付后配置
     */
    public static final String appid = "your appid";
    public static final String MCHID = "商戶號";
    public static final String notify_url = "你的通知接口地址";
    /**
     * 兩個商戶號的key又是一樣的,這里就共用一個變量。
     * key 查看方法:登錄微信商戶平台 --> 賬戶中心 --> API安全 --> API密鑰
     */
    public static final String key = "your key";


    /**
     * 創建微信交易對象
     */
    public static SortedMap<Object, Object> getWXPrePayID(String tradeType, String openid) {
        SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();

        Boolean isWap = false;
        Boolean isWx = false;
        if (AppUtils.isNotBlank(tradeType)) {
            if (tradeType.equals("JSAPI")) {
                //微信瀏覽器內執行
                isWx = true;
                isWap = true;
            }
            if (tradeType.equals("MWEB")) {
                //H5
                isWap = true;
            }
        }


        parameters.put("appid", isWap ? appid_wap : appid);
        parameters.put("mch_id", isWap ? MCHID_wap : MCHID);
        parameters.put("nonce_str", createNoncestr());
        parameters.put("fee_type", "CNY");
        parameters.put("notify_url", notify_url);
        parameters.put("trade_type", AppUtils.isBlank(tradeType) ? "APP" : tradeType);
        if (isWx) {
            //微信瀏覽器內執行需要openid
            parameters.put("openid", openid);
        }
        return parameters;
    }

    /**
     * 再次簽名(僅APP支付需要)
     */
    public static SortedMap<Object, Object> startWXPay(Map map) {
        try {

            SortedMap<Object, Object> parameterMap = new TreeMap<Object, Object>();
            parameterMap.put("appid", appid);
            parameterMap.put("partnerid", MCHID);
            parameterMap.put("prepayid", map.get("prepay_id"));
            parameterMap.put("package", "Sign=WXPay");
            parameterMap.put("noncestr", createNoncestr());
            // 10位
            parameterMap.put("timestamp",
                    Long.parseLong(String.valueOf(System.currentTimeMillis()).toString().substring(0, 10)));
            String sign = PayCommonUtil.createSign("UTF-8", parameterMap);
            parameterMap.put("sign", sign);
            return parameterMap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /*
    * 獲取真實IP
    * */
    public static String getRemortIP(HttpServletRequest request) {
        if (request.getHeader("x-forwarded-for") == null) {
            return request.getRemoteAddr();
        }
        return request.getHeader("x-forwarded-for");
    }

    /**
     * 創建隨機數
     *
     * @param length
     * @return
     */
    public static String createNoncestr() {
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        String res = "";
        for (int i = 0; i < 16; i++) {
            Random rd = new Random();
            res += chars.charAt(rd.nextInt(chars.length() - 1));
        }
        return res;
    }

    /**
     * @param characterEncoding 編碼格式
     * @param parameters        請求參數
     * @return
     * @Description:創建sign簽名
     */
    public static String createSign(String characterEncoding, SortedMap<Object, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + key);
        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        return sign;
    }

}


工具類MD5Util

public class MD5Util {

    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));

        return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString;
    }

    private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

}

 

2、接下來是調用,這里只貼核心部分代碼

/**
* body:訂單標題
* out_trade_no:結算單流水號,一般用商城訂單號就可以了,但是這樣會有個問題就是在不同平台(微信瀏覽器內、微信外的瀏覽器、APP等)分別提交訂單或者訂單價格的改變等因素都會出現“201 訂單重復“的問題,可根據實際業務需求做相應的處理
* fee:最小是1,表示1分錢
* tradeType:JSAPI(微信瀏覽器支付)、MWEB(微信以外的瀏覽器)、APP(原生支付)
* openid:當tradeType = JSAPI,該屬性是必須的
* redirectUrl:當tradeType = MWEB,表示支付成功后的跳轉地址,一般是跳轉到支付成功的頁面
*/    
    
    
    private Map getWXPay(String body,String out_trade_no,String fee,String tradeType,String openid,String redirectUrl) throws Exception{


        SortedMap<Object, Object> parameters = PayCommonUtil.getWXPrePayID(tradeType,openid); // 獲取預付單,此處已做封裝,需要工具類


//        String body = "雅量商品支付";
//
//        String out_trade_no = PayCommonUtil.getDateStr();
//
//        String fee = "1";


        parameters.put("body", AppUtils.isBlank(body) ? "雅量商品支付" : body);

        //獲取ip要注意反向代理的ip獲取配置
        parameters.put("spbill_create_ip", PayCommonUtil.getRemortIP(request));
        parameters.put("out_trade_no", out_trade_no); 
        parameters.put("total_fee", fee); 

        /**
         * 簽名(簽名規則:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3,)
         * 注意:使用SortedMap已符合”參數名ASCII碼從小到大排序(字典序)“
         */
        String sign = PayCommonUtil.createSign("UTF-8", parameters);
        parameters.put("sign", sign);


        // 封裝請求參數結束
        String requestXML = PayCommonUtil.getRequestXml(parameters); // 獲取xml結果
        
        /** 
        * 調用統一下單接口(APP、微信內支付、H5都是使用該接口)
        *
        * 區別:
        * APP支付:統一下單 --> 二次簽名 --> 返回數據給客戶端調起支付
        * 微信內支付:統一下單 --> 返回數據給客戶端調起支付
        * H5支付:統一下單 --> 返回鏈接給客戶端,客戶端進行跳轉
        */
        String result = PayCommonUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST",
                requestXML);




        Map<String, String> map = XMLUtil.doXMLParse(result);


        Map allData = new HashedMap();
        Map runRs = new HashedMap();
        String rsCode = map.get("result_code").toString();
        runRs.put("result_code", rsCode);
        if(rsCode.equals("FAIL")){
            runRs.put("err_code_des",map.get("err_code_des"));
        }
        allData.put("runRs",runRs);


        if(AppUtils.isNotBlank(tradeType) && (tradeType.equals("JSAPI") || tradeType.equals("MWEB"))){




            SortedMap<Object, Object> signData = new TreeMap<Object, Object>();

            signData.put("appId",map.get("appid"));

            signData.put("timeStamp",
                    Long.parseLong(String.valueOf(System.currentTimeMillis()).toString().substring(0, 10)));

            signData.put("nonceStr",map.get("nonce_str"));

            signData.put("package","prepay_id=" + map.get("prepay_id"));

            signData.put("signType","MD5");

            String paySign = PayCommonUtil.createSign("UTF-8", signData);

            signData.put("paySign",paySign);


            if(tradeType.equals("MWEB")){

             //跳轉鏈接
                signData.put("mwebUrl",map.get("mweb_url") + "&redirect_url=" + redirectUrl);
            }

            allData.put("data",signData);


        }else{
            SortedMap<Object, Object> parMap = PayCommonUtil.startWXPay(map);





            allData.put("data",parMap);
        }

        return allData;


    }

 

工具類XMLUtil

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;

/**
 * xml工具類
 * @author miklchen
 *
 */
public class XMLUtil {

    /**
     * 解析xml,返回第一級元素鍵值對。如果第一級元素有子節點,則此節點的值是子節點的xml數據。
     * @param strxml
     * @return
     * @throws JDOMException
     * @throws IOException
     */
    public static Map doXMLParse(String strxml) throws JDOMException, IOException {
        if(null == strxml || "".equals(strxml)) {
            return null;
        }
        
        Map m = new HashMap();
        InputStream in = HttpClientUtil.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 = XMLUtil.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(XMLUtil.getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }
        
        return sb.toString();
    }
    
    /**
     * 獲取xml編碼字符集
     * @param strxml
     * @return
     * @throws IOException 
     * @throws JDOMException 
     */
    public static String getXMLEncoding(String strxml) throws JDOMException, IOException {
        InputStream in = HttpClientUtil.String2Inputstream(strxml);
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        in.close();
        return (String)doc.getProperty("encoding");
    }
    
    
}

千萬要注意參數的大小寫問題,一個接口一個樣

 

3、接收通知 

支付成功后不是由客戶端通知服務器的,這樣無法保證正確性,而是應該由微信服務器來通知我們的項目服務器,並且要做簽名驗證保證正確性、一致性。

 

 

通知接口關鍵代碼

    @RequestMapping(value = "/notify", method = RequestMethod.POST)
    public void notify(HttpServletRequest request,HttpServletResponse response) throws Exception {
        String resXml = "";
        /** 支付成功后,微信回調返回的信息 */
        Map<String, String> map=null;
        try {
            map = WeiXinUtil.parseXml(request);
        } catch (XmlPullParserException e1) {
            e1.printStackTrace();
        } catch (IOException e1) {
            e1.printStackTrace();
         }
        
        try {
//               SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();  
//              parameters.put("appid", map.get("appid"));  
//              parameters.put("attach",map.get("attach"));  
//              parameters.put("bank_type",map.get("bank_type"));  
//              parameters.put("cash_fee", map.get("cash_fee"));  
//              parameters.put("fee_type",  map.get("fee_type"));  
//              parameters.put("is_subscribe", map.get("is_subscribe"));  
//              parameters.put("mch_id", map.get("mch_id"));  
//              parameters.put("nonce_str", map.get("nonce_str"));  
//              parameters.put("openid",  map.get("openid"));  
//              parameters.put("out_trade_no", map.get("out_trade_no"));  
//              parameters.put("result_code", map.get("result_code"));  
//              parameters.put("return_code",  map.get("return_code"));  
//              parameters.put("time_end", map.get("time_end"));  
//              parameters.put("total_fee", map.get("total_fee"));  
//              parameters.put("trade_type", map.get("trade_type"));  
//              parameters.put("transaction_id", map.get("transaction_id"));  
            // 用於驗簽
            SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
            for (Object keyValue : map.keySet()) {
                //** 輸出返回的訂單支付信息 *//*
                if (!"sign".equals(keyValue)) {
                    parameters.put(keyValue, map.get(keyValue));
                }
            }
            
            String out_trade_no=map.get("out_trade_no");
            String openid=map.get("openid");
            boolean config=true;
            
            RequestHandler reqHandler = new RequestHandler(request,response);
            String checkSign = reqHandler.createSign(parameters);
            
            //簽名驗證
            if(
            AppUtils.isBlank(out_trade_no) || 
            AppUtils.isBlank(openid) ||
            map.get("result_code").toString().equalsIgnoreCase("FAIL") || 
            !checkSign.equals(map.get("sign"))
            ){
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[報文有誤]]></return_msg>" + "</xml> ";
                config=false;
            }

            
            if(config){
            
                //根據out_trade_no查找訂單,代碼根據自己的業務進行修改
                SubSettlement subSettlement= subSettlementService.getSubSettlementBySn(out_trade_no);        
                if(AppUtils.isBlank(subSettlement)){
                    resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                            + "<return_msg><![CDATA[訂單找不到]]></return_msg>" + "</xml> ";
                    config=false;
                }
                
                if(config){

                        // ***************
                        //校驗返回的訂單金額是否與商戶側的訂單金額一致
                        String total_fee=map.get("total_fee");
                        String OrderTotal=String.valueOf(new BigDecimal(Arith.sub(subSettlement.getCashAmount(), subSettlement.getPdAmount())).setScale(2,BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).intValue());

                        if(total_fee.equals(OrderTotal)){ //微信返回的金額與數據庫的金額不一致性
                            
                            //業務邏輯,一般是改變訂單狀態、發送訂單通知等等
                            
                            // 支付成功,返回success,微信服務器就不會再重復發送該通知            
                            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> ";
                        }
                    
                }
                
            }
            
        }catch (Exception e) {
                e.printStackTrace();
                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();
        
    }

 

 

條碼支付很簡單,下載demo和證書下來配置一下就可以直接運行

微信條碼支付demo:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=11_1

微信簽名要求:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3

微信支付開發文檔:https://pay.weixin.qq.com/wiki/doc/api/index.html

 

轉載請注明博客出處:http://www.cnblogs.com/cjh-notes/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM