微信公众号网页支付summer版

/2017年06月07日 14:51/ 分类:微信网页版/阅读:283
该文仅对于中间这种支付方式有参考价值哟 一、开发背景在微信公众号中,需要进行微信支付且为微信公众号网页支付。二、准备工作1.申请商户平台step1. 登录公众平台,点击左侧菜单中的微信支付,如图, step2. 进入支付申请,按照指引填写相关的信息并提交; step3. 资料审核通过后,支付宝...

该文仅对于中间这种支付方式有参考价值哟

一、开发背景

在微信公众号中,需要进行微信支付且为微信公众号网页支付。

二、准备工作

1.申请商户平台

step1. 登录公众平台,点击左侧菜单中的微信支付,如图,

step2. 进入支付申请,按照指引填写相关的信息并提交;

step3. 资料审核通过后,支付宝会向登记的银行帐号打一笔款,收到款后,两次登录到公众平台填写正确的数字,签约成功,如图,

注意:在申请支付功能的时候,不要申请成了服务商,需要的是商户。虽然服务商也具备支付功能,但在调用 接口时会比商户复杂一点,申请过程也繁琐一点。另外服务商和商户是具备不同的业务功能的,常见的微商场用的更多是商户平台。对于服务商和商户的区别我理解的是,服务商是对商户提供技术支持的第三方团队。

finally ,  通过各种审核后,支付宝会向你的邮箱发送以下信息,保存好,在调用接口时会用到

2.配置支付参数

如下图,进入公众平台-微信支付-开发配置,设置支付授权目录及支付回调。
如果配置的是测试目录则还需要添加测试微信号进入测试白名单;

支付授权目录的要求:
1、所有使用公众号支付方式发起支付请求的链接地址,都必须在支付授权目录之下;
2、最多设置3个支付授权目录,且域名必须通过ICP备案;
3、头部要包含http或https,须细化到二级或三级目录,以左斜杠“/”结尾。

支付回调URl:头部要包括http或https,当公众平台接到扫码支付请求时,会回调此URL传递订单信息。
这里指的是支付成功后,支付宝服务器会直接调用商户系统的服务器对订单进行处理,比如修改状态等,用户是无感知的,可以当作一个方法,该方法中不支付session. 如果需要传递参数怎么办了,tell you later.

二、调用接口步骤

1)获取用户授权(获取openid, 获取方式可以参考微信公众号网页授权)

2)调用统一下单接口获取预支付id(prepay_id)

3)H5调起微信支付的内置JS

4)支付完成后,微信回调URL的处理

看文字觉得麻烦的同志请参考以下稍显形象的手绘图,

step1. 获取用户授权

参考 微信公众号网页授权summer版

step2. 获取Prepay_id

根据文档(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)要求,将传递的参数拼接成要求的xml格式到请求地址:https://api.mch.weixin.qq.com/pay/unifiedorder,获得prepay_id.

如果连图也懒得看的同志直接copy 代码吧,贴到eclipse慢慢晕,每个参数的意义都已经加以说明

highlight的参数注意一下,容易被坑。

在加密时还要注意变量key。这里提个问,整个过程中加密多少次?
String sign = PayCommonUtils.createSign("UTF-8",packageParams);

key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置

/**

* 构建微信统一支付请求

*/

