原理概述:
微信官方文檔:
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
這裏有詳細的入參列表,調用地址,但是我表示,看完文檔依舊寫不出可以馬上可用的接口
源碼詳述:
nonce_str參數:
public static String getRandomStringByLength(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
獲取統一下單接口地址:
https://api.mch.weixin.qq.com/pay/unifiedorder
從微信頒發的參數:
微信回調地址:
/**
* 微信支付回調函數
* @param request
* @param response
* @throws IOException
*/
@RequestMapping(value = "/callback")
public void callBack(HttpServletRequest request,HttpServletResponse response) throws IOException {
InputStream is = request.getInputStream();
HashMap<String, String> map = new HashMap<String, String>();
SAXReader reader = new SAXReader();
Document document = null;
try {
document = reader.read(is);
} catch (DocumentException e1) {
e1.printStackTrace();
}
String out_trade_no = "";//訂單ID
String total_fee = ""; //訂單金額
Element root = document.getRootElement();
List<Element> list = root.elements();
// 獲取微信返回值信息
for (Element e : list) {
map.put(e.getName().trim(), e.getText().trim());
if (e.getName().trim().equals("out_trade_no")) {
out_trade_no = e.getText().trim();
} else if (e.getName().trim().equals("cash_fee")) {
total_fee = e.getText().trim();
}
}
is.close();
// 克隆傳入的信息並進行驗籤,建議一定要驗證簽名,防止返回值被篡改
HashMap<String, String> signMap = (HashMap<String, String>) map.clone();
signMap.remove("sign");
// 這裏的wx_key 是用戶自定義的支付key
String key= PropertiesUtil.getValue("wechat.properties","wx_key");
String sign = SignatureUtils.signature(signMap,key);
if (!sign.equals(map.get("sign"))) {
return;
}
// 信息處理
String result_code = map.get("result_code");
try {
if ("SUCCESS".equals(result_code)) {
//由於微信後臺會同時回調多次,所以需要做防止重複提交操作的判斷
//此處放防止重複提交操作
} else if ("FAIL".equals(result_code)) {
}
} catch (Exception e) {
e.printStackTrace();
return;
}
//這裏是驗證返回值沒問題了,可以寫具體的支付成功的邏輯
// 返回信息,防止微信重複發送報文
String result = "<xml>"
+ "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml>";
PrintWriter out = new PrintWriter(response.getOutputStream());
out.print(result);
out.flush();
out.close();
}
驗籤源碼:(用於統一下單,微信回調)
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=20_1
源碼:/**
* 微信支付加密工具
*/
public static String signature(Map<String, String> map, String key) {
Set<String> keySet = map.keySet();
String[] str = new String[map.size()];
StringBuilder tmp = new StringBuilder();
// 進行字典排序
str = keySet.toArray(str);
Arrays.sort(str);
for (int i = 0; i < str.length; i++) {
String t = str[i] + "=" + map.get(str[i]) + "&";
tmp.append(t);
}
if (StringUtils.isNotBlank(key)) {
tmp.append("key=" + key);
}
String tosend = tmp.toString();
MessageDigest md = null;
byte[] bytes = null;
try {
md = MessageDigest.getInstance("MD5");
bytes = md.digest(tosend.getBytes("utf-8"));
} catch (Exception e) {
e.printStackTrace();
}
String singe = byteToStr(bytes);
return singe.toUpperCase();
}
統一下單源碼:
public static synchronized JSONObject createOrder(String detail, String desc, String openid, String ip, String goodSn, String orderSn, String amount, String type,String randomNum) {
JSONObject result = new JSONObject();
double relAmount = 0;// 對應微信支付的真實數目
try {
//微信接收的金額單位是分,如果客戶端不轉換,服務端要講金額轉換成分
relAmount = Double.parseDouble(amount) * 100;
} catch (Exception e) {
return result;
}
if (relAmount == 0) {
//微信支付的支付金額必須爲大於0的int類型,單位爲分
return result;
}
if (!("JSAPI".equalsIgnoreCase(type) || "NATIVE".equalsIgnoreCase(type) || "APP".equalsIgnoreCase(type))) {
return result;
}
// 獲取系統配置信息
String wx_order = PropertiesUtil.getValue("wechat.properties", "wx_order");//獲取統一下單接口地址
String mchappid = PropertiesUtil.getValue("wechat.properties", "mchappid");// 商戶appid
String mchid = PropertiesUtil.getValue("wechat.properties", "mchid");// 商戶ID
String wx_callback = PropertiesUtil.getValue("wechat.properties", "wx_callback");// 獲取微信支付回調接口
String wx_key = PropertiesUtil.getValue("wechat.properties", "wx_key");//微信商戶後臺設置的key
String app_mchid = PropertiesUtil.getValue("wechat.properties", "app_mchid");//APP調起微信支付的商戶ID
String app_mchappid = PropertiesUtil.getValue("wechat.properties", "app_mchappid");//APP調起微信的APPID
if (StringUtils.isBlank(wx_order) || StringUtils.isBlank(mchappid)|| StringUtils.isBlank(mchid) || StringUtils.isBlank(wx_callback)) {
return result;
}
// 發送報文模板,其中部分字段是可選字段
String xml = "" +
"<xml>" +
"<appid>APPID</appid>" +//公衆號ID
"<device_info>WEB</device_info>" +//設備信息
"<detail>DETAIL</detail>" +//商品詳情
"<body>BODY</body>" +//商品描述
"<mch_id>MERCHANT</mch_id>" +//微信給的商戶ID
"<nonce_str>FFHH</nonce_str>" +//32位隨機字符串
"<notify_url><![CDATA[URL_TO]]></notify_url>" +//微信回調信息通知頁面
"<openid>UserFrom</openid>" +//支付的用戶ID
"<fee_type>CNY</fee_type>" +//支付貨幣
"<spbill_create_ip>IP</spbill_create_ip>" +//用戶IP
"<time_start>START</time_start>" +//訂單開始時間
"<time_expire>STOP</time_expire>" +//訂單結束時間
"<goods_tag>WXG</goods_tag>" +//商品標記
"<product_id>GOODID</product_id>" +//商品ID
"<limit_pay>no_credit</limit_pay>" +//支付範圍,默認不支持信用卡支付
"<out_trade_no>PAY_NO</out_trade_no>" +//商城生成的訂單號
"<total_fee>TOTAL</total_fee>" +//總金額
"<trade_type>TYPE</trade_type>" +//交易類型,JSAPI,NATIVE,APP,WAP
"<sign>SIGN</sign>" +//加密字符串
"</xml>";
//生成訂單起始時間,訂單7天內有效
DateFormat df = new SimpleDateFormat("yyyyMMddhhmmss");
String start_time = df.format(new Date());
String stop_time = df.format(new Date().getTime() + 7 * 24 * 60 * 60 * 1000);
//xml數據封裝
//APP調起的時候,可能和公衆號調起的商戶號是不同的,所以需要分開設置
if ("APP".equalsIgnoreCase(type)) {
xml = xml.replace("MERCHANT", app_mchid);
xml = xml.replace("APPID", app_mchappid);
} else {
xml = xml.replace("MERCHANT", mchid);
xml = xml.replace("APPID", mchappid);
}
xml = xml.replace("FFHH", randomNum);
xml = xml.replace("DETAIL", detail);
xml = xml.replace("BODY", desc);
xml = xml.replace("URL_TO", wx_callback);
xml = xml.replace("IP", ip);
xml = xml.replace("START", start_time);
xml = xml.replace("STOP", stop_time);
xml = xml.replace("GOODID", goodSn);
xml = xml.replace("PAY_NO", orderSn);
xml = xml.replace("TOTAL", (int) relAmount + "");
xml = xml.replace("TYPE", type);
if ("NATIVE".equalsIgnoreCase(type) || "APP".equalsIgnoreCase(type)) {
xml = xml.replace("<openid>UserFrom</openid>", openid);
} else {
xml = xml.replace("UserFrom", openid);
}
// 4、加密
Map<String, String> map = new HashMap<String, String>();
map.put("device_info", "WEB");
map.put("detail", detail);
map.put("body", desc);
if ("APP".equalsIgnoreCase(type)) {
map.put("mch_id", app_mchid);
map.put("appid", app_mchappid);
} else {
map.put("mch_id", mchid);
map.put("appid", mchappid);
}
map.put("nonce_str", randomNum);
map.put("notify_url", wx_callback);
map.put("fee_type", "CNY");
map.put("spbill_create_ip", ip);
map.put("time_start", start_time);
map.put("time_expire", stop_time);
map.put("goods_tag", "WXG");
map.put("product_id", goodSn);
map.put("limit_pay", "no_credit");
map.put("out_trade_no", orderSn);
map.put("total_fee", (int) relAmount + "");
map.put("trade_type", type);
String sign = SignatureUtils.signature(map, wx_key);
xml = xml.replace("SIGN", sign);
// 請求
String response = "";
try {
//注意,此處的httputil一定發送請求的時候一定要注意中文亂碼問題,中文亂碼問題會導致在客戶端加密是正確的,可是微信端返回的是加密錯誤
response = HttpUtils.post(wx_order, xml);
} catch (Exception e) {
return result;
}
//處理請求結果
XStream s = new XStream(new DomDriver());
s.alias("xml", WechatOrder.class);
WechatOrder order = (WechatOrder) s.fromXML(response);
if ("SUCCESS".equals(order.getReturn_code()) && "SUCCESS".equals(order.getResult_code())) {
//支付成功的處理邏輯
} else {
//支付失敗的處理邏輯
}
HashMap<String, String> back = new HashMap<String, String>();
//生成客戶端調時需要的信息對象
//APP調起的時候,請注意,安卓端不能用駝峯法,所有的key必須使用小寫
String time = Long.toString(System.currentTimeMillis());
back.put("appid", app_mchappid);
back.put("timestamp", time);
back.put("partnerid", app_mchid);
back.put("noncestr", "5K8264ILTKCH16CQ2502SI8ZNMTM67VS");
back.put("prepayid", order.getPrepay_id());
back.put("package", "Sign=WXPay");
String sign2 = SignatureUtils.signature(back, wx_key);
JSONObject jsonObject = new JSONObject();
jsonObject.put("appid", app_mchappid);
jsonObject.put("timestamp", time);
jsonObject.put("partnerid", app_mchid);
jsonObject.put("noncestr", "5K8264ILTKCH16CQ2502SI8ZNMTM67VS");
jsonObject.put("prepayid", order.getPrepay_id());
//jsonObject.put("package", "Sign=WXPay");
jsonObject.put("sign", sign2);
result.put("status", "success");
result.put("msg", "下單成功");
result.put("obj", jsonObject);
return result;
}
HttpUtils.post源碼:
public static String post(String url, String str)throws Exception {
// 處理請求地址
URI uri = new URI(url);
HttpPost post = new HttpPost(uri);
post.setEntity(new StringEntity(str,"utf-8"));
// 執行請求
HttpResponse response = client.execute(post);
if (response.getStatusLine().getStatusCode() == 200) {
// 處理請求結果
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(
new InputStreamReader(in));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
} finally {
// 關閉流
if (in != null)
in.close();
}
return buffer.toString();
} else {
return null;
}
}
PS: