SpringBoot实现整合微信支付方法详解

1.准备工作

1.1 数据库表

这里涉及微信支付一共两个表:

订单表

支付记录表

1.2 实体类

数据库对应的实体类:

订单表

@Data
@ToString
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_order")
@ApiModel(value = "Order对象", description = "订单")
public class Order implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private String id;

    @ApiModelProperty(value = "订单号")
    private String orderNo;

    @ApiModelProperty(value = "课程id")
    private String courseId;

    @ApiModelProperty(value = "课程名称")
    private String courseTitle;

    @ApiModelProperty(value = "课程封面")
    private String courseCover;

    @ApiModelProperty(value = "讲师名称")
    private String teacherName;

    @ApiModelProperty(value = "会员id")
    private String memberId;

    @ApiModelProperty(value = "会员昵称")
    private String nickname;

    @ApiModelProperty(value = "会员手机")
    private String mobile;

    @ApiModelProperty(value = "订单金额(分)")
    private BigDecimal totalFee;

    @ApiModelProperty(value = "支付类型(1:微信 2:支付宝)")
    private Integer payType;

    @ApiModelProperty(value = "订单状态(0:未支付 1:已支付)")
    private Integer status;

    @ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
    private Boolean isDeleted;

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;

    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;
}

支付日志表

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_pay_log")
@ApiModel(value = "PayLog对象", description = "支付日志表")
public class PayLog implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private String id;

    @ApiModelProperty(value = "订单号")
    private String orderNo;

    @ApiModelProperty(value = "支付完成时间")
    private Date payTime;

    @ApiModelProperty(value = "支付金额(分)")
    private BigDecimal totalFee;

    @ApiModelProperty(value = "交易流水号")
    private String transactionId;

    @ApiModelProperty(value = "交易状态")
    private String tradeState;

    @ApiModelProperty(value = "支付类型(1:微信 2:支付宝)")
    private Integer payType;

    @ApiModelProperty(value = "其他属性")
    private String attr;

    @ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
    private Boolean isDeleted;

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;

    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;
}

1.3 导入依赖

在订单模块service_order导入微信支付需要的依赖:

<dependencies>
    <dependency>
        <groupId>com.github.wxpay</groupId>
        <artifactId>wxpay-sdk</artifactId>
        <version>0.0.3</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
</dependencies>

1.4 配置文件

在配置文件application.properties配置相关的信息:

# 服务端口
server.port=8007
# 服务名
spring.application.name=service-order
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

spring.jackson.time-zone=GMT+8
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/atguigu/eduorder/mapper/xml/*.xml
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#开启熔断机制
#feign.hystrix.enabled=true
# 设置hystrix超时时间,默认1000ms
#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000

#关联的公众号appid
wx.pay.app_id=wx74862e0dfc69954
#商户号
wx.pay.partner=155895011
#商户key
wx.pay.partnerkey=T6m9iK73b0kn9g5v426MKHQH7X8rKwb
#回调地址
wx.pay.notifyurl=http://guli.shop/api/order/weixinPay/weixinNotify
#微信提供的固定地址
wx.pay.wxurl=https://api.mch.weixin.qq.com/pay/unifiedorder
#微信查询状态地址
wx.pay.queryUrl=https://api.mch.weixin.qq.com/pay/orderquery

1.5 创建读取微信支付相关信息的工具类

创建一个读取微信支付需要的信息的工具类ConstantWxPayUtils:

@Controller
public class ConstantWxPayUtils implements InitializingBean {
    @Value("${wx.pay.app_id}")
    private String appID;
    @Value("${wx.pay.partner}")
    private String partner;
    @Value("${wx.pay.partnerkey}")
    private String partnerKey;
    @Value("${wx.pay.notifyurl}")
    private String notifyUrl;
    @Value("${wx.pay.wxurl}")
    private String wxUrl;
    @Value("${wx.pay.queryUrl}")
    private String queryUrl;

    //定义公共静态常量
    public static String WX_PAY_APP_ID;
    public static String WX_PAY_PARTNER;
    public static String WX_PAY_PARTNER_KEY;
    public static String WX_PAY_NOTIFY_URL;
    public static String WX_PAY_WX_URL;
    public static String WX_PAY_QUERY_URL;

    @Override
    public void afterPropertiesSet() throws Exception {
        WX_PAY_APP_ID = appID;
        WX_PAY_PARTNER = partner;
        WX_PAY_PARTNER_KEY = partnerKey;
        WX_PAY_NOTIFY_URL = notifyUrl;
        WX_PAY_WX_URL = wxUrl;
        WX_PAY_QUERY_URL=queryUrl;
    }
}

1.6 其他工具类

用于随机生成订单号的工具类OrderNoUtil:

public class OrderNoUtil {
    /**
     * 获取订单号
     * @return
     */
    public static String getOrderNo() {
        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;
    }
}

