You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
322 lines
7.7 KiB
322 lines
7.7 KiB
<?php
|
|
// namespace ISO8583;
|
|
|
|
// use ISO8583\Error\UnpackError;
|
|
// use ISO8583\Error\PackError;
|
|
require_once(dirname(__FILE__) . '/Error/UnpackError.php');
|
|
require_once(dirname(__FILE__) . '/Error/PackError.php');
|
|
|
|
require_once(dirname(__FILE__) . '/Mapper/AbstractMapper.php');
|
|
require_once(dirname(__FILE__) . '/Mapper/AlphaNumeric.php');
|
|
require_once(dirname(__FILE__) . '/Mapper/Binary.php');
|
|
|
|
/**
|
|
* 8583报文
|
|
*/
|
|
class Message
|
|
{
|
|
protected $protocol; //8583协议定义
|
|
protected $options; //配置信息 lengthPrefix appCategory
|
|
|
|
protected $length; //消息长度
|
|
|
|
protected $tpdu; //TPDU ID(60H)+目的地址(N2)+源地址(N2)
|
|
protected $header; //报文头 {应用类别(N2)}+软件版本号(N2)+终端状态(N1)+处理要求(N1)+保留使用(N6)
|
|
protected $encrypted; //加密信息 报文长度(A3)+算法标识(A1)+{商户号(A15)}+{终端号(A8)}+交易标识(A10)+响应码(A2)+保留(A2)
|
|
|
|
//应用数据 交易数据(ISO8583Msg)
|
|
protected $mti; //消息类型(Message Type Identifier) —— 0200金融类请求消息
|
|
protected $bitmap; //位图
|
|
protected $fields = []; //数据域
|
|
|
|
protected $mappers = [ //数据类型
|
|
'a' => AlphaNumeric::class,
|
|
'n' => AlphaNumeric::class,
|
|
's' => AlphaNumeric::class,
|
|
'an' => AlphaNumeric::class,
|
|
'as' => AlphaNumeric::class,
|
|
'ns' => AlphaNumeric::class,
|
|
'ans' => AlphaNumeric::class,
|
|
'b' => Binary::class,
|
|
'z' => AlphaNumeric::class
|
|
];
|
|
|
|
public function __construct(Protocol $protocol , $options = [])
|
|
{
|
|
$defaults = [
|
|
'lengthPrefix' => null
|
|
];
|
|
|
|
$this->options = $options + $defaults;
|
|
$this->protocol = $protocol;
|
|
}
|
|
|
|
protected function shrink(&$message, $length)
|
|
{
|
|
$message = substr($message, $length);
|
|
}
|
|
|
|
public function pack()
|
|
{
|
|
// 设置 TPDU
|
|
// $tpdu = $this->tpdu;
|
|
$tpdu = bin2hex($this->tpdu);
|
|
// 设置 报文头
|
|
// $header = $this->header;
|
|
$header = bin2hex($this->header);
|
|
// 设置 加密信息
|
|
// $encrypted = $this->encrypted;
|
|
$encrypted = bin2hex($this->encrypted);
|
|
// 设置 MTI
|
|
// $mti = $this->mti;
|
|
$mti = bin2hex($this->mti);
|
|
|
|
// Dropping bad fields
|
|
foreach($this->fields as $key=>$val) {
|
|
if (in_array($key, [1, 65])) {
|
|
unset($this->fields[$key]);
|
|
}
|
|
}
|
|
|
|
// 填充 位图
|
|
$bitmap = "";
|
|
$bitmapLength = 64 * (floor(max(array_keys($this->fields)) / 64) + 1);
|
|
|
|
$tmpBitmap = "";
|
|
for($i=1; $i <= $bitmapLength; $i++) {
|
|
if (
|
|
$i == 1 && $bitmapLength > 64 ||
|
|
$i == 65 && $bitmapLength > 128 ||
|
|
isset($this->fields[$i])
|
|
) {
|
|
$tmpBitmap .= '1';
|
|
} else {
|
|
$tmpBitmap .= '0';
|
|
}
|
|
|
|
if ($i % 64 == 0) {
|
|
for($i=0; $i<64; $i+=4){
|
|
$bitmap .= sprintf('%01x', base_convert(substr($tmpBitmap, $i, 4), 2, 10));
|
|
}
|
|
}
|
|
}
|
|
$this->bitmap = $bitmap;
|
|
|
|
// 域排序
|
|
ksort($this->fields);
|
|
|
|
// 打包8583报文
|
|
$message = "";
|
|
foreach($this->fields as $id => $data) {
|
|
$fieldData = $this->protocol->getFieldData($id);
|
|
$fieldMapper = $fieldData['type'];
|
|
|
|
if (!isset($this->mappers[$fieldMapper])) {
|
|
throw new \Exception('Unknown field mapper for "' . $fieldMapper . '" type');
|
|
}
|
|
|
|
$mapper = new $this->mappers[$fieldMapper]($fieldData['length']);
|
|
|
|
if (
|
|
($mapper->getLength() > strlen($data) && $mapper->getVariableLength() === 0 ) ||
|
|
$mapper->getLength() < strlen($data)
|
|
) {
|
|
$error = 'FIELD [' . $id . '] should have length: ' . $mapper->getLength() . ' and your message "' . $data . "' is " . strlen($data);
|
|
throw new PackError($error);
|
|
}
|
|
|
|
$message .= $mapper->pack($data);
|
|
}
|
|
|
|
//加密信息中包含 报文长度
|
|
if (strlen($encrypted) != 0) {
|
|
$encrypted = bin2hex(sprintf('%03d', strlen($message) / 2)) . $encrypted;
|
|
}
|
|
|
|
// 打包所有字段 依次为 TPDU、报文头、加密信息、报文体(MTI、位图、域值)
|
|
$message = $mti . $bitmap . $message;
|
|
|
|
//首位长度字段
|
|
if ($this->options['lengthPrefix'] > 0) {
|
|
$message = bin2hex(sprintf('%0' . $this->options['lengthPrefix'] . 'd', strlen($message) / 2)) . $message;
|
|
}
|
|
|
|
$message = $tpdu . $header . $encrypted . $message;
|
|
|
|
//返回打包后的信息
|
|
return $message;
|
|
}
|
|
|
|
public function unpack($message)
|
|
{
|
|
// Getting message length if we have one
|
|
if ($this->options['lengthPrefix'] > 0) {
|
|
$length = (int)hex2bin(substr($message, 0, (int)$this->options['lengthPrefix'] * 2));
|
|
$this->shrink($message, (int)$this->options['lengthPrefix'] * 2);
|
|
|
|
if (strlen($message) != $length * 2) {
|
|
throw new UnpackError('Message length is ' . strlen($message) / 2 . ' and should be ' . $length);
|
|
}
|
|
}
|
|
|
|
// Parsing TPDU
|
|
$this->setTPDU(hex2bin(substr($message, 0, 20)));
|
|
$this->shrink($message, 20);
|
|
|
|
// Parsing Header
|
|
$this->setHeader(hex2bin(substr($message, 0, 24)));
|
|
$this->shrink($message, 24);
|
|
|
|
// Parsing length
|
|
$this->length = hex2bin(substr($message, 0, 6));
|
|
|
|
// Parsing Encrypted
|
|
$this->setEncrypted(hex2bin(substr($message, 6, 76)));
|
|
$this->shrink($message, 82);
|
|
|
|
// Parsing MTI
|
|
$this->setMTI(hex2bin(substr($message, 0, 8)));
|
|
$this->shrink($message, 8);
|
|
|
|
// Parsing bitmap
|
|
$bitmap = "";
|
|
for(;;) {
|
|
$tmp = implode(null,
|
|
array_map(
|
|
function($bit) {
|
|
return str_pad(base_convert($bit, 16, 2), 8, 0, STR_PAD_LEFT);
|
|
},
|
|
str_split(substr($message, 0, 16), 2)
|
|
)
|
|
);
|
|
|
|
$this->shrink($message, 16);
|
|
$bitmap .= $tmp;
|
|
|
|
if (substr($tmp, 0, 1) !== "1" || strlen($bitmap) > 128) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
$this->bitmap = $bitmap;
|
|
|
|
if (strlen($message) != $this->length * 2) {
|
|
throw new UnpackError('Message length is ' . strlen($message) / 2 . ' and should be ' . $this->length);
|
|
}
|
|
|
|
// Parsing fields
|
|
for($i=0; $i < strlen($bitmap); $i++) {
|
|
if ($bitmap[$i] === "1") {
|
|
$fieldNumber = $i + 1;
|
|
|
|
if ($fieldNumber === 1 || $fieldNumber === 65) {
|
|
continue;
|
|
}
|
|
|
|
$fieldData = $this->protocol->getFieldData($fieldNumber);
|
|
$fieldMapper = $fieldData['type'];
|
|
|
|
if (!isset($this->mappers[$fieldMapper])) {
|
|
throw new \Exception('找不到指定的类型定义:' . $fieldMapper);
|
|
}
|
|
|
|
$mapper = new $this->mappers[$fieldMapper]($fieldData['length']);
|
|
$unpacked = $mapper->unpack($message);
|
|
|
|
$this->setField($fieldNumber, $unpacked);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function getTPDU()
|
|
{
|
|
return $this->tpdu;
|
|
}
|
|
|
|
public function setTPDU($tpdu)
|
|
{
|
|
// if (!preg_match('/^60[0-9]{8}$/', $tpdu)) {
|
|
// throw new UnpackError('TPDU字段应该是以60开头的10位数字:' . $tpdu);
|
|
// }
|
|
|
|
$this->tpdu = $tpdu;
|
|
}
|
|
|
|
public function getHeader()
|
|
{
|
|
return $this->header;
|
|
}
|
|
|
|
public function setHeader($header)
|
|
{
|
|
// if (!preg_match('/^[0-9]{12}$/', $header)) {
|
|
// throw new UnpackError('Header字段应该是12位数字:' . $header);
|
|
// }
|
|
|
|
$this->header = $header;
|
|
}
|
|
|
|
//直接获取加密信息中<strong>不</strong>包含报文长度
|
|
public function getEncrypted()
|
|
{
|
|
return $this->encrypted;
|
|
}
|
|
|
|
public function setEncrypted($encrypted)
|
|
{
|
|
// if (strlen($encrypted) != 38) {
|
|
// throw new UnpackError('Encrypted字段应该是38位字符:' . $encrypted);
|
|
// }
|
|
|
|
$this->encrypted = $encrypted;
|
|
}
|
|
|
|
public function getMTI()
|
|
{
|
|
return $this->mti;
|
|
}
|
|
|
|
public function setMTI($mti)
|
|
{
|
|
// if (!preg_match('/^[0-9]{4}$/', $mti)) {
|
|
// throw new UnpackError('MTI字段应该是4位数字!');
|
|
// }
|
|
|
|
$this->mti = $mti;
|
|
}
|
|
|
|
public function set(array $fields)
|
|
{
|
|
$this->fields = $fields;
|
|
}
|
|
|
|
public function getFieldsIds()
|
|
{
|
|
$keys = array_keys($this->fields);
|
|
sort($keys);
|
|
|
|
return $keys;
|
|
}
|
|
|
|
public function getFields()
|
|
{
|
|
ksort($this->fields);
|
|
|
|
return $this->fields;
|
|
}
|
|
|
|
public function setField($field, $value)
|
|
{
|
|
$this->fields[(int)$field] = $value;
|
|
}
|
|
|
|
public function getField($field)
|
|
{
|
|
return isset($this->fields[$field]) ? $this->fields[$field] : null;
|
|
}
|
|
|
|
public function getBitmap()
|
|
{
|
|
return $this->bitmap;
|
|
}
|
|
}
|
|
|