文章目录

  • 一.所需参数及maven依赖
  • 二.前端vue部分
  • 三.后端java部分

一.所需参数及maven依赖

官方文档
1.公众号id appid
2.商户号 mchid
3.APIv3秘钥 secret
4.商户api私钥 apiclient_key.pem
5.证书序列号

maven依赖

<!-- 微信支付V3版sdk -->
<dependency><groupId>com.github.wechatpay-apiv3</groupId><artifactId>wechatpay-apache-httpclient</artifactId><version>0.2.1</version>
</dependency>

二.前端vue部分

// 确认支付
onSubmit() {// 手机端校验if (!navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) {this.$dialog.alert({ message: '请在手机端微信关注公众号"跃迁赋能中心"进行购买' })return}// TODO 购买商品参数拼接、非空校验let that = thisthis.$api.request({url: '/wxpay/pay',method: 'post',params: params}).then(response => {// 拉起微信支付参数赋值that.payMap = response.dataif (typeof WeixinJSBridge === 'undefined') {if (document.addEventListener) {document.addEventListener('WeixinJSBridgeReady', this.onBridgeReady, false)} else if (document.attachEvent) {document.attachEvent('WeixinJSBridgeReady', this.onBridgeReady)document.attachEvent('onWeixinJSBridgeReady', this.onBridgeReady)}} else {this.onBridgeReady()}})
},
// 拉起微信支付
onBridgeReady() {let payMap = this.payMaplet that = thisWeixinJSBridge.invoke('getBrandWCPayRequest', {'appId': payMap.appId, // 公众号名称,由商户传入'timeStamp': payMap.timeStamp, // 时间戳,自1970年以来的秒数'nonceStr': payMap.nonceStr, // 随机串'package': payMap.package,'signType': payMap.signType, // 微信签名方式:'paySign': payMap.paySign // 微信签名},function(res) {if (res.err_msg == 'get_brand_wcpay_request:ok') {// 付款成功处理that.$router.replace({ path: '/' })}})
},

三.后端java部分

3.1 获取前端拉起微信支付参数
Controller

@ApiOperation("微信公众号支付")
@PostMapping("/pay")
public AjaxResult pay(HttpServletRequest request, @RequestParam(required = false) String miniIdStr,@RequestParam Double payAmount, @RequestParam String couponIdStr,@RequestParam(required = false) Long courseId) {Long personId = tokenService.getPersonId(request);// TODO 购买课程参数校验try {JSONObject result = officeOrderService.wxPay(personId, miniIdStr, payAmount, couponIdStr, courseId);Integer status = result.getInteger("status");if (status != null && status == 0) {// 支付失败不反回前端拉起微信支付参数return AjaxResult.error("支付失败,请稍后再试");}return AjaxResult.success(result);} catch (Exception e) {log.error("微信支付下单接口失败,personId:{},miniIdStr:{},payAmount:{},couponIdStr:{},courseId:{}", personId,miniIdStr, payAmount, couponIdStr, courseId, e);return AjaxResult.error("支付失败,请稍后再试");}
}

Service

@Override
public JSONObject wxPay(Long personId, String miniIdStr, Double payAmount, String couponIdStr, Long courseId) {// 根据personId获取公众号openIdString openId = wxLoginMapper.selectOpenIdByPersonId(personId);// 生成商品订单号String orderNumber = UUID.randomUUID().toString().replace("-", "");// TODO 查询购买课程信息// 返回前端数据集合JSONObject payMap = new JSONObject();// TODO 校验优惠券与购买课程是否相符(不符返回支付失败)// TODO 查询优惠券信息BigDecimal totalVal = BigDecimal.ZERO;// TODO 计算订单金额 填充生成本地订单数据(存入本地数据库的订单)Double total = totalVal.doubleValue();// 校验订单金额if (!total.equals(payAmount)) {log.error("订单金额不符,personId:{},miniIdStr:{},payAmount:{},couponIdStr:{},total:{}", personId, miniIdStr,payAmount, couponIdStr, total);payMap.put("status", 0);return payMap;}// 支付金额大于0时调用微信支付if (total > 0) {// 拼接统一下单参数JSONObject params = new JSONObject();params.put("appid", WxConstants.OFFICIAL_APPID);params.put("mchid", WxConstants.PAY_MCH_ID);params.put("description", "课程购买");params.put("notify_url", "http://yq.zdxueqian.com/office/wxpay/payNotify");params.put("out_trade_no", orderNumber);// 商品金额JSONObject amount = new JSONObject();amount.put("total", totalVal.multiply(new BigDecimal(100)).intValue());log.info("订单金额:" + amount.get("total"));params.put("amount", amount);// 支付者JSONObject payer = new JSONObject();payer.put("openid", openId);params.put("payer", payer);// 发送微信预支付订单请求String prepayId = WxPayUtils.sendPay(params);if (StringUtils.isBlank(prepayId)) {// 生成预支付订单id失败(发送微信下单请求)log.error("生成预支付订单id失败,params:{}", params);payMap.put("status", 0);return payMap;}// 填充返回前端数据 注:返回前端的timeStamp与nonceStr字段必须与生成签名的一致 否则前端验签失败payMap.put("appId", WxConstants.OFFICIAL_APPID);long timestamp = System.currentTimeMillis() / 1000;payMap.put("timeStamp", timestamp + "");String nonceStr = WxPayUtils.generateNonceStr();payMap.put("nonceStr", nonceStr);payMap.put("package", "prepay_id=" + prepayId);payMap.put("signType", "RSA");// 生成请求签名String paySign = WxPayUtils.getPaySign(nonceStr, timestamp, payMap.getString("package"));payMap.put("paySign", paySign);} else {// 订单金额为0时返回status为1(按支付成功处理)payMap.put("status", 1);}// TODO 添加本地数据库订单信息return payMap;
}

WxPayUtils工具类

package com.yueqian.common.utils.wxpay;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Random;import javax.servlet.http.HttpServletRequest;import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.springframework.core.io.ClassPathResource;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
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 com.yueqian.common.constant.WxConstants;import lombok.extern.slf4j.Slf4j;/*** 微信支付工具类* * @author suihao* @create 2020-10-22 16:51*/
@Slf4j
public class WxPayUtils {/*** 发送微信预支付订单请求* * @param params* @return* @throws IOException*/public static String sendPay(JSONObject params) {try {// 读取证书私钥PrivateKey privateKey = PemUtil.loadPrivateKey(new ClassPathResource("apiclient_key.pem").getInputStream());// 不需要传入微信支付证书,将会自动更新AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(new WechatPay2Credentials(WxConstants.PAY_MCH_ID,new PrivateKeySigner(WxConstants.PAY_SERIAL_NO, privateKey)),WxConstants.PAY_SECRET_V3.getBytes(StandardCharsets.UTF_8.name()));// 创建http请求HttpClient client = WechatPayHttpClientBuilder.create().withMerchant(WxConstants.PAY_MCH_ID, WxConstants.PAY_SERIAL_NO, privateKey).withValidator(new WechatPay2Validator(verifier)).build();// 配置http请求参数HttpPost post = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");post.setHeader("Content-Type", "application/json");post.setHeader("Accept", "application/json");post.setEntity(new StringEntity(params.toJSONString(), "utf-8"));// 获取请求结果HttpResponse response = client.execute(post);// System.out.println("响应码:" + response.getStatusLine());// System.out.println("响应body体:" + EntityUtils.toString(response.getEntity()));JSONObject jo = JSON.parseObject(EntityUtils.toString(response.getEntity()));// 预支付订单生成失败(微信端订单)if (response.getStatusLine().getStatusCode() != 200) {log.error("发送微信预支付订单请求失败:" + jo.getString("message") + ",params:{}", params);return null;}// 获取预支付订单idreturn jo.getString("prepay_id");} catch (IOException e) {log.error("发送微信预支付订单请求失败,params:{}", params, e);return null;}}/*** 获取调起微信支付签名* * @param nonceStr* @param timestamp* @param prepayId* @return*/public static String getPaySign(String nonceStr, long timestamp, String prepayId) {String message = WxConstants.OFFICIAL_APPID + "\n" + timestamp + "\n" + nonceStr + "\n" + prepayId + "\n";String signature = null;try {signature = sign(message.getBytes(StandardCharsets.UTF_8.name()), "apiclient_key.pem");} catch (UnsupportedEncodingException e) {log.error("生成微信支付签名失败,message:{}", message, e);}return signature;}/*** 读取请求体中数据* * @param request* @return*/public static String readData(HttpServletRequest request) {BufferedReader br = null;try {StringBuilder result = new StringBuilder();String line;for (br = request.getReader(); (line = br.readLine()) != null; result.append(line)) {if (result.length() > 0) {result.append("\n");}}line = result.toString();return line;} catch (IOException e) {throw new RuntimeException(e);} finally {if (br != null) {try {br.close();} catch (IOException e) {log.error("关闭字符输入流失败", e);}}}}/*** 微信支付回调签名验证并对加密请求参数解密** @param serialNo* @param result* @param signatureStr* @param nonce* @param timestamp* @param paySecretV3* @return*/public static String verifyNotify(String serialNo, String result, String signatureStr, String nonce,String timestamp, String paySecretV3) throws Exception {// 读取证书私钥PrivateKey privateKey = PemUtil.loadPrivateKey(new ClassPathResource("apiclient_key.pem").getInputStream());// 获取平台证书AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(new WechatPay2Credentials(WxConstants.PAY_MCH_ID,new PrivateKeySigner(WxConstants.PAY_SERIAL_NO, privateKey)),WxConstants.PAY_SECRET_V3.getBytes(StandardCharsets.UTF_8.name()));X509Certificate certificate = verifier.getValidCertificate();// 证书序列号String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();// 证书验证if (serialNumber.equals(serialNo)) {// 构造验签名串String buildSignMessage = timestamp + "\n" + nonce + "\n" + result + "\n";// 获取平台公钥PublicKey publicKey = certificate.getPublicKey();Signature signature = Signature.getInstance("SHA256WithRSA");signature.initVerify(publicKey);signature.update(buildSignMessage.getBytes(StandardCharsets.UTF_8));boolean verifySignature =signature.verify(Base64.getDecoder().decode(signatureStr.getBytes(StandardCharsets.UTF_8)));if (verifySignature) {JSONObject resultObject = JSON.parseObject(result);JSONObject resource = resultObject.getJSONObject("resource");String cipherText = resource.getString("ciphertext");String nonceStr = resource.getString("nonce");String associatedData = resource.getString("associated_data");AesUtil aesUtil = new AesUtil(paySecretV3.getBytes(StandardCharsets.UTF_8));return aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonceStr.getBytes(StandardCharsets.UTF_8), cipherText);}}return null;}/** 对字节数据进行私钥签名(加密) */private static String sign(byte[] message, String serialPath) {try {// 签名方式(固定SHA256withRSA)Signature sign = Signature.getInstance("SHA256withRSA");// 使用私钥进行初始化签名(私钥需要从私钥文件【证书】中读取)InputStream inputStream = new ClassPathResource(serialPath).getInputStream();sign.initSign(PemUtil.loadPrivateKey(inputStream));// 签名更新sign.update(message);// 对签名结果进行Base64编码return Base64.getEncoder().encodeToString(sign.sign());} catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException | IOException e) {log.error("微信支付进行私钥签名失败,message:{}", message, e);}return null;}/*** 获取随机字符串 Nonce Str* 随机字符从symbols获取* SecureRandom真随机数* * @return String 随机字符串*/public static String generateNonceStr() {String symbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";Random random = new SecureRandom();char[] nonceChars = new char[32];for (int index = 0; index < nonceChars.length; ++index) {nonceChars[index] = symbols.charAt(random.nextInt(symbols.length()));}return new String(nonceChars);}}

3.2 用户支付成功回调

@ApiOperation("微信公众号支付回调")
@RequestMapping("/payNotify")
public void payNotify(HttpServletRequest request, HttpServletResponse response) {JSONObject resObj = new JSONObject();String plainText = null;try {// 获取回调请求头String timestamp = request.getHeader("Wechatpay-Timestamp");String nonce = request.getHeader("Wechatpay-Nonce");String serialNo = request.getHeader("Wechatpay-Serial");String signature = request.getHeader("Wechatpay-Signature");// 获取回调支付密文String result = WxPayUtils.readData(request);// 验证签名并对请求参数解密plainText =WxPayUtils.verifyNotify(serialNo, result, signature, nonce, timestamp, WxConstants.PAY_SECRET_V3);if (StringUtils.isNotBlank(plainText)) {JSONObject payResult = JSON.parseObject(plainText);// TODO 用户支付成功业务处理(更改本地订单支付状态、添加购买记录、删除购物车内对应商品)// 注: 业务处理应先查本地数据库订单支付状态 未处理时再进行后面业务处理 微信支付可能会多次调用回调接口response.setStatus(200);resObj.put("code", "SUCCESS");resObj.put("message", "SUCCESS");} else {log.warn("签名校验失败");response.setStatus(500);resObj.put("code", "ERROR");resObj.put("message", "签名错误");}// 响应请求结果response.setHeader("Content-type", "application/json");response.getOutputStream().write(resObj.toJSONString().getBytes(StandardCharsets.UTF_8));response.flushBuffer();} catch (Exception e) {log.error("微信支付回调接口处理失败,plainText:{}", plainText, e);}
}

第一次写博文,去掉了业务处理部分,尽量使其简化,写的不好多见谅。

JSAPI微信公众号apiV3文档支付相关推荐

  1. 微信公众号推文发布方法(内涵详细步骤)

      今天又朋友专门发信息咨询微信公众号推文怎么发布以及维护方法,在大不分熟悉微信公众号发文流程的同学看来,其实是非常简单的:但是对于没有接触过这一方面的其他同学来讲,也是一件非常苦恼的事情,没有头绪. ...

  2. 微信公众号推文内可以添加附件了吗?

    微信公众号推文内可以添加附件了吗?答案是:是的. 我们都知道创建一个微信公众号,在公众号中发布一些文章是非常简单的,但公众号添加附件下载的功能却被限制,如今可以使用小程序"微附件" ...

  3. 微信公众号推文发布方法(内含详细步骤)

      今天有朋友专门发信息咨询微信公众号推文怎么发布以及维护方法,在大部分熟悉微信公众号发文流程的同学看来,其实是非常简单的:但是对于没有接触过这一方面的其他同学来讲,也是一件非常苦恼的事情,没有头绪. ...

  4. [新手入门]微信公众号推文制作

    随着互联网的发展,自媒体行业越来越火,很多人纷纷开通了自己的公众号,想要赶上这个热潮,但是在制作微信公众号的推文时,遇到了各种各样的问题,比如:文章的字体该怎么选取?字号.字体颜色该怎么设置?段落格式 ...

  5. 微信公众号官方文档入口

    微信公众号官方文档入口 微信公众号开发github

  6. 提高微信公众号推文阅读数小妙招

    考核微信小编KPI主要指标之一就是公众号推文阅读数.但现在实际情况是,运营一个公众号不是那么轻松,公众号推文阅读数也是寥寥无几.这里我们小编告诉你一个小秘密,提高微信公众号推文阅读数小妙招就用来逗编辑 ...

  7. 微信|公众平台开发者文档

    微信|公众平台开发者文档 http://mp.weixin.qq.com/wiki/3/ecfed6e1a0a03b5f35e5efac98e864b7.html 爱父母项目(提示用户名,密码在项目文 ...

  8. 如何使用Markdown排版微信公众号推文

    如何使用Markdown排版微信公众号推文 应用场景: 熟悉使用CSDN博客的我,想把CSDN上面的博客迁移到微信公众号上面,但是把文本拷贝过来再重新调整样式有太花时间了,像我这种对排版没有太多追求的 ...

  9. vue 在微信公众号里使用支付宝支付(h5支付宝支付)

    vue 在微信公众号里使用支付宝支付(h5支付宝支付) 需求:在微信公众号里面支持支付宝支付. 思路:微信是不能直接调用支付宝的,所以我们需要使用一个中间页提示用户在浏览器中打开,然后进行支付宝网页或 ...

  10. 数学建模学习1.20——b站公开视频与微信公众号推文

    b站视频学习--[零基础教程]老哥:数学建模算法.编程.写作和获奖指南全流程培训!21.10&21.11 数学建模题型划分,常用算法分类及其适用场景 数学建模常见的赛题类型 数学建模常见的赛题 ...

最新文章

  1. Rabbitmq 消息对列 生产者与消费者的具体实现 springboot
  2. 用border-width,border-color画三角形
  3. .net API跨域
  4. python画统计图怎么在右上角表示哪条线代表什么_Python-matplotlib统计图之箱线图漫谈...
  5. Android4.1 新功能 新特性(转)
  6. python 与别的程序通信_《Python》进程之间的通信(IPC)、进程之间的数据共享、进程池...
  7. 浅谈RSocket与响应式编程
  8. 资金时间价值的计算机应用视频讲解,第八章资金时间价值与方案经济比选20161018讲解.ppt...
  9. python经典书籍推荐:Python核心编程
  10. Java EE体系概述
  11. 安装Win10,ERROR_0x8007025D问题解决
  12. 6.MongoDB之索引
  13. 虚拟机ip映射到外网
  14. 时间序列分析中的增长率——同比与环比
  15. Vue3中点击箭头切换图片
  16. 拒绝丧偶式育儿,正确「养育男孩」
  17. bat一键清理系统垃圾/系统日志
  18. shiro身份认证(HelloWorld)
  19. unity3d发布webgl手机测试流程
  20. 梯度提升回归树(GBDT)

热门文章

  1. 自动跳动滑动门html,jQuery 滑动门自动滑动实现代码
  2. Node.js的安装下载和运行JS代码和常用命令和按键
  3. 数学概率之z=x+y和z=x-y和z=x/y的分布
  4. FFmpeg —— MP4文件提取h264文件
  5. vfp中写入文本文件_VFP中操作多种文件
  6. 提供SCDN基础版本售卖
  7. Sql server 中的bulk insert语句使用
  8. *printf()格式化串安全漏洞分析(上)
  9. SingBoot集成JPA时单元测试报错:could not initialize proxy - no Session
  10. 基于单片机烟雾温湿度甲醛监测设计