<dependency>
<groupId>com.github.wechatpay-apiv3groupId>
<artifactId>wechatpay-apache-httpclientartifactId>
<version>0.4.9version>
dependency>
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
public class HttpUtils {
/**
* 将通知参数转化为字符串
* @param request
* @return
*/
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum WxTradeState {
/**
* 支付成功
*/
SUCCESS("SUCCESS"),
/**
* 未支付
*/
NOTPAY("NOTPAY"),
/**
* 已关闭
*/
CLOSED("CLOSED"),
/**
* 转入退款
*/
REFUND("REFUND");
/**
* 类型
*/
private final String type;
}
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
/**
* @author xy-peng
*/
public class WechatPay2ValidatorForRequest {
protected static final Logger log = LoggerFactory.getLogger(WechatPay2ValidatorForRequest.class);
/**
* 应答超时时间,单位为分钟
*/
protected static final long RESPONSE_EXPIRED_MINUTES = 5;
protected final Verifier verifier;
protected final String requestId;
protected final String body;
public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {
this.verifier = verifier;
this.requestId = requestId;
this.body = body;
}
protected static IllegalArgumentException parameterError(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("parameter error: " + message);
}
protected static IllegalArgumentException verifyFail(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("signature verify fail: " + message);
}
public final boolean validate(HttpServletRequest request) throws IOException {
try {
//处理请求参数
validateParameters(request);
//构造验签名串
String message = buildMessage(request);
String serial = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
//验签
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
serial, message, signature, requestId);
}
} catch (IllegalArgumentException e) {
log.warn(e.getMessage());
return false;
}
return true;
}
protected final void validateParameters(HttpServletRequest request) {
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
String header = null;
for (String headerName : headers) {
header = request.getHeader(headerName);
if (header == null) {
throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
}
}
//判断请求是否过期
String timestampStr = header;
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
}
} catch (DateTimeException | NumberFormatException e) {
throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
}
}
protected final String buildMessage(HttpServletRequest request) throws IOException {
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
}
}
签名所需要的类
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
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.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
@Configuration
@PropertySource("classpath:wxpay.properties")
@Slf4j
public class WxPayConfig {
@Resource
private Environment config;
/**
* 获取签名验证器
* @return
*/
@Bean
public Verifier getVerifier() throws IOException, GeneralSecurityException, HttpCodeException, NotFoundException {
log.info("获取签名验证器");
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(config.getProperty("wxpay.mch-id"), new WechatPay2Credentials(config.getProperty("wxpay.mch-id"),
new PrivateKeySigner(config.getProperty("wxpay.mch-serial-no"), getPrivateKey())), config.getProperty("wxpay.api-v3-key").getBytes(StandardCharsets.UTF_8));
// ... 若有多个商户号,可继续调用putMerchant添加商户信息
// 从证书管理器中获取verifier
Verifier verifier = certificatesManager.getVerifier(config.getProperty("wxpay.mch-id"));
return verifier;
}
/**
* 获取http请求对象
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(Verifier verifier) throws IOException {
log.info("获取httpClient");
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(config.getProperty("wxpay.mch-id"), config.getProperty("wxpay.mch-serial-no"), getPrivateKey())
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
@Bean
public PrivateKey getPrivateKey() throws IOException {
// 加载商户私钥(privateKey:私钥字符串)
log.info("开始加载私钥,读取内容...");
String content = new String(Files.readAllBytes(Paths.get(config.getProperty("wxpay.private-key-path"))),StandardCharsets.UTF_8 );
System.out.println(content);
return PemUtil.loadPrivateKey(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
}
/**
* 获取无需签名验证http请求对象
* @param verifier
* @return
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient(Verifier verifier) throws IOException {
log.info("获取httpClient");
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(config.getProperty("wxpay.mch-id"), config.getProperty("wxpay.mch-serial-no"), getPrivateKey())
.withValidator((response) -> true);
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
}
wxpay.properties
# 微信支付相关参数
# 商户号
wxpay.mch-id=
# 商户API证书序列号
wxpay.mch-serial-no=
# 商户私钥文件
wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=
# APPID
wxpay.appid=
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置(我用小蝴蝶内网穿透)
wxpay.notify-domain=
通知报文解密方法
private String decryptFromResource(HashMap<String,Object> bodyMap) throws GeneralSecurityException {
log.info("解密报文");
Map<String,String> resource = (Map)bodyMap.get("resource");
//数据密文
String ciphertext = resource.get("ciphertext");
//原始类型
String originalType = resource.get("original_type");
//随机串
String nonce = resource.get("nonce");
//附加数据
String associatedData = resource.get("associated_data");
log.info("密文===>{}", ciphertext);
AesUtil aesUtil = new AesUtil(config.getProperty("wxpay.api-v3-key").getBytes(StandardCharsets.UTF_8));
String decryptToString = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
log.info("明文===>{}", decryptToString);
return decryptToString;
}
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/payment_demo?serverTimezone=GMT%2B8&characterEncoding=utf-8", "root", "123456")
.globalConfig(builder -> {
builder.author("xjq") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D:\\ideaProjects\\myProjects\\my-payment\\src\\main\\java"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.example.mypayment") // 设置父包名
.moduleName("") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D:\\ideaProjects\\myProjects\\my-payment\\src\\main\\java\\com\\example\\mypayment\\mapper\\xml")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_order_info","t_payment_info","t_product","t_refund_info") // 设置需要生成的表名
.addTablePrefix("t_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
代码生成需要的依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.2version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.5.2version>
dependency>
<dependency>
<groupId>org.freemarkergroupId>
<artifactId>freemarkerartifactId>
dependency>
/**
* 秒 分 时 日 月 周
* 以秒为例
* *:每秒都执行
* 1-3:从第1秒开始执行,到第3秒结束执行
* 0/3:从第0秒开始,每隔3秒执行1次
* 1,2,3:在指定的第1、2、3秒执行
* ?:不指定
* 日和周不能同时制定,指定其中之一,则另一个设置为?
*/
//@Scheduled(cron = "0/3 * * * * ?") 要想生效记得在启动类加上@EnableScheduling还有自己所在的类必须@Component交给spring管理
安装
npm install vue-qriously --save-dev
import Vue from 'vue'
import VueQriously from 'vue-qriously'
Vue.use(VueQriously)
在vue里使用
methods: {
//下载账单:微信支付
downloadBill(type){
//获取账单内容
billApi.downloadBillWxPay(this.billDate, type).then(response => {
console.log(response)
const element = document.createElement('a')
element.setAttribute('href', 'data:application/vnd.ms-excel;charset=utf-8,' + encodeURIComponent(response.data.result))
element.setAttribute('download', this.billDate + '-' + type)
element.style.display = 'none'
element.click()
})
},
//下载账单:支付宝
downloadBillAliPay(type){
billApi.downloadBillAliPay(this.billDate_alipay, type).then(response => {
console.log(response.data.downloadUrl)
const element = document.createElement('a')
element.setAttribute('href', response.data.downloadUrl)
element.setAttribute('download', this.billDate_alipay + '-' + type)
element.style.display = 'none'
element.click()
})
}
}
微信支付官网网址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/index.shtml