表现层
package com.fy.staffapi.app.controller;
import com.fy.common.protocol.JsonResult;
import com.fy.staffapi.service.ITransactionsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
/**
* 微信交易
*/
@Slf4j
@RequestMapping("transactions")
@RestController
public class TransactionsController {
@Autowired
private ITransactionsService transactionsService;
/**
* 生成收费链接
* @param money 金额 以元为单位
* @param description 描述
* @param outTradeNo 订单号
* @return 收费链接
*/
@GetMapping("generatePaymentQRCode")
public JsonResult generatePaymentQrCode(String money, String description, String outTradeNo) {
try {
String data = transactionsService.generatePaymentQrCode(money, description, outTradeNo);
return JsonResult.success().data(data).build();
} catch (IOException e) {
e.printStackTrace();
log.error("微信收费链接生成失败,IO异常:{}", e.getMessage());
return JsonResult.failure().build();
}
}
}
事务层
package com.fy.staffapi.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fy.common.okhttp.OkHttp;
import com.fy.staffapi.service.ITransactionsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Base64;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class ITransactionsServiceImpl implements ITransactionsService {
/**
* Native下单API 请求URL
*/
@Value("${wechat.collection.transactions-url}")
private String transactionsUrl;
/**
* 应用ID
*/
@Value("${wechat.collection.appid}")
private String appid;
/**
* 直连商户号
*/
@Value("${wechat.collection.mchid}")
private String mchid;
/**
* 证书序列号
*/
@Value("${wechat.collection.serial-no}")
private String serialNo;
/**
* 回调接口路径
*/
@Value("${wechat.collection.call-back-url}")
private String callBackUrl;
/**
* 密钥
*/
@Value("${wechat.cert-file.pwd}")
private String key;
/**
* 密钥
*/
@Value("${wechat.cert-file.file-name}")
private String fileName;
/**
* 网络请求工具类
*/
@Autowired
@Qualifier("encryptionOkHttp")
private OkHttp encryptionOkHttp;
/**
* json转换工具类
*/
@Autowired
private ObjectMapper objectMapper;
@Value("${wechat.cert-file.file-name:}")
private String certFileName;
/**
* 生成收款二维码
*
* @param money 金额
* @return 二维码链接
*/
@Override
public String generatePaymentQrCode(String money, String description, String outTradeNo) throws IOException {
ObjectNode rootNode = objectMapper.createObjectNode();
// 直连商户号
rootNode.put("mchid", mchid)
// 应用ID
.put("appid", appid)
// 商品描述
.put("description", description)
// 通知地址
.put("notify_url", callBackUrl)
// 商户订单号
.put("out_trade_no", outTradeNo);
// 订单金额 把元改成以分为单位
BigDecimal multiply = new BigDecimal(money).multiply(new BigDecimal(100));
rootNode.putObject("amount")
// 总金额
.put("total", multiply.intValue()).put("currency", "CNY");
String jsonParam = objectMapper.writeValueAsString(rootNode);
// 头文件
Map<String, String> header = initHeader(mchid, jsonParam);
String result = encryptionOkHttp.postJson(transactionsUrl, jsonParam, header);
if (!result.contains("code_url")) {
log.error("微信收费链接生成失败,记录日志:{}", result);
}
return result;
}
/**
* 生成头文件,请求形式,签名等信息
*
* @param mchid 商户号
* @param json post请求body体里的数据
* @return
*/
private Map<String, String> initHeader(String mchid, String json) {
// 当前毫秒值
long now = System.currentTimeMillis() / 1000;
// 32位随机数
String random = RandomStringUtils.randomAlphanumeric(32);
// 返回值初始化
Map<String, String> header = new HashMap<>(16);
header.put("Accept", "application/json");
header.put("Content-type", "application/json; charset=utf-8");
// 签名
String sign = createSign(now, json, random);
String append = "WECHATPAY2-SHA256-RSA2048 " +
"mchid=\"" + mchid + "\","
+ "nonce_str=\"" + random + "\","
+ "signature=\"" + sign + "\","
+ "timestamp=\"" + now + "\","
+ "serial_no=\"" + serialNo + "\"";
header.put("Authorization", append);
return header;
}
/**
* 生成sign
*
* @param now 当前毫秒值
* @param jsonParam 请求数据
* @param random 随机数
* @return 签名
*/
private String createSign(long now, String jsonParam, String random) {
String str = // 请求方式
"POST" + "\n" +
// 去掉域名的方法路径
transactionsUrl.substring(transactionsUrl.indexOf(".com") + 4) + "\n" +
// 系统当前时间戳
now + "\n" +
// 生成一个请求随机串
random + "\n" +
jsonParam + "\n";
return sign(str, key);
}
/**
* 签名 加密
*
* @param data 加密数据
* @param privateKeyPwd Key
* @return 加密后的签名
*/
private String sign(String data, String privateKeyPwd) {
if (data == null || privateKeyPwd == null) {
return "";
}
try {
// 获取证书的私钥
PrivateKey key = readPrivate(privateKeyPwd);
// 进行签名服务
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(key);
signature.update(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(signature.sign());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
log.error("创建签名实例失败,加密算法异常:{}", e.getMessage());
} catch (SignatureException | InvalidKeyException e) {
e.printStackTrace();
log.error("创建签名实例失败,签名生成异常:{}", e.getMessage());
}
return "";
}
/**
* 读取私钥
*
* @param privateKeyPwd 密钥
* @return 证书
*/
private PrivateKey readPrivate(String privateKeyPwd) {
if (privateKeyPwd == null) {
return null;
}
try {
// 获取JKS 服务器私有证书的私钥,取得标准的JKS的 KeyStore实例
KeyStore store = KeyStore.getInstance("JKS");
ClassPathResource cl = new ClassPathResource(fileName);
// jks文件密码,根据实际情况修改
store.load(cl.getInputStream(), privateKeyPwd.toCharArray());
// 获取jks证书别名
Enumeration<String> en = store.aliases();
String pName = null;
while (en.hasMoreElements()) {
String n = en.nextElement();
if (store.isKeyEntry(n)) {
pName = n;
}
}
// 获取证书的私钥
return (PrivateKey) store.getKey(pName,
privateKeyPwd.toCharArray());
} catch (IOException e) {
e.printStackTrace();
log.error("读取证书文件失败,IO异常:{}", e.getMessage());
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
log.error("读取证书文件失败,打开证书的Key不正确:{}", e.getMessage());
} catch (KeyStoreException e) {
e.printStackTrace();
log.error("读取证书内容失败,证书打开异常:{}", e.getMessage());
} catch (CertificateException e) {
e.printStackTrace();
log.error("读取证书文件失败,证书异常:{}", e.getMessage());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
log.error("读取证书文件失败,算法异常:{}", e.getMessage());
}
return null;
}
/**
* 实例化encryptionOkHttp 生成
* 含证书请求
*
* @return
*/
@Bean("encryptionOkHttp")
public OkHttp encryptionOkHttp() throws Exception {
OkHttp okHttp = new OkHttp();
ClassPathResource cl = new ClassPathResource(certFileName);
InputStream inputStream = cl.getInputStream();
byte[] bytes = IOUtils.toByteArray(inputStream);
okHttp.buildWithCert(bytes, mchid);
return okHttp;
}
}
需要把证书放到resource下,其他还有很多需要用的比如商户信息等功能,需要到微信端查询