Spring Boot + MyBatis 集成微信支付实现

Spring Boot + MyBatis 集成微信支付实现

下面我将详细介绍使用 Spring Boot + MyBatis 实现微信支付(JSAPI支付)的完整流程和代码示例。

微信支付核心流程(JSAPI支付)

  1. 商户系统生成订单
  2. 获取用户OpenID(微信公众号支付)
  3. 调用微信统一下单API
  4. 生成JSAPI调起支付参数
  5. 前端调起微信支付
  6. 微信异步通知支付结果
  7. 商户处理支付结果更新订单状态
  8. 返回处理结果给微信

代码实现示例

1. 添加依赖


<dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    
    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>2.2.2version>
    dependency>
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
    dependency>
    
    
    <dependency>
        <groupId>com.github.wechatpay-apiv3groupId>
        <artifactId>wechatpay-apache-httpclientartifactId>
        <version>0.4.7version>
    dependency>
    
    
    <dependency>
        <groupId>com.fasterxml.jackson.dataformatgroupId>
        <artifactId>jackson-dataformat-xmlartifactId>
    dependency>
    
    
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
    dependency>
dependencies>

2. 微信支付配置类

@Configuration
public class WxPayConfig {

    @Value("${wxpay.app_id}")
    private String appId;
    
    @Value("${wxpay.mch_id}")
    private String mchId;
    
    @Value("${wxpay.mch_key}")
    private String mchKey;
    
    @Value("${wxpay.notify_url}")
    private String notifyUrl;
    
    @Value("${wxpay.cert_path}")
    private String certPath; // 证书路径(apiclient_cert.p12)
    
    @Value("${wxpay.api_v3_key}")
    private String apiV3Key;

    // 微信支付HttpClient
    @Bean
    public CloseableHttpClient wxPayHttpClient() throws Exception {
        // 加载商户私钥
        PrivateKey merchantPrivateKey = getPrivateKey();
        
        // 使用自动更新平台证书的验证器
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
                apiV3Key.getBytes(StandardCharsets.UTF_8));
        
        // 初始化httpClient
        return WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
    }

    // 获取商户私钥
    private PrivateKey getPrivateKey() throws Exception {
        InputStream inputStream = new ClassPathResource(certPath).getInputStream();
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        keyStore.load(inputStream, mchId.toCharArray());
        return (PrivateKey) keyStore.getKey(mchId, mchId.toCharArray());
    }

    // 微信支付服务
    @Bean
    public WxPayService wxPayService(CloseableHttpClient wxPayHttpClient) {
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(appId);
        payConfig.setMchId(mchId);
        payConfig.setKey(mchKey);
        payConfig.setNotifyUrl(notifyUrl);
        payConfig.setApiV3Key(apiV3Key);
        return new WxPayServiceImpl(payConfig, wxPayHttpClient);
    }
}

3. 实体类和Mapper

// 订单实体
@Data
public class Order {
    private Long id;
    private String orderNo;   // 商户订单号
    private BigDecimal amount;// 支付金额
    private Integer status;   // 0-待支付, 1-已支付
    private LocalDateTime createTime;
    private String openid;    // 微信用户openid
}

// MyBatis Mapper
@Mapper
public interface OrderMapper {
    @Insert("INSERT INTO orders(order_no, amount, status, create_time, openid) " +
            "VALUES(#{orderNo}, #{amount}, 0, NOW(), #{openid})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insert(Order order);
    
    @Update("UPDATE orders SET status = #{status} WHERE order_no = #{orderNo}")
    void updateStatus(@Param("orderNo") String orderNo, @Param("status") int status);
    
    @Select("SELECT * FROM orders WHERE order_no = #{orderNo}")
    Order findByOrderNo(String orderNo);
}

4. 支付服务类

@Service
public class WxPayService {

    @Autowired private WxPayService wxPayService;
    @Autowired private OrderMapper orderMapper;

    // 创建支付订单
    public Map<String, String> createPayOrder(Order order) throws Exception {
        orderMapper.insert(order); // 保存订单到数据库

        // 构建统一下单请求
        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
        request.setBody("商品支付");
        request.setOutTradeNo(order.getOrderNo());
        request.setTotalFee(order.getAmount().multiply(new BigDecimal(100)).intValue()); // 微信支付单位为分
        request.setSpbillCreateIp("用户IP"); // 实际获取用户IP
        request.setNotifyUrl(wxPayConfig.getNotifyUrl());
        request.setTradeType("JSAPI");
        request.setOpenid(order.getOpenid());

        // 调用统一下单API
        WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(request);
        
        // 生成JSAPI调起支付参数
        Map<String, String> payParams = new HashMap<>();
        payParams.put("appId", appId);
        payParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
        payParams.put("nonceStr", WxPayUtil.generateNonceStr());
        payParams.put("package", "prepay_id=" + result.getPrepayId());
        payParams.put("signType", "RSA");
        
        // 生成签名
        String sign = WxPayUtil.generateSignature(payParams, mchKey);
        payParams.put("paySign", sign);
        
        return payParams;
    }

