Java-微信扫码支付功能模式二实战案例

3 篇文章 0 订阅

摘要

最近因为项目需要微信扫码支付功能,在网上找了很久的微信扫码支付模式二的案例,发现很多要么都是代码不全,要么就是代码错误。经过查找许多资料以及编写测试后,终于成功解决微信扫码支付模式二。

模式二与模式一相比,流程更为简单,不依赖设置的回调支付URL。商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付。

微信扫码支付流程时序图
原生支付模式二时序图
业务流程说明:

(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。
(12)商户确认订单已支付后给用户发货。


Java代码案例

微信扫码支付操作流程:

  1. 后台设置公众账号ID appid、商户号 mch_id、随机字符串 nonce_str、签名 sign、商品描述(可让前台传入) body、附加数据(可略) attach、商户订单号(可随机生成/前台传入) out_trade_no、服务器ip地址 spbill_create_ip、商户号密钥 key、微信回调接口 notify_url、扫码支付交易类型 trade_type 。
  2. 前台提交订单传支付金额到后台,后台生成微信支付二维码链接(codeUrl)和商户订单号(out_trade_no)给前台。
  3. 设置微信回调方法和回调接口,让微信自动回调。
  4. 前台页面设置定时调用后台查询订单支付状态链接,查询订单状态(订单未支付/支付成功)。

微信商户平台的扫码支付回调链接设置:
这里使用的是微信Nature扫码支付模式二,不需要安装操作证书。设置微信商户平台的扫码支付回调链接,让微信自己调用回调方法。微信商户平台的扫码支付回调链接设置如下图:
微信商户平台的扫码支付回调链接设置

下面是微信扫码支付模块代码图:
微信扫码支付模块代码图

下方代码只需更改【微信公众账号appid、微信商户号mch_id、服务器ip地址spbill_create_ip、微信回调接口notify_url、商户号密钥key】,其他信息可根据实际信息更改。

案例微信支付接口:http://www.xxx.cn/wxpay/nativePay
案例微信回调接口:http://www.xxx.cn/wxpay/results
案例微信查询微信订单状态接口:http://www.xxx.cn/wxpay/query

WeiXinPayController类

package com.aaa.project.tool.WeChatPay.controller;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.XmlUtil;
import com.aaa.project.tool.CORS.Cors;
import com.aaa.project.tool.WeChatPay.utils.WeiXinUtil;
import com.aaa.project.tool.WeChatPay.utils.XMLUtil4jdom;
import org.jdom2.JDOMException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import org.apache.commons.lang3.StringUtils;

@Controller
@RequestMapping("/wxpay")
public class WeiXinPayController {

   private String appid="xxxxxxxxxxxxx";//此处填写微信公众账号ID
   private String mch_id="xxxxxxxxx";//此处填写微信商户号
   private String spbill_create_ip="xxx.xxx.xxx.xxx";//此处填写服务器ip地址
   private String notify_url= "http://www.xxx.cn/wxpay/results";//此处填写微信回调接口
   private String key="xxxxxxxxxxxxxx";//此处填写商户号密钥key

   private static final String UNIFIEDORDERURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
   private static final String ORDERQUERYURL = "https://api.mch.weixin.qq.com/pay/orderquery";

   @Autowired
   private RestTemplate restTemplate;
   
   /**
    * 微信支付
    * 支付的金额 paymoney
    * @return
    */
   @RequestMapping("/nativePay")
   @ResponseBody
   public Map<String, Object> nativePay( String paymoney,HttpServletResponse response, HttpServletRequest request){
      //随机字符串
      String nonce_str=RandomUtil.randomString(15);
      //商户订单号(随机生成)
      String out_trade_no=new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+RandomUtil.randomInt(6);
      /*配置微信支付基础信息参数*/
      Map<String, String> requestData = new HashMap<String, String>();
      requestData.put("appid", appid);//公众账号ID
      requestData.put("mch_id",mch_id );//商户号
      requestData.put("nonce_str",nonce_str);//随机字符串 32位以内
      // APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
      requestData.put("spbill_create_ip", spbill_create_ip);
      requestData.put("trade_type", "NATIVE");//交易类型 扫码支付
      /*配置微信支付自定义支付信息参数*/
      requestData.put("attach", "附加数据返回");
      requestData.put("body", "商品描述");//商品简单描述
      requestData.put("out_trade_no", out_trade_no);//商户订单号
      requestData.put("total_fee", WeiXinUtil.getMoney(paymoney));//标价金额 按照分(0.01)进行计算
      requestData.put("notify_url",notify_url);//通知地址 异步接收微信支付结果通知的回调地址必须外网访问 不能携带参数
      /*配置微信支付sign信息参数*/
      String sign =  WeiXinUtil.generateSign(requestData,key);
      String payUrl = UNIFIEDORDERURL;

      requestData.put("sign", sign);

      /*将map信息转换成String*/
      String mapToXmlStr = XmlUtil.mapToXmlStr(requestData, "xml");

      /*调用微信统一下单Api将mapToXmlStr作为参数*/
      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.APPLICATION_XML);
      HttpEntity<String> formEntity = new HttpEntity<>(mapToXmlStr, headers);
      ResponseEntity<String> postForEntity = restTemplate.postForEntity(payUrl, formEntity, String.class);

      //获取微信返回的信息
      String returnXmlString = postForEntity.getBody();
      Map<String, Object> xmlToMap = XmlUtil.xmlToMap(returnXmlString);
      String returnCode = (String)xmlToMap.get("return_code");
      Map<String, Object> map=new HashMap<>();
      map.put("out_trade_no",out_trade_no);
      if("SUCCESS".equals(returnCode)){
         String codeUrl = (String)xmlToMap.get("code_url");
         map.put("codeUrl",codeUrl);
         //返回数据(商户订单号out_trade_no、二维码链接codeUrl)
         return map;
      }
      return null;
   }

   /**
    * 微信平台发起的回调方法
    * 调用我们这个系统的这个方法接口,将扫描支付的处理结果告知我们系统
    * @throws JDOMException
    * @throws Exception
    */
   @RequestMapping("/results")
   public void weixinNotify(HttpServletRequest request, HttpServletResponse response) throws JDOMException, Exception{
      //读取参数
      InputStream inputStream ;
      StringBuffer sb = new StringBuffer();
      inputStream = request.getInputStream();
      String s ;
      BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
      while ((s = in.readLine()) != null){
         sb.append(s);
      }
      in.close();
      inputStream.close();

      //解析xml成map
      Map<String, String> m = new HashMap<String, String>();
      m = XMLUtil4jdom.doXMLParse(sb.toString());

      //过滤空 设置 TreeMap
      SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();
      Iterator it = m.keySet().iterator();
      while (it.hasNext()) {
         String parameter = (String) it.next();
         String parameterValue = m.get(parameter);

         String v = "";
         if(null != parameterValue) {
            v = parameterValue.trim();
         }
         packageParams.put(parameter, v);
      }

      Map<String, String> parseNotifyParameter = WeiXinUtil.parseNotifyParameter(request);
      String sign = WeiXinUtil.generateSign(parseNotifyParameter,key);//生成签名
      //判断签名是否正确
      if(sign.equals(parseNotifyParameter.get("sign"))){
         //处理业务开始
         String resXml = "";
         if("SUCCESS".equals((String)packageParams.get("result_code"))&&(String)packageParams.get("result_code")!=null){
            // 这里是支付成功
            //执行自己的业务逻辑,如果有数据库表,可以添加到数据库中
//          String mch_id = (String)packageParams.get("mch_id");
//          String openid = (String)packageParams.get("openid");
//          String is_subscribe = (String)packageParams.get("is_subscribe");
//          String out_trade_no = (String)packageParams.get("out_trade_no");
//          String total_fee = (String)packageParams.get("total_fee");

            //暂时使用最简单的业务逻辑来处理:只是将业务处理结果保存到session中
            //(根据自己的实际业务逻辑来调整,很多时候,我们会操作业务表,将返回成功的状态保留下来)
            request.getSession().setAttribute("_PAY_RESULT", "OK");

            System.out.println("支付成功");
            //通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
            resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                  + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
         } else {
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                  + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
         }
         //处理业务完毕
         BufferedOutputStream out = new BufferedOutputStream(
               response.getOutputStream());
         out.write(resXml.getBytes());
         out.flush();
         out.close();
      } else{
         System.out.println("通知签名验证失败");
      }
   }

   /**
    * 前台定时查询支付结果,由前台设置定时任务,查询订单状态信息
    * 商户订单号 out_trade_no
    * @return
    */
   @RequestMapping("/query")
   @ResponseBody
   public String query(String out_trade_no,HttpServletResponse response, HttpServletRequest request){
      //随机字符串
      String nonce_str=RandomUtil.randomString(15);
      if(StringUtils.isBlank(out_trade_no)){
         throw new RuntimeException("订单号不能为空!");
      }
      //配置微信支付基础信息参数
      Map<String, String> requestData = new HashMap<String, String>();
      requestData.put("appid", appid);//公众账号ID
      requestData.put("mch_id", mch_id);//商户号
      requestData.put("nonce_str", nonce_str);//随机字符串
      requestData.put("spbill_create_ip", spbill_create_ip);
      //配置微信支付查询订单号参数
      requestData.put("out_trade_no", out_trade_no);//商户订单号

      //配置微信支付查询sign信息参数
      String sign = WeiXinUtil.generateSign(requestData,key);//生成签名
      requestData.put("sign", sign);

      //将map信息转换成String
      String mapToXmlStr = XmlUtil.mapToXmlStr(requestData, "xml");
      //调用微信统一下单Api 将xml的String信息作为参数
      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.APPLICATION_XML);
      HttpEntity<String> formEntity = new HttpEntity<>(mapToXmlStr, headers);
      ResponseEntity<String> postForEntity = restTemplate.postForEntity(ORDERQUERYURL, formEntity, String.class);

      //获取微信返回的信息
      String returnXmlString = postForEntity.getBody();
      Map<String, Object> xmlToMap = XmlUtil.xmlToMap(returnXmlString);
       //获取微信返回的订单状态
      String trade_state_desc = (String)xmlToMap.get("trade_state_desc");
      String code="0";
      if("订单未支付".equals(trade_state_desc)){
         return code;
      }else if("支付成功".equals(trade_state_desc)){
         code="1";
         return code;
      }
      return code;
   }
}

