微信支付之JSAPI支付

article/2025/9/13 20:36:06
  1. 首先看一下,微信支付关于jsapi的官方文档,相关接口,一共有下单,查询订单,关闭订单,调起jsapi支付,支付结果通知,申请退款,查询单笔退款,退款结果通知,申请交易账单,申请资金账单以及下载账单接口。其中稍微难一点的支付结果通知和退款结果通知,因为涉及到验签和解密报文,不过最新的官方已经给了示例,很方便的。

jsapi支付流程:

 黄色部分是需要我们处理。

1、jsapi下单接口

            HttpPost httpPost = new HttpPost(WxPayConstant.WX_PAY_DOMAIN.concat(WxApiType.JSAPI_PAY.getType()));httpPost.addHeader("Accept", "application/json");httpPost.addHeader("Content-type","application/json; charset=utf-8");ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectMapper objectMapper = new ObjectMapper();ObjectNode rootNode = objectMapper.createObjectNode();rootNode.put("mchid",WxPayConstant.WX_PAY_MERCHANT_ID).put("appid", WxPayConstant.WX_PAY_APPID).put("description", "测试").put("notify_url", WxPayConstant.WX_PAY_NOTIFY_DOMAIN.concat(WxNotifyType.JSAPI_NOTIFY.getType())).put("out_trade_no", wxPayQuery.getSn());rootNode.putObject("amount").put("total", totalPrice);rootNode.putObject("payer").put("openid", openid);objectMapper.writeValue(bos, rootNode);httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));CloseableHttpResponse response = httpClient.execute(httpPost);try {String bodyAsString = EntityUtils.toString(response.getEntity());//响应体int statusCode = response.getStatusLine().getStatusCode();//响应状态码if (statusCode == 200) {log.info("成功, 返回结果 = " + bodyAsString);} else if (statusCode == 204) { //处理成功,无返回Bodylog.info("成功");} else {log.info("jsapi下单失败,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);throw new ServiceException("request failed");}//把数据返回前端//String substring = bodyAsString.substring(12, 50);HashMap<String, String> hashMap = new HashMap<>();hashMap.put("prepay_id",bodyAsString);ResultMessage<HashMap<String, String>> data = ResultUtil.data(hashMap);return data;} finally {response.close();}

2、获取签名

String prepayId = "prepay_id="+wxPayQuery.getPrepayId();//这个地方很容易出错//获取32位随机字符串String nonceStr = GeneratorUtil.randomSequence(32);System.out.println(nonceStr);//APPIDString wxPayAppid = WxPayConstant.WX_PAY_APPID;//时间戳Long time = (new Date().getTime())/1000;String timeStamp = time.toString();System.out.println(timeStamp);//构造签名串String source=wxPayAppid+"\n"+timeStamp+"\n"+nonceStr+"\n"+prepayId+"\n";String paySign = "";try {// 加载商户私钥(privateKey:私钥字符串)String privateKey = FileUtils.readFileToString(new File(WxPayConstant.WX_PAY_PRIVATE_KEY_PATH));System.out.println(privateKey);paySign = SignUtil.signBySHA256WithRSA(source, privateKey, "UTF-8");} catch (Exception e) {e.printStackTrace();}//组装数据Map<String, String> map = new HashMap<>();map.put("timeStamp",timeStamp);map.put("nonceStr",nonceStr);map.put("appId",wxPayAppid);map.put("package",prepayId);map.put("paySign",paySign);ResultMessage<Map<String, String>> data = ResultUtil.data(map);return data;
}

3、处理支付结果通知

