05、SpringBoot+微信支付 - 支付通知(接收支付通知【签名验证、参数解密、处理订单(更新订单状态、记录支付日志、重复通知的接口幂等性处理、可重入锁)】和 返回应答【应答成功、应答失败】)

Native 下单

支付通知–接收支付通知和返回应答

支付通知(接收支付通知【签名验证、参数解密、处理订单(更新订单状态、记录支付日志、重复通知的接口幂等性处理、可重入锁)】和 返回应答【应答成功、应答失败】)

完整需求介绍:

就是当用户扫描二维码并支付之后,微信支付那边就会返回一个【支付通知】,来告诉商户他的支付结果。商户收到这个支付通知后,会进行一些签名的验签和订单的处理操作之类的,然后再响应回给微信系统,向微信支付系统应答我们对这个支付通知的处理情况。

(应答包括:接收支付通知成功(响应码:200或204)和接收失败(响应码:4xx或5xx))

正常就是告诉微信它发来的支付通知,我们这边已经收到了,并且通过支付通知的数据,处理完自己的核心业务了,并给微信支付平台一个成功的应答。

现在就是做图片中的这步:

微信平台异步通知商户支付结果,商户端告知支付通知的接收情况。

在这里插入图片描述

在这里插入图片描述

需求1:应答

这里是接收微信支付系统发来的商户支付后的支付通知,这边先不做验签和订单处理,先简单的写几个应答情况给微信支付平台看应答效果

应答情况分为:

接收支付通知成功则返回响应码200或204

接受支付通知失败则返回响应码5xx或4xx

支付通知:

在这里插入图片描述

代码:

这里是接收微信支付系统发来的商户支付后的支付通知,这边先不做验签和订单处理,先简单的测试应答情况给微信支付平台

在这里插入图片描述

WxPayController 的这个 nativeNotify() 方法是让微信支付平台来调用的,不是我们去调用的

路径的来源如图:之前调用微信平台的下单接口时,发送过去的我们定义的一个回调支付通知地址。

当我们支付成功后哦,微信平台就会通过这个地址,把支付情况发送给我们。

在这里插入图片描述

测试:

如图,发来的数据是加密的,后续需要对微信发来的结果进行验签,验签成功再用对称加密的密钥对数据进行解密。

在这里插入图片描述

测试:应答不符合规范

支付通知:

在这里插入图片描述
当响应给微信支付系统的响应码是错误的201时,属于应答不符合规范。

微信认为通知失败,因为我们要么没收到通知,要么处理通知失败,所以微信会通过一定的策略定期重新发起通知

在这里插入图片描述

测试:应答出错

在这里插入图片描述

测试:应答超时

回调处理逻辑注意事项
在这里插入图片描述

让线程睡眠5秒,然后微信支付那边会因为超时没有收到商户的应答,而认为商户这边没有收到通知或通知处理失败,就会继续按规则重复发送通知

在这里插入图片描述

需求2:验签

APIv3证书与密钥使用说明:

自定义一个针对微信平台发来的request类型的支付通知的签名,进行验签操作的工具类。

因为微信支付端并没有给我们提供默认的集成在SDK内部的不用我们自己编写的签名验证,所以我们要自己编写一个【针对微信发来的请求的一个签名验证】。

就是做图片中的这一步。
在这里插入图片描述

先看一个jar包里面的针对响应的一个签名验证工具类,参考SDK源码中的 WechatPay2Validator 创建通知验签工具类 WechatPay2ValidatorForRequest

我们要弄一个针对微信支付系统发来的请求来进行验证的工具类,就可以模仿这个工具类

在这里插入图片描述

构造验签名串:

在这里插入图片描述

代码:
WxPayController

在这里插入图片描述

在这里插入图片描述

WechatPay2ValidatorForRequest

自定义验签工具类
验签工具类–验的是微信支付平台发给商户端的支付通知,
属于request请求

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

测试:

在这里插入图片描述

完整代码:
WxPayController
@CrossOrigin //跨域
@RestController
@RequestMapping("/api/wx-pay")
@Api(tags = "网站微信支付API") //swagger 注解
@Slf4j
public class WxPayController
{
    @Resource
    private WxPayService wxPayService;

