一.主要流程:

  • 用户发起支付,后端生成新订单,向支付平台获取预支付交易会话标识

  • 前端拿着预支付交易会话标识调用支付平台处理支付接口

  • 用户支付完成后,支付平台异步通知我们的回调接口

  • 回调接口验证签名

  • 验证通过后更新订单状态!!!

  • 通知商家有新订单

注意事项:

1. 回调接口必须是公网可访问的

  • 需要正确配置支付宝和微信的公钥用于验证签名

  • 回调接口要做幂等处理,因为可能会收到多次通知

  • 建议记录支付日志,方便排查问题

  • 商家通知可以使用多种方式,如WebSocket、短信等

这样就实现了完整的支付流程:发起支付->用户支付->接收回调->更新状态->通知商家。



二、微信支付(接入APP支付和小程序支付

官方文档
JSAPI/小程序下单_JSAPI支付|微信支付合作伙伴文档中心

APP下单_APP支付|微信支付合作伙伴文档中心

1.准备工作

1.1 服务商配置:

- 申请服务商商户号(sp_mchid)和服务商APPID(sp_appid)

- 申请API证书和APIv3密钥

- 配置支付回调地址

1.2 子商户配置:

- 为子商户进件获取子商户号(sub_mchid)

- 小程序获取子商户APPID(sub_appid)

- APP获取子商户APPID(sub_appid)

# 微信支付配置
wxpay:
  app-id: ${your-sp-appid}        # 服务商应用ID
  mch-id: ${your-sp-mchid}        # 服务商户号
  sub-app-id: ${your-sub-appid}   # 子商户应用ID
  sub-mch-id: ${your-sub-mchid}   # 子商户号
  mch-serial-no: ${your-serial-no}
  private-key-path: classpath:cert/apiclient_key.pem
  api-v3-key: ${your-api-v3-key}
  notify-url: ${your-domain}/pay/notify/wxpay
  return-url: ${your-domain}/pay/return/wxpay
  refund-notify-url: ${your-domain}/pay/notify/wxpay/refund  # 退款通知地址

2.支付流程

1.流程图

app:

小程序:

2.后端调用下单接口(其实就是一个发起支付的过程)返回prepay_id(SysFeeConfigServiceImpl)

小程序:

app:


大致流程

// 2.1 小程序下单
String apiUrl = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi";
Map<String, Object> requestBody = new HashMap<>();
// 服务商信息
requestBody.put("sp_appid", wxConfig.getAppId());        
requestBody.put("sp_mchid", wxConfig.getMchId());        
requestBody.put("sub_appid", wxConfig.getSubAppId());    
requestBody.put("sub_mchid", wxConfig.getSubMchId());    
// 支付者信息
requestBody.put("payer", Collections.singletonMap("sub_openid", openId));

// 2.2 APP下单
String apiUrl = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/app";
Map<String, Object> requestBody = new HashMap<>();
// 服务商信息
requestBody.put("sp_appid", wxConfig.getAppId());        
requestBody.put("sp_mchid", wxConfig.getMchId());        
requestBody.put("sub_appid", wxConfig.getSubAppId());    
requestBody.put("sub_mchid", wxConfig.getSubMchId());    

完成具体代码实现

 /**
     * 处理微信支付下单
     */
    private String processWeChatPayment(Order order) throws Exception {
        // 构建基础请求参数
        Map<String, Object> requestBody = new HashMap<>();
        String apiUrl;
        
        if (isWxMiniProgram()) {
            // 小程序下单
            apiUrl = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi";
            // 添加小程序特有参数
            Map<String, String> payer = new HashMap<>();
            payer.put("sub_openid", getWxOpenId());
            requestBody.put("payer", payer);
        } else {
            // APP下单
            apiUrl = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/app";
        }

        // 服务商信息
        requestBody.put("sp_appid", wxConfig.getAppId());
        requestBody.put("sp_mchid", wxConfig.getMchId());
        requestBody.put("sub_appid", wxConfig.getSubAppId());
        requestBody.put("sub_mchid", wxConfig.getSubMchId());

        // 订单信息
        requestBody.put("description", order.getDescription());
        requestBody.put("out_trade_no", order.getOrderNo());
        requestBody.put("notify_url", wxConfig.getNotifyUrl());

        // 订单金额
        Map<String, Object> amount = new HashMap<>();
        amount.put("total", order.getAmount().multiply(new BigDecimal("100")).intValue());
        amount.put("currency", "CNY");
        requestBody.put("amount", amount);

        // 场景信息
        Map<String, Object> sceneInfo = new HashMap<>();
        sceneInfo.put("payer_client_ip", getClientIp());
        if (order.getMerchantId() != null) {
            Map<String, String> storeInfo = new HashMap<>();
            storeInfo.put("id", order.getMerchantId().toString());
            storeInfo.put("name", "xxx店");
            storeInfo.put("area_code", "000000");
            storeInfo.put("address", "xxx地址");
            sceneInfo.put("store_info", storeInfo);
        }
        requestBody.put("scene_info", sceneInfo);

        // 结算信息
        Map<String, Object> settleInfo = new HashMap<>();
        settleInfo.put("profit_sharing", true);
        requestBody.put("settle_info", settleInfo);

        // 发送请求
        String prepayId = sendWxPayRequest(apiUrl, requestBody);

        // 根据场景返回不同的支付参数
        return isWxMiniProgram() ? 
               generateWxMiniPayParams(prepayId) : 
               generateWxAppPayParams(prepayId);
    }

    /**
     * 发送微信支付请求
     */
    private String sendWxPayRequest(String apiUrl, Map<String, Object> requestBody) throws Exception {
        HttpPost httpPost = new HttpPost(apiUrl);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-Type", "application/json");
        
        String token = generateToken(wxConfig.getMchId(), 
                                   wxConfig.getMchSerialNo(),
                                   wxConfig.getPrivateKeyPath());
        httpPost.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 " + token);
        
        httpPost.setEntity(new StringEntity(objectMapper.writeValueAsString(requestBody)));

        try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
            String responseBody = EntityUtils.toString(response.getEntity());
            Map<String, Object> result = objectMapper.readValue(responseBody, Map.class);
            
            String prepayId = (String) result.get("prepay_id");
            if (prepayId == null) {
                throw new RuntimeException("获取prepay_id失败: " + responseBody);
            }
            return prepayId;
        }
    }

    /**
     * 生成小程序调起支付所需的参数
     */
    private String generateWxMiniPayParams(String prepayId) throws Exception {
        Map<String, Object> payParams = new HashMap<>();
        payParams.put("appId", wxConfig.getSubAppId());  // 注意这里使用子商户APPID
        payParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
        payParams.put("nonceStr", generateNonceStr());
        payParams.put("package", "prepay_id=" + prepayId);
        payParams.put("signType", "RSA");
        
        // 签名
        String message = payParams.get("appId") + "\n" +
                        payParams.get("timeStamp") + "\n" +
                        payParams.get("nonceStr") + "\n" +
                        payParams.get("package") + "\n";
        
        payParams.put("paySign", signMessage(message));
        
        return objectMapper.writeValueAsString(payParams);
    }

    /**
     * 生成APP调起支付所需的参数
     */
    private String generateWxAppPayParams(String prepayId) throws Exception {
        Map<String, Object> payParams = new HashMap<>();
        payParams.put("appid", wxConfig.getSubAppId());      // 注意这里使用子商户APPID
        payParams.put("partnerid", wxConfig.getSubMchId());  // 注意这里使用子商户号
        payParams.put("prepayid", prepayId);
        payParams.put("package", "Sign=WXPay");
        payParams.put("noncestr", generateNonceStr());
        payParams.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        
        // 签名
        String message = payParams.get("appid") + "\n" +
                        payParams.get("timestamp") + "\n" +
                        payParams.get("noncestr") + "\n" +
                        payParams.get("prepayid") + "\n";
        
        payParams.put("sign", signMessage(message));
        
        return objectMapper.writeValueAsString(payParams);
    }

    /**
     * 判断是否是小程序环境
     */
    private boolean isWxMiniProgram() {
        // TODO: 根据实际情况判断是否是小程序环境
        // 可以通过请求头或其他方式判断
        return true;
    }

    /**
     * 获取微信openid
     */
    private String getWxOpenId() {
        // TODO: 获取当前用户的微信openid
        // 可以从session或其他地方获取
        return "user_openid";
    }

    /**
     * 生成随机字符串
     */
    private String generateNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    /**
     * 签名
     */
    private String signMessage(String message) throws Exception {
        // TODO: 使用商户私钥对消息进行签名
        return "signature";
    }

    /**
     * 生成认证Token
     */
    private String generateToken(String mchId, String serialNo, String privateKeyPath) throws Exception {
        // TODO: 实现微信支付V3认证token生成
        // 1. 获取请求时间戳
        // 2. 生成随机串
        // 3. 构造签名串
        // 4. 使用商户私钥签名
        // 5. 按照格式拼接认证头
        return "mchid=\"" + mchId + "\",nonce_str=\"xxx\",timestamp=\"xxx\",serial_no=\"" + serialNo + "\",signature=\"xxx\"";
    }

    /**
     * 获取客户端IP
     */
    private String getClientIp() {
        // TODO: 实现获取客户端IP
        return "127.0.0.1";
    }

3..前端拿着prepay_id去调起支付接口

小程序:

app:

// 3.1 小程序调起支付
wx.requestPayment({
  appId: result.appId,
  timeStamp: result.timeStamp,
  nonceStr: result.nonceStr,
  package: result.package,
  signType: 'RSA',
  paySign: result.paySign,
  success(res) { },
  fail(err) { }
})

// 3.2 APP调起支付
// Android
PayReq request = new PayReq();
request.appId = payParams.appid;
request.partnerId = payParams.partnerid;
request.prepayId = payParams.prepayid;
request.packageValue = payParams.package;
request.nonceStr = payParams.noncestr;
request.timeStamp = payParams.timestamp;
request.sign = payParams.sign;
api.sendReq(request);

// iOS
PayReq *request = [[PayReq alloc] init];
request.partnerId = payParams[@"partnerid"];
request.prepayId = payParams[@"prepayid"];
request.package = payParams[@"package"];
request.nonceStr = payParams[@"noncestr"];
request.timeStamp = [payParams[@"timestamp"] intValue];
request.sign = payParams[@"sign"];
[WXApi sendReq:request];

4.后端调用支付结果回调接口(PayCallbackController)

//大致流程
@PostMapping("/pay/notify/wxpay")
public String wxpayNotify(HttpServletRequest request) {
    // 4.1 验证签名
    String timestamp = request.getHeader("Wechatpay-Timestamp");
    String nonce = request.getHeader("Wechatpay-Nonce");
    String signature = request.getHeader("Wechatpay-Signature");
    
    // 4.2 解密数据
    Map<String, Object> resource = (Map<String, Object>)notifyData.get("resource");
    String decryptData = decryptResource(resource);
    
    // 4.3 处理订单
    if ("SUCCESS".equals(tradeState)) {
        // 更新订单状态
        updateOrderStatus(order, transactionId);
        // 处理分账(可以像我具体实现代码一样直接集成到updateOrderStatus里面)
        handleProfitSharing(order, transactionId);
    }
}

具体回调通知完整具体实现

    /**
     * 微信支付回调通知
     */
    @PostMapping("/wxpay")
    public String wxpayNotify(HttpServletRequest request, HttpServletResponse response) {
        try {
            // 1. 获取请求头参数
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String signature = request.getHeader("Wechatpay-Signature");
            String serial = request.getHeader("Wechatpay-Serial");

            // 2. 读取请求体
            String body = readRequestBody(request);
            
            // 3. 验证签名
            if (!verifyWxSign(timestamp, nonce, body, signature, serial)) {
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                return "{\"code\": \"FAIL\",\"message\": \"签名验证失败\"}";
            }

            // 4. 解析通知数据
            Map<String, Object> notifyData = objectMapper.readValue(body, Map.class);
            if (!"TRANSACTION.SUCCESS".equals(notifyData.get("event_type"))) {
                response.setStatus(HttpServletResponse.SC_OK);
                return "";
            }

            // 5. 解密resource数据
            Map<String, Object> resource = (Map<String, Object>) notifyData.get("resource");
            String decryptData = decryptResource(resource);
            Map<String, Object> orderInfo = objectMapper.readValue(decryptData, Map.class);

            // 6. 处理订单
            String orderNo = (String) orderInfo.get("out_trade_no");
            String transactionId = (String) orderInfo.get("transaction_id");
            String tradeState = (String) orderInfo.get("trade_state");

            Order order = orderService.selectOrderByNo(orderNo);
            if (order != null && "SUCCESS".equals(tradeState)) {
                // 更新订单状态
                updateOrderStatus(order, transactionId, "WECHAT");
            }

            // 7. 返回成功
            response.setStatus(HttpServletResponse.SC_OK);
            return "";

        } catch (Exception e) {
            log.error("处理微信支付回调异常", e);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return "{\"code\": \"FAIL\",\"message\": \"处理失败\"}";
        }
    }

