近期公司调完银联,调支付宝,调完支付宝调微信.说实话微信的帮助文档确实是烂,而且有没有技术支持,害的我头发都掉了一桌.不说废话了,看代码.

首先登陆微信的公众平台(微信的服务号不是订阅号),然后选择微信支付-->开发设置,设置好支付回调URL和支付授权目录(授权目录最少精确到二级目录,比如你的需要使用微信支付的目录是:www.weixinpay.com/sp/weixin/pay.do,那么对应的是:www.weixinpay.com/sp/weixin/),设置好后编写代码.

对了,联调支付,一般都需要外网能够访问的URL地址,这里建议使用过ngrok软件,直接在本地联调,使用方式,下载一个ngrok,然后由命令行窗口进入ngrok解压的目录,然后执行:

ngrok.exe -config ngrok.cfg -subdomain weixinpay(可以改成自己喜欢的域名) 8080(可以改成自己喜欢的端口)

首先生成微信二维码(1):

 /**
* 生成微信二维码图片
* @throws Exception
*/
public void gainQRCode() throws Exception {
try {
String orderNu = request.getParameter("orderNu");
String describe = request.getParameter("describe");
String payCodeType = request.getParameter("payCodeType");
System.out.println("订单编号:"+"\n"+orderNu);
String price = request.getParameter("txnAmt");
if(StringUtils.isBlank(orderNu)){//账户充值,不用签名校验
orderNu = "WE"+StringUtil.getTableId(false);
} else {
String sign = request.getParameter("sign");
String signParam = "orderNu="+orderNu+"&payPrice="+price;
String newSign = DigestUtils.md5Hex(signParam.getBytes("utf-8"));
if(!newSign.equalsIgnoreCase(sign)){
Map param = new HashMap<>();
param.put("statu", "2");
JSONArray jsonProduct = JSONArray.fromObject(param);
System.out.println("json: "+jsonProduct.toString());
response.getWriter().print(jsonProduct.toString());
return ;
}
}
System.out.println("没有转换的金额:"+"\n"+price);
BigDecimal bigDecimalPrice = new BigDecimal(price);
String pric = bigDecimalPrice.multiply(new BigDecimal(100)).toString().split("\\.")[0];
System.out.println("转换后的金额:"+"\n"+pric);
String filePostfix = "jpg";
// 二维码图片名称
String codePng = System.currentTimeMillis() + "." + filePostfix;
// 保存路径
// 应用ID
String appid = Config.APPID;
// 商户ID
String mch_id = Config.MCHID;
String key = Config.KEY;
// 生成32位的随机字符串
String nonce_str = RandomStringUtil.generate().toUpperCase();
// 商户产品ID
User user = SessionUtil.getSysUserFormSession(request);
String product_id = "";
if(StringUtils.isNotBlank(payCodeType) && "1".equals(payCodeType)){//账户充值
product_id = "1_"+orderNu+"_"+"账户保证金充值"+"_"+pric+"_"+user.getUniversalid();
} else if (StringUtils.isNotBlank(payCodeType) && "2".equals(payCodeType)){//订单结算
product_id = "2_"+orderNu+"_"+describe+"_"+pric+"_"+user.getUniversalid();
}
         //上面的可以不关注.关注下面的签名和发送的参数,54行开始
// 时间戳
String time_stamp = System.currentTimeMillis()+"";
String sign = "";//这些参数可以去微信扫码的支付的文档看看具体是什么意思
String temp = "appid=" + appid + "&mch_id=" + mch_id + "&nonce_str=" + nonce_str + "&" + "product_id="
+ product_id + "&time_stamp=" + time_stamp;
String signTemp = temp + "&key=" + key;
System.out.println("二维码请求参数:"+"\n"+temp);
sign = Encrypt.e(signTemp).toUpperCase();
String contentUrl = "weixin://wxpay/bizpayurl?" + temp + "&sign=" + sign;
String url = QRCODE_PATH + File.separator + File.separator + codePng;
File f = new File(url);
if(!f.exists()){
f.mkdirs();
}
System.out.println("已生成二维码url");
QRImgCode.encode(contentUrl, "二维码", 400, 400, url);
System.out.println("二维码已经生成成功");
Map param = new HashMap<>();
param.put("statu", "1");
param.put("imgName", codePng);
System.out.println("返回参数封装成功");
JSONArray jsonProduct = JSONArray.fromObject(param);
System.out.println("json: "+jsonProduct.toString());
response.getWriter().print(jsonProduct.toString());
} catch (Exception e) {
throw e;
}
}