    //获取签名验证器需要的类
    @Resource
    private Verifier verifier;

    //调用统一下单API,生成支付二维码的链接和订单号
    //swagger注解
    @ApiOperation("调用统一下单API,生成支付二维码")
    @PostMapping("/native/{productId}")
    public R nativePay(@PathVariable Long productId) throws Exception
    {
        log.info("发起支付请求");
        //返回支付二维码的链接和订单号
        Map<String, Object> map = wxPayService.nativePay(productId);
        return R.ok().setData(map);
    }

    /*
     *  接收并处理完微信发来的通知后,需要给微信支付系统应答我们的处理情况
     *  告诉微信它发来的支付成功的通知,我们这边已经收到了,并且处理完自己的核心业务了,现在可以给微信一个成功的应答了。
     */
    //当我们支付后,微信支付系统会通过我们之前给的notify_url路径,把支付的结果响应回来
    //通知接口(回调通知)---接收微信服务器给我们发来的请求
    //request: 接收微信支付端发来的支付通知的请求的参数数据 , response: 响应回给微信支付端的应答
    @PostMapping("/native/notify")
    public String nativeNotify(HttpServletRequest request, HttpServletResponse response)
    {
        Gson gson = new Gson();
        //创建一个应答对象
        HashMap<String, String> map = new HashMap<>();
        try
        {
            //处理微信支付系统响应回来的通知参数
            String body = HttpUtils.readData(request);

            //gson.fromJson()是Gson库提供的一个方法,用于将 JSON 字符串转换为 Java 对象
            Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
            //微信支付端发来的支付通知的请求的id
            String requestId = (String) bodyMap.get("id");

            log.info("支付通知的id ====> {}", bodyMap.get("id"));

            //log.info("支付通知的完整数据 ====> {}", body);
            //todo:签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest =
                    new WechatPay2ValidatorForRequest(verifier, requestId,body);
            //判断验签是否成功
            if (!wechatPay2ValidatorForRequest.validate(request))
            {
                log.error("通知验签失败");
                //如果验签不通过,就给个失败的应答
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "微信支付系统发给商户的通知,验签失败");
                return gson.toJson(map);
            }
            log.info("签名验证成功");

            //todo :处理订单
            /*
             * 通知应答
             * 接收成功: HTTP应答状态码需返回200或204,无需返回应答报文。
             * 接收失败: HTTP应答状态码需返回5XX或4XX,同时需返回应答报文,格式如下
             */
            //Thread.sleep(10000); //单位:毫秒, 1000毫秒=1秒,这里是睡眠10秒钟
            //TimeUnit.SECONDS.sleep(6); //让线程睡眠超过5秒
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "成功");
            return gson.toJson(map);
        } catch (Exception e)
        {
            e.printStackTrace();
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "失败");
            return gson.toJson(map);
        }
    }


}
WechatPay2ValidatorForRequest

验签工具类–验的是微信支付平台发给商户端的支付通知,
属于request请求

package cn.ljh.paymentdemo.util;

import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
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.*;

//这个类用来验证微信支付系统发给商户的支付通知的签名,就是验签
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;

    //参数1:获取签名验证器  参数2:微信支付系统发来的那个请求通知的id    参数3:微信支付系统响应回来的通知参数
    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);

            //利用verifier这个对象,进行验签的具体操作
            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);
            }
        }

        //header 就是这个---> WECHAT_PAY_TIMESTAMP 时间戳,用于判断请求是否过期
        String timestampStr = header;
        try
        {
            //通过时间戳创建一个基于时间戳那个时间的时间对象
            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
            // 如果时间戳过期了,就拒绝过期请求,  创建一个基于此时此刻的时间对象--> Instant.now()
            //比较这两个时间对象 --> responseTimeh 和 Instant.now() ,如果比较的时间大于5分钟,就会认为这是一个过期的请求,那么就拒绝
            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";
    }
}

总结上面支付通知的流程:

流程:

1、商户端向微信平台发起支付请求。

2、微信平台则对该请求进行响应,响应的内容是一个订单编号和支付二维码的 URL 。

