Java实现微信支付

微信支付

小黄最在工作中对接需要对接微信支付,在此记录一下微信支付开发的相关流程,望能帮助到各位

前期准备

由于小黄是小程序端需要对接微信支付,需要注册小程序等,小黄会一一列举出来

小程序注册

所需文件
  • 没有注册过微信公众平台、微信开放平台的邮箱
  • 营业执照
  • 对公账户信息(需要对公账户打款来校验)
  • 纳税识别号
  • 300大洋
注册

注册网址:https://mp.weixin.qq.com/cgi-bin/wx

按要求填写所需文件信息即可,由于是企业注册的小程序,需要对公账户汇款,只需要几毛钱并且这个钱会退回来的

填写小程序信息

注册完成后,开始填写小程序信息,必须填写小程序信息,才能获得appid,也就是小程序id,开发中会用到

开通微信认证

这里需要支付300元的费用

微信支付注册

目前只支持企业注册微信支付

所需文件
  • 营业执照照片
  • 法人身份证照片
  • 所属行业的特殊资质
  • 超级管理员证明(需要公章)
  • 结算账户(对公账户)
注册

注册网址:https://pay.weixin.qq.com/index.php/apply/applyment_home/guide_normal

同样也是按要求填写信息即可,也是需要对公账户汇款

申请api证书

在开发中需要读取到api证书来解析你的api是否正确,所以这一步是必不可少的,另外推荐设置APIv3密钥,功能比v2版本多

image-20230914164243283

绑定微信小程序

在产品中心里,可以绑定微信小程序

image-20231005110030294

开发微信支付

微信支付有很多支付方式,具体可以参考产品文档,因为业务需求,小黄使用的是JSAPI支付方式,接下来就重点介绍一下JSAPI支付方式。

接入前准备

需要设置支付授权目录,这里小黄设置的就是公司测试域名

image-20231005110519599

引入maven

<dependency><groupId>com.github.wechatpay-apiv3</groupId><artifactId>wechatpay-java</artifactId><version>0.2.11</version>
</dependency>

支付

刚开始对接微信支付,咱们肯定先对接支付,看一下JSAPI支付的时序图,其实需要Java后端做的就只有2-6,15-21

示意图

微信创建订单

参考文档

在创建订单之前的业务处理,小黄就不贴出来了,每个开发的业务都不一样,主要是JSAPI下单给微信接受他的返回值,也就是时序图中的2-6步骤

创建配置类

商户证书序列号,可以通过执行openssl x509 -noout -serial -in apiclient_cert.pem指令解析出来,cmd到证书所在位置

image-20231005112010001

@Configuration
@Data
@RefreshScope
public class WechatConfig {@Value("${wechat.mchId}")private String mchId; //微信支付id@Value("${wechat.filePath}")private String filePath; //微信支付证书所存放的位置@Value("${wechat.merchantSerialNumber}")private String merchantSerialNumber; //商户证书序列号@Value("${wechat.apiV3Key}")private String apiV3Key; //apiv3密钥@Value("${wechat.appId}")private String appId; //绑定的小程序id@Value("${wechat.notify-url}")private String notifyUrl; //支付回调地址(目前用不到)@Value("${wechat.refund-notify-url}")private String refundNotifyUrl; //退款回调地址(目前用不到)@Beanpublic RSAAutoCertificateConfig rsaAutoCertificateConfig(){return new RSAAutoCertificateConfig.Builder().merchantId(mchId).merchantSerialNumber(merchantSerialNumber).apiV3Key(apiV3Key).privateKeyFromPath(filePath).build();}
}

通过api调用微信支付

之前小黄看到好多文档都是通过http请求来调用的,加了依赖后,相当于微信封装了一层,使用起来更方便了,只需要向PrepayRequest中塞数据即可

这里有两个注意点:

  • 金额的单位是分
  • 必须要填异步回调地址(回调地址的作用是当用户支付后,会访问这个地址访问支付信息,该地址必须是https,测试的时候建议使用花生壳工具走内网穿透)
