招商一网通支付接入

一网通支付是由招商银行推出的全新电子支付功能。

招商银行支付开放平台:http://58.61.30.110/openapi/Default.aspx

支持的银行

在这里只介绍一网通的手机支付,一网通也推出了PC的支付流程。

谁在使用?

由于招行推出的一网通是在16年4月份左右正式发布的 所以目前只有 滴滴打车 已接入招商一网通支付。

支付流程

项目需要接入招商一网通,需要进行签约 签约成功后 招商会给你们发送一份 一网通商户测试环境开通 里面有开发指南 以及商户的秘钥 由于考虑到一网通支付后面应该要继续完善,所以请参考实际收到的文件。

支付流程

用户唯一客户号

在业务上 一网通支付不同于 支付宝 和 微信,支付宝 和 微信 平台所关联的账号是用户单独的账号,比如 你的微信账号 和 你的支付宝账号 ,而一网通则是需要商户自动生成一个唯一的客户协议号。

签约(不含支付)

在使用一网通支付之前,需要用户添加银行卡,这一步叫签约 ,在签约成功之后支付时 可以选择你添加的银行卡进行支付。

支付(支付+签约)

  • 已签约用户再次支付

    只需输入支付密码即可完成支付

  • 用户没有进行签约

    自动跳转到签约页面,当签约完成后输入密码进行支付。

这一步是可以看做是(支付+签约),而上面的签约并没有支付。

输入密码

在这里我需要强调一下,一卡通的所有支付都是以H5页面进行展现的,并没有提供组件式的 Android 和 IOS 的 SDK,不过提供了 Android 和 IOS 的键盘 SDK ,在输入密码时 一卡通会自动调起 cmbKeyboard ,不过需要开发人员集成键盘 SDK 否则无法输入密码。

正是因为这个原因,没有办法在 PC 机上面调试输入密码场景(一旦点击输入密码 页面会进行跳转)

注意事项

在给你们提供的文件夹中,有很详细的教程,所以在这里只说一些我遇到的坑 让同学少走点弯路 快速接入一卡通支付。

在前期测试的时候,招商会给提供技术人员的邮件,你在开发中有什么问题都可以发送邮件,不过我建议大家 直接要到微信号 这样会快一些。

前期也会提供测试的 信用卡账号 一卡通账号 和 他行卡账号 和一些姓名、电话号码、身份证号码

注意在调试阶段的时候,所有请求地址前缀 : https://netpay.cmbchina.com/ 请修改为找招商提供的测试地址:比如 http://61.144.248.29:801/ 如果不进行修改 可能没办法正常的支付。

商户秘钥数据

这些数据招商会在邮件里提供。(这里只列出需要用到的)

1、8位虚拟企业网银编号(P0011559)

2、手机银行秘钥:abc1234

5、测试商户号:0016000010 (分行号是:0046 商户号是000010)

6、测试银行卡:(见附件)

7、相应分行技术联络人信息 ( 张三 联系电话)

8、商户结账处理系统的密码是 774411

签约

这里只是签约 不包含支付。

给用户添加银行卡的功能,一个用户可以添加多张银行卡 而一网通判断机制是客户协议号(商户自动生成)

请求地址:http://61.144.248.29:801/mobilehtml/DebitCard/M_NetPay/OneNetRegister/NP_BindCard.aspx

请求参数

名称 字段 类型 长度 说明 必填
流水号 REQSERIAL String 24 商户生成的交易流水号,同一交易日期唯一 YES
客服协议号 CUSTARGNO String 32 唯一协议号 商户生成 YES
商户号 MERCHANTNO String 8 企业网银编号(招行分配) YES
手机号 MOBILE String 11 客户手机号 NO
商户用户ID USERID String 13 客户唯一 NO
经度 LON decima 10 经度,手机定位数据一 NO
纬度 LAT decima 10 纬度,手机定位数据 NO
风险等级 RISKLEVEL int 4 用户风险等级 NO
交易时间 TIMESTAMP String 14 商户交易时间 YES
签约结果通知URL NOTICEURL String 100 商户接收签约设置结果url YES
通知附带的参数 NOTICEPARA String 100 通知需要附带的参数 NO
DES 加密参数 SIGN String 很长 DES 密文 YES

注意事项

这里的商户号说的是 8 位企业网银编号 不要输入 6 位的商户号。

商户用户ID主要是招商做账需要用到 不是必传 如要传入请保证用户唯一。