3、商户端这边接收到支付二维码的URL,通过这个URL获取支付二维码图片,并进行支付。

4、商户端进行支付之后,微信平台会自动发起一个请求,请求的内容是商户端支付的结果,就是支付通知。

5、商户端这边对该支付通知进行验签,得到该请求的数据并进行一些业务操作,然后给微信支付平台应答,告诉其自己已经成功收到该通知了。

应答的内容也包括:接收通知成功和接收通知失败两种。

6、如果给微信支付平台的应答是接受通知失败的状态码。那么微信支付平台就会根据规则,在一段事件内重复发送支付通知给客户端。当然,这个重复发送通知的作用,只是为了提高商户端那边能成功接收到通知的概率(比如商户端接收通知失败的原因可以有网络原因),但是不保证一定成功。

在这里插入图片描述

需求3:参数解密

签名和验签的理解:

商户端发送一个支付请求给微信支付平台:
商户端需要将请求中的一些敏感参数,通过微信支付平台提供的平台证书的公钥,进行加密,变成密文,然后再对整个请求进行签名加密,

微信支付平台接收到请求后:
1、需要先对签名进行验签,作用就是判断发过来的请求是否被篡改过。
2、如果没篡改过,就从该请求中获取数据密文ciphertext(就是加密了的那些参数存放的地方),再用微信平台自己的私钥(对称加密秘钥),对请求中的数据密文进行解密,解密成明文,然后使用该参数数据进行业务操作。

反过来,微信支付平台发送请求到商户端,也是一样的操作

如何加密解密敏感信息

在这里插入图片描述

在这里插入图片描述

需求:

商户端这边,对微信平台发来的支付通知中的密文(通知参数)进行解密。

APIv3证书与密钥使用说明

在这里插入图片描述

在这里插入图片描述

加密的数据就存在这个ciphertext里面,我们要把它解密出来

密文就在这里:支付通知中的resource中的ciphertext

在这里插入图片描述

在这里插入图片描述

代码:
WxPayController

在这里插入图片描述

WxPayService

在这里插入图片描述

WxPayServiceImpl

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

测试:

每次测试都要记得看ngrok是否已经过期了,要重新生成隧道–就是配置文件中的接收结果通知地址wxpay.notify-domain,不然的话,微信支付系统在回调方法的时候,调用不到这个nativeNotify()方法

cmd打开ngrok,输入这个:ngrok http 8090 命令创建一条隧道。

在这里插入图片描述

成功把参数密文解密成明文
在这里插入图片描述

需求4:处理订单

需求:

更新订单的支付状态 和 记录支付日志
**更新订单的支付状态:**就是更新未支付、已支付这些状态。
当我们支付成功并且收到微信支付平台发来的支付通知时,获取支付通知里面的支付状态,并更新设置到该订单表里面。

记录支付日志就是有一个专门的表用来记录支付的记录,从支付通知中获取的参数,得到想要的数据封装成一个PaymentInfo 支付记录对象,存到数据库表 t_payment_info 里面

支付通知

在这里插入图片描述

记录支付日志用到的参数
在这里插入图片描述

代码:

还是属于在支付通知的方法上面继续完善功能。

WxPayController

在这里插入图片描述

WxPayServiceImpl

在这里插入图片描述

更新订单状态

在这里插入图片描述

记录支付日志

记录支付日志的对象
在这里插入图片描述

记录支付日志的方法
在这里插入图片描述

在这里插入图片描述

测试:

成功更新订单状态和记录一条支付日志到指定表 t_payment_info 中。

在这里插入图片描述

更新订单状态
在这里插入图片描述

记录支付的日志记录

在这里插入图片描述

需求5:处理重复通知 (接口调用的幂等性)

处理重复通知:

问题产生原因:

商户端进行支付之后,微信支付平台就会返回一个支付通知,商户端这边接收到通知后,就会修改订单的支付状态,以及添加一条支付记录的日志,然后就会应答回给微信支付平台,说自己已经成功接收到支付通知了。

但是,商户端收到支付结果通知,需要在5秒内返回应答报文,否则微信支付会认为通知失败,后续会重复发送通知。