生成二维码(2):

 /**
* 无中间图片
* @param content
* @param width
* @param height
* @param destImagePath
*/
public static void encode(String content,String name,int width, int height, String destImagePath) {
try {
System.err.println("进入二维码方法");
BitMatrix bitMatrix = genBarcode(content, 190, 184);
System.out.println("生成二维码数据");
MatrixToImageWriter.writeToFile(bitMatrix, "jpg", new File(destImagePath));
System.out.println("二维码图片生成完成");
} catch (IOException e) {
e.printStackTrace();
} catch (WriterException e) {
e.printStackTrace();
}
}

PC端扫码后微信会去调用调用支付回调URL,接下来重要的步骤来了:(险些让我秃顶)

 /**
* 微信扫码之后的回调
* @return
*/
public void weixinPay() {
System.out.println("微信支付扫码回调,手动发起统一下单支付");
try {
InputStream inStream = this.request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String re = new String(outSteam.toByteArray(), "utf-8");
System.out.println("用户扫码后返回的参数: \n" + re);
      //转xml 微信传输的是xml
Map map = parseXML(re);
      //扫码后签名验证
String sign = gainValidateSign(map,false).toUpperCase();
if (!sign.equalsIgnoreCase((String)map.get("sign"))) {
System.out.println("扫码后生成的签名不正确");
return;
}//这些是扫码后的验证
System.out.println("扫码后生成的签名正确, 继续进行后续操作"); String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; String string = (String)map.get("product_id");
System.out.println("扫码的product_id参数:\n" + string);
String[] para = string.split("_");
//这里个方法是生成与支付订单的请求参数,看下面的方法,需要传输的参数可以去查看微信的帮助文档,虽然烂,但是这些还是有的.
StringEntity param = genProductArgs(para[0] + "_" + para[1], para[2], para[3]);
String result = sendPost(url, param);
System.out.println("发送请求得到结果===" + result); Map resultMap = parseXML(result);
String signResult = gainSign(resultMap);
if (!signResult.equalsIgnoreCase((String)resultMap.get("sign"))) {
System.out.println("统一下单接口-->签名不正确");
return;
}
if (StringUtils.isNotBlank(para[0]) && "1".equals(para[0])) {
RechargePrice r = rechargePriceServiceService.findByorderIdRecharge(para[1]);
if(r == null || r.getUniversalid() == null){
RechargePrice rp = new RechargePrice();
rp.setUserId(para[4]);
rp.setCreateDate(DateFormatUtil.strToDate(UtilDate.getDateFormatter()));
rp.setExplain("账户充值");
rp.setFlowAccountNum(StringUtil.getTableId(true));
BigDecimal bigDecimalPrice = new BigDecimal(para[3]); double doubleValue = bigDecimalPrice.divide(new BigDecimal("100")).doubleValue();
rp.setPrice(doubleValue+"");
rp.setOrderCode(para[1]);
rp.setTransactionStat("2");
rp.setTransactionType("1");
rp.setPaySource("5");
this.rechargePriceServiceService.saveRechargePrice(rp);
}
}
ServletOutputStream outputStream = this.response.getOutputStream(); String return_code = (String)resultMap.get("return_code");
String result_code = (String)resultMap.get("result_code");
if (StringUtils.isNotBlank(return_code) && StringUtils.isNotBlank(result_code) && return_code.equalsIgnoreCase("SUCCESS") && result_code.equalsIgnoreCase("SUCCESS")) {
String xml = genPay(resultMap);//对于中文乱码主要是上面的那个预支付订单请求,只要上面请求OK,这个请求就可以不用转码
System.out.println("统一下单接口后,向微信发送支付其你去的xml"+"\n" + xml);
outputStream.write(xml.getBytes());
outputStream.flush();
outputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}

字符串转xml:

 /**
* 解析xml方法
*/
@SuppressWarnings("rawtypes")
public Map<String, String> parseXML(String xml) {
Document doc;
Map<String, String> map = new LinkedHashMap<String, String>();
try {
doc = DocumentHelper.parseText(xml);
Element rootElement = doc.getRootElement();
Iterator elementIterator = rootElement.elementIterator();
while (elementIterator.hasNext()) {
Element recordEle = (Element) elementIterator.next();
String name = recordEle.getName();
String textTrim = recordEle.getTextTrim();
map.put(name, textTrim);
}
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
处理xml集合,返回签名:
 /**
* 处理xml字符串 返回签名
*/
public String gainValidateSign(Map<String, String> map, boolean isUtf8) {
StringBuffer sb = new StringBuffer();
Set<String> keySet = map.keySet();
List<String> list = new LinkedList<String>();
list.addAll(keySet);
Collections.sort(list);
for (String key : list) {
if (!key.equals("sign")) {
sb.append(key).append("=").append(map.get(key)).append("&");
}
}
sb.append("key=").append(Config.KEY);
String sign = "";
if(isUtf8){
try {
sign = Encrypt.e(new String(sb.toString().getBytes(), "utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
sign = Encrypt.e(sb.toString());
}
return sign;
}

封装请求参数返回xml字符串:

 /**
* 把一个参数添加到 一个集合中,按字典顺序,这是为了后面生成 签名方便
* @param attach
* @param map
*
* @return
* @throws Exception
*/
private StringEntity genProductArgs(String attach,String body,String price) throws Exception {
List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
packageParams.add(new BasicNameValuePair("appid", Config.APPID));
packageParams.add(new BasicNameValuePair("attach", attach));
packageParams.add(new BasicNameValuePair("body", body));
packageParams.add(new BasicNameValuePair("mch_id", Config.MCHID));
packageParams.add(new BasicNameValuePair("nonce_str", RandomStringUtil.generate().toUpperCase()));
packageParams.add(new BasicNameValuePair("notify_url", ""));
packageParams.add(new BasicNameValuePair("out_trade_no", com.eryansky.common.utils.StringUtils.getRandomNumbersAndLetters(15).toUpperCase()));
packageParams.add(new BasicNameValuePair("spbill_create_ip", request.getRemoteAddr()));
packageParams.add(new BasicNameValuePair("total_fee", Integer.parseInt(price)+""));
packageParams.add(new BasicNameValuePair("trade_type", Config.trade_type));
// 调用genXml()方法获得xml格式的请求数据
// String genXml = null;
try {
StringEntity stringEntityXml = getStringEntityXml(packageParams);
return stringEntityXml;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

返回发送的xml数据:(解决中文乱码问题)

 /**
* 生成xml文档发送微信生成支付信息
*
* @param params
* @return
* @throws Exception
*/
private StringEntity getStringEntityXml(List<NameValuePair> params) throws Exception {
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
sb2.append("<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><xml>");
for (int i = 0; i < params.size(); i++) {
// sb是用来计算签名的
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
sb.append('&');
// sb2是用来做请求的xml参数
sb2.append("<" + params.get(i).getName() + ">");
sb2.append(params.get(i).getValue());
sb2.append("</" + params.get(i).getName() + ">");
}
sb.append("key=");
sb.append(Config.KEY);
System.err.println("生成签名的参数:"+"\n"+sb.toString());
String packageSign = null;
// 生成签名-->签名和xml字符串都需要转成utf-8格式,中文就不会出现乱码
packageSign = DigestUtils.md5Hex(sb.toString().getBytes("utf-8")).toUpperCase();
// packageSign = DigestUtils.md5Hex(sb.toString()).toUpperCase();
System.out.println("生成发送统一接口的签名:"+"\n"+packageSign);
sb2.append("<sign><![CDATA[");
sb2.append(packageSign);
sb2.append("]]></sign>");
sb2.append("</xml>");
System.err.println("生成发送统一接口的xml"+"\n"+sb2.toString());
try {
StringEntity se = new StringEntity(sb2.toString(), "utf-8");
return se;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

发送请求:

 /**
* 发送请求
* @throws UnsupportedEncodingException
*/
public String sendPost(String url, StringEntity param) throws UnsupportedEncodingException {
String reslt = "";
try {
CloseableHttpClient httpClient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(30000).build();
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(param);
httpPost.setConfig(requestConfig);
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
reslt = EntityUtils.toString(entity, "UTF-8");
} catch (ParseException | IOException e1) {
e1.printStackTrace();
} //下面注释掉的直接忽略掉,贴上来主要想说,以前用这种方法中文乱码,而且用utf-8转换一下后直接报签名错误,把签名信息贴到微信的签名验证上面确实可以通过,真的是无语
// System.out.println("进入发送统一下单支付方法");
// PrintWriter out = null;
// BufferedReader in = null;
//
// String result = "";
// try {
// URL realUrl = new URL(url);
//
// URLConnection conn = realUrl.openConnection();
//
// conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
//
// conn.setDoOutput(true);
// conn.setDoInput(true);
//
// out = new PrintWriter(conn.getOutputStream());
// System.out.println("请求参数========" + param);
//// param = new String(param.getBytes(), "utf-8");
//
// out.write(param);
//
// out.flush();
//
// in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
// String line;
// while ((line = in.readLine()) != null)
// {
// result = result + line;
// }
// } catch (Exception e) {
// e.printStackTrace();
// try
// {
// if (out != null) {
// out.close();
// }
// if (in != null)
// in.close();
// }
// catch (IOException ex) {
// ex.printStackTrace();
// }
// }
// finally
// {
// try
// {
// if (out != null) {
// out.close();
// }
// if (in != null)
// in.close();
// }
// catch (IOException ex) {
// ex.printStackTrace();
// }
// }
// return new String(result.getBytes(), "utf-8");
return reslt;
}

拼接微信支付信息参数:

 /**
* 发送支付信息参数
* @param param
* @return
*/
public String genPay(Map<String, String> param) {
System.out.println("向微信发送支付请求的 参数-->"+"\n"+param.toString());
List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
packageParams.add(new BasicNameValuePair("appid", param.get("appid")));
packageParams.add(new BasicNameValuePair("mch_id", param.get("mch_id")));
packageParams.add(new BasicNameValuePair("nonce_str", RandomStringUtil.generate().toUpperCase()));
packageParams.add(new BasicNameValuePair("prepay_id", param.get("prepay_id")));
packageParams.add(new BasicNameValuePair("result_code", param.get("result_code")));
packageParams.add(new BasicNameValuePair("return_code", param.get("return_code")));
if ("FAIL".equalsIgnoreCase(param.get("result_code"))) {
packageParams.add(new BasicNameValuePair("return_code", param.get("result_code")));
if(StringUtils.isBlank(param.get("err_code_des"))){
packageParams.add(new BasicNameValuePair("err_code_des", "订单时效"));
}
packageParams.add(new BasicNameValuePair("err_code_des", param.get("err_code_des")));
}
String genXml = null;
try {
System.out.println("向微信发送支付请求的xml"+"\n"+packageParams.toString());
genXml = genXml(packageParams);
} catch (Exception e) {
e.printStackTrace();
}
return genXml;
}

生成微信支付xml参数:

 /**
* 生成xml文档发送个给微信支付
*
* @param params
* @return
* @throws Exception
*/
private String genXml(List<NameValuePair> params) throws Exception {
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
sb2.append("<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><xml>");
for (int i = 0; i < params.size(); i++) {
// sb是用来计算签名的
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
sb.append('&');
// sb2是用来做请求的xml参数
sb2.append("<" + params.get(i).getName() + ">");
sb2.append(params.get(i).getValue());
sb2.append("</" + params.get(i).getName() + ">");
}
sb.append("key=");
sb.append(Config.KEY);
System.err.println("生成签名的参数:"+"\n"+sb.toString());
String packageSign = null;
// 生成签名
packageSign = DigestUtils.md5Hex(sb.toString()).toUpperCase();
System.out.println("生成发送统一接口的签名:"+"\n"+packageSign);
sb2.append("<sign><![CDATA[");
sb2.append(packageSign);
sb2.append("]]></sign>");
sb2.append("</xml>");
System.err.println("生成发送统一接口的xml"+"\n"+sb2.toString());
try {
return sb2.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}

支付成功后:

 /**
* 微信扫码支付成功后的回调的接口
* @throws IOException
*/
public void weiXinnotify() throws IOException{
InputStream inStream = this.request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String re = new String(outSteam.toByteArray(), "utf-8");
System.out.println("用户扫码后支付返回的参数: \n" + re);
Map parseXML = parseXML(re);
System.out.println("转成xml后的map:\n" + parseXML);
String string = (String)parseXML.get("attach");
//处理完逻辑后记得向微信发送消息,不然微信会隔一段时间会访问
PrintWriter writer = response.getWriter();
writer.print(re);
writer.close();
}

最后,对于微信的帮助文档,是我目前见过最烂的了,前面联调支付宝和银联都没这样.哎不吐槽了,

本博客是根据:

http://www.cnblogs.com/zyw-205520/p/5495115.html这篇博客,写的很好,他自己写了一个开源项目,

以及和IT好的帮助下完成的,

如果有好的博客可以推荐给我,大家共同学习,谢谢!!

微信二维码支付-模式一(PC端,解决中文乱码问题)的更多相关文章

  1. 【二十九】php之简易微信二维码支付

    参考二维码支付接口文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5 index.php <!DOCTYPE htm ...

  2. asp.net mvc PC端二维码支付实例(微信二维码支付)

    一.微信支付方式介绍 微信提供了各种支付方式,试用于各种不同的支付场景,主要有如下几种: 1.刷卡支付 刷卡支付是用户展示微信钱包内的“刷卡条码/二维码”给商户系统扫描后直接完成支付的模式.主要应用线 ...

  3. 微信支付之二维码支付(native)

    HoJe男孩子你要加油阿 准备材料微信支付需要的参数统一下单二维码回调接口用到的工具类 . 准备材料 首先肯定是要有微信的开发文档开发文档地址再然后就是一些必的参数 注意:回调地址异步接收微信支付结果 ...

  4. JAVA实现的微信扫描二维码支付

    吐槽一下 支付项目采用springMvc+Dubbo架构实现,只对外提供接口. 话说,为什么微信支付比支付宝来的晚了那么一点,一句话,那一阵挺忙的,然后就没有时间整理,最近做完支付宝支付,顺便也把微信 ...

  5. java实现微信支付宝等多个支付平台合一的二维码支付(maven+spring springmvc mybatis框架)

    首先申明,本人实现微信支付宝等支付平台合多为一的二维码支付,并且实现有效时间内支付有效,本人采用的框架是spring springmvc mybatis 框架,maven管理.其实如果支付,不需要my ...

  6. react页面内嵌微信二维码 和 自定义样式 以及 微信网页共用unionId问题

    在react页面内嵌“微信二维码”,实现PC端通过微信扫码进行登录.首先去微信开放平台注册一个账号,创建一个网站应用,提交网站备案审核,获取appid和appsecret:其他开发流程根据微信文档来进 ...

  7. 微信二维码引擎OpenCV开源研究

    <微信二维码引擎OpenCV开源研究> 一.编译和Test测试        opencv_wechat_qrcode的编译需要同时下载opencv(https://github.com/ ...

  8. HTML5 微信二维码提示框

    这是一个js的小案例,主要效果是显示一个微信二维码的提示框,非常简单实用. 源码如下: JS部分 <script src="js/jquery-1.8.3.min.js"&g ...

  9. CSS实现鼠标经过网页图标弹出微信二维码

     特点 1.纯CSS实现二维码展示功能,减少加载JS: 2.使用CSS3 transform 属性: ## 第一步 在需要展示二维码的地方添加如下代码,其中<a>标签内容可以根据需要修改成 ...

随机推荐

  1. Hadoop 数据排序(一)

    1.概述 1TB排序通常用于衡量分布式数据处理框架的数据处理能力.Terasort是Hadoop中的的一个排序作业.那么Terasort在Hadoop中是怎样实现的呢?本文主要从算法设计角度分析Ter ...

  2. 【LaTeX排版】LaTeX使用--入门基础<一>

    经过两个多星期,毕业论文终于写完了.由于自己对Word软件并不是很熟悉,再加上在数模时见识过LaTex的强大之处,于是就决定用LaTex进行论文的排版.使用LaTex可以避免像Word那样换台机器而出 ...

  3. linux服务搭建---yum源服务搭建

    yum源服务 1.本地yum源 2.yum源不在本地          1>  ftp服务器     2>  nfs服务器 1.本地yum源 前提:    linux系统   找到一个相应 ...

  4. C语言之数值计算--级数算法

    在编程语言的学习中,我们学习过不少的算法,比如累加,累乘,数值交换,排序等等.在一些软件比赛和面试题中,有一类算法不容忽视,属于高频题目,我之前去企业面试的时候就遇到这样的一类题目,题目不算难,掌握方 ...

  5. Android群英传笔记——第三章:Android控件架构与自定义控件讲解

    Android群英传笔记--第三章:Android控件架构与自定义控件讲解 真的很久没有更新博客了,三四天了吧,搬家干嘛的,心累,事件又很紧,抽时间把第三章大致的看完了,当然,我还是有一点View的基 ...

  6. 能量最小化初探,graphcuts能量最小化调用

    1.相对于能量函数来说,能量最小化的办法都有哪些? 梯度下降 模拟退火 图割 2.这个 跟最优化问题的求解,有什么联系跟区别呢? 基本上差不多,其实就是求出来了函数的一个最小值,我们看问题的时候不妨把 ...

  7. 基于opencv的gpu与cpu对比程序,代码来自opencv的文档中

    原文链接: http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/gpu/gpu-basics-similarity/gpu-basi ...

  8. LeetCode(39)-Intersection of Two Linked Lists

    听歌曲初爱有感: 开头啰嗦两句,刚在做算法题目的时候,听到了杨宗纬的<初爱>,突然有了一种本科时候的感觉,想想自己现在研二了,青春喂了狗,我果断喝了一罐啤酒,循环这首歌到吐-.. 题目: ...

  9. ruby TkPackage can't find package BWidget 之解决办法

    一个特别短的ruby/tk代码: require 'tkextlib\iwidgets' require 'tkextlib\bwidget' x = 0 101.times {|i| x+=i} T ...

  10. C#中使用双缓冲来避免绘制图像过程中闪烁

    自己所做项目中,在显示医学图像的界面中,当鼠标拖动图像时,不断刷新从后台获取新的图像,而整个过程就很诡异,一直闪个不停. 找到的一个可行方法是:在用户控件的构造函数中加入以下代码: SetStyle( ...