SpringBoot + 微信支付 --- 内网穿透ngrok(安装、使用) 及 支付通知-->接收支付通知和返回应答

news2024/11/26 12:36:50

目录

  • Native 下单
    • 1、内网穿透 ngrok
      • 1-1:注册下载
      • 2-2:使用方式
      • 3-3:测试
    • 2、支付通知--接收支付通知和返回应答
      • 完整需求介绍:
      • 2-1、需求1:应答测试
      • 2-2、应答的代码:
      • 2-3、结果:
        • 测试:应答不符合规范
        • 测试:应答出错
        • 测试:应答超时
      • 2-4、需求2:验签
        • 代码:
          • WxPayController
          • WechatPay2ValidatorForRequest
      • 2-5、测试:
      • 2-6、总结支付通知的流程:
      • 2-7、完整代码:
        • WxPayController
        • WechatPay2ValidatorForRequest

Native 下单

1、内网穿透 ngrok

1-1:注册下载

在这里插入图片描述

下载
在这里插入图片描述

2-2:使用方式

直接在该目录cmd打开

在这里插入图片描述

第一次时候这个ngrok时,需要为计算机做授权

授权命令:
ngrok config add-authtoken 2XmL8EfYQe6uVAjM9Iami0pWogd_5ztKmSxHs6UeAQn9RQBFt

后面关闭ngrok再打开,就不需要再授权了,直接 ngrok http 8090 创建个隧道就可以了
在这里插入图片描述

在这里插入图片描述
这个免费的隧道,是有时效性的,当重启ngrok的时候,或长时间使用后,该隧道会改变的,需要记得修改

在这里插入图片描述

3-3:测试

之前写的一个方法,url是 http://localhost:8090/api/test
在这里插入图片描述

成功
在这里插入图片描述

2、支付通知–接收支付通知和返回应答

完整需求介绍:

就是当用户扫描二维码并支付之后,微信支付那边就会返回一个【支付通知】,来告诉商户他的支付结果。商户收到这个支付通知后,会进行一些签名的验签和订单的处理操作之类的,然后再响应回给微信系统,向微信支付系统应答我们对这个支付通知的处理情况。

(应答包括:接收支付通知成功(响应码:200或204)和接收失败(响应码:4xx或5xx))

正常就是告诉微信它发来的支付通知,我们这边已经收到了,并且通过支付通知的数据,处理完自己的核心业务了,并给微信支付平台一个成功的应答。

现在就是做图片中的这步:

微信平台异步通知商户支付结果,商户端告知支付通知的接收情况。

在这里插入图片描述

在这里插入图片描述

2-1、需求1:应答测试

这里是接收微信支付系统发来的商户支付后的支付通知,这边先不做验签和订单处理,先简单的写几个应答情况给微信支付平台看应答效果

应答情况分为:

接收支付通知成功则返回响应码200或204

接受支付通知失败则返回响应码5xx或4xx

支付通知:

在这里插入图片描述

2-2、应答的代码:

这里是接收微信支付系统发来的商户支付后的支付通知,这边先不做验签和订单处理,先简单的测试应答情况给微信支付平台

在这里插入图片描述

WxPayController 的这个 nativeNotify() 方法是让微信支付平台来调用的,不是我们去调用的

路径的来源如图:之前调用微信平台的下单接口时,发送过去的我们定义的一个回调支付通知地址。

当我们支付成功后哦,微信平台就会通过这个地址,把支付情况发送给我们。

在这里插入图片描述

2-3、结果:

如图,发来的数据是加密的,后续需要对微信发来的结果进行验签,验签成功再用对称加密的密钥对数据进行解密。

在这里插入图片描述

测试:应答不符合规范

支付通知:

在这里插入图片描述
当响应给微信支付系统的响应码是错误的201时,属于应答不符合规范。

微信认为通知失败,因为我们要么没收到通知,要么处理通知失败,所以微信会通过一定的策略定期重新发起通知

在这里插入图片描述

测试:应答出错

在这里插入图片描述

测试:应答超时

