Java实现公众号H5 微信支付

最近由于有微信项目使用到了微信支付这功能,我原本以为跟调用其它js接口一样,非常简单,实则不然,因为微信团队留下了很多天坑,提供的文档全是老版的,下载下来无法使用,导致这个支付功能 害我整整调了一天,全是微信团队留下的坑,有些签名并不是下载事例所说那样,接下来我将分享通过自身努力实现的微信支付成果:
第一步:微信支付配置文件 ConfigUtil
/**
* 微信支付配置文件
* @author Mark
*
*/

public class ConfigUtil {
/**
* 服务号相关信息
*/
public final static String APPID = “”;//服务号的appid
public final static String APP_SECRECT = “”;//服务号的appSecrect
public final static String TOKEN = ” “;//服务号的配置token
public final static String MCH_ID = “”;//开通微信支付分配的商户号
public final static String API_KEY = “”;//商户API密钥 自行去商户平台设置
public final static String SIGN_TYPE = “MD5”;//签名加密方式
//微信支付统一接口的回调action
public final static String NOTIFY_URL = “”; //用于告知微信服务器 调用成功

/**
 * 微信基础接口地址
 */
 //获取token接口(GET)
 public final static String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
 //oauth2授权接口(GET)
 public final static String OAUTH2_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
 //刷新access_token接口(GET)
 public final static String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
// 菜单创建接口(POST)
 public final static String MENU_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
// 菜单查询(GET)
 public final static String MENU_GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";
// 菜单删除(GET)
public final static String MENU_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";
/**
 * 微信支付接口地址
 */
//微信支付统一接口(POST)
public final static String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//微信退款接口(POST)
public final static String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
//订单查询接口(POST)
public final static String CHECK_ORDER_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
//关闭订单接口(POST)
public final static String CLOSE_ORDER_URL = "https://api.mch.weixin.qq.com/pay/closeorder";
//退款查询接口(POST)
public final static String CHECK_REFUND_URL = "https://api.mch.weixin.qq.com/pay/refundquery";
//对账单接口(POST)
public final static String DOWNLOAD_BILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";
//短链接转换接口(POST)
public final static String SHORT_URL = "https://api.mch.weixin.qq.com/tools/shorturl";
//接口调用上报接口(POST)
public final static String REPORT_URL = "https://api.mch.weixin.qq.com/payitil/report";

}
第二步:通用工具类 CommonUtil (用于请求接口)
/**
* 通用工具类
* @author Mark
*/
public class CommonUtil {
private static Logger log = LoggerFactory.getLogger(CommonUtil.class);
/**
* 发送https请求
* @param requestUrl 请求地址
* @param requestMethod 请求方式(GET、POST)
* @param outputStr 提交的数据
* @return 返回微信服务器响应的信息
*/
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance(“SSL”, “SunJSSE”);
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ssf);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.setRequestProperty(“content-type”, “application/x-www-form-urlencoded”);
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes(“UTF-8”));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, “utf-8”);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
return buffer.toString();
} catch (ConnectException ce) {
log.error(“连接超时:{}”, ce);
} catch (Exception e) {
log.error(“https请求异常:{}”, e);
}
return null;
}

public static String urlEncodeUTF8(String source){
    String result = source;
    try {
        result = java.net.URLEncoder.encode(source,"utf-8");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return result;
}

}
第三步:MD5处理 MD5Util
/**
* MD5处理
* @author Mark
*
*/
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" };

}
第四步:信任管理器 微信的信任证书 MyX509TrustManager
/**
* 信任管理器
* @author Mark
*/
public class MyX509TrustManager implements X509TrustManager {

// 检查客户端证书
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}

// 检查服务器端证书
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}

// 返回受信任的X509证书数组
public X509Certificate[] getAcceptedIssuers() {
    return null;
}

}
第五步:微信H5支付 签名 随机码 时间戳等管理 PayCommonUtil
/**
* 微信H5支付 签名 随机码 时间戳等管理
* @author Mark
*
*/

public class PayCommonUtil {

/**
 * 获取支付随机码
 * @return
 */
 public static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }
   /**
    * 获取微信支付时间戳
    * @return
    */
    public static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

    /**
     * 获取预支付ID时  获取随机码
     * @param length
     * @return
     */
    public static String CreateNoncestr(int length) {
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        String res = "";
        for (int i = 0; i < length; i++) {
            Random rd = new Random();
            res += chars.indexOf(rd.nextInt(chars.length() - 1));
        }
        return res;
    }
    /**
     * 获取预支付ID时  获取随机码
     * @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;
    }

/**
 * @author Mark
 * @Description:sign签名
 * @param characterEncoding 编码格式
 * @param parameters 请求参数
 * @return
 */
public static String createSign(SortedMap 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=" + ConfigUtil.API_KEY);
    String sign = MD5Util.MD5Encode(sb.toString(),"UTF-8").toUpperCase();
    return sign;
}


/**
 * @author Mark
 * @Description:将请求参数转换为xml格式的string
 * @param parameters  请求参数
 * @return
 */
public static String getRequestXml(SortedMap parameters){
    StringBuffer sb = new StringBuffer();
    sb.append("");
    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+">"+"");
        }else {
            sb.append("<"+k+">"+v+"");
        }
    }
    sb.append("");
    return sb.toString();
}
/**
 * @author Mark
 * @Description:返回给微信的参数
 * @param return_code 返回编码
 * @param return_msg  返回信息
 * @return
 */