@ApiOperation(value = "支付结果通知")
@PostMapping ("/jsapi/notify")
public String jsapiNotify(HttpServletRequest httpServletRequest, HttpServletResponse response){log.info("支付结果通知");//创建应答对象Map<String, String> responseMap = new HashMap<>();Gson gson = new Gson();try {//处理通知参数String body = HttpUtils.readData(httpServletRequest);log.info("支付结果通知"+body);Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);log.info("支付通知的id ===> {}", bodyMap.get("id").toString());//验证签名PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);// 获取证书管理器实例CertificatesManager certificatesManager = CertificatesManager.getInstance();// 向证书管理器增加需要自动更新平台证书的商户信息certificatesManager.putMerchant(WxPayConstant.WX_PAY_MERCHANT_ID,new WechatPay2Credentials(WxPayConstant.WX_PAY_MERCHANT_ID, new PrivateKeySigner(WxPayConstant.WX_PAY_MERCHANT_SERIAL_NUMBER,merchantPrivateKey)), WxPayConstant.WX_PAY_API_V3KEY.getBytes(StandardCharsets.UTF_8));// 从证书管理器中获取verifierVerifier verifier = certificatesManager.getVerifier(WxPayConstant.WX_PAY_MERCHANT_ID);String serialNumber = httpServletRequest.getHeader("Wechatpay-Serial");String nonce = httpServletRequest.getHeader("Wechatpay-Nonce");String timestamp = httpServletRequest.getHeader("Wechatpay-Timestamp");String signature = httpServletRequest.getHeader("Wechatpay-Signature");// 构建request,传入必要参数NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(serialNumber).withNonce(nonce).withTimestamp(timestamp).withSignature(signature).withBody(body).build();NotificationHandler handler = new NotificationHandler(verifier, WxPayConstant.WX_PAY_API_V3KEY.getBytes(StandardCharsets.UTF_8));// 验签和解析请求体Notification notification = handler.parse(request);Assert.assertNotNull(notification);// 从notification中获取解密报文String decryptData = notification.getDecryptData();HashMap resource =gson.fromJson(decryptData,HashMap.class);log.info("验签成功");//订单处理wxPayService.processOrder(resource);//应答 成功response.setStatus(200);responseMap.put("code","SUCCESS");responseMap.put("message","成功");return gson.toJson(responseMap);} catch (Exception e) {log.info("验签失败");response.setStatus(500);responseMap.put("code","FAIL");responseMap.put("message","失败");return gson.toJson(responseMap);}
}

4、关闭订单接口

log.info("关单接口的调用,订单号 ===> {}", orderSn);//创建远程请求对象
String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderSn);
url = WxPayConstant.WX_PAY_DOMAIN.concat(url);
HttpPost httpPost = new HttpPost(url);//组装json请求体
Gson gson = new Gson();
Map<String, String> paramsMap = new HashMap<>();
paramsMap.put("mchid", WxPayConstant.WX_PAY_MERCHANT_ID);
String jsonParams = gson.toJson(paramsMap);
log.info("请求参数 ===> {}", jsonParams);//将请求参数设置到请求对象中
StringEntity entity = new StringEntity(jsonParams,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);try {int statusCode = response.getStatusLine().getStatusCode();//响应状态码if (statusCode == 204) { //处理成功log.info("微信支付取消订单成功");} else {log.info("微信支付取消订单失败,响应码 = " + statusCode);throw new IOException("request failed");}} finally {response.close();
}

5、根据订单号查询订单状态

String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderSn);
url = WxPayConstant.WX_PAY_DOMAIN.concat(url).concat("?mchid=").concat(WxPayConstant.WX_PAY_MERCHANT_ID);HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpGet);
String bodyAsString = EntityUtils.toString(response.getEntity());//响应体try {int statusCode = response.getStatusLine().getStatusCode();//响应状态码if (statusCode == 200) { //处理成功log.info("成功, 返回结果 = " + bodyAsString);} else if (statusCode == 204) { //处理成功,无返回Bodylog.info("成功");} else {log.info("查单接口调用,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);throw new IOException("request failed");}} finally {response.close();

6、申请退款

