找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1888

积分

0

好友

255

主题
发表于 2025-12-25 03:20:34 | 查看: 32| 回复: 0

微信支付作为小程序生态中最核心的闭环能力,其后台实现是开发者必须掌握的关键技能。本文将基于SpringBoot框架,详细介绍如何集成微信支付APIv3,完成从配置、下单到签名的完整后端支付流程。

项目结构与核心依赖

首先,我们来看项目的整体目录结构,这有助于理解代码的组织方式:

项目目录结构

项目采用Maven构建,核心依赖如下,主要引入了SpringBoot Web、微信支付官方HTTP客户端以及一些常用的工具库:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!--微信支付SDK-->
    <dependency>
        <groupId>com.github.wechatpay-apiv3</groupId>
        <artifactId>wechatpay-apache-httpclient</artifactId>
        <version>0.3.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.83</version>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.3</version>
    </dependency>
</dependencies>

微信支付配置类

接下来是关键的配置类,它负责加载商户证书、API密钥并初始化微信支付所需的HTTP客户端与验签器。良好的配置管理与依赖注入是构建健壮后端服务的基础。

package com.smart.wechatpay.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.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;

@Slf4j
@Data
@ConfigurationProperties(prefix = "com.smart.wechatpay")
@Component
public class WxPayConfig {
    @Value("${com.smart.wechatpay.mchId}")
    private String mchId;
    @Value("${com.smart.wechatpay.mchSerialNo}")
    private String mchSerialNo;
    @Value("${com.smart.wechatpay.privateKeyPath}")
    private String privateKeyPath;
    @Value("${com.smart.wechatpay.apiV3Key}")
    private String apiV3Key;
    @Value("${com.smart.wechatpay.appid}")
    private String appid;
    @Value("${com.smart.wechatpay.domain}")
    private String domain;
    @Value("${com.smart.wechatpay.notifyUrl}")
    private String notifyUrl;

    // 加载商户私钥
    public PrivateKey getPrivateKey(String filename){
        try {
            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(privateKeyPath);
            assert inputStream != null;
            return PemUtil.loadPrivateKey(inputStream);
        } catch (Exception e) {
            throw new RuntimeException("私钥文件不存在", e);
        }
    }

    // 初始化证书验签器
    @Bean("verifier")
    public ScheduledUpdateCertificatesVerifier getVerifier(){
        PrivateKey privateKey = getPrivateKey(privateKeyPath);
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
        return new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                apiV3Key.getBytes(StandardCharsets.UTF_8));
    }

    // 初始化微信支付专用HTTP Client
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){
        PrivateKey privateKey = getPrivateKey(privateKeyPath);
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        return builder.build();
    }
}

控制器层

支付请求的入口由Controller提供,它接收前端传入的支付参数。

package com.smart.wechatpay.controller;

import com.smart.wechatpay.response.Response;
import com.smart.wechatpay.service.PayOrderService;
import com.smart.wechatpay.vo.PayFrontReqVo;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
public class PayController {
    private final PayOrderService payOrderService;

    @PostMapping("/pay")
    public Response<?> pay(@RequestBody PayFrontReqVo reqDTO) {
        try {
            return payOrderService.pay(reqDTO);
        } catch (Exception e) {
            return Response.buildFailed(e.getMessage());
        }
    }
}

业务逻辑层

Service层负责校验订单状态,并组装数据调用微信支付服务。

package com.smart.wechatpay.service.impl;

import com.smart.wechatpay.config.WxPayConfig;
import com.smart.wechatpay.enums.PayStatusEnum;
import com.smart.wechatpay.response.Response;
import com.smart.wechatpay.service.PayOrderService;
import com.smart.wechatpay.service.WxPayService;
import com.smart.wechatpay.vo.PayFrontReqVo;
import com.smart.wechatpay.vo.PayOrderVo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;

@Service
@RequiredArgsConstructor
public class PayOrderServiceImpl implements PayOrderService {
    private final WxPayService wxPayService;
    private final WxPayConfig wxPayConfig;

    @Override
    public Response<?> pay(PayFrontReqVo reqDTO) throws Exception {
        if (StringUtils.isEmpty(reqDTO.getOpenId())) {
            return Response.buildFailed("微信登录号不能为空");
        }
        // 模拟查询数据库订单
        PayOrderVo order = this.findOrderDetailByTradeNo(reqDTO.getTradeNo());
        if (!PayStatusEnum.PAY_ING.getCode().equals(order.getStatus())) {
            return Response.buildFailed("支付订单无效不能支付");
        }
        // 调用微信支付服务
        return Response.buildSuccessed(wxPayService.wxMinPay(order,
                wxPayConfig.getWxPayClient(wxPayConfig.getVerifier()),
                wxPayConfig));
    }

    // 模拟从数据库查询订单
    private PayOrderVo findOrderDetailByTradeNo(String tradeNo) {
        return PayOrderVo.builder()
                .orderNo(RandomUtil.randomNumbers(9))
                .tradeNo(RandomUtil.randomNumbers(12))
                .title("测试支付")
                .totalAmount(new BigDecimal("0.01"))
                .status(PayStatusEnum.PAY_ING.getCode())
                .openId("oA_zr44MfgEzyM9yZX222Vbha00")
                .build();
    }
}

微信支付服务层