回调处理逻辑注意事项
在这里插入图片描述

让线程睡眠5秒,然后微信支付那边会因为超时没有收到商户的应答,而认为商户这边没有收到通知或通知处理失败,就会继续按规则重复发送通知

在这里插入图片描述

2-4、需求2:验签

APIv3证书与密钥使用说明:

自定义一个针对微信平台发来的request类型的支付通知的签名,进行验签操作的工具类。

因为微信支付端并没有给我们提供默认的集成在SDK内部的不用我们自己编写的签名验证,所以我们要自己编写一个【针对微信发来的请求的一个签名验证】。

就是做图片中的这一步。
在这里插入图片描述

先看一个jar包里面的针对响应的一个签名验证工具类,参考SDK源码中的 WechatPay2Validator 创建通知验签工具类 WechatPay2ValidatorForRequest

我们要弄一个针对微信支付系统发来的请求来进行验证的工具类,就可以模仿这个工具类

在这里插入图片描述

构造验签名串:

在这里插入图片描述

代码:
WxPayController

在这里插入图片描述

在这里插入图片描述

WechatPay2ValidatorForRequest

自定义验签工具类
验签工具类–验的是微信支付平台发给商户端的支付通知,
属于request请求

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2-5、测试:

在这里插入图片描述

2-6、总结支付通知的流程:

流程:

1、商户端向微信平台发起支付请求。

2、微信平台则对该请求进行响应,响应的内容是一个订单编号和支付二维码的 URL 。

3、商户端这边接收到支付二维码的URL,通过这个URL获取支付二维码图片,并进行支付。

4、商户端进行支付之后,微信平台会自动发起一个请求,请求的内容是商户端支付的结果,就是支付通知。

5、商户端这边对该支付通知进行验签,得到该请求的数据并进行一些业务操作,然后给微信支付平台应答,告诉其自己已经成功收到该通知了。

应答的内容也包括:接收通知成功和接收通知失败两种。

6、如果给微信支付平台的应答是接受通知失败的状态码。那么微信支付平台就会根据规则,在一段事件内重复发送支付通知给客户端。当然,这个重复发送通知的作用,只是为了提高商户端那边能成功接收到通知的概率(比如商户端接收通知失败的原因可以有网络原因),但是不保证一定成功。

在这里插入图片描述

2-7、完整代码:

WxPayController
@CrossOrigin //跨域
@RestController
@RequestMapping("/api/wx-pay")
@Api(tags = "网站微信支付API") //swagger 注解
@Slf4j
public class WxPayController
{
    @Resource
    private WxPayService wxPayService;

    //获取签名验证器需要的类
    @Resource
    private Verifier verifier;

    //调用统一下单API,生成支付二维码的链接和订单号
    //swagger注解
    @ApiOperation("调用统一下单API,生成支付二维码")
    @PostMapping("/native/{productId}")
    public R nativePay(@PathVariable Long productId) throws Exception
    {
        log.info("发起支付请求");
        //返回支付二维码的链接和订单号
        Map<String, Object> map = wxPayService.nativePay(productId);
        return R.ok().setData(map);
    }

