1. 程式人生 > >支付寶手機網頁支付和微信公眾號支付接入

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

       先說支付寶的吧。

       第一步:去支付寶新建沙箱應用並申請開通相應許可權,也就是測試環境,完成後去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();
  }
}複製程式碼