java实现对接建行支付及其回调

最近公司要对接建行支付,也是查了很多资料,走了很多弯路,还问了建行的技术员,现把它记录下来,直接上代码。

调用支付所需常量

@Value("${ccb.MERCHANTID}")
    private String MERCHANTID;  //商户代码,固定写死的,需要申请
    //@Value("${ccb.POSID}")  //这里用yml提取出来就会报错..
    private String POSID = "xxxxx";           //商户柜台代码,固定写死的,需要申请
    @Value("${ccb.BRANCHID}")
    private String BRANCHID;  //分行代码,固定写死的,需要申请
    @Value("${ccb.PUB32TR2}")
    private String PUB32TR2; //公钥后30位
    private String BEGORDERID = "";
    private String ENDORDERID = "";
    private String QUPWD = "xxxxx";//这里写建行支付的商户密码
    //交易码 这个参数的值是固定的,不可以修改
    private String TXCODE = "xxxxx";
    /*必输项
    1页面形式
    2文件返回形式 (提供TXT和XML格式文件的下载)
    3 XML页面形式*/
    private String SEL_TYPE = "3";
    /*不知道干嘛用的*/
    private String CHANNEL = "";
    /*不知道干嘛用的*/
    private String OPERATOR = "";

    @Autowired
    private JinshiCCBPayMapper jinshiCCBPayMapper;

    @Override
    public JSONObject CCBPay(JSONObject jsonObject) {
        String CURCODE = "01";     //付款币种,固定写01 代表支付金额
        String TXCODE = "530550";  //由建行统一分配为530550
        String REMARK1 = "";
        String REMARK2 = "";
        String RETURNTYPE = "3"; // 返回类型,固定参数是3  ,代表是返回带url的支付信息
        String TIMEOUT = "";
        String ORDERID = String.valueOf(jsonObject.get("orderId"));    //订单号  由商户提供,最长40位,不能重复
        String PAYMENT = String.valueOf(jsonObject.get("payment"));//支付金额
        StringBuffer tmp = new StringBuffer();
        tmp.append("MERCHANTID=");
        tmp.append(MERCHANTID);
        tmp.append("&POSID=");
        tmp.append(POSID);
        tmp.append("&BRANCHID=");
        tmp.append(BRANCHID);
        tmp.append("&ORDERID=");
        tmp.append(ORDERID);
        tmp.append("&PAYMENT=");
        tmp.append(PAYMENT);
        tmp.append("&CURCODE=");
        tmp.append(CURCODE);
        tmp.append("&TXCODE=");
        tmp.append(TXCODE);
        tmp.append("&REMARK1=");
        tmp.append(REMARK1);
        tmp.append("&REMARK2=");
        tmp.append(REMARK2);
        tmp.append("&RETURNTYPE=");
        tmp.append(RETURNTYPE);
        tmp.append("&TIMEOUT=");
        tmp.append(TIMEOUT);
        tmp.append("&PUB=");
        tmp.append(PUB32TR2);
        Map map = new HashMap();
        map.put("CCB_IBSVersion", "V6");
        map.put("MERCHANTID", MERCHANTID);
        map.put("BRANCHID", BRANCHID);
        map.put("POSID", POSID);
        map.put("ORDERID", ORDERID);
        map.put("PAYMENT", PAYMENT);
        map.put("CURCODE", CURCODE);
        map.put("TXCODE", TXCODE);
        map.put("REMARK1", REMARK1);
        map.put("REMARK2", REMARK2);
        map.put("RETURNTYPE", RETURNTYPE);
        map.put("TIMEOUT", TIMEOUT);
        map.put("MAC", MD5.md5Str(tmp.toString()));
        // 这个url是建设银行指定的,尽量不要换
        String ret = HttpClientUtil.httpPost("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6", map);

        QrURLDemo qrURLDemo = JSON.parseObject(ret, QrURLDemo.class);
        // 这个url触发get请求会获取到一个新的页面
        String s = HttpClientUtil.httpGet(qrURLDemo.getPAYURL(), "UTF-8");

        //获取QRURL
        QrURLDemo qrURLDemo1 = JSON.parseObject(s, QrURLDemo.class);
        String decode = URLDecoder.decode(qrURLDemo1.getQRURL());
        String code = qrURLDemo1.getSUCCESS();
        // 安卓通过这个url就可以支付了
        JSONObject json = new JSONObject();
        json.put("decode",decode); //返回的这个decode就是支付所需跳转的url
        json.put("code",code); //这个是状态码
        //还会返回其他东西,我这里只需要这两个,所以只写了url和code
        return json;
    }