    /*
     *  接收并处理完微信发来的通知后,需要给微信支付系统应答我们的处理情况
     *  告诉微信它发来的支付成功的通知,我们这边已经收到了,并且处理完自己的核心业务了,现在可以给微信一个成功的应答了。
     */
    //当我们支付后,微信支付系统会通过我们之前给的notify_url路径,把支付的结果响应回来
    //通知接口(回调通知)---接收微信服务器给我们发来的请求
    //request: 接收微信支付端发来的支付通知的请求的参数数据 , response: 响应回给微信支付端的应答
    @PostMapping("/native/notify")
    public String nativeNotify(HttpServletRequest request, HttpServletResponse response)
    {
        Gson gson = new Gson();
        //创建一个应答对象
        HashMap<String, String> map = new HashMap<>();
        try
        {
            //处理微信支付系统响应回来的通知参数
            String body = HttpUtils.readData(request);

            //gson.fromJson()是Gson库提供的一个方法,用于将 JSON 字符串转换为 Java 对象
            Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
            //微信支付端发来的支付通知的请求的id
            String requestId = (String) bodyMap.get("id");

            log.info("支付通知的id ====> {}", bodyMap.get("id"));

            //log.info("支付通知的完整数据 ====> {}", body);
            //todo:签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest =
                    new WechatPay2ValidatorForRequest(verifier, requestId,body);
            //判断验签是否成功
            if (!wechatPay2ValidatorForRequest.validate(request))
            {
                log.error("通知验签失败");
                //如果验签不通过,就给个失败的应答
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "微信支付系统发给商户的通知,验签失败");
                return gson.toJson(map);
            }
            log.info("签名验证成功");

            //todo :处理订单
            /*
             * 通知应答
             * 接收成功: HTTP应答状态码需返回200或204,无需返回应答报文。
             * 接收失败: HTTP应答状态码需返回5XX或4XX,同时需返回应答报文,格式如下
             */
            //Thread.sleep(10000); //单位:毫秒, 1000毫秒=1秒,这里是睡眠10秒钟
            //TimeUnit.SECONDS.sleep(6); //让线程睡眠超过5秒
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "成功");
            return gson.toJson(map);
        } catch (Exception e)
        {
            e.printStackTrace();
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "失败");
            return gson.toJson(map);
        }
    }


}
WechatPay2ValidatorForRequest

验签工具类–验的是微信支付平台发给商户端的支付通知,
属于request请求

package cn.ljh.paymentdemo.util;

import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;

import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;

//这个类用来验证微信支付系统发给商户的支付通知的签名,就是验签
public class WechatPay2ValidatorForRequest
{
    protected static final Logger log = LoggerFactory.getLogger(WechatPay2ValidatorForRequest.class);
    /**
     * 应答超时时间,单位为分钟
     */
    protected static final long RESPONSE_EXPIRED_MINUTES = 5;
    protected final Verifier verifier;
    protected final String requestId;
    protected final String body;