HttpClient工具类:

/**
 * http请求客户端
 * 
 * @author xppll
 * 
 */
public class HttpClient {
    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 HttpClient(String url, Map<String, String> param) {
        this.url = url;
        this.param = param;
    }

    public HttpClient(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("?");
                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;
    }

}

2.生成订单

这里一共涉及service_order订单模块、service_ucenter用户模块、service-edu课程模块。

service_order使用Fegin远程调用其他模块的方法。

详细的Fegin的使用可以参考:SpringCloud-Feign远程调用

2.1 远程调用用户模块和课程模块

在service_order订单模块创建:

@Component
@FeignClient("service-ucenter") //调用的服务名称
public interface UcenterClient {

    //根据用户id获取用户信息,用于生成订单使用
    @PostMapping("/educenter/member/getUserInfoOrder/{id}")
    public UcenterMemberOrder getUserInfoOrder(@PathVariable("id") String id);
}
@Component
@FeignClient("service-edu") //调用的服务名称
public interface CourseClient {

    //根据课程id查询课程信息
    @PostMapping("/eduservice/coursefront/getCourseInfoOrder/{id}")
    public CourseWebOrder getCourseInfoOrder(@PathVariable("id") String id);

}

2.2 远程调用方法的实现

在service-edu课程模块实现根据课程id查询课程信息的getCourseInfoOrder方法

controller层:

/**
 * 根据课程id查询课程信息
 * @param id 客场id
 * @return CourseWebOrder
 */
@PostMapping("getCourseInfoOrder/{id}")
public CourseWebOrder getCourseInfoOrder(@PathVariable("id") String id) {
    CourseWebVo courseInfo = courseService.getBaseCourseInfo(id);
    CourseWebOrder courseWebOrder = new CourseWebOrder();
    BeanUtils.copyProperties(courseInfo, courseWebOrder);
    return courseWebOrder;
}

service层:

/**
 * 根据课程id,编写sql语句查询课程信息
 * @param courseId 课程id
 * @return CourseWebVo
 */
@Override
public CourseWebVo getBaseCourseInfo(String courseId) {
    return baseMapper.getBaseCourseInfo(courseId);
}

mapper层:

<!--根据课程id查询课程基本信息-->
<select id="getBaseCourseInfo" resultType="com.atguigu.eduservice.entity.frontvo.CourseWebVo">
    SELECT ec.id,
    ec.`title`,
    ec.`price`,
    ec.lesson_num as lessonNum,
    ec.cover,
    ec.buy_count  as buyCount,
    ec.view_count as viewCount,
    ecd.description,
    et.id            teacherId,
    et.`name`     AS teacherName,
    et.intro,
    et.avatar,
    es1.id        as subjectLevelOneId,
    es1.`title`   AS subjectLevelOne,
    es2.id        as subjectLevelTwoId,
    es2.`title`   AS subjectLevelTwo
    FROM edu_course ec
    LEFT JOIN edu_course_description ecd ON ec.id = ecd.id
    LEFT JOIN edu_teacher et ON ec.`teacher_id` = et.`id`
    LEFT JOIN edu_subject es1 ON ec.`subject_parent_id` = es1.`id`
    LEFT JOIN edu_subject es2 ON ec.`subject_id` = es2.`id`
    WHERE ec.id = #{courseId}
</select>

在service_ucenter用户模块实现根据用户id获取用户信息的getUserInfoOrder方法

controller层:

/**
 * 根据用户id获取用户信息,用于生成订单使用
 *
 * @param id 用户id
 * @return UcenterMemberOrder
 */
@PostMapping("getUserInfoOrder/{id}")
public UcenterMemberOrder getUserInfoOrder(@PathVariable("id") String id) {
    UcenterMember member = memberService.getById(id);
    UcenterMemberOrder memberOrder = new UcenterMemberOrder();
    BeanUtils.copyProperties(member, memberOrder);
    return memberOrder;
}

2.3 根据课程id和用户id生成订单

controller层:

@CrossOrigin
@RestController
@RequestMapping("/eduorder/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 生成订单的方法
     *
     * @param courseId 课程id
     * @param request  用于获取用户id
     * @return 返回订单号
     */
    @PostMapping("createOrder/{courseId}")
    public R saveOrder(@PathVariable("courseId") String courseId, HttpServletRequest request) {
        //通过JWT工具类获取用户id
        //创建订单,返回订单号
        String orderNo = orderService.createOrderById(courseId, JwtUtils.getMemberIdByJwtToken(request));
        return R.ok().data("orderId", orderNo);
    }
}

service层:

/**
 * 根据courseId和userId生成订单
 *
 * @param courseId 课程id
 * @param userId   用户id
 * @return 返回订单号
 */
@Override
public String createOrderById(String courseId, String userId) {
    //通过远程调佣根据用户id获取用户信息
    UcenterMemberOrder userInfoOrder = ucenterClient.getUserInfoOrder(userId);
    //通过远程调佣根据课程id获取课程信息
    CourseWebOrder courseInfoOrder = courseClient.getCourseInfoOrder(courseId);
    Order order = new Order();
    //订单号
    order.setOrderNo(OrderNoUtil.getOrderNo());
    order.setCourseId(courseId);
    order.setCourseTitle(courseInfoOrder.getTitle());
    order.setCourseCover(courseInfoOrder.getCover());

    order.setTeacherName(courseInfoOrder.getTeacherName());
    order.setTotalFee(courseInfoOrder.getPrice());
    order.setMemberId(userId);
    order.setMobile(userInfoOrder.getMobile());
    order.setNickname(userInfoOrder.getNickname());
    //支付状态  未支付:0  已支付:1
    order.setStatus(0);
    //支付类型  微信:1    支付宝:2
    order.setPayType(1);
    //保存到数据库
    baseMapper.insert(order);
    //返回订单号
    return order.getOrderNo();
}

3.查询订单信息

3.1 controller层

在OrderController里创建getOrderInfo用于生成订单:

/**
 * 根据订单id查询订单信息
 * @param orderId 订单id
 * @return 返回订单信息
 */
@GetMapping("getOrderInfo/{orderId}")
public R getOrderInfo(@PathVariable("orderId") String orderId) {
    Order order=orderService.getOrderByOrderId(orderId);
    return R.ok().data("item", order);
}

3.2 service层

/**
 * 根据订单id查询订单信息
 *
 * @param orderId 订单id
 * @return 返回订单信息
 */
@Override
public Order getOrderByOrderId(String orderId) {
    LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(Order::getOrderNo, orderId);
    return baseMapper.selectOne(queryWrapper);
}

4.生成微信支付的二维码

4.1 controller层

在PayLogController里创建createNative用于生成支付二维码:

@CrossOrigin
@RestController
@RequestMapping("/eduorder/paylog")
public class PayLogController {

    @Autowired
    private PayLogService payLogService;

    /**
     * 根据订单号生成微信支付二维码
     * @param orderNo 订单号
     * @return R
     */
    @GetMapping("createNative/{orderNo}")
    public R createNative(@PathVariable("orderNo") String orderNo){
        //返回信息,包含二维码地址,还有其他信息
        Map map=payLogService.createNative(orderNo);
        return R.ok().data(map);
    }
}

4.2 service层

  1. 生成微信支付二维码大概分为这几步:
  2. 根据订单号查询订单信息
  3. 使用map设置生成二维码需要的参数
  4. 发送httpclient请求,传递xml格式的参数,传入微信支付提供的固定地址
  5. 得到发送请求返回的结果
  6. 最终返回封装数据
/**
 * 根据订单号生成微信支付二维码
 * @param orderNo 订单号
 * @return map
 */
