PAYATHON 2026

POS Terminal 与 Bank 之间的 ISO 8583 支付通信

支付老李

结论

一条 ISO 8583 授权请求可以同时携带交易金额和磁道数据。金额通常放在 Data Element 4,Track 2 数据通常放在 Data Element 35,Track 1 数据可以放在 Data Element 45。

不过,这不意味着应该同时发送 Track 1 和 Track 2,也不意味着连接银行提供的 IP 和端口后就能直接交易。实际使用的报文字段、编码、报文头、长度前缀、加密方式和交易状态机,都必须按照收单银行的 POS 接入规范实现,并通过银行的联调、认证和安全审核。

生产系统一般优先发送 Track 2 Equivalent Data,不会同时发送两条磁道。最终要求取决于银行和卡组织的接入规范,不能只根据通用 ISO 8583 文档判断。

正确的业务链路

多数商户 POS 不直接连接发卡银行,而是连接收单银行或支付处理机构。典型链路如下:

Card
  → POS Terminal
  → Acquirer / Payment Processor
  → Card Network
  → Issuer Bank

只有目标银行明确提供了主机直连接口,程序才应直接连接该银行的 switching host。正式开发前,至少需要从银行取得以下资料:

  • ISO 8583 接口规范,以及使用的版本或私有变体
  • 每种交易对应的 MTI、Processing Code 和必填字段
  • TCP 报文长度前缀和自定义 Header 格式
  • 字段编码方式,例如 ASCII、EBCDIC、BCD 或二进制
  • Bitmap、LLVAR、LLLVAR 的具体编码规则
  • Terminal ID、Merchant ID、Acquirer ID 等分配信息
  • MAC 算法、密钥交换和密钥轮换方案
  • PIN Block 格式和 HSM 接入要求
  • Sign-on、Echo、Key Exchange、Sign-off 流程
  • 超时、重试、冲正、撤销、退款和结算规则
  • 测试环境、测试卡、认证案例和上线流程

ISO 8583 定义的是报文结构,并没有完整规定 TCP 传输、报文头、安全机制和所有业务语义。即使两家银行都声称使用 ISO 8583,它们的报文也未必能够互通。

一次消费交易的大致过程

简化后的磁条卡消费流程通常包括:

  1. POS 读取卡片数据。
  2. POS 获取金额,并在终端侧进行校验。
  3. 如果交易需要验证持卡人,则采集 PIN 或签名。
  4. POS 生成唯一的终端流水号。
  5. 组装金融授权请求,例如 0200。
  6. 通过银行指定的安全连接发送请求。
  7. 银行返回响应,例如 0210。
  8. POS 根据响应码判断交易是批准、拒绝还是出现异常。
  9. 如果请求结果无法确定,应按照规范进行查询或冲正,不能直接重发消费请求。
  10. 后续按照银行要求完成批结算或清算。

MTI 并不是全球统一且固定不变的。某些接口使用 0100/0110 处理授权,使用 0200/0210 处理金融交易,另一些接口可能采用不同的安排。具体应以银行规范为准。

金额和磁道数据如何放入同一报文

按照常见的 ISO 8583 字段安排,一条消费请求可能包含:

Data Element常见含义
DE 2Primary Account Number
DE 3Processing Code
DE 4Amount, Transaction
DE 7Transmission Date and Time
DE 11Systems Trace Audit Number
DE 14Expiration Date
DE 22Point of Service Entry Mode
DE 25POS Condition Code
DE 35Track 2 Data
DE 41Card Acceptor Terminal Identification
DE 42Card Acceptor Identification Code
DE 49Currency Code
DE 52PIN Data,在需要联机 PIN 时使用
DE 55EMV ICC Data
DE 64/128Message Authentication Code,具体位置取决于协议

金额和 Track 2 数据可以放在同一条消息中,例如:

MTI  = 0200
DE 3 = 000000
DE 4 = 000000012345
DE 11 = 123456
DE 22 = 021
DE 35 = <Track 2 data>
DE 41 = TERM0001
DE 42 = MERCHANT0000001
DE 49 = 156

DE 4 通常使用固定长度,以货币的最小单位表示金额。如果该货币有两位小数:

000000012345

可能表示 123.45。但不能把小数位写死,程序应根据币种指数和银行规范处理。