附 yml文件里配置的参数
ccb:
  # 商户代码,固定写死的,需要申请
  MERCHANTID: xxxxxxx
  POSID: xxxxxxx # 商户柜台代码,固定写死的,需要申请
  BRANCHID: xxxxxxx # 分行代码,固定写死的,需要申请
  CURCODE: xxxxxxx  #付款币种,固定写01 代表支付金额
  TXCODE: xxxxxxx  # 由建行统一分配为530550
  PUB32TR2: xxxxxxx # 公钥后30

微信支付界面
在这里插入图片描述
支付完成后,建行会自动调用回调地址,这个地址是在建行商户平台配置的,反馈有两种,网页反馈(方法:get)和服务器反馈(方法:post),请看下图
页面反馈:付款人付款完成后,点击“返回商户网站”按钮,触发页面反馈
服务器反馈:只要支付成功,无需触发,由建行支付网关,以post 方法,发信息给反馈URL
还分为网上银行和手机银行反馈,网上银行就是微信支付宝调用url支付,手机银行就是建行手机银行支付客户端,其实手机、网银,区分不是很严格,一般都设置成一样的,同一笔支付,可能会触发多渠道的同时反馈。所以,反馈机制,在响应的时候,是允许重复的。一般来说,服务器、页面,是写成两个不同的回调处理。或者,加个条件判断,同时允许post和get,也行。写成一个,就不太好判断反馈的来源了。写成两个,再通过日志,能区分反馈的来源。

在这里插入图片描述
支付完成后,出现这个页面,如果用户不点完成的话,直接左上角叉叉掉,服务器反馈有,页面反馈没有

付款这个环节不能携带任何参数显示出来
在这里插入图片描述
我这里页面反馈和服务器反馈写的不同方法,请求方式不同,服务器反馈主要是操作数据库,更新支付成功信息到数据库,页面反馈主要是用来展示成功的页面,订单信息等等展示给用户

/**
     * 支付回调(页面反馈 get)付款人付款完成后,点击“返回商户网站”按钮,触发页面反馈。
     *
     * @return
     */
    @GetMapping("/payCallBackForPage")
    @ResponseBody
    public SuccessVo payCallBackForPage(PayCallBackEntity payCallBackEntity,
                                        HttpServletResponse response) throws Exception {
        System.out.println("payCallBackEntity = " + payCallBackEntity);
        String success = payCallBackEntity.getSUCCESS();
        String orderId = payCallBackEntity.getORDERID();
        String payment = payCallBackEntity.getPAYMENT();
        System.out.println("success: -" + success);
        System.out.println("orderId: -" + orderId);
        if ("Y".equals(success)) {
            Map<String, Object> map = jinshiCCBPayService.selectByOrderId(orderId);
            Object returnCode = map.get("returnCode");
            Object returnMsg = map.get("returnMsg");
            logger.info("returnCode:  " + returnCode);
            logger.info("returnMsg:  " + returnMsg);
            if ("000000".equals(returnCode)) {
                //返回 "000000" 说明此订单号已支付成功
                //写支付成功以后的操作
                //todo
            }
        } else {
        	//支付失败
        }
        return new SuccessVo();
    }

其中用到了根据订单号查询订单的方法,如下:

@Override
    public Map<String, Object> selectByOrderId(String orderId) {
        //订单号  如果有了订单号,下面的 ORDERDATE BEGORDERTIME ENDORDERTIME 就无效了..所以就置空了.
        String ORDERID = orderId;
        String ORDERDATE = "20200114";  // 因为有ORDERID,所以这个字段无效了,但是不能删
        String BEGORDERTIME = "00:00:00";// 因为有ORDERID,所以这个字段无效了,但是不能删
        String ENDORDERTIME = "23:59:59";// 因为有ORDERID,所以这个字段无效了,但是不能删
        //txcode=410408
		/* 流程类型
		必输项
		0支付流水
		1退款流水*/
        String TYPE = "0";
		/*必输项(当日只有未结算流水可供查询)
		0 未结算流水
		1 已结算流水*/
        String KIND = "1";
		/*必输项
		0失败
		1成功
		2不确定
		3全部(已结算流水查询不支持全部)*/
        String STATUS = "1";
        //页码必输项,输入将要查询的页码。
        String PAGE = "1";
        String xmlString = this.getStringByHttpClient(ORDERDATE, BEGORDERTIME, ENDORDERTIME, ORDERID, TYPE, KIND, STATUS, PAGE);
        Document document = (Document) this.getDocumentByXMLStr(xmlString);
        List<QUERYORDER> queryorders = document.getQUERYORDER();
        Map<String, Object> resultMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(queryorders)) { // 如果没有查询到定单就是空的
            QUERYORDER queryOrder = queryorders.get(0); //因为只有一个结果,所以就获取索引为0的元素
            resultMap.put("queryOrder", queryOrder);
        }
        //不管查询成功查询失败都会有这个消息
        resultMap.put("returnCode", document.getRETURN_CODE());
        resultMap.put("returnMsg", document.getRETURN_MSG());
        return resultMap;
    }
    /**
     * 发送请求获取 String格式的字符串
     *
     * @param ORDERDATE
     * @param BEGORDERTIME
     * @param ENDORDERTIME
     * @param ORDERID
     * @param TYPE
     * @param KIND
     * @param STATUS
     * @param PAGE
     * @return string 格式的xml
     */
    private String getStringByHttpClient(Object ORDERDATE, Object BEGORDERTIME, Object ENDORDERTIME, String ORDERID, String TYPE, String KIND, String STATUS, String PAGE) {
        String param = "MERCHANTID=" + MERCHANTID + "&BRANCHID=" + BRANCHID + "&POSID=" + POSID + "&ORDERDATE=" + ORDERDATE + "&BEGORDERTIME=" + BEGORDERTIME
                + "&ENDORDERTIME=" + ENDORDERTIME + "&BEGORDERID=" + BEGORDERID + "&ENDORDERID=" + ENDORDERID + "&QUPWD=&TXCODE=" + TXCODE
                + "&SEL_TYPE=" + SEL_TYPE + "&OPERATOR=" + OPERATOR;
        if ("410408".equals(TXCODE)) {
            param = "MERCHANTID=" + MERCHANTID + "&BRANCHID=" + BRANCHID + "&POSID=" + POSID + "&ORDERDATE="
                    + ORDERDATE + "&BEGORDERTIME=" + BEGORDERTIME + "&ENDORDERTIME=" + ENDORDERTIME + "&ORDERID="
                    + ORDERID + "&QUPWD=&TXCODE=" + TXCODE + "&TYPE=" + TYPE + "&KIND=" + KIND + "&STATUS=" + STATUS +
                    "&SEL_TYPE=" + SEL_TYPE + "&PAGE=" + PAGE + "&OPERATOR=" + OPERATOR + "&CHANNEL=" + CHANNEL;
        }
        Map map = new HashMap();
        map.put("MERCHANTID", MERCHANTID);
        map.put("BRANCHID", BRANCHID);
        map.put("POSID", POSID);
        map.put("ORDERDATE", ORDERDATE);
        map.put("BEGORDERTIME", BEGORDERTIME);
        map.put("ENDORDERTIME", ENDORDERTIME);
        map.put("BEGORDERID", BEGORDERID);
        map.put("ENDORDERID", ENDORDERID);
        map.put("QUPWD", QUPWD);
        map.put("TXCODE", TXCODE);
        if ("410408".equals(TXCODE)) {
            map.put("TYPE", TYPE);
            map.put("KIND", KIND);
            map.put("STATUS", STATUS);
            map.put("ORDERID", ORDERID);
            map.put("PAGE", PAGE);
            map.put("CHANNEL", CHANNEL);
        }
        map.put("SEL_TYPE", SEL_TYPE);
        map.put("OPERATOR", OPERATOR);
        map.put("MAC", MD5.md5Str(param));
        // 调用银行的接口 基本是固定的地址
        String s = HttpClientUtil.httpPost("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?", map);
        //删除字符串防止解析xml报错
        s = s.replaceAll("\\n", "").
                replaceAll("\\t", "").
                replaceAll("\\r", "");
        return s;
    }

    /**
     * 从 string 格式的xml 里面提出取出 Document 实体类
     * @param ret string 格式的xml
     * @return
     */
    private Object getDocumentByXMLStr(String ret) {
        XStream xStream = new XStream();
        xStream.alias("DOCUMENT", Document.class);
        xStream.processAnnotations(Document.class);
        XStream.setupDefaultSecurity(xStream);
        xStream.allowTypesByWildcard(
                new String[]{"com.xxxxx.**"}
        );
        return xStream.fromXML(ret);
    }