5.分账处理(SysFeeConfigServiceImpl)

//大致流程
private void handleProfitSharing(Order order, String transactionId) {
    // 5.1 计算分账金额
    BigDecimal totalAmount = order.getAmount();
    BigDecimal serviceFee = totalAmount.multiply(new BigDecimal("0.05"));//平台收取5%服务费
    
    // 5.2 添加分账接收方
    List<ProfitSharingReceiver> receivers = new ArrayList<>();
    // 平台分账
    receivers.add(new ProfitSharingReceiver("平台", serviceFee));
    // 商家分账
    receivers.add(new ProfitSharingReceiver("商家", totalAmount.subtract(serviceFee)));
    
    // 5.3 请求分账
    profitSharingService.profitSharing(order.getOrderNo(), transactionId, receivers);
}

完整分账代码

    /**
     * 处理订单支付成功后的分账
     */
    private void handleProfitSharing(Order order, String transactionId, String payMethod) {
        try {
            // 计算平台服务费
            BigDecimal totalAmount = order.getAmount();
            SysFeeConfig sysFeeConfig=sysFeeConfigService.selectFeeConfigByType(order.getType());
            BigDecimal serviceFeeRate = sysFeeConfig.getFeePercent();// 平台抽成比例
            BigDecimal serviceFee = totalAmount.multiply(serviceFeeRate);
            order.setServiceFee(serviceFee);

            // 构建分账请求
            ProfitSharingRequest request = new ProfitSharingRequest();
            request.setTransactionId(transactionId);
            request.setOutOrderNo(order.getOrderNo());
            
            // 添加分账接收方
            List<ProfitSharingReceiver> receivers = new ArrayList<>();
            
            // 平台分账
            ProfitSharingReceiver platformReceiver = new ProfitSharingReceiver();
            platformReceiver.setType("MERCHANT_ID");
            platformReceiver.setAccount(getPlatformAccount(payMethod)); // 获取平台收款账号
            platformReceiver.setAmount(serviceFee);
            platformReceiver.setDescription("平台服务费");
            receivers.add(platformReceiver);
            
            // 商家分账
            ProfitSharingReceiver merchantReceiver = new ProfitSharingReceiver();
            merchantReceiver.setType("MERCHANT_ID");
            merchantReceiver.setAccount(order.getMerchantId().toString());
            merchantReceiver.setAmount(totalAmount.subtract(serviceFee));
            merchantReceiver.setDescription("商家结算");
            receivers.add(merchantReceiver);
            
            request.setReceivers(receivers);

            // 发起分账请求
            ProfitSharingResult result = profitSharingService.profitSharing(request, payMethod);
            
            if (result.isSuccess()) {
                // 更新订单分账状态
                order.setProfitSharingStatus(Order.PROFIT_SHARING_SUCCESS);
                orderService.updateOrder(order);
                log.info("订单{}分账成功", order.getOrderNo());
            } else {
                log.error("订单{}分账失败: {}", order.getOrderNo(), result.getErrorMsg());
                // 可以考虑重试机制或人工处理
            }
            
        } catch (Exception e) {
            log.error("处理订单{}分账时发生错误", order.getOrderNo(), e);
            // 记录分账失败,等待定时任务重试或人工处理
        }
    }

    /**
     * 获取平台收款账号
     */
    private String getPlatformAccount(String payMethod) {
        switch (payMethod) {
            case "WECHAT":
                return wxConfig.getMchId();
            case "ALIPAY":
                return aliPayConfig.getAppId();
            case "UNIONPAY":
                return unionPayConfig.getMerId();
            default:
                throw new IllegalArgumentException("Unsupported payment method: " + payMethod);
        }
    }

    /**
     * 更新订单状态并处理分账
     */
    private void updateOrderStatus(Order order, String transactionId, String payMethod) {
        order.setStatus(Order.STATUS_UNCONFIRMED);  // 更新为待确认状态
        order.setTransactionId(transactionId);      // 保存交易号
        order.setPaymentTime(new Date());           // 设置支付时间
        orderService.updateOrder(order);
        
        // 处理分账
        handleProfitSharing(order, transactionId, payMethod);
        
        // 发送通知给商家
        notifyMerchant(order);
    }

