Skip to content

微信支付核心功能

1. 配置

yaml
wechat:
  program:
    # 小程序AppID - 在微信公众平台(https://mp.weixin.qq.com)注册小程序后获取
    appid: YOUR_APPID
    # 商户号 - 在微信支付商户平台(https://pay.weixin.qq.com)入驻后获取
    mchId: YOUR_MCH_ID
    # API证书序列号 - 在微信支付商户平台下载API证书后,使用证书工具查看序列号
    certSerialNo: YOUR_CERT_SERIAL_NO
    # API证书私钥路径 - 商户平台下载的apiclient_key.pem 文件绝对路径
    privateKeyPath: /path/to/apiclient_key.pem
    # API证书公钥路径 - 从商户平台下载的微信平台证书,用于验签
    publicKeyPath: /path/to/pub_key.pem
    # APIv3密钥 - 商户平台API安全中设置的32位随机密钥
    apiV3Key: YOUR_API_V3_KEY

2. 初始化微信支付服务

java
@Service
public class WxPayServiceConfig {

    @Value("${wechat.program.appid:}")
    private String appId;

    @Value("${wechat.program.mchid:}")
    private String mchId;

    @Value("${wechat.program.certSerialNo:}")
    private String certSerialNo;

    @Value("${wechat.program.privateKeyPath:}")
    private String privateKeyPath;

    @Value("${wechat.program.apiV3Key:}")
    private String apiV3Key;

    private WxPayService wxPayService;

    @PostConstruct
    public void init() {
        WxPayConfig config = new WxPayConfig();
        config.setAppId(appId);
        config.setMchId(mchId);
        config.setCertSerialNo(certSerialNo);
        config.setPrivateKeyPath(privateKeyPath);
        config.setApiV3Key(apiV3Key);

        wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(config);
    }

    public WxPayService getWxPayService() {
        return wxPayService;
    }
}

3. JSAPI 统一下单

java
@Service
@RequiredArgsConstructor
public class PayService {

    private final WxPayService wxPayService;
    private final SnowflakeIdGenerator idGenerator;

    /**
     * 创建微信支付订单
     *
     * @param amount  订单金额(元)
     * @param openid  用户openid
     * @param notifyUrl 回调地址
     * @return 前端唤起支付所需参数
     */
    public Map<String, String> createWxPay(BigDecimal amount, String openid, String notifyUrl) {
        // 1. 生成商户订单号
        String outTradeNo = idGenerator.nextStringId();

        // 2. 构建请求参数
        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
        request.setAppid(appId);
        request.setMchid(mchId);
        request.setOutTradeNo(outTradeNo);
        request.setDescription("订单支付");
        request.setNotifyUrl(notifyUrl);
        // 金额转换为分
        request.setAmount(new WxPayUnifiedOrderV3Request.Amount()
                .setTotal(amount.multiply(new BigDecimal(100)).intValue()));
        request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(openid));

        try {
            // 3. 调用统一下单接口
            WxPayUnifiedOrderV3Result.JsapiResult result =
                    wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);

            // 4. 返回前端所需参数
            Map<String, String> payParams = new HashMap<>();
            payParams.put("outTradeNo", outTradeNo);
            payParams.put("timeStamp", result.getTimeStamp());
            payParams.put("nonceStr", result.getNonceStr());
            payParams.put("package", result.getPackageValue());
            payParams.put("signType", result.getSignType());
            payParams.put("paySign", result.getPaySign());
            return payParams;
        } catch (WxPayException e) {
            throw new RuntimeException("创建支付订单失败: " + e.getMessage(), e);
        }
    }
}

4. 前端唤起支付

javascript
function onWxPay(payParams) {
    return new Promise((resolve, reject) => {
        WeChatJSBridge.call('chooseWXPay', {
            timestamp: payParams.timeStamp,
            nonceStr: payParams.nonceStr,
            package: payParams.package,
            signType: payParams.signType,
            paySign: payParams.paySign,
            success: resolve,
            fail: reject
        });
    });
}

// 或使用 wx.config + wx.chooseWXPay

5. 支付回调处理

java
@PostMapping("/notify/pay")
public ResponseEntity<String> parseNotifyResult(@RequestBody String notifyData,
                                                 HttpServletRequest request) {
    try {
        // 1. 获取请求头签名信息
        SignatureHeader header = new SignatureHeader();
        header.setSignature(request.getHeader("Wechatpay-Signature"));
        header.setNonce(request.getHeader("Wechatpay-Nonce"));
        header.setSerial(request.getHeader("Wechatpay-Serial"));
        header.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));

        // 2. 解析并验证回调通知
        WxPayNotifyV3Result result = wxPayService.parseOrderNotifyV3Result(notifyData, header);
        WxPayNotifyV3Result.DecryptNotifyResult data = result.getResult();

        // 3. 获取支付结果
        String outTradeNo = data.getOutTradeNo();
        String transactionId = data.getTransactionId();
        String tradeState = data.getTradeState();

        // 4. 更新本地订单状态
        if ("SUCCESS".equals(tradeState)) {
            orderService.updatePaymentStatus(outTradeNo, "PAID");
            paymentTransactionService.updateWxTransactionNo(outTradeNo, transactionId);
        }

        // 5. 成功返回200
        return ResponseEntity.status(200).body("");
    } catch (WxPayException e) {
        return ResponseEntity.status(500).body("fail");
    }
}

