支付宝的在线支付
实例说明
随着网络购物的兴起,在线支付的方式和安全性成为商家和用户关注的问题,支付宝是目前比较流行的第三方支付软件,它提供了“网上快速付款服务”,保障商家与用户的交易安全,使用支付宝可实现在网络上的资金快速交割。在本实例中,通过支付宝完成网络商城的在线支付,实例运行如图1所示,在线支付如图2所示:
图1 用户在商城购买商品形成订单
图2 支付宝在线支付页面
设计思路
本实例使用支付宝实现网上商城的在线支付,程序的基本流程如图3所示。程序通过jsp页面展示网上商城中的商品,当用户确认购买时形成订单,此订单将按支付宝接口要求生成表单数据。用户审核无误提交订单,程序跳转到支付宝页面进行在线支付,并返回给程序支付的结果信息。在这一过程中,支付宝将对提交过来的数据进行签名验证,判断数据的真实性。同样,当用户支付完成,程序对支付宝的返回信息也做出了签名验证,从而保证了交易的安全。
技术要点
使用支付宝,要严格遵守支付宝接口的要求,为了方便开发人员的集成测试,支付宝提供了虚拟版本的接口程序。
(1)支付宝服务接口下载
第三方支付软件一般都提供文档服务和接口服务。文档服务是关于接口功能、接口参数以及返回类型等的详细描述。接口服务则是一个程序,允许开发人员输入参数并获取返回数据。
说明:获取支付宝提供的服务,可以到支付宝网站:http://www.alipay.com上获取虚拟集成接口程序及文档。
在支付宝网站上,可以通过“首页→社区→客户服务厅→商家工具→集成开发交流”的步骤支下载正式的集成开发接口和文档,如果开发过程中发生错误,也可以在此论坛进行交流。下载完成之后,还要申请开通服务,如果不开通,将不能使用虚拟服务接口。
(2)支付宝接口的使用
用户在商城中购买商品所形成的交易信息需提交到支付宝外部服务接口进行在线支付,支付宝提供的外部服务接口为:https://www.alipay.com/cooperate/gateway.do,提交方式为post方式或get方式,提交的主要参数如表1所示:
表1 主要参数
参数名称 | 变量名 | 说 明 |
接口名称 | service | 外部接口名称 |
合作伙伴ID | partner | 合作伙伴在支付宝的id |
通知 URL | notify_url | 通知返回的URL |
返回URL | return_url | 结果返回的URL |
签名 | sign | 加密码获取 |
签名类型 | sign_type | 签名类型(MD5或DSA) |
参数编码字符集 | _input_charset | 合作伙伴与支付宝间交互信息时使用的编码字符集 |
除表1中的参数外,支付宝还提供了多个参数,这些参数并非都要设置,如“物流信息”和“支付宝通知”等,一般商城用不到。
当支付完成后,支付宝将返回支付信息到所设置的return_url,程序中通过效验返回的数据判断是否支付成功。
开发步骤
(1)新建一个index.jsp文件,它是程序的首页文件。用于展示商城中的商品。
(2)创建名称为OrderForm的实体类,该类中用于放置支付宝要求的表单元素,并提供相应的setXXX()和getXXX()方法。属性如下:
- private String paygateway;
- private String service;
- private String sign_type;
- private String out_trade_no;
- private String input_charset;
- private String partner;
- private String key;
- private String seller_email;
-
- private String body;
- private String subject;
- private String price;
- private String quantity;
- private String show_url;
- private String payment_type;
- private String discount;
- private String return_url;
- private String sign;
为获取更详细的信息,支付宝外部服务接口提供了多个协议参数,商户可根据需要自行设置,在本实例中,设置了部分参数。
(3)创建名称为StringUtil类,它是一个工具类,主要用于计算签名。此类中通过getSign()方法计算程序向支付宝提交数据的签名,通过getReturnSign()方法计算支付宝返回参数的签名,关键代码如下:
- public static String getSign (OrderForm of) {
- Map params = new HashMap();
- params.put("service", of.getService());
- params.put("out_trade_no", of.getOut_trade_no());
- params.put("seller_email", of.getSeller_email());
- params.put("partner", of.getPartner());
- params.put("subject", of.getSubject());
- params.put("body", of.getBody());
- params.put("price", of.getPrice());
- params.put("show_url", of.getShow_url());
- params.put("quantity", of.getQuantity());
- params.put("payment_type", of.getPayment_type());
- params.put("discount", of.getDiscount());
- params.put("_input_charset", of.getInput_charset());
- params.put("return_url", of.getReturn_url());
- return getSign(params, of.getKey());
- }
-
- private static String map2Sign (Map params, String privateKey) {
- List keys = new ArrayList(params.keySet());
- Collections.sort(keys);
- StringBuffer signBuffer = new StringBuffer();
- for (int i = 0; i < keys.size(); i++) {
- String key = (String) keys.get(i);
- String value = (String) params.get(key);
- if (value == null || value.trim().length() == 0) {
- continue;
- }
-
- signBuffer.append((i == 0 ? "" : "&") + key + "=" + value);
- }
- System.out.println(signBuffer.toString() + privateKey);
- return MD5.encodeMD5(signBuffer.toString() + privateKey);
- }
-
- public static String getReturnSign(Map params, String privateKey) {
- List keys = new ArrayList(params.keySet());
- Collections.sort(keys);
- StringBuffer signBuffer = new StringBuffer();
- for (int i = 0; i < keys.size(); i++) {
- String key = (String) keys.get(i);
- String value = (String) params.get(key);
-
- if (key == null || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) {
- continue;
- }
-
- signBuffer.append((i == 0 ? "" : "&") + key + "=" + value);
- }
- return MD5.encodeMD5(signBuffer.toString() + privateKey);
- }
支付宝的签名信息是通过MD5加密参数组合成的字符串而获得,组合成的字符串的顺序要按参数的升序排序,否则将产生错误的签名。实例中,首先将参数放入到Map对象中,使用了Collections类的sort()方法对Map中的key值进行排序,然后再进行组合。
(4)创建名称为BuyServlet类,它是一个Servlet,用于形成某一商品的订单信息。此类中,通过商户信息和jsp页面传递的商品信息创建了OrderForm对象,保存到request中并进行转发。关键代码如下:
- String subject = request.getParameter("name");
- String price = request.getParameter("price");
- if (StringUtil.checkStr(subject) && StringUtil.checkStr(price)) {
- Properties props = new Properties();
-
- props.load(this.getClass().getResourceAsStream("/lyq/encrypt/AlipayConfig.properties"));
- OrderForm order = new OrderForm();
- order.setSubject(subject);
- order.setPrice(price);
- order.setOut_trade_no(UUID.randomUUID().toString());
- order.setBody(subject);
- order.setPayment_type(props.getProperty("payment_type"));
- order.setDiscount("0");
- order.setQuantity("1");
- order.setPaygateway(props.getProperty("paygateway"));
- order.setInput_charset(props.getProperty("input_charset"));
- order.setSign_type(props.getProperty("sign_type"));
- order.setService(props.getProperty("service"));
- order.setReturn_url(props.getProperty("return_url"));
- order.setKey(props.getProperty("key"));
- order.setSeller_email(props.getProperty("seller_email"));
- order.setPartner(props.getProperty("partner"));
- order.setSign(StringUtil.getSign(order));
- request.setAttribute("order", order);
-
- request.getRequestDispatcher("order.jsp").forward(request, response);
- }
为了方便商户修改个人信息,实例中,将商户信息保存在属性文件中,在设置OrderForm对象的商户信息属性时,从AlipayConfig.properties中读取,AlipayConfig.properties关键代码如下:
- #支付接口
- paygateway=https:
- #接口名称
- service=trade_create_by_buyer
- #签名方式
- sign_type=MD5
- #字符集
- input_charset=utf-8
- #支付宝合作伙伴id (账户内提取)
- partner=2088002xxxxxxxxxxxx
- #支付宝安全校验码(账户内提取)
- key=g21lr6ropkulxxxxxxxxxxxxxxxxxx
- #卖家支付宝帐户
- seller_email=xxxxxx@xxx.com
- #支付类型
- payment_type=1
- #支付完成后跳转返回的网址URL
- return_url=http:
- logistics_type=EMS
- logistics_fee=0.01
- logistics_payment=SELLER_PAY
(5)新建order.jsp页面,用于显示用户的订单信息,在这个页面中,接收BuyServlet类转发过来的OrderForm对象,按支付宝接口要求设置表单,此表单必须提交到支付宝提供的外部服务接口“https://www.alipay.com/cooperate/gateway.do?”,实例中表单元素如下:
- <form name="alipaysubmit" method="post" action="https://www.alipay.com/cooperate/gateway.do?_input_charset=utf-8">
- <input type="hidden" name="subject" value="${order.subject}"> <!--商品名称-->
- <input type="hidden" name="price" value="${order.price}"> <!--订单总价-->
- <input type="hidden" name="quantity" value="${order.quantity}"> <!--支付类型-->
- <input type="hidden" name="discount" value="${order.discount}"> <!--数量-->
- <input type="hidden" name="show_url" value="${order.show_url}"> <!--商品展示网址-->
- <input type="hidden" name="body" value="${order.body}"> <!--商品描述-->
- <input type="hidden" name="out_trade_no" value="${order.out_trade_no}"> <!--订单号-->
- <input type="hidden" name="partner" value="${order.partner}"> <!--支付宝合作伙伴id-->
- <input type="hidden" name="payment_type" value="${order.payment_type}"><!--支付类型-->
- <input type="hidden" name="seller_email" value="${order.seller_email}"> <!--卖家支付宝帐户-->
- <input type="hidden" name="service" value="${order.service}"> <!--接口名称-->
- <input type="hidden" name="sign" value="${order.sign}"> <!--签名认证-->
- <input type="hidden" name="sign_type" value="${order.sign_type}"> <!--签名方式-->
- <input type="hidden" name="return_url" value="${order.return_url}"> <!--支付完成后跳转返回的网址URL-->
- <input type="button" name="v_action" value="支付宝网上安全即时支付平台" onClick="document.alipaysubmit.submit()">
- </form>
(6)当支付宝通知接口返回支付信息,程序需发送http请求读取支付结果,实例中创建了URLUtil类,编写readUrl()方法读取这一数据,关键代码如下:
- URL u = new URL(url);
-
- HttpURLConnection huc = (HttpURLConnection)u.openConnection();
-
- InputStreamReader in = new InputStreamReader(huc.getInputStream());
- BufferedReader br = new BufferedReader(in);
- result = br.readLine().toString();
(7)创建名称为BuyReturnServlet类,它是一个Servlet,用于接收当支付宝通知接口返回的数据,此类通过接收支付宝返回数据,进行业务逻辑判断是否支付成功,并将支付结果信息返回给用户。此类的URL地址也是提交给支付宝的返回URL地址return_url,程序中关键代码如下:
- Properties props = new Properties();
-
- props.load(this.getClass().getResourceAsStream("/lyq/encrypt/AlipayConfig.properties"));
- String partner = props.getProperty("partner");
- String key = props.getProperty("key");
-
- String alipayNotifyURL = "http://notify.alipay.com/trade/notify_query.do?";
- String notify_id = request.getParameter("notify_id");
- String sign = request.getParameter("sign");
- String info = "<p>交易失败!</p>";
-
- if (StringUtil.checkStr(notify_id) && StringUtil.checkStr(sign)) {
-
- alipayNotifyURL = alipayNotifyURL + "partner=" + partner + "¬ify_id=" + notify_id;
- String responseTxt = URLUtil.readUrl(alipayNotifyURL);
- if ("true".equals(responseTxt)) {
- Map params = new HashMap();
-
- Map requestParams = request.getParameterMap();
-
- for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
- String name = (String) iter.next();
- String[] values = (String[]) requestParams.get(name);
- String valueStr = "";
-
- for (int i = 0; i < values.length; i++) {
- valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
- }
- params.put(name, valueStr);
- }
- String mysign = StringUtil.getReturnSign(params, key);
-
- if (mysign.equals(sign)) {
- info = "<p>交易成功,请等待商家发货!</p>";
- }
- }
- }
- request.setAttribute("info", info);
-
- request.getRequestDispatcher("buyReturn.jsp").forward(request, response);
(8)新建 buyReturn.jsp页面,为用户提供支付后的返回信息。关键代码如下:
<fontcolor="red">${info}</font>
支付宝的在线支付
实例说明
随着网络购物的兴起,在线支付的方式和安全性成为商家和用户关注的问题,支付宝是目前比较流行的第三方支付软件,它提供了“网上快速付款服务”,保障商家与用户的交易安全,使用支付宝可实现在网络上的资金快速交割。在本实例中,通过支付宝完成网络商城的在线支付,实例运行如图1所示,在线支付如图2所示:
图1 用户在商城购买商品形成订单
图2 支付宝在线支付页面
设计思路
本实例使用支付宝实现网上商城的在线支付,程序的基本流程如图3所示。程序通过jsp页面展示网上商城中的商品,当用户确认购买时形成订单,此订单将按支付宝接口要求生成表单数据。用户审核无误提交订单,程序跳转到支付宝页面进行在线支付,并返回给程序支付的结果信息。在这一过程中,支付宝将对提交过来的数据进行签名验证,判断数据的真实性。同样,当用户支付完成,程序对支付宝的返回信息也做出了签名验证,从而保证了交易的安全。
技术要点
使用支付宝,要严格遵守支付宝接口的要求,为了方便开发人员的集成测试,支付宝提供了虚拟版本的接口程序。
(1)支付宝服务接口下载
第三方支付软件一般都提供文档服务和接口服务。文档服务是关于接口功能、接口参数以及返回类型等的详细描述。接口服务则是一个程序,允许开发人员输入参数并获取返回数据。
说明:获取支付宝提供的服务,可以到支付宝网站:http://www.alipay.com上获取虚拟集成接口程序及文档。
在支付宝网站上,可以通过“首页→社区→客户服务厅→商家工具→集成开发交流”的步骤支下载正式的集成开发接口和文档,如果开发过程中发生错误,也可以在此论坛进行交流。下载完成之后,还要申请开通服务,如果不开通,将不能使用虚拟服务接口。
(2)支付宝接口的使用
用户在商城中购买商品所形成的交易信息需提交到支付宝外部服务接口进行在线支付,支付宝提供的外部服务接口为:https://www.alipay.com/cooperate/gateway.do,提交方式为post方式或get方式,提交的主要参数如表1所示:
表1 主要参数
参数名称 | 变量名 | 说 明 |
接口名称 | service | 外部接口名称 |
合作伙伴ID | partner | 合作伙伴在支付宝的id |
通知 URL | notify_url | 通知返回的URL |
返回URL | return_url | 结果返回的URL |
签名 | sign | 加密码获取 |
签名类型 | sign_type | 签名类型(MD5或DSA) |
参数编码字符集 | _input_charset | 合作伙伴与支付宝间交互信息时使用的编码字符集 |
除表1中的参数外,支付宝还提供了多个参数,这些参数并非都要设置,如“物流信息”和“支付宝通知”等,一般商城用不到。
当支付完成后,支付宝将返回支付信息到所设置的return_url,程序中通过效验返回的数据判断是否支付成功。
开发步骤
(1)新建一个index.jsp文件,它是程序的首页文件。用于展示商城中的商品。
(2)创建名称为OrderForm的实体类,该类中用于放置支付宝要求的表单元素,并提供相应的setXXX()和getXXX()方法。属性如下:
- private String paygateway;
- private String service;
- private String sign_type;
- private String out_trade_no;
- private String input_charset;
- private String partner;
- private String key;
- private String seller_email;
-
- private String body;
- private String subject;
- private String price;
- private String quantity;
- private String show_url;
- private String payment_type;
- private String discount;
- private String return_url;
- private String sign;
为获取更详细的信息,支付宝外部服务接口提供了多个协议参数,商户可根据需要自行设置,在本实例中,设置了部分参数。
(3)创建名称为StringUtil类,它是一个工具类,主要用于计算签名。此类中通过getSign()方法计算程序向支付宝提交数据的签名,通过getReturnSign()方法计算支付宝返回参数的签名,关键代码如下:
- public static String getSign (OrderForm of) {
- Map params = new HashMap();
- params.put("service", of.getService());
- params.put("out_trade_no", of.getOut_trade_no());
- params.put("seller_email", of.getSeller_email());
- params.put("partner", of.getPartner());
- params.put("subject", of.getSubject());
- params.put("body", of.getBody());
- params.put("price", of.getPrice());
- params.put("show_url", of.getShow_url());
- params.put("quantity", of.getQuantity());
- params.put("payment_type", of.getPayment_type());
- params.put("discount", of.getDiscount());
- params.put("_input_charset", of.getInput_charset());
- params.put("return_url", of.getReturn_url());
- return getSign(params, of.getKey());
- }
-
- private static String map2Sign (Map params, String privateKey) {
- List keys = new ArrayList(params.keySet());
- Collections.sort(keys);
- StringBuffer signBuffer = new StringBuffer();
- for (int i = 0; i < keys.size(); i++) {
- String key = (String) keys.get(i);
- String value = (String) params.get(key);
- if (value == null || value.trim().length() == 0) {
- continue;
- }
-
- signBuffer.append((i == 0 ? "" : "&") + key + "=" + value);
- }
- System.out.println(signBuffer.toString() + privateKey);
- return MD5.encodeMD5(signBuffer.toString() + privateKey);
- }
-
- public static String getReturnSign(Map params, String privateKey) {
- List keys = new ArrayList(params.keySet());
- Collections.sort(keys);
- StringBuffer signBuffer = new StringBuffer();
- for (int i = 0; i < keys.size(); i++) {
- String key = (String) keys.get(i);
- String value = (String) params.get(key);
-
- if (key == null || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) {
- continue;
- }
-
- signBuffer.append((i == 0 ? "" : "&") + key + "=" + value);
- }
- return MD5.encodeMD5(signBuffer.toString() + privateKey);
- }
支付宝的签名信息是通过MD5加密参数组合成的字符串而获得,组合成的字符串的顺序要按参数的升序排序,否则将产生错误的签名。实例中,首先将参数放入到Map对象中,使用了Collections类的sort()方法对Map中的key值进行排序,然后再进行组合。
(4)创建名称为BuyServlet类,它是一个Servlet,用于形成某一商品的订单信息。此类中,通过商户信息和jsp页面传递的商品信息创建了OrderForm对象,保存到request中并进行转发。关键代码如下:
- String subject = request.getParameter("name");
- String price = request.getParameter("price");
- if (StringUtil.checkStr(subject) && StringUtil.checkStr(price)) {
- Properties props = new Properties();
-
- props.load(this.getClass().getResourceAsStream("/lyq/encrypt/AlipayConfig.properties"));
- OrderForm order = new OrderForm();
- order.setSubject(subject);
- order.setPrice(price);
- order.setOut_trade_no(UUID.randomUUID().toString());
- order.setBody(subject);
- order.setPayment_type(props.getProperty("payment_type"));
- order.setDiscount("0");
- order.setQuantity("1");
- order.setPaygateway(props.getProperty("paygateway"));
- order.setInput_charset(props.getProperty("input_charset"));
- order.setSign_type(props.getProperty("sign_type"));
- order.setService(props.getProperty("service"));
- order.setReturn_url(props.getProperty("return_url"));
- order.setKey(props.getProperty("key"));
- order.setSeller_email(props.getProperty("seller_email"));
- order.setPartner(props.getProperty("partner"));
- order.setSign(StringUtil.getSign(order));
- request.setAttribute("order", order);
-
- request.getRequestDispatcher("order.jsp").forward(request, response);
- }
为了方便商户修改个人信息,实例中,将商户信息保存在属性文件中,在设置OrderForm对象的商户信息属性时,从AlipayConfig.properties中读取,AlipayConfig.properties关键代码如下:
- #支付接口
- paygateway=https:
- #接口名称
- service=trade_create_by_buyer
- #签名方式
- sign_type=MD5
- #字符集
- input_charset=utf-8
- #支付宝合作伙伴id (账户内提取)
- partner=2088002xxxxxxxxxxxx
- #支付宝安全校验码(账户内提取)
- key=g21lr6ropkulxxxxxxxxxxxxxxxxxx
- #卖家支付宝帐户
- seller_email=xxxxxx@xxx.com
- #支付类型
- payment_type=1
- #支付完成后跳转返回的网址URL
- return_url=http:
- logistics_type=EMS
- logistics_fee=0.01
- logistics_payment=SELLER_PAY
(5)新建order.jsp页面,用于显示用户的订单信息,在这个页面中,接收BuyServlet类转发过来的OrderForm对象,按支付宝接口要求设置表单,此表单必须提交到支付宝提供的外部服务接口“https://www.alipay.com/cooperate/gateway.do?”,实例中表单元素如下:
- <form name="alipaysubmit" method="post" action="https://www.alipay.com/cooperate/gateway.do?_input_charset=utf-8">
- <input type="hidden" name="subject" value="${order.subject}"> <!--商品名称-->
- <input type="hidden" name="price" value="${order.price}"> <!--订单总价-->
- <input type="hidden" name="quantity" value="${order.quantity}"> <!--支付类型-->
- <input type="hidden" name="discount" value="${order.discount}"> <!--数量-->
- <input type="hidden" name="show_url" value="${order.show_url}"> <!--商品展示网址-->
- <input type="hidden" name="body" value="${order.body}"> <!--商品描述-->
- <input type="hidden" name="out_trade_no" value="${order.out_trade_no}"> <!--订单号-->
- <input type="hidden" name="partner" value="${order.partner}"> <!--支付宝合作伙伴id-->
- <input type="hidden" name="payment_type" value="${order.payment_type}"><!--支付类型-->
- <input type="hidden" name="seller_email" value="${order.seller_email}"> <!--卖家支付宝帐户-->
- <input type="hidden" name="service" value="${order.service}"> <!--接口名称-->
- <input type="hidden" name="sign" value="${order.sign}"> <!--签名认证-->
- <input type="hidden" name="sign_type" value="${order.sign_type}"> <!--签名方式-->
- <input type="hidden" name="return_url" value="${order.return_url}"> <!--支付完成后跳转返回的网址URL-->
- <input type="button" name="v_action" value="支付宝网上安全即时支付平台" onClick="document.alipaysubmit.submit()">
- </form>
(6)当支付宝通知接口返回支付信息,程序需发送http请求读取支付结果,实例中创建了URLUtil类,编写readUrl()方法读取这一数据,关键代码如下:
- URL u = new URL(url);
-
- HttpURLConnection huc = (HttpURLConnection)u.openConnection();
-
- InputStreamReader in = new InputStreamReader(huc.getInputStream());
- BufferedReader br = new BufferedReader(in);
- result = br.readLine().toString();
(7)创建名称为BuyReturnServlet类,它是一个Servlet,用于接收当支付宝通知接口返回的数据,此类通过接收支付宝返回数据,进行业务逻辑判断是否支付成功,并将支付结果信息返回给用户。此类的URL地址也是提交给支付宝的返回URL地址return_url,程序中关键代码如下:
- Properties props = new Properties();
-
- props.load(this.getClass().getResourceAsStream("/lyq/encrypt/AlipayConfig.properties"));
- String partner = props.getProperty("partner");
- String key = props.getProperty("key");
-
- String alipayNotifyURL = "http://notify.alipay.com/trade/notify_query.do?";
- String notify_id = request.getParameter("notify_id");
- String sign = request.getParameter("sign");
- String info = "<p>交易失败!</p>";
-
- if (StringUtil.checkStr(notify_id) && StringUtil.checkStr(sign)) {
-
- alipayNotifyURL = alipayNotifyURL + "partner=" + partner + "¬ify_id=" + notify_id;
- String responseTxt = URLUtil.readUrl(alipayNotifyURL);
- if ("true".equals(responseTxt)) {
- Map params = new HashMap();
-
- Map requestParams = request.getParameterMap();
-
- for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
- String name = (String) iter.next();
- String[] values = (String[]) requestParams.get(name);
- String valueStr = "";
-
- for (int i = 0; i < values.length; i++) {
- valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
- }
- params.put(name, valueStr);
- }
- String mysign = StringUtil.getReturnSign(params, key);
-
- if (mysign.equals(sign)) {
- info = "<p>交易成功,请等待商家发货!</p>";
- }
- }
- }
- request.setAttribute("info", info);
-
- request.getRequestDispatcher("buyReturn.jsp").forward(request, response);
(8)新建 buyReturn.jsp页面,为用户提供支付后的返回信息。关键代码如下:
<fontcolor="red">${info}</font>