java服務端,微信支付功能的實現
阿新 • • 發佈:2018-12-30
接入微信的支付功能在java服務端,現在記錄下來這個過程,方便以後用到。程式碼都是參考網路,功能可以實現
實際參考https://github.com/wxpay/WXPay-SDK-Java
①,寫一個介面,作為引數配置的介面,當然也可以不用介面,直接在類中以屬性的方式配置相應的引數。
public interface WXPayConfig { /** * 獲取 App ID * * @return App ID */ public String getAppID(); /** * 獲取 Mch ID * * @return Mch ID */ public String getMchID(); /** * 獲取 API 金鑰 * * @return API金鑰 */ public String getKey(); /** * 獲取商戶證書內容 * * @return 商戶證書內容 */ public InputStream getCertStream(); /** * HTTP(S) 連線超時時間,單位毫秒 * * @return */ public int getHttpConnectTimeoutMs(); /** * HTTP(S) 讀資料超時時間,單位毫秒 * * @return */ public int getHttpReadTimeoutMs(); }
② 寫一個類實現上面的介面,將具體的微信支付的引數配置
public class MyConfig implements WXPayConfig{ public static String service="http://10.60.7.92/AppServer/WxCallbackServlet";//支付完成後的非同步回撥 private byte[] certData; public MyConfig() throws Exception { String certPath = "C:/......../apiclient_cert.p12";//從微信商戶平臺下載的安全證書存放的目錄 File file = new File(certPath); InputStream certStream = new FileInputStream(file); this.certData = new byte[(int) file.length()]; certStream.read(this.certData); certStream.close(); } public String getAppID() { return "wx1234567890987";//如初appid } public String getMchID() { return "12345678";//商戶號 } public String getKey() { return "I1234556789uyertgerger54";//金鑰 } public InputStream getCertStream() { ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData); return certBis; } public int getHttpConnectTimeoutMs() { return 8000; } public int getHttpReadTimeoutMs() { return 10000; } }
③部分請求連線個常量的配置
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"; public static final String MICROPAY_URL = "https://api.mch.weixin.qq.com/pay/micropay"; public static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; public static final String ORDERQUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery"; public static final String REVERSE_URL = "https://api.mch.weixin.qq.com/secapi/pay/reverse"; public static final String CLOSEORDER_URL = "https://api.mch.weixin.qq.com/pay/closeorder"; public static final String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund"; public static final String REFUNDQUERY_URL = "https://api.mch.weixin.qq.com/pay/refundquery"; public static final String DOWNLOADBILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill"; public static final String REPORT_URL = "https://api.mch.weixin.qq.com/payitil/report"; public static final String SHORTURL_URL = "https://api.mch.weixin.qq.com/tools/shorturl"; public static final String AUTHCODETOOPENID_URL = "https://api.mch.weixin.qq.com/tools/authcodetoopenid"; // sandbox public static final String SANDBOX_MICROPAY_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/micropay"; public static final String SANDBOX_UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/unifiedorder"; public static final String SANDBOX_ORDERQUERY_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery"; public static final String SANDBOX_REVERSE_URL = "https://api.mch.weixin.qq.com/sandboxnew/secapi/pay/reverse"; public static final String SANDBOX_CLOSEORDER_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/closeorder"; public static final String SANDBOX_REFUND_URL = "https://api.mch.weixin.qq.com/sandboxnew/secapi/pay/refund"; public static final String SANDBOX_REFUNDQUERY_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/refundquery"; public static final String SANDBOX_DOWNLOADBILL_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/downloadbill"; public static final String SANDBOX_REPORT_URL = "https://api.mch.weixin.qq.com/sandboxnew/payitil/report"; public static final String SANDBOX_SHORTURL_URL = "https://api.mch.weixin.qq.com/sandboxnew/tools/shorturl"; public static final String SANDBOX_AUTHCODETOOPENID_URL = "https://api.mch.weixin.qq.com/sandboxnew/tools/authcodetoopenid"; }
④微信支付的核心程式碼,用於簽名和驗證簽名的一些方法
import javax.net.ssl.*;
import com.github.wxpay.sdk.WXPayConstants.SignType;
import com.ruchuapp.tools.DateUtil;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class WXPay {
private WXPayConfig config;
private SignType signType;
private boolean useSandbox;
public WXPay(final WXPayConfig config) {
this(config, SignType.MD5, false);
}
public WXPay(final WXPayConfig config, final SignType signType) {
this(config, signType, false);
}
public WXPay(final WXPayConfig config, final SignType signType, final boolean useSandbox) {
this.config = config;
this.signType = signType;
this.useSandbox = useSandbox;
}
/**
* 向 Map 中新增 appid、mch_id、nonce_str、sign_type、sign <br>
* 該函式適用於商戶適用於統一下單等介面,不適用於紅包、代金券介面
*
* @param reqData
* @return
* @throws Exception
*/
public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {
reqData.put("appid", config.getAppID());
reqData.put("mch_id", config.getMchID());
reqData.put("nonce_str", WXPayUtil.generateNonceStr());//獲取隨機字串
if (SignType.MD5.equals(this.signType)) {
reqData.put("sign_type", WXPayConstants.MD5);
}
else if (SignType.HMACSHA256.equals(this.signType)) {
reqData.put("sign_type", WXPayConstants.HMACSHA256);
}
System.out.println("this.signType"+this.signType);
reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));
return reqData;
}
/**
* 判斷xml資料的sign是否有效,必須包含sign欄位,否則返回false。
*
* @param reqData 向wxpay post的請求資料
* @return 簽名是否有效
* @throws Exception
*/
public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
// 返回資料的簽名方式和請求中給定的簽名方式是一致的
return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);
}
/**
* 判斷支付結果通知中的sign是否有效
*
* @param reqData 向wxpay post的請求資料
* @return 簽名是否有效
* @throws Exception
*/
public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {
String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);
SignType signType;
if (signTypeInData == null) {
signType = SignType.MD5;
}
else {
signTypeInData = signTypeInData.trim();
if (signTypeInData.length() == 0) {
signType = SignType.MD5;
}
else if (WXPayConstants.MD5.equals(signTypeInData)) {
signType = SignType.MD5;
}
else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {
signType = SignType.HMACSHA256;
}
else {
throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));
}
}
return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType);
}
/**
* 不需要證書的請求
* @param strUrl String
* @param reqData 向wxpay post的請求資料
* @param connectTimeoutMs 超時時間,單位是毫秒
* @param readTimeoutMs 超時時間,單位是毫秒
* @return API返回資料
* @throws Exception
*/
public String requestWithoutCert(String strUrl, Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String UTF8 = "UTF-8";
String reqBody = WXPayUtil.mapToXml(reqData);
URL httpUrl = new URL(strUrl);
HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setConnectTimeout(connectTimeoutMs);
httpURLConnection.setReadTimeout(readTimeoutMs);
httpURLConnection.connect();
OutputStream outputStream = httpURLConnection.getOutputStream();
outputStream.write(reqBody.getBytes(UTF8));
// if (httpURLConnection.getResponseCode()!= 200) {
// throw new Exception(String.format("HTTP response code is %d, not 200", httpURLConnection.getResponseCode()));
// }
//獲取內容
InputStream inputStream = httpURLConnection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
final StringBuffer stringBuffer = new StringBuffer();
String line = null;
while ((line = bufferedReader.readLine()) != null) {
stringBuffer.append(line);
}
String resp = stringBuffer.toString();
if (stringBuffer!=null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream!=null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream!=null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// if (httpURLConnection!=null) {
// httpURLConnection.disconnect();
// }
return resp;
}
/**
* 需要證書的請求
* @param strUrl String
* @param reqData 向wxpay post的請求資料 Map
* @param connectTimeoutMs 超時時間,單位是毫秒
* @param readTimeoutMs 超時時間,單位是毫秒
* @return API返回資料
* @throws Exception
*/
public String requestWithCert(String strUrl, Map<String, String> reqData,
int connectTimeoutMs, int readTimeoutMs) throws Exception {
String UTF8 = "UTF-8";
String reqBody = WXPayUtil.mapToXml(reqData);
URL httpUrl = new URL(strUrl);
char[] password = config.getMchID().toCharArray();
InputStream certStream = config.getCertStream();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(certStream, password);
// 例項化金鑰庫 & 初始化金鑰工廠
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password);
// 建立SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setConnectTimeout(connectTimeoutMs);
httpURLConnection.setReadTimeout(readTimeoutMs);
httpURLConnection.connect();
OutputStream outputStream = httpURLConnection.getOutputStream();
outputStream.write(reqBody.getBytes(UTF8));
// if (httpURLConnection.getResponseCode()!= 200) {
// throw new Exception(String.format("HTTP response code is %d, not 200", httpURLConnection.getResponseCode()));
// }
//獲取內容
InputStream inputStream = httpURLConnection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
final StringBuffer stringBuffer = new StringBuffer();
String line = null;
while ((line = bufferedReader.readLine()) != null) {
stringBuffer.append(line);
}
String resp = stringBuffer.toString();
if (stringBuffer!=null) {
try {
bufferedReader.close();
} catch (IOException e) {
// e.printStackTrace();
}
}
if (inputStream!=null) {
try {
inputStream.close();
} catch (IOException e) {
// e.printStackTrace();
}
}
if (outputStream!=null) {
try {
outputStream.close();
} catch (IOException e) {
// e.printStackTrace();
}
}
if (certStream!=null) {
try {
certStream.close();
} catch (IOException e) {
// e.printStackTrace();
}
}
// if (httpURLConnection!=null) {
// httpURLConnection.disconnect();
// }
return resp;
}
/**
* 處理 HTTPS API返回資料,轉換成Map物件。return_code為SUCCESS時,驗證簽名。
* @param xmlStr API返回的XML格式資料
* @return Map型別資料
* @throws Exception
*/
public Map<String, String> processResponseXml(String xmlStr) throws Exception {
String RETURN_CODE = "return_code";
String return_code;
Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
if (respData.containsKey(RETURN_CODE)) {
return_code = respData.get(RETURN_CODE);
}
else {
throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
}
if (return_code.equals(WXPayConstants.FAIL)) {
return respData;
}
else if (return_code.equals(WXPayConstants.SUCCESS)) {
if (this.isResponseSignatureValid(respData)) {
return respData;
}
else {
throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
}
}
else {
throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
}
}
/**
* 作用:統一下單<br>
* 場景:公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料
* @return API返回資料
* @throws Exception
*/
public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception {
return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
}
/**
* 作用:統一下單<br>
* 場景:公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料
* @param connectTimeoutMs 連線超時時間,單位是毫秒
* @param readTimeoutMs 讀超時時間,單位是毫秒
* @return API返回資料
* @throws Exception
*/
public Map<String, String> unifiedOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String url;
if (this.useSandbox) {
url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL;
}
else {
url = WXPayConstants.UNIFIEDORDER_URL;
}
System.out.println(url);
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
System.out.println("this.processResponseXml(respXml)"+respXml);
return this.processResponseXml(respXml);
}
/**
* 作用:查詢訂單<br>
* 場景:刷卡支付、公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料
* @return API返回資料
* @throws Exception
*/
public Map<String, String> orderQuery(Map<String, String> reqData) throws Exception {
return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
}
/**
* 作用:查詢訂單<br>
* 場景:刷卡支付、公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料 int
* @param connectTimeoutMs 連線超時時間,單位是毫秒
* @param readTimeoutMs 讀超時時間,單位是毫秒
* @return API返回資料
* @throws Exception
*/
public Map<String, String> orderQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String url;
if (this.useSandbox) {
url = WXPayConstants.SANDBOX_ORDERQUERY_URL;
}
else {
url = WXPayConstants.ORDERQUERY_URL;
}
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
return this.processResponseXml(respXml);
}
/**
* 作用:撤銷訂單<br>
* 場景:刷卡支付
* @param reqData 向wxpay post的請求資料
* @return API返回資料
* @throws Exception
*/
public Map<String, String> reverse(Map<String, String> reqData) throws Exception {
return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
}
/**
* 作用:撤銷訂單<br>
* 場景:刷卡支付<br>
* 其他:需要證書
* @param reqData 向wxpay post的請求資料
* @param connectTimeoutMs 連線超時時間,單位是毫秒
* @param readTimeoutMs 讀超時時間,單位是毫秒
* @return API返回資料
* @throws Exception
*/
public Map<String, String> reverse(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String url;
if (this.useSandbox) {
url = WXPayConstants.SANDBOX_REVERSE_URL;
}
else {
url = WXPayConstants.REVERSE_URL;
}
String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
return this.processResponseXml(respXml);
}
/**
* 作用:關閉訂單<br>
* 場景:公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料
* @return API返回資料
* @throws Exception
*/
public Map<String, String> closeOrder(Map<String, String> reqData) throws Exception {
return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
}
/**
* 作用:關閉訂單<br>
* 場景:公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料
* @param connectTimeoutMs 連線超時時間,單位是毫秒
* @param readTimeoutMs 讀超時時間,單位是毫秒
* @return API返回資料
* @throws Exception
*/
public Map<String, String> closeOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String url;
if (this.useSandbox) {
url = WXPayConstants.SANDBOX_CLOSEORDER_URL;
}
else {
url = WXPayConstants.CLOSEORDER_URL;
}
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
return this.processResponseXml(respXml);
}
/**
* 作用:申請退款<br>
* 場景:刷卡支付、公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料
* @return API返回資料
* @throws Exception
*/
public Map<String, String> refund(Map<String, String> reqData) throws Exception {
return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
}
/**
* 作用:申請退款<br>
* 場景:刷卡支付、公共號支付、掃碼支付、APP支付<br>
* 其他:需要證書
* @param reqData 向wxpay post的請求資料
* @param connectTimeoutMs 連線超時時間,單位是毫秒
* @param readTimeoutMs 讀超時時間,單位是毫秒
* @return API返回資料
* @throws Exception
*/
public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String url;
if (this.useSandbox) {
url = WXPayConstants.SANDBOX_REFUND_URL;
}
else {
url = WXPayConstants.REFUND_URL;
}
String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
return this.processResponseXml(respXml);
}
/**
* 作用:退款查詢<br>
* 場景:刷卡支付、公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料
* @return API返回資料
* @throws Exception
*/
public Map<String, String> refundQuery(Map<String, String> reqData) throws Exception {
return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
}
/**
* 作用:退款查詢<br>
* 場景:刷卡支付、公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料
* @param connectTimeoutMs 連線超時時間,單位是毫秒
* @param readTimeoutMs 讀超時時間,單位是毫秒
* @return API返回資料
* @throws Exception
*/
public Map<String, String> refundQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String url;
if (this.useSandbox) {
url = WXPayConstants.SANDBOX_REFUNDQUERY_URL;
}
else {
url = WXPayConstants.REFUNDQUERY_URL;
}
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
return this.processResponseXml(respXml);
}
/**
* 作用:對賬單下載(成功時返回對賬單資料,失敗時返回XML格式資料)<br>
* 場景:刷卡支付、公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料
* @return API返回資料
* @throws Exception
*/
public Map<String, String> downloadBill(Map<String, String> reqData) throws Exception {
return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
}
/**
* 作用:對賬單下載<br>
* 場景:刷卡支付、公共號支付、掃碼支付、APP支付<br>
* 其他:無論是否成功都返回Map。若成功,返回的Map中含有return_code、return_msg、data,
* 其中return_code為`SUCCESS`,data為對賬單資料。
* @param reqData 向wxpay post的請求資料
* @param connectTimeoutMs 連線超時時間,單位是毫秒
* @param readTimeoutMs 讀超時時間,單位是毫秒
* @return 經過封裝的API返回資料
* @throws Exception
*/
public Map<String, String> downloadBill(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String url;
if (this.useSandbox) {
url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL;
}
else {
url = WXPayConstants.DOWNLOADBILL_URL;
}
String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim();
Map<String, String> ret;
// 出現錯誤,返回XML資料
if (respStr.indexOf("<") == 0) {
ret = WXPayUtil.xmlToMap(respStr);
}
else {
// 正常返回csv資料
ret = new HashMap<String, String>();
ret.put("return_code", WXPayConstants.SUCCESS);
ret.put("return_msg", "ok");
ret.put("data", respStr);
}
return ret;
}
/**
* 作用:交易保障<br>
* 場景:刷卡支付、公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料
* @return API返回資料
* @throws Exception
*/
public Map<String, String> report(Map<String, String> reqData) throws Exception {
return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
}
/**
* 作用:交易保障<br>
* 場景:刷卡支付、公共號支付、掃碼支付、APP支付
* @param reqData 向wxpay post的請求資料
* @param connectTimeoutMs 連線超時時間,單位是毫秒
* @param readTimeoutMs 讀超時時間,單位是毫秒
* @return API返回資料
* @throws Exception
*/
public Map<String, String> report(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String url;
if (this.useSandbox) {
url = WXPayConstants.SANDBOX_REPORT_URL;
}
else {
url = WXPayConstants.REPORT_URL;
}
String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
return WXPayUtil.xmlToMap(respXml);
}
}
⑤工具類,用於Map和xml的相互轉換及md5隨機字串,簽名等等
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.*;
import java.security.MessageDigest;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
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 com.github.wxpay.sdk.WXPayConstants.SignType;
public class WXPayUtil {
/**
* XML格式字串轉換為Map
*
* @param strXML XML字串
* @return XML資料轉換後的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
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) {
}
return data;
}
/**
* 將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).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)) {
System.out.println("HMACSHA256"+HMACSHA256(sb.toString(), key));
return HMACSHA256(sb.toString(), key);
}
else {
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);
}
/**
* 生成 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();
}
}
⑥網路請求服務端處理訂單簽名,最終將訂單返回給客戶端
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONObject;
import com.github.wxpay.sdk.WXPayConstants.SignType;
import com.ruchuapp.alipay.UtilDate;
import com.ruchuapp.service.WxAndAliPayService;
import com.ruchuapp.tools.DateUtil;
/**
* 微信支付請求連結
*/
@WebServlet("/WxPayServlet")
public class WxPayServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public WxPayServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
String ip = request.getParameter("ip");
String userid = request.getParameter("userid");
String cardid = request.getParameter("cardid");
String mess = getMess(ip,cardid,userid);
response.getWriter().print(mess);
}
private String getMess(String ip, String cardid,String userid) {
String uid = WxAndAliPayService.getuid(userid);
JSONObject orderInfo = WxAndAliPayService.getOrderInfo(cardid);
JSONObject joo=new JSONObject();
try {
String cardname=orderInfo.getString("cardname");
String cardprice=orderInfo.getString("cardprice");
System.out.println("money"+cardprice);
float cardprice1=Float.parseFloat(cardprice)*100;//微信的支付單位是分所以要轉換一些單位
int cardmoney=(int) cardprice1;
System.out.println("money1"+cardmoney);
String totalproce=String.valueOf(cardmoney);
String orderNum = UtilDate.getOrderNum();
//插入一條訂單記錄
WxAndAliPayService.insertOrderInfo(cardname,uid,orderNum,cardprice,"微信支付購買會員卡");
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config);
Map<String, String> data = new HashMap<String, String>();
data.put("body", "哈哈哈-"+cardname);
data.put("out_trade_no", orderNum);//生成隨機訂單號
data.put("total_fee", totalproce);
data.put("spbill_create_ip",ip);
data.put("notify_url", MyConfig.service);
data.put("trade_type", "APP"); // 此處app支付
Map<String, String> resp = wxpay.unifiedOrder(data);
JSONObject jo=new JSONObject(resp);
Map<String, String> data1 = new HashMap<String, String>();
if(jo.get("result_code").equals("SUCCESS") &&jo.get("return_code").equals("SUCCESS")){
data1.put("appid",config.getAppID());
data1.put("partnerid", UtilDate.getOrderNum());//生成隨機訂單號
data1.put("prepayid", jo.getString("prepay_id"));
data1.put("package","Sign=WXPay");
data1.put("noncestr",jo.getString("nonce_str"));
data1.put("timestamp",DateUtil.getTimestamp());
String sign = WXPayUtil.generateSignature(data1,config.getKey(),SignType.MD5);//再簽名一次
System.out.println(sign);
data1.put("sign", sign);
}
JSONObject jj=new JSONObject(data1);
joo.put("info", jj);
} catch (Exception e) {
e.printStackTrace();
}
return joo.toString();
}
}
⑦回撥事件,更新訂單狀態
import java.io.BufferedReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.client.ResponseHandler;
import org.json.JSONException;
import org.json.JSONObject;
import com.ruchuapp.service.WxAndAliPayService;
import com.ruchuapp.servlet.my.BuyVIPServlet;
import com.ruchuapp.tools.DbUtil;
/**
* 微信支付成功後的回撥連結
*/
@WebServlet("/WxCallbackServlet")
public class WxCallbackServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public WxCallbackServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("**********************************************");
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
System.out.println("來到了這裡");
BufferedReader reader = request.getReader();
String line="";
StringBuffer inputString=new StringBuffer();
while((line=reader.readLine())!=null){
inputString.append(line);
}
request.getReader().close();
System.out.println("接受到的報文"+inputString.toString());
String result_code = "";
String return_code = "";
String out_trade_no = "";
try {
Map<String, String> map = WXPayUtil.xmlToMap(inputString.toString());
result_code = map.get("result_code");
out_trade_no = map.get("out_trade_no");
return_code = map.get("return_code");
MyConfig config=new MyConfig();
//重新簽名判斷簽名是否正確
boolean signatureValid = WXPayUtil.isSignatureValid(map,config.getKey());
if(signatureValid){//簽名正確
//更新資料庫 1,訂單 2,userid表
//告訴微信伺服器,我收到資訊了,不要在呼叫回撥action了
boolean updateOrderInfo = WxAndAliPayService.updateOrderInfo(out_trade_no);
String returnXML = returnXML(return_code);
response.getWriter().write(returnXML);
}else{
String returnXML = returnXML("FAIL");
response.getWriter().write(returnXML);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 返回給微信伺服器的訊息
* @param return_code
* @return
*/
private String returnXML(String return_code) {
return "<xml><return_code><![CDATA["
+ return_code
+ "]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}
}
以上程式碼是完整的處理微信支付在服務端的簽名等過程,