Java接入微信支付ApiV3详细教程

一.继承支付

微信支付官网:Native支付产品介绍-文档中心-微信支付商户平台

github官网:GitHub - wechatpay-apiv3/wechatpay-apache-httpclient: 微信支付 APIv3 Apache HttpClient装饰器(decorator)

基础支付APIv3

1.使用我们的这个微信支付接口的这些需要准备的信息(由于我没有去申请商户信息),我们可以使用properties的文件来实现具体信息的读取

在这里需要注意的是接受结果通知地址是不一样的,因为我们到后期需要执行内网穿透

# 微信支付相关参数
# 商户号
wxpay.mch-id=1558950191
# 商户API证书序列号
wxpay.mch-serial-no=34345964330B66427E0D3D28826C4993C77E631F
# 商户私钥文件
wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B
# APPID
wxpay.appid=wx74862e0dfcf69954
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
wxpay.notify-domain=https://7d92-115-171-63-135.ngrok.io

在Springboot中如果出现了一下错误我们可以在pom文件中添加一个依赖即可解决,这个错误的信息就是我的这个Springboot信息无法加载配置文件

注意我们需要把这个这个apiclient_key.pem的这个文件放入到我们的这个项目的跟目录当中

pem文件在网盘中

链接:https://pan.baidu.com/s/13RgVEXn-jwgyJ1_Z9Th9BQ?pwd=n93p 
提取码:n93p

如果出现一下错误我们需要映入一个maven依赖即可

    <!--自定义原数据信息-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
<!--        使用谷歌的gson来实现json的传递-->
 <dependency>
       <groupId>com.google.code.gson</groupId>
       <artifactId>gson</artifactId>
  </dependency>

 然后配置我们的这个工具类


import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
//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.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;


@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {

    // 商户号
    private String mchId;

    // 商户API证书序列号
    private String mchSerialNo;

    // 商户私钥文件
    private String privateKeyPath;

    // APIv3密钥
    private String apiV3Key;

    // APPID
    private String appid;

    // 微信服务器地址
    private String domain;

    // 接收结果通知地址
    private String notifyDomain;

    // APIv2密钥
    private String partnerKey;

    /**
     * 获取商户的私钥文件
     * @param filename
     * @return
     */
    private PrivateKey getPrivateKey(String filename){
        try {
            return PemUtil.loadPrivateKey(new FileInputStream(filename));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在", e);
        }
    }

    /**
     * 用来获取我的这个微信的Http请求
     */
    @Bean(name = "WXPayClient")
    public CloseableHttpClient getWXPayClient(ScheduledUpdateCertificatesVerifier verifier){
        //获取商户密钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);
// 从证书管理器中获取verifier

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();
        return httpClient;//返回我们当这个http请求
    }
    /**
     * 获取我的这个签名验证器
     * @return
     */
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier(){
        //获取商户密钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);
        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
        //用来实现省份认证
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        // 获取证书管理器实例
        ScheduledUpdateCertificatesVerifier instance = new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                apiV3Key.getBytes(StandardCharsets.UTF_8)
        );
        return instance;
    }
    /**
     * 获取我的这个签名验证器
     * 跳过签名验证器
     * @return
     */
    @Bean(name = "wxPayNoSignClient")
    public CloseableHttpClient wxPayNoSignClient(){
        log.info("签名验证器");
        //获取商户密钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                //验证商户Id和商户APIId和商户秘钥
                .withMerchant(mchId, mchSerialNo, privateKey)
                //通过(response)->true实现一个空的应答
                .withValidator((response) -> true);
        return builder.build();//返回我的这个http的请求地址
    }
}

然后我们在controller中编写一个控制器,通过浏览器去访问我们的这个支付账户信息,测试成功

如果说感觉我们的这个proper的这个文件这个样子我们也可以把它编程一个springboot配置文件的小叶子,同时我们可以在idea中去自定义我们的这个Springboot的配置文件 

 

然后去选择我们的自定义的这个配置文件即可

2.引入我们的这个商户信息的文件,放到我们的这个跟路径下面

我们可以通过这个网址去查看

GitHub - wechatpay-apiv3/wechatpay-apache-httpclient: 微信支付 APIv3 Apache HttpClient装饰器(decorator)

导入我们的微信支付的相关依赖

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.3.0</version>
</dependency>

然后我们现需要把我们的这个获取私钥文件的方法放入到我们的那个微信支付的配置类当中,代码如上

其中我们的这个service层调用我们的这个微信支付的接口如下

/**
*得到我的这个微信支付的链接
*/
@Autowired
private CloseableHttpClient wxClient;
/**
*得到我们上面刚刚编写的微信支付工具类
*/
@Autowired
private WxPayConfig  wxPayConfig;
/**
 * 生成二维码接口,同意调用API
 * @param productId 商品编号
 * @return Code_Url 返回我的二维码信息
 */
@Override
public Map<String, Object> nativePay(Long productId) throws IOException {
    log.info("生成订单");
    //准备订单的消息
    OrderInfo orderInfo = new OrderInfo();//实例化我的这个order对象
    orderInfo.setTitle("test");
    orderInfo.setOrderNo(OrderNoUtils.getOrderNo());//获得这个订单编号
    orderInfo.setProductId(productId);//设置我的这个商品编号
    orderInfo.setTotalFee(1);//设置我的这个订单支付的金额,默认单位是一分钱
    orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());//在我的这个order的订单这里去添加我的这个订单状态
    //将订单存储到数据库
    HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));//用来调用我的这个二维码请求地址
    // 请求body参数
    Gson gson = new Gson();
    Map paramMap = new HashMap();//用来 定义一个集合存储我的这个调用微信支付接口的信息,他的这些参数在官网上有所指定
    paramMap.put("appid",wxPayConfig.getAppid());//用来获得我的这个AppId
    paramMap.put("mchid",wxPayConfig.getMchId());//有来获取我的商户Id
    paramMap.put("description",orderInfo.getTitle());//设置我的这个商户描述
    paramMap.put("out_trade_no",orderInfo.getOrderNo());//设置订单编号
    paramMap.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));//得到我们的这个同时地址

    Map<String, Object> amountMap = new HashMap<>();//因为我们在官网当中他的这个订单是单独列出来的,所以我们需要重新定义一个集合存入到我的这个Map中
    amountMap.put("total",orderInfo.getTotalFee());
    amountMap.put("currency","CNY");//设置这个货币类型
    paramMap.put("amount",amountMap);//把我的这个订单的集合存入到我的这个集合当中
    //将参数转换成字符串类型
    String jsonParams = gson.toJson(paramMap);//把我们的这个订单货币信息转换成json格式
    log.info("请求参数"+jsonParams);

    StringEntity entity = new StringEntity(jsonParams,"utf-8");//把我们的这个请求参数放到我们的这个entity的这个对象当中,并且更改我的这个编码
    entity.setContentType("application/json");//内容类型
    httpPost.setEntity(entity);//把这个对象放到这个post请求中
    httpPost.setHeader("Accept", "application/json");
    //完成签名并执行请求
    CloseableHttpResponse response = wxClient.execute(httpPost);
    try {
        String bodyAsString = EntityUtils.toString(response.getEntity());//获得我们当前的这个响应体
        int statusCode = response.getStatusLine().getStatusCode();//获得响应状态码
        if (statusCode == 200) { //处理成功
            log.info("成功,返回的结果是 = " +bodyAsString);
        } else if (statusCode == 204) { //处理成功,无返回Body
            log.info("成功,未返回结果");
        } else {
            log.info("下单失败,响应码 = " + statusCode+ ",返回结果 = " +bodyAsString);
            throw new IOException("request failed");
        }
        //响应我当前的这个相关的结果
        Map<String,String> hashMap = gson.fromJson(bodyAsString, HashMap.class);
        //解析二维码
        String codeUrl = hashMap.get("code_url");//从这个HashMap集合当中得到我的这个二维码信息
        //把我的这个二维码的信息响应给前端
        Map<String, Object> map = new HashMap<>();
        map.put("codeUrl",codeUrl);//返回我的这个二维码相关信息
        map.put("OrderNo",orderInfo.getOrderNo());//得到我的这个订单编号
        return map;//返回我的这个相关信息
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            response.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return null;
}

二维码的前端代码的实现:主要是通过qriously的这个插件来得到我的这个二维码的Url地址然后去生成二维码,前端代码如下

   <!-- 微信支付二维码 -->
    <el-dialog
      :visible.sync="codeDialogVisible"
      :show-close="false"
      @close="closeDialog"
      width="350px"
      center>
      <!-- qriously这个是用来生成二维码的代码
      这个codeUrl是我定义的变量,主要是接受后端传入的微信支付链接
        -->
     <qriously :value="codeUrl" :size="300"/>
        <!-- <img src="../assets/img/code.png" alt="" style="width:100%"><br> -->
        使用微信扫码支付
    </el-dialog>
<script>
 //确认支付
    toPayV2(){
      //禁用按钮,防止重复提交
      this.payBtnDisabled = true

      //微信支付
      if(this.payOrder.payType === 'wxpay'){
          //调用统一下单接口
        wxPayApi.nativePayV2(this.payOrder.productId).then(response => {
          this.codeUrl = response.data.codeUrl
          this.orderNo = response.data.orderNo
          
          //打开二维码弹窗
          this.codeDialogVisible = true

          //启动定时器
          this.timer = setInterval(() => {
            //查询订单是否支付成功
            this.queryOrderStatus()
          }, 3000)

        })
      }
    }
</script>

二.实现调用微信接口的底层实现方法

1>生成我们的这个请求头部

这个方法的底层主要是通过SignatureExec的这个类当中去调用我的这个getToken

主要是通过传入Authorization来传入这个addHeader的这个对象当中

源码如下:

private CloseableHttpResponse executeWithSignature(HttpRoute route, HttpRequestWrapper request,
        HttpClientContext context,
        HttpExecutionAware execAware) throws IOException, HttpException {
    // 上传类不需要消耗两次故不做转换
    if (!(request.getOriginal() instanceof WechatPayUploadHttpPost)) {
        convertToRepeatableRequestEntity(request);
    }
    // 添加认证信息,主要是通过AUTHORIZATION来传入进行授权,按照HTTp请求去发送
    request.addHeader(AUTHORIZATION, credentials.getSchema() + " " + credentials.getToken(request));

    // 执行
    CloseableHttpResponse response = mainExec.execute(route, request, context, execAware);

    // 对成功应答验签
    StatusLine statusLine = response.getStatusLine();
    if (statusLine.getStatusCode() >= SC_OK && statusLine.getStatusCode() < SC_MULTIPLE_CHOICES) {
        convertToRepeatableResponseEntity(response);
        if (!validator.validate(response)) {
            throw new HttpException("应答的微信支付签名验证失败");
        }
    }
    return response;
}

在微信支付的这个接口当中主要是通过WechatPay2Credentials类中的token方法来去生成相对应的请求路径,代码如下

