支付宝手机网页支付和微信公众号支付接入

2018/12/18 15:00
阅读数 54

  先说支付宝的吧。

       第一步:去支付宝新建沙箱应用并申请开通相应权限,也就是测试环境,完成后去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

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部