支付寶手機網頁支付和微信公眾號支付接入


  先說支付寶的吧。

       第一步:去支付寶新建沙箱應用並申請開通相應權限,也就是測試環境,完成后去https://auth.alipay.com/login/ant_sso_index.htm?goto=https%3A%2F%2Fopenhome.alipay.com%2Fplatform%2FappDaily.htm%3Ftab%3Dinfo查看自己的應用,在這里面會有APPID,支付寶網關等參數,密鑰和支付寶公鑰按提示生成即可,這些參數在之后代碼中都會用到。

       第二步:自己服務器的代碼部分,在寫代碼之前,先導入馬爸爸為我們准備的SDK,Maven依賴如下,

<dependency>
  <groupId>com.alipay.sdk</groupId>
  <artifactId>alipay-sdk-java</artifactId>
  <version>3.4.27.ALL</version>
</dependency>復制代碼

然后我們得先知道整個支付流程是怎么樣的。按照一般邏輯,前端拿着訂單ID請求后台的統一下單接口,此接口整理數據返回信息給前端,前端拿此信息請求支付寶開始支付,支付完成后根據return_url跳轉前端頁面同步通知,根據notify_url調用服務器后台接口異步通知

        下面進入正題:

        統一下單接口:從這里開始才是支付流程的第一步,整理數據給前端用以發起支付請求,這個接口里面需要的參數可以參考官方文檔https://docs.open.alipay.com/api(這個是所有API的文檔,找自己需要的接口看)。這里重點提一下,return_url和notify_url,return_url:同步跳轉路徑,是支付完成后前端跳轉頁面的路徑,通常為某個html頁面的路徑,這個跳轉只表示支付完成,意思是整個支付流程完成,並不代表支付成功或者失敗。而notify_url為異步通知,一般為后端controller的requestMapping,這個才是真正通知支付成功或者失敗的接口,后端要寫一個接受支付寶返回信息的接口,下面再講。這個統一下單接口會根據不同的請求方式會返回不同的信息,GET請求返回的是json或xml,POST則是直接返回一個帶訂單信息的form表單。前端拿到統一下單接口返回的信息后請求支付即可。

 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(); } }復制代碼

       支付寶參數實體類:我是寫成了去配置文件拿信息的,各位也可以直接寫成普通類放參數就行。

@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即可。

        打開穿透工具:如下圖,a為域名前綴,可自定義,8081為你自己本地后台服務器的端口。

      開啟成功:如下圖,這里好像只能使用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(); } }


作者:是一只有夢想又堅強的豬
鏈接:https://juejin.im/post/5c1849f7f265da61327f3c40


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM