Java后端对接微信支付(微信公众号、PC端扫码)

微信流程图

在这里插入图片描述

项目结构
在这里插入图片描述

前期准备

复制证书

将证书中名为apiclient_key.pem的文件复制到你的项目中

在这里插入图片描述

依赖

<!-- 微信支付SDK-->
<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.3.0</version>
</dependency>

properties文件(yaml也可以自己换一下)

# 微信支付相关参数
# 下面两个用来标识用户
# 商户号
wxpay.mch-id= ******************
# APPID
wxpay.appid= ******************   
    
# 接下来两个用来确保SSL(内容未作任何加密,只做了签名.)   
# 商户API证书序列号
wxpay.mch-serial-no= ******************

# 商户私钥文件(第一步)
wxpay.private-key-path= apiclient_key.pem
# APIv3密钥(在微信支付回调通知和商户获取平台证书使用APIv3密钥)
wxpay.api-v3-key= ******************

# 接下来两个是相关地址
# 微信服务器地址
wxpay.domain= https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
wxpay.notify-domain= 填自己的回调地址

写配置文件WxPayConfig(建议新建一个config文件夹存放配置文件)

package com.leng.paymentdemo.config;

import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;

/**
 * 微信支付信息配置
 * @author Admin
 */
@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {

    // 商户号
    private String mchId;

    // 商户API证书序列号
    private String mchSerialNo;

    // 商户私钥文件
    private String privateKeyPath;

    // APIv3密钥
    private String apiV3Key;

    // APPID
    private String appid;

    // 微信服务器地址
    private String domain;

    // 接收结果通知地址
    private String notifyDomain;

    //商户APIv2 key
    private String partnerKey;


    /**
     * 获取商户的私钥文件
     * @param filename
     * @return
     */
    public PrivateKey getPrivateKey(String filename){

        try {
            return PemUtil.loadPrivateKey(new FileInputStream(filename));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在",e);
        }
    }

    /**
     * 获取签名验证器
     * @return
     */
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier(){
        log.info("获取签名验证器");
        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);
        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
        //身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
        // 使用定时更新的签名验证器,不需要传入证书
        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                apiV3Key.getBytes(StandardCharsets.UTF_8));
        return verifier;
    }

    /**
     * 获取http请求对象
     * @param verifier
     * @return
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){

        log.info("获取httpClient");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        return httpClient;
    }

}

自定义统一返回结果(建议创建一个response存放)

CustomizeResultCode

package com.leng.paymentdemo.response;

public interface CustomizeResultCode {
    /**
     * 获取错误代码
     * @return 错误状态码
     */
    Integer getCode();

    /**
     * 获取错误信息
     * @return 错误信息
     */
    String getMessage();
}

Result

package com.leng.paymentdemo.response;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

import java.util.HashMap;
import java.util.Map;

/**
 * 自定义的统一返回结果类
 */
@Data
@Accessors(chain = true) //允许当前类进行链式操作
public class Result {

    @ApiModelProperty(value = "是否成功")
    private Boolean success;

    @ApiModelProperty(value = "返回码")
    private Integer code;

    @ApiModelProperty(value = "返回消息")
    private String message;

    @ApiModelProperty(value = "返回数据")
    private Map<String, Object> data = new HashMap<>();

    /**
     * 构造方法私有化,里面的方法都是静态方法
     * 达到保护属性的作用
     */
    private Result(){

    }

    /**
     * 这里是使用链式编程
     * @return 返回访问成功后的结果集
     */
    public static Result ok(){
        Result result=new Result();
        result.setSuccess(true);
        result.setCode(ResultCode.SUCCESS.getCode());
        result.setMessage(ResultCode.SUCCESS.getMessage());
        return result;
    }

    /**
     * 失败
     * @return
     */
    public static Result error(){
        Result result=new Result();
        result.setSuccess(false);
        result.setCode(ResultCode.COMMON_FAIL.getCode());
        result.setMessage(ResultCode.COMMON_FAIL.getMessage());
        return result;
    }

    /**
     * 根基错误类型返回
     * @param resultCoe
     * @return
     */
    public static Result error(ResultCode resultCoe){
        Result result=new Result();
        result.setSuccess(false);
        result.setCode(resultCoe.getCode());
        result.setMessage(resultCoe.getMessage());
        return result;
    }

    public Result success(Boolean success){
        this.setSuccess(success);
        return this;
    }


    public Result message(String message){
        this.setMessage(message);
        return this;
    }

    public Result code(Integer code){
        this.setCode(code);
        return this;
    }

    public Result data(String key,Object value){
        this.data.put(key,value);
        return this;
    }
}

ResultCode

package com.leng.paymentdemo.response;
/**
 * @Author:
 * @Description: 返回码定义
 * 规定:
 * #200表示成功
 * #1001~1999 区间表示参数错误
 * #2001~2999 区间表示用户错误
 * #3001~3999 区间表示接口异常
 * #后面对什么的操作自己在这里注明就行了
 */
public enum ResultCode implements CustomizeResultCode {

    /* 成功 */
    SUCCESS(200,"成功"),
    /* 默认失败 */
    COMMON_FAIL(999,"失败"),

    /* 参数错误:1000~1999 */
    PARAM_NOT_VALID(1001,"参数无效"),
    PARAM_IS_BLANK(1002,"参数无效"),
    PARAM_TYPE_ERROR(1003,"参数类型错误"),
    PARAM_NOT_COMPLETE(1004,"参数缺失"),

    /* 用户错误 */
    USER_NOT_LOGIN(2001,"用户未登录"),
    USER_ACCOUNT_EXPIRED(2002,"账户已过期"),
    USER_CREDENTIALS_ERROR(2003,"密码错误"),
    USER_CREDENTIALS_EXPIRED(2004,"密码过期"),
    USER_ACCOUNT_DISABLE(2005,"账户不可用"),
    USER_ACCOUNT_LOCKED(2006,"账户被锁定"),
    USER_ACCOUNT_NOT_EXIST(2007,"账户不存在"),
    USER_ACCOUNT_ALREADY_EXIST(2008,"账户已存在"),
    USER_ACCOUNT_USE_BY_OTHERS(2009,"账户下线"),

    /* 部门错误 */
    DEPARTMENT_NOT_EXIST(3007,"部门不存在"),
    DEPARTMENT_ALREADY_EXIST(3008,"部门已存在"),

    /* 业务错误 */
    NO_PERMISSION(3001,"没有权限"),
    BUCKET_IS_EXISTS(3002,"bucket已经存在了"),

    /* 算数异常 */
    ARITHMETIC_EXCEPTION(9001,"算数异常"),
    NULL_POINTER_EXCEPTION(9002,"空指针异常");


    private Integer code;

    private String message;

    ResultCode(Integer code,String message){
        this.code=code;
        this.message=message;
    }

    @Override
    public Integer getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }
}

定义一个全局异常处理器(exception)

BusinessException

package com.leng.paymentdemo.exception;

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BusinessException extends RuntimeException{

    @ApiModelProperty(value = "状态码")
    private Integer code;

    @ApiModelProperty(value = "错误信息")
    private String errMsg;

}

GlobalExceptionHandler

package com.leng.paymentdemo.exception;

import com.leng.paymentdemo.response.Result;
import com.leng.paymentdemo.response.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;

/**
 *@作者: 冷俊杰
 *@类名: GlobalExceptionHandler类
 *@创建时间: 2022/8/22 14:21
 *@描述:
 *全局异常处理器,它只会处理controller层的异常
 *@修改原图
 *@修改作者
 *@修改时间
 */
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseBody
    @ExceptionHandler(value = ArithmeticException.class)
    public Result exception(ArithmeticException e) {
        System.out.println("出现了异常");
        return Result.error(ResultCode.ARITHMETIC_EXCEPTION);
    }

    @ResponseBody
    @ExceptionHandler(value = NullPointerException.class)
    public Result exception(NullPointerException e) {
        System.out.println("出现了异常");
        return Result.error(ResultCode.NULL_POINTER_EXCEPTION);
    }

/*    @ResponseBody
    @ExceptionHandler(value = BusinessException.class)
    public Result exception(BusinessException e) {
        System.out.println("出现了异常");
        return Result.error().code(e.getCode()).message(e.getErrMsg());
    }*/

    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Result exception(Exception e) {
        e.printStackTrace();
        log.error("出现了异常"+e.getMessage()+"-----------");
        return Result.error();
    }



    //处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public Result BindExceptionHandler(BindException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        return Result.error().message(message);
    }

    //处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是javax.validation.ConstraintViolationException
/*    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public ResponseBean ConstraintViolationExceptionHandler(ConstraintViolationException e) {
        String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());
        return ResponseBean.error(message);
    }*/

    //处理请求参数格式错误 @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常。
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Result MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        return Result.error().message(message);
    }

    /**
     * 处理servlet异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = ServletException.class)
    @ResponseBody
    public  Result servletExceptionHandler(HttpServletRequest req, ServletException e){
        log.error("web服务器异常 {}",e.getMessage());
        return Result.error().message(e.getMessage());
    }

    /**
     * 处理自定义的业务异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public  Result bizExceptionHandler(HttpServletRequest req, BusinessException e){
        log.error("业务异常=>{}",e.getMessage());
        return Result.error().code(e.getCode()).message(e.getErrMsg());
    }


    /**
     * shiro的异常
     * @param e
     * @return
     */
/*    @ExceptionHandler(ShiroException.class)
    public ResponseBean handle401(ShiroException e) {
        log.error("shiro异常=>{}",e.getMessage());
        return new ResponseBean(401, e.getMessage(), null);
    }*/


    /**
     * 获取状态码
     * @param request
     * @return
     */
    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        return HttpStatus.valueOf(statusCode);
    }
}

返回状态码(自定义)

OrderStatus类

package com.leng.paymentdemo.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum OrderStatus {
    /**
     * 未支付
     */
    NOTPAY("未支付"),


    /**
     * 支付成功
     */
    SUCCESS("支付成功"),

    /**
     * 已关闭
     */
    CLOSED("超时已关闭"),

    /**
     * 已取消
     */
    CANCEL("用户已取消"),

    /**
     * 退款中
     */
    REFUND_PROCESSING("退款中"),

    /**
     * 已退款
     */
    REFUND_SUCCESS("已退款"),

    /**
     * 退款异常
     */
    REFUND_ABNORMAL("退款异常");

    /**
     * 类型
     */
    private final String type;
}

PayType

package com.leng.paymentdemo.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum PayType {
    /**
     * 微信
     */
    WXPAY("微信"),


    /**
     * 支付宝
     */
    ALIPAY("支付宝");

    /**
     * 类型
     */
    private final String type;
}

WxApiType

package com.leng.paymentdemo.enums.wxpay;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum WxApiType {

   /**
    * Native下单
    */
   NATIVE_PAY("/v3/pay/transactions/native"),

   /**
    * Native下单
    */
   NATIVE_PAY_V2("/pay/unifiedorder"),

   /**
    * 查询订单
    */
   ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),

   /**
    * 关闭订单
    */
   CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),

   /**
    * 申请退款
    */
   DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),

   /**
    * 查询单笔退款
    */
   DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),

   /**
    * 申请交易账单
    */
   TRADE_BILLS("/v3/bill/tradebill"),

   /**
    * 申请资金账单
    */
   FUND_FLOW_BILLS("/v3/bill/fundflowbill");


   /**
    * 类型
    */
   private final String type;
}

WxNotifyType

package com.leng.paymentdemo.enums.wxpay;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum WxNotifyType {

   /**
    * 支付通知
    */
   NATIVE_NOTIFY("/api/wx-pay/native/notify"),

   /**
    * 支付通知
    */
   NATIVE_NOTIFY_V2("/api/wx-pay-v2/native/notify"),



   /**
    * 退款结果通知
    */
   REFUND_NOTIFY("/api/wx-pay/refunds/notify");



   /**
    * 类型
    */
   private final String type;
}

WxRefundStatus

package com.leng.paymentdemo.enums.wxpay;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum WxRefundStatus {

    /**
     * 退款成功
     */
    SUCCESS("SUCCESS"),

    /**
     * 退款关闭
     */
    CLOSED("CLOSED"),

    /**
     * 退款处理中
     */
    PROCESSING("PROCESSING"),

    /**
     * 退款异常
     */
    ABNORMAL("ABNORMAL");

    /**
     * 类型
     */
    private final String type;
}

WxTradeState

package com.leng.paymentdemo.enums.wxpay;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum WxTradeState {

    /**
     * 支付成功
     */
    SUCCESS("SUCCESS"),

    /**
     * 未支付
     */
    NOTPAY("NOTPAY"),

    /**
     * 已关闭
     */
    CLOSED("CLOSED"),

    /**
     * 转入退款
     */
    REFUND("REFUND");

    /**
     * 类型
     */
    private final String type;
}

utils工具类

HttpClientUtils

package com.leng.paymentdemo.util;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * http请求客户端
 */
public class HttpClientUtils {
   private String url;
   private Map<String, String> param;
   private int statusCode;
   private String content;
   private String xmlParam;
   private boolean isHttps;

   public boolean isHttps() {
      return isHttps;
   }

   public void setHttps(boolean isHttps) {
      this.isHttps = isHttps;
   }

   public String getXmlParam() {
      return xmlParam;
   }

   public void setXmlParam(String xmlParam) {
      this.xmlParam = xmlParam;
   }

   public HttpClientUtils(String url, Map<String, String> param) {
      this.url = url;
      this.param = param;
   }

   public HttpClientUtils(String url) {
      this.url = url;
   }

   public void setParameter(Map<String, String> map) {
      param = map;
   }

   public void addParameter(String key, String value) {
      if (param == null)
         param = new HashMap<String, String>();
      param.put(key, value);
   }

   public void post() throws ClientProtocolException, IOException {
      HttpPost http = new HttpPost(url);
      setEntity(http);
      execute(http);
   }

   public void put() throws ClientProtocolException, IOException {
      HttpPut http = new HttpPut(url);
      setEntity(http);
      execute(http);
   }

   public void get() throws ClientProtocolException, IOException {
      if (param != null) {
         StringBuilder url = new StringBuilder(this.url);
         boolean isFirst = true;
         for (String key : param.keySet()) {
            if (isFirst) {
               url.append("?");
               isFirst = false;
            }else {
               url.append("&");
            }
            url.append(key).append("=").append(param.get(key));
         }
         this.url = url.toString();
      }
      HttpGet http = new HttpGet(url);
      execute(http);
   }

   /**
    * set http post,put param
    */
   private void setEntity(HttpEntityEnclosingRequestBase http) {
      if (param != null) {
         List<NameValuePair> nvps = new LinkedList<NameValuePair>();
         for (String key : param.keySet())
            nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
         http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
      }
      if (xmlParam != null) {
         http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
      }
   }

   private void execute(HttpUriRequest http) throws ClientProtocolException,
         IOException {
      CloseableHttpClient httpClient = null;
      try {
         if (isHttps) {
            SSLContext sslContext = new SSLContextBuilder()
                  .loadTrustMaterial(null, new TrustStrategy() {
                     // 信任所有
                     public boolean isTrusted(X509Certificate[] chain,
                           String authType)
                           throws CertificateException {
                        return true;
                     }
                  }).build();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                  sslContext);
            httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
                  .build();
         } else {
            httpClient = HttpClients.createDefault();
         }
         CloseableHttpResponse response = httpClient.execute(http);
         try {
            if (response != null) {
               if (response.getStatusLine() != null)
                  statusCode = response.getStatusLine().getStatusCode();
               HttpEntity entity = response.getEntity();
               // 响应内容
               content = EntityUtils.toString(entity, Consts.UTF_8);
            }
         } finally {
            response.close();
         }
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         httpClient.close();
      }
   }

   public int getStatusCode() {
      return statusCode;
   }

   public String getContent() throws ParseException, IOException {
      return content;
   }

}

HttpUtils

package com.leng.paymentdemo.util;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;


public class HttpUtils {

    /**
     * 将通知参数转化为字符串
     * @param request
     * @return
     */
    public static String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

OrderNoUtils

package com.leng.paymentdemo.util;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

/**
 * 订单号工具类
 *
 * @author qy
 * @since 1.0
 */
public class OrderNoUtils {

    /**
     * 获取订单编号
     * @return
     */
    public static String getOrderNo() {
        return "ORDER_" + getNo();
    }

    /**
     * 获取退款单编号
     * @return
     */
    public static String getRefundNo() {
        return "REFUND_" + getNo();
    }

    /**
     * 获取编号
     * @return
     */
    public static String getNo() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String newDate = sdf.format(new Date());
        String result = "";
        Random random = new Random();
        for (int i = 0; i < 3; i++) {
            result += random.nextInt(10);
        }
        return newDate + result;
    }

}

使用

创建支付

官方接口文档可以参考微信支付-开发者文档 (qq.com)

新建一个WxPayController类,用来实现对下单,退款等操作

Native下单方法
/**
 * Native下单
 * @param productId
 * @return
 * @throws Exception
 */
@ApiOperation("调用统一下单API,生成支付二维码")
@PostMapping("native/{productId}")
public Result nativePay(@PathVariable Long productId) throws Exception {
    log.info("发起支付请求 v3.....................");
    //通过map接受返回的支付二维码链接和订单号
    Map<String, Object> map = wxPayService.nativePay(productId);
    return Result.ok().setData(map);
}
service层
/**
 * 创建订单,调用Native支付接口
 */
Map<String, Object> nativePay(Long productId) throws Exception;
service实现层
/**
 * 创建订单,调用Native支付接口
 */
@Transactional(rollbackFor = Exception.class)
@Override
public Map<String, Object> nativePay(Long productId) throws Exception {
    log.info("生成订单");
    //生成订单并存入数据库
    OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);
    String code_url = orderInfo.getCodeUrl();
    if (orderInfo != null && !StringUtils.isEmpty(code_url)) {
        log.info("订单已存在,二维码以保存");
        //创建返回值
        HashMap<String, Object> map = new HashMap<>();
        map.put("codeUrl", code_url);
        map.put("orderNo", orderInfo.getOrderNo());
        return map;
    }
    log.info("调用统一下单API");
    //调用统一下单API
    HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
    // 请求body参数
    Gson gson = new Gson();
    Map paramsMap = new HashMap<>();
    paramsMap.put("appid", wxPayConfig.getAppid());//应用ID
    paramsMap.put("mchid", wxPayConfig.getMchId());//直连商户号
    paramsMap.put("description", orderInfo.getTitle());//商品描述
    paramsMap.put("out_trade_no", orderInfo.getOrderNo());//商户订单号
    paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));//通知地址
    HashMap<String, Object> amountMap = new HashMap<>();
    amountMap.put("total", orderInfo.getTotalFee());
    amountMap.put("currency", "CNY");
    paramsMap.put("amount", amountMap);

    //将参数转换成字符串形式
    String jsonParams = gson.toJson(paramsMap);
    log.info("请求参数" + jsonParams);

    StringEntity entity = new StringEntity(jsonParams,"utf-8");
    entity.setContentType("application/json");
    httpPost.setEntity(entity);
    httpPost.setHeader("Accept", "application/json");

    //完成签名并执行请求
    CloseableHttpResponse response = wxPayClient.execute(httpPost);

    try {
        //响应体
        String bodyAsString = EntityUtils.toString(response.getEntity());
        //响应状态码
        int statusCode = response.getStatusLine().getStatusCode();
        //处理成功
        if (statusCode == 200) {
            log.info("成功,返回结果y = " + bodyAsString);
            //处理成功,无返回Body
        } else if (statusCode == 204) {
            log.info("成功");
        } else {
            log.error("Native下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
            throw new IOException("request failed");
        }

        //响应结果
        Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
        //二维码
        code_url = resultMap.get("code_url");

        //保存二维码
        String orderNo = orderInfo.getOrderNo();
        orderInfoService.saveCodeUrl(orderNo, code_url);

        //创建返回值:二维码
        Map<String, Object> map = new HashMap<>();
        map.put("codeUrl", code_url);
        map.put("orderNo", orderInfo.getOrderNo());
        return map;
    } finally {
        //释放资源
        response.close();
    }
}
生成订单并存入数据库

servcie层

/**
 * 生成订单并存入数据库
 */
OrderInfo createOrderByProductId(Long productId);

service实现层

/**
 * 生成订单并存入数据库
 */
@Override
public OrderInfo createOrderByProductId(Long productId) {
    //查询已存在但是未支付的订单
    OrderInfo orderInfo = this.getNoPayOrderByProductId(productId);
    if (orderInfo != null) {
        return orderInfo;
    }
    //获取商品信息
    Product product = productMapper.selectById(productId);
    //生成订单
    orderInfo = new OrderInfo();
    orderInfo.setTitle(product.getTitle());//订单标题
    orderInfo.setOrderNo(OrderNoUtils.getOrderNo());//订单号
    orderInfo.setProductId(productId);//支付产品id
    orderInfo.setTotalFee(product.getPrice());//商品金额,单位是分
    orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());//订单状态
    baseMapper.insert(orderInfo);
    return orderInfo;
}
存储订单二维码

servcie层

/**
 * 存储订单二维码
 */
void saveCodeUrl(String orderNo, String codeUrl);

servcie实现层

/**
 * 存储订单二维码
 */
@Override
public void saveCodeUrl(String orderNo, String codeUrl) {
    QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<OrderInfo>();
    queryWrapper.eq("order_no", orderNo);
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setCodeUrl(codeUrl);
    baseMapper.update(orderInfo, queryWrapper);
}

支付通知

官方接口文档:微信支付-开发者文档 (qq.com)

支付通知方法

/**
 * 支付通知
 * 微信支付通过支付通知接口将用户支付成功消息通知给商户
 */
@PostMapping("/native/notify")
public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {
    Gson gson = new Gson();
    //应答对象
    HashMap<String, String> map = new HashMap<>();

    try {

        //处理通知参数
        String body = HttpUtils.readData(request);
        log.info("支付通知的参数 ===> {}", body);
        Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
        String requestId = (String) bodyMap.get("id");
        log.info("支付通知的id ===> {}", requestId);

        //签名的验证
        WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, requestId, body);
        if (!wechatPay2ValidatorForRequest.validate(request)) {
            log.error("验签失败");
            //失败应答
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "验签失败");
            return gson.toJson(map);
        }
        log.info("验签成功");

        //处理订单
        wxPayService.processOrder(bodyMap);

        //应答超时
        //模拟接收微信端的重复通知
        //TimeUnit.SECONDS.sleep(5);

        //成功应答
        response.setStatus(200);
        map.put("code", "SUCCESS");
        map.put("message", "成功");
        return gson.toJson(map);

    } catch (Exception e) {
        e.printStackTrace();
        //失败应答
        response.setStatus(500);
        map.put("code", "ERROR");
        map.put("message", "失败");
        return gson.toJson(map);
    }
}

处理订单

service层

/**
 * 处理订单
 */
void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException;

service实现层

/**
 * 处理订单
 */
@Transactional(rollbackFor = Exception.class)
@Override
public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
    log.info("处理订单");
    //解密报文
    String plainText = decryptFromResource(bodyMap);
    //将明文转换成map
    Gson gson = new Gson();
    HashMap hashMap = gson.fromJson(plainText, HashMap.class);
    String orderNo = (String) hashMap.get("out_trade_no");

    /*在对业务数据进行状态检查和处理之前,
      要采用数据锁进行并发控制,
      以避免函数重入造成的数据混乱*/
    //尝试获取锁:
    // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放
    if (lock.tryLock()) {
        try {
            //处理重复通知
            //保证接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的
            String orderStatus = orderInfoService.getOrderStatus(orderNo);
            if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
                return;
            }
            //更新订单状态
            orderInfoService.updateStatusByorderNo(orderNo, OrderStatus.SUCCESS);
            //记录支付日志
            paymentInfoService.createPaymentInfo(plainText);
        } finally {
            //要主动释放锁
            lock.unlock();
        }
    }
}

查询订单

官方接口文档:微信支付-开发者文档 (qq.com)

/**
 * 查询订单
 *
 * @param orderNo
 * @return
 * @throws URISyntaxException
 * @throws IOException
 */
@ApiOperation("查询订单:测试订单状态用")
@GetMapping("query/{orderNo}")
public Result queryOrder(@PathVariable String orderNo) throws Exception {
    log.info("查询订单");
    String result = wxPayService.queryOrder(orderNo);
    return Result.ok().setMessage("查询成功").data("result", result);
}

service层

/**
 * 查单接口调用
 */
String queryOrder(String orderNo) throws Exception;

service实现层

/**
 * 根据订单号查询微信支付查单接口,核实订单状态
 * 如果订单已支付,则更新商户端订单状态,并记录支付日志
 * 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
 *
 * @param orderNo
 */
@Transactional(rollbackFor = Exception.class)
@Override
public void checkOrderStatus(String orderNo) throws Exception {
    log.warn("根据订单号核实订单状态 ===> {}", orderNo);
    //调用微信支付查单接口
    String result = this.queryOrder(orderNo);
    Gson gson = new Gson();
    Map resultMap = gson.fromJson(result, HashMap.class);
    //获取微信支付端的订单状态
    Object tradeState = resultMap.get("trade_state");
    //判断订单状态
    if(WxTradeState.SUCCESS.getType().equals(tradeState)){
        log.warn("核实订单已支付 ===> {}", orderNo);
    //如果确认订单已支付则更新本地订单状态
        orderInfoService.updateStatusByorderNo(orderNo, OrderStatus.SUCCESS);
    //记录支付日志
        paymentInfoService.createPaymentInfo(result);
    }
    if(WxTradeState.NOTPAY.getType().equals(tradeState)){
        log.warn("核实订单未支付 ===> {}", orderNo);
    //如果订单未支付,则调用关单接口
        this.closeOrder(orderNo);
    //更新本地订单状态
        orderInfoService.updateStatusByorderNo(orderNo, OrderStatus.CLOSED);
    }
}

关单接口

/**
 * 关单接口的调用
 *
 * @param orderNo
 */
private void closeOrder(String orderNo) throws Exception {
    log.info("关单接口的调用,订单号 ===> {}", orderNo);
    //创建远程请求对象
    String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
    url = wxPayConfig.getDomain().concat(url);
    HttpPost httpPost = new HttpPost(url);
    //组装json请求体
    Gson gson = new Gson();
    Map<String, String> paramsMap = new HashMap<>();
    paramsMap.put("mchid", wxPayConfig.getMchId());
    String jsonParams = gson.toJson(paramsMap);
    log.info("请求参数 ===> {}", jsonParams);
    //将请求参数设置到请求对象中
    StringEntity entity = new StringEntity(jsonParams, "utf-8");
    entity.setContentType("application/json");
    httpPost.setEntity(entity);
    httpPost.setHeader("Accept", "application/json");
    //完成签名并执行请求
    CloseableHttpResponse response = wxPayClient.execute(httpPost);
    try {
        int statusCode = response.getStatusLine().getStatusCode();//响应状态码
        if (statusCode == 200) { //处理成功
            log.info("成功200");
        } else if (statusCode == 204) { //处理成功,无返回Body
            log.info("成功204");
        } else {
            log.info("Native下单失败,响应码 = " + statusCode);
            throw new IOException("request failed");
        }
    } finally {
        response.close();
    }
}

申请退款


    /**
     * 申请退款
     */
    @ApiOperation("申请退款")
    @PostMapping("/refunds/{orderNo}/{reason}")
    public Result refunds(@PathVariable String orderNo, @PathVariable String reason)
            throws Exception {
        log.info("申请退款");
        wxPayService.refund(orderNo, reason);
        return Result.ok();
    }

service层

/**
 * 退款
 */
void refund(String orderNo, String reason) throws Exception;

service实现层

 * 退款
 *
 * @param orderNo
 * @param reason
 */
@Transactional(rollbackFor = Exception.class)
@Override
public void refund(String orderNo, String reason) throws Exception {
    log.info("创建退款单记录");
    //根据订单编号创建退款单
    RefundInfo refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo,
            reason);
    log.info("调用退款API");
    //调用统一下单API
    String url =
            wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
    HttpPost httpPost = new HttpPost(url);
    // 请求body参数
    Gson gson = new Gson();
    Map paramsMap = new HashMap();
    paramsMap.put("out_trade_no", orderNo);//订单编号
    paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号
    paramsMap.put("reason", reason);//退款原因
    paramsMap.put("notify_url",
            wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址
    Map amountMap = new HashMap();
    amountMap.put("refund", refundsInfo.getRefund());//退款金额
    amountMap.put("total", refundsInfo.getTotalFee());//原订单金额
    amountMap.put("currency", "CNY");//退款币种
    paramsMap.put("amount", amountMap);
    //将参数转换成json字符串
    String jsonParams = gson.toJson(paramsMap);
    log.info("请求参数 ===> {}" + jsonParams);
    StringEntity entity = new StringEntity(jsonParams, "utf-8");
    entity.setContentType("application/json");//设置请求报文格式
    httpPost.setEntity(entity);//将请求报文放入请求对象
    httpPost.setHeader("Accept", "application/json");//设置响应报文格式
    //完成签名并执行请求,并完成验签
    CloseableHttpResponse response = wxPayClient.execute(httpPost);
    try {
        //解析响应结果
        String bodyAsString = EntityUtils.toString(response.getEntity());
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) {
            log.info("成功, 退款返回结果 = " + bodyAsString);
        } else if (statusCode == 204) {
            log.info("成功");
        } else {
            throw new RuntimeException("退款异常, 响应码 = " + statusCode + ", 退款返回结果 = " + bodyAsString);
        }
        //更新订单状态
        orderInfoService.updateStatusByorderNo(orderNo,
                OrderStatus.REFUND_PROCESSING);
        //更新退款单
        refundsInfoService.updateRefund(bodyAsString);
    } finally {
        response.close();
    }
}

创建退款订单

service层

/**
 * 根据订单号创建退款订单
 */
RefundInfo createRefundByOrderNo(String orderNo, String reason);

service实现层

/**
 * 根据订单号创建退款订单
 *
 * @param orderNo
 * @param reason
 */
@Override
public RefundInfo createRefundByOrderNo(String orderNo, String reason) {
    //根据订单号获取订单信息
    OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);
    //根据订单号生成退款订单
    RefundInfo refundInfo = new RefundInfo();
    refundInfo.setOrderNo(orderNo);//订单编号
    refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号
    refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(分)
    refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(分)
    refundInfo.setReason(reason);//退款原因
    //保存退款订单
    baseMapper.insert(refundInfo);
    return refundInfo;
}

获取订单状态

/**
 * 根据订单号获取订单
 */
OrderInfo getOrderByOrderNo(String orderNo);
/**
 * 根据订单号获取订单
 *
 * @param orderNo
 */
@Override
public OrderInfo getOrderByOrderNo(String orderNo) {
    QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("order_no", orderNo);
    OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
    return orderInfo;
}

根据退款单号核实退款单状态

service层

/**
 * 根据退款单号核实退款单状态
 */
void checkRefundStatus(String refundNo) throws Exception;

service实现层

/**
 * 根据退款单号核实退款单状态
 *
 * @param refundNo
 */
@Transactional(rollbackFor = Exception.class)
@Override
public void checkRefundStatus(String refundNo) throws Exception {
    log.warn("根据退款单号核实退款单状态 ===> {}", refundNo);
    //调用查询退款单接口
    String result = this.queryRefund(refundNo);
    //组装json请求体字符串
    Gson gson = new Gson();
    Map<String, String> resultMap = gson.fromJson(result, HashMap.class);
    //获取微信支付端退款状态
    String status = resultMap.get("status");
    String orderNo = resultMap.get("out_trade_no");
    if (WxRefundStatus.SUCCESS.getType().equals(status)) {
        log.warn("核实订单已退款成功 ===> {}", refundNo);
        //如果确认退款成功,则更新订单状态
        orderInfoService.updateStatusByorderNo(orderNo,
                OrderStatus.REFUND_SUCCESS);
        //更新退款单
        refundsInfoService.updateRefund(result);
    }
    if (WxRefundStatus.ABNORMAL.getType().equals(status)) {
        log.warn("核实订单退款异常 ===> {}", refundNo);
        //如果确认退款成功,则更新订单状态
        orderInfoService.updateStatusByorderNo(orderNo,
                OrderStatus.REFUND_ABNORMAL);
        //更新退款单
        refundsInfoService.updateRefund(result);
    }
}

更新退款状态

service层

/**
 * 记录退款记录
 */
void updateRefund(String content);

service实现层

/**
 * 记录退款记录
 *
 * @param content
 */
@Override
public void updateRefund(String content) {
    //将json字符串转换成Map
    Gson gson = new Gson();
    Map<String, String> resultMap = gson.fromJson(content, HashMap.class);
    //根据退款单编号修改退款单
    QueryWrapper<RefundInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("refund_no", resultMap.get("out_refund_no"));
    //设置要修改的字段
    RefundInfo refundInfo = new RefundInfo();
    refundInfo.setRefundId(resultMap.get("refund_id"));//微信支付退款单号
    //查询退款和申请退款中的返回参数
    if(resultMap.get("status") != null){
        refundInfo.setRefundStatus(resultMap.get("status"));//退款状态
        refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段
    }
    //退款回调中的回调参数
    if(resultMap.get("refund_status") != null){
        refundInfo.setRefundStatus(resultMap.get("refund_status"));//退款状态
        refundInfo.setContentNotify(content);//将全部响应结果存入数据库的content字段
    }
    //更新退款单
    baseMapper.update(refundInfo, queryWrapper);
}

处理退款

service层

/**
 * 处理退款单
 */
void processRefund(Map<String, Object> bodyMap) throws Exception;

service实现层

/**
 * 处理退款单
 *
 * @param bodyMap
 */
@Transactional(rollbackFor = Exception.class)
@Override
public void processRefund(Map<String, Object> bodyMap) throws Exception {
    log.info("退款单");
    //解密报文
    String plainText = decryptFromResource(bodyMap);
    //将明文转换成map
    Gson gson = new Gson();
    HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
    String orderNo = (String) plainTextMap.get("out_trade_no");
    if (lock.tryLock()) {
        try {
            String orderStatus = orderInfoService.getOrderStatus(orderNo);
            if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {
                return;
            }
            //更新订单状态
            orderInfoService.updateStatusByorderNo(orderNo,
                    OrderStatus.REFUND_SUCCESS);
            //更新退款单
            refundsInfoService.updateRefund(plainText);
        } finally {
            //要主动释放锁
            lock.unlock();
        }
    }
}

源码

GitHub

Gitee

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PerryLes

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值