以下的实体类省略getset方法
Document 实体类

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;

import java.util.List;
@XStreamAlias("DOCUMENT")
public class Document {
	@XStreamImplicit(itemFieldName ="QUERYORDER")
	private List<QUERYORDER> QUERYORDER ; // 查询订单

	private String RETURN_CODE;   // 返回状态码
	private String RETURN_MSG;   // 返回消息
	private String CURPAGE;   //当前页
	private String PAGECOUNT;  //总页数
	private String TOTAL;   //总数
	private String PAYAMOUNT;  //付款方式
	private String REFUNDAMOUNT;  //  修正???
}

PayCallBackEntity 实体类

/**
 * 建行支付回调实体类
 */
public class PayCallBackEntity {
	
	private String POSID; //商户柜台代码
	private String BRANCHID;//分行代码
	private String ORDERID; //定单号
	private String PAYMENT; //付款金额
	private String CURCODE;  //币种
	private String REMARK1;  //备注一
	private String REMARK2;  //备注二
	private String ACC_TYPE;  //账户类型  服务器通知中有此字段返回且参与验签
	private String SUCCESS;  //成功标志  成功-Y,失败-N
	private String TYPE;   //接口类型  分行业务人员在P2员工渠道后台设置防钓鱼的开关。 1.开关关闭时,无此字段返回且不参与验签 2.开关打开时,有此字段返回且参与验签。参数值为 1-防钓鱼接口

	private String REFERER;  //Referer信息  分行业务人员在P2员工渠道后台设置防钓鱼开关。 1.开关关闭时,无此字段返回且不参与验签。 2.开关打开时,有此字段返回且参与验签
	private String CLIENTIP;  //客户端IP  分行业务人员在P2员工渠道后台设置防钓鱼的开关。 1.开关关闭时,无此字段返回且不参与验签 2.开关打开时,有此字段返回且参与验签。参数值为 客户在建行系统中的IP
	private String ACCDATE;  //系统记账日期  商户登陆商户后台设置返回记账日期的开关 1.开关关闭时,无此字段返回且不参与验签。 2.开关打开时,有此字段返回且参与验签。参数值格式为YYYYMMDD(如20100907)。
	private String USRMSG; //支付账户信息  分行业务人员在P2员工渠道后台设置防钓鱼开关和返回账户信息的开关。 1.开关关闭时,无此字段返回且不参与验签。2.开关打开但支付失败时,无此字段返回且不参与验签。3.开关打开且支付成功时,有此字段返回且参与验签。无PAYTYPE返回时,参数值格式如下:“姓名|账号加密后的密文”。有PAYTYPE返回时,该参数值为空。
	private String USRINFO;   //客户加密信息   分行业务人员在P2员工渠道后台设置防钓鱼开关和客户信息加密返回的开关。 1.开关关闭时,无此字段返回且不参与验签
	private String PAYTYPE;  //支付方式   ALIPAY:支付宝 WEIXIN:微信 为空:建行龙支付 该字段有返回时参与验签,无此字段返回时不参与验签。
	private String SIGN;  //数字签名
}

