银联卡8583协议小额免密免签交易总结

之前做过金融支付这块儿。到过北京石景山区银行卡检测中心过检PBOC的level2认证,去过上海银联总部和湖南银联对接银联卡和扫码支付。对金融支付和卡交易这块儿熟悉。现在这块儿知识用不上了总结下留作备忘,同时分享给有需要的人。

关于免密免密交易

免密支付指的是在支付一定金额时,不用输入密码,即可完成交易。小额免密免签,也叫小额双免。是中国银联提供的一种小额快速支付服务。银联闪付就是免密支付的应用,通过联机方式,使用具有“闪付”标识的银行银联芯信用卡,或承载个人信用卡信息的移动支付产品,将银联芯片卡靠近终端“闪付”感应区即可完成支付。

这是大概在2015年10月银联为了紧跟时代潮流和方便用户推出的新业务。我们知道原来的银行卡用起来很不方便,随后它推出的有一个"电子现金"功能,但还是需要先到ATM机上往卡里圈存一部分钱才能用,这部分圈存到卡片上的钱俗称电子现金,消费通过带有闪付标识的银联pos机使用,只需挥卡不用输入密码和签名即可消费,用在一些小额支付场景如公交,地铁和商超。

现在这种小额支付免密交易很流行,各大平台和支付结构都有一定小额的免密交易。银联小额免密免签不需要密码也能保障资金安全,因为小额免密免签使用高安全性的金融ic卡,同时银联联合银行为持卡人提供风险控制和赔付机制。

银联8583协议

8583协议在金融系统中很常用,报文简短,定义清晰规范。是基于ISO8583报文国际标准的包格式的通讯协议,8583包最多由128个字段域组成,每个域都有统一的规定,并有定长与变长之分。8583包前面一段为位图,它是打包解包确定字段域的关键代替。

曾写过一个java版的8583解析协议,全互联网最简单好用的,地址在这里:

java版银联8583协议解析,超简单超直观的实现及示例(全互联网最简单)_特立独行的猫a的博客-CSDN博客_银联8583demo

这里写图片描述

 

这里写图片描述

银联公网接入规范

银联提供了公网https接入规范。HTTP交易报文由http头加传统交易报文数据组成。http头中主要定义了交易报文类型及长度,同时需符合http协议规范。

POST /mjc/webtrans/XX HTTP/1.1
HOST: 145.4.206.XX:XX
User-Agent: XXXX
Cache-Control: no-cache
Content-Type:x-ISO-TPDU/x-auth
Accept: */*
Content-Length: length

https握手流程

HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL。在发送HTTP报文之前需要建立SSL连接,以此加密HTTP数据。关系如下图所示:

SSL连接分为两个阶段,即握手和数据传输阶段;传输任何应用数据之前必须先完成握手。

握手协议:

  1. 对服务器进行认证;
  2. 确立用于保护数据传输的加密密钥;
  3. 记录协议:
  4. 传输数据;

银联通信MAC算法

8583通信有个MAC校验,相关算法参照博主博文:

ANSI-X99MAC算法和PBOC的3DES MAC算法_特立独行的猫a的博客-CSDN博客

Java的HTTP接口封装

没有使用第三方的jar包,使用java自带的net.HttpURLConnection包的封装,简单易用。支持http和https,可以让https不验证本地证书。

package com.yang.utils;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

/**
 * https请求工具类
 *
 * @author yang
 * @date 2018/6/6 19:12
 */
public class HttpsUtils {