public static SortedMap wechatPayment(HttpServletRequest request, HttpServletResponse response,) throws JDOMException, IOException {

SortedMap packageParams = new TreeMap();

//nonce_str

String currTime = CommonUtils.getCurrTime();

String strTime = currTime.substring(8, currTime.length());

String strRandom = CommonUtils.buildRandom(4) + "";

//10位序列号,可以自行调整。

String strReq = strTime + strRandom;

String nonceStr = strReq;

String signType = "MD5";

String spbillCreateIp = request.getLocalAddr();//客户IP

/* 设置package订单参数 */

packageParams.put("appid", Constants.APPID);//微信分配的公众账号ID

packageParams.put("mch_id", Constants.MCH_ID);//微信支付分配的商户号

packageParams.put("device_info", "WEB");//公众号内支付请传"WEB"

packageParams.put("nonce_str", CommonUtils.CreateNoncestr(16));//随机字符串,不长于32位

packageParams.put("sign_type", signType);//签名类型,目前支持HMAC-SHA256和MD5,默认为MD5

packageParams.put("body", companyName);

packageParams.put("out_trade_no", orderNo);//商户系统内部的订单号,32个字符内

packageParams.put("attach", companyId + "&" + orderIds);//传递的参数,在返回时会原样 返回

packageParams.put("total_fee", CommonUtils.changeY2F(totalPrice));//订单总金额,单位为分

packageParams.put("total_fee", "1");//订单总金额  //测试用

packageParams.put("fee_type", "CNY");//符合ISO 4217标准的三位字母代码,默认人民币:CNY

packageParams.put("notify_url", Constants.WECHAT_NOTIFY_URL);//接收微信支付异步通知回调地址

packageParams.put("spbill_create_ip", spbillCreateIp);//APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP

//packageParams.put("time_start", new Date().toString());//订单生成时间

packageParams.put("trade_type", "JSAPI");//JSAPI,NATIVE,APP

packageParams.put("openid", openId);//openid ,关于openid的获取请参考公众号网页授权

String sign = PayCommonUtils.createSign("UTF-8", packageParams);//将以上参数进行签名

packageParams.put("sign", sign);

packageParams.put("attach", companyId + "&" + orderIds);

String requestXML = PayCommonUtils.getRequestXml(packageParams);//生成接口需要的数据样式

System.out.println("request xml::" + requestXML);

//请求的数据样式

//

wx2421b1c4370ec43b

支付测试

JSAPI支付测试

10000100

1add1a30ac87aa2db72f57a2375d8fec

http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php

oUpF8uMuAJO_M2pxb1Q9zNjWeS6o

1415659990

14.23.150.211

1

JSAPI

0CB01533B8C1EF103065174F50BCA001

String responseStr = HttpUtils.httpsRequest(“https://api.mch.weixin.qq.com/pay/unifiedorder”, "POST", requestXML);//发送请求

//返回的数据样式

//responseStr= "";

System.out.println("response str::" + responseStr);

Map map = XMLUtils.doXMLParse(responseStr);//解析微信返回的信息,以Map形式存储便于取值

String returnCode = map.get("return_code");

System.out.println("get prepay_id result::" + returnCode);

SortedMap finalpackage = new TreeMap();

if ("SUCCESS".equals(returnCode)) {

/*生成最终提交订单参数*/

String timestamp = CommonUtils.getTimeStamp();

String prepay_id = map.get("prepay_id");

System.out.println("prepay_id is :: " + prepay_id);//取得prepay_id,用于在H5调用支付api

String packages = "prepay_id=" + prepay_id;

finalpackage.put("appId", Constants.APPID);

finalpackage.put("timeStamp", timestamp);

finalpackage.put("nonceStr", nonceStr);

finalpackage.put("package", packages);

finalpackage.put("signType", signType);

String paySign = PayCommonUtils.createSign("UTF-8", finalpackage);

System.out.println("paySign is ::" + paySign);

finalpackage.put("paySign", paySign);

finalpackage.put("sendUrl", Constants.WECHAT_NOTIFY_URL); //付款成功后跳转的页面

finalpackage.put("errorCode", map.get("err_code_des"));

String responseJsonStr = JSONObject.toJSONString(finalpackage);

} else {

finalpackage.put("errorCode", map.get("return_msg"));

finalpackage.put("result", "failed");

}

return finalpackage;

}

step3. 网页端调起支付api

将上一步取得的prepay_id存到package中,在微信js接口中将以下参数传递。

$('.btn-info').click(function () {

if ("1" == mPaymentType) {      //微信支付

$.ajax({

type: "post",

dataType: "json",

url: contextPath + "/order/order_payment_wechat.html",

data: {

mPaymentType: mPaymentType,

companyId: companyId,

},

}).done(function (response) {

console.log(response);

if (!isNull(response.errorCode)) {

alert(errorCode);

return false;

}

var obj = response.data;

WeixinJSBridge.invoke('getBrandWCPayRequest', {

"appId": obj.appId,                  //公众号名称,由商户传入

"timeStamp": obj.timeStamp,          //时间戳

"nonceStr": obj.nonceStr,        //随机串

"package": obj.package,      //商品包信息

"signType": obj.signType,        //微信签名方式:

"paySign": obj.paySign      //微信签名

}, function (res) {

if (res.err_msg == "get_brand_wcpay_request:ok") {

alert('支付成功!');

} else if (res.err_msg == "get_brand_wcpay_request:cancel") {

alert("支付取消");

} else {

alert("支付失败!");

}

});

});

}

js调用接口时会将微信支付窗口拉起,

支付成功会跳转到如图页面,点击完成页面会返回到回调函数中,

if (res.err_msg == "get_brand_wcpay_request:ok") {

alert('支付成功!');

}

在js调用过程中,还是遇到一些错误,不要惊慌,将错误提示alert出来,或者在微信web开发者工具中console.log()出,方便调错


step4:处理支付返回值 

支付成功后,支付宝会调用之前配置的回调函数,我们只需要用response去接受

/**

* 微信支付成功回调, 这个方法是我配置的回调函数

*

*@return

*/

@RequestMapping("modify_order_status_wechat.html")

public voidmodifyOrderStatusWechat(HttpServletRequest request,HttpServletResponse response)throwsIOException {

System.out.println("i am in modify_order_status_wechat begin");

try{

String paymentResponse = WechatUtils.paymentNotify(request,response);

System.out.println("paymentResponse:"+ paymentResponse);

Map map = XMLUtils.doXMLParse(paymentResponse);

if("SUCCESS".equals(map.get("result_code"))) {

String attach = map.get("attach");// 在step1中传递的参数原样取回

String attachTmp[] = attach.split("&");

Integer companyId = Integer.parseInt(attachTmp[0]);

System.out.println("companyId is :"+ companyId);

String orderIdTmp[] = attachTmp[1].split(",");

}

}catch(Exception e) {

e.printStackTrace();

}

}

/**

* 获取支付返回值

*/

public static String paymentNotify(HttpServletRequest request,HttpServletResponse response)throwsIOException,JDOMException {

System.out.println("~~~~~~~~~~~~~~~~I am in paymentNotify~~~~~~~~~");

InputStream inStream = request.getInputStream();

ByteArrayOutputStream outSteam =newByteArrayOutputStream();

byte[] buffer =new byte[1024];

intlen =0;

while((len = inStream.read(buffer)) != -1) {

outSteam.write(buffer,0,len);

}

System.out.println("~~~~~~~~~~~~~~~~付款成功~~~~~~~~~");

outSteam.close();

inStream.close();

String result =newString(outSteam.toByteArray(),"utf-8");//获取微信调用我们notify_url的返回信息

System.out.println("pay result is::"+ result);

Mapmap = XMLUtils.doXMLParse(result);

for(Object keyValue : map.keySet()) {

System.out.println(keyValue +"="+ map.get(keyValue));

}

if(map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) {

response.getWriter().write(PayCommonUtils.setXML("SUCCESS",""));//告诉微信服务器,收到信息,不要在调用回调action了

System.out.println("-------------"+ PayCommonUtils.setXML("SUCCESS",""));

PayCommonUtils.setXML("SUCCESS","");

}else{

PayCommonUtils.setXML("Failed","");

}

return result;

}

三、测试

如果页面还没备好,只是想测试下参数是否传递成功,可以使用微信提供的在线调试工具:https://pay.weixin.qq.com/wiki/tools/signverify/。

因为微信接口对客户端有一些限制,所有搭建测试环境有点费时,可以参考微信公众号网页授权summer版

四、工具类共享

在调用接口中多次使用到xml的解析,md5加密,http请求以及对金额的处理。他们都在这里,不能上传附件,贴出来有点乱,但总有那么一句可能你会用到。

xml 解析(XMLUtils):

packagecom.hj.custsys.wechat.util;

importorg.jdom.Document;

importorg.jdom.Element;

importorg.jdom.JDOMException;

importorg.jdom.input.SAXBuilder;

importjava.io.ByteArrayInputStream;

importjava.io.IOException;

importjava.io.InputStream;

importjava.util.HashMap;

importjava.util.Iterator;

importjava.util.List;

importjava.util.Map;

/**

* Created by LiuBo on 2016/12/5.

*/

public classXMLUtils {

/**

* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。

*

*@paramstrxml

*@return

*@throwsJDOMException

*@throwsIOException

*/

public staticMapdoXMLParse(String strxml)throwsJDOMException,IOException {

strxml = strxml.replaceFirst("encoding=".*"","encoding="UTF-8"");

if(null== strxml ||"".equals(strxml)) {

return null;

}

Map m =newHashMap();

InputStream in =newByteArrayInputStream(strxml.getBytes("UTF-8"));

SAXBuilder builder =newSAXBuilder();

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 = XMLUtils.getChildrenText(children);

}

m.put(k,v);

}

//关闭流

in.close();

returnm;

}

/**

* 获取子结点的xml

*

*@paramchildren

*@returnString

*/

public staticStringgetChildrenText(List children) {

StringBuffer sb =newStringBuffer();

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(XMLUtils.getChildrenText(list));

}

sb.append(value);

sb.append("");

}

}