@Override
public final String getToken(HttpRequestWrapper request) throws IOException {
    String nonceStr = generateNonceStr();
    long timestamp = generateTimestamp();

    String message = buildMessage(nonceStr, timestamp, request);
    log.debug("authorization message=[{}]", message);

    Signer.SignatureResult signature = signer.sign(message.getBytes(StandardCharsets.UTF_8));

    String token = "mchid=\"" + getMerchantId() + "\","
            + "nonce_str=\"" + nonceStr + "\","
            + "timestamp=\"" + timestamp + "\","
            + "serial_no=\"" + signature.certificateSerialNumber + "\","
            + "signature=\"" + signature.sign + "\"";
    log.debug("authorization token=[{}]", token);

    return token;

然后生成我们的这个Http的签名

2>查看微信支付底层是如何实现获取签名证书的

获取我的这个签名证书的方法他主要是通过CerManagerSiglent的这个类来实现调用我的这个init方法,然后我的这个接口去调用我的这个获取我的这个签名证书的方法,通过initCertificates去调用downloadAndUpdateCert这个方法,我们的这个签名证书主要是在downloadAndUpdateCert来生成的

CerManagerSiglent类中的init方法如下

/**
 * 初始化平台证书管理器实例,在使用前需先调用该方法
 *
 * @param credentials 认证器
 * @param apiV3Key APIv3密钥
 * @param minutesInterval 定时更新间隔时间
 */
public synchronized void init(Credentials credentials, byte[] apiV3Key, long minutesInterval) {
    if (credentials == null || apiV3Key.length == 0 || minutesInterval == 0) {
        throw new IllegalArgumentException("credentials或apiV3Key或minutesInterval为空");
    }
    if (this.credentials == null || this.apiV3Key.length == 0 || this.executor == null
            || this.certificates == null) {
        this.credentials = credentials;
        this.apiV3Key = apiV3Key;
        this.executor = new SafeSingleScheduleExecutor();
        this.certificates = new ConcurrentHashMap<>();

        // 初始化证书
        initCertificates();

        // 启动定时更新证书任务
        Runnable runnable = () -> {
            try {
                Thread.currentThread().setName(SCHEDULE_UPDATE_CERT_THREAD_NAME);
                log.info("Begin update Certificate.Date:{}", Instant.now());
                updateCertificates();
                log.info("Finish update Certificate.Date:{}", Instant.now());
            } catch (Throwable t) {
                log.error("Update Certificate failed", t);
            }
        };
        executor.scheduleAtFixedRate(runnable, 0, minutesInterval, TimeUnit.MINUTES);//这个是来设置我的这个签名认证书的超时时间,默认是六十分钟
    }
}

downloadAndUpdateCert的源码如下:

private synchronized void downloadAndUpdateCert(Verifier verifier) {
    try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
            .withCredentials(credentials)
            .withValidator(verifier == null ? (response) -> true
                    : new WechatPay2Validator(verifier))
            .build()) {
        HttpGet httpGet = new HttpGet(CERT_DOWNLOAD_PATH);
        httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            int statusCode = response.getStatusLine().getStatusCode();
            String body = EntityUtils.toString(response.getEntity());
            if (statusCode == SC_OK) {
                Map<BigInteger, X509Certificate> newCertList = CertSerializeUtil.deserializeToCerts(apiV3Key, body);
                if (newCertList.isEmpty()) {
                    log.warn("Cert list is empty");
                    return;
                }
                certificates.clear();
                certificates.putAll(newCertList);
            } else {
                log.error("Auto update cert failed, statusCode = {}, body = {}", statusCode, body);
            }
        }
    } catch (IOException | GeneralSecurityException e) {
        log.error("Download Certificate failed", e);
    }
}

得到我的这个证书之后他会去定时的调用更新任务,保证我们的这个证书是最新的,默认是六十分钟

3>微信验签的流程

验签的主要的源码是WechatPay2Validator

在这个类中他会去执行我们的这个validate的这个方法,那么在这个方法当中他会去调用我们的这个validateparameters的这个方法,在这个方法中主要是用来验证我们的这个签名是否过期,是否存在等等

validate的源码如下

@Override
public final boolean validate(CloseableHttpResponse response) throws IOException {
    try {
        //调用验证的方法
        validateParameters(response);
            
        String message = buildMessage(response);
        String serial = response.getFirstHeader(WECHAT_PAY_SERIAL).getValue();
        String signature = response.getFirstHeader(WECHAT_PAY_SIGNATURE).getValue();

        if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
            throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
                    serial, message, signature, response.getFirstHeader(REQUEST_ID).getValue());
        }
    } catch (IllegalArgumentException e) {
        log.warn(e.getMessage());
        return false;
    }

    return true;
}

validateParameters的源码如下

protected final void validateParameters(CloseableHttpResponse response) {
    Header firstHeader = response.getFirstHeader(REQUEST_ID);//首先用来获得我的这个签名ID
    if (firstHeader == null) {//判断我的这个ID是否为空
        throw parameterError("empty " + REQUEST_ID);
    }
    String requestId = firstHeader.getValue();

    // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
    //然后把我们 这个微信支付的 微信支付系列,微信支付签名,微信支随机数,此时微信支付时间的时间戳存入到数组中
    String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};

    Header header = null;
    //通过这个数组去循环便利判断是否为空,如果为空的话则会抛出异常,不会显示相关的二维码
    for (String headerName : headers) {
        header = response.getFirstHeader(headerName);
        if (header == null) {
            throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
        }
    }

    String timestampStr = header.getValue();//会的当前的这个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);
    }
}

三.实现内网穿透得到微信端响应的结果

1.为什么需要内网穿透

因为当我们用户是扫描了二维码支付了之后我的这个微信需要返回一些相关的商户信息返回给我们的 这个自己的服务器,所以需要进行内网穿透,微信支付的流程图

内网穿透的官网:ngrok - secure introspectable tunnels to localhost

内网穿透的工具在上面的网盘当中

然后我们设置当前这台电脑允许内网穿透,步骤如下

进入装在这个ngrok的这个exe文件夹下面打开我们的这个cmd命令窗口

然后执行这个命令

ngrok config add-authtoken 28KJJF4kYniqkPI0Kz3nmAk58PU_4MMeQbihq96YpdsbNVkVu

 需要映射的地址

ngrok http 映射的端口号

其次使用的过程

然后就可以得到这个外网的IP

然后我们通过外网IP访问到我的这个接口响应的 信息了

 2.接受微信支付发起的应答

1>验证签名

如果说我在调用微信支付的时候我们在响应微信支付二维码之前我会把我的这个回调的这个路径存入到我的这个Map集合当中进行返回给我的这个微信端,然后当我的这个客户扫描了微信二维码的时候他会去根据我传入的这个路径进入到我的这个请求的路径中

