SpringBoot对接微信支付

开放平台

微信支付也有对应的开放平台,类似支付宝。官网链接

在这里插入图片描述

微信支付的接口分v2和v3版本,早期使用的是v2版本,目前推荐使用v3版本,v2与v3的区别如下:

在这里插入图片描述

需要注意的是,微信并没有提供沙箱环境,只能使用正式环境的账号信息才能接口调试。

Native支付

微信中的扫码支付称之为【Native支付】,原理与支付宝类型。官网链接

1、商户下单获取订单的二维码链接code_url,将code_url转换为二维码图片展示给用户。

2、用户使用微信“扫一扫”进行扫码(不支持通过相册识别或长按识别二维码的方式完成支付)。

3、扫码进入到微信的支付确认界面,用户可在该页面确认收款方和金额。

4、用户确认订单收款方和金额无误后,点击“立即支付”会出现验密界面(验证密码或指纹等),同时在该页面也可选择支付方式(零钱或银行卡等)。

5、验密付款成功后,微信会展示支付成功页面。

6、支付成功后,用户在微信支付-我的账单-账单明细中查看账单。

SDK

微信支付的接口是标准的RETful风格,同样也提供了SDK,与支付宝提供的SDK相比就简化了很多,微信支付的SDK仅仅是基于Httpclient进行了必要的封装,并没有将业务api封装进去。

编写代码

加入以下依赖
<properties>
    <java.version>21</java.version>
    <!--支付宝-->
    <alipay.easysdk.version>2.2.3</alipay.easysdk.version>
    <!--微信支付-->
    <wechatpay.version>0.4.7</wechatpay.version>
</properties>

<dependencies>
	<dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.22</version>
    </dependency>
    <dependency>
        <groupId>com.github.wechatpay-apiv3</groupId>
        <artifactId>wechatpay-apache-httpclient</artifactId>
        <version>${wechatpay.version}</version>
    </dependency>
    <!--支付宝支付easysdk-->
    <dependency>
        <groupId>com.alipay.sdk</groupId>
        <artifactId>alipay-easysdk</artifactId>
        <version>${alipay.easysdk.version}</version>
    </dependency>
    <!--web起步依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!--单元测试-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
编写WechatNativePayHandler
package com.liming.handler.wechat;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.liming.handler.NativePayHandler;
import com.liming.handler.wechat.response.WeChatResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 微信二维码支付
 */
@Slf4j
@Component("wechatNativePayHandler")
public class WechatNativePayHandler implements NativePayHandler {

    @Override
    public void createDownLineTrading(String orderNo) throws Exception {
        // 查询配置
        WechatPayHttpClient client = WechatPayHttpClient.get();
        //请求地址
        String apiPath = "/v3/pay/transactions/native";

        //请求参数
        Map<String, Object> params = MapUtil.<String, Object>builder()
                .put("mchid", client.getMchId())
                .put("appid", client.getAppId())
                .put("description", "jzj-餐饮")
                .put("notify_url", client.getNotifyUrl())
                .put("out_trade_no", orderNo)
                .put("amount", MapUtil.<String, Object>builder()
                        .put("total", Convert.toInt(NumberUtil.mul(200, 100))) //金额,单位:分
                        .put("currency", "CNY") //人民币
                        .build())
                .build();

        try {
            WeChatResponse response = client.doPost(apiPath, params);
            if (!response.isOk()) {
                //下单失败
                throw new RuntimeException("统一下单交易失败");
            }
            //指定统一下单code
            log.info("指定统一下单code,{}",Convert.toStr(response.getStatus()));
            //二维码需要展现的信息
            log.info("二维码需要展现的信息,{}",JSONUtil.parseObj(response.getBody()).getStr("code_url"));
            //指定统一下单json字符串
            log.info("指定统一下单json字符串,{}",response.getBody());
        } catch (Exception e) {
            throw new RuntimeException("统一下单交易失败");
        }
    }

}
WechatPayHttpClient
package com.liming.handler.wechat;

import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.net.url.UrlPath;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.liming.handler.wechat.response.WeChatResponse;
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.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;

import java.io.ByteArrayInputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.util.Map;