returnsb.toString();

}

}

签名:

/**

*@paramcharacterEncoding编码格式

*@paramparameters请求参数

*@return

*@Description:sign签名

*/

public staticStringcreateSign(String characterEncoding,SortedMap parameters) {

StringBuffer sb =newStringBuffer();

Set es = parameters.entrySet();

Iterator it = es.iterator();

while(it.hasNext()) {

Map.Entry entry = (Map.Entry) it.next();

String k = (String) entry.getKey();

Object v = entry.getValue();

if(null!= v && !"".equals(v)

&& !"sign".equals(k) && !"key".equals(k)) {

sb.append(k +"="+ v +"&");

}

}

sb.append("key="+ Constants.WECHAT_KEY);

String sign = MD5SignUtils.MD5Encode(sb.toString(),characterEncoding);

returnsign;

}

public staticStringsetXML(String return_code,String return_msg) {

return"

+"]]>

+"]]>";

}

金额的处理:

/**

* 金额为分的格式

*/

public static finalStringCURRENCY_FEN_REGEX="\-?[0-9]+";

/**

* 将分为单位的转换为元并返回金额格式的字符串 (除100)

*

*@paramamount

*@return

*@throwsException

*/

public staticStringchangeF2Y(Long amount)throwsException {

if(!amount.toString().matches(CURRENCY_FEN_REGEX)) {

throw newException("金额格式有误");

}

intflag =0;

String amString = amount.toString();

if(amString.charAt(0) =='-') {

flag =1;

amString = amString.substring(1);

}

StringBuffer result =newStringBuffer();

if(amString.length() ==1) {

result.append("0.0").append(amString);

}else if(amString.length() ==2) {

result.append("0.").append(amString);

}else{

String intString = amString.substring(0,amString.length() -2);

for(inti =1;i <= intString.length();i++) {

if((i -1) %3==0&& i !=1) {

result.append(",");

}

result.append(intString.substring(intString.length() - i,intString.length() - i +1));

}

result.reverse().append(".").append(amString.substring(amString.length() -2));

}

if(flag ==1) {

return"-"+ result.toString();

}else{

returnresult.toString();

}

}

