基于 Spring Boot 框架实现微信支付接口调用及回调功能

实现微信支付接口调用及回调功能,以下是完整的步骤及代码实现,基于 Spring Boot 框架。

1. 微信支付开发准备

开通微信支付

  1. 确保已在 微信商户平台上注册并完成企业认证。
  2. 获取以下重要信息:
    • 商户号(mch_id
    • 商户密钥(key
    • 微信支付分配的公众账号 ID(appid
  3. 下载商户证书(如需处理退款)。

配置 API 安全密钥

前往商户平台的【账户设置】-【API安全】中配置 API 密钥。

2. 集成依赖

在 Spring Boot 项目中添加 HTTP 客户端依赖,例如 RestTemplateOkHttp。也可用支付宝/微信专用 SDK。

pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.11.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

3. 微信支付统一下单接口调用

核心代码实现

import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.security.MessageDigest;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;

@Service
public class WeChatPayService {

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

    @Value("${wechat.pay.mch_id}")
    private String mchId;

    @Value("${wechat.pay.key}")
    private String apiKey;

    @Value("${wechat.pay.notify_url}")
    private String notifyUrl;

    private static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    public String createOrder(String orderNo, String description, int totalFee, String clientIp) throws Exception {
        SortedMap<String, String> params = new TreeMap<>();
        params.put("appid", appId);
        params.put("mch_id", mchId);
        params.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", ""));
        params.put("body", description);
        params.put("out_trade_no", orderNo);
        params.put("total_fee", String.valueOf(totalFee)); // 金额,单位分
        params.put("spbill_create_ip", clientIp); // 调用支付的设备 IP
        params.put("notify_url", notifyUrl); // 异步回调地址
        params.put("trade_type", "NATIVE"); // 交易类型,NATIVE 表示扫码支付

        // 签名
        String sign = generateSign(params);
        params.put("sign", sign);

        // 构造 XML 请求
        String requestXml = mapToXml(params);

        // 使用 OkHttp 发起请求
        OkHttpClient client = new OkHttpClient();
        RequestBody body = RequestBody.create(MediaType.parse("application/xml; charset=utf-8"), requestXml);
        Request request = new Request.Builder().url(UNIFIED_ORDER_URL).post(body).build();
        Response response = client.newCall(request).execute();

        // 解析返回结果
        String responseXml = response.body().string();
        Map<String, String> result = xmlToMap(responseXml);

        if ("SUCCESS".equals(result.get("return_code")) && "SUCCESS".equals(result.get("result_code"))) {
            return result.get("code_url"); // 返回支付二维码链接
        } else {
            throw new Exception("微信支付下单失败:" + result.get("return_msg"));
        }
    }

    private String generateSign(SortedMap<String, String> params) throws Exception {
        StringBuilder sb = new StringBuilder();
        params.forEach((key, value) -> {
            if (value != null && !"".equals(value) && !"sign".equals(key)) {
                sb.append(key).append("=").append(value).append("&");
            }
        });
        sb.append("key=").append(apiKey);
        return md5(sb.toString()).toUpperCase();
    }

    private String md5(String str) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(str.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte b : array) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    private String mapToXml(SortedMap<String, String> map) {
        StringBuilder xml = new StringBuilder("<xml>");
        map.forEach((key, value) -> xml.append("<").append(key).append(">").append(value)
                .append("</").append(key).append(">"));
        xml.append("</xml>");
        return xml.toString();
    }

    private Map<String, String> xmlToMap(String xml) throws Exception {
        // 使用 Jackson 或其他工具解析 XML 字符串为 Map
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.readValue(xml, Map.class);
    }
}

统一下单接口调用

@RestController
@RequestMapping("/api/pay")
public class PayController {

    @Autowired
    private WeChatPayService weChatPayService;

    @PostMapping("/createOrder")
    public String createOrder(@RequestParam String orderNo,
                              @RequestParam String description,
                              @RequestParam int totalFee,
                              @RequestParam String clientIp) {
        try {
            return weChatPayService.createOrder(orderNo, description, totalFee, clientIp);
        } catch (Exception e) {
            e.printStackTrace();
            return "Error: " + e.getMessage();
        }
    }
}

4. 微信支付回调处理

配置回调接口

回调地址需在统一下单时配置,示例代码如下:

import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@RestController
@RequestMapping("/api/pay")
public class PayCallbackController {

    @PostMapping("/notify")
    public String weChatNotify(HttpServletRequest request) {
        try {
            // 解析 XML 请求
            String requestBody = request.getReader().lines()
                .reduce("", (accumulator, actual) -> accumulator + actual);
            Map<String, String> resultMap = xmlToMap(requestBody);

            // 验签
            String receivedSign = resultMap.get("sign");
            resultMap.remove("sign");
            String calculatedSign = generateSign(resultMap);

            if (!calculatedSign.equals(receivedSign)) {
                return "<xml><return_code>FAIL</return_code><return_msg>签名失败</return_msg></xml>";
            }

            // 处理业务逻辑
            if ("SUCCESS".equals(resultMap.get("result_code"))) {
                String orderNo = resultMap.get("out_trade_no");
                // 更新订单状态
                System.out.println("支付成功,订单号:" + orderNo);
            }

            return "<xml><return_code>SUCCESS</return_code><return_msg>OK</return_msg></xml>";
        } catch (Exception e) {
            e.printStackTrace();
            return "<xml><return_code>FAIL</return_code><return_msg>处理失败</return_msg></xml>";
        }
    }
}

5. 注意事项

  1. 安全性:使用 HTTPS 确保传输安全。
  2. 验签:每次回调需要严格验签,防止伪造回调。
  3. 金额校验:确认支付金额与订单金额一致。
  4. 防止重复通知:回调接口需支持幂等性。

通过以上步骤,可以实现微信支付接口调用及回调处理功能。如果有更多需求(如退款或查询订单状态),可以扩展上述代码。

发布者:myrgd,转载请注明出处:https://www.object-c.cn/4432

Like (0)
Previous 2024年11月23日 下午1:49
Next 2024年11月23日 下午1:56

相关推荐

  • 微信小程序中,通过Node.js连接本地 MySQL 实现数据的增删改查

    在微信小程序中,通过Node.js连接本地 MySQL 实现数据的增删改查的过程,可以分为以下几个步骤: 1. 准备工作 必要工具: 2. 搭建 Node.js 后端服务 安装依赖 在 Node.js 项目中,使用 npm init 初始化项目,并安装以下依赖: express: 用于搭建 Web 服务。 mysql: 用于连接和操作 MySQL 数据库。 …

    2024年12月9日
    00
  • 实现“鼠标点击器外挂”,模拟快速点击或者自动化点击的工具

    实现一个“鼠标点击器外挂”,通常是指模拟快速点击或者自动化点击的工具。以下是一个实现的基本思路和代码示例,适合用于演示或学习目的。 功能描述自动模拟鼠标点击(单击或双击)。用户可以设置点击间隔和总点击次数。提供启动和停止按钮,方便控制。 示例实现HTML CSS (style.css) JavaScript (script.js) 功能实现说明用户输入:用户…

    2024年11月30日
    00
  • 解决登录Google账号,手机上Google账号无法验证问题?

    遇到 Google账号无法验证 的问题时,通常是因为以下几个原因之一: 以下是一些解决方法: 1. 检查手机网络和信号确保手机信号和网络连接稳定,有时验证码可能由于网络问题无法及时收到。如果你使用的是 短信验证,确保手机信号良好,短信接收正常。如果使用的是 Google 提供的 Google Authenticator 应用,确保应用正常工作,并且时间同步(…

    2024年11月28日
    00
  • 在 Vue3 和 uniapp 的 H5 环境中使用 QRCode.toDataURL() 生成二维码时无法显示的问题

    1. 图片格式或生成的 DataURL 处理问题QRCode.toDataURL() 方法通常会生成一个 Base64 编码的 PNG 图片。某些 Android 浏览器或 WebView 可能对 Base64 编码的图片支持不好,或者由于资源限制无法正常解析。解决方法:尝试通过生成二维码的图片 URL 直接展示而非使用 Base64 编码。可以通过将二维码…

    2024年11月27日
    00
  • 在开发 Angular 项目时,使用 RangeSlider 控件时的常见问题

    在开发 Angular 项目时,使用 RangeSlider 控件时,可能会遇到一些常见的问题。以下是一些问题及其解决方案:1. 滑块值不更新问题描述:当用户拖动滑块时,滑块的值不会实时更新,或者显示的值不正确。解决方案:确保绑定的模型是双向绑定,使用 ngModel 或者监听 input 事件来确保滑块值能实时更新。例如: 确保你已经导入了 FormsMo…

    2024年11月27日
    00
  • Web实时通信和 @microsoft/signalr 微软开发的一款基于 SignalR 的实时通信库

    Web实时通信和 @microsoft/signalr@microsoft/signalr 是微软开发的一款基于 SignalR 的实时通信库,专为 Web 应用提供强大的实时通信功能。SignalR 的主要特点包括支持双向通信、自动选择传输协议(WebSockets、Server-Sent Events 或 Long Polling)以及简化的服务器与客户…

    2024年12月1日
    00
  • 把一个矩形div变成一个直角三角形

    要将一个矩形 div 变成一个直角三角形,可以通过 CSS 的一些属性进行实现。具体的方法是利用 border 属性来隐藏矩形的部分,留下一个直角三角形的形状。以下是实现的步骤:CSS 方法: 解释:width 和 height 设置为 0:通过将 div 的宽度和高度设置为 0,实际上把矩形的主体部分隐藏了。border-left 和 border-bot…

    2024年11月27日
    00
  • 在 Nuxt.js 应用中,webpack 的 compile 事件钩子构建过程

    在 Nuxt.js 应用中,webpack 的 compile 事件钩子通常用于在构建过程中处理或监听 Webpack 编译的状态。webpack 是 Nuxt.js 中的核心构建工具之一,而 Nuxt.js 本身是基于 Webpack 配置的,允许你通过扩展 Webpack 配置来进行自定义。要使用 webpack 的 compile 事件钩子,首先你需要…

    2024年11月29日
    00
  • 使用 Webpack 5 优化构建减少生成文件的体积提升前端性能

    在使用 Webpack 5 时,优化构建以减少生成文件的体积是提升前端性能的重要一步。以下是一些常见的优化方法和策略: 1. 开启生产模式 确保构建时使用生产模式,Webpack 会自动应用多种优化(如代码压缩、Tree Shaking 等): 或在配置文件中明确设置: 2. 启用 Tree Shaking Tree Shaking 是 Webpack 内置…

    2024年12月3日
    00
  • 在 Jupyter Notebook 中使用 Markdown 的相关技巧

    在 Jupyter Notebook 中使用 Markdown 是一种常见的方式来撰写文档和注释,增强数据分析和报告的可读性。Markdown 在 Jupyter Notebook 中不仅支持标准的文本格式化功能,还提供了许多扩展功能,比如数学公式、表格、代码块等。以下是一些常用的 Markdown 技巧,可以帮助你提升 Jupyter Notebook 中…

    2024年11月27日
    00
  • 微信小程序使用 map 组件实现拖动地图并获取当前地图中心的经纬度

    在微信小程序中,使用 map 组件可以轻松实现拖动地图并获取当前地图中心的经纬度。以下是实现步骤和代码示例: 实现思路 代码实现 1. 页面 WXML 添加 map 组件并设置属性。 2. 页面 WXSS 定义地图样式和中心点标记样式。 3. 页面 JS 初始化地图中心点的经纬度,并监听地图拖动。 4. 中心点图标 将一个中心标记图标放置在地图中间。可以在小…

    2024年11月25日
    00
  • 在 Ant Design ProTable 中,如何设置不分页,依然显示分页信息,前端分页不触发

    在 Ant Design ProTable 中,默认情况下,分页是与数据请求(request)相关联的。也就是说,每当分页切换时,request 会被触发,重新请求新的数据。如果你希望在禁用分页的同时,依然显示分页控件,并且不触发 request 请求,可以通过以下方法进行配置。解决方案要在 Ant Design ProTable 中禁用分页的同时保留分页信…

    2024年11月29日
    00
  • 微信小程序RequestTask.onChunkReceived 监听分块数据接收事件接口

    在微信小程序中,RequestTask.onChunkReceived 是用于监听分块数据接收事件的接口。此功能适用于需要逐步接收大数据(例如流媒体、文件分块等)的场景,可以显著提升大文件传输的性能和用户体验。 以下是详细介绍和使用方法: 1. 功能说明用途:监听通过 wx.request 或 wx.downloadFile 发起的请求过程中,每次接收到的数…

    2024年11月28日
    00
  • 修复 Elementor 网站上出现的 HTTPS 400 错误请求(服务器错误)

    在修复 Elementor 网站上出现的 HTTPS 400 错误请求(服务器错误)时,您需要采取以下步骤来排查和解决问题。这类错误通常与服务器配置、插件冲突或 HTTPS 配置相关。 1. 检查 HTTPS 配置 2. 排查插件冲突 3. 检查主题兼容性 4. 调整服务器配置 URL 重写规则: 5. 清理缓存 6. 检查网络请求 7. 联系主机服务商 如…

    2024年12月9日
    00
  • 如何使用uni-app-qrcode插件生成二维码?

    以下是使用uni-app-qrcode插件生成二维码的详细步骤: 安装插件 或者使用yarn进行安装: 引入插件 使用插件生成二维码 上述代码中,首先定义了要编码到二维码中的内容content和生成二维码的配置选项options,然后通过this.$refs.qrcodeCanvas获取页面中的canvas元素,最后调用QRCode.make方法生成二维码。…

    2024年12月22日
    00

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

在线咨询: QQ交谈

邮件:723923060@qq.com

关注微信