RestTemplateConfig类

package com.aaa.project.tool.WeChatPay.resttemplate.config;

import com.aaa.project.tool.WeChatPay.resttemplate.support.CustomConnectionKeepAliveStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

@Configuration
public class RestTemplateConfig {
   @Bean
   public RestTemplate restTemplate(RestTemplateBuilder builder) {
      return builder
            .setConnectTimeout(Duration.ofMillis(100))
            .setReadTimeout(Duration.ofMillis(500))
            .requestFactory(this::requestFactory)
            .build();
   }
   
   @Bean
   public HttpComponentsClientHttpRequestFactory requestFactory() {
      PoolingHttpClientConnectionManager connectionManager =
            new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
      connectionManager.setMaxTotal(200);
      connectionManager.setDefaultMaxPerRoute(20);
      CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .evictIdleConnections(30, TimeUnit.SECONDS)
            .disableAutomaticRetries()
            // 有 Keep-Alive 认里面的值,没有的话永久有效
            //.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)
            // 换成自定义的
            .setKeepAliveStrategy(new CustomConnectionKeepAliveStrategy())
            .build();
      HttpComponentsClientHttpRequestFactory requestFactory =
            new HttpComponentsClientHttpRequestFactory(httpClient);
      return requestFactory;
   }
}

