一.所需参数及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 = this
this.$api.request({
url: '/wxpay/pay',
method: 'post',
params: params
}).then(response => {
// 拉起微信支付参数赋值
that.payMap = response.data
if (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.payMap
let that = this
WeixinJSBridge.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获取公众号openId
String 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;
}
// 获取预支付订单id
return 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);
}
}
第一次写博文,去掉了业务处理部分,尽量使其简化,写的不好多见谅。
版权声明:本文为weixin_45847916原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。