场景:PC端点击下单后,生成二维码返回给前端,用户扫码支付
直接上代码
主要依赖
<!-- 微信开发工具 -->
<dependency>
<groupId>com.github.liyiorg</groupId>
<artifactId>weixin-popular</artifactId>
<version>2.8.16</version>
</dependency>
<dependency>
<groupId>com.yungouos.pay</groupId>
<artifactId>yungouos-pay-sdk</artifactId>
<version>1.1.19</version>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.1</version>
</dependency>
MyConfig类是微信的一些配置类,大家可以用自己的写法
/**
*获取二维码
*/
public PreOrderResult getQRCode(Product product) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
//微信以分为单位,所以要转换一下
Double i = Double.parseDouble(product.getTotalFee()) * 100;
int totaltoint = new Double(i).intValue();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("appid", MyConfig.getAppID())
.put("mchid", MyConfig.getMchID())
.put("description", product.getSubject())
.put("out_trade_no", product.getOutTradeNo());
rootNode.putObject("amount")
.put("total", totaltoint)
.put("currency", "CNY");
// 异步通知地址
rootNode.put("notify_url", MyConfig.getNotifyUrlV3());
CloseableHttpResponse response = null;
try {
//生成封装请求头
String Post = myConfigV3.getToken(myConfigV3.getPOST(), HttpUrl.parse(myConfigV3.getPcPAYurl()),
MyConfig.getMchID(), MyConfig.getSerialno(), MyConfig.getPrivateKey(), rootNode.toString());
//请求微信端服务
response = WeChatV3Util.WeChatPostNative(myConfigV3.getPcPAYurl(), rootNode, Post);
if (response == null) {
log.info("请求失败!");
}
int statusCode = response.getStatusLine().getStatusCode();
HttpEntity entity1 = response.getEntity();
if (statusCode == 200) {
HttpEntity entity = response.getEntity();
String boye = EntityUtils.toString(entity);
JSONObject jsonObject = JSONObject.fromObject(boye);
String code_url = (String) jsonObject.get("code_url");
return new PreOrderResult(true, "成功获取二维码", code_url, product.getOutTradeNo());
// return new AsyncResult<String>(boye);
} else if (statusCode == 204) {
log.info("支付请求发送成功: 返回代码:+statusCode+ 返回的信息:" + EntityUtils.toString(response.getEntity()));
return null;
} else {
log.info("支付请求发送失败: 错误代码:" + statusCode + " 返回的信息: " + EntityUtils.toString(response.getEntity()));
return new PreOrderResult(false, "获取二维码失败", null, product.getOutTradeNo());
}
} catch (Exception e) {
e.printStackTrace();
return new PreOrderResult(false, "接口异常失败", null, product.getOutTradeNo());
} finally {
if (response != null) {
response.close();
}
}
}
总体的逻辑代码如上,接下来看看关键的几个方法
package com.pay.modules.wxpay.util;
import com.pay.common.util.CommonConstant;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import okhttp3.HttpUrl;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.ParseException;
import java.util.Base64;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
@Data
@Component
public class MyConfigV3 {
static final int TAG_LENGTH_BIT = 128;
/**微信Native支付请求*/
private String PcPAYurl = "https://api.mch.weixin.qq.com/v3/pay/transactions/native";
private String query ="https://api.mch.weixin.qq.com/v3/pay/transactions/id/out_trade_no?mchid=Mchid";
/**请求类型*/
private String POST="POST";
/**
* 生成组装请求头
*
* @param method 请求方式
* @param url 请求地址
* @param mercId 商户ID
* @param serial_no 证书序列号
* @param privateKeyFilePath 私钥路径
* @param body 请求体
* @return 组装请求的数据
* @throws Exception
*/
public String getToken(String method, HttpUrl url, String mercId, String serial_no, String privateKeyFilePath, String body) throws Exception {
String nonceStr = UUID.randomUUID().toString().replace("-", "");
long timestamp = System.currentTimeMillis() / 1000;
String message = buildMessage(method, url, timestamp, nonceStr, body);
System.out.println("明文: "+message);
String signature = sign(message.getBytes("UTF-8"), privateKeyFilePath);
System.out.println("生成的签名:"+signature);
return "mchid=\"" + mercId + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + serial_no + "\","
+ "signature=\"" + signature + "\"";
}
public String sign(byte[] message ,String privateKeyFilePath) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(getPrivateKey(privateKeyFilePath));
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
public String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
String canonicalUrl = url.encodedPath();
if (url.encodedQuery() != null) {
canonicalUrl += "?" + url.encodedQuery();
}
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
/**
* 获取私钥。
*
* @param filename 私钥文件路径 (required)
* @return 私钥对象
*/
public PrivateKey getPrivateKey(String filename) throws IOException {
String s="";
String configContentStr = "";
try {
InputStream is = this.getClass().getResourceAsStream(filename);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
while((s=br.readLine())!=null) {
configContentStr = configContentStr+s;
}
String privateKey = configContentStr.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
public static X509Certificate getCertificate(InputStream fis) throws IOException {
BufferedInputStream bis = new BufferedInputStream(fis);
try {
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
cert.checkValidity();
return cert;
} catch (CertificateExpiredException e) {
throw new RuntimeException("证书已过期", e);
} catch (CertificateNotYetValidException e) {
throw new RuntimeException("证书尚未生效", e);
} catch (CertificateException e) {
throw new RuntimeException("无效的证书文件", e);
} finally {
bis.close();
}
}
}
//
回调方法,如果用户支付成功后,微信服务端会以起请求到我们的后台(前面填的异步通知地址),具体方法如下,这里会有一个验签操作,校验是不是微信端发来的,防止恶意请求。
/**
* Native支付 微信支付成功回调 ojj
* 通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m
* @param response
* @param request
* @throws Exception
*/
@ApiOperation(value = "微信支付成功回调")
@PostMapping("/notifyurl")
public void notifyurl(HttpServletResponse response, HttpServletRequest request) throws Exception {
//微信返回的证书序列号
String serialNo = request.getHeader("Wechatpay-Serial");
//微信返回的随机字符串
String nonceStr = request.getHeader("Wechatpay-Nonce");
//微信返回的时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
//微信返回的签名
String wechatSign = request.getHeader("Wechatpay-Signature");
Map<String, String> map = new HashMap<>(12);
try{
String result = WeChatV3Util.readData(request);
log.info("回调的信息" +result);
if (WeChatV3Util.verifiedSign(request,result)){
//验证通过,做你的业务
}else {
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "签名错误");
}
}catch (Exception e){
e.printStackTrace();
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
版权声明:本文为weixin_46164384原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。