//调用统一下单API
String url = WxPayConstant.WX_PAY_DOMAIN.concat(WxApiType.DOMESTIC_REFUNDS.getType());
HttpPost httpPost = new HttpPost(url);// 请求body参数
Gson gson = new Gson();
Map paramsMap = new HashMap();
paramsMap.put("out_trade_no", tradeSn);//订单编号
paramsMap.put("out_refund_no", order.getRefundNo());//退款单编号
paramsMap.put("reason",reason);//退款原因
paramsMap.put("notify_url", WxPayConstant.WX_PAY_NOTIFY_DOMAIN.concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址Map amountMap = new HashMap();//把元转换为分
String flowPrice = order.getFlowPrice().toString();
String total = AmountUtils.changeY2F(flowPrice);
Integer totalPrice = Integer.valueOf(total);amountMap.put("refund", totalPrice);//退款金额
amountMap.put("total", totalPrice);//原订单金额
amountMap.put("currency", "CNY");//退款币种
paramsMap.put("amount", amountMap);//将参数转换成json字符串
String jsonParams = gson.toJson(paramsMap);
log.info("请求参数 ===> {}" + jsonParams);StringEntity entity = new StringEntity(jsonParams,"utf-8");
entity.setContentType("application/json");//设置请求报文格式
httpPost.setEntity(entity);//将请求报文放入请求对象
httpPost.setHeader("Accept", "application/json");//设置响应报文格式//完成签名并执行请求,并完成验签
CloseableHttpResponse response = httpClient.execute(httpPost);try {//解析响应结果String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 退款返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("退款异常, 响应码 = " + statusCode+ ", 退款返回结果 = " + bodyAsString);}} finally {response.close();
}

7、查询单笔退款

log.info("查询退款接口调用 ===> {}", refundNo);String url =  String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
url = WxPayConstant.WX_PAY_DOMAIN.concat(url);//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 查询退款返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ", 查询退款返回结果 = " + bodyAsString);}return ResultUtil.data(bodyAsString);} finally {response.close();
}

8、退款通知

@ApiOperation("退款结果通知")
@PostMapping("/refunds/notify")
public String refundsNotify(HttpServletRequest httpServletRequest, HttpServletResponse response){log.info("退款通知执行");Gson gson = new Gson();Map<String, String> map = new HashMap<>();//应答对象try {//处理通知参数String body = HttpUtils.readData(httpServletRequest);Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);String requestId = (String)bodyMap.get("id");log.info("支付通知的id ===> {}", requestId);//验证签名PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);// 获取证书管理器实例CertificatesManager certificatesManager = CertificatesManager.getInstance();// 向证书管理器增加需要自动更新平台证书的商户信息certificatesManager.putMerchant(WxPayConstant.WX_PAY_MERCHANT_ID,new WechatPay2Credentials(WxPayConstant.WX_PAY_MERCHANT_ID, new PrivateKeySigner(WxPayConstant.WX_PAY_MERCHANT_SERIAL_NUMBER,merchantPrivateKey)), WxPayConstant.WX_PAY_API_V3KEY.getBytes(StandardCharsets.UTF_8));// 从证书管理器中获取verifierVerifier verifier = certificatesManager.getVerifier(WxPayConstant.WX_PAY_MERCHANT_ID);String serialNumber = httpServletRequest.getHeader("Wechatpay-Serial");String nonce = httpServletRequest.getHeader("Wechatpay-Nonce");String timestamp = httpServletRequest.getHeader("Wechatpay-Timestamp");String signature = httpServletRequest.getHeader("Wechatpay-Signature");// 构建request,传入必要参数NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(serialNumber).withNonce(nonce).withTimestamp(timestamp).withSignature(signature).withBody(body).build();NotificationHandler handler = new NotificationHandler(verifier, WxPayConstant.WX_PAY_API_V3KEY.getBytes(StandardCharsets.UTF_8));// 验签和解析请求体Notification notification = handler.parse(request);//非空校验Assert.assertNotNull(notification);// 从notification中获取解密报文String decryptData = notification.getDecryptData();HashMap resource =gson.fromJson(decryptData,HashMap.class);log.info("通知验签成功");//处理退款单wxPayService.processRefund(resource);//成功应答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);}
}

9、获取账单的url

log.warn("申请账单接口调用 {}", billDate);String url = "";
if("tradebill".equals(type)){url =  WxApiType.TRADE_BILLS.getType();
}else if("fundflowbill".equals(type)){url =  WxApiType.FUND_FLOW_BILLS.getType();
}else{throw new RuntimeException("不支持的账单类型");
}url = WxPayConstant.WX_PAY_DOMAIN.concat(url).concat("?bill_date=").concat(billDate);//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Accept", "application/json");//使用wxPayClient发送请求得到响应
CloseableHttpResponse response = httpClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 申请账单返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("申请账单异常, 响应码 = " + statusCode+ ", 申请账单返回结果 = " + bodyAsString);}//获取账单下载地址Gson gson = new Gson();Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);return resultMap.get("download_url");} finally {response.close();}

10、下载账单 注意返回的文件流 需要自己去处理

