微信支付学习

学习微信支付时比较有用的代码


<dependency>
    <groupId>com.github.wechatpay-apiv3groupId>
    <artifactId>wechatpay-apache-httpclientartifactId>
    <version>0.4.9version>
dependency>

1.将request转化为字符串的工具类

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

2.简易封装枚举类型类

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum WxTradeState {

    /**
     * 支付成功
     */
    SUCCESS("SUCCESS"),

    /**
     * 未支付
     */
    NOTPAY("NOTPAY"),

    /**
     * 已关闭
     */
    CLOSED("CLOSED"),

    /**
     * 转入退款
     */
    REFUND("REFUND");

    /**
     * 类型
     */
    private final String type;
}

3.微信支付签名验证V3类

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

3.mybatis-plus代码生成示例代码

    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>

4.cron表达式:

/**
     * 秒 分 时 日 月 周
     * 以秒为例
     * *:每秒都执行
     * 1-3:从第1秒开始执行,到第3秒结束执行
     * 0/3:从第0秒开始,每隔3秒执行1次
     * 1,2,3:在指定的第1、2、3秒执行
     * ?:不指定
     * 日和周不能同时制定,指定其中之一,则另一个设置为?
     */
    //@Scheduled(cron = "0/3 * * * * ?")   要想生效记得在启动类加上@EnableScheduling还有自己所在的类必须@Component交给spring管理

5.生成二维码插件vue-qriously

安装

npm install vue-qriously --save-dev
  • main.js入口文件中引入
import Vue from 'vue'
import VueQriously from 'vue-qriously'
Vue.use(VueQriously)

在vue里使用






6.下载账单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

你可能感兴趣的:(springboot,微信,学习,java)