长话短说,本文根据银联官方说明文档,简单总结下,并且说明下中途碰到问题该如何解决。
一、开发前的准备工作
1. 打开https://open.unionpay.com/,后续说的文档下载、FAQ查询等都在这个平台操作。
2. 下载规范和开发包。帮助中心-下载-产品接口规范-手机控件支付产品接口规范,帮助中心-下载-产品接口规范-手机控件支付产品技术开发包。
3. 开发人员都请先看下6.2的消费的交易流程。
流程图说明:
(1)用户在客户端中点击购买商品,客户端发起订单生成请求到商户后台;
(2)商户后台收到订单生成请求后,按照《手机控件支付产品接口规范》组织并推送订单信息至银联后台;
(3)银联后台接收订单信息并检查通过后,生成对应交易流水号(即TN),并回复交易流水号至商户后台(应答要素:交易流水号等);
(4)商户后台接收到交易流水号,将交易流水号返回给客户端;
(5)客户端通过交易流水号(TN)调用支付控件;
(6)用户在支付控件中输入相关支付信息后,由支付控件向银联后台发起支付请求;
(7)支付成功后,银联后台将支付结果通知给商户后台;
(8)银联将支付结果通知支付控件;
(9)支付控件显示支付结果并将支付结果返回给客户端;
注: 本文档主要关注上述流程中(5)、(9)部分的实现
目前各个平台支持的设备情况如下:
Android平台SDK主要适用于Android 2.1及以上版本的终端设备;
iOS版本支付控件适用iOS 6.0及以上版本终端设备。
4. 相关测试参数:
如果已签约,有自己的测试商户,则直接用自己的商户号测试,测试证书开发包里都有。* 尽量用真实商户号测试,防止有时候参数配错能尽早发现。
如果没有签约,或者商户号尚未分配,请在平台里自行获取商户并且开交易权限,方法为:
a) 左上角注册;
b) 登陆后右上角我的测试-测试参数
c) 我的测试-产品-选下自己集成的。
测试卡号信息:(此类信息仅供测试,不会发生正式交易)
借记卡:6226090000000048
手机号:18100000000
密码:111101
短信验证码:123456
(短信验证码记得点下获取验证码之后再输入)--如果提示短信验证码错误[8100201],要看下是测试环境还是生产环境,另外控件版本是否是最新的
贷记卡:6226388000000095;
手机号:18100000000;
cvn2:248;
有效期:1219;
短信验证码:123456
(短信验证码记得点下获取验证码之后再输入)
二、客户端开发步骤
1. 参考文档《中国银联手机支付控件使用指南》(该文档位于前台开发包的doc目录下),建立一下工程。
2. 在后台开发实现消费(获取tn)请求前,App开发可以看看demo代码怎么调起控件的,demo里默认由银联的一个商户仿真获取tn(http://202.101.25.178:8080/sim/gettn或http://101.231.204.84:8091/sim/getacptn),之后需要改从商户自己的后台那里获取tn的。
3. 后台开发完成消费请求后,与后台开发商讨一下后台和app间传递tn的方式。
4. 改为从自己后台tn做测试。
5. 自行增加其他业务逻辑。
这里介绍下,客户端整合jar包的方式,如下图,一目了然
控件的调用(BYbrid插件方式):
public class PayPlugin extends CordovaPlugin { @Override
public boolean execute(String action, JSONArray args,
CallbackContext callbackContext) throws JSONException { String tn = args.getString(0); if(action.equals("unionPay")) {
UPPayAssistEx.startPayByJAR(cordova.getActivity(),
PayActivity.class, null, null, tn, "00");
} return super.execute(action, args, callbackContext);
} }
返回结果处理(在MainActivity里面处理):
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
/*************************************************
* 步骤3:处理银联手机支付控件返回的支付结果
************************************************/
if (data == null) {
return;
} String msg = "";
/*
* 支付控件返回字符串:success、fail、cancel 分别代表支付成功,支付失败,支付取消
*/
String str = data.getExtras().getString("pay_result");
if (str.equalsIgnoreCase("success")) {
msg = "支付成功!";
} else if (str.equalsIgnoreCase("fail")) {
msg = "支付失败!";
} else if (str.equalsIgnoreCase("cancel")) {
msg = "用户取消了支付";
} Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
}
集成方式说明:
安卓分为静态库集成和apk接入两种方式:Apk接入方式、静态库集成。
Apk接入方式:调用startPay方法,调起手机中单独安装的控件。
静态库集成:调用startPayByJar方法,调起内置在商户app里的控件。
2种方式对比:
Apk接入方式:控件为独立安装。把demo代码中未安装控件时的代码修改为从银联官网下载控件apk后,商户就可以不用关心控件的版本了。控件apk下载地址:http://mobile.unionpay.com/getclient?platform=android&type=securepayplugin。
静态库集成:安装完商户app之后不需要再安装一次控件,但是商户需关心控件版本是否升级。另外iOS也是静态库集成,同时需要集成iOS的推荐用此方法,2边一致。
* 遇到问题可到https://open.unionpay.com/先自行看看能否解决:
1) 如果是代码异常:帮助中心-FAQ中把异常拷贝一小段搜索;或可以到FAQ的开发问题类别下,搜“安卓”或“iOS”可搜到对应的全量开发问题。
2) 如果是控件出错,报错信息有7位数字:技术集成-应答码,输入7位数字搜索。
3) 如果是控件出错,没有报错信息或没有7位数字:帮助中心-FAQ-测试问题,搜“app”可搜到控件的全量测试问题。
* 开发包中有pro和不带pro的,请使用不带pro的开发包,pro的开发包因包含一般接入方不使用的特殊功能,有些代码需要特殊处理。
* 安卓用startPay还是用startPayByJar的问题参考FAQ“apk方式和jar方式有何区别”。
三、后台开发步骤
1. 打开后台开发包,找到对应语言\示例代码文件夹,看下readme.txt,按步骤部署、修改配置文件等。
2. 其中Form_6_2_AppConsume的接口可获取tn,请跟客户端开发确定一下后台和app间传递tn的方式,最简单的方法就是直接在页面打印tn,不过部分语言直接仅打印tn的时候会带换行符,建议手机开发在收到tn的时候trim一下。
3. 后续参考readme完成。
简单介绍下后台获取tn的方法:
// 参考Form_6_2_AppConsume.java /**
* 组装请求报文
*/
Map<String, String> data = new HashMap<String, String>();
// 版本号
data.put("version", "5.0.0");
// 字符集编码 默认"UTF-8"
data.put("encoding", "UTF-8");
// 签名方法 01 RSA
data.put("signMethod", "01");
// 交易类型 01-消费
data.put("txnType", "01");
// 交易子类型 01:自助消费 02:订购 03:分期付款
data.put("txnSubType", "01");
// 业务类型
data.put("bizType", "000201");
// 渠道类型,07-PC,08-手机
data.put("channelType", "08");
// 前台通知地址 ,控件接入方式无作用
data.put("frontUrl", "http://localhost:8080/ACPTest/acp_front_url.do");
// 后台通知地址
data.put("backUrl", "http://222.222.222.222:8080/ACPTest/acp_back_url.do");
// 接入类型,商户接入填0 0- 商户 , 1: 收单, 2:平台商户
data.put("accessType", "0");
// 商户号码,请改成自己的商户号
data.put("merId", "888888888888888");
// 商户订单号,8-40位数字字母
data.put("orderId", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
// 订单发送时间,取系统时间
data.put("txnTime", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
// 交易金额,单位分
data.put("txnAmt", "1");
// 交易币种
data.put("currencyCode", "156");
// 请求方保留域,透传字段,查询、通知、对账文件中均会原样出现
// data.put("reqReserved", "透传信息");
// 订单描述,可不上送,上送时控件中会显示该信息
// data.put("orderDesc", "订单描述"); data = signData(data); // 交易请求url 从配置文件读取
String requestAppUrl = SDKConfig.getConfig().getAppRequestUrl(); Map<String, String> resmap = submitUrl(data, requestAppUrl); System.out.println("请求报文=["+data.toString()+"]");
System.out.println("应答报文=["+resmap.toString()+"]");
如上 resmap.get("tn"); 就是我们所需要的交易流水号。
后台银联回调方法实现:
// 参考BackRcvResponse.java protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException { LogUtil.writeLog("BackRcvResponse接收后台通知开始"); req.setCharacterEncoding("ISO-8859-1");
String encoding = req.getParameter(SDKConstants.param_encoding);
// 获取请求参数中所有的信息
Map<String, String> reqParam = getAllRequestParam(req);
// 打印请求报文
LogUtil.printRequestLog(reqParam); Map<String, String> valideData = null;
if (null != reqParam && !reqParam.isEmpty()) {
Iterator<Entry<String, String>> it = reqParam.entrySet().iterator();
valideData = new HashMap<String, String>(reqParam.size());
while (it.hasNext()) {
Entry<String, String> e = it.next();
String key = (String) e.getKey();
String value = (String) e.getValue();
value = new String(value.getBytes("ISO-8859-1"), encoding);
valideData.put(key, value);
}
} // 验证签名
if (!SDKUtil.validate(valideData, encoding)) {
LogUtil.writeLog("验证签名结果[失败].");
} else {
System.out.println(valideData.get("orderId")); //其他字段也可用类似方式获取
LogUtil.writeLog("验证签名结果[成功].");
// 这里写你的业务逻辑。。。
} LogUtil.writeLog("BackRcvResponse接收后台通知结束");
}
另外,支付回调有可能出现一次不会回调成功的情况,所以往往我们还需要在后台配置定时器来对你的支付数据表进行轮询,在轮询代码里面再次对银联全渠道平台发起交易状态查询,得到交易成功的信息之后才将支付表的临时数据删除:
// 参考 Form_6_5_Query.java /**
* 组装请求报文
*/
Map<String, String> data = new HashMap<String, String>();
// 版本号
data.put("version", "5.0.0");
// 字符集编码 默认"UTF-8"
data.put("encoding", "UTF-8");
// 签名方法 01 RSA
data.put("signMethod", "01");
// 交易类型
data.put("txnType", "00");
// 交易子类型
data.put("txnSubType", "00");
// 业务类型
data.put("bizType", "000000");
// 渠道类型,07-PC,08-手机
data.put("channelType", "08");
// 接入类型,商户接入填0 0- 商户 , 1: 收单, 2:平台商户
data.put("accessType", "0");
// 商户号码,请改成自己的商户号
data.put("merId", "888888888888888");
// 商户订单号,请修改被查询的交易的订单号
data.put("orderId", "20150211215817604");
// 订单发送时间,请修改被查询的交易的订单发送时间
data.put("txnTime", "20150211215817"); data = signData(data); // 交易请求url 从配置文件读取
String url = SDKConfig.getConfig().getSingleQueryUrl(); Map<String, String> resmap = submitUrl(data, url); System.out.println("请求报文=["+data.toString()+"]");
System.out.println("应答报文=["+resmap.toString()+"]");
注意点:
1 如果支付成功但没有到账,有可能是回调方法出了问题,比如回调方法可能根本没进去(检查方法权限),或者回调方法可能抛出异常;
2 如果支付成功但没有马上到账,而是过了一个有规律的时间到账,那同意可能是回调方法出了问题,到账的原因是你的轮询方法起了作用。
* 遇到问题可到https://open.unionpay.com/先自行看看能否解决:
1) 如果是代码异常:帮助中心-FAQ中把异常拷贝一小段搜索;或可以到FAQ的开发问题类别下,搜“java”、“c#”、“php”可搜到对应的全量开发问题。
2) 如果是正常收到应答respcode非00,可以看一下respMsg,里面的7位数字在平台上技术集成-应答码搜索。
3) 如果是其他错,在FAQ的测试问题类别下看看是否有自己遇到的问题。
最重要的一点:
开发过程中难免碰到一些难点无法独立解决,这时可以联系银联的客服:
https://open.unionpay.com/ajweb/help?id=291
入网流程问题
业务申请和咨询,可发送邮件至operation@unionpay.com或电话021-50362428,银联有专门的人员进行处理。其他专项服务:请见《业务运营服务指引(商户) 》日常业务运营服务。
入网测试问题
您可发送邮件到acpservice@unionpay.com邮箱,(邮件标题为:商户号+商户名称+联系QQ),提供商户号和联系QQ,银联测试服务专员会将商户接口人的联系QQ加入到银联统一服务QQ中提供支持服务(商户也可以通过服务电话021-38929999-2049,来提供自己的商户号和联系QQ)
代收、订购、无跳转支付产品暂不支持自助测试,请见谅。
人工服务时间:工作日9:00-11:30,13:00-17:00