log.warn("下载账单接口调用 {}, {}", billDate, type);//获取账单url地址
String downloadUrl = this.queryBill(billDate, type);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(downloadUrl);
httpGet.addHeader("Accept", "application/json");//使用wxPayClient发送请求得到响应
CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet);try {String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 下载账单返回结果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("下载账单异常, 响应码 = " + statusCode+ ", 下载账单返回结果 = " + bodyAsString);}return bodyAsString;} finally {response.close();
}

简单总结一下,最开始看微信开发文档的时候,简直是头皮发麻,需要配置的参数也很多,而且必须很仔细,特别是支付授权目录以及秘钥,要是配错了再在前端发起支付的时候会报验签错误,排查也比较麻烦,所以需要细心些。还有就是看验签和解密报文的时候,开始看简直打脑壳,不过多看一下也还好,上面的方法获取的数据就直接是解密报文里面的数据,后面就需要自己拿到数据根据自己业务场景去处理订单。


http://chatgpt.dhexx.cn/article/ATTl5bpf.shtml

相关文章

微信支付JSAPI

一、什么是JSAPI支付 JSAPI支付是指商户通过调用微信支付提供的JSAPI接口&#xff0c;在支付场景中调起微信支付模块完成收款。 应用场景有&#xff1a; 线下场所&#xff1a;调用接口生成二维码&#xff0c;用户扫描二维码后在微信浏览器中打开页面后完成支付 公众号场景&…

微信支付之JSAPI支付开发流程

JSAPI支付 前言准备开发1.流程说明2.下单&#xff08;预支付&#xff09;3.前端调起支付4.支付结果异步通知5.退款申请6.退款结果异步通知 结语 前言 最近项目涉及到微信支付的功能&#xff0c;在这里简单分享下整体的开发流程,这里要介绍的是JSAPI支付。 JSAPI网页支付&#…

FME是一个好东东

FME产品分为三个层次&#xff1a; 一、入门级 1、特点&#xff1a;支持常见的GIS软件的数据交换&#xff0c;如 MapInfo TAB, DGN, DXF, DWG, SDTS, SHP, and TIGER&#xff1b;可以运行大部分函数&#xff08;Funtion和Factory&#xff09;&#xff1b;不支持由Plug-in开发的第…

黑马程序员Maven学习笔记

前言 这里是黑马程序员Maven学习笔记分享&#xff0c;这是视频链接。 我还有其它前端内容的笔记&#xff0c;有需要可以查看。 文章目录 前言基础Maven简介Maven是什么Maven的作用 Maven的下载Maven的基础概念仓库坐标本地仓库配置远程仓库的配置 第一个Maven项目Maven的项目…

MAEKDOWN

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

谷粒商城前端笔记

前端笔记 JavaScript ES6 ECMAScript6.0&#xff08;以下简称ES6&#xff0c;ECMAScript是一种由Ecma国际通过ECMA-262标准化的脚本&#xff09;&#xff0c;是JavaScript语言的下一代标准&#xff0c;2015年6月正式发布&#xff0c;从ES6开始的版本号采用年号&#xff0c;如…

谷粒商城-分布式基础【业务编写】

谷粒商城-分布式基础篇【环境准备】谷粒商城-分布式基础【业务编写】谷粒商城-分布式高级篇【业务编写】持续更新谷粒商城-分布式高级篇-ElasticSearch谷粒商城-分布式高级篇-分布式锁与缓存项目托管于gitee 一、三级分类 此处三级分类最起码得启动renren-fast、nacos、gate…

1. 谷粒商城架构

架构图 解析 客户通过任意客户端&#xff08;app/Web&#xff09;向服务器发送请求&#xff0c; 请求首先来到Nginx集群&#xff0c;Nginx将请求转交给Api网关&#xff08;SpringCloud Gateway&#xff09;, Api网关&#xff1a; 可以根据当前请求&#xff0c;动态路由到指…

谷粒商城详细笔记

前言 mysql安装在腾讯云 redis安装在本地虚拟机master上 运行时&#xff0c;renren-fast这个项目要到单独开个idea窗口打开。 一、项目简介 1、项目微服务架构图 微服务&#xff1a;拒绝大型单体应用&#xff0c;基于业务边界进行服务微化拆分&#xff0c;各个服务独立部…

谷粒商城之分布式基础(二)