Track 2 一般以 DE 35 的 LLVAR 数据发送。ISO 8583 报文通常不包含读取磁条时的起始符和结束符。因此,是否移除 ; 和 ?、使用 = 还是 D 作为分隔符,以及如何计算长度,都要以银行规定的字段编码规则为准。

不建议同时发送 Track 1 和 Track 2

DE 35 和 DE 45 虽然可以同时存在,但正常交易一般不需要这样处理。常见情况包括:

  • 磁条交易发送 DE 35。
  • 某些特定交易或主机规范要求发送 DE 45。
  • EMV 芯片交易主要发送 DE 55,也可能在 DE 35 中携带 Track 2 Equivalent Data。
  • 非接触式 EMV 交易同样不能按普通磁条数据处理。
  • 手工输入卡号时,一般发送 DE 2 和 DE 14,并设置相应的 Entry Mode,而不是伪造磁道数据。

银行会按照自己的字段组合规则进行校验,是否允许同时发送 DE 35 和 DE 45,取决于这套规则。多发字段可能造成格式错误、交易被拒绝,甚至触发风控。

磁道数据解析不能只靠 Split

字符串分割适合用来演示基本思路,但不能直接用于支付终端,原因包括:

  • 没有处理 %、;、?、换行和回车。
  • Track 1 和 Track 2 同时出现时,if/else if 只会处理 Track 1。
  • 没有检查字段数量和最小长度。
  • Substring 遇到截断的数据会抛出异常。
  • 没有验证 LRC 或读卡器返回状态。
  • 将卡号和磁道数据放入普通 UI 控件,会增加敏感数据的暴露范围。
  • 完成支付授权通常不需要持卡人姓名。

如果只在隔离的测试环境中解析模拟数据,可以先分别提取两条磁道:

using System;
using System.Text.RegularExpressions;

public sealed record MagneticStripeData(
    string? Track1,
    string? Track2,
    string? Pan,
    string? ExpirationYYMM,
    string? ServiceCode);

public static class MagneticStripeParser
{
    private static readonly Regex Track1Regex = new(
        @"%B(?<pan>[0-9]{1,19})\^(?<name>[^^]{0,26})\^(?<expiry>[0-9]{4})(?<service>[0-9]{3})(?<discretionary>[^?]*)\?",
        RegexOptions.CultureInvariant);

    private static readonly Regex Track2Regex = new(
        @";(?<pan>[0-9]{1,19})=(?<expiry>[0-9]{4})(?<service>[0-9]{3})(?<discretionary>[^?]*)\?",
        RegexOptions.CultureInvariant);

    public static MagneticStripeData Parse(string raw)
    {
        ArgumentNullException.ThrowIfNull(raw);

        Match track1 = Track1Regex.Match(raw);
        Match track2 = Track2Regex.Match(raw);

        if (!track1.Success && !track2.Success)
        {
            throw new FormatException("No valid Track 1 or Track 2 data was found.");
        }

        Match source = track2.Success ? track2 : track1;

        return new MagneticStripeData(
            Track1: track1.Success ? track1.Value : null,
            Track2: track2.Success ? track2.Value : null,
            Pan: source.Groups["pan"].Value,
            ExpirationYYMM: source.Groups["expiry"].Value,
            ServiceCode: source.Groups["service"].Value);
    }
}

这段代码只负责识别输入格式,不代表它符合 PCI、EMV 或任何银行的生产要求。正则表达式也无法替代读卡器状态检查、设备认证和终端内核。

Track 1 中的姓名通常采用 SURNAME/FIRST NAME 格式,不能据此可靠判断固定的“名/姓”顺序。有效期格式是 YYMM,展示时可以转换成 MM/YY,但发送给主机时必须保留协议要求的格式。

ISO 8583 组包示意

如果不使用第三方库,就需要自行实现字段定义、Bitmap、变长字段和编码。可以先建立清晰的数据模型:

public sealed record IsoFieldDefinition(
    int Number,
    int MaxLength,
    bool VariableLength,
    int LengthDigits,
    IsoEncoding Encoding);

public enum IsoEncoding
{
    Ascii,
    Bcd,
    Binary
}

生成 Bitmap 时,每个存在的字段对应一个 bit。下面的代码只演示主 Bitmap 的位设置,不包括银行 Header、长度前缀和字段编码:

using System;