WXUser类(此类可有可无)

package com.aaa.project.tool.WeChatPay.resttemplate.model;

import java.io.Serializable;

public class WXUser implements Serializable{
   private String name;
    private Integer age;
   private String addr;
    public String getAddr() {
      return addr;
   }
   public void setAddr(String addr) {
      this.addr = addr;
   }
   public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}

CustomConnectionKeepAliveStrategy类

package com.aaa.project.tool.WeChatPay.resttemplate.support;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.http.HttpResponse;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import java.util.Arrays;

public class CustomConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy {
    private final long DEFAULT_SECONDS = 30;
       @Override
       public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
           return Arrays.asList(response.getHeaders(HTTP.CONN_KEEP_ALIVE))
                   .stream()
                   .filter(h -> StringUtils.equalsIgnoreCase(h.getName(), "timeout")
                           && StringUtils.isNumeric(h.getValue()))
                   .findFirst()
                   .map(h -> NumberUtils.toLong(h.getValue(), DEFAULT_SECONDS))
                   .orElse(DEFAULT_SECONDS) * 1000;
       }
}

WeiXinUtil类

package com.aaa.project.tool.WeChatPay.utils;

import cn.hutool.crypto.SecureUtil;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.*;
import java.util.Map.Entry;

public class WeiXinUtil {

