前段時間給了了需求對公司網站添加微信支付,由於之前沒接觸過,簡單的東西愣是寫了好幾天。話不多少,直接開始。
首先你需要先看微信的官方文檔https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_1,這裏面幾乎介紹了全部流程了。等你瞭解大概流程之後,需要在微信公衆平臺和微信商戶平臺拿到或者配置一下參數
appid:公衆平臺的appid
商戶號:公衆平臺的商戶號
appsecrut:商戶平臺裏的簽名密鑰(17年8月開始移到商戶平臺了,這個要保存好,現在不支持查看,忘了就得重新配置了)
最後在商戶平臺上圖這個位置配置h5支付的域名(需要通過備案的域名,外網能直接訪問)
拿到以上數據後,就開始寫代碼吧。首先下單
@ResponseBody
@RequestMapping(value = "/pay" ,produces = { "application/json;charset=UTF-8" })
public String weixinPayWap(HttpServletRequest request, HttpServletResponse response, ModelMap model) {
String APPID = "你的apid";
String MERID = "你的商戶號";
String SIGNKEY = "你的商戶密鑰";
String spbill_create_ip = getIpAddr(request);//生產
System.out.println("spbill_create_ip="+spbill_create_ip);
//String spbill_create_ip = "";//測試地址,也就是本地真是ip,用於本地測試用
String scene_info = "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"這裏寫在h5支付配置的那個域名\",\"wap_name\": \"信息認證\"}}";//我這裏是網頁入口,app入口參考文檔的安卓和ios寫法
String tradeType = "MWEB";//H5支付標記
String MD5 = "MD5";//雖然官方文檔不是必須參數,但是不送有時候會驗籤失敗
JSONObject result = new JSONObject();
String subject = request.getParameter("subject");//前端上送的支付主題
String total_amount = request.getParameter("totalAmount");//前端上送的支付金額
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
//金額轉化爲分爲單位 微信支付以分爲單位
String finalmoney = StringUtils.getMoney(total_amount);
int randomNum = (int) (Math.random() * 1999+5000);
String out_trade_no = TimeUtils.getSysTime("yyyyMMddHHmmss") + randomNum;
//隨機數
String nonce_str= MD5Utils.getMessageDigest(String.valueOf(new Random().nextInt(10000)).getBytes());
//簽名數據
StringBuilder sb = new StringBuilder();
sb.append("appid="+APPID);
sb.append("&body="+subject);
sb.append("&mch_id="+MERID);
sb.append("&nonce_str="+nonce_str);
sb.append("¬ify_url="+"這裏寫回調地址");
sb.append("&out_trade_no="+out_trade_no);
sb.append("&scene_info="+scene_info);
sb.append("&sign_type="+"MD5");
sb.append("&spbill_create_ip="+spbill_create_ip);
sb.append("&total_fee="+finalmoney);
sb.append("&trade_type="+tradeType);
sb.append("&key="+SIGNKEY);
System.out.println("sb="+sb);
//簽名MD5加密
String sign = "把sb.toString()做MD5操作並且toUpperCase()一下,至於怎麼MD5,百度一下或者看官方文檔";
System.out.println("sign="+sign);
log.info("簽名數據:"+sign);
//封裝xml報文
String xml="<xml>"+
"<appid>"+ APPID+"</appid>"+
"<mch_id>"+ MERID+"</mch_id>"+
"<nonce_str>"+nonce_str+"</nonce_str>"+
"<sign>"+sign+"</sign>"+
"<body>"+subject+"</body>"+//
"<out_trade_no>"+out_trade_no+"</out_trade_no>"+
"<total_fee>"+finalmoney+"</total_fee>"+//
"<trade_type>"+tradeType+"</trade_type>"+
"<notify_url>"+"這裏寫回調地址"+"</notify_url>"+
"<sign_type>MD5</sign_type>"+
"<scene_info>"+scene_info+"</scene_info>"+
"<spbill_create_ip>"+spbill_create_ip+"</spbill_create_ip>"+
"</xml>";
String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";//微信統一下單接口
String mweb_url = "";
Map map = new HashMap();
try {
//預下單 獲取接口地址
map = WebUtils.getMwebUrl(createOrderURL, xml);
String return_code = (String) map.get("return_code");
String return_msg = (String) map.get("return_msg");
if("SUCCESS".equals(return_code) && "OK".equals(return_msg)){
mweb_url = (String) map.get("mweb_url");//調微信支付接口地址
System.out.println("mweb_url="+mweb_url);
}else{
System.out.println("統一支付接口獲取預支付訂單出錯");
result.put("msg", "支付錯誤");
return result.toString();
}
} catch (Exception e) {
System.out.println("統一支付接口獲取預支付訂單出錯");
result.put("msg", "支付錯誤");
return result.toString();
}
result.put("mwebUrl",mweb_url);
//添加微信支付記錄日誌等操作
result.put("msg", "success");
return result.toString();
}
/**
* 獲取用戶實際ip
* @param request
* @return
*/
public String getIpAddr(HttpServletRequest request){
String ipAddress = request.getHeader("x-forwarded-for");
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
//根據網卡取本機配置的IP
InetAddress inet=null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress= inet.getHostAddress();
}
}
//對於通過多個代理的情況,第一個IP爲客戶端真實IP,多個IP按照','分割
if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
if(ipAddress.indexOf(",")>0){
ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
}
}
return ipAddress;
}
如果簽名驗證失敗就在https://pay.weixin.qq.com/wiki/tools/signverify/調試下簽名 記住上送xml裏的全都要進行簽名,並且排列順序要按ASCLL碼的順序從小到大(好像是這樣,可以看官方文檔的簽名方式)
接下來是回調
@RequestMapping(value = "/notify")
public void weixinPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
BufferedReader reader = request.getReader();
String line = "";
Map map = new HashMap();
String xml = "<xml><return_code><![CDATA[FAIL]]></xml>";;
JSONObject dataInfo = new JSONObject();
StringBuffer inputString = new StringBuffer();
while ((line = reader.readLine()) != null) {
inputString.append(line);
}
request.getReader().close();
System.out.println("----接收到的報文---"+inputString.toString());
if(inputString.toString().length()>0){
map = XMLUtils.parseXmlToList(inputString.toString());
}else{
System.out.println("接受微信報文爲空");
}
System.out.println("map="+map);
if(map!=null && "SUCCESS".equals(map.get("result_code"))){
//成功的業務。。。
xml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}else{
//失敗的業務。。。
}
//告訴微信端已經確認支付成功
response.getWriter().write(xml);
}
接下來是工具類
public class WebUtils {
public static Map getMwebUrl(String url,String xmlParam){
String jsonStr = null;
HttpClient httpClient = new HttpClient();
Map map = new HashMap();
try {
PostMethod method = null;
RequestEntity reqEntity = new StringRequestEntity(xmlParam,"text/json","UTF-8");
method = new PostMethod(url);
method.setRequestEntity(reqEntity);
method.addRequestHeader("Content-Type","application/json;charset=utf-8");
httpClient.executeMethod(method);
StringBuffer resBodyBuf = new StringBuffer();
byte[] responseBody = new byte[1024];
int readCount = 0;
BufferedInputStream is = new BufferedInputStream(method.getResponseBodyAsStream());
while((readCount = is.read(responseBody,0,responseBody.length))!=-1){
resBodyBuf.append(new String(responseBody,0,readCount,"utf-8"));
}
jsonStr = resBodyBuf.toString();
System.out.println(jsonStr);
map = XMLUtils.parseXmlToList(jsonStr);
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
public class XMLUtils {
/**
* description: 解析微信通知xml
*
* @param xml
* @return
* @author ex_yangxiaoyi
* @see
*/
@SuppressWarnings({ "unused", "rawtypes", "unchecked" })
public static Map parseXmlToList(String xml) {
Map retMap = new HashMap();
try {
StringReader read = new StringReader(xml);
// 創建新的輸入源SAX 解析器將使用 InputSource 對象來確定如何讀取 XML 輸入
InputSource source = new InputSource(read);
// 創建一個新的SAXBuilder
SAXBuilder sb = new SAXBuilder();
// 通過輸入源構造一個Document
Document doc = (Document) sb.build(source);
Element root = doc.getRootElement();// 指向根節點
List<Element> es = root.getChildren();
if (es != null && es.size() != 0) {
for (Element element : es) {
retMap.put(element.getName(), element.getValue());
}
}
} catch (Exception e) {
e.printStackTrace();
}
return retMap;
}
}
public class TimeUtils {
/**
* 取得系統時間
* @param pattern eg:yyyy-MM-dd HH:mm:ss,SSS
* @return
*/
public static String getSysTime(String pattern) {
return formatSysTime(new SimpleDateFormat(pattern));
}
/**
* 格式化系統時間
* @param format
* @return
*/
private static String formatSysTime(SimpleDateFormat format) {
String str = format.format(Calendar.getInstance().getTime());
return str;
}
}
public class StringUtils {
/**
* 元轉換成分
* @param money
* @return
*/
public static String getMoney(String amount) {
if(amount==null){
return "";
}
// 金額轉化爲分爲單位
String currency = amount.replaceAll("\\$|\\¥|\\,", ""); //處理包含, ¥ 或者$的金額
int index = currency.indexOf(".");
int length = currency.length();
Long amLong = 0l;
if(index == -1){
amLong = Long.valueOf(currency+"00");
}else if(length - index >= 3){
amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));
}else if(length - index == 2){
amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);
}else{
amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");
}
return amLong.toString();
}
}
public class MD5Utils {
public final static String getMessageDigest(byte[] buffer) {
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
try {
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(buffer);
byte[] md = mdTemp.digest();
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
return null;
}
}
}
前端頁面調用返回的地址就行了
location.href = data.mwebUrl;//這應該看的懂吧 ajax發微信下單請求,返回一個地址直接調用,注意要在手機裏調用,不然會報商戶格式錯誤。
整個微信h5支付就這樣了,應該沒有遺漏了吧。如果會的話代碼其實很簡單,親測可以。本地測試最多能到下單成功返回mwebUrl這步,支付和回調必須線上測試,如果怕出問題可以寫一個模擬頁面(只有你知道地址)打到線上真是環境測試。
下篇再寫了公衆號支付的文章,公衆號支付和h5支付大體相同,就是多了獲取用戶openid和前端需要調用微信js接口。