评论

微信支付v3接口 回调+回调验签 java示例代码

微信支付v3接口回调接口java示例代码

这里使用的是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"+'"'+":"+'"'+"成功"+'"'+
                   "}";
    }
}


最后一次编辑于  2022-05-27  
点赞 1
收藏
评论

15 个评论

  • 及時行樂
    及時行樂
    2023-02-28

    官方工具总是验签失败咋回事啊?

    请求头里的四个参数和拼接的验签串打印出来都有数据,格式也都没问题

    日志提示验签失败

    2023-02-28
    赞同
    回复 1
    • HOPE
      HOPE
      2023-03-02
      你把证书打印出来,然后跟单独下的证书对比一下,要注意,使用的是微信支付平台证书
      2023-03-02
      回复
  • @@
    @@
    2022-11-24

    java.lang.IllegalArgumentException: 无效的ApiV3Key,长度必须为32个字节

    这个密钥是apiv3密钥不是吗

    我的是用的這個

    AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
            //商户平台查看                            //不是API密钥
            new WechatPay2Credentials(wxPayConfig.getMerchantId(),
                    new PrivateKeySigner(wxPayConfig.getMerchantSerialNumber(), merchantPrivateKey)),
            wxPayConfig.getPrivateKey().getBytes("utf-8"));
    
    2022-11-24
    赞同
    回复 6
    • @@
      @@
      2022-11-25
      搞定了搞定了
      2022-11-25
      回复
    • ㅤ
      2023-04-07回复@@
      兄弟,你怎么解决的
      2023-04-07
      回复
    • ㅤ
      2023-04-07回复
      好吧,我也解决了。。。。。。。。。。。。。
      2023-04-07
      回复
    • 官方
      官方
      2023-04-22回复
      怎么解决的,兄弟
      2023-04-22
      回复
    • 官方
      官方
      2023-04-22回复官方
      我也解决了……,后面的兄弟注意把这个地方改了
      2023-04-22
      回复
    查看更多(1)
  • Print
    Print
    2022-10-26

    最后aes就是解密之后的报文了吗,就像jsapi下单之后的详细信息了吗

    2022-10-26
    赞同
    回复 1
    • HOPE
      HOPE
      2022-10-27
      是的
      2022-10-27
      回复
  • Xiao_hu
    Xiao_hu
    2022-05-26
      //加载平台证书,官方的sdk,s为
              PrivateKey merchantPrivateKey = PemUtil
                      .loadPrivateKey(new ByteArrayInputStream(s.getBytes("utf-8")));
    



    s是啥?

    2022-05-26
    赞同
    回复 1
    • HOPE
      HOPE
      2022-05-27
      商户API证书私钥内容
      2022-05-27
      回复
  • 凉快
    凉快
    2021-12-21

    请问一下,“商户api证书序列号”怎么获取?

    2021-12-21
    赞同
    回复

正在加载...

登录 后发表内容