这是最核心的部分,负责与微信支付APIv3直接交互,构造请求参数、调用下单接口并生成小程序端调起支付所需的签名。

/**
 * 微信小程序支付
 */
@Override
public Map<String, Object> wxMinPay(PayOrderVo order, CloseableHttpClient wxPayClient, WxPayConfig config) throws Exception {
    Map<String, Object> resultMap = new HashMap<>();
    // 1. 组装请求参数
    Map<String, Object> wxRequestParam = MapUtil.ofEntries(
            entry("out_trade_no", order.getTradeNo()),
            entry("mchid", config.getMchId()),
            entry("appid", config.getAppid()),
            entry("notify_url", config.getNotifyUrl()),
            entry("attach", order.getOrderNo()),
            entry("description", "测试支付"),
            entry("payer", MapUtil.of("openid", order.getOpenId()))
    );
    // 设置金额参数(单位:分)
    wxRequestParam.put("amount", MapUtil.ofEntries(
            entry("total", order.getTotalAmount().multiply(new BigDecimal(100)).intValue()),
            entry("currency", "CNY")
    ));

    String jsonParams = gson.toJson(wxRequestParam);
    HttpPost httpPost = new HttpPost(config.getDomain().concat(WxApiType.NATIVE_JSAPI.getType()));
    StringEntity entity = new StringEntity(jsonParams, "utf-8");
    entity.setContentType("application/json");
    httpPost.setEntity(entity);
    httpPost.setHeader("Accept", "application/json");

    // 2. 执行HTTP请求,调用微信支付下单API
    try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
        String bodyAsString = EntityUtils.toString(response.getEntity());
        int statusCode = response.getStatusLine().getStatusCode();
        if (!(statusCode == SUCCESS_RESULT_CODE_200.getCode() || statusCode == SUCCESS_RESULT_CODE_204.getCode())) {
            throw new BusinessException(bodyAsString, String.valueOf(statusCode));
        }

        // 3. 解析响应,生成小程序调起支付所需参数
        HashMap hashMap = gson.fromJson(bodyAsString, HashMap.class);
        String time = String.valueOf(System.currentTimeMillis() / 1000);
        String randomAlphabetic = RandomStringUtils.randomAlphabetic(32);

        resultMap.put("timeStamp", time);
        resultMap.put("nonceStr", randomAlphabetic);
        resultMap.put("packag", "prepay_id=" + hashMap.get("prepay_id"));
        resultMap.put("signType", "HMAC-SHA256");
        // 生成支付签名
        String message = buildMessage(config.getAppid(), Long.valueOf(time), randomAlphabetic, "prepay_id=" + hashMap.get("prepay_id"));
        resultMap.put("paySign", sign(message.getBytes("utf-8")));
    }
    return resultMap;
}

// 构造签名字符串
private String buildMessage(String appid, long timestamp, String nonceStr, String prepay_id) {
    return appid + "\n"
            + timestamp + "\n"
            + nonceStr + "\n"
            + prepay_id + "\n";
}

// 使用商户私钥进行SHA256 with RSA签名
private String sign(byte[] message) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
    Signature sign = Signature.getInstance("SHA256withRSA");
    PrivateKey merchantPrivateKey = wxPayConfig.getPrivateKey(null);
    sign.initSign(merchantPrivateKey);
    sign.update(message);
    return Base64.getEncoder().encodeToString(sign.sign());
}

配置与测试

需要在application.yml中配置商户信息,具体路径和密钥需根据实际情况填写:

配置文件示例

使用Postman测试支付接口,请求Body需要包含交易单号和用户OpenID:

Postman请求示例

接口成功调用后,将返回小程序调起支付所需的五个核心参数:

{
    "code": "000",
    "data": {
        "timeStamp": "1763220706",
        "paySign": "WEE3P9OoOUmiFGFwWSV0ohVmhfETPltNLr1PBYGG5T1Wk6pJG+OGvlrEI+aUxlX154TC2W6P/0lhhGMtaE/xhhmjGcO7jB0feWeyBtbfh5C+39khM1UVQtaATMO3H8WeF2LXveX2i/yaiYMa/lrG2UL/gKGRW4ynWExCPqEynPlRI35BNFx6LTaPKuarUhaRouaiPCILENiyvqIoFZ65dBO+IT2FktFgW5Lduw14pXpox/YkP4ZPxbWejTuhXvW0S/PUm7/khoohKhOb9NU/f0pCpowRLhSxrcKtnEwUrRwyJMtAINeQxQn+I3W+Ujwyzyw==",
        "signType": "HMAC-SHA256",
        "packag": "prepay_id=wx1532333033562601399dc24655612e40001",
        "nonceStr": "yzsgDIPqKSXFVynZweYYiuFGafMXjvzlT"
    }
}

小程序端调起支付

最后,将后端返回的五个参数填入微信小程序的wx.requestPayment接口,即可唤起支付界面。

小程序调起支付参数

通过以上步骤,一个完整的、安全的微信小程序支付后端就搭建完成了。重点在于理解APIv3的签名验证流程、证书的管理以及如何生成符合小程序端要求的支付参数。




上一篇:Kite:现代化Kubernetes多集群管理面板,可视化运维与监控实战
下一篇:百度百舸开源上下文并行方案:实现DeepSeek-V3.2长序列秒级推理
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 11:55 , Processed in 0.292918 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表