    // 处理支付结果通知
    public String handleNotify(HttpServletRequest request) {
        try {
            // 解析通知内容
            String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
            WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(xmlResult);

            // 验证签名和业务结果
            if ("SUCCESS".equals(notifyResult.getResultCode())) {
                // 更新订单状态
                String orderNo = notifyResult.getOutTradeNo();
                orderMapper.updateStatus(orderNo, 1); // 更新为已支付
                
                // 返回成功响应
                return "";
            }
        } catch (Exception e) {
            // 记录日志
        }
        return "";
    }
}

5. 控制器

@RestController
@RequestMapping("/wxpay")
public class WxPayController {

    @Autowired private WxPayService wxPayService;

    // 创建支付订单(返回调起支付所需参数)
    @PostMapping("/create")
    public Map<String, String> createOrder(@RequestParam BigDecimal amount, 
                                          @RequestParam String openid) throws Exception {
        Order order = new Order();
        order.setOrderNo(UUID.randomUUID().toString().replace("-", ""));
        order.setAmount(amount);
        order.setOpenid(openid);
        return wxPayService.createPayOrder(order);
    }

    // 微信支付结果通知(需要公网可访问)
    @PostMapping(value = "/notify", produces = "application/xml; charset=UTF-8")
    public String wxpayNotify(HttpServletRequest request) {
        return wxPayService.handleNotify(request);
    }
    
    // 查询订单状态
    @GetMapping("/status")
    public String getOrderStatus(@RequestParam String orderNo) {
        Order order = orderMapper.findByOrderNo(orderNo);
        if (order == null) {
            return "订单不存在";
        }
        return order.getStatus() == 1 ? "已支付" : "未支付";
    }
}

6. 前端调用示例(Vue.js)

<template>
  <div>
    <button @click="createOrder">微信支付button>
  div>
template>

<script>
import axios from 'axios';

export default {
  methods: {
    async createOrder() {
      try {
        // 1. 获取用户openid(实际项目中需要通过OAuth2授权获取)
        const openid = '用户openid';
        
        // 2. 创建订单
        const response = await axios.post('/wxpay/create', {
          amount: 100, // 支付金额(元)
          openid: openid
        });
        
        // 3. 调起微信支付
        const payParams = response.data;
        wx.chooseWXPay({
          ...payParams,
          success: (res) => {
            console.log('支付成功', res);
            // 可跳转到支付成功页面
          },
          fail: (err) => {
            console.error('支付失败', err);
          }
        });
      } catch (error) {
        console.error('创建订单失败', error);
      }
    }
  }
}
script>

7. 配置文件

# application.properties
# 微信支付配置
wxpay.app_id=wx1234567890abcdef
wxpay.mch_id=1234567890
wxpay.mch_key=your_mch_key
wxpay.api_v3_key=your_api_v3_key
wxpay.notify_url=http://your-domain.com/wxpay/notify
wxpay.cert_path=classpath:cert/apiclient_cert.p12

# MySQL配置
spring.datasource.url=jdbc:mysql://localhost:3306/wxpay_demo
spring.datasource.username=root
spring.datasource.password=123456

关键流程说明

  1. 获取用户OpenID

    • 微信公众号支付需要获取用户的openid
    • 通过微信OAuth2授权流程获取(需在微信公众号后台配置授权域名)
  2. 调用统一下单API

    • 使用 WxPayUnifiedOrderRequest 构建请求
    • 关键参数:订单号、金额(分)、openid、回调地址
    • 返回 prepay_id(预支付交易会话标识)
  3. 生成JSAPI调起支付参数

    • 包含 appId、timeStamp、nonceStr、package、signType
    • 使用商户密钥生成签名(paySign)
  4. 前端调起支付

    • 使用微信JSAPI的 chooseWXPay 方法
    • 传入支付参数调起支付界面
  5. 接收异步通知

    • 必须验证签名(防止伪造请求)
    • 检查 result_code 是否为 SUCCESS
    • 更新订单状态(注意处理幂等性)
  6. 安全注意事项

    • 支付金额需与订单金额比对(防止金额篡改)
    • 敏感操作记录日志
    • 异步通知处理需要保证幂等性

微信支付与支付宝支付的区别

特性 微信支付 支付宝支付
支付方式 JSAPI、Native、App、H5等 电脑网站、手机网站、App等
金额单位
签名算法 HMAC-SHA256/RSA RSA/RSA2
通知格式 XML Form表单/JSON
证书要求 需要API证书 不需要证书
OpenID 需要获取用户openid 不需要用户标识
支付流程 需要前端调起支付 自动跳转支付页面

常见问题解决方案

  1. 支付金额单位错误

    • 微信支付单位为分,支付宝单位为元
    • 转换公式:微信金额 = 支付宝金额 × 100
  2. 签名验证失败

    • 检查商户密钥是否正确
    • 确认签名算法一致(微信默认HMAC-SHA256)
    • 验证参数是否完整且顺序正确
  3. 异步通知处理

    • 必须返回XML格式的响应
    • 处理速度要快(微信会在30秒内重试)
    • 保证幂等性(防止重复处理)
  4. 跨域问题

    • 前端调起支付需要在微信内置浏览器
    • 确保公众号JS接口安全域名配置正确
  5. 证书管理

    • 定期更新API证书
    • 证书文件妥善保管(不要提交到代码仓库)

以上是使用 Spring Boot + MyBatis 实现微信支付的完整流程和代码示例。实际开发中需要根据具体业务需求进行调整,并注意支付安全相关事项。

你可能感兴趣的:(后端技术,spring,boot,mybatis,微信)