6. 申请退款

java
@Service
@RequiredArgsConstructor
public class RefundService {

    private final WxPayService wxPayService;

    /**
     * 申请微信支付退款
     *
     * @param outTradeNo   原商户订单号
     * @param outRefundNo  商户退款单号
     * @param totalFee     订单总金额(分)
     * @param refundFee    退款金额(分)
     * @param refundReason 退款原因
     */
    public void wxRefund(String outTradeNo, String outRefundNo,
                         Integer totalFee, Integer refundFee,
                         String refundReason) {
        WxPayRefundV3Request request = new WxPayRefundV3Request();
        request.setOutTradeNo(outTradeNo);
        request.setOutRefundNo(outRefundNo);
        request.setReason(refundReason);
        request.setNotifyUrl("/notify/refund");

        WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
        amount.setTotal(totalFee);
        amount.setRefund(refundFee);
        amount.setCurrency("CNY");
        request.setAmount(amount);

        try {
            WxPayRefundV3Result result = wxPayService.refundV3(request);
            log.info("退款成功,退款单号: {}", result.getRefundId());
        } catch (WxPayException e) {
            throw new RuntimeException("退款失败: " + e.getMessage(), e);
        }
    }
}

7. 退款回调处理

java
@PostMapping("/notify/refund")
public ResponseEntity<String> parseRefundNotifyResult(@RequestBody String notifyData,
                                                       HttpServletRequest request) {
    try {
        SignatureHeader header = new SignatureHeader();
        header.setSignature(request.getHeader("Wechatpay-Signature"));
        header.setNonce(request.getHeader("Wechatpay-Nonce"));
        header.setSerial(request.getHeader("Wechatpay-Serial"));
        header.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));

        // 解析退款回调
        WxPayRefundNotifyV3Result result = wxPayService.parseRefundNotifyV3Result(notifyData, header);
        WxPayRefundNotifyV3Result.DecryptNotifyResult data = result.getResult();

        String outRefundNo = data.getOutRefundNo();
        String refundStatus = data.getRefundStatus();
        String refundId = data.getRefundId();

        // 更新退款状态
        if ("SUCCESS".equals(refundStatus)) {
            refundService.updateRefundStatus(outRefundNo, "REFUND_SUCCESS", refundId);
        } else if ("CLOSE".equals(refundStatus)) {
            refundService.updateRefundStatus(outRefundNo, "REFUND_CLOSE", refundId);
        }

        return ResponseEntity.status(200).body("");
    } catch (WxPayException e) {
        return ResponseEntity.status(500).body("fail");
    }
}

8. 查询订单状态

java
public String queryOrderStatus(String outTradeNo) {
    WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request();
    request.setOutTradeNo(outTradeNo);
    request.setMchid(mchId);

    try {
        WxPayOrderQueryV3Result result = wxPayService.queryOrderV3(request);
        return result.getTradeState().name();
    } catch (WxPayException e) {
        throw new RuntimeException("查询订单失败: " + e.getMessage(), e);
    }
}

9. 手续费计算

java
public class WechatPayFeeUtils {

    private static final BigDecimal FEE_RATE = new BigDecimal("0.006");
    private static final BigDecimal MIN_CHARGE = new BigDecimal("0.01");

    /**
     * 计算手续费(元)
     */
    public static BigDecimal calculateFee(BigDecimal amount) {
        BigDecimal fee = amount.multiply(FEE_RATE)
                .setScale(2, RoundingMode.HALF_UP);
        return fee.compareTo(MIN_CHARGE) < 0 ? BigDecimal.ZERO : fee;
    }

    /**
     * 计算手续费(分)
     */
    public static int calculateFeeFen(int amountInFen) {
        BigDecimal amount = new BigDecimal(amountInFen).divide(new BigDecimal(100));
        BigDecimal fee = calculateFee(amount);
        return fee.multiply(new BigDecimal(100)).intValue();
    }
}

10. 支付流水表设计

sql
CREATE TABLE payment_transaction (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    transaction_no VARCHAR(64) COMMENT '商户交易流水号',
    wx_transaction_no VARCHAR(64) COMMENT '微信交易单号',
    business_type INT COMMENT '业务类型: 1-订单 2-退款',
    business_no BIGINT COMMENT '业务编号',
    amount INT COMMENT '交易金额(分)',
    fee INT COMMENT '手续费(分)',
    direction INT COMMENT '1-支出 2-收入',
    pay_status VARCHAR(20) COMMENT '支付状态',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME ON UPDATE CURRENT_TIMESTAMP
);

流程图

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  用户下单    │ -> │  统一下单    │ -> │ 前端唤起支付 │
└─────────────┘    └─────────────┘    └─────────────┘

                                               v
┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  更新订单    │ <- │  支付回调   │ <- │   微信通知   │
│    状态      │    └─────────────┘    └─────────────┘
└─────────────┘

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  申请退款    │ -> │  微信退款   │ -> │  退款回调    │
└─────────────┘    └─────────────┘    └─────────────┘

                                               v
┌─────────────┐    ┌─────────────┐
│  更新退款    │ <- │  处理结果   │
│    状态      │    └─────────────┘
└─────────────┘

关键依赖

xml
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-pay</artifactId>
    <version>4.5.0</version>
</dependency>