HttpUtils工具类

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();
                }
            }
        }
    }
}

这个主要是根据我们的这个微信提供的这个API的这个WechatPay2Validator类来进行更改的然后我们需要自定义validate的方法,因为他的API默认提供的是respons请求没所以我们在请求响应的时候需要把他更改request请求去进行验签,判断我的这个验证签名是否正确

相关的工具类如下

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;//得到我的这个body对象,来得到我的这个请求体信息

    /**
     * 得到需要验签的参数信息
     * @param verifier 我的验证器
     * @param requestId 我的这个
     * @param 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);
        }
    }

    /**
     * 返回相对应的标签信息
     * @param request
     * @return
     * @throws IOException
     */
    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";//把我的这个body进行校验返回
    }

    protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
    }

}

2>通知参数解密,我们可以通过gson来把我的这个字符串更改为一个HashMap集合,我们 可以在这个集合中得到我需要得到的参数,他的参数key的名称的参考文档:

微信支付-开发者文档

/**`
 * 响应我的 这个明文数据
 * @param bodyMap
 * @return
 */
private String decryptFormatResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
    log.info("密文解密");
    //得到这个解密对象,但是这个对象需要传入我的这个微信的APIMV3的这个参数
    //需要将我的这个数据转换成Butes的数组类型的对象
    AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
    //得到我的这个通知数据
    Map<String,String> resourceMap =(Map<String, String>) bodyMap.get("resource");
    //得到我的这个附加数据
    byte[] associatedData = resourceMap.get("associated_data").getBytes(StandardCharsets.UTF_8);
    log.info("密文数据==>{}",associatedData);
    //得到我的这个随机串
    byte[] nonce = resourceMap.get("nonce").getBytes(StandardCharsets.UTF_8);
    //得到这个密文数据
    String ciphertext = resourceMap.get("ciphertext");
    //这个是我的这个揭秘对象
    String plainText = aesUtil.decryptToString(associatedData, nonce, ciphertext);//得到我的这个明文数据,但是他需要去传递一些相关参数
    log.info("明文数据==>{}",plainText);
    return plainText;//返回我的这个明文信息
}

3.解决微信支付的重复发送通知

在支付的过程当中如果说文档这个系统出现了,超时,那么我们的这个微信支付他会重复的给我们的这个系统发送通知,我们的这个系统会重复的给出响应,这样会损耗我们这个系统,那么该如何解决呢

我们可以在这个方法的前面去定义一个查询数据库的接口,让他每次在后台发送请求的时候进行验证我的这个Mysql中的这个订单数据是否已经支付,如果说我的这个订单已经支付成功了的话那么我们就可以直接返回null,然后我的这个微信平台就不会去重复的调用接口

4.解决并发调用微信支付接口

我们可以在更改支付状态的时候我们可以去加一个Lock,来设置允许只有我一个线程可以进行更改我的这个订单信息

5. 验证我当前用户是否更改这个用户是否已经扫码支付成功

当我们的这个用户在扫码支付成功之后我们可以通过在前端编写一个定时器,来调用我们的这个后台的这个微信顾客是否扫码成功,我们的后台只要是通过前端的定时任务去查询我的这个订单的状态,是否支付成功

6.用户取消订单功能

功能思路分析:

我们的这个取消订单功能的思路是:

1.我们需要去我们数据库当中去修改我当前的这个商品的一个支付状态

2.需要向我们的这个微信支付端去发送一个关单的功能

在这里我们主要是将如何去像微信端去发送关单的功能,需要参考微信支付官网网址:微信支付-开发者文档

申请微信支付关单功能的思路:

1>我们需要准备我们的给微信端发送关单的请求这个请求当中包含了我们的这个微信的订单,这个我们可以定义成一个常量,写一个方法,把订单编号作为我的这个请求路径中的参数

2.我们现需要准备一个Map集合,再这个Map集合存入我的这个商户号,然后我们可以通过使用谷歌旗下的gson来将我的这个map集合转换成一个json,其次我们可以通过微信支付所提供的工具类:wxClien的这个工具类,通过这个工具类去得到我的这个微信给我发送的响应状态

/**
 *执行我的这个微信远程的关单接口
 * @param orderNo
 */
private void closeOrder(String orderNo) throws Exception {
    log.info("实现我的这个关单的接口");
    //创建我的这个远程请求的对象
    String url = wxPayConfig.getDomain()
            //WxApiType.CLOSE_ORDER_BY_NO.getType()这个是我们的这个微信支付的状态,他会临时会进行修改
            .concat(String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(),orderNo));//因为我的这个微信支付的这个变量他里面有个微信支付的关单接口,所以我们要使用format和订单编号来进行拼接
    HttpPost httpPost = new HttpPost(url);
    //组装我们的这个请求路径,并且转换成json格式进行发送
        try {
            Gson gson = new Gson();
            Map <String,String> paramsMap = new HashMap<>();//实例化一个hashMap集合
            //微信官网,关单操作:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_3.shtml
            //根据我们的这个微信支付官网所说的,需要提供我的这个微信支付的商户号存入我们的这恶鬼Map集合当中
            paramsMap.put("mchid",wxPayConfig.getMchId());
            String jsonParams = gson.toJson(paramsMap);//把我们的这个map集合转换成json格式进行返回
            log.info("发送请求信息==>{}",jsonParams);//将我们的这个请求参数进行日志打印
            //将请求参数这知道我们的这个请求对象当中
            StringEntity entity = new StringEntity(jsonParams,"utf-8");//把我们的这个请求参数放到我们的这个entity的这个对象当中,并且更改我的这个编码
            entity.setContentType("application/json");//内容类型
            httpPost.setEntity(entity);//把这个对象放到这个post请求中
            httpPost.setHeader("Accept", "application/json");
            //完成签名并执行请求,向微信进行远程的请求
            CloseableHttpResponse response = wxClient.execute(httpPost);//发送我的相关的请求信息
            int statusCode = response.getStatusLine().getStatusCode();//得到我的微信响应的支付状态码
            if (statusCode == 200) {
                log.info("成功200");
            }else if(statusCode == 204){
                log.info("成功204");
            }else{
                log.info("对不起响应失败,响应码==>{}",statusCode);
             throw new IOException("request filed");//如果响应失败则抛出相关异常信息
            }
        } catch (Exception e) {
            e.printStackTrace();
    }
}