public static String setXML(String return_code, String return_msg) {
    return "";
}

}
第六步:调用微信H5支付统一下单接口 得到预支付ID WxPayUtil
/**
* 调用微信H5支付统一下单接口 得到预支付ID
* @author Mark
*
*/

public class WxPayUtil {

@SuppressWarnings("unchecked")
public static String unifiedorder(String body,String out_trade_no,String openid) {
    SortedMap parameters = new TreeMap();
    parameters.put("appid", ConfigUtil.APPID);

    parameters.put("mch_id", ConfigUtil.MCH_ID);
    parameters.put("nonce_str", PayCommonUtil.CreateNoncestr());
    parameters.put("body", body);
    parameters.put("out_trade_no", out_trade_no);
    parameters.put("total_fee", "1");
    parameters.put("spbill_create_ip","113.57.246.11");
    parameters.put("notify_url", ConfigUtil.NOTIFY_URL);
    parameters.put("trade_type", "JSAPI");
    parameters.put("openid", openid);
    String sign = PayCommonUtil.createSign(parameters);
    parameters.put("sign", sign);
    String requestXML = PayCommonUtil.getRequestXml(parameters);
    System.out.println(requestXML.toString());
    String result =CommonUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL, "POST", requestXML);
    System.out.println(result.toString());
    Map map=new HashMap();
    try {
        map = XMLUtil.doXMLParse(result);
    } catch (JDOMException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }//解析微信返回的信息,以Map形式存储便于取值
    return map.get("prepay_id").toString();
}

}
第七步:xml管理 XMLUtil
/**
* xml管理
* @author Mark
*
*/
public class XMLUtil {
/**
* 解析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 m = new HashMap();

    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 = 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("");
        }
    }

    return sb.toString();
}

}
第八步:接下来是Controller中调用,方式如下:
@ApiOperation(value = “微信支付调用”, httpMethod = “POST”)
@RequestMapping(“/couponsConfirm”)
public String couponsConfirm(Model m,@RequestParam(“openid”)String openid,@RequestParam(“orderNo”)String orderNo) {
//openid可通过微信高级接口oath2.0网页授权接口获取到用户信息,此接口本文中就不提供了,如果有需要,请留言。
m.addAttribute(“openid”, openid);
//orderNo是你的商品订单号,自行生成的随机订单号,但是要保证随机性,不能有重复订单号。
m.addAttribute(“orderNo”, orderNo);

    String timeStamp=PayCommonUtil.create_timestamp();
    String nonceStr=PayCommonUtil.create_nonce_str();
    m.addAttribute("appid", ConfigUtil.APPID);
    m.addAttribute("timestamp", timeStamp);
    m.addAttribute("nonceStr", nonceStr);
    m.addAttribute("openid",openid);

    String prepayId=WxPayUtil.unifiedorder("外卖下单",orderNo, openid);

// String userAgent = request.getHeader(“user-agent”);
// char agent = userAgent.charAt(userAgent.indexOf(“MicroMessenger”)+15);
// m.addAttribute(“agent”, new String(new char[]{agent}));//微信版本号,用于前面提到的判断用户手机微信的版本是否是5.0以上版本。

    SortedMap signParams = new TreeMap();  
    signParams.put("appId", ConfigUtil.APPID);  
    signParams.put("nonceStr",nonceStr);  
    signParams.put("package", "prepay_id="+prepayId);  
    signParams.put("timeStamp", timeStamp);  
    signParams.put("signType", "MD5");  

    // 生成支付签名,要采用URLENCODER的原始值进行SHA1算法!  
    String sign= PayCommonUtil.createSign(signParams);


    m.addAttribute("paySign", sign);

    m.addAttribute("packageValue", "prepay_id="+prepayId);

    return "跳转到你的支付页面";
}

最后一步:页面调用
WeixinJSBridge.invoke(‘getBrandWCPayRequest’,{
“appId”:(“#appid”).val(),  
                            “timeStamp”:
(“#timestamp”).val(),
“nonceStr”:(“#nonceStr”).val(),   
                            “package”:
(“#packageValue”).val(),
“signType”:”MD5”,
“paySign”:$(“#paySign”).val()
},function(res){
WeixinJSBridge.log(res.err_msg);
if(res.err_msg == “get_brand_wcpay_request:ok”){
alert(“支付成功”);
}else if(res.err_msg == “get_brand_wcpay_request:cancel”){
alert(“取消支付!”)

    }else{  
         alert("支付失败!")
    }  

到此,所有需要的代码都已提供,各位在借鉴的时候,记得也要做些内容修改,ConfigUtil中的一些参数,需要各位自行提供。其它的都可以直接使用,微信支付整体并不难,难点就在于微信签名,很多人调试微信支付,都被这个微信签名给难倒了,这也是微信团队的留下的一天坑。

**最后一句:如果各位在借鉴中,发现缺失什么,可以私信找我要,也可以留言。如果觉得我写的好,就来犒劳我一下吧Java实现公众号H5 微信支付_第1张图片

你可能感兴趣的:(springmvc,java,微信,H5微信支付,公众号微信支付,java,微信,支付)