/**

* 将分为单位的转换为元 (除100)

*

*@paramamount

*@return

*@throwsException

*/

public staticStringchangeF2Y(String amount)throwsException {

if(!amount.matches(CURRENCY_FEN_REGEX)) {

throw newException("金额格式有误");

}

returnBigDecimal.valueOf(Long.valueOf(amount)).divide(newBigDecimal(100)).toString();

}

/**

* 将元为单位的转换为分 (乘100)

*

*@paramamount

*@return

*/

public staticStringchangeY2F(doubleamount) {

returnBigDecimal.valueOf(amount).multiply(newBigDecimal(100)).toString();

}

/**

* 将元为单位的转换为分 替换小数点,支持以逗号区分的金额

*

*@paramamount

*@return

*/

public staticStringchangeY2F(String amount) {

String currency = amount.replaceAll("\$|\¥|\,","");// 处理包含, ¥

// 或者$的金额

intindex = currency.indexOf(".");

intlength = 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");

}

returnamLong.toString();

}

http请求(HttpUtils)

packagecom.hj.custsys.wechat.util.http;

importjava.io.*;

importjava.net.ConnectException;

importjava.net.MalformedURLException;

importjava.net.URL;

importjava.net.URLConnection;

importjava.util.*;

importjava.util.regex.Matcher;