7.服务端给微信端发送订单查询的请求

如果说我的这个服务端迟迟没有得到微信的这个响应,那么我们的这个服务端就需要去给我们的这个微信端根据我的这个订单的编号查询我的这个微信支付端,我当前是否已经支付成功

他的一个解决思路就是说我可以把我们的这个url进行拼接,把url地址和为我们的这个订单编号进行凭借,拼接完之后我们就可以更具我们的这个url去想我们的这个微信端发起相关的请求信息

他的 一个解决思路就是我们可以通过定时任务去查询我的这个订单的状态在我们的这个微信端是怎样的一个状态

1>去查询我们的这个微信端的相关信息

查询我的这个请求状态的接口方法如下:

public String queryOrder(String orderNo) throws IOException{
    log.info("接口查询订单编号=====>{}",orderNo);
String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(),orderNo);//得到我的这个查询订单的常量和我的这个微信的订单编号
url=wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());//通过我的这个微信的商户,给我们的这个参数进行拼接,把我们的这个商户编号
HttpGet httpGet = new HttpGet(url);//因为我是需要获取我的这个请求信息所以需要得到我的这个Http的get请求对象
httpGet.setHeader("Accept","application/json");//设置我当前的这个请求头信息
CloseableHttpResponse response = wxClient.execute(httpGet);//得到我的则个微信请求的接口
//然后通过这个execute的请求参数得到我的这个响应信息
String bodyString = EntityUtils.toString(response.getEntity());//得到我的这个请求的body请求信息
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
    log.info("请求状态码为200");
}else if(statusCode == 204){
    log.info("请求状态码为204");
}else {
    log.info("查询订单失败,响应码 ==>{},响应信息是===>{}",statusCode,bodyString);
    throw new IOException();//抛出异常
}
return bodyString;//返回我的这个响应信息
}

2>通过定时任务去查找我的这个相关信息

定时任务在前面有仔细讲解,那么在这里主要是介绍我们应该如何从我们的那个响应的按个字符串信息去得到我的这个微信服务端发送的支付状态的信息,这个的思路就是通过调用我上面的那个方法去得到返回的字符串,然后通过gson来把这个json字符串去转换成我的这个HashMap集合,最后在这个map集合当中通过tread_state的这个参数去读取我的支付状态的信息

代码如下:

controller:

/**
 * 测试我的这个服务端请求微信端的查单接口
 * 我的这个订单是否存在
 * @param orderNo
 * @return
 */
 @GetMapping("/query/{orderNo}")
public R queryOrder(@PathVariable String orderNo) throws IOException {
   String result = wxPayService.queryOrder(orderNo);//得到我的这个微信的请求参数然后得到我的这个微信端响应的参数
    return R.ok().setMessage(result);//返回我当前的这个微信端发送的请求信息
}

service

/**
 * 根据我的这个订单号查询我的这个微信支付的查单接口
 * 核实订单状态
 * @param orderNo
 */
@Override
public void checkOrderStatus(String orderNo) throws Exception {
    //微信支付的查单接口
    String result = this.queryOrder(orderNo);
    Gson gson = new Gson();//得到我当前这个订单的状态
    HashMap hashMap = gson.fromJson(result, HashMap.class);//得到我的这个支付状态在我的这个微信支付端是什么状态
    Object tread_state = hashMap.get("tread_state");//得到我的这个Map集合当中的状态信息
    if (WxTradeState.SUCCESS.getType().equals(tread_state)) {//判断我当前的这个微信端的请求状态是否和我的这个本地的状态符合一致,如果 不一致则返回错误信息
        log.warn("核实订单已经支付成功=={}",orderNo);
        //如果说我本地的订单状态如果说已经更新成功那么我们就需要去更改我们本地的这个订单状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);//用来调用我们这个修改订单状态的方法
        //记录我的这个支付日志
        paymentInfoService.createPaymentInfo(result);//因为我的这个微信支付平台上面所响应的字段是和我的这个数据库上面的字段是一样的,所以可以直接进行赋值
    }
    //如果我的这个微信支付端如果是未支付状态我们就需要去调用我们的这个关单接口
    if (WxTradeState.NOTPAY.getType().equals(tread_state)){
      log.warn("订单未支付===>{}",orderNo);
      //调用我的这个微信端的这个远程的这个关单接口
        this.closeOrder(orderNo);//执行我的这个远程订单关单的方法
      //更改我的这个数据库当中的这个订单状态,显示我的这个订单是超时关单的状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
    }
}

三.微信支付的退款功能

1.官方网址:微信支付-开发者文档

1.首先我在申请退款功能的时候我需要把我的这个退款表中的一些相关信息存入到我的这个数据库里面,然后存入了之后我需要把我们的这个退款申请信息返回给这个方法的调用者,其次向我的这个微信支付端发送请求信息,更改订单和现在退款表的方法就不进行代码显示了,发送请求的方法信息如下,注意在我们向微信端发送请求的时候需要注意我的这个请求体中的数据要和官网的符合        一致,然后我的这个请求头中类型一定要是setContentType否则会响应失败,抛出异常

/**
 * 申请退款的方法
 * @param orderNo
 * @param reason
 */