3.退款流程

这块无论是小程序,还是app还是h5或者是别的,都一样,他们只有支付的时候接口不一样

(SysFeeConfigServiceImpl)

/**
     * 处理微信退款
     */
    private String processWeChatRefund(Order order, BigDecimal refundAmount, String refundNo) throws Exception {
        // 构建退款请求参数
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("sp_appid", wxConfig.getAppId());
        requestBody.put("sp_mchid", wxConfig.getMchId());
        requestBody.put("sub_mchid", wxConfig.getSubMchId());
        requestBody.put("transaction_id", order.getTransactionId());
        requestBody.put("out_refund_no", refundNo);

        // 退款金额信息
        Map<String, Object> amount = new HashMap<>();
        amount.put("refund", refundAmount.multiply(new BigDecimal("100")).intValue());
        amount.put("total", order.getAmount().multiply(new BigDecimal("100")).intValue());
        amount.put("currency", "CNY");
        requestBody.put("amount", amount);

        // 退款原因
        requestBody.put("reason", "商家退款");
        requestBody.put("notify_url", wxConfig.getRefundNotifyUrl());

        // 发送退款请求
        String apiUrl = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
        HttpPost httpPost = new HttpPost(apiUrl);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-Type", "application/json");
        
        String token = generateToken(wxConfig.getMchId(), 
                                   wxConfig.getMchSerialNo(),
                                   wxConfig.getPrivateKeyPath());
        httpPost.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 " + token);
        
        httpPost.setEntity(new StringEntity(objectMapper.writeValueAsString(requestBody)));

        try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
            String responseBody = EntityUtils.toString(response.getEntity());
            Map<String, Object> result = objectMapper.readValue(responseBody, Map.class);
            
            // 处理退款结果
            String status = (String) result.get("status");
            if ("SUCCESS".equals(status) || "PROCESSING".equals(status)) {
                return refundNo;
            } else {
                throw new RuntimeException("退款失败: " + result.get("message"));
            }
        }
    }

4.pom文件

<!-- 工具库 -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

<!-- JSON处理 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

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

<!-- 加密和HTTP -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.70</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.14</version>
</dependency>

  <!-- Spring Boot Configuration Processor -->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
  </dependency>  

