背景
公司的公衆号有個保險項目需要用到微信支付,官網java文檔寫的并不是很詳細。個人根據網上一些demo總結出來的微信支付功能,中間遇到過幾個坑,後續會詳細講解。話不多說,先上代碼!
一.轉換MD5格式類MD5Util類
import java.security.MessageDigest;
public class MD5Util {
public final static String MD5(String s) {
char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
try {
byte[] btInput = s.getBytes();
MessageDigest mdInst = MessageDigest.getInstance("MD5");
mdInst.update(btInput);
byte[] md = mdInst.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];
}
String md5Str = new String(str);
return md5Str;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
二.統一下單參數類UnifiedOrderRequest
public class UnifiedOrderRequest {
private String appid;// 公衆賬号ID 是 String(32) wxd678efh567hg6787 微信支付配置設定的公衆賬号ID(企業号corpid即為此appId)
private String mch_id;//商戶号 必填 String(32) 1230000109 微信支付配置設定的商戶号
private String device_info; //裝置号 否 String(32) 013467007045764 自定義參數,可以為終端裝置号(門店号或收銀裝置ID),PC網頁或公衆号内支付可以傳"WEB"
private String nonce_str;//随機字元串 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随機字元串,長度要求在32位以内。推薦随機數生成算法
private String sign;//簽名 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 通過簽名算法計算得出的簽名值,詳見簽名生成算法
private String sign_type;//簽名類型 sign_type 否 String(32) HMAC-SHA256 簽名類型,預設為MD5,支援HMAC-SHA256和MD5。
private String body;//商品描述 body 是 String(128) 騰訊充值中心-QQ會員充值 商品簡單描述,該字段請按照規範傳遞,具體請見參數規定
private String detail;//商品詳情 detail 否 String(6000) 單品優惠字段(暫未上線)
private String attach;//附加資料 attach 否 String(127) 深圳分店 附加資料,在查詢API和支付通知中原樣傳回,可作為自定義參數使用。
private String out_trade_no;//商戶訂單号 out_trade_no 是 String(32) 20150806125346 商戶系統内部訂單号,要求32個字元内,隻能是數字、大小寫字母_-|*@ ,且在同一個商戶号下唯一。詳見商戶訂單号
private String fee_type;//标價币種 fee_type 否 String(16) CNY 符合ISO 4217标準的三位字母代碼,預設人民币:CNY,詳細清單請參見貨币類型
private String total_fee;//标價金額 total_fee 是 Int 88 訂單總金額,機關為分,詳見支付金額
private String spbill_create_ip;//終端IP spbill_create_ip 是 String(16) 123.12.12.123 APP和網頁支付送出使用者端ip,Native支付填調用微信支付API的機器IP。
private String time_start;//交易起始時間 time_start 否 String(14) 20091225091010 訂單生成時間,格式為yyyyMMddHHmmss,如2009年12月25日9點10分10秒表示為20091225091010。其他詳見時間規則
private String time_expire;//交易結束時間 time_expire 否 String(14) 20091227091010 訂單失效時間,格式為yyyyMMddHHmmss,如2009年12月27日9點10分10秒表示為20091227091010。其他詳見時間規則 注意:最短失效時間間隔必須大于5分鐘
private String goods_tag;//訂單優惠标記 goods_tag 否 String(32) WXG 訂單優惠标記,使用代金券或立減優惠功能時需要的參數,說明詳見代金券或立減優惠
private String notify_url;//通知位址 notify_url 是 String(256) http://www.weixin.qq.com/wxpay/pay.php 異步接收微信支付結果通知的回調位址,通知url必須為外網可通路的url,不能攜帶參數。
private String trade_type;//交易類型 trade_type 是 String(16) JSAPI 取值如下:JSAPI,NATIVE,APP等,說明詳見參數規定
private String product_id;//商品ID product_id 否 String(32) 12235413214070356458058 trade_type=NATIVE時(即掃碼支付),此參數必傳。此參數為二維碼中包含的商品ID,商戶自行定義。
private String limit_pay;//指定支付方式 limit_pay 否 String(32) no_credit 上傳此參數no_credit--可限制使用者不能使用信用卡支付
private String openid;//使用者辨別 openid 否 String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI時(即公衆号支付),此參數必傳,此參數為微信使用者在商戶對應appid下的唯一辨別。openid如何擷取,可參考【擷取openid】。企業号請使用【企業号OAuth2.0接口】擷取企業号内成員userid,再調用【企業号userid轉openid接口】進行轉換
//以下省略get和set方法
}
三.統一下單傳回參數類UnifiedOrderRespose
public class UnifiedOrderRespose {
private String return_code; //傳回狀态碼
private String return_msg; //傳回資訊
private String appid; //公衆賬号ID
private String mch_id; //商戶号
private String device_info; //裝置号
private String nonce_str; //随機字元串
private String sign; //簽名
private String result_code; //業務結果
private String err_code; //錯誤代碼
private String err_code_des; //錯誤代碼描述
private String trade_type; //交易類型
private String prepay_id; //預支付交易會話辨別
private String code_url; //二維碼連結
//....以下省略set和get方法
}
四.将傳回結果轉換為json類WXAuthUtil,該類部分微信參數,其中MCH_ID、APPID、APPSECRET在微信公衆号設定,KEY在微信商戶号上設定,KEY用于後面生成簽名。
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class WXAuthUtil {
public static final String MCH_ID="";//商戶号
public static final String APPID="";//
public static final String APPSECRET ="";
public static final String KEY="";
public static JSONObject doGetJson(String url) throws ClientProtocolException, IOException {
JSONObject jsonObject =null;
DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpGet =new HttpGet(url);
HttpResponse response = client.execute(httpGet);
HttpEntity entity =response.getEntity();
if(entity!=null)
{
//把傳回的結果轉換為JSON對象
String result =EntityUtils.toString(entity, "UTF-8");
jsonObject =JSON.parseObject(result);
}
return jsonObject;
}
}
五.簽名方式類WXPayConstants,包含微信傳回結果定義的參數
public class WXPayConstants {
public enum SignType {
MD5, HMACSHA256
}
public static final String FAIL = "FAIL";
public static final String SUCCESS = "SUCCESS";
public static final String HMACSHA256 = "HMAC-SHA256";
public static final String MD5 = "MD5";
public static final String FIELD_SIGN = "sign";
public static final String FIELD_SIGN_TYPE = "sign_type";
}
六.請求連接配接類HttpKit,統一下單、查詢訂單用到
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.Response;
/**
* https 請求 微信為https的請求
*
* @author andy
* @date 2015-10-9 下午2:40:19
*/
public class HttpKit {
private static final String DEFAULT_CHARSET = "UTF-8";
/**
* @return 傳回類型:
* @throws IOException
* @throws UnsupportedEncodingException
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
* @description 功能描述: get 請求
*/
public static String get(String url, Map<String, String> params, Map<String, String> headers) throws IOException, ExecutionException, InterruptedException {
AsyncHttpClient http = new AsyncHttpClient();
AsyncHttpClient.BoundRequestBuilder builder = http.prepareGet(url);
builder.setBodyEncoding(DEFAULT_CHARSET);
if (params != null && !params.isEmpty()) {
Set<String> keys = params.keySet();
for (String key : keys) {
//builder.addQueryParame(key, params.get(key));
builder.addQueryParam(key, params.get(key));
}
}
if (headers != null && !headers.isEmpty()) {
Set<String> keys = headers.keySet();
for (String key : keys) {
builder.addHeader(key, params.get(key));
}
}
Future<Response> f = builder.execute();
String body = f.get().getResponseBody(DEFAULT_CHARSET);
http.close();
return body;
}
/**
* @return 傳回類型:
* @throws IOException
* @throws UnsupportedEncodingException
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
* @description 功能描述: get 請求
*/
public static String get(String url) throws KeyManagementException, NoSuchAlgorithmException, NoSuchProviderException, UnsupportedEncodingException, IOException, ExecutionException, InterruptedException {
return get(url, null);
}
/**
* @return 傳回類型:
* @throws IOException
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
* @throws UnsupportedEncodingException
* @description 功能描述: get 請求
*/
public static String get(String url, Map<String, String> params) throws KeyManagementException, NoSuchAlgorithmException, NoSuchProviderException, UnsupportedEncodingException, IOException, ExecutionException, InterruptedException {
return get(url, params, null);
}
/**
* @return 傳回類型:
* @throws IOException
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
* @description 功能描述: POST 請求
*/
public static String post(String url, Map<String, String> params) throws IOException, ExecutionException, InterruptedException {
AsyncHttpClient http = new AsyncHttpClient();
AsyncHttpClient.BoundRequestBuilder builder = http.preparePost(url);
builder.setBodyEncoding(DEFAULT_CHARSET);
if (params != null && !params.isEmpty()) {
Set<String> keys = params.keySet();
for (String key : keys) {
//builder.addParameter(key, params.get(key));
builder.addQueryParam(key, params.get(key));
}
}
Future<Response> f = builder.execute();
String body = f.get().getResponseBody(DEFAULT_CHARSET);
http.close();
return body;
}
public static String post(String url, String s) throws IOException, ExecutionException, InterruptedException {
AsyncHttpClient http = new AsyncHttpClient();
AsyncHttpClient.BoundRequestBuilder builder = http.preparePost(url);
builder.setBodyEncoding(DEFAULT_CHARSET);
builder.setBody(s);
Future<Response> f = builder.execute();
String body = f.get().getResponseBody(DEFAULT_CHARSET);
http.close();
return body;
}
}
七.支付工具類
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.security.MessageDigest;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import com.rybsj.entity.PaymentOrder;
import com.rybsj.tools.WXPayConstants.SignType;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 支付工具類
* @author lh
*/
public class WXPayUtil {
public static Logger log=LoggerFactory.getLogger(WXPayUtil.class);
/**
* 生成訂單對象資訊
* @param orderId 訂單号
* @param appId 微信appId
* @param mch_id 微信配置設定的商戶ID
* @param body 支付介紹主體
* @param price 支付價格(放大100倍)
* @param spbill_create_ip 終端IP
* @param notify_url 異步直接結果通知接口位址
* @param noncestr
* @return
*/
public static Map<String,Object> createOrderInfo(Map<String, String> requestMap) {
//生成訂單對象
UnifiedOrderRequest unifiedOrderRequest = new UnifiedOrderRequest();
unifiedOrderRequest.setAppid(requestMap.get("appId"));//公衆賬号ID
unifiedOrderRequest.setBody(requestMap.get("body"));//商品描述
unifiedOrderRequest.setMch_id(requestMap.get("mch_id"));//商戶号
unifiedOrderRequest.setNonce_str(requestMap.get("nonce_str"));//随機字元串
unifiedOrderRequest.setNotify_url(requestMap.get("notify_url"));//通知位址
unifiedOrderRequest.setOpenid(requestMap.get("userWeixinOpenId"));
unifiedOrderRequest.setDetail(requestMap.get("detail"));//詳情
unifiedOrderRequest.setOut_trade_no(requestMap.get("out_trade_no"));//商戶訂單号
unifiedOrderRequest.setSpbill_create_ip(requestMap.get("spbill_create_ip"));//終端IP
unifiedOrderRequest.setTotal_fee(requestMap.get("payMoney")); //金額需要擴大100倍:1代表支付時是0.01
unifiedOrderRequest.setTrade_type("JSAPI");//JSAPI--公衆号支付、NATIVE--原生掃碼支付、APP--app支付
SortedMap<String, String> packageParams = new TreeMap<String, String>();
packageParams.put("appid", unifiedOrderRequest.getAppid());
packageParams.put("body", unifiedOrderRequest.getBody());
packageParams.put("mch_id", unifiedOrderRequest.getMch_id());
packageParams.put("nonce_str", unifiedOrderRequest.getNonce_str());
packageParams.put("notify_url", unifiedOrderRequest.getNotify_url());
packageParams.put("openid", unifiedOrderRequest.getOpenid());
packageParams.put("detail", unifiedOrderRequest.getDetail());
packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no());
packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip());
packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee());
packageParams.put("trade_type", unifiedOrderRequest.getTrade_type());
try {
unifiedOrderRequest.setSign(generateSignature(packageParams,"你的密匙"));//簽名 ,此處第二個參數填寫秘鑰
} catch (Exception e) {
e.printStackTrace();
}
//将訂單對象轉為xml格式
xstream.alias("xml", UnifiedOrderRequest.class);//根元素名需要是xml
System.out.println("封裝好的統一下單請求資料:"+xstream.toXML(unifiedOrderRequest).replace("__", "_"));
Map<String,Object> responseMap = new HashMap<String,Object>();
responseMap.put("orderInfo_toString", xstream.toXML(unifiedOrderRequest).replace("__", "_"));
responseMap.put("unifiedOrderRequest",unifiedOrderRequest);
return responseMap;
}
/**
* 生成簽名
* @param appid_value
* @param mch_id_value
* @param productId
* @param nonce_str_value
* @param trade_type
* @param notify_url
* @param spbill_create_ip
* @param total_fee
* @param out_trade_no
* @return
*/
private static String createSign(UnifiedOrderRequest unifiedOrderRequest) {
//根據規則建立可排序的map集合
SortedMap<String, String> packageParams = new TreeMap<String, String>();
packageParams.put("appid", unifiedOrderRequest.getAppid());
packageParams.put("body", unifiedOrderRequest.getBody());
packageParams.put("mch_id", unifiedOrderRequest.getMch_id());
packageParams.put("nonce_str", unifiedOrderRequest.getNonce_str());
packageParams.put("notify_url", unifiedOrderRequest.getNotify_url());
packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no());
packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip());
packageParams.put("trade_type", unifiedOrderRequest.getTrade_type());
packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee());
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 + "&");
}
}
//第二步拼接key,key設定路徑:微信商戶平台(pay.weixin.qq.com)-->賬戶設定-->API安全-->密鑰設定
sb.append("key="+"你的密匙");
String sign = MD5Util.MD5(sb.toString()).toUpperCase();//MD5加密
log.error("方式一生成的簽名="+sign);
return sign;
}
private static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 對所有xml節點的轉換都增加CDATA标記
boolean cdata = true;
String NodeName = "";
@SuppressWarnings("unchecked")
public void startNode(String name, Class clazz) {
NodeName = name;
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
if(!NodeName.equals("detail")){
writer.write(text);
}else{
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
}
} else {
writer.write(text);
}
}
};
}
});
//xml解析
public static SortedMap<String, String> doXMLParseWithSorted(String strxml) throws Exception {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if(null == strxml || "".equals(strxml)) {
return null;
}
SortedMap<String,String> m = new TreeMap<String,String>();
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 = getChildrenText(children);
}
m.put(k, v);
}
//關閉流
in.close();
return m;
}
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(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
/**
* 調統一下單API
* @param orderInfo
* @return
*/
public static UnifiedOrderRespose httpOrder(String orderInfo) {
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
//加入資料
conn.setRequestMethod("POST");
conn.setDoOutput(true);
BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream());
buffOutStr.write(orderInfo.getBytes("UTF-8"));
buffOutStr.flush();
buffOutStr.close();
//擷取輸入流
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line = null;
StringBuffer sb = new StringBuffer();
while((line = reader.readLine())!= null){
sb.append(line);
}
//将請求傳回的内容通過xStream轉換為UnifiedOrderRespose對象
xstream.alias("xml", UnifiedOrderRespose.class);
UnifiedOrderRespose unifiedOrderRespose = (UnifiedOrderRespose)xstream.fromXML(sb.toString());
return unifiedOrderRespose;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* XML格式字元串轉換為Map
*
* @param strXML XML字元串
* @return XML資料轉換後的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 将Map轉換為XML格式的字元串
*
* @param data Map類型資料
* @return XML格式的字元串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key: data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* 生成帶有 sign 的 XML 格式字元串
*
* @param data Map類型資料
* @param key API密鑰
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
return generateSignedXml(data, key, SignType.MD5);
}
/**
* 生成帶有 sign 的 XML 格式字元串
*
* @param data Map類型資料
* @param key API密鑰
* @param signType 簽名類型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
String sign = generateSignature(data, key, signType);
data.put(WXPayConstants.FIELD_SIGN, sign);
return mapToXml(data);
}
/**
* 判斷簽名是否正确
*
* @param xmlStr XML格式資料
* @param key API密鑰
* @return 簽名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
Map<String, String> data = xmlToMap(xmlStr);
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key).equals(sign);
}
/**
* 判斷簽名是否正确,必須包含sign字段,否則傳回false。使用MD5簽名。
*
* @param data Map類型資料
* @param key API密鑰
* @return 簽名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, SignType.MD5);
}
/**
* 判斷簽名是否正确,必須包含sign字段,否則傳回false。
*
* @param data Map類型資料
* @param key API密鑰
* @param signType 簽名方式
* @return 簽名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key, signType).equals(sign);
}
/**
* 生成簽名
*
* @param data 待簽名資料
* @param key API密鑰
* @return 簽名
*/
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, SignType.MD5);
}
/**
* 生成簽名. 注意,若含有sign_type字段,必須和signType參數保持一緻。
*
* @param data 待簽名資料
* @param key API密鑰
* @param signType 簽名方式
* @return 簽名
*/
public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k)!=null&&data.get(k).trim().length() > 0) // 參數值為空,則不參與簽名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(key);
if (SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
}
else if (SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
}
else {
log.error("擷取簽名失敗,失敗原因:"+String.format("Invalid sign_type: %s", signType));
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 擷取随機字元串 Nonce Str
* @return String 随機字元串
*/
public static String generateNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
/**
* Map轉xml資料
*/
public static String GetMapToXML(Map<String,String> param){
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
for (Map.Entry<String,String> entry : param.entrySet()) {
sb.append("<"+ entry.getKey() +">");
sb.append(entry.getValue());
sb.append("</"+ entry.getKey() +">");
}
sb.append("</xml>");
return sb.toString();
}
/**
* 生成 MD5
* @param data 待處理資料
* @return MD5結果
*/
public static String MD5(String data) throws Exception {
java.security.MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 生成 HMACSHA256
* @param data 待處理資料
* @param key 密鑰
* @return 加密結果
* @throws Exception
*/
public static String HMACSHA256(String data, String key) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 日志
* @return
*/
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
/**
* 擷取目前時間戳,機關秒
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
/**
* 擷取目前時間戳,機關毫秒
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
/**
* 生成 uuid, 即用來辨別一筆單,也用做 nonce_str
* @return
*/
public static String generateUUID() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
/**
* 支付簽名
* @param timestamp
* @param noncestr
* @param packages
* @return
* @throws UnsupportedEncodingException
*/
public static String paySign(String timestamp, String noncestr,String packages,String appId){
Map<String, String> paras = new HashMap<String, String>();
paras.put("appid", appId);
paras.put("timestamp", timestamp);
paras.put("noncestr", noncestr);
paras.put("package", packages);
paras.put("signType", "MD5");
StringBuffer sb = new StringBuffer();
Set es = paras.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 + "&");
}
}
String sign = MD5Util.MD5(sb.toString()).toUpperCase();//MD5加密
return sign;
}
/**
* map轉對象
* @param map
* @return
*/
public static PaymentOrder getPaymentOrders(Map<String,String> map) {
PaymentOrder payOrder=new PaymentOrder();
payOrder.setAppid(map.get("appid"));
payOrder.setOpenid(map.get("openid"));
payOrder.setOrdetaileid(Integer.parseInt(map.get("out_trade_no")));
payOrder.setTotal_fee(Integer.parseInt(map.get("total_fee")));
payOrder.setTrade_type(map.get("trade_type"));
//payOrder.setTime_start(map.get("time_start"));
//payOrder.setTime_expire(map.get("time_expire"));
return payOrder;
}
/**
* 對象轉map
*/
public static Map<String,String> getMaptoOrder(PaymentOrder porder){
Map<String,String> map=new HashMap<String,String>();
map.put("appId", porder.getAppid());
map.put("timeStamp", porder.getTimestamp());
map.put("nonceStr", porder.getNonce_str());
map.put("signType", porder.getSignType());
map.put("package", porder.getPackages());
map.put("paySign", porder.getPaySign());
return map;
}
/**
* description: 解析微信通知xml
*
* @param xml
* @return
* @author ex_yangxiaoyi
* @see
*/
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;
}
}
以上為工具類,後面講支付流程。首先需要使用者授權擷取openid,注意不要認為官方文檔必填項顯示此參數為否就不用openid了,後面解釋有說明當trade_type=JSAPI(即公衆号支付)時此參數必填。
首先擷取使用者權限,需要設定設定回調域名(即redirect_uri的域名),該域名需為80端口,且需要下載下傳一個txt檔案到該域名下,具體操作如下圖:

此外擷取access_token還要配置IP白名單,如下圖:找到開發下面的基本配置,點選IP白名單的配置,多個ip可用Enter鍵隔開
配置完後就可以通過微信授權擷取openid了。看了很多demo微信授權連結都是直接寫在背景的,本人試了下寫在前台也能用。其中appid改成自己公衆号的appid,redirect_uri就是剛才設定的授權回調域名,getWXUserInformation是對應背景的擷取openid的方法,此方法也可擷取微信頭像和名字等資訊。state=123是授權時帶過去的參數,可以随便修改。scope=snsapi_userinfo時表示授權時會彈出授權頁面需要使用者同意,此時可以擷取使用者其他資訊,當scope=snsapi_base時表示授權不會彈出授權頁面,此時隻能擷取openid。
window.location="https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx。。。。。&redirect_uri=。。。/getWXUserInformation&response_type=code&scope=snsapi_userinfo&state=123#wechat_redirect";
下面附上背景擷取openid業務層類Access_tokenAction
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.apache.http.client.ClientProtocolException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.alibaba.fastjson.JSONObject;
import com.rybsj.service.UserService;
import com.rybsj.tools.WXAuthUtil;
@Controller
public class Access_tokenAction {
@Resource(name="userService")
private UserService userimpl;
private static Logger logger=LoggerFactory.getLogger(Access_tokenAction.class);
/**
* 擷取微信使用者資訊
* @throws IOException
* @throws ClientProtocolException
*/
@RequestMapping("/getWXUserInformation")
public String getWXUserInformation(HttpServletRequest request) throws ClientProtocolException, IOException {
/*
* start 擷取微信使用者基本資訊
*/
String code=request.getParameter("code");
System.out.println(code);
logger.info("code="+code);
//第二步:通過code換取網頁授權access_token
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+WXAuthUtil.APPID
+ "&secret="+WXAuthUtil.APPSECRET
+ "&code="+code
+ "&grant_type=authorization_code";
System.out.println("url:"+url);
logger.info("url="+url);
JSONObject jsonObject = WXAuthUtil.doGetJson(url);
System.out.println(jsonObject.toString());
logger.info("擷取token傳回資訊為:"+jsonObject.toString());
/*
{ "access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}
*/
String openid = jsonObject.getString("openid");
String access_token = jsonObject.getString("access_token");
String refresh_token = jsonObject.getString("refresh_token");
//第五步驗證access_token是否失效;暫時不需要
String chickUrl="https://api.weixin.qq.com/sns/auth?access_token="+access_token+"&openid="+openid;
JSONObject chickuserInfo = WXAuthUtil.doGetJson(chickUrl);
System.out.println(chickuserInfo.toString());
logger.info("傳回驗證資訊:"+chickuserInfo.toString());
if(!"0".equals(chickuserInfo.getString("errcode"))){
// 第三步:重新整理access_token(如果需要)-----暫時沒有使用,參考文檔https://mp.weixin.qq.com/wiki,
String refreshTokenUrl="https://api.weixin.qq.com/sns/oauth2/refresh_token?appid="+openid+"&grant_type=refresh_token&refresh_token="+refresh_token;
JSONObject refreshInfo = WXAuthUtil.doGetJson(chickUrl);
/*
* { "access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE" }
*/
System.out.println(refreshInfo.toString());
access_token=refreshInfo.getString("access_token");
}
// 第四步:拉取使用者資訊(需scope為 snsapi_userinfo)
String infoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token="+access_token
+ "&openid="+openid
+ "&;
System.out.println("infoUrl:"+infoUrl);
JSONObject userInfo = WXAuthUtil.doGetJson(infoUrl);
logger.info("使用者資訊為:"+userInfo.toString());
/*
{ "openid":" OPENID",
" nickname": NICKNAME,
"sex":"1",
"province":"PROVINCE"
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
"privilege":[ "PRIVILEGE1" "PRIVILEGE2" ],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
*/
System.out.println("JSON-----"+userInfo.toString());
System.out.println("名字-----"+userInfo.getString("nickname"));
System.out.println("頭像-----"+userInfo.getString("headimgurl"));
//request.getSession().setAttribute("openid", openid);
Map<String, String> map=new HashMap<String,String>();
map.put("openid", openid);
map.put("nickname", userInfo.getString("nickname"));
map.put("headimgurl", userInfo.getString("headimgurl"));
request.getSession().setAttribute("wxUserMap", map);
/*
* end 擷取微信使用者基本資訊
*/
//擷取到使用者資訊後就可以進行重定向,走自己的業務邏輯了。。。。。。
//接來的邏輯就是你系統邏輯了,請自由發揮
return "operatingVan";
}
}
擷取openid後就可以微信下單了。首先設定支付授權目錄,該目錄以前是在微信平台設定的 現在改在商戶平台設定了
先附上前端代碼orderPayment.jsp和背景對應下單方法getPaymentOrder,此頁面首先通過pay()方法從背景的擷取getPaymentOrder方法擷取微信支付的參數appId,timeStamp,nonceStr,signType,package,paySign.然後調用onBridgeReady()方法實作微信支付,當使用者輸入密碼後點選立即支付會跳轉到商戶背景的getPaymentResult()方法内,此處跳轉可在getPaymentOrder()方法的下單參數notify_url中設定
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE html>
<html >
<head>
<base href="<%=basePath%>" target="_blank" rel="external nofollow" >
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>微信支付</title>
<script>
setRem();
window.addEventListener('orientationchange', setRem);
window.addEventListener('resize', setRem);
function setRem() {
var html = document.querySelector('html');
var width = html.getBoundingClientRect().width;
html.style.fontSize = width / 16 + 'px';
}
</script>
<link rel="stylesheet" href="css/common.css" target="_blank" rel="external nofollow" />
<link rel="stylesheet" type="text/css" href="css/payment.css" target="_blank" rel="external nofollow" />
</head>
<body>
<!--訂單需支付-->
<div class="payment-box">
<p>訂單需支付</p>
<ul>
<li>
<span class="left">首期款</span>
<span class="right">¥<span class="decime" >${order.initialcost}</span></span>
</li>
<li>
<span class="left">服務費</span>
<span class="right">¥<span class="decime" >${order.servicecost}</span></span>
</li>
<li>
<span class="left">合計</span>
<span class="right amount">¥<span class="decime">${sum }</span></span>
</li>
</ul>
</div>
<div class="height20"></div>
<!--是否需要開發票-->
<div class="invoice">
<div class="left">是否需要開發票</div>
<div class="right"><img src="images/Off.png" id="onOff" class="active"></div>
</div>
<div class="address">
<ul>
<li>
<div class="info">
發票資訊
</div>
<div class="info-input">
<input type="text" placeholder="請輸入">
<i><img src="images/icon-right.png"></i>
</div>
</li>
<li>
<div class="second-info">
<span>收貨位址</span>
<input type="text" name="" placeholder="我是一個收件位址,也可能是其它"/>
<i><img src="images/icon-right.png"></i>
</div>
</li>
</ul>
</div>
<div class="height20"></div>
<!--支付方式-->
<div class="payment-style">
<ul>
<li class="payments">
<div class="left">
<img src="images/select-false.png" class="weixin-img"><span>微信支付</span>
</div>
<div class="right">
<img src="images/icon-right.png">
</div>
</li>
<li class="payments">
<div class="left">
<img src="images/select-false.png" class="transfer-img"><span>轉賬支付</span>
</div>
<div class="right">
<img src="images/icon-down.png" class="iconDow">
</div>
</li>
<div class="child-info">
<ul>
<li>
<div class="left">賬戶名</div>
<div class="right">aaa</div>
</li>
<li>
<div class="left">開戶行</div>
<div class="right">中國銀行</div>
</li>
<li>
<div class="left">賬号</div>
<div class="right">0000 1111 222 3333 4444</div>
</li>
</ul>
</div>
<li class="payments">
<div class="left">
<img src="images/select-false.png" class="qr-img"><span>二維碼支付</span>
</div>
<div class="right">
<img src="images/icon-down.png" class="iconDow">
</div>
</li>
<div class="last-info">
<img src="images/ma.png">
</div>
</ul>
</div>
<input type="hidden" id="order" value="${order.ordetaileid }" />
<script type="text/javascript" src="js/jquery.js" ></script>
<script type="text/javascript" src="js/carinformation.js" ></script>
<script>
var appId,timeStamp,nonceStr,pg,signType,paySign;
var ordetaileid=$("#order").val();
var initialcost=parseFloat($(".decime").eq(0).html());
var servicecost=parseFloat($(".decime").eq(1).html());
var sum=parseFloat($(".decime").eq(2).html());
$(".decime").eq(0).html(formatCurrency(initialcost));
$(".decime").eq(1).html(formatCurrency(servicecost));
$(".decime").eq(2).html(formatCurrency(sum));
(function(){
//開關事件
$("#onOff").on("touchend",function(){
if($(this).hasClass("active")){
$(this).attr("src","images/On.png");
$(this).removeClass("active");
}else{
$(this).attr("src","images/Off.png");
$(this).addClass("active");
}
$(".address").stop().slideToggle();
})
//支付事件
$(".payments").on("touchend",function(){
var judgeClass = $(this).find(".left img").attr("class");
$(this).find(".left img").attr("src","images/select-true.png").end().siblings("li").find(".left img").attr("src","images/select-false.png");
if(judgeClass == "weixin-img"){
$(".last-info").stop().slideUp();
$(".child-info").stop().slideUp();
$(this).find(".right img").attr("src","images/icon-right.png");
//alert("微信支付");
//微信支付
pay();
}else if(judgeClass == "transfer-img"){//轉賬支付
if($(".child-info").is(":visible")){
$(".child-info").stop().slideUp();
$(this).find(".left img").attr("src","images/select-false.png");
$(this).find(".right .iconDow").attr("src","images/icon-down.png");
}else{
$(".child-info").stop().slideDown();
$(this).find(".right .iconDow").attr("src","images/icon-up.png").end().siblings("li").find(".right .iconDow").attr("src","images/icon-down.png");
}
$(".last-info").stop().slideUp();
}else if(judgeClass == "qr-img"){//二維碼支付
if($(".last-info").is(":visible")){
$(".last-info").stop().slideUp();
$(this).find(".left img").attr("src","images/select-false.png");
$(this).find(".right .iconDow").attr("src","images/icon-down.png");
}else{
$(".last-info").stop().slideDown();
$(this).find(".right .iconDow").attr("src","images/icon-up.png").end().siblings("li").find(".right .iconDow").attr("src","images/icon-down.png");
}
$(".child-info").stop().slideUp();
}
})
})();
function pay() {
$.ajax({
url:"getPaymentOrder",
dataType:"json",
async:false,
data:"ordetaileid="+ordetaileid+"&money="+sum,
success:function(data){
if(data==1){
alert("該訂單已支付");//調用支付查詢接口
$.ajax({
url:"findWXOrder",
dataType:"json",
async:false,
data:"ordetaileid="+ordetaileid,
success:function(v){
if(v==2){
window.location="userSignList.html";//支付成功,跳轉至簽約界面
return;
}else{
return;
}
}
})
return;
}
appId=data.appId;
timeStamp=data.timeStamp;
nonceStr=data.nonceStr;
pg=data.package;
signType=data.signType;
paySign=data.paySign;
}
})
if (typeof WeixinJSBridge == "undefined"){//非微信浏覽器
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{//微信内部浏覽器
onBridgeReady();
}
window.event.returnValue = false;
return false;
}
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公衆号名稱,由商戶傳入
"timeStamp":timeStamp, //時間戳,自1970年以來的秒數
"nonceStr":nonceStr, //随機串
"package":pg,
"signType":signType, //微信簽名方式:
"paySign":paySign //微信簽名
},
function(res){
//支付驗證簽名失敗!
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
alert("支付成功");
window.location="userSignList.html";
}// 使用以上方式判斷前端傳回,微信團隊鄭重提示:res.err_msg将在使用者支付成功後傳回 ok,但并不保證它絕對可靠。
}
);
}
</script>
</body>
</html>
背景業務類PaymentOrderAction,此處注意兩次簽名(下單和擷取prepay_id後再次簽名)都要用MD5加密,簽名用到的key是商戶背景的密鑰,本人之前用的是APPSECRET生成訂單一直失敗。getPaymentResult()此方法擷取支付結果并可操作自己的業務邏輯,然後将傳回結果傳到前台。若支付成功,前台res.err_msg == "get_brand_wcpay_request:ok"。同時也可調用查詢訂單接口路徑findWXOrder和關閉訂單接口路徑wxCloseorder
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSONObject;
import com.rybsj.entity.PaymentOrder;
import com.rybsj.service.PaymentOrderService;
import com.rybsj.tools.HttpKit;
import com.rybsj.tools.WXAuthUtil;
import com.rybsj.tools.WXPayUtil;
@Controller
public class PaymentOrderAction extends BaseAction{
@Resource(name="paymentOrderService")
private PaymentOrderService paymentImpl;
/**
* 點選微信支付彈出支付頁面并擷取支付參數
* @param request
* @param money
* @return
* @throws Exception
*/
@RequestMapping("/getPaymentOrder")
@ResponseBody
public String getPaymentOrder(HttpServletRequest request,int ordetaileid,String sum) throws Exception {
Map<String,String> wxUserMap = (Map<String,String>)request.getSession().getAttribute("wxUserMap");
String openId=wxUserMap.get("openid");
PaymentOrder order=paymentImpl.getPaymentOrderOrdetaileid(ordetaileid);
Map<String, String> payMap=null;
//String openId="oW-bM0tabXXrrNzfjeq4jNlahGJQ";
//double sum2=mul(Double.parseDouble(sum),100);//金額轉分
//System.out.println(sum2);
if(order==null||order.getStatus()==3) {//該訂單不存在或失效
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("appid", WXAuthUtil.APPID);
paraMap.put("body", "測試購買支付");
paraMap.put("mch_id", WXAuthUtil.MCH_ID);//商戶id
paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
paraMap.put("notify_url", "---------/getPaymentResult");// 此路徑是微信伺服器調用支付結果通知路徑
paraMap.put("openid", openId);
paraMap.put("out_trade_no", ordetaileid+"");
//paraMap.put("spbill_create_ip", "123.12.12.123");
paraMap.put("spbill_create_ip", "192.168.1.123");
paraMap.put("total_fee", "1"); //金額
paraMap.put("trade_type", "JSAPI"); //交易類型(公衆号支付)
System.out.println(paraMap.toString());
String sign =WXPayUtil.generateSignature(paraMap, WXAuthUtil.KEY);
paraMap.put("sign", sign);
// 統一下單 https://api.mch.weixin.qq.com/pay/unifiedorder
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String xml = WXPayUtil.GetMapToXML(paraMap);
String xmlStr = HttpKit.post(url, xml);
System.out.println("xml="+xmlStr);
// 微信預付訂單id
String prepay_id = "";
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
if ("SUCCESS".equals(map.get("return_code"))&&"SUCCESS".equals(map.get("result_code"))) {
//Map<String, String> map = doXMLParse(xmlStr);
prepay_id = map.get("prepay_id");
} else {
System.out.println("調用微信支付出錯,傳回狀态碼:"+map.get("return_code")+",傳回資訊:"+map.get("return_msg"));
return JSONObject.toJSONString(2);//調用微信支付出錯
}
//保險系統訂單入庫
PaymentOrder payOrder=WXPayUtil.getPaymentOrders(paraMap);
payOrder.setPrepay_id(prepay_id);
payOrder.setTime_start(getTimesMiao());
payOrder.setStatus(1);
if(order==null)paymentImpl.addPaymentOrder(payOrder);
else if(order.getStatus()==3) paymentImpl.updatePaymentOrder(payOrder);
//int pid=payOrder.getPid();//商戶預付訂單id
//
String timeStamp = WXPayUtil.getCurrentTimestamp()+"";//擷取目前時間戳(時分秒)
String nonceStr=WXPayUtil.generateNonceStr();
payMap = new HashMap<String, String>();
payMap.put("appId", paraMap.get("appid"));
payMap.put("timeStamp", timeStamp);
payMap.put("nonceStr", nonceStr);
payMap.put("signType", "MD5");
payMap.put("package", "prepay_id=" + prepay_id);
String paySign = WXPayUtil.generateSignature(payMap, WXAuthUtil.KEY);
System.out.println(paySign);
payMap.put("paySign", paySign);
//添加商戶系統訂單傳到前台的資料appid,timestamp,noncestr,signType,package
payOrder.setNonce_str(nonceStr);
payOrder.setTimestamp(timeStamp);
payOrder.setSignType("MD5");
payOrder.setPackages(payMap.get("package"));
payOrder.setPaySign(paySign);
paymentImpl.updatePaymentOrder(payOrder);
}else if(order.getStatus()==1){//訂單未支付,
payMap = WXPayUtil.getMaptoOrder(order);
}else {
//該訂單已支付,但由于各種原因未跳轉至指定頁面
return JSONObject.toJSONString(1);//訂單已支付
}
String str=JSONObject.toJSONString(payMap);
System.out.println(str);
return str;
}
/**
* 支付結果通知頁面
* @throws Exception
*/
@RequestMapping("/getPaymentResult")
@ResponseBody
public String getPaymentResult(HttpServletRequest request,HttpServletResponse response) throws Exception {
String out_trade_no=null;
String return_code =null;
String result_code=null;
Map<String, Object> resultMap=null;
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();
String resultStr = new String(outSteam.toByteArray(),"utf-8");
System.out.println(resultStr);
//logger.info("支付成功的回調:"+resultStr);
resultMap = WXPayUtil.parseXmlToList(resultStr);
request.setAttribute("out_trade_no", out_trade_no);
//通知微信.異步确認成功.必寫.不然微信會一直通知背景.八次之後就認為交易失敗了.
//response.getWriter().write(RequestHandler.setXML("SUCCESS", ""));
} catch (Exception e) {
//logger.error("微信回調接口出現錯誤:",e);
try {
//response.getWriter().write(RequestHandler.setXML("FAIL", "error"));
} catch (Exception e1) {
e1.printStackTrace();
}
}
PaymentOrder paymentOrder=new PaymentOrder();
Map<String,String> return_data = new HashMap<String,String>();
return_code=(String)resultMap.get("return_code");
out_trade_no=(String)resultMap.get("out_trade_no");
if(return_code.equals("SUCCESS")){
//支付成功的業務邏輯,如果支付成功,則修改商戶訂單的狀态,
paymentOrder=paymentImpl.getPaymentOrderOrdetaileid(Integer.parseInt(out_trade_no));
if(paymentOrder==null) {
return_data.put("return_code", "FAIL");
return_data.put("return_msg", "訂單不存在");
return WXPayUtil.mapToXml(return_data);
}else {
result_code=(String)resultMap.get("result_code");
if(result_code.equals("SUCCESS")) {
if(paymentOrder.getStatus()==2) {
return_data.put("return_code", "SUCCESS");
return_data.put("return_msg", "OK");
return WXPayUtil.mapToXml(return_data);
}else {
//String sign = resultMap.get("sign").toString();
String total_fee = resultMap.get("total_fee").toString();//訂單金額
if(!(paymentOrder.getTotal_fee()+"").equals(total_fee)) {
return_data.put("return_code", "FAIL");
return_data.put("return_msg", "金額異常");
return WXPayUtil.mapToXml(return_data);
}else {
String time_end = resultMap.get("time_end").toString();
//String settlement_total_fee = resultMap.get("settlement_total_fee").toString();
paymentOrder.setStatus(2);
paymentOrder.setTime_expire(time_end);
paymentOrder.setOrdetaileid(Integer.parseInt(out_trade_no));
//paymentOrder.setTime_expire(System.currentTimeMillis()+"");
int i=paymentImpl.updatePaymentOrder(paymentOrder);
if(i<=0) {
return_data.put("return_code", "FAIL");
return_data.put("return_msg", "更新訂單失敗");
return WXPayUtil.mapToXml(return_data);
}else {
return_data.put("return_code", "SUCCESS");
return_data.put("return_msg", "OK");
return WXPayUtil.mapToXml(return_data);
}
}
}
}
}
}else{
//支付失敗的業務邏輯
if(paymentOrder!=null) {
paymentOrder.setStatus(1);
paymentImpl.updatePaymentOrder(paymentOrder);
}
return_data.put("return_code", "FAIL");
return_data.put("return_msg",resultMap.get("return_msg").toString());
return WXPayUtil.mapToXml(return_data);
}
String xml = WXPayUtil.mapToXml(return_data);
System.out.println(xml);
return xml;
}
/**
* 查詢訂單接口
* @throws Exception
*/
@RequestMapping("/findWXOrder")
@ResponseBody
public String findWXOrder(int ordetaileid) throws Exception {
String appid=WXAuthUtil.APPID;
String mch_id=WXAuthUtil.MCH_ID;
PaymentOrder paymentOrder=paymentImpl.getPaymentOrderOrdetaileid(ordetaileid);
//String transaction_id="wx20180320140726da1319d3f80503302647";//微信訂單号
String nonce_str=WXPayUtil.generateNonceStr();
String sign_type="MD5";
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("appid", appid);
paraMap.put("mch_id", mch_id);
paraMap.put("out_trade_no", ordetaileid+"");
paraMap.put("nonce_str", nonce_str);
paraMap.put("sign_type", sign_type);
String sign=WXPayUtil.generateSignature(paraMap, WXAuthUtil.KEY);
paraMap.put("sign", sign);
String url = "https://api.mch.weixin.qq.com/pay/orderquery";
String xml = WXPayUtil.GetMapToXML(paraMap);
String xmlStr = HttpKit.post(url, xml);
System.out.println(xmlStr);
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
if(map.get("return_code").equals("FAIL")) {
paymentOrder.setStatus(1);
paymentImpl.updatePaymentOrder(paymentOrder);
return JSONObject.toJSONString(1);//微信支付失敗,修改商戶訂單狀态為待支付
}else if(map.get("return_code").equals("SUCCESS")) {
if(map.get("result_code").equals("FAIL")) {
paymentOrder.setStatus(1);
paymentImpl.updatePaymentOrder(paymentOrder);
return JSONObject.toJSONString(1);//微信支付失敗,修改商戶訂單狀态為待支付
}else {
if(map.get("trade_state").equals("SUCCESS")) {
return JSONObject.toJSONString(2);//微信支付成功,将資訊傳給前端并跳轉至簽約界面
}else if(map.get("trade_state").equals("CLOSED")) {
paymentOrder.setStatus(3);
paymentImpl.updatePaymentOrder(paymentOrder);
return JSONObject.toJSONString(1);//微信支付失敗,修改商戶訂單狀态為已失效
}else {
paymentOrder.setStatus(1);
paymentImpl.updatePaymentOrder(paymentOrder);
return JSONObject.toJSONString(1);//微信支付失敗,修改商戶訂單狀态為待支付
}
}
}
return JSONObject.toJSONString(1);
}
/**
* 微信關閉訂單
* @throws Exception
*/
@RequestMapping("/wxCloseorder")
@ResponseBody
public String wxCloseorder(String out_trade_no,String nonce_str) throws Exception {
String appid=WXAuthUtil.APPID;
String mch_id=WXAuthUtil.MCH_ID;
String url="https://api.mch.weixin.qq.com/pay/closeorder";
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("appid", appid);
paraMap.put("mch_id", mch_id);
paraMap.put("out_trade_no", out_trade_no);
paraMap.put("nonce_str", nonce_str);
String sign=WXPayUtil.generateSignature(paraMap, WXAuthUtil.KEY);
paraMap.put("sign", sign);
String xml = WXPayUtil.GetMapToXML(paraMap);
String xmlStr = HttpKit.post(url, xml);
System.out.println(xmlStr);
//Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
return "";
}
}
剛開始這樣做試了無數次支付頁面就是出不來,直接彈出支付失敗。在網上找了下可能有以下原因:
1.支付授權目錄未設定或與自己使用的notify_url不對應
2.簽名失敗或簽名錯誤
3.參數大小寫錯誤
4.ip白名單未設定或使用者授權失敗
接下來便一一排除,首先再次進入微信公衆平台驗證了下設定的支付授權目錄、ip白名單以及APPID、APPSECRET、MCH_ID和KEY,确定無誤後便排除了1和4,在通過官方文檔可排除3,最後通過https://pay.weixin.qq.com/wiki/tools/signverify/官方調試工具,将自己生成的簽名和官方生成的簽名做了對比,發現一模一樣,排除了2。排除完之後居然還沒發現問題所在,自己又用手機打開支付連結試了幾次,支付頁面依然出不來。心想:努力了這麼久難道要功虧一篑嗎。好在中午睡覺的時候想到還有一種可能情況,既然是公衆号支付是不是隻有在公衆号内才會出現支付頁面。果不其然,下午把項目重新發版,在公衆号居然能打開了,卡在此處兩天了終于成功了,此時内心的激動是無法言語的。總的來說,開發微信支付還是先要把官方文檔看清楚,把流程弄清楚,不然開發起來會出現很多坑。
另外,支付成功後商戶背景會顯示支付記錄,也可以進行退款等功能
如果有不對的地方請留言指正!