    /*
     * https请求是在http请求的基础上加上一个ssl层
     */
    public static String doPost(String requestUrl, String bodyStr, Map<String, String> header, String charset, String contentType) throws Exception {
        System.out.printf("https post 请求地址:%s 内容:%s", requestUrl, bodyStr);
        charset = null == charset ? "utf-8" : charset;

        // 创建SSLContext
        SSLContext sslContext = SSLContext.getInstance("SSL");
        TrustManager[] trustManagers = {new X509TrustManager() {
            /*
             * 实例化一个信任连接管理器
             * 空实现是所有的连接都能访问
             */
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }};
        // 初始化
        sslContext.init(null, trustManagers, new SecureRandom());
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        URL url = new URL(requestUrl);
        HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
        httpsURLConnection.setSSLSocketFactory(sslSocketFactory);

        // 以下参照http请求
        httpsURLConnection.setDoOutput(true);
        httpsURLConnection.setDoInput(true);
        httpsURLConnection.setUseCaches(false);
        httpsURLConnection.setRequestMethod("POST");
        httpsURLConnection.setRequestProperty("Accept-Charset", charset);
        if (null != bodyStr) {
            httpsURLConnection.setRequestProperty("Content-Length", String.valueOf(bodyStr.length()));
        }
        if (contentType == null) {
            httpsURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        } else {
            httpsURLConnection.setRequestProperty("Content-Type", contentType);
        }
        if (!header.isEmpty()) {
            for (Map.Entry<String, String> entry : header.entrySet()) {
                httpsURLConnection.setRequestProperty(entry.getKey(), entry.getValue());
            }
        }
        httpsURLConnection.connect();

        // 读写内容
        OutputStream outputStream = null;
        InputStream inputStream = null;
        InputStreamReader streamReader = null;
        BufferedReader bufferedReader = null;
        StringBuffer stringBuffer;
        try {
            if (null != bodyStr) {
                outputStream = httpsURLConnection.getOutputStream();
                outputStream.write(bodyStr.getBytes(charset));
                outputStream.close();
            }

            if (httpsURLConnection.getResponseCode() >= 300) {
                throw new Exception("https post failed, response code " + httpsURLConnection.getResponseCode());
            }

            inputStream = httpsURLConnection.getInputStream();
            streamReader = new InputStreamReader(inputStream, charset);
            bufferedReader = new BufferedReader(streamReader);
            stringBuffer = new StringBuffer();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuffer.append(line);
            }
        } catch (Exception e) {
            throw e;
        } finally {
            if (outputStream != null) {
                outputStream.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
            if (streamReader != null) {
                streamReader.close();
            }
            if (bufferedReader != null) {
                bufferedReader.close();
            }
        }
        System.out.printf("--- https post 返回内容:%s", stringBuffer.toString());
        return stringBuffer.toString();
    }

    public static byte[] doUpHttpsPost(String requestUrl, byte[] bodyHex) throws Exception {
        System.out.printf("请求地址:%s\n", requestUrl);
        // 创建SSLContext
        SSLContext sslContext = SSLContext.getInstance("SSL");
        TrustManager[] trustManagers = {new X509TrustManager() {
            /*
             * 实例化一个信任连接管理器
             * 空实现是所有的连接都能访问
             */
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                //return new X509Certificate[0];
                return new X509Certificate[]{};
            }
        }};
       
        // 初始化
        sslContext.init(null, trustManagers, new SecureRandom());
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        URL url = new URL(requestUrl);
        HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
        httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
        HostnameVerifier hv =  new HostnameVerifier(){
            @Override
            public boolean verify(String string, SSLSession ssls) {
                return true;//To change body of generated methods, choose Tools | Templates.
            }
        };
        httpsURLConnection.setHostnameVerifier(hv);

        // 以下参照http请求
        httpsURLConnection.setDoOutput(true);
        httpsURLConnection.setDoInput(true);
        httpsURLConnection.setUseCaches(false);
        httpsURLConnection.setRequestMethod("POST");
        
        String strLen = String.valueOf(bodyHex.length);
       
        httpsURLConnection.setRequestProperty("User-Agent", "Donjin Http 0.1");
        httpsURLConnection.setRequestProperty("Content-Length", strLen);
        httpsURLConnection.setRequestProperty("Accept", "*/*");
        httpsURLConnection.setRequestProperty("Content-Type", "x-ISO-TPDU/x-auth");
        httpsURLConnection.setRequestProperty("Cache-Control", "no-cache");
        httpsURLConnection.connect();

        // 读写内容
        OutputStream outputStream = null;
        InputStream inputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
          