@Override
public void refund(String orderNo, String reason) throws IOException {
    //申请订单在这里一共有两个步骤
    // 第一个步骤修改数据库的订单状态,添加退款信息到退款表中
    // 第二申请微信端发送请求进行退款操作
    log.info("更改数据库当中的信息");
    //TODO:修改数据库的退款表
    RefundInfo refundInfo = refundInfoService.createOrefundByOrderNo(orderNo, reason);
    //TODO:向微信端发送请求信息
    String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());//得到我的这个申请退款的url地址,主机的地址拼接我们的这个退款的地址
    Gson gson = new Gson();//得到我的这个转换成json字符串的订单信息
    HttpPost httpPost = new HttpPost(url);//得到我的这个http请求,向微信服务器发送相关的信息
    Map<String, Object>  paraMap = new HashMap<>();//实例化一个集合用来存储我的这个申请退款的订单信息

    paraMap.put("out_trade_no",orderNo);//存入我的这个订单编号
    paraMap.put("out_refund_no",refundInfo.getRefundNo());//商户退款单号
    paraMap.put("reason",reason);//退款原因
    paraMap.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//设置我的这个订单的退款之后回调的url地址

    Map amount = new HashMap();//定义我的这个退款金额的集合
    amount.put("refund",refundInfo.getRefund());//退款金额
    amount.put("total",refundInfo.getTotalFee());//设置我的这个原单金额
    amount.put("currency","CNY");//设置我的这个货币的类型
    paraMap.put("amount",amount);//把金额的集合存入到这个订单集合中
    String jsonParam = gson.toJson(paraMap);//得到我的这个json字符串
    log.info("请求参数===>{}",jsonParam);
    StringEntity entity = new StringEntity(jsonParam, "utf-8");//通过我的这个json字符串转换成Entity格式的信息
    entity.setContentType("application/json");//设置我的这个-报文的类型  注意:我的这个地址 一定要是contentType
    httpPost.setEntity(entity);//把我的这个entity进行返回
    httpPost.setHeader("Accept","application/json");//设置我当前的这个请求头信息
    CloseableHttpResponse response = wxClient.execute(httpPost);//把这个请求信息存放到这个微信响应的这个里面,最终完成签名
    try {

            String bodyAsString = EntityUtils.toString(response.getEntity());//得到我的这个请求返回参数信息没在出现错误的时候我们需要打印相关日志信息
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) {
             log.info("成功!!!响应的结果是200");
        }else if (statusCode == 204) {
             log.info("成功!!!,响应的结果是204");
        }else {
             throw new Exception("退款异常,响应码是:" + statusCode + "响应的信息是:" + bodyAsString);
        }
        //我们向平台发送了申请退款的请求,那么同时也要进行做一定的操作
        //TODO:更新我们的退款单
        orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.REFUND_PROCESSING);//把我当前的这个转台更改这个信息
        //TODO:更新订单状态
        refundInfoService.updateRefund(bodyAsString);
    } catch (Exception e) {
            e.printStackTrace();
    } 
}

然后请求完这个路径之后就需要去我的这个订单表中去更改我的这个订单状态,更改完之后再去更改退款表中的退款表中的信息,那么在更改退款表中的数据时我们需要注意把我们传入的这个bodyAsString使用gson来解析成一个Map集合,然后通过微信支付官网提供的key的名称去读取微信端所响应的相关数据,然后把这个数据存入到我们的这个数据库当中即可,代码如下:

/**
 * 这个可以使用退款通知和我的这个退款申请同事都可以使用我们的这个方法
 * 更新我们的这个退款转态
 * @param bodyAsString
 */ 
public void updateRefund(String bodyAsString) {
    Gson gson = new Gson();//实例化一个json格式转换的工具类
    Map<String,String> resultHashMap = gson.fromJson(bodyAsString, HashMap.class);//先把微信端响应的结果转换成一个HashMap集合

    QueryWrapper<RefundInfo>  RefundWrapper = new QueryWrapper<RefundInfo>()
            .eq("refund_no", resultHashMap.get("out_refund_no"));//根据退款订单得到我的这个queryWrapper对象
    //准备一些需要修改的数据,实例化一个退款的实体类
    RefundInfo refundInfo = new RefundInfo();
    refundInfo.setRefundNo( resultHashMap.get("refund_id"));//微信支付退款单号
     //因为当前的这个方法在回调的时候也会执行这个方法,所以我们先进行相关的判断,一个是申请中的回调和回调中的参数是不一样的

    //查询我的这个退款通知是否为空,因为他们是同一个字段来进行赋值操作
    if (resultHashMap.get("status") != null) {//微信支付的订单状态
        refundInfo.setRefundStatus(resultHashMap.get("status"));
        refundInfo.setContentReturn(bodyAsString);//然后把整个申请退款返回通知的数据参数存入到我的这个数据库当中
    }
    //退款通知是否为空
    if (resultHashMap.get("refund_status") != null) {
        refundInfo.setRefundStatus(resultHashMap.get("refund_status"));
        refundInfo.setContentNotify(bodyAsString);//然后把整个退款结果通知的数据参数存入到我的这个数据库当中
    }
    baseMapper.update(refundInfo,RefundWrapper);//执行修改的操作
}

 四.查询退款的API

参考微信支付官方文档:微信支付-开发者文档

1.查询退款和查询订单的思路是一样的,需要我们使用定时任务去不断的给微信服务端去发送请求,去查询我的这个退款的信息,代码如下

controller

/**
 * 更具我的这个退款的编号去查询相关的商户信息
 * @param refundNo 退款编号
 * @return
 */
@GetMapping("/query-refund/{refundNo}")
public R queryRefund(@PathVariable String refundNo) throws IOException {
    log.info("查询退款订单");
    String result = wxPayService.queryRefund(refundNo);
    log.info("查询出来的订单信息==>{}",result);
    return R.ok().setMessage("查询成功").data("result",result);//返回我的这个退款状态
}

这个service层的方法实现的思路是:首先我需要通过得到我的这个微信端的请求路径,与我的这个微信端的服务器响应地址和我的这个申请查询退款订单状态的url地址使用concat来进行拼接然后实例化出来一个HttpGet的对象出来,并且把url地址进行存入到HttpGet请求中,作为构造方法,因为当时是需要得到微信端的响应信息,所以用get,代码如下:

service层

/**
 * 根据订单编号查询相关的退款信息
 * @param refundNo 订单编号
 * @return
 */
@Override
public String   queryRefund(String refundNo) throws IOException {
    log.info("查询退款信息,退款编号==>{}",refundNo);
    //首先第一步我们需要拼接我们的这个请求的url地址
    String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType());//得到我的这个请求的url地址
    url = wxPayConfig.getDomain().concat(url);//把发送请求的前头和后尾的语句进行拼接形成url地址
    HttpGet httpGet = new HttpGet(url);//得到我的这个http的get请求对象并且把这个url地址拼接进去
    httpGet.setHeader("Accept","application/json");
    CloseableHttpResponse response = wxClient.execute(httpGet);//得到我的这个响应
    try {

        String bodyAsString = EntityUtils.toString(response.getEntity());
        int statusCode = response.getStatusLine().getStatusCode();//得到微信端发送的响应码
        if (statusCode == 200) {
            log.info("响应成功,状态码为==>{}",statusCode);
        }else if (statusCode == 204){
            log.info("响应成功,状态码为==>{}",statusCode);
        }else {
             throw new Exception("查询退款申请失败,状态码为==>"+statusCode+",响应的信息是==>"+bodyAsString);//如果响应失败则抛出异常
        }
        return bodyAsString;
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        response.close();//释放当前只有
    }
    return null;
}

五.退款结果通知

使用退款通知的步骤如下:

首先我可以通过HttpUtils中的readDate方法得到我的这个所响应出来的body字符串,然后我们可以通过gson中的fromJson去吧我的这个body字符串转换成Map集合,得到这个集合之后我们可以通过这个bodyMap集合去得到我的requestId人,然后我们可以通过我们自定义的这个WchatPayValidatorRequest来进行验证我的这个签名如果说验证签名成功就去更改我的这个订单的状态,如果验证不通过则返回我的这个500异常信息

代码如下

/**
 * 处理退款单
 * @param request
 * @param response
 * @return 返回微信端响应的结果信息
 */
@PostMapping("/refunds/notify")
 public String refundNotify(HttpServletRequest request,HttpServletResponse response){
     log.info("得到微信端发送的响应");
     Gson gson = new Gson();//得到转换成json的对象
     Map<String,String> map = new HashMap<>();//实例化一个HashMap集合,用来存储信息
    try {
       String body = HttpUtils.readData(request);//得到我的这个请求参数信息
        HashMap bodyMap = gson.fromJson(body, HashMap.class);//得到用户响应的相关信息
        String requestId = (String) bodyMap.get("requestId");//得到我当前的这个请求Id
        WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
                = new WechatPay2ValidatorForRequest(verifier, requestId, body);//实现签名验证,并且把需要传入的方法放到签名验证器对象中
        if (!wechatPay2ValidatorForRequest.validate(request)) {//调用验证器中的这个验证签名中的方法,判断签名是否正确
            log.error("对不起校验失败");
            response.setStatus(500);//返回我当前的这个响应
            map.put("code","500");//把当前的这个状态码放在这个集合当中进行响应
            map.put("message","失败");//把当前的这个消息进行存入到集合中进行响应
            return gson.toJson(map);//把这个集合转换成map类型进行返回
        }
        wxPayService.reprocessRefund(bodyMap);//用来处理退款单
        //如果响应成功则返回成功的响应码
        map.put("code","200");//把当前的这个状态码放在这个集合当中进行响应
        map.put("message","成功");//把当前的这个消息进行存入到集合中进行响应
        return gson.toJson(map);//把这个集合转换成map类型进行返回
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
 }

service层的代码主要是通过验证退款信息的方法所传入的这个bodyMap集合的话我们需要通过我们自定义的这个解密工具才能够得到我的这个微信端所响应出来的数据,代码如下

/**
 * 用来实现操作退款单的信息
 * @param bodyMap
 */
@Transactional(rollbackFor = Exception.class)//用来开启我的这个事务回滚操作
@Override
public void reprocessRefund(HashMap bodyMap) throws Exception {
    bodyMap.get("退款单");
    String plainText = decryptFormatResource(bodyMap);//因为我们的这个微信响应的报文是加密形式的,解密我当前的这个报文信息
    Gson gson = new Gson();//得到解析Json字符串的对象
    HashMap hashMap = gson.fromJson(plainText, HashMap.class);//把这个字符串解析成一个Map集合
        String orderNo = (String) hashMap.get("out_trade_no");//得到我的这个商户号的信息

    String orderStatus = orderInfoService.getOrderStatus(orderNo);//得到我的这个订单状态
    if (OrderStatus.REFUND_PROCESSING.equals(orderStatus)) {//判断我当前的这个订单是否是在退款中的这个状态,如果是退款中的状态则直接返回空
        return;
    }
    orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);//更改当前的这个退款状态
    refundInfoService.updateRefund(plainText);//把这个报文信息更新到退款申请的数据库当中
}

六.下载交易账单信息和资金账单信息

1.申请交易账单和申请资金账单

1>编写我的这个生成申请资金账单的Url地址

首先我们需要先向微信端去申请我的这个url地址,申请下载excel表格的url地址所发送请求的url地址的组成部分,第一部分就是我的这个微信服务端的头部url地址,第二部分就是我的这个请求的类型,比如说tradebill交易账单类型的,或者是fundflowbill资金账单类型的,第三部分就是我需要提交的这个时间范围,然后通过这个url得到HttpGet对象,注意在得到这个httpget对象的时候一定要设置我的这个微信的响应头,的类型httpGet.setHeader("Accept","application/Json"),把需要查询的相关时间以字符串类型的参数传入到我的这个微信服务器端,最后通过CloseableHttpRequest的这个对象响应得到我的这个response的响应对象,通过这个对象去得到我的这个getStatusCode的这个状态码,然后得到微信响应的这个字符串类型的参数通过EntityUtils.toString(response.getEntity)来得到这个微信响应的数据信息,然后通过CloseableHttpRequest通过getStatusLine().getStatusCode来得到我的这个微信支付端响应的状态码,然后进行判断,如果说没有问题的就通过Gson的这个对象解析我的这个EntityUtils.toString(respons)的对象,转换成一个HashMap对象,然后再这个HashMap集合当中通过download_url的地址来得到我的这个Excel表格下载的地址,然后进行返回