Google开发的Java工具库

  • 提供集合、缓存、字符串处理、并发等实用工具

  • 可以简化代码编写,提高开发效率

FastJson,阿里巴巴开发的JSON处理库

  • 用于Java对象与JSON字符串的相互转换

  • 性能较好,使用广泛

bouncycastle- 开源的密码学库

  • bcprov: 提供基础的密码学算法

  • bcpkix: 提供证书处理相关功能

  • 用于处理PEM格式的密钥、证书等

apache.httpcomponents- Apache提供的HTTP客户端库

  • 用于发送HTTP请求

  • 支持HTTPS、代理、连接池等功能

5、相关配置文件config

微信配置

package com.ruoyi.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

/**
 * 微信支付配置
 *
 * @author adi
 * #Date: 2025/2/28 18:24
 */
@Data
@Primary
@Component("wxAccountConfig")
@ConfigurationProperties(prefix = "wxpay")
public class WxAccountConfig {
    /** 服务商应用ID */
    private String appId;
    
    /** 服务商户号 */
    private String mchId;
    
    /** 子商户应用ID */
    private String subAppId;
    
    /** 子商户号 */
    private String subMchId;
    
    /** 商户API证书序列号 */
    private String mchSerialNo;
    
    /** 商户私钥文件路径 */
    private String privateKeyPath;
    
    /** APIv3密钥 */
    private String apiV3Key;
    
    /** 异步通知地址 */
    private String notifyUrl;
    
    /** 同步跳转地址 */
    private String returnUrl;
    
    /** 退款通知地址 */
    private String refundNotifyUrl;
}

6.启动项目加注解

package com.ruoyi;

import com.ruoyi.config.AliPayAccountConfig;
import com.ruoyi.config.WxAccountConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

/**
 * 启动程序
 * 
 * @author ruoyi
 */
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@EnableConfigurationProperties({//属性的自动注入
    AliPayAccountConfig.class,
    WxAccountConfig.class
})
public class RuoYiApplication
{
    public static void main(String[] args)
    {
        // System.setProperty("spring.devtools.restart.enabled", "false");
        SpringApplication.run(RuoYiApplication.class, args);
        System.out.println("(♥◠‿◠)ノ゙  若依启动成功   ლ(´ڡ`ლ)゙  \n" +
                " .-------.       ____     __        \n" +
                " |  _ _   \\      \\   \\   /  /    \n" +
                " | ( ' )  |       \\  _. /  '       \n" +
                " |(_ o _) /        _( )_ .'         \n" +
                " | (_,_).' __  ___(_ o _)'          \n" +
                " |  |\\ \\  |  ||   |(_,_)'         \n" +
                " |  | \\ `'   /|   `-'  /           \n" +
                " |  |  \\    /  \\      /           \n" +
                " ''-'   `'-'    `-..-'              ");
    }
}

三、支付宝支付

四、集成多种支付统一管理

创建一个PaymentService服务层,实现类里通过processPayment的switch来管理多种支付情况

通过processRefund来管理退款平台

@Override
public String processPayment(Order order, String paymentMethod) {
    try {
        // 设置订单描述
        String description = String.format("%s订单-%s", order.getType(), order.getOrderNo());
        order.setDescription(description);
        
        // 设置支付金额(押金 + 费用)
        if (order.getAmount() == null) {
            BigDecimal totalAmount = order.getDeposit().add(order.getPrice());
            order.setAmount(totalAmount);
        }

    switch (paymentMethod) {
        case "WECHAT":
            return processWeChatPayment(order);//调用微信支付
        case "ALIPAY":
            return processAlipayPayment(order);//支付部
            case "UNIONPAY":
                return processUnionPayment(order);//云闪付
        case "BALANCE":
            return processBalancePayment(order);//余额支付
        default:
            throw new IllegalArgumentException("Unsupported payment method: " + paymentMethod);
        }
    } catch (Exception e) {
        log.error("支付处理失败", e);
        throw new RuntimeException("支付处理失败: " + e.getMessage());
    }
}



 @Override
    public String processRefund(Order order, BigDecimal refundAmount) throws Exception {
        // 验证退款金额
        if (refundAmount.compareTo(order.getAmount()) > 0) {
            throw new IllegalArgumentException("退款金额不能大于支付金额");
        }

        // 生成退款单号
        String refundNo = generateRefundNo(order.getOrderNo());

        // 根据支付方式处理退款
        switch (order.getPaymentType()) {
            case "WECHAT":
                return processWeChatRefund(order, refundAmount, refundNo);
            case "ALIPAY":
                return processAlipayRefund(order, refundAmount, refundNo);
            case "UNIONPAY":
                return processUnionPayRefund(order, refundAmount, refundNo);
            default:
                throw new IllegalArgumentException("Unsupported payment method: " + order.getPaymentType());
        }
    }

