本文转自:[转]网银在线支付接口和应用
网银的接口不难,但是开通网银接口需要不小的费用。 相关资源汇编下载:
最近关注项目中在线支付,所以看一下文档,在线支付应用开发:
基本所有的在线支付均采用以下方式:
客户点击结帐时将关于訂單的信息和货币信息,相应的信息URL,经过md5或其他方式发送(可能Socket和Http或Https)支付平台(块钱,paypal或支付宝等),支付平臺处理完毕时根据相应URL,返回相关的信息(付款信息,訂單信息,验证信息).
在实际操作Money的问题人们一向关注他的安全性等问题,同时本人习惯在通过http方式访问非外网时采用Commons-httpclient的post发送实现,简单方便,所以采用此种实现:具体看以下API和原代码:
网银在线支付API接口:
商户>>>>>>网银在线支付:
<form method=post action="https://pay.chinaebank.cn/select_bank">
<input type=hidden name=v_mid value="1001"> 商户编号
<input type=hidden name=v_oid value="19990720-1001-000001234"> 订单编号
<input type=hidden name=v_amount value="13.45"> 订单总金额
<input type=hidden name=v_moneytype value="0"> 币种
<input type=hidden name=v_url value="http://domain/program">
支付动作完成后返回到该url,支付结果以POST方式发送
<input type=hidden name=v_md5info value="1630DC083D70A1E8AF60F49C143A7B95"> 订单MD5校验码
<input type="hidden" name="remark1 " value="">备注字段1
<input type="hidden" name="remark2" value="">备注字段2
<input type=hidden name=v_rcvname value="张三"> 收货人姓名
<input type=hidden name=v_rcvaddr value="北京海淀"> 收货人地址
<input type=hidden name=v_rcvtel value="68475566"> 收货人电话
<input type=hidden name=v_rcvpost value="100036"> 收货人邮编
<input type=hidden name=v_orderstatus value="0"> 商品信息
<input type=hidden name=v_ordername value="李四"> 订货人姓名
<input type=hidden name= v_orderemail value="test@test.com"> 订货人邮件
<input type=submit value="网银在线支付">
</form>
MD5校验串生成方法:当消费者在商户端生成最终订单的时候,将订单中的v_amount v_moneytype v_oid v_mid v_url key六个参数的value值拼成一个无间隔的字符串(顺序不要改变)。参数key是商户的MD5密钥(该密匙可在登陆商户管理界面后自行更改。)
网银在线支付>>>商户
支付完成后页面转到商户,从网银在线支付返回的消息格式为:
<form method=get action="v_url" target=_self> <input type="hidden" name="v_oid" value=""> <input type="hidden" name="v_pstatus" value=""> <input type="hidden" name="v_pstring" value=""> <input type="hidden" name="v_pmode" value=""> <input type="hidden" name="v_md5str" value=""> <input type="hidden" name="v_amount" value=""> <input type="hidden" name="v_moneytype" value=""> <input type="hidden" name="remark1 " value=""> <input type="hidden" name="remark2" value=""> </form> |
该消息格式详细解释如下:v_url是该笔订单提交时参数v_url 的值,即网银返回到商户的接口地址。
变量名称 |
变量命名 |
返回值说明 |
|
订单编号 |
v_oid |
商户发送的v_oid定单编号。 |
|
支付状态 |
v_pstatus |
20(表示支付成功) 30(表示支付失败) |
|
支付结果信息 |
v_pstring |
支付完成 支付完成 |
|
支付方式 |
v_pmode |
支付银行,例如工商银行 |
|
订单MD5校验码 |
v_md5str |
该参数的MD5字符串的顺序为:v_oid,v_pstatus,v_amount,v_moneytype,key MD5字符串示例: 20050320-1001-0000012342012.340key 用MD5函数加密上述字符串后得到的值如果和v_md5str值相等即表明返回的信息没有被纂改 |
|
订单总金额 |
v_amount |
订单实际支付金额 |
|
币种 |
v_moneytype |
订单实际支付币种 |
|
备注字段1 |
remark1 |
|
|
备注字段2 |
remark2 |
|
表3
package cn.com.vnvtrip.china.pay.proxy;
import static cn.com.vnvtrip.china.pay.commons.ChinaPayConstants.CHINABANK_NOTIFY_URL_HTTP; import static cn.com.vnvtrip.china.pay.commons.ChinaPayConstants.CHINABANK_PAY_HTTPS; import static cn.com.vnvtrip.china.pay.commons.ChinaPayConstants.CHINABANK_PAY_MD5_KEY;
import java.util.HashMap; import java.util.Map; import java.util.Properties;
import org.apache.commons.codec.digest.DigestUtils;
import cn.com.vnvtrip.china.pay.commons.Env; import cn.com.vnvtrip.china.pay.commons.HTTPClient;
/** * * 网银接口服务的代理 * * @author longgangbai * */ public class ChinaPayProxy { /** * 在下訂單時采用的的Md5加密的信息: MD5校验串生成方法:当消费者在商户端生成最终订单的时候, 将订单中的v_amount * v_moneytype v_oid v_mid v_url key六个参数的value值拼成一个无间隔的字符串(顺序不要改变)。 * 参数key是商户的MD5密钥(该密匙可在登陆商户管理界面后自行更改。) * * @param v_amount * @param v_moneytype * @param v_oid * @param v_mid * @param v_url * @param key * @return */ private static String getMd5Sign(String v_amount, String v_moneytype, String v_oid, String v_mid, String v_url, String key) { StringBuffer sb = new StringBuffer(); sb.append(v_amount); sb.append(v_moneytype); sb.append(v_oid); sb.append(v_mid); sb.append(v_url); sb.append(key); byte[] bytes = DigestUtils.md5(sb.toString()); String md5info = new String(bytes).toUpperCase(); return md5info; }
/** * 调用支付网关接口网址 银行结帐的接口代理 (本人习惯采用Commons-httpclient实现) * 用途:用来接受商户发给网银在线服务支付的订单信息 * * @param v_mid * 商户编号(非空) * @param v_oid * 订单编号(非空)(格式:订单生成日期(yyyymmdd)-商户编号-商户流水号)字段不可超过64位 * @param v_amount * 订单总金额 (非空) * @param v_moneytype * 货币类型 (非空) 0:RMB 1美元 * @param v_url * (非空) 支付的动作完成时返回的该url,支付结果以post方式发送 * @param v_md5info * 订单md5校验码 * @param remark1 * 备注字段1(可选字段) * @param remark2 * 备注字段2 (可选字段) * @param v_vmd * yyyymmdd 备注字段2 (不可为空字段) * @param v_rcvname * 收货人姓名 (自定义非网银必须字段) * @param v_rcvaddr * 收货人地址(自定义非网银必须字段) * @param v_rcvtel * 收货人电话(自定义非网银必须字段) * @param v_rcpost * 收货人邮编(自定义非网银必须字段) * @param v_orderstatus * 商品信息(自定义非网银必须字段) * @param v_ordername * 订货人姓名(自定义非网银必须字段) * @param v_orderemail * 订货人邮件(自定义非网银必须字段) * @return */ public static boolean chinaBankPayCheck(String v_mid, String v_oid, String v_amount, String v_moneytype, String v_url, String remark1, String remark2, String v_rcvname, String v_rcvaddr, String v_rcvtel, String v_rcpost, String v_orderstatus, String v_ordername, String v_orderemail) { Properties p = Env.getEnv().getProperties(); String md5key = p.getProperty(CHINABANK_PAY_MD5_KEY); String v_md5info = getMd5Sign(v_amount, v_moneytype, v_oid, v_mid, v_url, md5key); Map<String, String> paramMaps = new HashMap<String, String>(); paramMaps.put("v_mid", v_mid); paramMaps.put("v_oid", v_oid); paramMaps.put("v_amount", v_amount); paramMaps.put("v_moneytype", v_moneytype); paramMaps.put("v_url", p.getProperty(CHINABANK_NOTIFY_URL_HTTP)); paramMaps.put("v_md5info", v_md5info); paramMaps.put("remark1", remark1); paramMaps.put("remark2", remark2); paramMaps.put("v_rcvname", v_rcvname); paramMaps.put("v_rcvaddr", v_rcvaddr); paramMaps.put("v_rcvtel", v_rcvtel); paramMaps.put("v_rcpost", v_rcpost); paramMaps.put("v_orderstatus", v_orderstatus); paramMaps.put("v_ordername", v_ordername); paramMaps.put("v_orderemail", v_orderemail); return HTTPClient.executeHttp(CHINABANK_PAY_HTTPS, paramMaps, null); }
/** * result为支付完毕接受的结果的map 校验检测在网银支付数据是否被拦截的 * * @param v_oid * 获取结果中的訂單编号 * @param v_pstatus * 获取訂單的支付状态 * @param v_pstring * 支付的结果 * @param v_amount * 实际支付的金额 * @param v_moneytype * 实际支付的币种 * @param v_md5str * 获取訂單校验的MD5验证 * @return */ public static boolean checkPayOff(String v_oid, String v_pstatus, String v_pstring, String v_amount, String v_moneytype, String v_md5str) { Properties p = Env.getEnv().getProperties(); String md5key = p.getProperty(CHINABANK_PAY_MD5_KEY); String checkmd5 = getCheckMd5(v_oid, v_pstatus, v_amount, v_moneytype, md5key); if (checkmd5.equals(v_md5str)) { return true; } return false; }
/** * 得到网银訂單付款后Md5加密检查 * * @param v_oid * @param v_pstatus * @param v_amount * @param v_moneytype * @param key * @return */ private static String getCheckMd5(String v_oid, String v_pstatus, String v_amount, String v_moneytype, String key) { StringBuffer sb = new StringBuffer(); sb.append(v_oid); sb.append(v_pstatus); sb.append(v_amount); sb.append(v_moneytype); sb.append(key); byte[] bytes = DigestUtils.md5(sb.toString()); String md5info = new String(bytes).toUpperCase(); return md5info; } }
来自http://topmanopensource.javaeye.com/blog/497872
2010-02-09
建设银行对接(一)
文章分类:Java编程
这几天 项目需要对接建设银行的支付和查询功能,在支付和查询的时候将系统链接到建行指定的页面上,由于这些页面是基于互联网的,开放的,所以需要对数据加密和数字签名。 我来实现这个数据加密解密模块,功能已经完成了,唉,不过让我晕死的是,建行其实一并提供了 jar 包,已经实现了数据加密解密,校验数字签名的功能,只不过同事没注意到,只发接口文档给我,没发 jar 包给我,害我白着急了几天,不过工作也没算浪费,自己实现的还是比较放心些吧。这些页面的跳转没什么技术,主要在于数据加密和数字签名,在链接到建行页面之前,先将参数加密,在收到建行跳转过来的链接参数后,取出参数里的签名,将签名和原始参数进行校验,以确认目前跳转过来的的确是建行。页面跳转没什么好说的,我所感兴趣的在于加密这些地方,以及对建行文档的理解。
按照建行的规定,我们发送的数据需要进行 MD5 加密,建行对返回的数据进行了数字签名,我们需要校验签名的有效性。以下是建行的两项约定:
建行附录 1 : MAC 算法说明
Html代码
- 建设银行家居银行项目组决定对商户向网上银行系统提交的交易内容进行MAC校验,校验算法采用标准MD5算法,不带密钥。该算法的详细说明请参见RFC 1321文档。
- 商户实行标准的MD5算法对向网上银行系统提交的交易内容进行MAC校验,产生128位(bit)的MAC结果。输入为字符串,输出为16进制字符表示的字符串。
- 下面是对MAC结果的显示方式的描述:
- 对128位的交易结果按4位为一个单位进行划分,共获得32段
- 将每段看成一个16进制数,如0011为0X3,1101为0Xd。
- 将这个数映射到ASCII码表,形成相应的字符,如0X2为“2”,0Xd为“d”。
- 将这些字符连成一个字符串,长度为32。
- 下面是一些字符串进行MAC并按上述方法进行转换后获得的结果:
- MD5 ("") = d41d8cd98f00b204e9800998ecf8427e
- MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661
- MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72
校验,校验算法采用标准MD5算法,不带密钥。该算法的详细说明请参见RFC 1321文档。
算法对向网上银行系统提交的交易内容进行MAC校验,产生128位(bit)的MAC结果。输入为字符串,输出为16进制字符表示的字符串。
结果的显示方式的描述:
位的交易结果按4位为一个单位进行划分,共获得32段
进制数,如0011为0X3,1101为0Xd。
码表,形成相应的字符,如0X2为“2”,0Xd为“d”。
。
并按上述方法进行转换后获得的结果:
MD5 ("") = d41d8cd98f00b204e9800998ecf8427e
MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661
MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72
建行附录 2 :数字签名算法说明
Html代码
- 银行将客户支付信息实时通知给商户时,使用的数字签名算法是MD5withRSA算法。商户验证签名的公钥在商户在网银系统开户,获取数字证书后,登录到网银系统中,通过下载公钥交易获取。(下载后需妥善管理并及时更新商户公钥,以防公私钥不匹配造成验签不通过)。(目前家居银行项目组采用静态密钥对,上线前生成一对,发给合作商户。)
- 商户获取的公钥用X.509格式表示,并且将其按照每4位(bit)转换为一个16进制数的方式表示,产生16进制的字符串。家居银行使用标准MD5withRSA算法对给商户的响应进行签名,产生1024位(bit)的签名结果,并且将其按照每4位(bit)转换为一个16进制数的方式表示,形成16进制的字符串,长度为256。
- 下面是对签名结果的表示方式的描述:
- 对1024位的交易结果按4位为一个单位进行划分,共获得256段
- 将每段看成一个16进制数,如0011为0X3,1101为0Xd。
- 将这个数映射到ASCII码表,形成相应的字符,如0X2为“2”,0Xd为“d”。
- 将这些字符连成一个字符串,长度为256。
- 例如:
- 待签名的字符串为:
- POSID=000000000&BRANCHID=110000000&ORDERID=19991101234&PAYMENT=500.00&CURCODE=01&REMARK1=19991101&REMARK2=merchantname&SUCCESS=Y
- 签名结果为:
- 4b3ef029516193b7d969ac1840083635a3e0901b8cd526caa44c1a072f496d7f0d4bca3942c0d9030bede37c7809b835cec787eb39e18b7596a724fba9805b24714dfbb0f4a3fb430b32e075254a114d4c38a0ac52ef46a0ad33dec3fbfc15417402a1399e65e46996c0cf49fc7ffca9222f8cd693c8376b6f928828967bec42
- 当商户收到银行传来的CGI串后,从中获取签名(格式如上)和需签名的原文。商户端程序(商户自行开发MD5withRSA签名校验程序)将签名和商户端的公钥转换成二进制格式,与签名的原文一起对签名的正确性进行校验,校验步骤如下:
- 使用公钥进行签名的逆运算
- 使用标准MD5算法运算原文
- 比较1)、2)结果。
算法。商户验证签名的公钥在商户在网银系统开户,获取数字证书后,登录到网银系统中,通过下载公钥交易获取。(下载后需妥善管理并及时更新商户公钥,以防公私钥不匹配造成验签不通过)。(目前家居银行项目组采用静态密钥对,上线前生成一对,发给合作商户。)
格式表示,并且将其按照每4位(bit)转换为一个16进制数的方式表示,产生16进制的字符串。家居银行使用标准MD5withRSA算法对给商户的响应进行签名,产生1024位(bit)的签名结果,并且将其按照每4位(bit)转换为一个16进制数的方式表示,形成16进制的字符串,长度为256。
位的交易结果按4位为一个单位进行划分,共获得256段
进制数,如0011为0X3,1101为0Xd。
码表,形成相应的字符,如0X2为“2”,0Xd为“d”。
。
串后,从中获取签名(格式如上)和需签名的原文。商户端程序(商户自行开发MD5withRSA签名校验程序)将签名和商户端的公钥转换成二进制格式,与签名的原文一起对签名的正确性进行校验,校验步骤如下:
算法运算原文
、2)结果。
仔细看上面两项约定,无论是 MD5 加密还是 RSA 加密,都有一个基础工作,就是将二进制数据分割,换算成 16 进制字符,还需要进行逆运算。将结果按 4 位为一个单位进行划分,共获得 32 段, 将每段看成一个 16 进制数,如 0011 为 0X3 , 1101 为 0Xd 。 将这个数映射到 ASCII 码表,形成相应的字符,如 0X2 为“ 2 ”, 0Xd 为“ d ”。 将这些字符连成一个字符串,长度为 32 。 我先实现这个功能,代码如下:
ByteUtil.java
Java代码
-
- package cn.ipanel.payment.business.bank.ccb.encryption;
- /**
- * 字节运算工具,其作用和背景请见建行接口文档的"附录1:MAC算法说明"
- *
- * @author wangxiaoxue
- *
- */
- public class ByteUtil {
- // 用来将字节转换成 16 进制表示的字符
- private static char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
- '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
- /**
- * 找到字符在数组中的位置
- *
- * @param c
- * @return
- */
- private static int getIndex(char c) {
- int p = -1;
- for (int i = 0; i < hexDigits.length; i++) {
- if (hexDigits[i] == c) {
- p = i;
- break;
- }
- }
- return p;
- }
- /**
- * 将字节转化成字符串,转换算法如下:<br>
- * 1:每个字节长度为8位,分割为两个4位,高四位和低四位<br>
- * 2:将每个四位换算成16进制,并且对应ascii码,如0x01对应1,0x0d对应d,具体对应关系请见数组hexDigits[]<br>
- * 3:将得到的字符拼成字符串
- *
- * @param bytes
- * @return
- */
- public static String byteToChar(byte[] bytes) {
- // 每个字节用 16 进制表示的话,使用两个字符,所以字符数组长度是字节数字长度的2倍
- char str[] = new char[bytes.length * 2];
- // 表示转换结果中对应的字符位置
- int = 0;
- // 每一个字节转换成 16 进制字符
- for (int i = 0; i < bytes.length; i++) {
- byte byte0 = bytes[i]; // 取第 i 个字节
- // 取字节中高 4 位(左边四位)的数字转换,>>>为逻辑右移,右移后,高四位变成低四位,需要对低四位之外的值进行消零运算
- str[k++] = hexDigits[byte0 >>> 4 & 0xf];
- // 取字节中低 4 位(右边四位)的数字转换,并且和0xf进行"逻辑与"运算,以消除高位的值,得到纯净的低四位值
- str[k++] = hexDigits[byte0 & 0xf];
- }
- return new String(str);
- }
- /**
- * 将字节转换成二进制数组,是byteToChar方法的逆运算,转换算法如下:<br>
- * 1:将字符按顺序每两个分为一组,分别找出每个字符在映射表hexDigits[]中的索引值,请见getIndex(char c)方法<br>
- * 2:每两个字符一组进行运算,将第一个字符的索引值逻辑左移四位,并和"0xf"进行"逻辑或"运算,目的是将低四位都设置为1,因为逻辑左移后,低四位都变成0了<br>
- * 3:将第二个字符的索引值和
- "0xf0"进行"逻辑或"运算,目的的是将高位设置为1<br>
- * 4:将两个运算完的索引值进行"逻辑与"运算,得到了两个字符所代表的一个字节值<br>
- * 5:依次运算,最后得到字节数组,返回
- *
- * @param str
- * @return
- */
- public static byte[] charToByte(String str) {
- char[] chars = str.toCharArray();
- byte[] bytes = new byte[chars.length / 2];
- int k = 0;
- for (int i = 0; i < chars.length; i = i + 2) {
- // 得到索引值
- byte high = (byte) getIndex(chars[i]);
- byte low = (byte) getIndex(chars[i + 1]);
- // 第一个字符索引逻辑左移四位,并进行或运算,将低四位设置为1
- high = (byte) ((high << 4) | 0xf);
- // 第二个字符索引进行或运算,将高四位设置为1
- low = (byte) (low | 0xf0);
- // 两个字节进行与运算
- bytes[k++] = (byte) (high & low);
- }
- return bytes;
- }
- public static void main(String[] args) {
- String str = "abgcd1234";
- System.out.println("原始字符串:" + str);
- String result = ByteUtil.byteToChar(str.getBytes());
- System.out.println("运算结果:" + result);
- byte[] resultbytes = ByteUtil.charToByte(result);
- System.out.println("逆运算结果:" + new String(resultbytes));
- }
- }