Java实现公众号H5 微信支付

来源:互联网 发布:mac微信接收的文件 编辑:程序博客网 时间:2024/04/30 23:37

最近由于有微信项目使用到了微信支付这功能,我原本以为跟调用其它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<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=" + 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<Object,Object> parameters){    StringBuffer sb = new StringBuffer();    sb.append("<xml>");    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+">"+"<![CDATA["+v+"]]></"+k+">");        }else {            sb.append("<"+k+">"+v+"</"+k+">");        }    }    sb.append("</xml>");    return sb.toString();}/** * @author Mark * @Description:返回给微信的参数 * @param return_code 返回编码 * @param return_msg  返回信息 * @return */public static String setXML(String return_code, String return_msg) {    return "<xml><return_code><![CDATA[" + return_code            + "]]></return_code><return_msg><![CDATA[" + return_msg            + "]]></return_msg></xml>";}


第六步:调用微信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<Object,Object> parameters = new TreeMap<Object,Object>();    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<String, String> map=new HashMap<String, String>();    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("</" + name + ">");        }    }    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<Object,Object> signParams = new TreeMap<Object,Object>();      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中的一些参数,需要各位自行提供。其它的都可以直接使用,微信支付整体并不难,难点就在于微信签名,很多人调试微信支付,都被这个微信签名给难倒了,这也是微信团队的留下的一天坑。

出处:http://blog.csdn.net/kiss_mark/article/details/52671494

0 0
原创粉丝点击