风险等级由商户自己定义 没有此业务的可以忽略。

交易时间 如果传入的时间和一网通时间相差 10 分钟 否则请求过期。

由于字段名称要全部大写,但是遵循 Java 命名规范 建议大家在对象里面还是驼峰命名法 只是字符串处理时 转换成大写。

DES加密

  • 按照顺序拼接参数
    REQSERIAL=XXX&CUSTARGNO=XXX&MERCHANTNO=XXX&MOBILE=XXX&USERID=XXX&LON=XXX&LAT=XXX&RISKLEVEL=XXX&TIMESTAMP=XXX&NOTICEURL=XXX&NOTICEPARA=XX
    

可以看做是序列化表单,即使有的值为空 也不能填充为 NULL 应该 是 LON=&LAT=&RISKLEVEL=1 ,这里的所有参数类型必须全部大写!! 一定不能小写,需要注意这里序列化里面没有SING参数(&SIGN=)

  • 进行 DES 密文加密

由于 DES 加密和解密算法都是公开的,密文的安全性都是由秘钥来保证的,所以请一定不要泄露秘钥,这里的秘钥就是上面 手机银行秘钥

在测试的时候 尽量不要去使用线上的 DES 加密工具,因为很有可能和招商的不一样 ,而一直无法进行验证 因为DES加密模式有很多种,招商用的是默认的 DES/ECB/PKCS5Padding 这里还是把加密代码放出来,方便各位同学测试:

import java.security.SecureRandom;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
import java.util.*;
import sun.misc.BASE64Encoder;
import java.net.URLDecoder;

public class DESEncoding {
    public DESEncoding() {
    }

    public static void main(String args[]) throws Exception {

     String msg = "123456";
     String password = "12343245";    //商户秘钥

     msg = URLDecoder.decode(msg); //进行url解码

     byte[] result = DESEncoding.encrypt(msg.getBytes(),password);

    BASE64Encoder en = new BASE64Encoder();
    String pswStr = en.encode(result);

    pswStr = pswStr.replaceAll("\r|\n","");

     System.out.println("加密后:" + new String(pswStr));
 }
    /**
     * 加密
     * @param datasource byte[]
     * @param password String
     * @return byte[]
     */
    public static  byte[] encrypt(byte[] datasource, String password) {
        try{
        SecureRandom random = new SecureRandom();
        DESKeySpec desKey = new DESKeySpec(password.getBytes());
        //创建一个密匙工厂,然后用它把DESKeySpec转换成
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey securekey = keyFactory.generateSecret(desKey);
        //Cipher对象实际完成加密操作
        Cipher cipher = Cipher.getInstance("DES");
        //用密匙初始化Cipher对象
        cipher.init(Cipher.ENCRYPT_MODE, securekey, random);
        //现在,获取数据并加密
        //正式执行加密操作
        return cipher.doFinal(datasource);
        }catch(Throwable e){
                e.printStackTrace();
        }
        return null;
}
    /**
     * 解密
     * @param src byte[]
     * @param password String
     * @return byte[]
     * @throws Exception
     */
    public static byte[] decrypt(byte[] src, String password) throws Exception {
            // DES算法要求有一个可信任的随机数源
            SecureRandom random = new SecureRandom();
            // 创建一个DESKeySpec对象
            DESKeySpec desKey = new DESKeySpec(password.getBytes());
            // 创建一个密匙工厂
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            // 将DESKeySpec对象转换成SecretKey对象
            SecretKey securekey = keyFactory.generateSecret(desKey);
            // Cipher对象实际完成解密操作
            Cipher cipher = Cipher.getInstance("DES");
            // 用密匙初始化Cipher对象
            cipher.init(Cipher.DECRYPT_MODE, securekey, random);
            // 真正开始解密操作
            return cipher.doFinal(src);
        }
}

加密后的密文 如果不进行 Base64 的编码处理 可能是乱码状态 ,在经过处理后 还要去掉换行符(\r|\n)。

到了这里,签约就算完毕了,这个时候可以提交你的数据到 测试接口 ,如果返回的是下面的页面 说明签约配置成功

如果这里出现 非法请求 那么很有可能是你的 SIGN 参数错误了,请仔细检查。

在 PC 上面调试时不能输入取款密码的,因为调不起 cmbKeyboard(招商一网通安全支付键盘),关于这个验证码 招商会给提供一个验证码查询地址 调试期间直接去查询拷贝过来即可。