importjava.util.regex.Pattern;

importjavax.net.ssl.SSLSocketFactory;

importorg.apache.commons.httpclient.NameValuePair;

importorg.apache.commons.httpclient.methods.GetMethod;

importorg.apache.commons.httpclient.methods.PostMethod;

importorg.apache.commons.httpclient.params.HttpMethodParams;

importorg.apache.http.HttpEntity;

importorg.apache.http.HttpResponse;

importorg.apache.http.HttpStatus;

importorg.apache.http.ParseException;

importorg.apache.http.client.ClientProtocolException;

importorg.apache.http.client.HttpClient;

importorg.apache.http.client.entity.UrlEncodedFormEntity;

importorg.apache.http.client.methods.HttpGet;

importorg.apache.http.client.methods.HttpPost;

importorg.apache.http.client.methods.HttpUriRequest;

importorg.apache.http.impl.client.DefaultHttpClient;

importorg.apache.http.message.BasicNameValuePair;

importorg.apache.http.protocol.HTTP;

importorg.apache.http.util.EntityUtils;

importjavax.net.ssl.TrustManager;

importjavax.net.ssl.HttpsURLConnection;

importjavax.net.ssl.SSLContext;

importjavax.servlet.http.HttpServletRequest;

public classHttpUtils{

public staticStringgetUrl(String url) {

String result =null;

try{

// 根据地址获取请求

HttpGet request =newHttpGet(url);

// 获取当前客户端对象

HttpClient httpClient =newDefaultHttpClient();

// 通过请求对象获取响应对象

HttpResponse response = httpClient.execute(request);

// 判断网络连接状态码是否正常(0--200都数正常)

if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {

result = EntityUtils.toString(response.getEntity());

}

}catch(Exception e) {

//TODO Auto-generated catch block

e.printStackTrace();

}

returnresult;

}

/**

* 发送https请求

*

*@paramrequestUrl请求地址

*@paramrequestMethod请求方式(GET、POST)

*@paramoutputStr提交的数据

*@return返回微信服务器响应的信息

*/

public staticStringhttpsRequest(String requestUrl,String requestMethod,

String outputStr) {

try{

// 创建SSLContext对象,并使用我们指定的信任管理器初始化

TrustManager[] tm = {newTrustAnyTrustManager()};

SSLContext sslContext = SSLContext.getInstance("SSL","SunJSSE");

sslContext.init(null,tm, newjava.security.SecureRandom());

// 从上述SSLContext对象中得到SSLSocketFactory对象

SSLSocketFactory ssf = sslContext.getSocketFactory();

URL url =newURL(requestUrl);

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

conn.setSSLSocketFactory(ssf);

conn.setDoOutput(true);

conn.setDoInput(true);

conn.setUseCaches(false);

// 设置请求方式(GET/POST)

conn.setRequestMethod(requestMethod);

conn.setRequestProperty("content-type",

"application/x-www-form-urlencoded");

// 当outputStr不为null时向输出流写数据

if(null!= outputStr) {

OutputStream outputStream = conn.getOutputStream();

// 注意编码格式

outputStream.write(outputStr.getBytes("UTF-8"));

outputStream.close();

}

// 从输入流读取返回内容

InputStream inputStream = conn.getInputStream();

InputStreamReader inputStreamReader =newInputStreamReader(

inputStream,"utf-8");

BufferedReader bufferedReader =newBufferedReader(

inputStreamReader);

String str =null;

StringBuffer buffer =newStringBuffer();

while((str = bufferedReader.readLine()) !=null) {

buffer.append(str);

}

// 释放资源

bufferedReader.close();

inputStreamReader.close();

inputStream.close();

inputStream =null;

conn.disconnect();

returnbuffer.toString();

}catch(ConnectException ce) {

ce.printStackTrace();

}catch(Exception e) {

e.printStackTrace();

}

return null;

}

/**

* 发起https请求并获取结果

*/

public staticStringhttpRequest(String urlStr,String xmlFileName) {

String resultStr ="";

try{

URL url =newURL(urlStr);

URLConnection con = url.openConnection();

con.setDoOutput(true);

OutputStreamWriter out =newOutputStreamWriter(con.getOutputStream());

String xmlInfo = xmlFileName;

System.out.println("urlStr="+ urlStr);

System.out.println("xmlInfo="+ xmlInfo);

out.flush();

out.close();

BufferedReader br =newBufferedReader(newInputStreamReader(con

.getInputStream()));

String line ="";

StringBuffer sb =newStringBuffer();

for(line = br.readLine();line !=null;line = br.readLine()) {

sb.append(line);

}

resultStr = sb.toString();

}catch(MalformedURLException e) {

e.printStackTrace();

}catch(IOException e) {

e.printStackTrace();

}

returnresultStr;

}

public staticStringpost(String url,Map params) {

DefaultHttpClient httpclient =newDefaultHttpClient();

String body =null;

HttpPost post =postForm(url,params);

body =invoke(httpclient,post);

httpclient.getConnectionManager().shutdown();

returnbody;

}

public staticStringget(String url) {

DefaultHttpClient httpclient =newDefaultHttpClient();

String body =null;

HttpGet get =newHttpGet(url);

body =invoke(httpclient,get);

httpclient.getConnectionManager().shutdown();

returnbody;

}

private staticStringinvoke(DefaultHttpClient httpclient,

HttpUriRequest httpost) {

HttpResponse response =sendRequest(httpclient,httpost);

String body =paseResponse(response);

returnbody;

}

private staticStringpaseResponse(HttpResponse response) {

HttpEntity entity = response.getEntity();

String charset = EntityUtils.getContentCharSet(entity);

String body =null;

try{

body = EntityUtils.toString(entity,"UTF-8");

}catch(ParseException e) {

e.printStackTrace();

}catch(IOException e) {

e.printStackTrace();

}

returnbody;

}

private staticHttpResponsesendRequest(DefaultHttpClient httpclient,

HttpUriRequest httpost) {

HttpResponse response =null;

try{

response = httpclient.execute(httpost);

}catch(ClientProtocolException e) {

e.printStackTrace();

}catch(IOException e) {

e.printStackTrace();

}

returnresponse;

}

private staticHttpPostpostForm(String url,Map params) {

HttpPost httpost =newHttpPost(url);

List nvps =newArrayList();

Set keySet = params.keySet();

for(String key : keySet) {

nvps.add(newBasicNameValuePair(key,params.get(key)));

}

try{

httpost.setEntity(newUrlEncodedFormEntity(nvps,HTTP.UTF_8));

}catch(UnsupportedEncodingException e) {

e.printStackTrace();

}

returnhttpost;

}

}


在html中调用微信js接口时,我对官方提供的写法很没信心,心想没有引入任何js文件怎么可能引用到方法,抱着试一试的想法,呀,居然有反映。置疑和相信都存在时,带着置疑大胆去try try try...at last , you'll trust the truth,哈哈哈...。另外,在测试过程中时常会遇到一些偶发的bug, 从最初的小侥幸到现在的事出必有因不知放掉了多少bug,现在明白选择相信科学错出来也不会太离谱。

TAG:49681829
阅读:283
扩展阅读:
2014-2017 96新媒体 All right reserved. 版权所有 琼ICP备17000490号-2