1.2 微信Native支付 - 支付流程、验签器、HttpClient、自定义API字典

微信支付

一、微信支付

首先说明

SDK的位置:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay6_0.shtml

image-20231102222707638

其实现在版本有两个

版本1:wechatpay-java推荐):https://github.com/wechatpay-apiv3/wechatpay-java

版本:2:wechatpay-apache-httpclient:https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient

但是这篇文章用的是不推荐的那个

1.1 流程介绍

  • 引入支付参数

    支付参数包含商户号、AppId、Api秘钥、数字证书等

    使用代码将支付参数加载到程序之中

  • 加载商户私钥

    在非对称加密中需要用到私钥和公钥。

    我们的平台向微信发送请求

    当我们向微信平台发送请求的时候,商户需要用私钥进行签名,微信平台接收到商户的请求之后,需要使用商户的公钥进行验签

  • 获取平台证书和验签器

    微信向我们的支付平台发送请求

    当微信向支付平台发送请求的时候,微信支付平台会用他的私钥进行签名,那我们的商户会使用微信支付平台的公钥进行验签

    而平台的公钥是从平台的数字证书当中获取的。所以这一步我们要获取平台的数字证书,冰倩创建我们的签名验证器

  • 获取HttpClient对象

    我们远程请求的发送时建立在http连接的基础上,所以我们需要使用httpClient工具建立远程连接

  • API字段和接口规则

  • 内网穿透

    微信向我们的开发服务器发送请求的时候,我们的开发服务器必须有一个微信可以访问的外网地址,但我们的开发机一般都是局域网环境的,没有独立IP,因此需要进行内网穿透,通过这种方式帮助我们将我们的开发机器映射到外网

  • API v3

1.2 配置参数

# 微信支付相关参数
# 商户号
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
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
wxpay.notify-domain=https://500c-219-143-130-12.ngrok.io

# APIv2密钥
wxpay.partnerKey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
  • 商户号

商户号,在“微信平台”中“账户中心”的“个人信息”模块,复制“登录账号”即可

image-20231102153749157

  • 商户API证书序列号

商户API证书序列号,需要在商户平台申请一个证书

申请的时候其实是申请的私钥与证书,但是证书里面封装了公钥

申请了证书之后,每一个证书都会有一个商户证书API序列号

证书序列号一定要和证书对应

image-20231102154043182

image-20231102154319517

  • 商户私钥文件

    商户私钥文件加载到程序中的目的就是为了做签名

    用私钥将我们的请求进行签名,然后把我们的请求信息发送给微信的服务器端

    微信的服务端根据我们商户API证书序列号找到对应的证书,然后从证书中解析出公钥,然后用公钥对我们的请求进行验签

image-20231102154448430

  • API v3密钥

    在下面这个地方设置,此密钥是对称加密的密钥

image-20231102155354128

  • APPID

    APPID是我们在申请商户号的时候也申请了一个微信的公众号,APPID就是微信公众号中帮我们分配的

  • 微信服务器地址

    我们调用微信API v3接口时,都是往微信服务器发送请求

  • 接收结果通知地址

    微信也要向我们的商户端发送请求,我们这里用到了内网穿透

1.3 读取配置参数

@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
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;

}

1.4 读取私钥

首先我们需要找到这个私钥文件

image-20231102154448430

把这个证书复制到项目的根目录下

image-20231102161125998

怎么读取私钥文件

下面这个图截错了

应该是进:https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient这个地址

image-20231102162035207

点击“wechatpay”便进入GitHub了,但是进GitHub的速度很慢,需要等待

image-20231102222348811

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

引进微信的SDK,那怎么使用呢

继续查看GitHub

我们这个项目中,私钥是存储在文件中,所以选择第一种示例

image-20231102163040990

将下面这段代码写在@Configuration修饰的配置类中

设置为私有方法是为了保证安全

   /**
     * 获取商户的私钥文件
     * @param filePath 私钥文件路径
     * @return
     */
    public PrivateKey getPrivateKey(String filePath){
    
    

        try {
    
    
            return PemUtil.loadPrivateKey(new FileInputStream(filePath));
        } catch (FileNotFoundException e) {
    
    
            throw new RuntimeException("私钥文件不存在", e);
        }
    }

下面进行测试

@Autowired
private WxPayConfig config;

@Test
void contextLoads() {
    
    
    String privateKeyPath = config.getPrivateKeyPath();
    PrivateKey privateKey = config.getPrivateKey(privateKeyPath);

    System.out.println(privateKeyPath);
    System.out.println(privateKey);
}
    @Autowired
    private WxPayConfig wxPayConfig;

    /**
     * 获取商户的私钥
     */
    @Test
    void testGetPrivateKey() {
    
    

        //获取私钥路径
        String privateKeyPath = wxPayConfig.getPrivateKeyPath();

        //获取私钥
        PrivateKey privateKey = wxPayConfig.getPrivateKey(privateKeyPath);

       System.out.println(privateKeyPath);
       System.out.println(privateKey);

    }

读取到的内容

RSA是私钥的非对称加密算法,用的是2048 bits的密钥

image-20231102170333419

二、 获取验签器和HttpClient

2.1 证书密钥使用说明

网站:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_0.shtml

下图就是这个网站中的内容

下图中的内容完整的描述了商户和微信支付平台的支付和响应流程

M代表商户,W代表微信支付平台

当我们的商户向微信支付平台发送请求之前,在我们商户这一侧需要用我们的私钥对请求进行签名,将请求发送之后,微信这一端用商户的公钥进行签名的验证,这个地方是一个很典型的非对称加密过程