/**
 * 微信支付远程调用对象
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WechatPayHttpClient {

    private String mchId; //商户号
    private String appId; //应用号
    private String privateKey; //私钥字符串
    private String mchSerialNo; //商户证书序列号
    private String apiV3Key; //V3密钥
    private String domain; //请求域名
    private String notifyUrl; //请求地址

    public static WechatPayHttpClient get() {
        //通过渠道对象转化成微信支付的client对象
        return WechatPayHttpClient.builder()
                .appId("wx6592a2db3f85ed25")
                .domain("api.mch.weixin.qq.com")
                .privateKey("-----BEGIN PRIVATE KEY-----\n" +
                        "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBHGgIh80193Gh\n" +
                        "dpD1LtMZfTRpcWI0fImyuBCyrd3gYb3rrsARebGcHdJsQA3mVjVqVp5ybhEZDPa4\n" +
                        "ecoK4Ye1hTppNpI/lmLt4/uUV/zhF5ahli7hi+116Ty6svHSbuMQBuUZeTFOwGrx\n" +
                        "jvofU/4pGIwh8ZvkcSnyOp9uX2177UVxDBkhgbZbJp9XF2b83vUa5eHo93CziPzn\n" +
                        "3hFdAlBCdTXB7DH+m0nN3Jou0szGukvq7cIgGpHku4ycKSTkIhhl9WRhN6OoSEJx\n" +
                        "q88MXzjkzTruc85PHN52aUTUifwg3T8Y4XqFQ61dTnEmgxeD2O6/pLdB9gLsp6yC\n" +
                        "GqN5Lqk7AgMBAAECggEBAL4X+WzUSbSjFS9NKNrCMjm4H1zgqTxjj6TnPkC1mGEl\n" +
                        "tjAHwLgzJBw62wWGdGhWWpSIGccpBBm1wjTMZpAZfF66fEpP1t1Ta6UjtGZNyvfF\n" +
                        "IZmE3jdWZ/WXGBnsxtFQKKKBNwrBW0Fbdqq9BQjLxLitmlxbmwrgPttcy855j6vZ\n" +
                        "qq4MBT1v8CtUT/gz4UWW2xWovVnmWOrRSScv7Nh0pMbRpPLkNHXrBwSSNz/keORz\n" +
                        "XB9JSm85wlkafa7n5/IJbdTml3A/uAgW3q3JZZQotHxQsYvD4Zb5Cnc9CPAXE5L2\n" +
                        "Yk877kVXZMGt5QPIVcPMj/72AMtaJT67Y0fN0RYHEGkCgYEA38BIGDY6pePgPbxB\n" +
                        "7N/l6Df0/OKPP0u8mqR4Q0aQD3VxeGiZUN1uWXEFKsKwlOxLfIFIFk1/6zQeC0xe\n" +
                        "tNTKk0gTL8hpMUTNkE7vI9gFWws2LY6DE86Lm0bdFEIwh6d7Fr7zZtyQKPzMsesC\n" +
                        "3XV9sdSUExEi5o/VwAyf+xZlOXcCgYEA3PGZYlILjg3esPNkhDz2wxFw432i8l/B\n" +
                        "CPD8ZtqIV9eguu4fVtFYcUVfawBb0T11RamJkc4eiSOqayC+2ehgb+GyRLJNK4Fq\n" +
                        "bFcsIT+CK0HlscZw51jrMR0MxTc4RzuOIMoYDeZqeGB6/YnNyG4pw2sD8bIwHm84\n" +
                        "06gtJsX/v10CgYAo8g3/aEUZQHcztPS3fU2cTkkl0ev24Ew2XGypmwsX2R0XtMSB\n" +
                        "uNPNyFHyvkgEKK2zrhDcC/ihuRraZHJcUyhzBViFgP5HBtk7VEaM36YzP/z9Hzw7\n" +
                        "bqu7kZ85atdoq6xpwC3Yn/o9le17jY8rqamD1mv2hUdGvAGYsHbCQxnpBwKBgHTk\n" +
                        "eaMUBzr7yZLS4p435tHje1dQVBJpaKaDYPZFrhbTZR0g+IGlNmaPLmFdCjbUjiPy\n" +
                        "A2+Znnwt227cHz0IfWUUAo3ny3419QkmwZlBkWuzbIO2mms7lwsf9G6uvV6qepKM\n" +
                        "eVd5TWEsokVbT/03k27pQmfwPxcK/wS0GFdIL/udAoGAOYdDqY5/aadWCyhzTGI6\n" +
                        "qXPLvC+fsJBPhK2RXyc+jYV0KmrEv4ewxlK5NksuFsNkyB7wlI1oMCa/xB3T/2vT\n" +
                        "BALgGFPi8BJqceUjtnTYtI4R2JIVEl08RtEJwyU5JZ2rvWcilsotVZYwfuLZ9Kfd\n" +
                        "hkTrgNxlp/KKkr+UuKce4Vs=\n" +
                        "-----END PRIVATE KEY-----\n")
                .mchId("1561414331")
                .mchSerialNo("25FBDE3EFD31B03A4377EB9A4A47C517969E6620")
                .apiV3Key("CZBK51236435wxpay435434323FFDuv3")
                .notifyUrl("https://www.baidu.cn/")
                .build();
    }

    /***
     * 构建CloseableHttpClient远程请求对象
     * @return org.apache.http.impl.client.CloseableHttpClient
     */
    public CloseableHttpClient createHttpClient() throws Exception {
        // 加载商户私钥(privateKey:私钥字符串)
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes(StandardCharsets.UTF_8)));

        // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, merchantPrivateKey);
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        // 向证书管理器增加需要自动更新平台证书的商户信息
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));

        // 初始化httpClient
        return com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(certificatesManager.getVerifier(mchId)))
                .build();
    }

    /***
     * 支持post请求的远程调用
     *
     * @param apiPath api地址
     * @param params 携带请求参数
     * @return 返回字符串
     */
    public WeChatResponse doPost(String apiPath, Map<String, Object> params) throws Exception {
        String url = StrUtil.format("https://{}{}", this.domain, apiPath);
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");

        String body = JSONUtil.toJsonStr(params);
        httpPost.setEntity(new StringEntity(body, CharsetUtil.UTF_8));

        CloseableHttpResponse response = this.createHttpClient().execute(httpPost);
        return new WeChatResponse(response);
    }

    /***
     * 支持get请求的远程调用
     * @param apiPath api地址
     * @param params 在路径中请求的参数
     * @return 返回字符串
     */
    public WeChatResponse doGet(String apiPath, Map<String, Object> params) throws Exception {
        URI uri = UrlBuilder.create()
                .setHost(this.domain)
                .setScheme("https")
                .setPath(UrlPath.of(apiPath, CharsetUtil.CHARSET_UTF_8))
                .setQuery(UrlQuery.of(params))
                .setCharset(CharsetUtil.CHARSET_UTF_8)
                .toURI();
        return this.doGet(uri);
    }

    /***
     * 支持get请求的远程调用
     * @param apiPath api地址
     * @return 返回字符串
     */
    public WeChatResponse doGet(String apiPath) throws Exception {
        URI uri = UrlBuilder.create()
                .setHost(this.domain)
                .setScheme("https")
                .setPath(UrlPath.of(apiPath, CharsetUtil.CHARSET_UTF_8))
                .setCharset(CharsetUtil.CHARSET_UTF_8)
                .toURI();
        return this.doGet(uri);
    }

    private WeChatResponse doGet(URI uri) throws Exception {
        HttpGet httpGet = new HttpGet(uri);
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = this.createHttpClient().execute(httpGet);
        return new WeChatResponse(response);
    }

}
WeChatResponse
package com.liming.handler.wechat.response;