public static class IsoBitmap
{
    public static byte[] CreatePrimary(params int[] fields)
    {
        var bitmap = new byte[8];

        foreach (int field in fields)
        {
            if (field < 2 || field > 64)
            {
                throw new ArgumentOutOfRangeException(
                    nameof(fields),
                    "Primary bitmap supports fields 2 through 64.");
            }

            int bitIndex = field - 1;
            int byteIndex = bitIndex / 8;
            int bitInByte = bitIndex % 8;

            bitmap[byteIndex] |= (byte)(0x80 >> bitInByte);
        }

        return bitmap;
    }
}

实际组包还要处理以下内容:

[Length Prefix]
[Bank-specific Header]
[MTI]
[Primary Bitmap]
[Secondary Bitmap, if present]
[DE 2]
[DE 3]
[DE 4]
...
[MAC]

其中任何一项都可能使用 ASCII、BCD 或二进制编码。LLVAR 的长度头也可能是两个 ASCII 数字,或者一个压缩 BCD 字节。没有银行的接口文档,就无法安全地确定最终字节流。

连接不仅是 IP + Port

银行提供的地址和端口只是连接入口,还需要确认:

  • 连接使用普通 TCP、TLS、VPN、专线还是双向 TLS。
  • 是否需要将固定源 IP 加入白名单。
  • 每条消息前是否包含 2 字节或 4 字节长度。
  • 长度采用大端、Little Endian、ASCII 还是 BCD。
  • 是否需要 TPDU、Network Header 或银行私有 Header。
  • 使用长连接,还是每笔交易重新建立连接。
  • 空闲连接如何保活。
  • 是否需要 0800/0810 Sign-on 和 Echo。
  • 请求和响应如何关联。
  • Socket 断开或超时时,如何判断交易状态。

需要特别注意,超时并不等于交易失败。银行可能已经处理了请求,只是响应在传输过程中丢失。此时直接重发 0200 可能造成重复扣款。通常应按照银行规范发送冲正,例如 0400/0410 或其他约定消息。

安全和合规要求

磁道数据是高度敏感的支付数据,不能按普通字符串处理。

以下要求尤其需要注意:

  • 不要记录完整的 Track 1 或 Track 2。
  • 不要在异常、调试日志、数据库或监控系统中保存磁道数据。
  • 不要保存 CVV/CVC、PIN、PIN Block 或完整磁道数据。
  • 展示 PAN 时,只显示必要的位数,例如掩码后的后四位。
  • PIN 必须通过经过认证的安全 PIN 输入设备采集。
  • PIN Block 和支付密钥通常必须交给 HSM 处理。
  • 不要将 Track 数据长期保存在托管字符串中。
  • 网络传输必须使用银行认可的加密和消息认证机制。
  • 系统必须满足适用的 PCI DSS、PCI PTS、EMV 和卡组织规则。
  • 读卡设备及其固件通常也需要经过认证,生产卡数据不能只靠 PC 上的文本输入框接收。

在生产环境中,“完全不使用 third party tools”通常并不现实。支付系统依赖经过认证的 POS 设备、EMV Kernel、密码设备、HSM、密钥管理设施和收单银行认证。自行编写 ISO 8583 编解码器在技术上可行,但自行替代这些安全组件通常很难通过认证,也会带来很高的资金和合规风险。

建议的实施顺序

  1. 与收单银行签订接入协议,取得正式接口规范。
  2. 确认交易类型,包括磁条、EMV 接触式、非接触式、手工输入或预授权。
  3. 获取银行分配的 Terminal ID、Merchant ID 和测试密钥。
  4. 使用经过认证的支付终端和安全 PIN 输入设备。
  5. 根据银行字段表实现 ISO 8583 Pack/Unpack。
  6. 实现网络 Header、长度前缀、TLS/VPN 和 MAC。
  7. 建立完整的交易状态机,覆盖超时、冲正、撤销、退款和结算。
  8. 使用银行测试环境和测试卡联调,不使用真实卡数据。
  9. 执行 PCI 和银行要求的安全评估。
  10. 通过银行认证后再进入生产环境。

参考资料

可以从以下规范入手,但部分完整标准需要购买或通过合作银行取得:

这些通用资料可以帮助理解基础结构,但最终决定字段、字节编码和业务流程的,仍是目标银行提供的 host interface specification、security specification 和 certification test cases。

备注:内容仅供参考。