cmbKeyboard

主动支付

系统会根据商户送过来的客户协议号(PNo)是否已经存在协议,判断是否需要引导客户进行协议签署。

请求地址:http://61.144.248.29:801/netpayment/BaseHttp.dll?PrePayEUserP

请求参数

名称 字段 类型 长度 说明 必填
日期 date String 8 当天日期 yyyyMMdd YES
商户分行号 BranchID String 4 招商提供的分行号 YES
商户号 CoNo String 6 商户号(不是8位网银编号) YES
订单号 BillNo String 10 6位或10位数字,一天内不能重复 YES
支付金额 Amount String 定单总金额,格式为:xxxx.xx元 YES
支付总时间 ExpireTimeSpan String 默认为30分钟 该参数指定当前支付请求必须在指定时间跨S度内完成,否则按过期处理 NO
支付结果通知URL MerchantUrl String 128 支付通知结果 不能携带参数 YES
支付结果参数 MerchantPara String 128 支付结果参数 商户自定义 NO
商户校验码 MerchantCode String 14 见下面说明 YES
支付成功后需要跳转的地址 MerchantRetUrl String 128 商户接收签约设置结果url YES
通知附带的参数 MerchantRetPara String 128 通知的参数 NO

支付成功后需要跳转的地址:在支付成功后5秒钟,会自动进行跳转 页面也会出现 返回商户按钮 点击效果相同。

返回商户

商户校验码:根据下面的方法获取到商户校验码。

商户校验码

商户校验码的获取方式并不是 Web Service ,而是招商提供的商户开发包里面的方法 我这里只介绍 Java,其它语言差别不大。

商户校验码只是确保订单内容不被恶意篡改 所以商户校验码参数里面的大多数属性都要和支付订单属性保持一致,否则会提示订单内容被篡改。

public static String genMerchantCode(String strKey, 
String strDate,
String strBranchID,
String strCono,
String strBillNo,
String strAmount,
String strMerchantPara,
String strMerchantUrl,
String strPayerID,
String strPayeeID,
String strClientIP, 
String strGoodsType,
String strReserved)

这里要吐槽一下 这么多的参数 封装成 POJO 对象也好啊! 排查错误的时候眼睛都看瞎了。

参数说明

参数名称 参数说明 是否必传
strKey 商户秘钥 YES
strDate 订单日期 YES
strBranchID 开户分行号 YES
strCono 商户号(6位商户号) YES
strBillNo 订单号 YES
strAmount 订单金额 YES
strMerchantPara 商户自定义参数 NO
strMerchantUrl 商户接收通知的URL YES
strPayerID 付款方用户表示 唯一 (USERID) NO
strClientIP 商户取得的客户端IP,如果有多个 IP 用逗号”,”分隔。长度限制为64字节。 NO
strGoodsType 商品类型,长度限制为8字节。 NO
strReserve 保留字段,长度限制为1024字节 YES

即使不是必传参数 也不能写NULL 可以是空字符串。

strReserve:说是保留字段,但是却一定不能少了它 这个属性里面存放的是签约的参数

<Protocol>
<PNo>客户协议号</PNo>
<TS>交易时间</TS> <!--yyyyMMddHHmmss-->
<MchNo>协议商户企业编号</MchNo> <!--8位网银商户号-->

<Seq>协议开通请求流水号</Seq>
<URL>协议开通结果通知命令请求地址</URL>
<Para>协议开通结果通知命令参数</Para>

<MUID>协议用户ID</MUID>
<Mobile>协议手机号</Mobile>
<LBS>地理位置</LBS>
<RskLvl>客户风险等级</RskLvl>
</Protocol>

把以上字段拼接成 XML 格式,放入 strReserve 属性里面。

完成上面步骤之后,就能够调起网银一卡通一键支付。

最后

支付 和 签约 提交参数都是 POST 的方式提交。

所有的回调URL参数必须拼接成这样的:MerchantPara=Ref1=12345678|Ref2=ABCDEFG|Ref3=HIJKLM

订单号:10 位数字 (支付使用)
流水号:20 位字符串 (签约使用)

订单号 和 流水号 都是按照天进行分组 只要确保一天内不会重复即可。

一卡通还提供了 退款操作 以及 取消签约 文档里面都有说明 但参数传入方式和逻辑都是按照上面的步骤进行 有此业务的同学可以查看下。