            outputStream = httpsURLConnection.getOutputStream();
            outputStream.write(bodyHex);
            outputStream.flush();
            outputStream.close();
            System.out.printf("\n--- https ResponseCode:%d\n", httpsURLConnection.getResponseCode());
            if (httpsURLConnection.getResponseCode() >= 300) {
                throw new Exception("https post failed, response code " + httpsURLConnection.getResponseCode());
            }

            inputStream = httpsURLConnection.getInputStream();
            byteArrayOutputStream = new ByteArrayOutputStream();
            int len = 0;
            byte[] bytes = new byte[inputStream.available()];
            while ((len = inputStream.read(bytes)) != -1) {
                byteArrayOutputStream.write(bytes, 0, len);
            }//写入数据
            return byteArrayOutputStream.toByteArray();
             //byte[] nbytes = bytes.c
        } catch (Exception e) {
            throw e;
        } finally {
            if (outputStream != null) {
                outputStream.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
            if (byteArrayOutputStream != null) {
                byteArrayOutputStream.close();
            }
        }
    }

}

免密免签交易流程

测试源码在这里:

https://download.csdn.net/download/qq8864/87280073

报文示例

test...
2022/12/13 18:57:16 Server:XXX.XX.XX.XXX
2022/12/13 18:57:16 Port:0
2022/12/13 18:57:16 Url:https://202.xx.25.xx:xx/mjc/webtrans/ABC
pack 8583 fields
Print fields...

==========================================
Len:    0057
Tpdu:   6002100000
Head:   613100311108
Msge:   0800
Bitmap: 0020000000c00016

==========================================
[field:11] [000001]

------------------------------
[field:41] [3134313030303033]

------------------------------
[field:42] [313431343230313431313130303033]

------------------------------
[field:60] [len:0011] [000000000030]

------------------------------
[field:62] [len:0025] [53657175656e6365204e6f3132333036303134313030303033]

------------------------------
[field:63] [len:0003] [303031]

------------------------------
2022/12/13 18:57:16 connect:server=https://2xx.101.25.18x:2x141/mjc/webtrans/ABC
send:0057600210000061310031110808000020000000c0001600000131343130303030333134313432303134313131303030330011000000000030002553657175656e6365204e6f31323330363031343130303030330003303031
ReadFile err: open UP.pem: no such file or directory
2022/12/13 18:57:16 begin post...
User-Agent : Donjin Http 0.1
Content-Type :  x-ISO-TPDU/x-auth
Cache-Control : no-cache
2022/12/13 18:57:16 recv ok!len=123
recv:007960000002106131003111080810003800010ac00014000001185716121308000952103138353731363035373232373030313431303030303331343134323031343131313030303300110000001700300040b31544e6cf8a5d091450ad5970c2143e7e960636afd9761ce25f41f70000000000000000c7e1867b
ans 8583 fields
解析成功
Print fields...

==========================================
Len:    0079
Tpdu:   6002100000
Head:   613100311108
Msge:   0810
Bitmap: 003800010ac00014

==========================================
[field:11] [000001]

------------------------------
[field:12] [185716]

------------------------------
[field:13] [1213]

------------------------------
[field:32] [len:08] [00095210]

------------------------------
[field:37] [313835373136303537323237]

------------------------------
[field:39] [3030]

------------------------------
[field:41] [3134313030303033]

------------------------------
[field:42] [313431343230313431313130303033]

------------------------------
[field:60] [len:0011] [000000170030]

------------------------------
[field:62] [len:0040] [b31544e6cf8a5d091450ad5970c2143e7e960636afd9761ce25f41f70000000000000000c7e1867b]