创建一个配置类。集中管理支付配置

package com.ruoyi.config;

import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.Resource;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;

@Slf4j
@Configuration
@RequiredArgsConstructor
public class BestPayConfig {

    @Qualifier("aliPayAccountConfig")
    private final AliPayAccountConfig aliPayConfig;
    
    @Qualifier("wxAccountConfig")
    private final WxAccountConfig wxConfig;

    /**
     * 配置ObjectMapper
     */
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        // 配置序列化特性
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return mapper;
    }

    /**
     * 配置支付宝客户端
     */
    @Bean
    public AlipayClient alipayClient() {
        return new DefaultAlipayClient(
            aliPayConfig.getGatewayUrl(),
            aliPayConfig.getAppId(),
            aliPayConfig.getMerchantPrivateKey(),
            aliPayConfig.getFormat(),
            aliPayConfig.getCharset(),
            aliPayConfig.getAlipayPublicKey(),
            aliPayConfig.getSignType()
        );
    }

    /**
     * 配置微信支付客户端
     */
    @Bean
    public CloseableHttpClient wxPayClient() {
        try {
            // 加载商户私钥
            String privateKeyPath = wxConfig.getPrivateKeyPath();
            String privateKeyContent;

            // 使用 Resource 加载文件
            Resource resource = new ClassPathResource(privateKeyPath.replace("classpath:", ""));
            if (!resource.exists()) {
                log.error("微信支付证书文件不存在: {}", privateKeyPath);
                // 如果证书不存在,返回一个默认的 HttpClient
                // 这样可以让应用启动,但微信支付功能不可用
                return HttpClients.createDefault();
            }

            try (InputStream inputStream = resource.getInputStream()) {
                byte[] bytes = IOUtils.toByteArray(inputStream);
                privateKeyContent = new String(bytes, StandardCharsets.UTF_8);
                log.info("成功加载微信支付证书");
            }

            // 使用 BouncyCastle 加载私钥
            PrivateKey merchantPrivateKey = loadPrivateKeyFromPem(privateKeyContent);

            // 创建支付客户端
            return WechatPayHttpClientBuilder.create()
                .withMerchant(wxConfig.getMchId(), wxConfig.getMchSerialNo(), merchantPrivateKey)
                .withValidator(response -> true) // 暂时不验证应答签名
                .build();

        } catch (Exception e) {
            log.error("初始化微信支付客户端失败", e);
            // 返回一个默认的 HttpClient,让应用可以启动
            return HttpClients.createDefault();
        }
    }

    /**
     * 从PEM格式的字符串中加载私钥
     */
    private PrivateKey loadPrivateKeyFromPem(String pemContent) throws Exception {
        try (PEMParser pemParser = new PEMParser(new StringReader(pemContent))) {
            PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) pemParser.readObject();
            return new JcaPEMKeyConverter().getPrivateKey(privateKeyInfo);
        }
    }

    /**
     * 配置通用HTTP客户端
     */
    @Bean
    public CloseableHttpClient httpClient() {
        return HttpClients.createDefault();
    }
}