代码如下(Service):

/**
 * 下载我当前的这个交易账单或者是资金账单
 * @param billDate 查询日期
 * @param type 交易账单类型
 * @return
 */
@Override
public String queryBill(String billDate, String type) throws Exception {
    log.warn("申请账单下载接口==>{}",billDate);
    String url="";//定义这个url地址,url的组成部分是微信服务器地址+账单下载地址+账单交易类型+请求
    if (type.equals("tradebill")) {//判断当前的这个账单是否是交易账单
        url=wxPayConfig.getDomain().concat(WxApiType.TRADE_BILLS.getType());//得到我的这个单笔交易账单的url
    }else if(type.equals("fundflowbill")){//判断当前的这个账单是否是资金账单
        url =wxPayConfig.getDomain().concat(WxApiType.FUND_FLOW_BILLS.getType());//拼接资金账单的信息
    }
    url=url.concat("?bill_date=").concat(billDate);//因为在微信支付官网中显示必须要传递当前的这个查询日期
    HttpGet httpGet = new HttpGet(url);
    httpGet.setHeader("Accept","application/json");//更改我的这个响应头信息
    CloseableHttpResponse response = wxClient.execute(httpGet);//得到后端响应的信息
    try {
        int statusCode = response.getStatusLine().getStatusCode();
        String bodyAsString = EntityUtils.toString(response.getEntity());//得到我的这个微信端响应的详细信息
        if (statusCode == 200) {//判断微信响应的状态
            log.warn("响应成功,状态码是==>{},申请返回结果==>{}",statusCode,bodyAsString);
        }else if(statusCode == 204){
            log.warn("响应成功");
        }else {//如果说不是204和200的状态码就说明存在异常
            throw new Exception("请求信息失败,出现异常,状态码是==>"+statusCode+",申请返回的结果是==>"+bodyAsString);
        }
        Gson gson = new Gson();
        Map resultMap = gson.fromJson(bodyAsString, HashMap.class);
        String downloadUrl = (String) resultMap.get("download_url");//得到我的这个excel下载的地址
        return downloadUrl;//返回这个下载地址
    } finally {
        response.close();//释放资源
    }
}

2>下载账单

在下载资金账单或者是交易账单的时候如果说我们使用不传入我的这个签名的话他会报错,刚好我们的这个申请交易账单下载的时候我们是不需要提供我们的这个签名标签的,所以我们需要在这个配置签名的这个配置类当中添加一下的代码:这段代码主要是传入一个空的签名应答,当我们在请求资金账单excel表格的时候我们就可以通过调用这个配置类里面的信息来跳过我们的这个签名验证,(改代码在上面的WxPayConfig代码当中已经添加了

/**
 * 获取我的这个签名验证器
 * 跳过签名验证器
 * @return
 */
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient wxPayNoSignClient(){
    log.info("签名验证器");
    //获取商户密钥
    PrivateKey privateKey = getPrivateKey(privateKeyPath);
    WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
            //验证商户Id和商户APIId和商户秘钥
            .withMerchant(mchId, mchSerialNo, privateKey)
            //通过(response)->true实现一个空的应答
            .withValidator((response) -> true);
    return builder.build();//返回我的这个http的请求地址
}

 然后我们在这个WxPayConfig的这个类中添加了这个代码之后我们就可以正常的去申请我们的这个资金账单进行下载了

@Autowired
@Qualifier("wxPayNoSignClient")
private CloseableHttpClient wxPayNoSignClient; //无需应答签名/**
 * 下载excel表格的接口方法
 * @param billDate 我的这个查询的时间
 * @param type 订单的类型
 * @return
 */
@Override
public String downloadBill(String billDate, String type) throws Exception {
    log.warn("下载账单的接口调用,时间==>{},订单类型==>{}",billDate,type);
    String downloadUrl = this.queryBill(billDate, type);//调用到我的这个账单下载url地址的方法
    HttpGet httpGet = new HttpGet(downloadUrl);//把我的这个申请得到的这个Url地址存放到HttpGet中,通过这个地址去得到我下载Excel表格需要的数据
    httpGet.addHeader("Accept", "application/json");//设置响应头
    System.err.println("excel下载的网址====>"+downloadUrl);
        CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet);//得到这个响应信息
    try {
        int statusCode = response.getStatusLine().getStatusCode();
        String bodyAsString = EntityUtils.toString(response.getEntity());//得到我的这个微信端发送的请求信息
        if (statusCode == 200) {
            log.info("响应成功,请求状态码==>{},请求信息==>{}",statusCode,bodyAsString);
        }else if (statusCode == 204){
            log.info("响应成功,请求状态==>{}",statusCode);
        }else {
            throw new RuntimeException("响应失败,请求状态码==>"+statusCode+",请求信息==>"+bodyAsString);
        }
        //如果响应成功则返回我的这个微信端响应的信息
        return bodyAsString;
    } finally {
        response.close();//释放资源
    }
}

然后这个bodyAsString是一个String类型的Url,主要是返回这个Url来下载当前账户所消费的账单然后再前端vue来设置这个数据以excel表格渲染,来设置我的这个前端的请求头,前端的代码如下

//下载账单
    downloadBill(type){
      //获取账单内容
      billApi.downloadBill(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()
      })

我的微信支付demo的gtiee网址如下:

后端代码:Gong/ouygoing

前端代码:Gong/ouygoingVue

  • 5
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值