Мобильный веб-платеж Alipay и доступ к официальному платежному аккаунту WeChat

WeChat Alipay

Давайте сначала поговорим об Alipay.

Шаг 1: Перейдите в Alipay, чтобы создать новое приложение для песочницы и подать заявку на получение соответствующего разрешения, которое является тестовой средой.После завершения перейдите на https://auth.alipay.com/login/ant_sso_index.htm?goto=https% 3A%2F%2Fopenhome.alipay.com%2Fplatform%2FappDaily.htm%3Ftab%3Dinfo проверьте свое собственное приложение, там будут такие параметры, как APPID, шлюз Alipay и т. д., ключ и открытый ключ Alipay могут быть сгенерированы в соответствии с подсказки, эти параметры будут использоваться в коде позже.

Второй шаг: кодовая часть вашего собственного сервера, прежде чем писать код, сначала импортируйте SDK, подготовленный для нас отцом Ма. Maven зависит от следующего,

<dependency>
  <groupId>com.alipay.sdk</groupId>
  <artifactId>alipay-sdk-java</artifactId>
  <version>3.4.27.ALL</version>
</dependency>

Затем мы должны знать, на что похож весь процесс оплаты. По общей логике фронтенд берет ID заказа и запрашивает бэкендЕдиный интерфейс заказа, этот интерфейс сортирует данные и возвращает информацию во внешний интерфейс, внешний интерфейс использует эту информацию, чтобы запросить у Alipay начать платеж, после завершения платежа перейти к уведомлению о синхронизации страницы внешнего интерфейса в соответствии с return_url и вызвать фоновый интерфейс сервера в соответствии с notify_urlАсинхронное уведомление.

        Перейдем к делу:

Единый интерфейс заказа:Отсюда первый шаг в процессе оплаты, сортировка данных во внешнем интерфейсе для инициирования платежных запросов, параметры, необходимые в этом интерфейсе, могут ссылаться на официальную документацию https://docs.open.alipay.com/api (это всю документацию по API, ищите нужный интерфейс). Здесь важно упомянуть,return_url и notify_url, return_url: путь синхронного перехода, который является путем страницы перехода внешнего интерфейса после завершения платежа, обычно это путь страницы html. Этот переход означает только то, что платеж завершен, что означает, что весь Процесс оплаты завершен и не означает, что оплата прошла успешно или неудачно. notify_url — это асинхронное уведомление, которое обычно представляет собой requestMapping внутреннего контроллера. Это интерфейс, который действительно уведомляет об успешном или неудачном платеже. Серверной части необходимо написать интерфейс, который принимает информацию, возвращаемую Alipay. , о чем пойдет речь ниже.. Этот унифицированный интерфейс заказа будет возвращать различную информацию в соответствии с различными методами запроса: GET-запросы возвращают json или xml, а POST напрямую возвращает форму с информацией о заказе. Внешний интерфейс может запросить оплату после получения информации, возвращаемой унифицированным интерфейсом заказа.

 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();
  }
}

       Класс сущности параметра Alipay:Я написал это, чтобы перейти к файлу конфигурации, чтобы получить информацию, вы также можете написать это напрямую как общий класс и поставить параметры.

@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:Это автоматически отправленная форма, которую можно загрузить непосредственно из внешнего интерфейса.


Проникновение в интранет DingTalk:И return_url, и notify_url должны быть доступны из внешней сети.Если вы хотите провести локальное тестирование, вы можете загрузить инструмент проникновения в интранет DingTalk https://open-doc.dingtalk.com/microapp/debug/ucof2g, здесь Например, я говорил о том, как его использовать. Операция 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/ ххх.


       Интерфейс обратного вызова платежа:Механизм, который дает Alipay: обратный звонок после завершения оплаты, прекращение звонка после получения УСПЕХА от нашего сервера, и весь процесс оплаты завершен. Если УСПЕХ возвращаем в Alipay не получено через 15 секунд (данные не очень хорошо запоминаются, можете проверить официальный документ), то продолжайте звонить, и этот временной интервал будет постепенно увеличиваться.

Итак, первый шаг — проверить, была ли обработана информация в нашей базе данных в соответствии с номером заказа, возвращенным Alipay, потому что может случиться следующее: Предполагая, что интервал обратного вызова составляет 15 секунд, мы возвращаем УСПЕХ в Alipay на 14-й секунде, однако , по сетевым причинам Alipay не получил нашу информацию на 15-й секунде, и он все равно перезвонил нам, но на самом деле мы уже обработали информацию о заказе уже на 14-й секунде, но Alipay этого не знал, поэтому, если заказ был обработан, независимо от того, был ли платеж успешным или платеж не прошел, напрямую вернитесь к УСПЕХУ, чтобы сказать Alipay, чтобы он сказал об этом, мой отец уже знает, давайте уйдем на пенсию.

Второй шаг — проверка подписи.Информация, возвращаемая Alipay, содержится в запросе, а подпись после проверки извлекается напрямую. Если проверка не пройдена, это может быть злонамеренная атака с другого сервера, и ошибка будет возвращена в Alipay. После того, как подпись будет передана, оцените APPID, сумму платежа, Alipay дает мне знак успеха и т. Д. И измените информацию о заказе, чтобы оплатить успех или неудачу.

  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();
  }
}

Оплата Alipay завершена, все инструменты, использованные выше, инкапсулированы в SDK, Ma Dad действительно хорош! Обязательно измените параметры на производственную среду при подключении к сети.



Далее поговорим о WeChat.

Прежде всего, WeChat не имеет такой же среды песочницы, как Alipay, и может быть написан только непосредственно в реальной среде. Процесс остался прежним: фронтенд берет идентификатор заказа для запроса единого интерфейса заказа в фоновом режиме, бэкэнд систематизирует различные данные и возвращает их во фронтенд, а фронтенд берет эти данные для запроса Оплата WeChat, оплата WeChat не имеет return_url, фронтенд пишет саму функцию обратного вызова, notify_url и Alipay То же самое.

Здесь подчеркивается: обратный вызов платежа WeChat может использовать только порт 80! ! !

Здесь подчеркивается: обратный вызов платежа WeChat может использовать только порт 80! ! !

Здесь подчеркивается: обратный вызов платежа WeChat может использовать только порт 80! ! !

Тебя вырвало кровью из ямы? ? ? ? ? ?

       Единый интерфейс заказа:OpenId и т. д. могут быть основаны на вашем собственном проекте.У меня есть база данных, и я прокомментировал другие моменты, требующие внимания. В основном его можно разделить на две части: первая часть, получение идентификатора предоплаты 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;
}

    Единый интерфейс заказа возвращает результат:


      Класс объекта платежного параметра WeChat:

/**
 * 微信支付配置信息
 *
 * @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();
  }

}

Обратный вызов платежа: информация об обратном вызове платежа WeChat существует в потоке, и ее необходимо извлечь самостоятельно Логика обработки аналогична Alipay.

Кроме того, здесь нельзя проникнуть во внутреннюю сеть Dingding (нет 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();
  }
}