httpclient工具类

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

public class HttpClientUtil {


	public static String httpReader(String url, String code){
		System.out.println("GetPage:"+url);
		
		HttpClient client = new HttpClient();
		GetMethod method = new GetMethod(url);
		
		String result = null;
		try {
			client.executeMethod(method);
			int status = method.getStatusCode();
			if (status == HttpStatus.SC_OK) {
				result = method.getResponseBodyAsString();
			} else {
				System.out.println("Method failed: " + method.getStatusLine());
			}
		} catch (HttpException e) {
			System.out.println("Please check your provided http address!");
			e.printStackTrace();
		} catch (IOException e) {
		
			e.printStackTrace();
		} finally{
			if(method!=null)method.releaseConnection();
			method = null;
			client = null;
		}
		return result;
	}
	
	public static String httpGet(String url,String code) {
		System.out.println("GetPage:"+url);
		String content = null;
		HttpClient httpClient = new HttpClient();
		httpClient.getParams().setParameter(HttpMethodParams.USER_AGENT,"Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.2) Gecko/20090803 Fedora/3.5.2-2.fc11 Firefox/3.5.2");
		GetMethod method = new GetMethod(url);
		try {
			int statusCode = httpClient.executeMethod(method);
			System.out.println("httpClientUtils::statusCode="+statusCode);
			System.out.println(method.getStatusLine());
			content = new String(method.getResponseBody(), code);
			
		} catch (Exception e) {
			System.out.println("time out");
			e.printStackTrace();
		} finally {
			if(method!=null)method.releaseConnection();
			method = null;
			httpClient = null;
		}
		return content;
	}
	
	public static String httpPost(String url, Map paramMap, String code) {
		System.out.println("GetPage:"+url);
		String content = null;
		if (url == null || url.trim().length() == 0 || paramMap == null
				|| paramMap.isEmpty())
			return null;
		HttpClient httpClient = new HttpClient();

		httpClient.getParams().setParameter(HttpMethodParams.USER_AGENT,"Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.2) Gecko/20090803 Fedora/3.5.2-2.fc11 Firefox/3.5.2");//

		
		PostMethod method = new PostMethod(url);
		Iterator it = paramMap.keySet().iterator();
		

		while (it.hasNext()) {
			String key = it.next() + "";
			Object o = paramMap.get(key);
			if (o != null && o instanceof String) {
				method.addParameter(new NameValuePair(key, o.toString()));
			}
			if (o != null && o instanceof String[]) {
				String[] s = (String[]) o;
				if (s != null)
					for (int i = 0; i < s.length; i++) {
						method.addParameter(new NameValuePair(key, s[i]));
					}
			}
		}
		try {
			
			int statusCode = httpClient.executeMethod(method);
			
			System.out.println("httpClientUtils::statusCode="+statusCode);

			System.out.println(method.getStatusLine());
			content = new String(method.getResponseBody(), code);
			
		} catch (Exception e) {
			System.out.println("time out");
			e.printStackTrace();
		} finally {
			if(method!=null)method.releaseConnection();
			method = null;
			httpClient = null;
		}
		return content;

	}

	public static String httpPost(String url, Map paramMap) {

		return HttpClientUtil.httpPost(url, paramMap, "UTF-8");
	}
}

MD5工具类

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5 {

	public static String md5Str(String str) {
		if (str == null) return "";
		return md5Str(str, 0);
	}

	public static String md5Str(String str, int offset) {
		try {
			MessageDigest md5 = MessageDigest.getInstance("MD5");
			byte[] b = str.getBytes("UTF8");
			md5.update(b, offset, b.length);
			return byteArrayToHexString(md5.digest());
		} catch (NoSuchAlgorithmException ex) {
			ex.printStackTrace();
			return null;
		} catch (UnsupportedEncodingException ex) {
			ex.printStackTrace();
			return null;
		}
	}

	/**
	 * @param b byte[]
	 * @return String
	 */
	public static String byteArrayToHexString(byte[] b) {
		String result = "";
		for (int i = 0; i < b.length; i++) {
			result += byteToHexString(b[i]);
		}
		return result;
	}

	private static String[] hexDigits =
			{
					"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b",
					"c", "d", "e", "f"};


	public static String byteToHexString(byte b) {
		int n = b;
		if (n < 0) {
			n = 256 + n;
		}
		int d1 = n / 16;
		int d2 = n % 16;
		return hexDigits[d1] + hexDigits[d2];
	}
}