6 商品服务 6.1 三级分类 商城的商品页面展示是一个三级分类的。有一级分类、二级分类、三级分类。这就是我们接下来要进行的操作。 6.1.1 数据库 首先我们在gulimall_pms这个数据库中的pms_category这个表下插入数据 商品三级分类SQL代码 6.1.2 查出所有分类及其子分类 1…

麦克

品牌&#xff1a;InvenSense Kingstate RS PRO 灵敏度&#xff1a;-27到-44db之间 方向性&#xff1a;全方位、单向性、噪声消除 标准操作电压&#xff1a;1.5V到3.3V均有 安装方式&#xff1a;导线、表面贴装、通孔 输出阻抗大小&#xff1a;1.8K、2.2K、200欧、350欧…

Kubeedge Beehive 模块源码分析

文章目录 概述结构Model --- 消息模型Header --- 消息头Router --- 消息路由资源操作资源类型 Context --- 上下文ModuleContext --- 模块上下文MessageContext --- 消息上下文GlobalContext --- 全局上下文方法 Channel Context数据结构方法ModuleContext 接口实现AddModuleAd…

谷粒商城简介(1~5集)

谷粒商城简介&#xff08;1~5集&#xff09; 一、项目简介 1、项目背景 1&#xff09;、电商模式 市面上有 5 种常见的电商模式 B2B、B2C、C2B、C2C、O2O&#xff1b; 1、B2B 模式 B2B (Business to Business)&#xff0c; 是指商家与商家建立的商业关系。 如&#xff1a;阿…

谷粒商城:分布式基础概念(2)

微服务 微服务架构风格&#xff0c;就像是把一个单独的应用程序开发为一套小服务&#xff0c;每个小服务运行在自 己的进程中&#xff0c;并使用轻量级机制通信&#xff0c;通常是 HTTP API。这些服务围绕业务能力来构建&#xff0c; 并通过完全自动化部署机制来独立部署。这些…

beetl,freemarker,thymeleaf对比及springboot集成

调研类型&#xff1a; Freemarker&#xff0c;Thymeleaf&#xff0c;Beetl&#xff0c;Velocity 调研方向&#xff1a; 性能&#xff0c;活跃度&#xff0c;各自优缺点&#xff0c;应用实例 2.1、性能报告&#xff1a; Jdk:1.8 Cpu: 8核12线程 Jvm : -Xms512m -Xmx512m B…

部分壳与脱壳

壳与脱壳 对网上部分壳与脱壳的摘录与总结&#xff0c;仅供参考&#xff0c;侵删 参考链接1 https://www.52pojie.cn/thread-138380-1-1.html 参考链接2 https://www.cnblogs.com/milantgh/p/3869083.html 参考链接3 http://blog.sina.com.cn/s/blog_3e28c8a5010132m6.html 壳…

谷粒商城项目学-分布式基础

项目框架图 分布式基础概念 • 微服务、注册中心、配置中心、远程调用、Feign、网关 • 2、基础开发 • SpringBoot2.0、SpringCloud、Mybatis-Plus、Vue组件化、阿里云对象存储 • 3、环境 • Vagrant、Linux、Docker、MySQL、Redis、逆向工程&人人开源 • 4、开发规范 •…

【笔记/后端】谷粒商城基础篇

目录 一、环境配置1 Docker1.1 Docker是什么&#xff1f;1.2 安装&启动1.2.1 阿里云镜像加速 1.3 安装MySQL1.4 安装Redis 2 开发环境2.1 Maven2.2 Git2.3 Node 二、创建微服务项目1 内容2 问题记录3 renren-generator 三、分布式组件1 Nacos1.1 注册中心1.2 配置中心1.2.1…

谷粒商城(二)

谷粒商城&#xff08;二&#xff09; 后台商品服务 - 三级分类1、查询1&#xff09;、接口编写2&#xff09;、树形展示三级分类数据3&#xff09;、配置网关路由1 更改前端 base 路径2 将服务注册进nacos3 网关模块配置路由4 测试 4&#xff09;、解决跨域 2、删除1&#xff0…

谷粒商城(五)

谷粒商城&#xff08;五&#xff09; 订单服务1、环境搭建1&#xff09;、页面2&#xff09;、代码 2、订单登录拦截3、订单确认页1&#xff09;、VO模型2&#xff09;、订单确认页数据查询1 接口编写2 调用远程服务 3&#xff09;、Feign远程调用丢失请求头启动服务报错解决 4…