微信在处理商户的请求之后,在向商户返回响应之前,微信平台使用自己的私钥对将要响应的数据进行签名,签名之后响应给商户平台,商户平台再用微信平台的公钥对微信平台响应的内容进行验签,当商户平台验证成功后才会继续做下一步的处理

这就是一个完整的同步请求和响应的流程

对编程的过程来说,我们要实现计算签名和验证签名的过程

我们也不用很带劲计算签名和验证签名,因为之前我们引入过微信平台的SDK,我们只需要为SDK提供一些必要的参数即可

img

2.2 HttpClient

1.5.2与1.5.3能够完成定时更新微信平台证书功能

我们需要创建一个HttpClient对象,这个对象会执行一个execute方法,执行成功后我们就会得到响应

下面标红的地方的httpClient.execute就是请求的发送和响应的过程

image-20231102225111644

上面的这份文档在这里

image-20231102222742630

下面我们就创建一个HttpClient

/**
 * 获取http请求对象
 * @param verifier 签名验证器
 * @return
 */
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){
    
    

    log.info("获取httpClient");

    //获取商户私钥
    PrivateKey privateKey = getPrivateKey(privateKeyPath);

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

    // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
    CloseableHttpClient httpClient = builder.build();

    return httpClient;
}

对于ScheduledUpdateCertificatesVerifier,查看1.5.3即可

2.3 获取签名验证器 verifier

我们如果要对微信平台端返回的响应结果进行验签,我们需要拿到微信平台的公钥。为了防止公钥被冒用,是不能进行简单的分发的,我们要以证书的形式分发。所以拿到微信平台的公钥就意味着要拿到微信平台的证书。

但是证书是有有效期的,为了保证微信平台的证书永远不过期,所以在SDK中提供了一个“定时更新平台证书功能”

image-20231102221106863

    /**
     * 获取签名验证器
     * @return
     */
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier() {
    
    

        log.info("获取签名验证器");

        //TODO 获取商户私钥
        //参数1:商户私钥文件在哪里
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        //TODO 私钥签名对象
        //参数1:商户API证书序列号, 参数2:商户私钥文件
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);

        //TODO 身份认证对象
        //参数1:商户号,参数2:私钥签名对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        //TODO 使用定时更新的签名验证器,不需要传入证书
        //参数1:身份认证对象,参数2:对称加密的密钥(数据传输的过程中也有对称加密)
        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                apiV3Key.getBytes(StandardCharsets.UTF_8));

        return verifier;
    }

三、 API 字典

我们可以在网站中看到Native支付的一些内容

https://pay.weixin.qq.com/wiki/doc/apiv3/apis/index.shtml

image-20231102230749837

3.1 封装API路径

@AllArgsConstructor
@Getter
public enum WxApiType {
    
    

    /**
     * Native下单
     */
    NATIVE_PAY("/v3/pay/transactions/native"),

    /**
     * Native下单V2
     */
    NATIVE_PAY_V2("/pay/unifiedorder"),

    /**
     * 查询订单
     */
    ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),

    /**
     * 关闭订单
     */
    CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),

    /**
     * 申请退款
     */
    DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),

    /**
     * 查询单笔退款
     */
    DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),

    /**
     * 申请交易账单
     */
    TRADE_BILLS("/v3/bill/tradebill"),

    /**
     * 申请资金账单
     */
    FUND_FLOW_BILLS("/v3/bill/fundflowbill");


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

比如Native下单的路径是https://api.mch.weixin.qq.com/v3/pay/transactions/native,我们将其封装成一个枚举

image-20231102232115375

3.2 封装通知

比如说下面的支付通知和退款结果通知,我们需要给微信平台提供一些路径,他们才能给我们通知,我们把我们开发的接口的路径放到枚举里面

image-20231102232352406

下面的路径都是我们开发的,不是微信平台的路径。我们要将接口的路径告诉微信平台,他们才能给我们发送通知

@AllArgsConstructor
@Getter
public enum WxNotifyType {
    
    

    /**
     * 支付通知
     */
    NATIVE_NOTIFY("/api/wx-pay/native/notify"),

    /**
     * 支付通知V2
     */
    NATIVE_NOTIFY_V2("/api/wx-pay-v2/native/notify"),


    /**
     * 退款结果通知
     */
    REFUND_NOTIFY("/api/wx-pay/refunds/notify");

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

3.3 微信退款状态

@AllArgsConstructor
@Getter
public enum WxRefundStatus {
    
    

    /**
     * 退款成功
     */
    SUCCESS("SUCCESS"),

    /**
     * 退款关闭
     */
    CLOSED("CLOSED"),

    /**
     * 退款处理中
     */
    PROCESSING("PROCESSING"),

    /**
     * 退款异常
     */
    ABNORMAL("ABNORMAL");

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

3.4 微信交易状态

是我们的商户平台和微信平台直接的交易状态,是微信定义的

@AllArgsConstructor
@Getter
public enum WxTradeState {
    
    

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

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

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

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

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

3.5 业务订单状态

下面的枚举和微信平台没有关系,这只是我们系统中定义的一些业务订单状态

也就是用户和我们的商户平台之前的一些状态

@AllArgsConstructor
@Getter
public enum OrderStatus {
    
    
    /**
     * 未支付
     */
    NOTPAY("未支付"),


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

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

    /**
     * 已取消
     */
    CANCEL("用户已取消"),

    /**
     * 退款中
     */
    REFUND_PROCESSING("退款中"),

    /**
     * 已退款
     */
    REFUND_SUCCESS("已退款"),

    /**
     * 退款异常
     */
    REFUND_ABNORMAL("退款异常");

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

猜你喜欢

转载自blog.csdn.net/weixin_51351637/article/details/134197626
今日推荐