一 :微信支付基本流程
商户系统和微信支付系统主要交互说明:
步骤1:用户在商户APP中选择商品,提交订单,选择微信支付。
步骤2:商户后台收到用户支付单,调用微信支付统一下单接口。参见【统一下单API】。
步骤3:统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appid,partnerid,prepayid,noncestr,timestamp,package。注意:package的值格式为Sign=WXPay
步骤4:商户APP调起微信支付。api参见【app端开发步骤说明】
步骤5:商户后台接收支付通知。api参见【支付结果通知API】
步骤6:商户后台查询支付结果。,api参见【查询订单API】
二 :具体准备步骤
1:填写微信支付的相关信息和app信息,等待审核通过
2:获取 APPID (app 应用id)
3 :申请成功后获取商户号
商户号及密码会发送到申请时的邮箱上
4:登录 微信商户平台 设置API_KEY(API密钥)和下载证书
5:在服务器上安装API证书
三 : 具体demo 代码
1 :工具类
import java.util.Date;
/**
*
* @author jiangbo_lin * @2018-09-14 * @desc:日期工具 */ public class DateUtils { /** * 获取当前系统的时间戳 * * @return */ public static String getCurrentTimestamp() { long timeStamp = new Date().getTime(); return String.valueOf(timeStamp); } public static String getCurrentTimestamp10() { long timeStamp = new Date().getTime() / 1000; String timestr = String.valueOf(timeStamp); return timestr; } public static String getTimeStamp() { int time = (int) (System.currentTimeMillis() / 1000); return String.valueOf(time); } }
import java.util.*;
import java.util.Map.Entry;
/** *@author jiangbo_lin * 2018-09-18 * @desc:对map的key进行ASCII排序 */ public class MapUtils { /** * 对map根据key进行排序 ASCII 顺序 * * @param map 无序的map * @return */ public static SortedMap<String, Object> sortMap(Map<String, Object> map) { List<Entry<String, Object>> infoIds = new ArrayList<Entry<String, Object>>(map.entrySet()); // 排序 Collections.sort(infoIds, new Comparator<Entry<String, Object>>() { public int compare(Entry<String, Object> o1, Entry<String, Object> o2) { // return (o2.getValue() - o1.getValue());//value处理 return (o1.getKey()).toString().compareTo(o2.getKey()); } }); // 排序后 SortedMap<String, Object> sortmap = new TreeMap<String, Object>(); for (int i = 0; i < infoIds.size(); i++) { Entry<String, Object> data = infoIds.get(i); String key = data.getKey() ; Object value = data.getValue(); if(value==null){ sortmap.put(data.getKey(), data.getValue()); }else{ sortmap.put(data.getKey(),data.getValue().toString()); } } return sortmap; } /** * map to String * * @param map * @return */ public static String toString(Map<String, Object> map) { StringBuffer buf = new StringBuffer(); buf.append("{"); Iterator<Entry<String, Object>> i = map.entrySet().iterator(); boolean hasNext = i.hasNext(); while (hasNext) { Entry<String, Object> e = i.next(); Object key = e.getKey(); Object value = e.getValue(); if (key == MapUtils.class) buf.append("(this Map)"); else buf.append(key); buf.append("="); if (value == MapUtils.class) buf.append("(this Map)"); else buf.append(value); hasNext = i.hasNext(); if (hasNext) buf.append(", "); } buf.append("}"); return buf.toString(); } }
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; /** * * @desc:XML 解析工具 */ @SuppressWarnings("all") public class XMLUtil { /** * 解析xml,返回第一级元素键值对。 * 如果第一级元素有子节点, * 则此节点的值是子节点的xml数据。 * * @param strxml * @return * @throws JDOMException * @throws IOException */ public static SortedMap<String, Object> doXMLParse(String strxml) throws JDOMException, IOException { strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\""); if (null == strxml || "".equals(strxml)) { return null; } SortedMap<String, Object> map = new TreeMap<String, Object>(); 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 key = e.getName(); String value = ""; List children = e.getChildren(); if (children.isEmpty()) { value = e.getTextNormalize(); } else { value = XMLUtil.getChildrenText(children); } map.put(key, value); } // 关闭流 in.close(); return map; } /** * 获取子结点的xml * @param children * @return */ 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(); } }
import java.security.MessageDigest;
/**
*
* @author jiangbo_lin * @2018/-09-14 * @desc:md5的工具类 */ 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" }; }
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; /** * * @author jiangbo_lin * @2018-09-14 * @desc:信任管理中心 */ public class FsTrustManager 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; } }
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.xml.parsers.ParserConfigurationException; import java.io.*; import java.net.ConnectException; import java.net.URL; import java.security.KeyStore; import java.text.SimpleDateFormat; import java.util.*; public class WeixinPayUtil { //获取日志记录器Logger,名字为本类类名 private static Logger logger = LoggerFactory.getLogger(WeixinPayUtil.class); /** * 生成随机数的方法 * * @return String 随机数 */ public static String generateNonceStr() { 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 String 签名字符串 */ public static String createSign(String characterEncoding, SortedMap<String, Object> parameters) { StringBuffer sb = new StringBuffer(); Iterator<Map.Entry<String, Object>> it = parameters.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it.next(); String key = (String) entry.getKey(); Object value = entry.getValue();//去掉带sign的项 if (null != value && !"".equals(value) && !"sign".equals(key) && !"key".equals(key)) { sb.append(key + "=" + value + "&"); } } sb.append("key=" + WeixinConfig.API_KEY); //注意sign转为大写 return MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); } /** * @param parameters 请求参数 * @return String xml格式的string * @Description:将请求参数转换为xml格式的string */ public static String getRequestXml(SortedMap<String, Object> parameters) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Iterator<Map.Entry<String, Object>> iterator = parameters.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Object> entry = iterator.next(); String key = entry.getKey(); String value = (String) entry.getValue(); if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key) || "sign".equalsIgnoreCase(key)) { sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">"); } else { sb.append("<" + key + ">" + value + "</" + key + ">"); } } sb.append("</xml>"); return sb.toString(); } /** * @param return_code 返回编码 * @param return_msg 返回信息 * @return * @Description:返回给微信的参数 */ 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>"; } /** * 发送https请求 * * @param requestUrl 请求地址 * @param requestMethod 请求方式(GET、POST) * @param outputStr 提交的数据 * @return 返回微信服务器响应的信息 * @throws Exception */ public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) throws Exception { try { // 创建SSLContext对象,并使用我们指定的信任管理器初始化 TrustManager[] tm = {new FsTrustManager()}; 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) { logger.error("【微信支付-连接超时】:{}", ce); throw new RuntimeException("链接异常" + ce); } catch (Exception e) { logger.error("微信支付-https请求异常】:{}", e); throw new RuntimeException("https请求异常" + e); } } /** * https双向签名认证,用于支付申请退款 */ public static String payHttps(String url, String requestMethod, String xml) throws Exception { //