@Override
public Map createNative(String orderNo) {
    try {
        //1.根据订单号查询订单信息
        Order order = orderService.getOrderByOrderId(orderNo);
        //2.使用map设置生成二维码需要的参数
        Map m = new HashMap();
        //关联的公众号appid
        m.put("appid", ConstantWxPayUtils.WX_PAY_APP_ID);
        //商户号
        m.put("mch_id", ConstantWxPayUtils.WX_PAY_PARTNER);
        //随机字符串
        m.put("nonce_str", WXPayUtil.generateNonceStr());
        //课程标题
        m.put("body", order.getCourseTitle());
        //订单号
        m.put("out_trade_no", orderNo);
        //价格
        m.put("total_fee", order.getTotalFee().multiply(new BigDecimal("100")).longValue() + "");
        //支付的ip地址
        m.put("spbill_create_ip", "127.0.0.1");
        m.put("notify_url", ConstantWxPayUtils.WX_PAY_NOTIFY_URL);
        m.put("trade_type", "NATIVE");

        //3.发送httpclient请求,传递参数xml格式,传入微信支付提供的固定地址
        HttpClient client = new HttpClient(ConstantWxPayUtils.WX_PAY_WX_URL);
        //设置xml格式的参数,需要传入二维码参数m和商户key
        client.setXmlParam(WXPayUtil.generateSignedXml(m, ConstantWxPayUtils.WX_PAY_PARTNER_KEY));
        //默认不支持https,设置为true支持
        client.setHttps(true);
        //执行请求发送
        client.post();
        //4.得到发送请求返回的结果
        //返回的内容是xml格式
        String xml = client.getContent();
        //把xml格式转换为map集合
        Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);

        //5.最终返回封装数据
        Map map = new HashMap();
        //订单号
        map.put("out_trade_no", orderNo);
        //课程id
        map.put("course_id", order.getCourseId());
        //价格
        map.put("total_fee", order.getTotalFee());
        //返回二维码操作状态码
        map.put("result_code", resultMap.get("result_code"));
        //二维码地址
        map.put("code_url", resultMap.get("code_url"));
        return map;
    } catch (Exception e) {
        throw new GuliException(20001, "生成微信支付二维码失败");
    }
}

5.查询订单支付状态

5.1 controller层

在PayLogController里创建queryPayStatus用于获取支付状态:

/**
 * 获取支付状态
 * @param orderNo 订单号
 * @return R
 */
@GetMapping("queryPayStatus/{orderNo}")
public R queryPayStatus(@PathVariable("orderNo") String orderNo){
    Map<String, String> map=payLogService.queryPayStatus(orderNo);
    if(map==null){
        return R.error().message("支付出错!");
    }
    //如果map不为空,通过map获取订单状态
    if(map.get("trade_state").equals("SUCCESS")){
        //添加记录到支付表,更新订单表订单状态
        payLogService.updateOrdersStatus(map);
        return R.ok().message("支付成功!");
    }
    return R.ok().code(25000).message("正在支付中...");
}

5.2 service层

根据订单号查询订单支付状态大概分为一下几步:

  1. 封装参数
  2. 发送httpclient
  3. 得到请求返回的内容
/**
 * 根据订单号查询订单支付状态
 * @param orderNo
 * @return
 */
@Override
public Map<String, String> queryPayStatus(String orderNo) {
    try {
        //1.封装参数
        Map m=new HashMap();
        //关联的公众号appid
        m.put("appid",ConstantWxPayUtils.WX_PAY_APP_ID);
        //商户号
        m.put("mch_id",ConstantWxPayUtils.WX_PAY_PARTNER);
        //订单号
        m.put("out_trade_no",orderNo);
        //随机字符串
        m.put("nonce_str",WXPayUtil.generateNonceStr());
        //2.发送httpclient
        HttpClient client = new HttpClient(ConstantWxPayUtils.WX_PAY_QUERY_URL);
        client.setXmlParam(WXPayUtil.generateSignedXml(m,ConstantWxPayUtils.WX_PAY_PARTNER_KEY));
        client.setHttps(true);
        client.post();
        //3.得到请求返回的内容
        String xml = client.getContent();
        Map<String, String> resultMap=WXPayUtil.xmlToMap(xml);
        return resultMap;
    } catch (Exception e) {
        e.printStackTrace();
        throw  new GuliException(20001,"查询订单支付状态失败");
    }
}

如果支付成功,需要添加记录到支付表,更新订单表订单状态:

/**
 * 向支付表添加记录,更新订单表订单状态
 * @param map
 */
@Override
public void updateOrdersStatus(Map<String, String> map) {
    //从map获取订单号
    String orderNo = map.get("out_trade_no");
    Order order = orderService.getOrderByOrderId(orderNo);
    //更新订单表t_order的订单状态status
    if(order.getStatus().intValue()==1){
        return;
    }
    order.setStatus(1);
    orderService.updateById(order);

    //向支付表 t_pag_log 添加记录
    PayLog payLog=new PayLog();
    payLog.setOrderNo(orderNo);

    payLog.setPayTime(new Date());
    //支付类型
    payLog.setPayType(1);
    //支付金额
    payLog.setTotalFee(order.getTotalFee());
    //支付状态
    payLog.setTradeState(map.get("trade_state"));
    //交易流水号
    payLog.setTransactionId(map.get("transaction_id"));
    //其他属性,转为json字符串
    payLog.setAttr(JSONObject.toJSONString(map));
    baseMapper.insert(payLog);
} 

以上就是SpringBoot实现整合微信支付方法详解的详细内容,更多关于SpringBoot整合微信支付的资料请关注云海天教程其它相关文章!