**问题来了:**商户端这边每次接收到通知,都会修改一下订单的状态和记录一条支付日志。在微信支付那边重复发送通知的情况下,修改订单状态因为每次都是修改支付成功,所以修改多次没影响,但是记录支付日志,如果重复接收到支付通知,那么就会重复的添加日志记录,原本只有一条的记录,会变成记录了很多条。

**影响场景:**如果这个添加日志记录有顺便添加积分之类的功能,就会导致原本只加10积分,变成加了几十上百的积分。

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

需求:

处理重复通知:---------- 处理因商户端没及时应答而导致微信支付端重复发送支付通知而导致出现多条重复的支付日志记录的情况

要实现接口调用的幂等性:就是无论接口被调用多少次,产生的结果都是一致的。

支付通知:

在这里插入图片描述

思路:

很简单。

每次的接口调用,都先根据商品的订单号查询该商品的订单状态,如果订单状态不是【未支付】,说明该订单已经处理过支付通知了(就是处理过日志记录这些操作了)。

那么就直接return结束这个接口方法的执行就可以了。

代码:

在这个接收微信支付通知的方法里面,弄个睡眠。
在这里插入图片描述

判断订单的支付状态,如果不是未支付,表示这个订单已经处理过了,那么就直接结束这个方法的执行。
在这里插入图片描述

测试:

成功,无论因为应答不及时,而导致微信支付多次调用这个处理支付通知的接口,最终的结果都是支付日志记录只有一条,保证了接口调用的幂等性。

如图,可以看出该接口被多次调用

在这里插入图片描述

成功,虽然支付通知的接口被多次调用,但是同一个订单的日志记录始终只有一条,也只能有一条。
在这里插入图片描述

需求6:数据锁(可重入锁)

问题产生的原因:

模拟第一次处理通知时,因为网络问题,在商户端没有及时应答的情况下,微信支付平台定时重复发起支付通知,然后刚好多个调用这个接口的线程一起要执行这一步,就会导致出现多条支付日志记录。

(但是我演示的时候,并没有出现多条支付日志记录,但还是会出现这个问题)

就是微信支付系统多次调用这个支付通知的方法,有可能会同时执行这个记录支付日志的方法导致出现多条同样的日志记录

在这里插入图片描述

思路:

使用锁对数据进行并发控制。

使用可重入锁把这个判断和执行记录支付日志的方法包起来。

如果使用普通的synchronized锁,因为一个资源只能被一个线程占有,没释放之前其他线程是不能获取这个资源的。

可重入锁就是,我这个线程获取到这个资源的锁后,可以根据需要,再重复获取这个资源的锁,而且不会引发死锁。

在这里插入图片描述

代码:

在这里插入图片描述

处理订单的方法

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

测试:

因为写这锁之前,那个延时模拟并发通知的效果没出现,所以测试并没有测试出区别,跟需求5的处理重复通知一样的效果。

什么是可重入锁?

A线程要访问B共享资源:

正常情况的锁:A获取了B资源的锁,进行访问,然后根据需要,A线程再次获取这个B资源的锁再进行访问,正常情况是不行的,引发死锁,就是我占用了这个锁,又要再去获取这个锁,而我本身又因为线程没执行完,还占有这个锁,就造成了死锁。

可重入锁:就是在上面这种情况下,A已经持有B资源的锁了,又想再次获取B资源的锁,如果这个B资源方法是用可重入锁包起来的,那么就没问题了,因为可重入锁允许线程多次获取同一把锁,从而避免死锁的问题。

