前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >我想在我自己的系统中加入微信支付功能,原来这么简单!!!

我想在我自己的系统中加入微信支付功能,原来这么简单!!!

作者头像
用户4919348
发布2023-07-20 21:38:22
5810
发布2023-07-20 21:38:22
举报
文章被收录于专栏:波波烤鸭波波烤鸭

微信支付功能实现

在这里插入图片描述
在这里插入图片描述

一、创建SpringBoot项目

??我们首先创建一个基本的SpringBoot项目。添加相关的依赖。

代码语言:javascript
复制
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

??然后引入Swagger。目的是自动生成接口文档和测试页面

代码语言:javascript
复制
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>

??然后添加Swagger的配置文件。创建对应的config包和对应的Swagger2Config配置类

代码语言:javascript
复制
@Configuration
@EnableSwagger2
public class Swagger2Config {

    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder().title("微信支付接口文档").build());
    }
}

创建测试接口

代码语言:javascript
复制
@RestController
public class WebController {

    @ApiOperation("测试接口")
    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }
}

swagger中的两个核心注解要注意:

  • @Api(tags=‘xxx’) 作用在类上
  • @ApiOperation(‘xxxx’) 作用在方法上

启动服务后。访问:http://localhost:8080/swagger-ui.html 测试即可

image.png
image.png

引入lombok依赖。简化实体类的开发

代码语言:javascript
复制
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

二、集成MyBatisPlus

??本案例中还是有些数据需要持久化到数据库中。这块我们通过MyBatisPlus来实现处理。先添加相关的Maven依赖:

代码语言:javascript
复制
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

然后添加对应的数据库配置信息

代码语言:javascript
复制
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/payment_demo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true&nullCatalogMeansCurrent=true
spring.datasource.username=root
spring.datasource.password=123456

mybatis-plus.mapper-locations=classpath:mapper/*.xml

然后通过MyBatis的自动代码生成器来生成相关的模板代码

代码语言:javascript
复制
DROP TABLE IF EXISTS `t_order_info`;

CREATE TABLE `t_order_info` (
  `id` int NOT NULL AUTO_INCREMENT,
  `title` varchar(32) DEFAULT NULL COMMENT '订单标题',
  `orderNo` varchar(64) DEFAULT NULL COMMENT '商户订单编号',
  `userId` int DEFAULT NULL COMMENT '用户id',
  `productId` int DEFAULT NULL COMMENT '支付产品id',
  `totalFee` int DEFAULT NULL COMMENT '订单金额(分)',
  `codeUrl` varchar(128) DEFAULT NULL COMMENT '订单二维码链接',
  `orderStatus` varchar(32) DEFAULT NULL COMMENT '订单状态',
  `createTime` datetime DEFAULT NULL COMMENT '创建时间',
  `updateTime` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

/*Data for the table `t_order_info` */

/*Table structure for table `t_payment_info` */

DROP TABLE IF EXISTS `t_payment_info`;