    //参数1:获取签名验证器  参数2:微信支付系统发来的那个请求通知的id    参数3:微信支付系统响应回来的通知参数
    public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body)
    {
        this.verifier = verifier;
        this.requestId = requestId;
        this.body = body;
    }

    protected static IllegalArgumentException parameterError(String message, Object... args)
    {
        message = String.format(message, args);
        return new IllegalArgumentException("parameter error: " + message);
    }

    protected static IllegalArgumentException verifyFail(String message, Object... args)
    {
        message = String.format(message, args);
        return new IllegalArgumentException("signature verify fail: " + message);
    }

    //验签方法
    public final boolean validate(HttpServletRequest request) throws IOException
    {
        try
        {
            //处理请求参数
            validateParameters(request);

            //调用构造验签名串方法
            String message = buildMessage(request);
            //从请求头当中拿到平台证书序列号
            String serial = request.getHeader(WECHAT_PAY_SERIAL);
            //从请求头当中拿到请求当中携带的签名
            String signature = request.getHeader(WECHAT_PAY_SIGNATURE);

            //利用verifier这个对象,进行验签的具体操作
            if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature))
            {
                //如果验签失败,则抛异常
                throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
                        serial, message, signature, requestId);
            }
        } catch (IllegalArgumentException e)
        {
            log.warn(e.getMessage());
            return false;
        }
        //验签成功
        return true;
    }

    //处理请求参数的方法-- 对参数进行判断的过程
    protected final void validateParameters(HttpServletRequest request)
    {

        // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
        String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};

        String header = null;
        //判空
        for (String headerName : headers)
        {
            header = request.getHeader(headerName);
            if (header == null)
            {
                throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
            }
        }

        //header 就是这个---> WECHAT_PAY_TIMESTAMP 时间戳,用于判断请求是否过期
        String timestampStr = header;
        try
        {
            //通过时间戳创建一个基于时间戳那个时间的时间对象
            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
            // 如果时间戳过期了,就拒绝过期请求,  创建一个基于此时此刻的时间对象--> Instant.now()
            //比较这两个时间对象 --> responseTimeh 和 Instant.now() ,如果比较的时间大于5分钟,就会认为这是一个过期的请求,那么就拒绝
            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES)
            {
                throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
            }
        } catch (DateTimeException | NumberFormatException e)
        {
            throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
        }
    }

    //构造验签名串方法
    protected final String buildMessage(HttpServletRequest request) throws IOException
    {
        //获取时间戳
        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        //随机数的字符串形式
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        return timestamp + "\n"
                + nonce + "\n"
                + body + "\n";
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1179101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vscode配置环境变量

首先点击下面这个链接。 sMinGW-w64 - for 32 and 64 bit Windows - Browse Files at SourceForge.net 然后选择Files这个选项 向下移选择下载这个文件 解压完成之后&#xff0c;找到这个文件的bin目录复制路径后&#xff0c;添加到环境变量中 依次点击后打开cmd&#xff0…

高性能三防工业平板电脑 防摔防爆电容屏工控平板

HT1000是一款高性能工业三防平板&#xff0c;10.1英寸超清大屏&#xff0c;厚度仅14.9mm&#xff0c;超薄机身&#xff0c;可轻松插入袋中&#xff0c;方便携带&#xff0c;搭载8核2.0GHz高性能CPU&#xff0c;行业领先的Android 11.0&#xff0c;设备性能大幅提升&#xff0c;…

通过使用Amazon Neptune来预测电影类型初体验

文章目录 福利来袭Amazon Neptune什么是图数据库为什么要使用图数据库什么是Amazon NeptuneNeptune 的特点 快速入门环境搭建notebook 图神经网络快速构建加载数据配置端点Gremlin 查询清理 删除环境S3 存储桶删除 福利来袭 前几天有小伙伴在群里灵魂发问&#xff1a;双11到来…

245:vue+openlayers 利用canvas绘制边线纹路

第245个 点击查看专栏目录 本示例的目的是介绍如何在vue+openlayers项目中利用canvas绘制边线纹路。思路就是将stroke渲染通过canvas设定的pattern模式。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共170行)专…

大语言模型研究进展综述

1、历史 自20世纪50年代图灵测试被提出以来&#xff0c;研究人员一直在探索和开发能够理解并掌握语言的人工智能技术。 作为重要的研究方向之一&#xff0c;语言模型得到了学术界的广泛研究&#xff0c;从早期的统计语言模型和神经语言模型开始&#xff0c;发展到基于Transform…

EasyHttp框架的使用

项目的集成 // JitPack 远程仓库&#xff1a;https://jitpack.io maven { url https://jitpack.io } // 网络请求框架&#xff1a;https://github.com/getActivity/EasyHttp implementation com.github.getActivity:EasyHttp:12.5 // OkHttp 框架&#xff1a;https://github.c…

ssm+vue的疫情高校师生外出请假管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的疫情高校师生外出请假管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三…

2023版Pycharm关闭一直显示closing project,正在关闭项目

点击 帮助 下的 查找操作 英文版为 Help 下的 Find Action 输入 Registry 禁用 ide.await.scope.completion 即可 PS&#xff1a;按 Ctrl F 输入可以快速检索

快速实现一个企业级域名 SSL 证书有效期监控巡检系统

Why 现在对于企业来说&#xff0c;HTTPS 已经不是可选项&#xff0c;已经成为一个必选项。HTTPS 协议采用 SSL 协议&#xff0c;采用公开密钥的技术&#xff0c;提供了一套 TCP/IP 传输层数据加密的机制。SSL 证书是一种遵守 SSL 协议的服务器数字证书&#xff0c;一般是由权威…

《golang设计模式》第三部分·行为型模式-04-迭代器模式(Iterator)

文章目录 1. 概念1.1 角色1.2 类图 2. 代码示例2.1 需求2.2 代码2.3 类图 1. 概念 迭代器&#xff08;Iterator&#xff09;能够在不暴露聚合体内部表示的情况下&#xff0c;向客户端提供遍历聚合元素的方法。 1.1 角色 InterfaceAggregate&#xff08;抽象聚合&#xff09;…