import cn.hutool.core.util.CharsetUtil;
import lombok.Data;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;

@Data
public class WeChatResponse {

    private int status; //响应状态码
    private String body; //响应体数据

    public WeChatResponse() {

    }

    public WeChatResponse(CloseableHttpResponse response) {
        this.status = response.getStatusLine().getStatusCode();
        try {
            this.body = EntityUtils.toString(response.getEntity(), CharsetUtil.UTF_8);
        } catch (Exception e) {
            // 如果出现异常,响应体为null
        }
    }

    public Boolean isOk() {
        return this.status == 200;
    }

}
测试用例
package com.liming.handler;

import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class WechatNativePayHandlerTest {

    @Resource(name = "wechatNativePayHandler")
    NativePayHandler nativePayHandler;

    @Test
    void createDownLineTrading() throws Exception {

        this.nativePayHandler.createDownLineTrading("123456");

    }
}
`WechatPay-HttpClient` 和 `WechatPay-Java SDK` 都是用来接入微信支付的功能模块,但它们之间存在一些关键区别: 1. **封装程度**: - WechatPay-Java SDK:提供了一个相对完整的API集,包括微信支付的基础操作,如统一下单、退款等,以及处理回调等高级功能。它通常已经进行了错误处理和异步处理的封装,开发者只需要简单的调用即可。 - WechatPay-Apache-HttpClient:这是基于Apache HttpClient自定义的一个网络请求组件,用于实现微信支付接口的直接访问。这种方式需要开发者手动构建HTTP请求、处理响应和解析数据,工作量更大,但灵活性更高,允许更细粒度的控制。 2. **易用性**: - WechatPay-Java SDK倾向于提升用户体验,通过预设的配置和方法,降低了调用微信支付接口的复杂性。 - WechatPay-Apache-HttpClient则需要开发者具备一定的网络编程知识,尤其是处理HTTP请求头、参数编码等细节。 3. **文档支持**: - WechatPay-Java SDK通常会附带详细的文档和示例,便于新手快速上手。 - WechatPay-Apache-HttpClient的文档可能不如官方SDK那么全面,开发者可能需要查阅Apache HttpClient的官方文档以及微信支付的官方文档。 4. **安全性**: - WechatPay-Java SDK可能内置了认证和加密机制,确保请求的安全性。 - WechatPay-Apache-HttpClient虽然也支持安全连接,但需要开发者自行管理SSL证书和请求配置。 总之,如果你寻求简洁、易用并且希望降低出错率,推荐使用WechatPay-Java SDK;如果你想拥有更大的定制自由度和深入理解网络通信过程,可以选择WechatPay-Apache-HttpClient。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小钟佳运

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值