QrURLDemo 实体类

/**
 * 建行无感支付实体类
 */
public class QrURLDemo {

	private String SUCCESS;
	private String PAYURL;
	private String QRURL;  //安卓点这个会直接跳到支付页面
}

QUERYORDER 实体类

public class QUERYORDER {
	private String MERCHANTID;  //商户代码
	private String BRANCHID;  //分行代码
	private String POSID;  //柜台号
	private String ORDERID;  //订单号
	private String ORDERDATE;  //订单支付的时间
	private String ACCDATE;  //访问日期记录
	private String AMOUNT;  //支付金额
	private String STATUSCODE;  //状态码
	private String STATUS; //交易状态  支付成功的话, 会返回 "成功"
	private String REFUND;  //退税???
	private String SIGN;  //签名
}
  • 17
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 53
    评论
对接微信支付,你需要使用微信支付提供的开发工具包(SDK)来实现支付功能。以下是一个简单示例,展示如何使用Java代码对接微信支付: ```java import com.github.wxpay.sdk.WXPay; import com.github.wxpay.sdk.WXPayConfig; import com.github.wxpay.sdk.WXPayConstants; import com.github.wxpay.sdk.WXPayUtil; import java.util.HashMap; import java.util.Map; public class WechatPaymentProgram { public static void main(String[] args) { // 商户号 String mchId = "your_mch_id"; // 商户密钥 String key = "your_mch_key"; // 应用ID String appId = "your_app_id"; // 回调URL String notifyUrl = "your_notify_url"; // 创建配置对象 WXPayConfig config = new WXPayConfig() { @Override public String getAppID() { return appId; } @Override public String getMchID() { return mchId; } @Override public String getKey() { return key; } }; // 创建WXPay对象 WXPay wxPay = new WXPay(config, WXPayConstants.SignType.MD5); // 创建订单参数 Map<String, String> data = new HashMap<>(); data.put("body", "商品描述"); data.put("out_trade_no", "商户订单号"); data.put("total_fee", "订单金额(单位为分)"); data.put("spbill_create_ip", "用户IP地址"); data.put("notify_url", notifyUrl); data.put("trade_type", "NATIVE"); // 扫码支付 try { // 调用统一下单接口 Map<String, String> resp = wxPay.unifiedOrder(data); // 判断返回结果 if ("SUCCESS".equals(resp.get("return_code")) && "SUCCESS".equals(resp.get("result_code"))) { String qrCodeUrl = resp.get("code_url"); // 获取支付二维码链接 System.out.println("请扫描以下二维码进行支付:" + qrCodeUrl); // TODO: 根据实际情况处理支付结果通知等逻辑 } else { System.out.println("下单失败:" + resp.get("return_msg")); } } catch (Exception e) { e.printStackTrace(); } } } ``` 在这个示例中,你需要替换代码中的 `your_mch_id`、`your_mch_key`、`your_app_id`和 `your_notify_url` 分别为你的微信商户号、商户密钥、应用ID和支付结果通知URL。然后根据实际情况填充订单参数,调用统一下单接口获取支付二维码链接,并输出给用户。最后根据微信支付结果通知的回调结果处理支付结果逻辑。 请注意,这只是一个简单的示例,并没有完整考虑所有的异常情况和业务逻辑。在实际应用中,你还需要处理用户扫码支付后的回调通知、订单查询、退款等功能。建议阅读微信支付官方文档和相关SDK的使用文档,了解完整的接口和流程。
评论 53
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值