public PrepayWithRequestPaymentResponse prepay(JsPrepayDto jsPrepayDto, MemberUserEntity memberUser) {//下单并生成调起支付的参数JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();PrepayRequest request = new PrepayRequest();request.setAppid(wechatConfig.getAppId());request.setMchid(wechatConfig.getMchId());request.setOutTradeNo(jsPrepayDto.getTradeNo()); //传入自己平台的订单idrequest.setDescription(jsPrepayDto.getDescription());request.setNotifyUrl(wechatConfig.getNotifyUrl());Amount amount = new Amount();//todo:测试只支付一分amount.setTotal(1);request.setAmount(amount);Payer payer = new Payer();payer.setOpenid(memberUser.getWxOpenId());request.setPayer(payer);PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);return response;
}
支付通知

参考文档

创建订单完成后,微信会主动调用设置的回调地址,将加密的数据返回给我们

注意

对后台通知交互时,如果微信收到应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功

  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  • 如果在所有通知频率后没有收到微信侧回调。商户应调用查询订单接口确认订单状态。

特别提醒: 商户系统对于开启结果通知的内容一定要做签名验证,并校验通知的信息是否与商户侧的信息一致,防止数据泄露导致出现“假通知”,造成资金损失。

解析请求

private String getRequestBody(HttpServletRequest request) {StringBuffer sb = new StringBuffer();try (ServletInputStream inputStream = request.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));) {String line;while ((line = reader.readLine()) != null) {sb.append(line);}} catch (IOException e) {log.error("读取数据流异常:{}", e);}return sb.toString();
}

解密、处理业务

Transaction类有很多,注意是com.wechat.pay.java.service.payments.model包下的

@Transactional(rollbackFor = Exception.class)
public Map wechatNotify(HttpServletRequest request, HttpServletResponse response) {HashMap<String, String> map = new HashMap<>();//获取报文String body = getRequestBody(request);//随机串String nonceStr = request.getHeader("Wechatpay-Nonce");//微信传递过来的签名String signature = request.getHeader("Wechatpay-Signature");//证书序列号(微信平台)String serialNo = request.getHeader("Wechatpay-Serial");//时间戳String timestamp = request.getHeader("Wechatpay-Timestamp");// 构造 RequestParamRequestParam requestParam = new RequestParam.Builder().serialNumber(serialNo).nonce(nonceStr).signature(signature).timestamp(timestamp).body(body).build();// 初始化 NotificationParserNotificationParser parser = new NotificationParser(config);Transaction transaction = null;try {// 验签、解密并转换成 Transactiontransaction = parser.parse(requestParam, Transaction.class);log.info("=============解密:Transaction ============= {}", transaction);} catch (Exception e) {log.error("签名验证失败");response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);map.put("code", "FAIL");map.put("message", "失败");return map;}String lockName = "wechat_notice_" + transaction.getOutTradeNo();RLock lock = redissonClient.getLock(lockName);try {//微信可能会重复发送数据,如果已经处理过,应该直接返回成功//加锁,避免并发重复添加lock.lock(5,TimeUnit.SECONDS);log.info("=============接收到微信支付回调通知=============");//-----处理业务逻辑-----//通知微信回调成功map.put("code", "SUCCESS");return map;} catch (Exception e) {log.error("业务处理失败");response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);map.put("code", "FAIL");map.put("message", "失败");return map;} finally {lock.unlock();}
}
主动查询订单

参考文档

这个请求可以查询订单的支付状态