在这里插入图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: Vue、Spring Boot、微信H5支付、电商、Git 是一些常见的技术和工具。下面我将依次对它们进行解释和描述: Vue 是一个现代化的前端框架,采用了一种响应式的编程方式,可以轻松构建交互丰富、高能、可维护的web应用程序。Vue 提供了诸多的功能和特,例如组件化开发、虚拟DOM、数据双向绑定等,极大地提高了前端开发的效率。 Spring Boot 是一个轻量级、快速开发的Java后端框架,它简化了Spring应用程序的配置和部署,提供了自动化配置和快速启动的特。Spring Boot具有强大的生态系统和丰富的功能,可以方便地构建高效、安全且可扩展的后端应用程序。 微信H5支付微信公众平台提供的一种在线支付方式,可以通过微信支付接口实现在手机浏览器中进行支付。它支持用户在H5页面中通过微信进行支付,适用于电商、在线购物等需要在线支付的场景。微信H5支付具有便捷、安全的特点,可以提供良好的用户支付体验。 电商是指以电子商务技术为支撑,通过互联网进行商品、服务的交易和商务活动的行业。电商平台的建设包括商品展示、购物车、订单管理、支付处理等模块,需求复杂多样。Vue和Spring Boot可以结合使用,前端通过Vue来实现电商平台的用户界面,后端采用Spring Boot来开发后台接口处理业务逻辑。 Git 是一个分布式版本控制系统,用于保存和管理软件开发过程中的各个版本。团队可以通过Git来协同开发,追踪代码变更、回滚、合并等。在开发电商平台时,Git助力多人合作开发,有效地管理代码库,解决并发开发、版本控制的问题。团队成员可以通过Git来共享和提交自己的代码,并通过分支管理功能来进行并行开发和版本控制。 总结来说,Vue是一个前端框架,Spring Boot是一个后端框架,微信H5支付是一种在线支付方式,电商是基于电子商务技术的行业,Git是一个用于版本控制的工具。这些技术和工具可以相互结合,用于开发电商平台,并实现高效、可靠的软件开发和交付。 ### 回答2: Vue是一种流行的前端框架,它使用JavaScript和HTML来构建交互式的用户界面。它具有简单易学、灵活强以及能优化等优势,因此在前端开发中得到了广泛的应用。 Spring Boot是一个用于简化基于Java的应用程序开发的框架。它提供了开箱即用的功能,使开发人员能够快速构建高效、可扩展的应用程序。Spring Boot还具有自动化配置和简化部署等特,使开发过程更加便捷。 微信H5支付是一种在线支付方式,允许用户在手机端使用微信进行购物支付。它具有支付安全、使用便捷以及适用范围广泛的特点,已经成为电商领域中非常常见的支付方式。 Git是一个分布式版本控制系统,它用于跟踪文件的修改和历史记录。在开发过程中,开发人员可以使用Git来管理代码版本,通过分支、合并等功能来协同开发和解决冲突。 综上所述,Vue和Spring Boot是用于构建应用程序的框架,微信H5支付是一种在线支付方式,而Git是用于版本控制的工具。它们在电商领域中都扮演着要角色,能够使开发人员更加高效地开发、部署和维护应用程序。 ### 回答3: Vue是一个用于构建用户界面的开源JavaScript框架,它采用了MVVM的架构模式,能够使开发者更高效地构建Web应用程序。Vue具有简单易学、灵活可扩展以及高能等特点,因此在前端开发中广受欢迎。 Spring Boot是一个基于Spring框架的快速开发框架,它通过内嵌的Web服务器、自动化配置和约定优于配置的原则,极大地简化了Java Web应用的开发工作。Spring Boot提供了丰富的开发工具和组件,使得开发人员可以更轻松地构建出高效、健壮的后端应用。 微信H5支付是指在移动端浏览器中使用微信支付功能的一种支付方式,用户可以通过浏览器直接打开商户的H5页面进行支付微信H5支付不需要下载或安装微信客户端,能够为电商平台提供更便捷的支付方式,提升用户体验并增加交易转化率。 Git是一个分布式版本控制系统,它可以记录和管理代码的版本变更。在团队协作开发中,Git可以帮助开发人员更好地进行代码管理、合并和回滚等操作,确保团队开发的代码始终处于一个稳定的状态。Git也是目前最流行的版本控制工具之一,被广泛应用于软件开发行业。 综上所述,Vue、Spring Boot、微信H5支付和Git分别代表了前端开发、后端开发、支付方式和版本控制等不同方面的技术和工具。结合这些技术和工具,我们可以构建出一个使用Vue作为前端框架,Spring Boot作为后端框架,集成微信H5支付的电商平台,并且通过Git进行代码版本管理和团队协作开发。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_L_J_H_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值