图像切分:将一张长图片切分为指定长宽的多张图片

1.需求 比如有一张很长的图片其大小为宽度779&#xff0c;高度为122552&#xff0c;那我想把图片切分为779乘以1280的格式。 步骤如下&#xff1a; 使用图像处理库&#xff08;如PIL或OpenCV&#xff09;加载原始图片。确定子图片的宽度和高度。计算原始图片的宽度和高度&am…

6、Python控制流:if语句、for循环、while循环、循环控制语句

文章目录 Python控制流:if语句、for循环、while循环、循环控制语句if语句示例:for循环示例:while循环示例:循环控制语句示例:最佳实践Python控制流:if语句、for循环、while循环、循环控制语句 控制流是编程中的基础概念,它允许我们根据不同的条件执行不同的代码块,或者…

day3作业

自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c; 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void show() #include <io…

哈尔滨招聘网站哪个好

哈尔滨招聘网站 吉鹿力招聘网 比较好&#xff0c;我们都知道在现代社会中&#xff0c;互联网已经成为了人们获取信息的主要渠道之一。在寻找工作方面也不例外。吉鹿力招聘网是哈尔滨找工作的求职招聘网站。 登录 吉鹿力招聘网 “注册账号”&#xff0c;然后输入个人基本信息&a…

Android-JobService

JobService 这里写目录标题 JobService一、API详解1 onStartJob2 onStopJob 二、onStartJob | onStopJob 返回值case 1case 2case 3 ref: 深入理解JobScheduler与JobService的使用 - 掘金 (juejin.cn) (28条消息) JobService的使用介绍_TechMerger的博客-CSDN博客 (28条消息) J…

使用 Threejs 从基础开始构建 3D 地球

需求 threejs学习-3D 地球 实现&#xff1a; 1、使用粒子效果模拟宇宙星空 2、贴图、模型等资源的加载 3、加载资源的监听 4、效果合成器 EffectComposer 的初级使用 5、在地球上设置坐标以及坐标涟漪动画 6、标点间建立飞线 7、简单动画建议先浏览一遍git地址上代码&#xff…

Java2 - 数据结构

5 数据类型 5.1 整数类型 在Java中&#xff0c;数据类型用于定义变量或表达式可以存储的数据的类型。Java的数据类型可分为两大类&#xff1a;基本数据类型和引用数据类型。 byte&#xff0c;字节 【1字节】表示范围&#xff1a;-128 ~ 127 即&#xff1a;-2^7 ~ 2^7 -1 sho…

LeetCode | 面试题 02.02. 返回倒数第 k 个节点

LeetCode | 面试题 02.02. 返回倒数第 k 个节点 OJ链接 思路&#xff1a;定义两个快慢指针&#xff0c;让快指针先提前走k个节点&#xff0c;然后再让慢结点和快结点一起走&#xff0c;当快指针 NULL时&#xff0c;慢指针就是倒数第k个节点 代码如下&#xff1a; int kthT…

ARMday02(汇编语法、汇编指令)

汇编语法 汇编文件中的内容 1.伪操作&#xff1a;在汇编程序中不占用存储空间&#xff0c;但是可以在程序编译时起到引导和标识作用 .text .global .glbal .if .else .endif .data .word.... 2.汇编指令&#xff1a;每一条汇编指令都用来标识一个机器码&#xff0c;让计算机做…

windows 下 QT Android 环境搭建(QGC 4.2.x + Qt 5.15.2)

文章目录 1. QT Creator 环境搭建2. JDK1&#xff09;官网途径&#xff1a;2) 360 安装&#xff1a;配置 3. SDK1) 通过 Android Studio2&#xff09;QT 配置中安装 姊妹篇&#xff1a; win10下新版QGC地面站环境搭建全面攻略&#xff08;v4.x.x QGroundControl地面站搭建&…