第一步:去支付寶新建沙箱應用並申請開通相應權限,也就是測試環境,完成後去https://auth.alipay.com/login/ant_sso_index.htm?goto=https%3A%2F%2Fopenhome.alipay.com%2Fplatform%2FappDaily.htm%3Ftab%3Dinfo查看本身的應用,在這裏面會有APPID,支付寶網關等參數,密鑰和支付寶公鑰按提示生成便可,這些參數在以後代碼中都會用到。php
第二步:本身服務器的代碼部分,在寫代碼以前,先導入馬爸爸爲咱們準備的SDK,Maven依賴以下,html
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.4.27.ALL</version>
</dependency>複製代碼
而後咱們得先知道整個支付流程是怎麼樣的。按照通常邏輯,前端拿着訂單ID請求後臺的統一下單接口,此接口整理數據返回信息給前端,前端拿此信息請求支付寶開始支付,支付完成後根據return_url跳轉前端頁面同步通知,根據notify_url調用服務器後臺接口異步通知。前端
下面進入正題:java
統一下單接口:從這裏開始纔是支付流程的第一步,整理數據給前端用以發起支付請求,這個接口裏面須要的參數能夠參考官方文檔https://docs.open.alipay.com/api(這個是全部API的文檔,找本身須要的接口看)。這裏重點提一下,return_url和notify_url,return_url:同步跳轉路徑,是支付完成後前端跳轉頁面的路徑,一般爲某個html頁面的路徑,這個跳轉只表示支付完成,意思是整個支付流程完成,並不表明支付成功或者失敗。而notify_url爲異步通知,通常爲後端controller的requestMapping,這個纔是真正通知支付成功或者失敗的接口,後端要寫一個接受支付寶返回信息的接口,下面再講。這個統一下單接口會根據不一樣的請求方式會返回不一樣的信息,GET請求返回的是json或xml,POST則是直接返回一個帶訂單信息的form表單。前端拿到統一下單接口返回的信息後請求支付便可。git
public void alipay(UserEntity userEntity, HttpServletResponse response, Long id) {算法
try {
//根據訂單ID從數據庫獲取訂單信息用於請求支付寶接口的數據封裝(這裏最好是把當前登陸的用戶一塊兒傳入sql查詢,防止查出非本人的訂單)
BookingOrderEntity bookingOrderEntity = userDao.getOrderDetail(userEntity.getId(), id);
//封裝公共參數
AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig.getGatewayUrl(), alipayConfig.getAppId(),
alipayConfig.getMerchantPrivateKey(), "json", AlipayConfig.charset, alipayConfig.getAlipayPublicKey(),
AlipayConfig.signType);
//建立API對應的request(手機網頁支付,APP支付均不一樣,此處根據本身需求更改)
AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
//在公共參數中設置前端同步回跳頁面和後臺異步通知路徑
alipayRequest.setReturnUrl(alipayConfig.getReturnUrl().replace("ID", bookingOrderEntity.getId().toString()));
alipayRequest.setNotifyUrl(alipayConfig.getNotifyUrl());
//填充業務參數,即訂單信息
String subject = bookingOrderEntity.getSnapshotHotelName() + "-" + bookingOrderEntity.getBookingNo();
alipayRequest.setBizContent("{" +
" \"out_trade_no\":" + bookingOrderEntity.getBookingNo() + "," +
" \"total_amount\":" + bookingOrderEntity.getPayPrice() + "," +
" \"subject\":\"" + subject + "\"," +
" \"product_code\":\"QUICK_WAP_WAY\"" +
" }");
//調用SDK生成表單
String form = alipayClient.pageExecute(alipayRequest).getBody();
response.setContentType("text/html;charset=utf-8");
//直接將完整的表單html輸出到頁面
response.getWriter().write(form);
} catch (AlipayApiException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}複製代碼
支付寶參數實體類:我是寫成了去配置文件拿信息的,各位也能夠直接寫成普通類放參數就行。sql
@Component
@ConfigurationProperties(prefix = "alipay")
public class AlipayConfig {
/**
* 應用ID,您的APPID,收款帳號既是您的APPID對應支付寶帳號
*/
@Value("${alipay.appId}")
private String appId;
/**
* 商戶私鑰,您的PKCS8格式RSA2私鑰
*/
@Value("${alipay.merchantPrivateKey}")
private String merchantPrivateKey;
/**
* 支付寶公鑰,查看地址:https://openhome.alipay.com/platform/keyManage.htm 對應APPID下的支付寶公鑰。
*/
@Value("${alipay.alipayPublicKey}")
private String alipayPublicKey;
/**
* 服務器異步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網能夠正常訪問
*/
@Value("${alipay.notifyUrl}")
private String notifyUrl;
/**
* 頁面跳轉同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網能夠正常訪問
*/
@Value("${alipay.return_url}")
private String returnUrl;
/**
* 支付寶網關
*/
@Value("${alipay.gatewayUrl}")
private String gatewayUrl;
/**
* 簽名方式
*/
public static String signType = "RSA2";
/**
* 字符編碼格式
*/
public static String charset = "utf-8";
public String getAppId() {
return appId;
}
public String getMerchantPrivateKey() {
return merchantPrivateKey;
}
public String getAlipayPublicKey() {
return alipayPublicKey;
}
public String getNotifyUrl() {
return notifyUrl;
}
public String getReturnUrl() {
return returnUrl;
}
public String getGatewayUrl() {
return gatewayUrl;
}
public void setAppId(String appId) {
this.appId = appId;
}
public void setMerchantPrivateKey(String merchantPrivateKey) {
this.merchantPrivateKey = merchantPrivateKey;
}
public void setAlipayPublicKey(String alipayPublicKey) {
this.alipayPublicKey = alipayPublicKey;
}
public void setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
}
public void setReturnUrl(String returnUrl) {
this.returnUrl = returnUrl;
}
public void setGatewayUrl(String gatewayUrl) {
this.gatewayUrl = gatewayUrl;
}
}複製代碼
POST返回的信息:是一個自動提交的form表單,前端直接加載便可。數據庫
釘釘內網穿透:return_url和notify_url都必須是外網可以訪問到的,若是在本地想要測試,能夠下載一個釘釘內網穿透工具https://open-doc.dingtalk.com/microapp/debug/ucof2g,這裏面以Mac爲例講了怎麼使用,windows操做:cd至git克隆下來的目錄,有mac和windows兩個版本,進入windows的,不須要chmode命令,這個命令是用來改權限的,啓動命令改成ding -config=ding.cfg -subdomain=a 8080便可。json
打開穿透工具:以下圖,a爲域名前綴,可自定義,8081爲你本身本地後臺服務器的端口。windows
開啓成功:以下圖,這裏好像只能使用http不能用https的,開啓成功後只需將notify_url改成: http://a.vaiwan.com:8081/xxx/xxx便可。
支付回調接口:支付寶給的機制是:支付完成後回調,收到咱們服務器返回SUCCESS後就中止調用,整個支付流程完成。若是15秒(這個數據不太記得了,能夠查看官方文檔)後還沒收到咱們返回給支付寶的SUCCESS則繼續調用,這個時間間隔會逐漸增加。
因此第一步,先根據支付寶返回的訂單號查詢咱們數據庫中該條信息有沒有被處理過,由於有可能出現這種狀況:假設回調間隔爲15秒,咱們在第14秒返回SUCCESS給支付寶,可是由於網絡緣由,第15秒的時候支付寶沒收到咱們的信息,它依然給咱們回調過來了,可是其實早在第14秒的時候咱們已經處理完這個訂單信息只是支付寶不知道而已,因此若是訂單已經被處理過,不管是支付成功仍是支付失敗,直接返回SUCCESS給支付寶告訴它,本爸爸已經知道了,退朝吧。
第二步,驗籤,支付寶返回的信息在request中,直接取出後驗籤。若是驗籤失敗,有多是其餘服務器發出的惡意攻擊,則返回failure給支付寶。驗籤經過後判斷APPID,付款金額,支付寶給我成功標誌等,修改訂單信息支付成功或失敗。
public void notify(HttpServletRequest request, HttpServletResponse response) {
try {
//獲取支付寶POST過來反饋信息
Map<String, String> params = new HashMap<>(30);
Map requestParams = request.getParameterMap();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
// 商戶訂單號
String outTradeNo = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
// 支付寶交易號
String tradeNo = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");
// 付款金額
String totalAmount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"), "UTF-8");
// 交易狀態
String tradeStatus = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8");
// APPID
String appId = new String(request.getParameter("app_id").getBytes("ISO-8859-1"), "UTF-8");
//切記alipaypublickey是支付寶的公鑰,請去open.alipay.com對應應用下查看。
boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getAlipayPublicKey(), AlipayConfig.charset, AlipayConfig.signType);
Boolean isPaysuccess = payDao.getPayStatus(outTradeNo);
if (isPaysuccess != null) {
response.getWriter().write("success");
} else {
//業務處理
if (signVerified) {
//根據返回的訂單號查詢支付金額用於支付金額驗證
BigDecimal payPrice = payDao.getPayPrice(outTradeNo);
//普通即時到賬狀態下交易成功狀態
String normalTradeStatus = "TRADE_FINISHED";
//高級即時到賬狀態下易成功狀態
String advancedTradeStatus = "TRADE_SUCCESS";
//支付金額、訂單完成標識、appid驗證
Boolean priceFlag = new BigDecimal(totalAmount).compareTo(payPrice) == 0;
Boolean tradeFlag = normalTradeStatus.equals(tradeStatus) || advancedTradeStatus.equals(tradeStatus);
Boolean appidFlag = alipayConfig.getAppId().equals(appId);
if (priceFlag && tradeFlag && appidFlag) {
//將訂單狀態改成預約中(支付成功)
payDao.updateOrderStateByNo(outTradeNo, BookingConst.STATE_BOOKING.getCode(), CommonConstant.ALIPAY, CommonConstant.PAYSUCCESS);
response.getWriter().write("success");
} else {
//將訂單狀態改成支付失敗
payDao.updateOrderStateByNo(outTradeNo, BookingConst.STATE_ORDER_PAY_FAIL.getCode(), CommonConstant.ALIPAY, CommonConstant.PAYFAIL);
response.getWriter().write("success");
}
} else {
response.getWriter().write("failure");
}
}
} catch (AlipayApiException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}複製代碼
支付寶支付就完成了,以上全部用到的工具類都封裝在SDK中,馬爸爸是真的好!上線必定記得把參數換生產環境的。
首先,微信是沒有和支付寶同樣的沙箱環境的只能直接用真實環境寫。流程仍是同樣:前端拿訂單ID請求後臺統一下單接口,後端整理好各類數據後返回前端,前端拿這些數據請求微信支付,微信支付沒有return_url,是前端本身寫回調函數的,notify_url和支付寶同樣。
這裏重點強調:微信支付回調只能用80端口!!!
這裏重點強調:微信支付回調只能用80端口!!!
這裏重點強調:微信支付回調只能用80端口!!!
被坑吐血了有沒有??????
統一下單接口:openId這些什麼的能夠根據本身項目來,我是存在數據庫了,其餘須要注意的點我都如今註釋上了。主要能夠分爲兩部分:第一部分,經過訂單信息和公共參數獲取預支付ID prepay_id 這個東西。第二部分:將這個參數和其餘部分參數放一塊兒從新生成簽名返回給前端。
public Map<Object, Object> wechatPay(HttpServletRequest request, HttpServletResponse response, Long userId, Long id) {
SortedMap<Object, Object> param = new TreeMap<>();
try {
//從數據庫中獲取openid
String openid = userDao.getOpenid(userId);
//根據訂單ID獲取訂單信息用於請求支付寶接口的數據封裝
BookingOrderEntity bookingOrderEntity = userDao.getOrderDetail(userId, id);
// 設置package訂單參數
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
packageParams.put("appid", wechatPayConfig.getAppId());
packageParams.put("mch_id", wechatPayConfig.getMchId());
// 生成簽名的時候須要你本身設置隨機字符串
packageParams.put("nonce_str", RandomUtil.generateStr(32));
packageParams.put("out_trade_no", bookingOrderEntity.getBookingNo());
packageParams.put("openid", openid);
//微信api要求傳入金額單位爲分
packageParams.put("total_fee", bookingOrderEntity.getPayPrice().setScale(2, RoundingMode.HALF_UP).multiply(new BigDecimal(100)).stripTrailingZeros().toPlainString());
packageParams.put("spbill_create_ip", HttpUtil.getRealIp(request));
packageParams.put("notify_url", wechatPayConfig.getNotifyUrl());
packageParams.put("trade_type", wechatPayConfig.getTradeType());
packageParams.put("body", wechatPayConfig.getBody());
//生成簽名
String sign = PayCommonUtil.createSign("UTF-8", packageParams, wechatPayConfig.getAppKey());
packageParams.put("sign", sign);
String requestXML = PayCommonUtil.getRequestXml(packageParams);
//請求統一下單接口(主要爲獲取prepay_id這個參數)
String resXml = HttpUtil.postData(wechatPayConfig.getUfdorUrl(), requestXML, null);
Map<String, Object> map = XmlUtil.doXMLParse(resXml);
//判斷請求結果 若returnCode和resultCode均爲success 則按新參數從新生成簽名返回前端以供前端請求支付接口
String mark = "SUCCESS";
String returnCode = "return_code";
String resultCode = "result_code";
if (mark.equals(map.get(returnCode)) && mark.equals(map.get(resultCode))) {
param.put("appId", wechatPayConfig.getAppId());
param.put("nonceStr", RandomUtil.generateStr(32));
param.put("package", "prepay_id=" + map.get("prepay_id"));
param.put("signType", "Md5");
param.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
//以新參數生成新的簽名
String paySign = PayCommonUtil.createSign("UTF-8", param, wechatPayConfig.getAppKey());
param.put("paySign", paySign);
} else {
throw new SzException("統一下單出錯!");
}
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return param;
}複製代碼
統一下單接口返回結果:
微信支付參數實體類:
/**
* 微信支付配置信息
*
* @author fzx
* @date 2018/11/12
*/
@Component
@ConfigurationProperties(prefix = "wechatpay")
public class WechatPayConfig {
/**
* 微信號id
*/
@Value("${wechatpay.appId}")
private String appId;
/**
* 應用對應的憑證
*/
@Value("${wechatpay.appSecret}")
private String appSecret;
/**
* 商戶密鑰
*/
@Value("${wechatpay.appKey}")
private String appKey;
/**
* 商業號
*/
@Value("${wechatpay.mchId}")
private String mchId;
/**
* 回調地址
*/
@Value("${wechatpay.notifyUrl}")
private String notifyUrl;
/**
* 商品名稱
*/
private String body = "閃住平臺酒店預訂";
/**
* 交易類型:公衆號支付
*/
private String tradeType = "JSAPI";
/**
* 微信統一下單接口請求地址
*/
@Value("${wechatpay.ufdorUrl}")
private String ufdorUrl;
/**
* 微信支付V2帳單查詢接口
*/
@Value("${wechatpay.orderQuery}")
private String orderQuery;
/**
* 根據code獲取openid接口
*/
@Value("${wechatpay.clientAccessTokenUrl}")
private String clientAccessTokenUrl;
/**
* 獲取accessToken地址
*/
private String accessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?";
/**
* 獲取jsApiTicket地址
*/
private String jsApiTicketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?";
public void setAccessTokenUrl(String accessTokenUrl) {
this.accessTokenUrl = accessTokenUrl;
}
public void setJsApiTicketUrl(String jsApiTicketUrl) {
this.jsApiTicketUrl = jsApiTicketUrl;
}
public void setAppId(String appId) {
this.appId = appId;
}
public void setAppSecret(String appSecret) {
this.appSecret = appSecret;
}
public void setAppKey(String appKey) {
this.appKey = appKey;
}
public void setMchId(String mchId) {
this.mchId = mchId;
}
public void setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
}
public void setBody(String body) {
this.body = body;
}
public void setTradeType(String tradeType) {
this.tradeType = tradeType;
}
public void setUfdorUrl(String ufdorUrl) {
this.ufdorUrl = ufdorUrl;
}
public void setOrderQuery(String orderQuery) {
this.orderQuery = orderQuery;
}
public void setClientAccessTokenUrl(String clientAccessTokenUrl) {
this.clientAccessTokenUrl = clientAccessTokenUrl;
}
public String getAppId() {
return appId;
}
public String getAppSecret() {
return appSecret;
}
public String getAppKey() {
return appKey;
}
public String getMchId() {
return mchId;
}
public String getNotifyUrl() {
return notifyUrl;
}
public String getBody() {
return body;
}
public String getTradeType() {
return tradeType;
}
public String getUfdorUrl() {
return ufdorUrl;
}
public String getOrderQuery() {
return orderQuery;
}
public String getClientAccessTokenUrl() {
return clientAccessTokenUrl;
}
public String getAccessTokenUrl() {
return accessTokenUrl;
}
public String getJsApiTicketUrl() {
return jsApiTicketUrl;
}
}
複製代碼
工具類:
/**
* 微信支付工具類
*
* @author fzx
* @date 2018/11/15
*/
public class PayCommonUtil {
public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String apiKey) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + apiKey);
String sign = Md5.calc(sb.toString()).toUpperCase();
return sign;
}
public static void main(String[] args) {
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
packageParams.put("appid", "wx399ce4c35a00290f");
packageParams.put("attach", "支付測試");
packageParams.put("body", "H5支付測試");
packageParams.put("mch_id", "1503803601");
packageParams.put("nonce_str", "ibuaiVcKdpRxkhJA");
packageParams.put("notify_url", "http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php");
packageParams.put("out_trade_no", "1415659990");
packageParams.put("scene_info", "{\"h5_info\": {\"type\":\"IOS\",\"app_name\": \"王者榮耀\",\"package_name\": \"com.tencent.tmgp.sgame\"}}");
packageParams.put("spbill_create_ip", "125.118.106.114");
packageParams.put("total_fee", "1");
packageParams.put("trade_type", "MWEB");
System.out.println(createSign("utf-8", packageParams, "981BF84C66A78E328FDE7469F697B4DA"));
}
/**
* @param parameters 請求參數
* @return
* @author
* @date 2016-4-22
* @Description:將請求參數轉換爲xml格式的string
*/
public static String getRequestXml(SortedMap<Object, Object> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k) || "return_code".equalsIgnoreCase(k) || "return_msg".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
} else {
if ("total_fee".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + Integer.parseInt(v) + "</" + k + ">");
} else {
sb.append("<" + k + ">" + v + "</" + k + ">");
}
}
}
sb.append("</xml>");
return sb.toString();
}
/**
* 驗證回調簽名
*
* @return
*/
public static boolean isTenpaySign(Map<String, String> map, String appKey) {
String charset = "utf-8";
String signFromAPIResponse = map.get("sign");
if (signFromAPIResponse == null || "".equals(signFromAPIResponse)) {
return false;
}
//過濾空 設置 TreeMap
SortedMap<String, String> packageParams = new TreeMap();
for (String parameter : map.keySet()) {
String parameterValue = map.get(parameter);
String v = "";
if (null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + appKey);
//將API返回的數據根據用簽名算法進行計算新的簽名,用來跟API返回的簽名進行比較
//算出簽名
String resultSign = "";
String tobesign = sb.toString();
if (null == charset || "".equals(charset)) {
resultSign = Md5.calc(tobesign).toUpperCase();
} else {
try {
resultSign = Md5.calc(tobesign).toUpperCase();
} catch (Exception e) {
resultSign = Md5.calc(tobesign).toUpperCase();
}
}
String tenpaySign = (packageParams.get("sign")).toUpperCase();
return tenpaySign.equals(resultSign);
}
}複製代碼
**
* Http工具類,發送Http請求, Get請求請將參數放在url中 Post請求請將參數放在Map中
*
* @author fzx
* @date 2018/11/5
*/
public class HttpUtil {
private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);
private static final CloseableHttpClient HTTP_CLIENT = HttpClients.createDefault();
private static final String USERAGENT = "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36";
private final static int CONNECT_TIMEOUT = 5000;
private final static String DEFAULT_ENCODING = "UTF-8";
/**
* 發送HttpGet請求 * * @param url * 請求地址 * @return 返回字符串
*/
public static String sendGet(String url) {
String result = null;
CloseableHttpResponse response = null;
try {
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("User-Agent", USERAGENT);
response = HTTP_CLIENT.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
result = EntityUtils.toString(entity);
}
} catch (Exception e) {
log.error("處理失敗 {}" + e);
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
return result;
}
/**
* 發送HttpPost請求,參數爲map * * @param url * 請求地址 * @param map * 請求參數 * @return 返回字符串
*/
public static String sendPost(String url, Map<String, String> map) {
// 設置參數
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> entry : map.entrySet()) {
formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
// 編碼
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
// 取得HttpPost對象
HttpPost httpPost = new HttpPost(url);
// 防止被當成攻擊添加的
httpPost.setHeader("User-Agent", USERAGENT);
// 參數放入Entity
httpPost.setEntity(formEntity);
CloseableHttpResponse response = null;
String result = null;
try {
// 執行post請求
response = HTTP_CLIENT.execute(httpPost);
// 獲得entity
HttpEntity entity = response.getEntity();
// 獲得字符串
result = EntityUtils.toString(entity);
} catch (IOException e) {
log.error(e.getMessage());
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
return result;
}
public static String postData(String urlStr, String data, String contentType) {
BufferedReader reader = null;
try {
URL url = new URL(urlStr);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(CONNECT_TIMEOUT);
if (contentType != null) {
conn.setRequestProperty("content-type", contentType);
}
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);
if (data == null) {
data = "";
}
writer.write(data);
writer.flush();
writer.close();
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\r\n");
}
return sb.toString();
} catch (IOException e) {
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
}
}
return null;
}
/**
*
* 獲取真實ip地址 經過阿帕奇代理的也能獲取到真實ip
*
* @param request
* @return
*/
public static String getRealIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
String unkonwType = "unknown";
if (ip == null || ip.length() == 0 || unkonwType.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || unkonwType.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || unkonwType.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}複製代碼
/**
* xml工具類
*
* @author miklchen
*/
public class XmlUtil {
/**
* 解析xml,返回第一級元素鍵值對。若是第一級元素有子節點,則此節點的值是子節點的xml數據。
*
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws JDOMException, IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap(10);
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = "";
List children = e.getChildren();
if (children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = XmlUtil.getChildrenText(children);
}
m.put(k, v);
}
// 關閉流
in.close();
return m;
}
/**
* 獲取子結點的xml
*
* @param children
* @return String
*/
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if (!children.isEmpty()) {
Iterator it = children.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if (!list.isEmpty()) {
sb.append(XmlUtil.getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
}複製代碼
支付回調:微信支付回調信息是存在流中,須要本身取出來,處理邏輯和支付寶差很少。
另外,這裏不能用釘釘的內網穿透了(沒有80端口),要使用natapp。
public void wechatPayNotify(HttpServletRequest request, HttpServletResponse response) {
SortedMap<Object, Object> map = new TreeMap<>();
try {
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String resultStr = new String(outSteam.toByteArray(), "utf-8");
Map<String, String> resultMap = XmlUtil.doXMLParse(resultStr);
PayCommonUtil.isTenpaySign(resultMap, wechatPayConfig.getAppKey());
String outTradeNo = resultMap.get("out_trade_no");
String resultCode = resultMap.get("result_code");
String returnCode = resultMap.get("return_code");
//先查詢數據庫該訂單支付狀態 若已支付則直接返回SUCCESS給微信
Boolean isPaysuccess = payDao.getPayStatus(outTradeNo);
if (isPaysuccess != null) {
map.put("return_code", "SUCCESS");
map.put("return_msg", "OK");
response.getWriter().write(PayCommonUtil.getRequestXml(map));
} else {
//驗籤
if (PayCommonUtil.isTenpaySign(resultMap, wechatPayConfig.getAppKey())) {
//通知微信.異步確認成功
map.put("return_code", "SUCCESS");
map.put("return_msg", "OK");
response.getWriter().write(PayCommonUtil.getRequestXml(map));
String mark = "SUCCESS";
if (mark.equals(resultCode) && mark.equals(returnCode)) {
//將訂單狀態改成預約中(支付成功)
payDao.updateOrderStateByNo(outTradeNo, BookingConst.STATE_BOOKING.getCode(), CommonConstant.WECHATPAY, CommonConstant.PAYSUCCESS);
} else {
//將訂單狀態改成支付失敗
payDao.updateOrderStateByNo(outTradeNo, BookingConst.STATE_ORDER_PAY_FAIL.getCode(), CommonConstant.WECHATPAY, CommonConstant.PAYFAIL);
map.put("return_code", "SUCCESS");
map.put("return_msg", "OK");
response.getWriter().write(PayCommonUtil.getRequestXml(map));
}
} else {
//通知微信.異步確認失敗
map.put("FAIL", "ERROR");
response.getWriter().write(PayCommonUtil.getRequestXml(map));
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (JDOMException e) {
e.printStackTrace();
}
}複製代碼