@Transactional(rollbackFor = Exception.class)
public void findPaymentOrder(String payOrderId) {JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();request.setMchid(wechatConfig.getMchId());request.setOutTradeNo(payOrderId);Transaction transaction = service.queryOrderByOutTradeNo(request);log.info("============= Transaction ============= {}", transaction);String lockName = "wechat_notice_" + transaction.getOutTradeNo();RLock lock = redissonClient.getLock(lockName);try {//加锁,避免并发重复添加lock.lock(5,TimeUnit.SECONDS);//处理业务逻辑} finally {lock.unlock();}
}

退款

支付功能完成后,咱们开始着手做退款功能,其实跟支付差不多,先是去创建退款订单,再来接受微信发起的退款回调

创建退款订单

参考文档

这里需要注意的是,创建退款订单会有一个返回对象,里面包含着请求信息,但是微信退款是有延迟的,测试的时候到账时间在1-10秒内,如果要在到账后在做操作,建议通过回调来实现

public Boolean refundOrder(WechatRefundReq data) {//查询支付订单信息PayOrderDetailsEntity order = payOrderDetailsDao.selectOne(new LambdaQueryWrapper<PayOrderDetailsEntity>().eq(PayOrderDetailsEntity::getPayOrderId, data.getPayOrderId()));if (data.getRefundAmount().compareTo(order.getPayAmount()) > 0) {log.error("退款金额超出订单金额:data : {} , order : {}", data, order);return false;}//退款接口RefundService service = new RefundService.Builder().config(config).build();CreateRequest request = new CreateRequest();request.setTransactionId(order.getTradeNo());request.setOutRefundNo(data.getRefundOrderId());//用户退款原因,会在用户端微信提示
//        request.setReason(data.getReason());request.setNotifyUrl(wechatConfig.getRefundNotifyUrl());AmountReq amount = new AmountReq();amount.setRefund(data.getRefundAmount().multiply(BigDecimal.valueOf(100)).longValue());amount.setTotal(order.getPayAmount().multiply(BigDecimal.valueOf(100)).longValue());amount.setCurrency("CNY");request.setAmount(amount);log.info("======开始处理微信退款======");Refund refund = service.create(request);log.info("refund : {}" ,refund);return true;
}
退款通知

参考文档

注意

对后台通知交互时,如果微信收到应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功

  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  • 如果在所有通知频率后没有收到微信侧回调。商户应调用查询订单接口确认订单状态。

特别提醒: 商户系统对于开启结果通知的内容一定要做签名验证,并校验通知的信息是否与商户侧的信息一致,防止数据泄露导致出现“假通知”,造成资金损失。

@Transactional(rollbackFor = Exception.class)
public Map wechatNotify(HttpServletRequest request, HttpServletResponse response) {HashMap<String, String> map = new HashMap<>();//获取报文String body = getRequestBody(request);//随机串String nonceStr = request.getHeader("Wechatpay-Nonce");//微信传递过来的签名String signature = request.getHeader("Wechatpay-Signature");//证书序列号(微信平台)String serialNo = request.getHeader("Wechatpay-Serial");//时间戳String timestamp = request.getHeader("Wechatpay-Timestamp");// 构造 RequestParamRequestParam requestParam = new RequestParam.Builder().serialNumber(serialNo).nonce(nonceStr).signature(signature).timestamp(timestamp).body(body).build();// 初始化 NotificationParserNotificationParser parser = new NotificationParser(config);Transaction transaction = null;try {// 验签、解密并转换成 Transactiontransaction = parser.parse(requestParam, Transaction.class);log.info("=============解密:Transaction ============= {}", transaction);} catch (Exception e) {log.error("签名验证失败");response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);map.put("code", "FAIL");map.put("message", "失败");return map;}String lockName = "wechat_notice_" + transaction.getOutTradeNo();RLock lock = redissonClient.getLock(lockName);try {//微信可能会重复发送数据,如果已经处理过,应该直接返回成功//加锁,避免并发重复添加lock.lock(5,TimeUnit.SECONDS);log.info("=============接收到微信支付回调通知=============");//-----处理业务逻辑-----//通知微信回调成功map.put("code", "SUCCESS");return map;} catch (Exception e) {log.error("业务处理失败");response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);map.put("code", "FAIL");map.put("message", "失败");return map;} finally {lock.unlock();}
}

总结

至此,微信支付的主要流程就已经走完了,其他的api可以参考官方文档,对照着SDK来做,应该是轻而易举的

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

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

相关文章

私有云盘:lamp部署nextcloud+高可用集群

目录 一、实验准备&#xff1a; 二、配置mariadb主从复制 三台主机下载mariadb 1&#xff09;主的操作 2&#xff09;从的操作 3&#xff09;测试数据是否同步 三、配置nfs让web服务挂载 1、安装 2、配置nfs服务器 3、配置web服务的httpd 4、测试 四、web 服务器 配…

排序篇(三)----交换排序

排序篇(三)----交换排序 1.冒泡排序 基本思想: ​ 通过不断地比较相邻的元素&#xff0c;将较大的元素往后移动&#xff0c;从而实现排序的目的。 具体的步骤如下&#xff1a; 从待排序的数组中选择相邻的两个元素进行比较&#xff0c;如果前一个元素大于后一个元素&#…

ParagonNTFSforMac_15.5.102中文版最受欢迎的NTFS硬盘格式读取工具

Paragon NTFS for Mac是一款可以为您轻松解决Mac平台上不能识别Windows通用的NTFS文件难题&#xff0c;这是一款强大的Mac读写工具&#xff0c;相信在很多时候&#xff0c;Mac用户需要对NTFS文件的移动硬盘进行写入&#xff0c;但是macOS系统默认是不让写入的&#xff0c;使用小…

Nginx与Spring Boot的错误模拟实践:探索502和504错误的原因

文章目录 前言502和504区别---都是Nginx返回的access.log和error.log介绍SpringBoot结合Nginx实战502 and 504准备工作Nginx配置host配置SpringBoot 502模拟access.logerror.log 504模拟access.logerror.log 500模拟access.logerror.log 总结 前言 刚工作那会&#xff0c;最常…

Jmeter基础篇

1.性能测试指标 【虚拟用户数】&#xff1a;线程用户 【并发数】&#xff1a;指在某一时间&#xff0c;一定数量的虚拟用户同时对系统的某个功能进行交互&#xff0c;一般通过集合点实现 【事务】:事务代表一个完整的功能&#xff0c;一个接口可以是事务&#xff0c;多个接口…

第八章 排序 三、希尔排序

目录 一、算法思想 二、例子 三、代码实现 五、验证 六、空间复杂度 七、时间复杂度 八、稳定性 一、算法思想 先追求表中元素部分有序&#xff0c;在逐渐逼近表中元素全部有序。 二、例子 1、我们要升序排列此表 2、取一个差值作为子表的划分的条件&#xff0c;希尔本…

医疗器械标准目录汇编2022版共178页(文中附下载链接!)

为便于更好地应用医疗器械标准&#xff0c;国家药监局医疗器械标准管理中心组织对现行1851项医疗器械国家和行业标准按技术领域&#xff0c;编排形成《医疗器械标准目录汇编&#xff08;2022版&#xff09;》 该目录汇编分为通用技术领域和专业技术领域两大类&#xff0c;通用…

【逐步剖C】-第十一章-动态内存管理

一、为什么要有动态内存管理 从我们平常的学习经历来看&#xff0c;所开辟的数组一般都为固定长度大小的数组&#xff1b;但从很多现实需求来看需要我们开辟一个长度“可变”的数组&#xff0c;即这个数组的大小不能在建立数组时就指定&#xff0c;需要根据某个变量作为标准。…

分词.join 保存txt

要求 分词.join 保存txt 第1种方法 分词.join 保存txt input多行文本 /storage/emulated/0/数据中心/txt没有就新建为什么会想到这么做 1. 是因为有分词文件&#x1f4c4;要处理 2. 对各种词语和线索进行分类 3. 解释一下生活中不常见的现象&#xff0c;但是深刻的符合社会…

Inno Setup新手使用教程

1.编写脚本.iss文件 2.使用Inno Setup打开脚本 3.点击运行 4.打包好的文件在output文件夹下 注&#xff1a;运行不通过可能是文件不存在或者路径错误 推荐一个零声学院项目课&#xff0c;个人觉得老师讲得不错&#xff0c;分享给大家&#xff1a; 零声白金学习卡&#xff08;含…

PsychoPy Coder 心理学实验 斯特鲁普效应

选题&#xff1a;斯特鲁普效应实验 选题来源&#xff1a;你知道的「有趣的心理学实验」有哪些&#xff1f; - 知乎 (zhihu.com) 测试目标&#xff1a;探索斯特鲁普效应&#xff0c;即被试在判断文字颜色时&#xff0c;当文字的颜色与其所表示的颜色名称不一致时&#xff0c;是…

博途1200/1500 ALT指令

SMART PLC的ALT指令实现代码,请查看下面文章博客 SMART PLC如何构造ALT指令_smart200类似alt指令-CSDN博客单按钮启停这些老生常谈的问题,很多人感兴趣。这篇博文讨论下不同的实现方法,希望对大家有所帮助。指令虽然简单,但是在编程的时候合理使用对我们高效率编程帮助还是…

C/S架构学习之TCP的三次握手和四次挥手

TCP的三次握手&#xff1a;一定由客户端主动发起的&#xff0c;发生在建立连接的过程中。此过程发生在客户端的connect()函数和服务器的accept()函数之间。第一次握手&#xff1a;客户端向服务器发送一个带有SYN标志的数据包&#xff0c;表示客户端请求建立连接。并且客户端会选…

GO 中优雅编码和降低圈复杂度

本次主要是聊聊关于使用接口抽象和降低圈复杂度的方式 工作中&#xff0c;难免会遇到老项目老代码&#xff0c;不仅仅需要我们维护&#xff0c;可能还需要我们在原来的垃圾代码上进行新增功能或者是进行优化调整 例如 现有的老代码中关于用户系统这一块就已经经是摇摇欲坠&a…

python修改unittestreport中的用例条数

背景: 自动化框架中使用yaml文件作为数据配置&#xff0c;使用ddt作为数据驱动来运行测试用例&#xff0c;由于测试用例都是基于场景去编写&#xff0c;目前都是一个测试类算是一条测试用例&#xff0c;但基于测试报告里面一个类运行的测试方法有多个&#xff0c;因此统计的测试…

MATLAB 函数签名器

文章目录 MATLAB 函数签名器注释规范模板参数类型 kind数据格式 type选项的支持 使用可执行程序封装为m函数程序输出 编译待办事项推荐阅读附录 MATLAB 函数签名器 MATLAB 函数签名器 (FUNCSIGN) &#xff0c;在规范注释格式的基础上为函数文件或类文件自动生成函数签名&#…

专题一:双指针【优选算法】

双指针应用场景&#xff1a; 数组划分、数组分块 目录 一、移动0 二、复写0 从后向前 三、快乐数 链表带环 四、盛水最多的容器 单调性双指针 五、有效三角形个数 单调性双指针 六、和为s的两个数字 七、三数之和 细节多 需再练 一、移动0 class Solution { public:void move…

使用Jest测试Cesium源码

使用Jest测试Cesium源码 介绍环境Cesium安装Jest安装Jest模块包安装babel安装Jest的VSC插件 测试例子小结 介绍 在使用Cesium时&#xff0c;我们常常需要编写自己的业务代码&#xff0c;其中需要引用Cesium的源码&#xff0c;这样方便调试。此外&#xff0c;目前代码中直接使用…

ChatGPT付费创作系统V2.3.4独立版 +WEB端+ H5端 + 小程序最新前端

人类小徐提供的GPT付费体验系统最新版系统是一款基于ThinkPHP框架开发的AI问答小程序&#xff0c;是基于国外很火的ChatGPT进行开发的Ai智能问答小程序。当前全民热议ChatGPT&#xff0c;流量超级大&#xff0c;引流不要太简单&#xff01;一键下单即可拥有自己的GPT&#xff0…

C++——list(2)

作者&#xff1a;几冬雪来 时间&#xff1a;2023年9月28日 内容&#xff1a;C——list内容讲解 目录 前言&#xff1a; list的const迭代器&#xff1a; const的iterator&#xff1a; const迭代器&#xff1a; operator->: 拷贝构造&#xff1a; 迭代器接口补充&…