微信扫码支付用的是apiv3接口,点击查看微信扫码支付官方文档
- 编写微信支付封装实体类
/**
* 微信平台证书VO
* @author shenlu
* @date 2020/12/21 16:14
*/
@Data
public class Certificate {
/**
* 平台证书序列号
*/
private String serial_no;
/**
* 平台证书有效时间
*/
private String effective_time;
/**
* 平台证书过期时间
*/
private String expire_time;
/**
* 加密证书
*/
private EncryptCertificate encrypt_certificate;
/**
* 加密证书
*/
@Data
public class EncryptCertificate {
/**
* 算法
*/
private String algorithm;
/**
* 随机字符串
*/
private String nonce;
/**
* 相关数据
*/
private String associated_data;
/**
* 密文
*/
private String ciphertext;
}
}
/**
* 微信通知数据解密后对象
* @author shenlu
* @date 2020/12/18 11:17
*/
@Data
public class NotifyResource {
/**
* 公众号ID
*/
private String appid;
/**
* 直连商户号
*/
private String mchid;
/**
* 商户订单号
*/
private String out_trade_no;
/**
* 微信支付订单号
*/
private String transaction_id;
/**
* 交易类型
*/
private String trade_type;
/**
* 交易状态
*/
private String trade_state;
/**
* 交易状态描述
*/
private String trade_state_desc;
/**
* 付款银行
*/
private String bank_type;
/**
* 支付完成时间
*/
private String success_time;
/**
* 支付者
*/
private Payer payer;
/**
* 订单金额
*/
private Amount amount;
/**
* 支付者
*/
@Data
public class Payer{
/**
* 用户标识
*/
private String openid;
}
/**
* 订单金额
*/
@Data
public class Amount{
/**
* 总金额
*/
private Integer total;
/**
* 用户支付金额
*/
private Integer payer_total;
/**
* 货币类型
*/
private String currency;
/**
* 用户支付币种
*/
private String payer_currency;
}
}
/**
* 接收微信支付通知VO
* @author shenlu
* @date 2020/12/18 11:08
*/
@Data
public class PayNotify {
/**
* 通知的唯一ID
*/
private String id;
/**
* 通知创建时间
*/
private String create_time;
/**
* 通知类型 支付成功通知的类型为TRANSACTION.SUCCESS
*/
private String event_type;
/**
* 通知数据类型 支付成功通知为encrypt-resource
*/
private String resource_type;
/**
* 通知资源数据
*/
private Resource resource;
/**
* 回调摘要
*/
private String summary;
/**
* 通知资源数据
*/
@Data
public class Resource{
/**
* 加密算法类型
*/
private String algorithm;
/**
* 数据密文
*/
private String ciphertext;
/**
* 附加数据
*/
private String associated_data;
/**
* 随机串
*/
private String nonce;
}
}
- 编写微信配置类,获取httpclient对象
@Configuration
public class WeChatConfig {
private String mchId = "xxxxxxxxxx";//商户号
private String mchSerialNo = "xxxxxxx";//证书序列号
private String apiV3Key = "xxxxxxxxx";//apiv3密钥
private String filePath = "/usr/local/wechat/1605438448_20210607_cert/apiclient_key.pem";//证书私钥地址
@Bean
public PrivateKey privateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
String content = new String(Files.readAllBytes(Paths.get(filePath)), "utf-8");
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey merchantPrivateKey = kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
return merchantPrivateKey;
}
@Bean
public CloseableHttpClient closeableHttpClient() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, ParseException, CertificateException, URISyntaxException {
//不需要传入微信支付证书了
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, privateKey())),
apiV3Key.getBytes("utf-8"));
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey())
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
}
- 编写工具类,便于生成二维码和关闭订单
@Component
public class PayUtils {
private static String apiV3Key = "xxxxxxxxxxx";
private static String mchId = "xxxxxxxxxx";//商户号
// 定义全局容器 保存微信平台证书公钥 注意线程安全
public static Map<String, X509Certificate> certificateMap = new ConcurrentHashMap<>();
@Autowired
private CloseableHttpClient httpClient;
public String getQRCode(int price, String orderNumber){
try {
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
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", "xxxxxxxx")
.put("appid", "xxxxxxxxxxx")
.put("description", "\n" +
"xxxxxxxxxxxx")
.put("notify_url", "http://xxxxxxxxxxxxxx")
.put("out_trade_no", orderNumber);
rootNode.putObject("amount")
.put("total", price)
.put("currency", "CNY");
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode==200){
String var = EntityUtils.toString(response.getEntity());
JSONObject json = JSONObject.parseObject(var);
return json.getString("code_url");
}else {
System.out.println("生成二维码失败:"+statusCode+",错误信息为:"+EntityUtils.toString(response.getEntity()));
return null;
}
} catch (JsonGenerationException e) {
e.printStackTrace();
return null;
} catch (ClientProtocolException e) {
e.printStackTrace();
return null;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
} catch (JsonMappingException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public CloseableHttpResponse closeOrder(String orderNumber){
try {
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+orderNumber+"/close");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
//请求body参数
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid", mchId);
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
return response;
}catch (JsonGenerationException e) {
e.printStackTrace();
return null;
} catch (ClientProtocolException e) {
e.printStackTrace();
return null;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
} catch (JsonMappingException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public CloseableHttpResponse sendPost(String url,ByteArrayOutputStream bos) throws IOException {
System.out.println(httpClient);
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
System.out.println(url);
System.out.println(bos);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
System.out.println("设置参数成功");
CloseableHttpResponse response = httpClient.execute(httpPost);
System.out.println(response);
return response;
}
/**
* 用微信V3密钥解密响应体.
*
* @param associatedData response.body.data[i].encrypt_certificate.associated_data
* @param nonce response.body.data[i].encrypt_certificate.nonce
* @param ciphertext response.body.data[i].encrypt_certificate.ciphertext
* @return the string
* @throws GeneralSecurityException the general security exception
*/
public String decryptResponseBody(String associatedData, String nonce, String ciphertext) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
byte[] bytes;
try {
bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException(e);
}
return new String(bytes, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
public CloseableHttpResponse sendGet(String url,ByteArrayOutputStream bos) throws IOException, URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(url);
uriBuilder.setParameter("mchid", mchId);
//完成签名并执行请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
return response;
}
/**
* 获取平台证书Map
* @return
* @throws ParseException
* @throws IllegalAccessException
* @throws NoSuchAlgorithmException
* @throws IOException
* @throws InstantiationException
* @throws SignatureException
* @throws InvalidKeyException
* @throws CertificateException
*/
public Map<String, X509Certificate> refreshCertificate() throws ParseException, CertificateException, IOException, URISyntaxException {
//获取平台证书json
CloseableHttpResponse response = sendGet("https://api.mch.weixin.qq.com/v3/certificates", null);
JSONObject jsonObject = JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
List<Certificate> certificateList = JSON.parseArray(jsonObject.getString("data"), Certificate.class);
//最新证书响应实体类
Certificate newestCertificate = null;
//最新时间
Date newestTime = null;
for (Certificate certificate:certificateList){
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
//如果最新时间是null
if (newestTime == null){
newestCertificate = certificate;
//设置最新启用时间
newestTime = formatter.parse(certificate.getEffective_time());
}else{
Date effectiveTime = formatter.parse(certificate.getEffective_time());
//如果启用时间大于最新时间
if (effectiveTime.getTime() > newestTime.getTime()){
//更换最新证书响应实体类
newestCertificate = certificate;
}
}
}
Certificate.EncryptCertificate encryptCertificate = newestCertificate.getEncrypt_certificate();
//获取证书字公钥
String publicKey = decryptResponseBody(encryptCertificate.getAssociated_data(),encryptCertificate.getNonce(),encryptCertificate.getCiphertext());
CertificateFactory cf = CertificateFactory.getInstance("X509");
//获取证书
ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));
X509Certificate certificate = null;
try {
certificate = (X509Certificate) cf.generateCertificate(inputStream);
} catch (CertificateException e) {
e.printStackTrace();
}
//保存微信平台证书公钥
Map<String, X509Certificate> certificateMap = new ConcurrentHashMap<>();
// 清理HashMap
certificateMap.clear();
// 放入证书
certificateMap.put(newestCertificate.getSerial_no(), certificate);
return certificateMap;
}
/**
* 验证微信签名
* @param request
* @param body
* @return
* @throws GeneralSecurityException
* @throws IOException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws ParseException
*/
public boolean verifiedSign(HttpServletRequest request,String body) throws GeneralSecurityException, ParseException, IOException, URISyntaxException {
//微信返回的证书序列号
String serialNo = request.getHeader("Wechatpay-Serial");
//微信返回的随机字符串
String nonceStr = request.getHeader("Wechatpay-Nonce");
//微信返回的时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
//微信返回的签名
String wechatSign = request.getHeader("Wechatpay-Signature");
//组装签名字符串
String signStr = Stream.of(timestamp, nonceStr, body)
.collect(Collectors.joining("\n", "", "\n"));
//当证书容器为空 或者 响应提供的证书序列号不在容器中时 就应该刷新了
if (certificateMap.isEmpty() || !certificateMap.containsKey(serialNo)) {
certificateMap = refreshCertificate();
}
//根据序列号获取平台证书
X509Certificate certificate = certificateMap.get(serialNo);
//获取失败 验证失败
if (certificate == null){
return false;
}
//SHA256withRSA签名
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(certificate);
signature.update(signStr.getBytes());
//返回验签结果
return signature.verify(Base64Utils.decodeFromString(wechatSign));
}
}
- 发送请求
@RestController
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/payment")
public class PaymentController {
@Autowired
private IOrderService orderService;
@Autowired
private IVideoService videoService;
@Autowired
private PayUtils payUtils;
@Autowired
private CloseableHttpClient httpClient;
@PostMapping("/closeOrder")
//https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}/close
public JSONObject closeOrder(@RequestBody String param) throws IOException {
JSONObject json = JSONObject.parseObject(param);
if (json == null) {
json.put("success", 0);
return json;
}
String orderNumber = json.getString("orderNumber");
json.clear();
if (orderNumber == null) {
json.put("success", 0);
return json;
}
CloseableHttpResponse response = payUtils.closeOrder(orderNumber);
if (response == null) {
System.out.println("response为空");
json.put("success", 0);
return json;
}
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("关闭订单成功:" + EntityUtils.toString(response.getEntity()));
QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
orderQueryWrapper.eq("order_number", orderNumber);
Order dbOrder = orderService.getOne(orderQueryWrapper);
dbOrder.setIsPay(-1);
orderService.updateById(dbOrder);
json.put("success", 1);
return json;
} else if (statusCode == 204) {
QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
orderQueryWrapper.eq("order_number", orderNumber);
Order dbOrder = orderService.getOne(orderQueryWrapper);
dbOrder.setIsPay(-1);
orderService.updateById(dbOrder);
json.put("success", 1);
return json;
} else {
json.put("success", 1);
System.out.println("关闭订单失败,状态码:" + statusCode + "错误信息为:" + EntityUtils.toString(response.getEntity()));
return json;
}
}
public String[] queryIsHaveOrder(String memberId, Integer videoId) {
QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
orderQueryWrapper.eq("user_id", memberId).eq("video_id", videoId);
Order dbOrder = orderService.getOne(orderQueryWrapper);
if (dbOrder != null) {
if (dbOrder.getIsPay() == -1) {
String orderNumber = UUID.randomUUID().toString().replaceAll("-","");
float price = dbOrder.getPrice();
String code = payUtils.getQRCode((int) (price*100),orderNumber);
String[] strs = new String[2];
strs[0] = orderNumber;
if (code == null) {
strs[1] = "error";
} else {
dbOrder.setCreateTime(new Date());
dbOrder.setIsPay(0);
dbOrder.setOrderNumber(orderNumber);
orderService.updateById(dbOrder);
strs[1] = code;
}
return strs;
} else {
String orderNumber = dbOrder.getOrderNumber();
float price = dbOrder.getPrice();
String code = payUtils.getQRCode((int) (price * 100), orderNumber);
String[] strs = new String[2];
strs[0] = orderNumber;
if (code == null) {
strs[1] = "error";
} else {
strs[1] = code;
}
return strs;
}
}
return null;
}
@PostMapping("/getQRCode")
public JSONObject getQRCode(@RequestBody String param, HttpServletRequest request) throws IOException {
JSONObject json = JSONObject.parseObject(param);
Integer videoId = json.getInteger("videoId");
json.clear();
String memberId = JwtUtils.getMemberIdByJwtToken(request);
if (memberId == null || memberId.equals("")) {
json.put("success", 0);
return json;
}
String[] var = queryIsHaveOrder(memberId, videoId);
if (var != null) {
if (var[1].equals("error")) {
json.put("success", 0);
return json;
} else {
json.put("codeUrl", var[1]);
json.put("orderNumber", var[0]);
json.put("success", 1);
return json;
}
}
Video video = videoService.getById(videoId);
if (video == null) {
json.put("success", 0);
return json;
}
String orderNumber = UUID.randomUUID().toString().replaceAll("-", "");
float price = video.getPrice();
String code = payUtils.getQRCode((int) (price * 100), orderNumber);
if (code != null) {
json.put("codeUrl", code);
//写入数据库
Order order = new Order();
order.setUserId(memberId);
order.setVideoId(videoId);
order.setPrice(price);
order.setOrderNumber(orderNumber);
order.setIsPay(0);
order.setCreateTime(new Date());
boolean b = orderService.save(order);
System.out.println("保存数据库:" + b);
json.put("orderNumber", orderNumber);
json.put("success", 1);
return json;
} else {
json.put("success", 0);
}
return json;
}
//供微信调用的接口
@PostMapping("/wechatNotify")
public JSONObject wechatNotify(@RequestBody String param, HttpServletRequest request) throws ParseException, GeneralSecurityException, IOException, URISyntaxException {
JSONObject json = new JSONObject();
//如果验证签名序列号通过
if (payUtils.verifiedSign(request, param)) {
System.out.println("验证签名成功");
//微信支付通知实体类
PayNotify payNotify = JSONObject.parseObject(param, PayNotify.class);
//如果支付成功
if ("TRANSACTION.SUCCESS".equals(payNotify.getEvent_type())) {
//通知资源数据
PayNotify.Resource resource = payNotify.getResource();
//解密后资源数据
String notifyResourceStr = payUtils.decryptResponseBody(resource.getAssociated_data(), resource.getNonce(), resource.getCiphertext());
//通知资源数据对象
NotifyResource notifyResource = JSONObject.parseObject(notifyResourceStr, NotifyResource.class);
String orderNumber = notifyResource.getOut_trade_no();
String trade_state = notifyResource.getTrade_state();
String payTime = notifyResource.getSuccess_time();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date date = formatter.parse(payTime);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
date = sdf.parse(sdf.format(date));
int payer_total = notifyResource.getAmount().getPayer_total();
QueryWrapper<Order> accountQueryWrapper = new QueryWrapper<>();
accountQueryWrapper.eq("order_number", orderNumber);
Order order = orderService.getOne(accountQueryWrapper);
if (order != null) {
if (payer_total == order.getPrice() * 100) {
//如果订单状态是提交状态
if (order.getIsPay() == 0) {
//如果付款成功
if ("SUCCESS".equals(trade_state)) {
order.setPayTime(date);
order.setIsPay(1);
orderService.updateById(order);
} else {
//修改失败参数
}
}
} else {
System.out.println("订单被修改!");
}
} else {
System.out.println("微信返回支付错误摘要:" + payNotify.getSummary());
}
System.out.println(payNotify);
System.out.println(notifyResource);
//通知微信正常接收到消息,否则微信会轮询该接口
json.put("code", "SUCCESS");
json.put("message", "");
return json;
}
}
System.out.println("验证签名失败");
return json;
}
//前端不断发请求查询订单是否成功
@PostMapping("/getPayResult")
public JSONObject getPayResult(@RequestBody String param) {
JSONObject json = JSONObject.parseObject(param);
if (json == null) {
json.clear();
json.put("success", 0);
return json;
}
String orderNumber = json.getString("orderNumber");
json.clear();
if (orderNumber == null) {
json.put("success", 0);
return json;
}
QueryWrapper<Order> accountQueryWrapper = new QueryWrapper<>();
accountQueryWrapper.eq("order_number", orderNumber);
Order order = orderService.getOne(accountQueryWrapper);
if (order.getIsPay() == 1) {
json.put("success", 1);
return json;
}
json.put("success", 0);
return json;
}
}