这里使用的是springboot框架进行测试,比较方便简单,代码仅供参考
原理:java 的前后端通信
注意 :1、域名一般需要备案:如果没有备案可能会被拦截,无法正常应答验签(申请域名的平台都会提供备案的攻略,按照攻略正常备案即可)
2、一般无法本地测试(也是就是个人pc电脑),因为没有固定的ip地址,需要申请购买一个服务器后,有固定的对外ip(记得把对应默认端口后80打开,不然会被拦截),把对应的ip映射到对应的域名后(映射其实就是绑定的意思,在申请到域名后设置一下,比较简单),看看外网能不能ping通,如果无法ping通就说明没有映射成功,或者映射的ip不是外网可以访问
pox文件:
4.0.0org.springframework.bootspring-boot-starter-parent2.4.3 cn.hzldemo0.0.1-SNAPSHOTjardemoDemo project for Spring Boot1.82.9.2org.springframework.bootspring-boot-starterorg.springframework.bootspring-boot-starter-testtest
org.springframework.bootspring-boot-starter-web
com.github.wechatpay-apiv3wechatpay-apache-httpclient0.2.3org.projectlomboklombokcom.squareup.okhttp3okhttp3.14.9org.springframeworkspring-tx5.3.1io.springfoxspringfox-swagger2${swagger2.version}io.springfoxspringfox-swagger-ui${swagger2.version}com.alibabafastjson1.2.60
com.github.wechatpay-apiv3wechatpay-apache-httpclient0.2.3
org.springframework.bootspring-boot-maven-plugin
启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
配置文件:(application.yml)只配置了默认的端口号
server:
port: 80
回调主要代码:
import com.alibaba.fastjson.JSON;
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.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import io.swagger.annotations.ApiOperation;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.cert.X509Certificate;
import java.security.*;
import java.util.*;
@RestController
@RequestMapping("/weixin/pay")
public class WeiXinPayController {
//商户api证书私钥文件的私钥串,apiclient_key.pem文本形式打开即可获取,不需要前后缀
static String s ="商户api证书私钥文件的私钥串";
//验签方法,非微信官方的,微信支付官方的基本也查不到,封装的方式不一样,下面也有
public boolean verify(String srcData, X509Certificate certificate, String sign, String Serial) throws Exception {
//先验证证书序列号是否正确
if (certificate.getSerialNumber().toString(16).toUpperCase().equals(Serial)){
Signature sha256withRSA = Signature.getInstance("SHA256withRSA");
sha256withRSA.initVerify(certificate.getPublicKey());
sha256withRSA.update(srcData.getBytes());
return sha256withRSA.verify(Base64Utils.decodeFromString(sign));
}else {
return false;
* 微信支付回调地址
@ApiOperation(value = "微信支付回调地址")
@PostMapping(value = "/callback/test")//域名后面的路径,可以根据自己的喜好或者业务需求设置,前面是域名,不能携带端口号,比如https://www.baidu.com/weixin/pay/callback/test, https://www.baidu.com找到你的服务器,/weixin/pay/callback/test服务器的哪个方法进行处理
* request 请求体
* response 响应体
public String callBack(HttpServletRequest request, HttpServletResponse response) throws IOException, GeneralSecurityException {
String characterEncoding = request.getCharacterEncoding();
System.out.println("characterEncoding=" + characterEncoding);
//从请求头获取验签字段
String Timestamp = request.getHeader("Wechatpay-Timestamp");
String Nonce = request.getHeader("Wechatpay-Nonce");
String Signature = request.getHeader("Wechatpay-Signature");
String Serial = request.getHeader("Wechatpay-Serial");
System.out.println("开始读取请求头的信息");
//请求头
System.out.println("Wechatpay-Timestamp=" + Timestamp);
System.out.println("Wechatpay-Nonce=" + Nonce);
System.out.println("Wechatpay-Signature=" + Signature);
System.out.println("Wechatpay-Serial=" + Serial);
System.out.println("=================");
//加载平台证书,官方的sdk,s为商户api证书私钥
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(s.getBytes("utf-8")));
//加载官方自动更新证书
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
//商户平台查看 //不是API密钥
new WechatPay2Credentials("商户号", new PrivateKeySigner("商户api证书序列号", merchantPrivateKey)), "APIv3密钥".getBytes("utf-8"));
//读取请求体的信息
System.out.println("开始读取请求体的信息");
ServletInputStream inputStream = request.getInputStream();
StringBuffer stringBuffer = new StringBuffer();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s;
//读取回调请求体
while ((s = bufferedReader.readLine()) != null) {
stringBuffer.append(s);
String s1 = stringBuffer.toString();
System.out.println("请求体" + s1);
Map requestMap = (Map) JSON.parse(s1);
//开始按照验签进行拼接
String id = requestMap.get("id");
System.out.println("id=" + id);
String resource = String.valueOf(requestMap.get("resource"));
System.out.println("resource=" + resource);
Map requestMap2 = (Map) JSON.parse(resource);
String associated_data = requestMap2.get("associated_data");
String nonce = requestMap2.get("nonce");
String ciphertext = requestMap2.get("ciphertext");
//按照文档要求拼接验签串
String VerifySignature = Timestamp + "\n" + Nonce + "\n" + s1 + "\n";
System.out.println("拼接后的验签串=" + VerifySignature);
//使用官方验签工具进行验签
boolean verify1 = verifier.verify(Serial, VerifySignature.getBytes(), Signature);
System.out.println("官方工具验签=" + verify1);
//使用自己写的验签方法进行验签
//赋一个默认值
boolean verify = false;
try {
verify = verify(VerifySignature, verifier.getValidCertificate(), Signature, Serial);
} catch (Exception e) {
e.printStackTrace();
System.out.println("验签方法进行验签=" + verify);
//判断验签的结果
System.out.println("=======判断验签结果=======");
if (verify == false) {
System.out.println("验签失败,应答接口");
Map map = new HashMap<>();
//响应接口
//设置状态码
response.setStatus(500);
return "{"+
'"'+ "code"+'"'+":"+'"'+"FAIL"+'"'+" "+
'"'+ "message"+'"'+":"+'"'+"失败"+'"'+
"}";
System.out.println("验签成功后,开始进行解密");
//解密,如果这里报错,就一定是APIv3密钥错误
AesUtil aesUtil = new AesUtil("APIv3密钥".getBytes());
String aes = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
System.out.println("解密后=" + aes);
Map map = new HashMap<>();
//响应接口
map.put("code", "SUCCESS");
map.put("message", "成功");
//设置状态码
response.setStatus(200);
return "{"+
'"'+ "code"+'"'+":"+'"'+"SUCCESS"+'"'+" "+
'"'+ "message"+'"'+":"+'"'+"成功"+'"'+
"}";