   /**
    * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
    */
   public static String generateSign(Map<String, String> requestData,String key) {
      TreeMap<String, String> sortMapByKey = (TreeMap<String, String>) sortMapByKey(requestData);
      StringBuffer keyWithValue = splicingKeyAndValue(sortMapByKey);
      keyWithValue.append("key=" + key);
      String sign = SecureUtil.md5().digestHex(keyWithValue.toString(), "UTF-8");
      return sign;
   }
   
   /**
    * 拼接Map中的key和value通过&
    * @param sortMapByKey
    * @return
    */
   private static StringBuffer splicingKeyAndValue(TreeMap<String, String> sortMapByKey) {
      StringBuffer sb = new StringBuffer();
      Set<Entry<String, String>> es = sortMapByKey.entrySet();
      Iterator<Entry<String, String>> it = es.iterator();
      while (it.hasNext()) {
         Entry<String, String> entry = (Entry<String, String>) it.next();
         String k = (String) entry.getKey();
         String v = (String) entry.getValue();
         if (null != v && !"".equals(v) && !"sign".equals(k)
               && !"key".equals(k) && ! "sign_type".equals(k)) {
            sb.append(k + "=" + v + "&");
         }
      }
      return sb;
   }
   
   /**
     * Map 按key排序
     * @param oriMap
     * @return
     */
    public static Map<String, String> sortMapByKey(Map<String, String> oriMap) {  
        if (oriMap == null || oriMap.isEmpty()) {  
            return null;  
        }  
        Map<String, String> sortedMap = new TreeMap<String, String>();  
        sortedMap.putAll(oriMap);  
        return sortedMap;  
    }
    
   /**
    * 元转换成分
    * @param //money
    * @return
    */
   public static String getMoney(String amount) {
      if(amount==null){
         return "";
      }
      // 金额转化为分为单位
      String currency =  amount.replaceAll("\\$|\\¥|\\,", "");  //处理包含, ¥ 或者$的金额  
      int index = currency.indexOf(".");  
      int length = currency.length();  
      Long amLong = 0l;  
      if(index == -1){  
         amLong = Long.valueOf(currency+"00");  
      }else if(length - index >= 3){  
         amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));  
      }else if(length - index == 2){  
         amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);  
      }else{  
         amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");  
      }  
      return amLong.toString(); 
   }
   
    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        Map<String, String> data = new HashMap<String, String>();
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
        InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
        org.w3c.dom.Document doc = documentBuilder.parse(stream);
        doc.getDocumentElement().normalize();
        NodeList nodeList = doc.getDocumentElement().getChildNodes();
        for (int idx=0; idx<nodeList.getLength(); ++idx) {
            Node node = nodeList.item(idx);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                data.put(element.getNodeName(), element.getTextContent());
            }
        }
        try {
            stream.close();
        }
        catch (Exception ex) {    
        return data;
    }

   /**
    * 从request的inputStream中获取参数
    * @param request
    * @return
    * @throws Exception
    */
   public static Map<String, String> parseNotifyParameter(HttpServletRequest request) throws Exception {
      InputStream inputStream = request.getInputStream();
      ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
      byte[] buffer = new byte[1024];
      int length = 0;
      while ((length = inputStream.read(buffer)) != -1) {
         outSteam.write(buffer, 0, length);
      }
      outSteam.close();
      inputStream.close();
      // 获取微信调用我们notify_url的返回信息
      String resultXml = new String(outSteam.toByteArray(), "utf-8");
      Map<String, String> notifyMap = WeiXinUtil.xmlToMap(resultXml);
      return notifyMap;
   }
}

XMLUtil4jdom类

package com.aaa.project.tool.WeChatPay.utils;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class XMLUtil4jdom {

    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     * @param strxml
     * @return
     * @throws JDOMException
     * @throws IOException
     */
    public static Map doXMLParse(String strxml) throws JDOMException, IOException {
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
        if(null == strxml || "".equals(strxml)) {
            return null;
        }
        Map<String, String> m = new HashMap<String, String>();
        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while(it.hasNext()) {
            Element e = (Element) it.next();
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if(children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = XMLUtil4jdom.getChildrenText(children);
            }
            m.put(k, v);
        }
        //关闭流
        in.close();
        return m;
    }

    /**
     * 获取子结点的xml
     * @param children
     * @return String
     */
    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if(!children.isEmpty()) {
            Iterator it = children.iterator();
            while(it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if(!list.isEmpty()) {
                    sb.append(XMLUtil4jdom.getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }
        return sb.toString();
    }
}

最后

上面就是微信扫码支付模式二的Java代码案例,如有疑问都可以在下面评论留言,作者会帮忙解答。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值