现在的网页支付(PC和微信H5)和app支付,用的比较多的是微信支付、银联支付和支付宝支付,其余的是这些支付的第三方支付,我目前了解的只有这么多。我目前做了银联支付和微信支付,这里说一些银联支付的开发的一些介绍吧。
根据我们公司的应用经验,银联支付时费率最低的,如果和银联商务谈判的好,自身的交易量比较大,费率可能更低(具体不能透露了)。银联支付一种是按百分比收取手续费,另一种按笔数收取手续费,例如,每笔0.9元。我们公司用了2种银联支付,一开始用的是银联支付,后来因费率问题,使用银联支付第三方平台--千引支付,实质上也是银联支付。
我们申请的银联支付,在网上营业厅、微信服务号、Android和iOS平台都在使用,是“手机WAP支付产品”。主要用了2个接口,第一个是“消费类交易”,即银联支付申请接口。它也可以分为两部分,(1)通过拼接报文,签名加密,生成html网页,浏览器跳转请求到银联支付页面。此时,订单生成,用户可以在银联页面完成支付。(2)支付成功后,银联会根据请求参数里的配置,主动回调前台、后台通知地址,通知用户支付成功。第二个接口是“交易状态查询交易”,即查询订单交易结果。
现在具体讲一下代码:
消费类交易接口:官方文档地址:https://open.unionpay.com/ajweb/help/api
(1)交易申请。初始化证书,拼接报文,数据签名,创建表单或取得tn号。调用此接口,微信、网厅可得到一个html的https post表单,浏览器执行js自动提交,访问银联支付申请接口,跳转银联支付页面。此时,服务端只是拼接报文,签名加密,请求的动作,是用户的浏览器做的,可以保证银联支付的安全性。app端则是,调用接口,取得tn号,调用app已安装的银联控件跳往银联支付页面。
private String pc2UnionPay(String orderId, String incMoney,
String txnTime) {
/**
* 初始化证书
*/
SDKConfig.getConfig().loadPropertiesFromSrc();// 从classpath加载acp_sdk.properties文件
/**
* 交易请求url 从配置文件读取
*/
String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl();
/**
* 组装请求报文
*/
Map
// 版本号
data.put("version", "5.0.0");
// 字符集编码 默认"UTF-8"
String encoding = "UTF-8";
data.put("encoding", encoding);
// 签名方法 01 RSA
data.put("signMethod", "01");
// 交易类型 01-消费
data.put("txnType", "01");
// 交易子类型 01:自助消费 02:订购 03:分期付款
data.put("txnSubType", "01");
// 业务类型 000201 B2C网关支付
data.put("bizType", "000201");
// 渠道类型 07-互联网渠道
data.put("channelType", "07");
// 商户/收单前台接收地址 选送
// 后台服务对应的写法参照 FrontRcvResponse.java
data.put("frontUrl", frontUrl);
// 商户/收单后台接收地址 必送
// 后台服务对应的写法参照 BackRcvResponse.java
data.put("backUrl", backUrl);
// 接入类型:商户接入填0 0- 商户 , 1: 收单, 2:平台商户
data.put("accessType", "0");
// 商户号码
data.put("merId", merId);
// 订单号 商户根据自己规则定义生成,每订单日期内不重复
data.put("orderId", orderId);
// 订单发送时间 格式: YYYYMMDDhhmmss 商户发送交易时间,根据自己系统或平台生成
data.put("txnTime", txnTime);
// 交易金额 分
data.put("txnAmt", incMoney);
// 交易币种
data.put("currencyCode", "156");
// 若报文中的数据元标识的key对应的value为空,不上送该报文域
/**
* 创建表单
*/
String html = null;
for (int i = 1; i <= 3; i++) {
html = createHtml(requestFrontUrl, signData(data));
if (html != null) {
return html;
}
}
return "服务器忙";
}
public static String createHtml(String action, Map
StringBuffer sf = new StringBuffer();
sf.append("");
sf.append("");
sf.append("");
sf.append("");
sf.append("");
return sf.toString();
}
(2)支付成功通知商户。根据申请时取得的前后台通知地址,银联在支付成功后,会主动通知商户交易结果,但应以后台通知为准。前台应答是通过 浏览器,用户点击“返回商户”按钮发送给商户的,后台通知是银联系统异步把交易状态为成功的交易通知给商户,失败交易不发送。前台应答及后台通知,商户都需要验签。
public class BackNoticeServlet extends HttpServlet {
private static final long serialVersionUID = -900467945307979675L;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
LogUtil.writeLog("后台接收银行通知开始……");
BankService bService = (BankService) PayCache.getInstance().getBean("bankService");
RechargeService service = (RechargeService)PayCache.getInstance().getBean("rechargeService");
// request.setCharacterEncoding("ISO-8859-1");
String encoding = request.getParameter(SDKConstants.param_encoding);
// 获取请求参数中所有的信息
Map
// 打印请求报文
LogUtil.printRequestLog(reqParam);
Map
if (null != reqParam && !reqParam.isEmpty()) {
Iterator
.iterator();
valideData = new HashMap
while (it.hasNext()) {
Entry
String key = (String) e.getKey();
String value = (String) e.getValue();
value = new String(value.getBytes("ISO-8859-1"), encoding);
valideData.put(key, value);
}
Map
// 验证签名
if (!SDKUtil.validate(valideData, encoding)) {
LogUtil.writeLog("验证签名结果[失败].");
} else {
LogUtil.writeLog("验证签名结果[成功].");
// 持久化银行返回的数据
LogUtil.writeLog("持久化银行返回的数据");
String settleDate = reqParam.get("settleDate");
Calendar cal=Calendar.getInstance();
settleDate = String.valueOf(cal.get(Calendar.YEAR)) + settleDate;//加上年份
reqParam.put("settleDate", settleDate);
service.addPayresult(reqParam);
if("00".equals(reqParam.get("respCode"))){
service.updOrder(reqParam.get("orderId"),settleDate);
}
LogUtil.writeLog("返回银行数据给商户开始……");
res.put("respCode", reqParam.get("respCode"));
res.put("respMsg", reqParam.get("respMsg"));
res.put("orderId", reqParam.get("orderId"));
res.put("txnTime", reqParam.get("txnTime"));
res.put("txnAmt", reqParam.get("txnAmt"));
res.put("queryId", reqParam.get("queryId"));
res.put("settleDate", reqParam.get("settleDate"));
res.put("settleAmt", reqParam.get("settleAmt"));
res.put("txnType", reqParam.get("txnType"));
String statusCode = "";
ProfitApplyOrder order = bService.queryOrder(reqParam.get("orderId"));
for(int i=0;i<5;i++){
statusCode = XmlUtil.invokeMethod(res,order.getBackUrl());
if(statusCode.equals("success")){
LogUtil.writeLog("返回银行数据给商户结束[成功].");
break;
}
}
LogUtil.writeLog("银行数据接受完成!");
}
}
} catch (Exception e) {
e.printStackTrace();
LogUtil.writeLog("接受银行数据失败:" + e.getMessage());
} finally {
response.setHeader("Content-type", "text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.print(System.currentTimeMillis());
writer.flush();
}
}
public static Map
final HttpServletRequest request) {
Map
Enumeration> temp = request.getParameterNames();
if (null != temp) {
while (temp.hasMoreElements()) {
String en = (String) temp.nextElement();
String value = request.getParameter(en);
res.put(en, value);
// 在报文上送时,如果字段的值为空,则不上送<下面的处理为在获取所有参数数据时,判断若值为空,则删除这个字段>
// System.out.println(" temp数据的键=="+en+" 值==="+value);
if (null == res.get(en) || "".equals(res.get(en))) {
res.remove(en);
}
}
}
return res;
}
}
交易状态查询交易:(以下摘自银联官方文档)
商户收到后台通知后,应根据通知报文中订单号等关键信息,更新系统中的订单支付状态。
商户在以下情况下,可主动发起交易状态查询,并根据查询结果更新交易状态。
(1)前台交易,在5分钟内未收到后台通知。
(2)后台交易,在1秒内未收到后台通知 。
(3)商户在特殊情况下没有正确处理后台通知,需要重新判定交易状态。
商户应注意对同一笔交易,对不同时间点可能先后收到的查询结果及后台通知进行合适的处理。
以上,此接口调用查询订单交易结果,是商户主动发起的,没什么好说的,把官方文档贴了一下。附上交易应答码地址:https://open.unionpay.com/ajweb/help?id=262