------------------------------
mackey:e5986862d3efefae
2022/12/13 18:57:16 签到成功
银联 ISO8583 文档: 前 言 VI 1 范围 1 2 规范性引用文件 1 3 术语和定义 1 3.1 受理方 (ACQUIRER) 1 3.2 发卡方 (ISSUER) 1 3.3 转入方 (TRANSFER-IN) 2 3.4 转出方 (TRANSFER-OUT) 2 3.5 交换系统 (BANK CARD SWITCHING SYSTEM) 2 3.6 请求 (REQUEST) 2 3.7 响应码 (RESPONSE CODE) 2 3.8 冲正 (REVERSAL) 2 3.9 清算 (SETTLEMENT) 2 3.10 交易 (TRANSACTION) 2 3.11 通知 (ADVICE) 2 3.12 报文 (MESSAGE) 2 3.13 数据包 (DATAGRAM) 2 4 公共支付交易处理说明 3 4.1 公共支付业务联机交易处理 3 4.1.1 委托关系建立/委托关系撤销(0100/0110) 3 4.1.2 订购(类似预授权0100/0110) 3 4.1.3 金融类交易(0200/0210) 5 4.1.4 金融类撤销交易(0200/0210) 8 4.1.5 查询类交易(0200/0210) 9 4.1.6 转账类交易(0200/0210) 10 4.1.7 冲正通知类交易(0420/0430) 10 4.1.8 金融通知类交易(0220/0230) 12 4.1.9 与服务提供商无关的交易 12 4.2 增值业务文件方式处理 13 4.2.1 非实时查询交易 13 4.2.2 非实时缴费/充值交易 14 4.2.3 批量代收/批量代付文件方式 14 4.2.4 委托关系建立/委托关系撤销文件方式 15 4.3 超时限定 16 4.4 公共支付平台二级清算处理 17 4.4.1 截账日切通知(0820/0830) 17 4.4.2 批结对账交易(0522/0532) 17 4.4.3 公共支付平台二级清算产生的交易处理流程 18 4.4.4 公共支付平台清分清算的时序 19 4.5 管理及安全控制交易处理 19 4.5.1 网络管理交易(0820/0830) 19 4.5.2 重置密钥(0800/0810) 21 4.6 交易的异常处理流程 21 4.6.1 概述 21 4.6.2 异常处理原则 21 4.6.3 通信异常 22 5 报文域定义 31 5.1 说明 31 5.2 数据类型定义 31 5.3 域名称及定义 31 5.3.1 报文头 31 5.3.2 MTI 报文类型 36 5.3.3 第一位图 37 5.3.4 第二位图 37 5.3.5 域2 主账号PAN 37 5.3.6 域3 交易处理码 38 5.3.7 域4 交易金额 40 5.3.8 域7交易传输时间 40 5.3.9 域11系统跟踪号 41 5.3.10 域12受卡方所在地时间 41 5.3.11 域13受卡方所在地日期 42 5.3.12 域14卡有效期 42 5.3.13 域15清算日期 42 5.3.14 域18商户类型 43 5.3.15 域22服务点输入方式码 43 5.3.16 域25服务点条件码 44 5.3.17 域26服务点PIN获取码 44 5.3.18 域32代理机构标识码 45 5.3.19 域33发送机构标识码 45 5.3.20 域35第二磁道数据 45 5.3.21 域36第三磁道数据 46 5.3.22 域37检索参考号 46 5.3.23 域39应答码 47 5.3.24 域41受卡机终端标识码 47 5.3.25 域42受卡方标识码 47 5.3.26 域43受卡方名称地址 47 5.3.27 域44附加响应数据 48 5.3.28 域48附加自定义数据 48 5.3.29 域49交易货币代码 54 5.3.30 域50清算货币代码 54 5.3.31 域52个人标识码数据 54 5.3.32 域53安全控制信息 55 5.3.33 域54实际余额 56 5.3.34 域59明细查询数据 57 5.3.35 域60自定义域 61 5.3.36 域61证件编号 63 5.3.37 域70网络管理信息码 66 5.3.38 域74 贷记交易笔数 67 5.3.39 域75 冲正贷记笔数 67 5.3.40 域76 借记交易笔数 67 5.3.41 域77 冲正借记笔数 67 5.3.42 域78 转账笔数 68 5.3.43 域79 冲正转账笔数 68 5.3.44 域80 查询笔数 68 5.3.45 域81 授权笔数 68 5.3.46 域82 贷记服务费金额 69 5.3.47 域84 借记服务费金额 69 5.3.48 域86 贷记交易金额 69 5.3.49 域87 冲正贷记金额 70 5.3.50 域88 借记交易金额 70 5.3.51 域89 冲正借记金额 70 5.3.52 域90原始数据元 70 5.3.53 域96报文安全码 71 5.3.54 域99清算机构代码 71 5.3.55 域100接收机构标识码 72 5.3.56 域102账户标识1 72 5.3.57 域103账户标识2 72 5.3.58 域121 交换系统保留 72 5.3.59 域122受理方保留 75 5.3.60 域123发卡方保留 75 5.3.61 域128报文鉴别码MAC 75 6 公共支付平台交易接口报文 77 6.1 说明 77 6.1.1 符号约定 77 6.1.2 报文格式说明示意 77 6.1.3 报文域条件数据元说明 78 6.2 公共支付平台联机交易报文 78 6.2.1 欠费查询/资金户余额查询报文 78 6.2.2 账单明细查询报文 79 6.2.3 银行卡余额查询 81 6.2.4 订购(预授权)报文 82 6.2.5 订购撤销(预授权撤销)报文 84 6.2.6 订购完成(预授权完成)报文 85 6.2.7 订购完成撤销(预授权完成撤销) 86 6.2.8 缴费/充值报文 87 6.2.9 缴费撤销报文 89 6.2.10 冲正通知报文 90 6.2.11 建立/撤销委托关系报文 93 6.2.12 查询委托关系报文 95 6.2.13 转账类交易报文 96 6.2.14 与服务提供商无关的交易报文 99 6.3 清分清算和日终批处理的报文接口 错误!未定义书签。 6.3.1 批结对账交易报文 错误!未定义书签。 6.3.2 截账日切通知报文 错误!未定义书签。 6.4 网络管理及安全控制报文 107 6.4.1 网络管理报文 107 6.4.2 安全控制报文 109 7 缴费终端接口报文 111 7.1 说明 111 7.1.1 标准接口报文及流程图 111 7.1.2 消息格式 111 7.1.3 消息格式的表示方法 111 7.2 委托关系建立/委托关系撤销终端报文 111 7.3 待缴费用/资金户余额查询终端报文 111 7.4 缴费账单明细查询终端报文 111 7.5 缴费/缴费撤销终端报文 111 7.6 银行卡转出/银行卡转入终端报文 111 7.7 终端签到 111 7.8 终端签退 错误!未定义书签。 7.9 终端批结对账交易、批上送完成通知(可选) 错误!未定义书签。 7.10 终端批上送记录(可选) 错误!未定义书签。 8 文件接口规范 111 8.1 概述 111 8.1.1 目的 111 8.1.2 适用范围 111 8.1.3 相关文档 111 8.2 文件存取方式说明 111 8.2.1 FTP方式 112 8.2.2 WEB方式 113 8.3 文件使用说明 115 8.3.1 基本的命名约定 115 8.3.2 记录格式基本约定 116 8.3.3 流水文件说明 120 8.3.4 批量代收/代付文件说明 121 8.4 文件格式说明 123 8.4.1 符号定义 123 8.4.2 流水文件格式 123 8.4.3 批量代收/代付文件格式 125 8.4.4 非实时待缴费用托管文件格式 129 8.4.5 委托关系文件格式 130 9 通信接口规范 131 9.1 目的 131 9.2 网络架构 132 9.3 网络接口 132 9.3.1 接入设备基本要求 132 9.3.2 通信软件接口 132 10 数据安全传输控制 134 10.1 个人标识(PIN)的加密和解密 134 10.1.1 PIN的长度 135 10.1.2 PIN的字符集 136 10.1.3 PIN格式 136 10.1.4 PIN异常的处理 137 10.2 报文来源正确性鉴别MAC 137 10.2.1 MAC报文域的选择 138 10.2.2 MAC域的构成规则 140 10.2.3 MAC的计算 140 10.2.4 MAC错误异常处理 141 附 录 A 142 参考文献 154
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

特立独行的猫a

您的鼓励是我的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值