二、環境需求
開通微信服務號需要下面的信息:
appid
appsecret
商業號
商戶平臺配置API
需要相關的jar包:
core-3.3.0.jar (Google 二維碼生成相關jar包)(也可以將微信生成的鏈接返回給前端,前端生成二維碼)
三、實現
3.0 流程介紹
商戶後臺系統先調用微信支付的統一下單接口,微信後臺系統返回鏈接參數code_url,商戶後臺系統將code_url值生成二維碼圖片,用戶使用微信客戶端掃碼後發起支付。注意:code_url有效期爲2小時,過期後掃碼不能再發起支付。【引用自微信開發文檔】
業務流程說明:
(1)商戶後臺系統根據用戶選購的商品生成訂單。
(2)用戶確認支付後調用微信支付【統一下單API】生成預支付交易;
(3)微信支付系統收到請求後生成預支付交易單,並返回交易會話的二維碼鏈接code_url。
(4)商戶後臺系統根據返回的code_url生成二維碼。
(5)用戶打開微信“掃一掃”掃描二維碼,微信客戶端將掃碼內容發送到微信支付系統。
(6)微信支付系統收到客戶端請求,驗證鏈接有效性後發起用戶支付,要求用戶授權。
(7)用戶在微信客戶端輸入密碼,確認支付後,微信客戶端提交授權。
(8)微信支付系統根據用戶授權完成支付交易。
(9)微信支付系統完成支付交易後給微信客戶端返回交易結果,並將交易結果通過短信、微信消息提示用戶。微信客戶端展示支付交易結果頁面。
(10)微信支付系統通過發送異步消息通知商戶後臺系統支付結果。商戶後臺系統需回覆接收情況,通知微信後臺系統不再發送該單的支付通知。
(11)未收到支付通知的情況,商戶後臺系統調用【查詢訂單API】。
(12)商戶確認訂單已支付後給用戶發貨。
3.1 微信方面
歡迎大家關注“大米時代官微”,會推薦很多很火的技術文章。
3.1 建立一個web項目
小編使用的是Eclipse,打開Eclipse –> File –> new –> Dynamic Web Project,建立一個名字爲WCPay的web項目。然後在WebRoot –> WEB-INF –> lib下添加小編上面的jar包。
3.2 掃碼頁面建立index.jsp
建立一下jsp頁面,用於生成訂單費用的二維碼,供用戶掃描:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<h1>掃碼支付:</h1>
<img src="${pageContext.request.contextPath}/PayServelt" />
</body>
</html>
3.3 建立PayServlet
在PayServlet.java中,doPost方法調用doGet方法;doGet方法用於調用weixinPay方法,生成二維碼。用戶掃描後,支付後,自動調用weixinPay方法配置的回調方法。
建立一個PayServelt,用於向微信的“統一下單”接口發送信息。然後收到返回的信息。
PayServelt:
package com.dmsd.wechat.pay;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.dmsd.wechat.util.HttpUtil;
import com.dmsd.wechat.util.PayCommonUtil;
import com.dmsd.wechat.util.PayConfigUtil;
import com.dmsd.wechat.util.XMLUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
/**
* 支付的servlet-2017年10月4日
* @author Ares
*/
public class PayServelt extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* 調用微信支付的接口
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
//String productId = request.getParameter("productId");
String productId = "5533675";
String userId = "user01";
String text = weixinPay(userId, productId);
System.out.println("********************成功獲取二維碼url****************************************");
//根據url來生成生成二維碼
int width = 300;
int height = 300;
//二維碼的圖片格式
String format = "gif";
Hashtable hints = new Hashtable();
//內容所使用編碼
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
BitMatrix bitMatrix;
try {
bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);
MatrixToImageWriter.writeToStream(bitMatrix, format, response.getOutputStream());
} catch (WriterException e) {
e.printStackTrace();
}
} catch (Exception e) {
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
/**
* 調用微信統一支付接口-王雷-2017年10月4日23:18:04
* @param userId 用戶id
* @param productId 商品id
* @return
* @throws Exception
*/
public static String weixinPay(String userId, String productId) throws Exception {
// 賬號信息
String appid = PayConfigUtil.appid; // appid
String appsecret = PayConfigUtil.APP_SECRET; // appsecret
String mch_id = PayConfigUtil.MCH_ID; // 商業號
String key = PayConfigUtil.API_KEY; // key
String currTime = PayCommonUtil.getCurrTime(); //獲取當前時間
String strTime = currTime.substring(8, currTime.length());
String strRandom = PayCommonUtil.buildRandom(4) + ""; //取出一個指定長度大小的隨機正整數
String nonce_str = strTime + strRandom;
String order_price = "1"; // 價格 注意:價格的單位是分
String body = "可樂"; // 商品名稱
String out_trade_no = "789456"; // 訂單號
//獲取發起電腦 ip
String spbill_create_ip = PayConfigUtil.CREATE_IP;
//回調接口
String notify_url = PayConfigUtil.NOTIFY_URL;
String trade_type = "NATIVE";
SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();
packageParams.put("appid", appid);
packageParams.put("mch_id", mch_id);
packageParams.put("nonce_str", nonce_str);
packageParams.put("body", body);
packageParams.put("out_trade_no", out_trade_no);
packageParams.put("total_fee", order_price);
packageParams.put("spbill_create_ip", spbill_create_ip);
packageParams.put("notify_url", notify_url);
packageParams.put("trade_type", trade_type);
String sign = PayCommonUtil.createSign("UTF-8", packageParams,key);
packageParams.put("sign", sign);
String requestXML = PayCommonUtil.getRequestXml(packageParams);
System.out.println(requestXML);
String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML);
System.out.println(resXml);
Map map = XMLUtil.doXMLParse(resXml);
//String return_code = (String) map.get("return_code");
//String prepay_id = (String) map.get("prepay_id");
String urlCode = (String) map.get("code_url");
return urlCode;
}
}
3.4 工具類
在PayServelt的weixinPay方法中,用到了HttpUtil、MD5Util、PayCommonUtil、PayConfigUtil、XMLUtil 五個相關的方法類。
PayCommonUtil:
package com.dmsd.wechat.util;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
public class PayCommonUtil {
/**
* 是否簽名正確,規則是:按參數名稱a-z排序,遇到空值的參數不參加簽名。
* @return boolean
*/
public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.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(!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
//算出摘要
String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();
String tenpaySign = ((String)packageParams.get("sign")).toLowerCase();
//System.out.println(tenpaySign + " " + mysign);
return tenpaySign.equals(mysign);
}
/**
* @author
* @date 2016-4-22
* @Description:sign簽名
* @param characterEncoding
* 編碼格式
* @param parameters
* 請求參數
* @return
*/
public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.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 (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* @author
* @date 2016-4-22
* @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();
}
/**
* 取出一個指定長度大小的隨機正整數.
*
* @param length
* int 設定所取出隨機數的長度。length小於11
* @return int 返回生成的隨機數。
*/
public static int buildRandom(int length) {
int num = 1;
double random = Math.random();
if (random < 0.1) {
random = random + 0.1;
}
for (int i = 0; i < length; i++) {
num = num * 10;
}
return (int) ((random * num));
}
/**
* 獲取當前時間 yyyyMMddHHmmss
*
* @return String
*/
public static String getCurrTime() {
Date now = new Date();
SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String s = outFormat.format(now);
return s;
}
}
HttpUtil:
package com.dmsd.wechat.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
public class HttpUtil {
//private static final Log logger = Logs.get();
private final static int CONNECT_TIMEOUT = 5000; // in milliseconds
private final static String DEFAULT_ENCODING = "UTF-8";
public static String postData(String urlStr, String data){
return postData(urlStr, data, null);
}
public static String postData(String urlStr, String data, String contentType){
BufferedReader reader = null;
try {
URL url = new URL(urlStr);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(CONNECT_TIMEOUT);
if(contentType != null)
conn.setRequestProperty("content-type", contentType);
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);
if(data == null)
data = "";
writer.write(data);
writer.flush();
writer.close();
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\r\n");
}
return sb.toString();
} catch (IOException e) {
//logger.error("Error connecting to " + urlStr + ": " + e.getMessage());
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
}
}
return null;
}
}
MD5Util:
package com.dmsd.wechat.util;
import java.security.MessageDigest;
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" };
}
PayConfigUtil:
package com.dmsd.wechat.util;
public class PayConfigUtil {
// 賬號信息
public static String appid = "********"; // appid 填寫自己的
public static String APP_SECRET = "********"; // appsecret 填寫自己的
public static String MCH_ID = "********"; // 商業號 填寫自己微信的
public static String API_KEY ="*********"; // Api key 填寫自己微信的
public static String CREATE_IP ="192.168.21.95"; //ip地址
//public static String NOTIFY_URL ="http://www.weixin.qq.com/wxpay/pay.php"; //回調url,這個是微信自己的回調接口,小編測試使用
public static String NOTIFY_URL ="http://tfjybj.com/WCPay/"; //回調url
public static String UFDODER_URL ="https://api.mch.weixin.qq.com/pay/unifiedorder"; //統一下單url,微信提供的
}
XMLUtil:
package com.dmsd.wechat.util;
import java.io.ByteArrayInputStream;
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;
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();
}
}
3.5 用戶支付後,回調方法
異步接收微信支付結果通知的回調地址,通知url必須爲外網可訪問的url,不能攜帶參數。
小編通過阿里雲來進行外網訪問,然後通過Nginx來做反向代理,映射內網地址。回調地址是訪問一個Servlet,通過這個Servlet來解析返回的信息,根據自己的業務在Servlet中做自己的業務,比如把信息存儲到數據庫,或者其他的操作。
NotifyServlet:
package com.dmsd.wechat.pay;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jdom.JDOMException;
import com.dmsd.wechat.util.PayCommonUtil;
import com.dmsd.wechat.util.PayConfigUtil;
import com.dmsd.wechat.util.XMLUtil;
/**
* 微信支付回調url,調用這個方法,獲取返回的是否支付成功的數據-王雷-2017年10月5日08:05:26
* @author Ares
*
*/
public class NotifyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// request.setAttribute("test", "跳轉成功");
// // 信息界面顯示
// request.getRequestDispatcher("/result.jsp").forward(request, response);
//讀取參數
InputStream inputStream ;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String s ;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null){
sb.append(s);
}
in.close();
inputStream.close();
//解析xml成map
Map<String, String> m = new HashMap<String, String>();
try {
m = XMLUtil.doXMLParse(sb.toString());
} catch (JDOMException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//過濾空 設置 TreeMap
SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();
Iterator it = m.keySet().iterator();
while (it.hasNext()) {
String parameter = (String) it.next();
String parameterValue = m.get(parameter);
String v = "";
if(null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
// 賬號信息
String key = PayConfigUtil.API_KEY; //key
//判斷簽名是否正確
if(PayCommonUtil.isTenpaySign("UTF-8", packageParams,key)) {
//------------------------------
//處理業務開始
//------------------------------
String resXml = "";
if("SUCCESS".equals((String)packageParams.get("result_code"))){
// 這裏是支付成功
//////////執行自己的業務邏輯////////////////
String mch_id = (String)packageParams.get("mch_id");
String openid = (String)packageParams.get("openid");
String is_subscribe = (String)packageParams.get("is_subscribe");
String out_trade_no = (String)packageParams.get("out_trade_no");
String total_fee = (String)packageParams.get("total_fee");
//////////執行自己的業務邏輯////////////////
//暫時使用最簡單的業務邏輯來處理:只是將業務處理結果保存到session中
//(根據自己的實際業務邏輯來調整,很多時候,我們會操作業務表,將返回成功的狀態保留下來)
request.getSession().setAttribute("_PAY_RESULT", "OK");
System.out.println("支付成功");
//通知微信.異步確認成功.必寫.不然會一直通知後臺.八次之後就認爲交易失敗了.
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> ";
}
//------------------------------
//處理業務完畢
//------------------------------
BufferedOutputStream out = new BufferedOutputStream(
response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
} else{
System.out.println("通知簽名驗證失敗");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
四、運行結果
在瀏覽器中輸入:http://localhost:8080/WCPay/index.jsp ,就會在網頁顯示生成的二維碼:
用手機掃描後,就可以進行支付了,這個支付的流程相信大家都很熟悉了:
賬單顯示:在微信的商戶平臺中,就會添加交易記錄:
五、小結
小編想吐槽一下,申請微信支付的時候,各種提交的信息問題,聯繫客服。有一個是剛提交了資料,等微信給小編打一筆錢,作爲驗證碼。一直遲遲沒有打過來,其實是小編的信息填錯了。所以,還是要多多的研究公司的一些信息。謝謝大家。
轉載: https://blog.csdn.net/kisscatforever/article/details/78159015