CREATE TABLE `t_payment_info` (
  `id` int NOT NULL AUTO_INCREMENT,
  `orderNo` varchar(64) DEFAULT NULL COMMENT '商品订单编号',
  `transactionId` varchar(64) DEFAULT NULL COMMENT '支付系统交易编号',
  `paymentType` varchar(32) DEFAULT NULL COMMENT '支付类型',
  `tradeType` varchar(32) DEFAULT NULL COMMENT '交易类型',
  `tradeState` varchar(32) DEFAULT NULL COMMENT '交易状态',
  `payerTotal` int DEFAULT NULL COMMENT '支付金额(分)',
  `content` varchar(64) DEFAULT NULL COMMENT '通知参数',
  `createTime` datetime DEFAULT NULL COMMENT '创建时间',
  `updateTime` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

/*Data for the table `t_payment_info` */

/*Table structure for table `t_product` */

DROP TABLE IF EXISTS `t_product`;

CREATE TABLE `t_product` (
  `id` int NOT NULL AUTO_INCREMENT,
  `title` varchar(32) DEFAULT NULL,
  `price` int DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;



/*Table structure for table `t_refund_info` */

DROP TABLE IF EXISTS `t_refund_info`;

CREATE TABLE `t_refund_info` (
  `id` int NOT NULL AUTO_INCREMENT,
  `orderNo` varchar(64) DEFAULT NULL COMMENT '商品订单编号',
  `refundNo` varchar(64) DEFAULT NULL COMMENT '退款单编号',
  `refundId` varchar(64) DEFAULT NULL COMMENT '支付系统退款单号',
  `totalFee` int DEFAULT NULL COMMENT '原订单金额(分)',
  `refund` int DEFAULT NULL COMMENT '退款金额(分)',
  `reason` varchar(64) DEFAULT NULL COMMENT '退款原因',
  `refundStatus` varchar(32) DEFAULT NULL COMMENT '退款单状态',
  `contentReturn` varchar(64) DEFAULT NULL COMMENT '申请退款返回参数',
  `contentNotify` varchar(128) DEFAULT NULL COMMENT '退款结果通知参数',
  `createTime` datetime DEFAULT NULL COMMENT '创建时间',
  `updateTime` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
代码语言:javascript
复制
        <!-- MyBatisPlus 代码生成器 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!-- 在MyBatisPlus的代码生成器中我们需要导入 freemarker的依赖 -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

代码生成器的相关代码

代码语言:javascript
复制
public class MyFastGeneratorConfiguration {
    public static void main(String[] args) {

        FastAutoGenerator.create("jdbc:mysql://localhost:3306/payment_demo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true"
                , "root", "123456")
                .globalConfig(builder -> {
                    builder.author("boge") // 设置作者
                            //.enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("D://pay"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("com.boge") // 设置父包名
                            .moduleName("pay") // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, "D://pay")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("t_order_info","t_payment_info","t_product","t_refund_info") // 设置需要生成的表名
                            .addTablePrefix("t_"); // 设置过滤表前缀

                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();

    }
}
image.png
image.png

最后添加扫描的路径

image.png
image.png

三、添加支付页面

??导入我们提前准备好的支付页面,具体的代码在附近中

image.png
image.png

四、定义相关枚举类型

??在支付案例中我们会涉及到各种类型的使用。所以我们会定义各种类型来应用。

支付类型:

代码语言:javascript
复制
@AllArgsConstructor
@Getter
public enum PayType {
    /**
     * 微信
     */
    WXPAY("微信"),


    /**
     * 支付宝
     */
    ALIPAY("支付宝");

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

订单状态:

代码语言:javascript
复制
@AllArgsConstructor
@Getter
public enum OrderStatus {
    /**
     * 未支付
     */
    NOTPAY("未支付"),


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

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

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

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

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

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

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

微信Native下单接口

代码语言:javascript
复制
@AllArgsConstructor
@Getter
public enum WxApiType {

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

	/**
	 * Native下单
	 */
	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;
}

微信支付通知相关接口

代码语言:javascript
复制
@AllArgsConstructor
@Getter
public enum WxNotifyType {

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

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


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

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

退款相关状态

代码语言:javascript
复制
@AllArgsConstructor
@Getter
public enum WxRefundStatus {

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

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

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

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

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

支付相关的状态

代码语言:javascript
复制
@AllArgsConstructor
@Getter
public enum WxTradeState {

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

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

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

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

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

五、基础支付API V3

1.引入相关参数

??在项目的resources目录中创建wxpay.properties文件。并在其中定义相关的支付属性.

代码语言:javascript
复制
# 微信支付相关参数
# 商户号
wxpay.mch-id=1640588*****
# 商户API证书序列号
wxpay.mch-serial-no=36E5C0892B813B5B78BC44CFB90******
# 商户私钥文件
wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=Y7CgFTppIyzzOXLm3RU1IFS*****
# APPID
wxpay.appid=wx1d4eab9******
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
wxpay.notify-domain=https://b513049e52.zicp.fun

# APIv2密钥
wxpay.partnerKey: xxxxxxxxxxxxxxx

2.读取支付参数

??我们在配置文件中配置的数据。系统启动的时候还是需要加载到内存中的。为了便于管理。我们创建一个WxPayConfig这个配置文件。来存储对应的配置信息

代码语言:javascript
复制
@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;



}

对应的我们需要添加对应的依赖信息

代码语言:javascript
复制
        <!-- 生成自定义配置的元数据信息 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

然后我们简单测试下是否能够读取到对应的信息:

代码语言:javascript
复制
@Controller
public class WebController {

    @Autowired
    private WxPayConfig wxPayConfig;

    @GetMapping({"/index","/"})
    public String index(){
        System.out.println(wxPayConfig.getAppid());
        System.out.println(wxPayConfig.getMchId());
        return "index";
    }

}

重启服务后访问。可以看到获取到了相关的配置信息

image.png
image.png

3.加载商户私钥

3.1 复制商户私钥

??把我们前面下载的私钥文件复制到项目的根目录下:

image.png
image.png

3.2 引入SDK

??我们可以使用官方提供的 SDK,帮助我们完成开发。实现了请求签名的生成和应答签名的验证:

https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay6_0.shtml

image.png
image.png
代码语言:javascript
复制
<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.4.9</version>
</dependency>

3.3 获取商户私钥

代码语言:javascript
复制
    /**
     * 获取商户的私钥文件
     * @param filename
     * @return
     */
    public PrivateKey getPrivateKey(String filename){
        try {
            return PemUtil.loadPrivateKey(getClass().getResourceAsStream("/"+filename));
        } catch (RuntimeException e) {
            throw new RuntimeException("私钥文件不存在", e);
        }
    }

3.4 测试获取私钥

??我们在测试的时候可以把上面的方法设置为public。

代码语言:javascript
复制
    @Autowired
    private WxPayConfig wxPayConfig;

    @GetMapping({"/index","/"})
    public String index(){
        System.out.println(wxPayConfig.getAppid());
        System.out.println(wxPayConfig.getMchId());
        System.out.println("------获取私钥信息------");
        PrivateKey privateKey = wxPayConfig.getPrivateKey(wxPayConfig.getPrivateKeyPath());
        System.out.println(privateKey);
        return "index";
    }

效果如下:

image.png
image.png

4.获取签名验证器和HttpClient

4.1 证书秘钥使用说明

微信支付-开发者文档 (qq.com)

4.2 获取签名验证器

签名验证器:(定时更新平台证书功能) 平台证书:平台证书封装了微信的公钥,商户可以使用平台证书中的公钥进行验签。 签名验证器:帮助我们进行验签工作,我们单独将它定义出来,方便后面的开发。

代码语言:javascript
复制
    /**
     * 获取签名验证器
     * @return
     */
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier(){
        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);
        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
        //身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
        // 使用定时更新的签名验证器,不需要传入证书
        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                apiV3Key.getBytes(StandardCharsets.UTF_8));
        return verifier;
    }

4.3 获取HttpClient对象

HttpClient 对象:是建立远程连接的基础,我们通过SDK创建这个对象。

代码语言:javascript
复制
    /**
     * 获取http请求对象
     * @param verifier
     * @return
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){
        //获取商户私钥
        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;
    }

5. API字典和相关工具

5.1 API列表

我们的项目中要实现以下所有API的功能。

https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_3.shtml

image.png
image.png

5.2 接口规则

基本规则-接口规则 | 微信支付商户平台文档中心 (qq.com)

微信支付 APIv3 使用 JSON 作为消息体的数据交换格式。

image.png
image.png

5.3 添加工具类

??将资料文件夹中的 util 目录复制到源码目录中,我们将会使用这些辅助工具简化项目的开发

image.png
image.png

6. Native下单API

6.1 Native支付流程

??要完成Native下单我们需要先搞清楚Native的完整流程。这个在官网有详细的介绍:微信支付-开发者文档 (qq.com)

业务流程说明:

(1)商户后台系统根据用户选购的商品生成订单。

(2)用户确认支付后调用微信支付【Native下单API】生成预支付交易;

(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。

(4)商户后台系统根据返回的code_url生成二维码。

(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。

(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。

(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。

(8)微信支付系统根据用户授权完成支付交易。

(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。

(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。

(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。

(12)商户确认订单已支付后给用户发货。

6.2 Native下单API

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

??商户端发起支付请求,微信端创建支付订单并生成支付二维码链接,微信端将支付二维码返回给商户 端,商户端显示支付二维码,用户使用微信客户端扫码后发起支付。

处理的核心代码:

代码语言:javascript
复制
    @Override
    public Map<String, Object> nativePay(Long productId) throws Exception{
//生成订单
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setTitle("test");
        orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //订单号
        orderInfo.setProductId(productId.intValue());
        orderInfo.setTotalFee(1); //分
        orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());
        //TODO:存入数据库
        //调用统一下单API
        HttpPost httpPost = new
                HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("mchid", wxPayConfig.getMchId());
        paramsMap.put("description", orderInfo.getTitle());
        paramsMap.put("out_trade_no", orderInfo.getOrderNo());
        paramsMap.put("notify_url",
                wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
        Map amountMap = new HashMap();
        amountMap.put("total", orderInfo.getTotalFee());
        amountMap.put("currency", "CNY");
        paramsMap.put("amount", amountMap);
//将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        StringEntity entity = new StringEntity(jsonParams,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.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("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " +
                        bodyAsString);
                throw new IOException("request failed");
            }
            //响应结果
            Map<String, String> resultMap = gson.fromJson(bodyAsString,
                    HashMap.class);
            //二维码
            String codeUrl = resultMap.get("code_url");
            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfo.getOrderNo());
            return map;
        } finally {
            response.close();
        }

    }

运行后的提示报错:

image.png
image.png

登录商户平台发下Native支付为开通。我们需要开通改服务

image.png
image.png

申请开通:

image.png
image.png

然后我们再通过PostMan来测试访问:

image.png
image.png

在控制台也可以看到成功的信息

image.png
image.png

6.3 二维码展示

??上面响应返回了对应的二维码地址。我们还需要把这个内容以图片的方式展示给客户。但是我们没有办法直接通过这个url地址来生成二维码图片,我们需要使用第三方库将 code_url 转化为二维码图片,例如 qrcode 库。

??QRCode库是一个用于生成和解析二维码的开源库,它支持多种编程语言,如Java、Python、C++等。该库提供了丰富的API,可以用于生成不同大小、颜色和格式的二维码。同时,它还支持错误校验和纠正,可以确保生成的二维码在有损情况下仍然可读。

QRCode库的主要功能包括:

  1. 生成二维码:可以生成不同大小、颜色和格式的二维码,支持自定义错误校验和纠正。
  2. 解析二维码:可以解析已有的二维码并获取其中的信息。
  3. 自定义样式:可以自定义二维码的样式,如颜色、背景图片等。
  4. 支持多种编程语言:支持多种编程语言,如Java、Python、C++等。

QRCode库的使用非常简单,只需要导入库并调用相应的API即可。由于其开源的特性,用户也可以根据自己的需要对其进行二次开发。

导入相关的依赖:

代码语言:javascript
复制
<!--        生成二维码-->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.3.0</version>
        </dependency>

然后创建相关的测试代码

代码语言:javascript
复制
static final String BASEPATH = "D://weixin";
  
    public static void generateQRCodeImage(String text, int width, int height, String fileName)
            throws WriterException, IOException {
        QRCodeWriter qrCodeWriter = new QRCodeWriter();
        BitMatrix bitMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, width, height);

        File file = new File(BASEPATH);
        if(!file.exists()){
            file.mkdir();
        }
        Path path = FileSystems.getDefault().getPath(BASEPATH+"/"+fileName);
        MatrixToImageWriter.writeToPath(bitMatrix, "PNG", path);
    }

    public static void main(String[] args) {
        try {
            generateQRCodeImage("weixin://wxpay/bizpayurl?pr=YC9gpgMzz", 350, 350, "QRTest.png");
        } catch (WriterException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

在对应的controller中调用生成即可

代码语言:javascript
复制
@ResponseBody
    @PostMapping("/native/{productId}")
    public Map<String,Object> nativePay(@PathVariable Long productId) throws Exception{
        Map<String, Object> map = wxPayService.nativePay(productId);
        // 把文件存储在 D://weixin/orderId.png
        WxPayConfig.generateQRCodeImage(map.get("codeUrl").toString(),350, 350,map.get("orderNo")+".png");
        return map;
    }

我们先提供一个下载二维码图片的功能

代码语言:javascript
复制
@GetMapping("/download")
    public void downloadImg(String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 加载需要下载的文件
        InputStream in = new FileInputStream(WxPayConfig.BASE_PATH+"/"+fileName);
        int size = in.available();
        byte[] data = new byte[size];
        in.read(data);
        in.close();
        // 把读取的数据响应给客户端
        response.setContentType("image/jpg");
        ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write(data);
        outputStream.flush();

    }

然后在页面中点击支付弹出对应的支付二维码信息

代码语言:javascript
复制
    function goPay(){
        $.post("/api/wx-pay/native/1001",function(data) {
            console.log(data.orderNo);
            $("#payImg").attr("src","/api/wx-pay/download?fileName="+data.orderNo+".png")
            $(".mask").show();
            $("body").css({overflow: "hidden"});
            // 检查是否已经存在倒计时定时器,避免重复启动
            if (!countdownInterval) {
                startCountdown();
            }
        })
    }

支付成功的截图

image.png
image.png

还有重复支付的截图

image.png
image.png

7.签名生成和验签

7.1 签名生成

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

上面的生成订单展示对应的支付二维码是如下的完整的流程

image.png
image.png

那么在这个流程中设计到了签名的生成和服务端的签名验证:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_0.shtml

image.png
image.png

签名的流程:

  1. 构造签名串:签名是密文,那么这个签名串就是这个密文对应的明文
  2. 计算签名值:对签名串加密,通过特定的规则加密
  3. 将签名发送给微信服务器:通过在http的请求头中传递

源码层面的逻辑:SignatureExec中的executeWithSignature方法中

image.png
image.png

然后进入到getToken中

代码语言:javascript
复制
@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;
    }

7.2 签名验证

??商户可以按照下述步骤验证应答或者回调的签名。

如果验证商户的请求签名正确,微信支付会在应答的HTTP头部中包括应答签名。我们建议商户验证应答签名。

同样的,微信支付会在回调的HTTP头部中包括回调报文的签名。商户必须 验证回调的签名,以确保回调是由微信支付发送。

获取平台证书

微信支付API v3使用微信支付 的平台私钥(不是商户私钥 )进行应答签名。相应的,商户的技术人员应使用微信支付平台证书中的公钥验签。目前平台证书只提供API进行下载

Get方法:https://api.mch.weixin.qq.com/v3/certificates

image.png
image.png

在具体的代码中。我们在系统启动的时候需要加载微信的证书列表

image.png
image.png
image.png
image.png

设置更新的频率是60分钟

image.png
image.png

处理的核心代码

image.png
image.png
image.png
image.png
image.png
image.png

签名验证

做超时时间处理

image.png
image.png

验证签名的逻辑

image.png
image.png
image.png
image.png
image.png
image.png

Wechatpay-Signature的字段值使用Base64进行解码,得到应答签名

image.png
image.png
image.png
image.png
image.png
image.png

8.内网穿透

??在用户支付完成后。微信服务的会调用我们本地服务来做支付的通知。这时就需要让我们本地的服务可以被微信的服务端访问到。这时需要利用内网穿透的方式来解决。 http://ngrok.com .下载对应操作系统的工具

image.png
image.png

然后下载对应的客户端段。解压缩后在对应的目录下打开cmd窗口。

image.png
image.png

然后我们利用对应的地址访问即可

9.支付通知

??用户支付成功后。微信服务器会回调我们在发起支付的时候传递的回调通知的地址

代码语言:javascript
复制
Map<String,Object> map = new HashMap<>();
        map.put("appid",wxPayConfig.getAppid());
        map.put("mchid",wxPayConfig.getMchId());
        map.put("description",orderInfo.getTitle());
        map.put("out_trade_no",orderInfo.getOrderNo());
        map.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));

那么我们需要修改在配置文件中的通知地址为我们内网穿透设置的地址

代码语言:javascript
复制
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
wxpay.notify-domain=https://c65f-183-215-31-250.ngrok-free.app

然后就可以定义对应的回调接口了

代码语言:javascript
复制
 /**
     * 微信服务的通知支付结果的方法
     * @param request
     * @param response
     * @return
     */
    @ResponseBody
    @PostMapping("/native/notify")
    public String nativePayNotify(HttpServletRequest request,HttpServletResponse response){
        // 1.获取微信服务器发送的通知内容  读取的内容是一个JSON格式的字符串
        String body = HttpUtils.readData(request);
        Gson gson = new Gson();
        Map<String,Object> bodyJson = gson.fromJson(body,HashMap.class);
        System.out.println("响应数据的ID===》"+bodyJson.get("id"));
        System.out.println("响应的JSON数据===》"+body);
        // 1.1 签名验证

        // 1.2 更新订单信息

        // 2.响应成功应答给微信服务器
        response.setStatus(200);
        Map<String,String> map = new HashMap<>();
        map.put("code","SUCCESS");
        map.put("message","成功");
        return gson.toJson(map);
    }

然后当我们支付成功后。就可以看到下面的打印信息了。说明接口实现是成功。

代码语言:javascript
复制
响应数据的ID===》379de717-0f49-5f76-8fda-fd91688642e5
响应的JSON数据===》{"id":"379de717-0f49-5f76-8fda-fd91688642e5","create_time":"2023-06-27T20:43:44+08:00","resource_type":"encrypt-resource","event_type":"TRANSACTION.SUCCESS","summary":"支付成功","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"t96vhF2Z/VptyJ8m+3PUtxp5RQcDcqezxgV/K/Ik4qJtFjO80TVDDbsrGaCCmJKbN9eV997vgkxOhjeDPt4SN09JPAiK7cQitlcvY9BsSMvji+fFoigbP0sae/4dSccW12g/DCXA3MMgPItRuy2Tq84rvgPcGF/fSZ6ZFDEQ4lLQpAO3Dk7RPYFeDJEhYRtC9FFjuM4J2voSg8O3Rg+VarHne0Hou14JZoPep0Q1Zdi3j34kULHPNIHLLrxf7bWUgtM0aCv2fjscqPaIH9CVB8bih9l30xjxrlIbolziuwY8bo45oT/N6z9mM5Dxb6glga3AQOrJkFKmKOYSigHo/jqRumpnCD024wlfBYtDRrJh8/n3gVlf1JguLzWcA5Ed9EAsShCxBt9Zb4/DqQt1LaFU9bivNV1T4Jpi2+LG55zNR/0fygG5Xi/MLAcjwdt7TGfqjeHStKZnTDkw7pcrri/svKGooZ2rJCr4APmd27OO/TX6P3ks1GmzRoaJI4WL85VaiUDrix889ngRjNetoU4FolaCrulg+TxHKX+AlBDvO2ajTFhIafd7MOZ4XaFDMEXczFn5cg==","associated_data":"transaction","nonce":"mrOQHEfn99Qw"}}
响应数据的ID===》af5afe61-af37-508d-9e04-5d27094ac30d
响应的JSON数据===》{"id":"af5afe61-af37-508d-9e04-5d27094ac30d","create_time":"2023-06-27T20:44:57+08:00","resource_type":"encrypt-resource","event_type":"TRANSACTION.SUCCESS","summary":"支付成功","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"FeE/+/rD0SqNKXTCMLMoWnms0deI6VnQzm15N2jUzvJoJ4RN5DvOiXpoqEX5XQ2+o+jd5ho/h4EZvsWP4Cb+x9/6MbkRu5IVVRMbNXI+teMpv3yMi++vqoYwutwYc7K31bv6AeofH921U0kzBdFpOO9YtSogSaVHA6UlXxleyKdmXulSH1e2QzC9vAzzh/pxWgFWfu3+bJ/MmucWrVMmpCb0iWKBgUMU8V1HFTcyeofm9GDoMk1SjffiA+USL3jSxxql6pP+4j9DfcpShmj27qROO8c/Tzcgwi+Oy3jdPjWnvkHHHk2oozcLzqrc7cWHxmcXfvfvQ56RqEXQL5wJZHZe3kteL7TH/bZonnS7ZPrYLHGeQCy5N1zRX/6wH/jAl7y2IwAocIVSZgsrgTOIElM9UncqpxYZn1pL0fywEkmHHNgqHVphyuMtPI3PBkKSiHFl8jlT2WbTLe8eTHIL+6Lsp6h/KvzGjkdKJaR0MZRMUxSwLun+99u+zDAnKsgKpJV41XwXZc9k1MD0L8c2nMTI+9Z4OYwZ7T+m8V6Gf+rIe76mLtJNw1QFhTmyVFKDlZYfIHVdtw==","associated_data":"transaction","nonce":"rvBWUUlQaVLh"}}
本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-07-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 微信支付功能实现
  • 一、创建SpringBoot项目
  • 二、集成MyBatisPlus
  • 三、添加支付页面
  • 四、定义相关枚举类型
  • 五、基础支付API V3
    • 1.引入相关参数
      • 2.读取支付参数
        • 3.加载商户私钥
          • 3.1 复制商户私钥
          • 3.2 引入SDK
          • 3.3 获取商户私钥
          • 3.4 测试获取私钥
        • 4.获取签名验证器和HttpClient
          • 4.1 证书秘钥使用说明
          • 4.2 获取签名验证器
          • 4.3 获取HttpClient对象
        • 5. API字典和相关工具
          • 5.1 API列表
          • 5.2 接口规则
          • 5.3 添加工具类
        • 6. Native下单API
          • 6.1 Native支付流程
          • 6.2 Native下单API
          • 6.3 二维码展示
        • 7.签名生成和验签
          • 7.1 签名生成
          • 7.2 签名验证
        • 8.内网穿透
          • 9.支付通知
          相关产品与服务
          短信
          腾讯云短信(Short Message Service,SMS)可为广大企业级用户提供稳定可靠,安全合规的短信触达服务。用户可快速接入,调用 API / SDK 或者通过控制台即可发送,支持发送验证码、通知类短信和营销短信。国内验证短信秒级触达,99%到达率;国际/港澳台短信覆盖全球200+国家/地区,全球多服务站点,稳定可靠。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
          http://www.vxiaotou.com