支付宝的在线支付 实例说明 随着网络购物的兴起,在线支付的方式和安全性成为商家和用户关注的问题,支付宝是目前比较流行的第三方支付软件,它提供了“网上快速付款服务”,保障商家与用户的交易安全,使用支付宝可实现在网络上的资金快速交割。在本实例中,通过支付宝完成网络商城的在线支付,实例运行如图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; //支付宝合作伙伴id (账户内提取)
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; //支付完成后跳转返回的网址URL
private String sign; //签名
为获取更详细的信息,支付宝外部服务接口提供了多个协议参数,商户可根据需要自行设置,在本实例中,设置了部分参数。 (3)创建名称为StringUtil类,它是一个工具类,主要用于计算签名。此类中通过getSign()方法计算程序向支付宝提交数据的签名,通过getReturnSign()方法计算支付宝返回参数的签名,关键代码如下:
public static String getSign (OrderForm of) {
Map params = new HashMap(); //创建Map对象
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());//支付宝合作伙伴id (账户内提取)
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()); //支付完成后跳转返回的网址URL
return getSign(params, of.getKey()); //返回签名
}
//通过Map对象和key获取签名
private static String map2Sign (Map params, String privateKey) {
List keys = new ArrayList(params.keySet()); //转换成List
Collections.sort(keys); //排序
StringBuffer signBuffer = new StringBuffer(); //创建StringBuffer对象
for (int i = 0; i < keys.size(); i++) { //循环获取Map值
String key = (String) keys.get(i); //key值
String value = (String) params.get(key); //Map中的value值
if (value == null || value.trim().length() == 0) { //如果值为空则跳过
continue;
}
//向signBuffer中添加值
signBuffer.append((i == 0 ? "" : "&") + key + "=" + value);
}
System.out.println(signBuffer.toString() + privateKey);
return MD5.encodeMD5(signBuffer.toString() + privateKey);//返回加密后的数据
}
//通过key获取签名
public static String getReturnSign(Map params, String privateKey) {
List keys = new ArrayList(params.keySet()); //转换成List
Collections.sort(keys); //排序
StringBuffer signBuffer = new StringBuffer();//创建StringBuffer对象
for (int i = 0; i < keys.size(); i++) { //循环获取Map值
String key = (String) keys.get(i); //获取key值
String value = (String) params.get(key); //获取Map中的value值
//过滤掉空的key值和名为sign、sign_type的值
if (key == null || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) {
continue;
}
//向signBuffer中添加值
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()); //通过UUID生成订单号
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")); //支付完成后跳转返回的网址URL
order.setKey(props.getProperty("key")); //交易安全校验码(key)
order.setSeller_email(props.getProperty("seller_email")); //卖家支付宝帐户
order.setPartner(props.getProperty("partner")); //合作者身份(partnerID)
order.setSign(StringUtil.getSign(order)); //签名
request.setAttribute("order", order); //将OrderForm对象保存到request中
//转发到order.jsp
request.getRequestDispatcher("order.jsp").forward(request, response);
}
为了方便商户修改个人信息,实例中,将商户信息保存在属性文件中,在设置OrderForm对象的商户信息属性时,从AlipayConfig.properties中读取,AlipayConfig.properties关键代码如下:
#支付接口
paygateway=https://www.alipay.com/cooperate/gateway.do?
#接口名称
service=trade_create_by_buyer
#签名方式
sign_type=MD5
#字符集
input_charset=utf-8
#支付宝合作伙伴id (账户内提取)
partner=2088002xxxxxxxxxxxx
#支付宝安全校验码(账户内提取)
key=g21lr6ropkulxxxxxxxxxxxxxxxxxx
#卖家支付宝帐户
[email protected]
#支付类型
payment_type=1
#支付完成后跳转返回的网址URL
return_url=http://192.168.1.67:8084/ali/BuyReturn
logistics_type=EMS
logistics_fee=0.01
logistics_payment=SELLER_PAY
(5)新建order.jsp页面,用于显示用户的订单信息,在这个页面中,接收BuyServlet类转发过来的OrderForm对象,按支付宝接口要求设置表单,此表单必须提交到支付宝提供的外部服务接口“https://www.alipay.com/cooperate/gateway.do?”,实例中表单元素如下:
(6)当支付宝通知接口返回支付信息,程序需发送http请求读取支付结果,实例中创建了URLUtil类,编写readUrl()方法读取这一数据,关键代码如下:
URL u = new URL(url); //通过字符串url创建URL对象
//获取HttpURLConnection
HttpURLConnection huc = (HttpURLConnection)u.openConnection();
//从获取HttpURLConnection中读取输入流
InputStreamReader in = new InputStreamReader(huc.getInputStream());
BufferedReader br = new BufferedReader(in); //创建BufferedReader对象
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"); //partner合作伙伴id(必须填写)
String key = props.getProperty("key"); //partner 的对应交易安全校验码(必须填写)
//返回url
String alipayNotifyURL = "http://notify.alipay.com/trade/notify_query.do?";
String notify_id = request.getParameter("notify_id"); //支付宝流水号
String sign = request.getParameter("sign"); //签名
String info = "交易失败!
"; //返回信息
if (StringUtil.checkStr(notify_id) && StringUtil.checkStr(sign)) {
//http请求URL
alipayNotifyURL = alipayNotifyURL + "partner=" + partner + "¬ify_id=" + notify_id;
String responseTxt = URLUtil.readUrl(alipayNotifyURL); //返回的支付结果
if ("true".equals(responseTxt)) { //支付结果为true交易成功
Map params = new HashMap();
//获得POST 过来参数设置到新的params中
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); //重新放入Map中
}
String mysign = StringUtil.getReturnSign(params, key);//计算签名
//如果计算签名与返回的签名相同,则信息是真实的
if (mysign.equals(sign)) {
info = "交易成功,请等待商家发货!
";
}
}
}
request.setAttribute("info", info); //将支付信息放入到request中
//转发到buyReturn.jsp
request.getRequestDispatcher("buyReturn.jsp").forward(request, response);
(8)新建 buyReturn.jsp页面,为用户提供支付后的返回信息。关键代码如下:${info}