微信官方文檔:https://pay.weixin.qq.com/wiki/doc/api/index.html
MD5Util.java
package weixin;
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" };
}
RandomUtil.java
package weixin;
import java.util.Random;
public class RandomUtil {
private static char ch[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', '0', '1' };//最后又重復兩個0和1,因為需要湊足數組長度為64
private static Random random = new Random();
//生成指定長度的隨機字符串
public static synchronized String createRandomString(int length) {
if (length > 0) {
int index = 0;
char[] temp = new char[length];
int num = random.nextInt();
for (int i = 0; i < length % 5; i++) {
temp[index++] = ch[num & 63];//取后面六位,記得對應的二進制是以補碼形式存在的。
num >>= 6;//63的二進制為:111111
// 為什么要右移6位?因為數組里面一共有64個有效字符。為什么要除5取余?因為一個int型要用4個字節表示,也就是32位。
}
for (int i = 0; i < length / 5; i++) {
num = random.nextInt();
for (int j = 0; j < 5; j++) {
temp[index++] = ch[num & 63];
num >>= 6;
}
}
return new String(temp, 0, length);
}
else if (length == 0) {
return "";
}
else {
throw new IllegalArgumentException();
}
}
}
SignUtil.java
package weixin;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
public class SignUtil {
/**
* 微信支付簽名算法sign
* @param parameters
* @return
*/
@SuppressWarnings("unchecked")
public static String createSign(SortedMap<Object,Object> parameters,String key){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有參與傳參的參數按照accsii排序(升序)
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=" + key);
String sign = MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
return sign;
}
/**
* 與接口配置信息中的 token 要一致,這里賦予什么值,在接口配置信息中的Token就要填寫什么值,
* 兩邊保持一致即可,建議用項目名稱、公司名稱縮寫等,我在這里用的是項目名稱weixinface
*/
private static String token = "HR9QhjKMCoUQlwd";
/**
* 驗證簽名
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce){
String[] arr = new String[]{token, timestamp, nonce};
// 將 token, timestamp, nonce 三個參數進行字典排序
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for(int i = 0; i < arr.length; i++){
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 將三個參數字符串拼接成一個字符串進行 shal 加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
content = null;
// 將sha1加密后的字符串可與signature對比,標識該請求來源於微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()): false;
}
/**
* 將字節數組轉換為十六進制字符串
* @param digest
* @return
*/
private static String byteToStr(byte[] digest) {
// TODO Auto-generated method stub
String strDigest = "";
for(int i = 0; i < digest.length; i++){
strDigest += byteToHexStr(digest[i]);
}
return strDigest;
}
/**
* 將字節轉換為十六進制字符串
* @param b
* @return
*/
private static String byteToHexStr(byte b) {
// TODO Auto-generated method stub
char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] tempArr = new char[2];
tempArr[0] = Digit[(b >>> 4) & 0X0F];
tempArr[1] = Digit[b & 0X0F];
String s = new String(tempArr);
return s;
}
}
XmlPostUtil.java
package weixin;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class XmlPostUtil {
public static byte[] sendXmlRequest(String path, String params) throws Exception {
URL url = new URL(path);
System.out.println("發送xml:" + params);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");// 提交模式
conn.setDoOutput(true);// 是否輸入參數
conn.setRequestProperty("Pragma:", "no-cache");
conn.setRequestProperty("Cache-Control", "no-cache");
conn.setRequestProperty("Content-Type", "text/xml");
byte[] bypes = params.toString().getBytes("UTF-8");
conn.getOutputStream().write(bypes);// 輸入參數
InputStream inStream = conn.getInputStream();
return readInputStream(inStream);
}
public static byte[] readInputStream(InputStream inStream) throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
byte[] data = outStream.toByteArray();//網頁的二進制數據
outStream.close();
inStream.close();
System.out.println(new String(data, "utf-8"));
return data;
}
}
XMLUtil.java
package weixin;
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.*;
/**
*xml工具類
*
*/
public class XMLUtil {
/**
* 解析xml,返回第一級元素鍵值對。如果第一級元素有子節點,則此節點的值是子節點的xml數據。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws JDOMException, IOException {
if(null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap();
InputStream in = String2Inputstream(strxml);
SAXBuilder builder = new SAXBuilder();
/**********************修復部分內容*********************/
String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
builder.setFeature(FEATURE, true);
FEATURE = "http://xml.org/sax/features/external-general-entities";
builder.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
builder.setFeature(FEATURE, false);
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
builder.setFeature(FEATURE, false);
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();
}
public static InputStream String2Inputstream(String str) {
return new ByteArrayInputStream(str.getBytes());
}
public static String mapToXml(SortedMap<Object, Object> sortedMap){
StringBuffer sb = new StringBuffer("<xml>");
Iterator iterator = sortedMap.keySet().iterator();
while (iterator.hasNext()) {
Object key = (String) iterator.next();
Object value = sortedMap.get(key);
sb.append("<"+key+">");
sb.append("<![CDATA["+value+"]]>");
sb.append("</"+key+">");
}
sb.append("</xml>");
return sb.toString();
}
}
PaymentConfig.java
package weixin;
public class PaymentConfig {
/*******微信支付參數*********/
//公眾賬號ID
public static final String appid="";
//密鑰
public static final String appKey="";
//商戶號
public static final String mch_id="";
//接口地址
public static final String pay_url="https://api.mch.weixin.qq.com/pay/unifiedorder";
//支付返回地址
public static final String wxRetrun="";
//交易場景信息 具體參照微信官方文檔不同支付類型的寫法
public static final String scene_info = "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"https://pay.qq.com\",\"wap_name\": \"微信支付\"}} ";
public static final int ENROLL_PRICE = 200;
}
WeChatPay.java
package weixin; import com.payment.util.RandomUtil; import com.payment.util.SignUtil; import com.payment.util.XMLUtil; import com.payment.util.XmlPostUtil; import org.jdom.JDOMException; import java.io.IOException; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; public class WeChatPay { /** * 二維碼支付 * @param orderNo * @param money * @param body * @param ip * @return */ public Map getPaymentMapCode(String orderNo,int money,String body,String ip,String wxReturn){ //一次簽名 SortedMap<Object, Object> paramMap = new TreeMap<Object, Object>(); paramMap.put("appid",PaymentConfig.appid);//公眾號ID paramMap.put("mch_id",PaymentConfig.mch_id);//商戶號 paramMap.put("nonce_str", RandomUtil.createRandomString(32));//32位隨機字符串 paramMap.put("body",body);//商品描述 paramMap.put("out_trade_no",orderNo);//商戶訂單號 paramMap.put("total_fee",String.valueOf(money));//設置交易金額 金額為分 //paramMap.put("total_fee",1);//設置交易金額 金額為分 paramMap.put("spbill_create_ip",ip);//客戶機IP paramMap.put("notify_url",wxReturn);//通知地址 paramMap.put("trade_type","NATIVE");//支付方式 原生掃碼 paramMap.put("product_id", "shangpingid"); //自行定義 paramMap.put("sign", SignUtil.createSign(paramMap, PaymentConfig.appKey)); String rXml = ""; String prepayid=""; try { rXml = new String(XmlPostUtil.sendXmlRequest(PaymentConfig.pay_url, XMLUtil.mapToXml(paramMap))); prepayid = (String) XMLUtil.doXMLParse(rXml).get("prepay_id");//得到預支付id } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //二次簽名 SortedMap<Object, Object> paramMap1 = new TreeMap<Object, Object>(); paramMap1.put("appId", PaymentConfig.appid); paramMap1.put("timeStamp", System.currentTimeMillis()); paramMap1.put("package", "prepay_id="+prepayid); paramMap1.put("signType", "MD5"); paramMap1.put("nonceStr", RandomUtil.createRandomString(32)); paramMap1.put("paySign", SignUtil.createSign(paramMap1, PaymentConfig.appKey)); try { Map map = XMLUtil.doXMLParse(rXml); System.out.println("return_code:"+map.get("return_code")); System.out.println("code_url:"+map.get("code_url")); return map; } catch (JDOMException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return paramMap1; } //通知微信正確接收 public static String getSuccessXml() { String xml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml>"; return xml; } }
調用類方法,需要自行完善邏輯代碼
public void weixinPay(HttpServletRequest request){ //請求IP地址 String ip = request.getRemoteAddr(); //發起支付 WeChatPay weChatPay = new WeChatPay(); //wxReturn 為微信異步回調地址,這里可以根據自己的方式獲取 String wxReturn = PropertyUtils.getPropertyValue(new File(realPathResolver.get(CONFIG)), WEIXIN_NOTICE_URL); /** * 調用微信支付 * order.getOrderNo() 訂單號 * price 訂單價格精度轉換后的價格 order.getPrice() 訂單價格,因為微信是分為單位 所以這里要乘以100 關於支付精度轉換,可以查看 https://www.cnblogs.com/pxblog/p/13186037.html */
int price=0.01; Map map = weChatPay.getPaymentMapCode(order.getOrderNo(),price, "微信支付", ip, wxReturn); String return_code = String.valueOf(map.get("return_code")); if (return_code.equals("SUCCESS")) { //微信調用成功
//code_url是支付的鏈接
request.getSession().setAttribute("code_url", map.get("code_url") + "");
//跳轉到支付頁面
} else { //微信支付調取失敗! } }
QRCodeUtil.java
package weixin; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.util.Hashtable; public class QRCodeUtil { private static final String CHARSET = "utf-8"; private static final String FORMAT_NAME = "JPG"; // 二維碼尺寸 private static final int QRCODE_SIZE = 300; // LOGO寬度 private static final int WIDTH = 60; // LOGO高度 private static final int HEIGHT = 60; private static BufferedImage createImage(String content, String imgPath, boolean needCompress) throws Exception { Hashtable hints = new Hashtable(); hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); hints.put(EncodeHintType.CHARACTER_SET, CHARSET); hints.put(EncodeHintType.MARGIN, 1); BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints); int width = bitMatrix.getWidth(); int height = bitMatrix.getHeight(); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF); } } if (imgPath == null || "".equals(imgPath)) { return image; } // 插入圖片 QRCodeUtil.insertImage(image, imgPath, needCompress); return image; } private static void insertImage(BufferedImage source, String imgPath, boolean needCompress) throws Exception { File file = new File(imgPath); if (!file.exists()) { System.err.println(""+imgPath+" 該文件不存在!"); return; } Image src = ImageIO.read(new File(imgPath)); int width = src.getWidth(null); int height = src.getHeight(null); if (needCompress) { // 壓縮LOGO if (width > WIDTH) { width = WIDTH; } if (height > HEIGHT) { height = HEIGHT; } Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH); BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g = tag.getGraphics(); g.drawImage(image, 0, 0, null); // 繪制縮小后的圖 g.dispose(); src = image; } // 插入LOGO Graphics2D graph = source.createGraphics(); int x = (QRCODE_SIZE - width) / 2; int y = (QRCODE_SIZE - height) / 2; graph.drawImage(src, x, y, width, height, null); Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6); graph.setStroke(new BasicStroke(3f)); graph.draw(shape); graph.dispose(); } //獲取生成二維碼的圖片流 public static ByteArrayOutputStream encodeIO(String content,String imgPath,Boolean needCompress) throws Exception { BufferedImage image = QRCodeUtil.createImage(content, imgPath, needCompress); //創建儲存圖片二進制流的輸出流 ByteArrayOutputStream baos = new ByteArrayOutputStream(); //將二進制數據寫入ByteArrayOutputStream ImageIO.write(image, "jpg", baos); return baos; } }
生成二維碼請求
@RequestMapping(value = "/get_qr_code") public void getQrCode(HttpServletRequest request, HttpServletResponse response) throws Exception {
//從session中獲取前面放在code_url地址
String content = request.getSession().getAttribute("code_url") + ""; System.out.printf(content); //二維碼圖片中間logo String imgPath = ""; Boolean needCompress = true; //通過調用我們的寫的工具類,拿到圖片流 ByteArrayOutputStream out = QRCodeUtil.encodeIO(content, imgPath, needCompress); //定義返回參數 response.setCharacterEncoding("UTF-8"); response.setContentType("image/jpeg;charset=UTF-8"); response.setContentLength(out.size()); ServletOutputStream outputStream = response.getOutputStream(); outputStream.write(out.toByteArray()); outputStream.flush(); outputStream.close(); }
支付頁面代碼、顯示二維碼,由於微信支付沒有同步支付通知,所以需要在這個頁面上寫輪詢方法,查詢自己數據庫訂單,判斷是否已經支付
<img src="/get_qr_code"><br/>
輪詢js
//2秒輪詢一次 setInterval("get_pay_status()",2000); //輪詢訂單支付狀態 function get_pay_status() { $.ajax({ url:'/get_order_status', data:{ orderNo:'訂單編號' }, success:function (data) { if (data.code==1000){ //如果后台返回支付成功,則跳轉到支付成功頁面 window.location.href="}/to_weixin_return_url.jspx?orderNo=訂單編號"; } } }) }
微信異步回調類,需要自行完善邏輯代碼
/** * 微信支付通知 * * @return */ @ResponseBody @RequestMapping(value = "/wechart_notice") public String wechartNotice(HttpServletRequest request, HttpServletResponse response, ModelMap model) { String result = ""; try { InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } outSteam.close(); inStream.close(); result = new String(outSteam.toByteArray(), "utf-8"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } //判斷返回報文是否為空 if (StringUtils.isNotBlank(result)) { try { Map<String, String> map = XMLUtil.doXMLParse(result); //獲取商家訂單編號 對應orderNo String orderNo = map.get("out_trade_no");
//獲取微信支付訂單號 String transaction_id = map.get("transaction_id"); Order order = orderMng.findByOrderNo(orderNo); //判斷支付是否成功 if ("SUCCESS".equals(map.get("result_code"))) { //支付成功,這里之所以加了一個判斷,是因為這個回調可能會有多次,所以我們只有當訂單狀態時未支付的情況下,才執行下面操作 if (!Constants.ORDER_SUCCESS.equals(order.getStatus())) { //當微信支付成功后,把訂單支付狀態改為已支付 order.setStatus(Constants.ORDER_SUCCESS); } //處理業務邏輯 } else { //支付失敗 order.setStatus(Constants.ORDER_FAIL); }
//更新數據庫訂單狀態 orderMng.update(order); } catch (JDOMException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } WeChatPay weChatPay = new WeChatPay(); return weChatPay.getSuccessXml(); }
