Merge pull request #6 from kaisims/preAuth

More Features! SSL, PreAuthorisation and PartialCancellation And last_printout fix
This commit is contained in:
László Károlyi 2020-07-22 13:33:54 +02:00 committed by GitHub
commit aee6f4d9a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 129 additions and 6 deletions

View file

@ -16,7 +16,7 @@ from ecrterm.exceptions import (
from ecrterm.packets.base_packets import ( from ecrterm.packets.base_packets import (
Authorisation, CloseCardSession, Completion, DisplayText, EndOfDay, Packet, Authorisation, CloseCardSession, Completion, DisplayText, EndOfDay, Packet,
PrintLine, ReadCard, Registration, ResetTerminal, StatusEnquiry, PrintLine, ReadCard, Registration, ResetTerminal, StatusEnquiry,
StatusInformation, WriteFiles) StatusInformation, WriteFiles, PreAuthorisation, PartialCancellation, AbortCommand)
from ecrterm.packets.types import ConfigByte from ecrterm.packets.types import ConfigByte
from ecrterm.transmission._transmission import Transmission from ecrterm.transmission._transmission import Transmission
from ecrterm.transmission.signals import ACK, DLE, ETX, NAK, STX, TRANSMIT_OK from ecrterm.transmission.signals import ACK, DLE, ETX, NAK, STX, TRANSMIT_OK
@ -110,6 +110,14 @@ class ECR(object):
Pass `socket://` prefixed IP address and port for TCP/IP Pass `socket://` prefixed IP address and port for TCP/IP
transport: `socket://192.168.1.163:20007` transport: `socket://192.168.1.163:20007`
You can set various timeouts by passing
it in the uri. An example:
`socket://192.168.1.163:20007?connect_timeout=5&so_keepalive=5&tcp_keepidle=1&tcp_keepintvl=3&tcp_keepcnt=5`
See http://man7.org/linux/man-pages/man7/tcp.7.html for TCP
flags details.
Use Flag `ssl=true` to use connection over a secured connection. SSl/TLS
""" """
if device.startswith('/') or device.startswith('COM'): if device.startswith('/') or device.startswith('COM'):
self.transport = SerialTransport(device) self.transport = SerialTransport(device)
@ -221,7 +229,10 @@ class ECR(object):
for entry in self.transmitter.last_history: for entry in self.transmitter.last_history:
inc, packet = entry inc, packet = entry
if inc and isinstance(packet, PrintLine): if inc and isinstance(packet, PrintLine):
printout += [packet.fixed_values['text']] if packet.text is not None:
printout += [packet.text]
else:
printout += [""]
return printout return printout
def payment(self, amount_cent=50, listener=None): def payment(self, amount_cent=50, listener=None):
@ -252,6 +263,69 @@ class ECR(object):
logger.error("transmit error?") logger.error("transmit error?")
return False return False
def preauthorisation(self, amount_cent=50, listener=None):
"""
executes a preauthorisation in amount of cents.
@returns: Receipt Number, if preAuthorisation was successful, or False if it was
canceled.
throws exceptions.
"""
packet = PreAuthorisation(
amount=amount_cent, # in cents.
currency_code=978, # euro, only one that works, can be skipped.
tlv=[],
)
if listener:
packet.register_response_listener(listener)
code = self.transmit(packet=packet)
if code == 0:
# now check if the packet actually got what it wanted.
for entry in self.transmitter.last_history:
inc, paket = entry
if inc and isinstance(paket, StatusInformation):
return paket.get_receipt_number()
return False
else:
# @todo: remove this.
logger.error("transmit error?")
return False
def partialcancellation(self, receipt=None, amount_cent=50, listener=None):
"""
executes a preauthorisation in amount of cents.
@returns: Receipt Number, if preAuthorisation was successful, or False if it was
canceled.
throws exceptions.
"""
packet = PartialCancellation(
receipt=receipt,
amount=amount_cent, # in cents.
currency_code=978, # euro, only one that works, can be skipped.
tlv=[],
)
if listener:
packet.register_response_listener(listener)
code = self.transmit(packet=packet)
if code == 0:
# now check if the packet actually got what it wanted.
if self.transmitter.last.completion:
if isinstance(self.transmitter.last.completion, Completion):
return True
else:
return False
else:
# @todo: remove this.
logger.error("transmit error?")
return False
def abort(self):
"""
sends abort command
"""
return self.transmit(AbortCommand())
def restart(self): def restart(self):
"""Restarts/resets the PT.""" """Restarts/resets the PT."""
self._state_registered = False self._state_registered = False

View file

@ -95,12 +95,14 @@ class Registration(CommandWithPassword):
06 00 06 00
Registration. Registration.
arguments: password, cc, config_byte arguments: password, cc, config_byte
bitmaps: service_byte bitmaps: service_byte, tlv
""" """
CMD_CLASS = 0x06 CMD_CLASS = 0x06
CMD_INSTR = 0x00 CMD_INSTR = 0x00
wait_for_completion = True wait_for_completion = True
ALLOWED_BITMAPS = ['tlv']
config_byte = FlagByteField(data_type=ConfigByte) config_byte = FlagByteField(data_type=ConfigByte)
cc = BCDIntField(data_type=CurrencyCode, length=2, required=False) cc = BCDIntField(data_type=CurrencyCode, length=2, required=False)
@ -116,6 +118,7 @@ class EndOfDay(CommandWithPassword):
CMD_INSTR = 0x50 CMD_INSTR = 0x50
wait_for_completion = True wait_for_completion = True
class LogOff(Packet): class LogOff(Packet):
"""06 02 Log Off""" """06 02 Log Off"""
CMD_CLASS = 0x06 CMD_CLASS = 0x06
@ -318,6 +321,14 @@ class StatusInformation(Packet):
ret.update(float_version) ret.update(float_version)
return ret return ret
def get_receipt_number(self):
bdict = self.as_dict()
if 'receipt' not in bdict.keys():
return {}
else:
ret = bdict['receipt']
return ret
class IntermediateStatusInformation(Packet): class IntermediateStatusInformation(Packet):
""" """
@ -368,6 +379,36 @@ class Authorisation(Packet):
'pump_nr', 'cvv', 'additional', 'card_type', 'tlv'] 'pump_nr', 'cvv', 'additional', 'card_type', 'tlv']
class PreAuthorisation(Packet):
"""
06 22
If you want to authorize a transaction, this is the packet you need
to start with. Also for reading card data in general.
"""
CMD_CLASS = 0x06
CMD_INSTR = 0x22
wait_for_completion = True
ALLOWED_BITMAPS = [
'amount', 'currency_code', 'status_byte', 'track_1', 'card_expire',
'card_number', 'track_2', 'track_3', 'timeout', 'max_status_infos',
'pump_nr', 'trace_number', 'additional', 'card_type', 'tlv']
class PartialCancellation(Packet):
"""
06 23
This command executes a Partial-Cancellation for a Pre-Authorisation to release the unused amount of the reservation.
This command is also used for the Booking of a Reservation.
"""
CMD_CLASS = 0x06
CMD_INSTR = 0x23
wait_for_completion = True
ALLOWED_BITMAPS = [
'receipt', 'amount', 'currency_code', 'trace_number', 'additional', 'aid', 'tlv']
class PrintLine(Packet): class PrintLine(Packet):
""" """
06 D1 06 D1

View file

@ -3,6 +3,7 @@ from binascii import hexlify
from functools import partial from functools import partial
from socket import ( from socket import (
IPPROTO_TCP, SHUT_RDWR, SO_KEEPALIVE, SOL_SOCKET, create_connection) IPPROTO_TCP, SHUT_RDWR, SO_KEEPALIVE, SOL_SOCKET, create_connection)
import ssl
from socket import timeout as SocketTimeout from socket import timeout as SocketTimeout
from struct import unpack from struct import unpack
from sys import platform from sys import platform
@ -48,7 +49,7 @@ class SocketTransport(Transport):
insert_delays = False insert_delays = False
defaults = dict( defaults = dict(
connect_timeout=5, so_keepalive=0, tcp_keepidle=1, tcp_keepintvl=3, connect_timeout=5, so_keepalive=0, tcp_keepidle=1, tcp_keepintvl=3,
tcp_keepcnt=5, debug='false', packetdebug='false') tcp_keepcnt=5, ssl=False, debug='false', packetdebug='false')
def __init__(self, uri: str): def __init__(self, uri: str):
"""Setup the IP and Port.""" """Setup the IP and Port."""
@ -69,6 +70,8 @@ class SocketTransport(Transport):
'tcp_keepintvl', [self.defaults['tcp_keepintvl']])[0]) 'tcp_keepintvl', [self.defaults['tcp_keepintvl']])[0])
self.tcp_keepcnt = int(qs_parsed.get( self.tcp_keepcnt = int(qs_parsed.get(
'tcp_keepcnt', [self.defaults['tcp_keepcnt']])[0]) 'tcp_keepcnt', [self.defaults['tcp_keepcnt']])[0])
self.ssl = qs_parsed.get(
'ssl', [self.defaults['ssl']])[0] == 'true'
self._debug = qs_parsed.get( self._debug = qs_parsed.get(
'debug', [self.defaults['debug']])[0] == 'true' 'debug', [self.defaults['debug']])[0] == 'true'
self._packetdebug = qs_parsed.get( self._packetdebug = qs_parsed.get(
@ -79,11 +82,16 @@ class SocketTransport(Transport):
Connect to the TCP socket. Return `True` on successful Connect to the TCP socket. Return `True` on successful
connection, `False` on an unsuccessful one. connection, `False` on an unsuccessful one.
""" """
context = ssl._create_unverified_context()
if timeout is None: if timeout is None:
timeout = self.connect_timeout timeout = self.connect_timeout
socket = create_connection(address=(self.ip, self.port), timeout=timeout)
try: try:
self.sock = create_connection( socket = create_connection(address=(self.ip, self.port), timeout=timeout)
address=(self.ip, self.port), timeout=timeout) self.sock = socket
if self.ssl:
self.sock = context.wrap_socket(socket, server_hostname=self.ip)
if self.so_keepalive: if self.so_keepalive:
self.sock.setsockopt( self.sock.setsockopt(
SOL_SOCKET, SO_KEEPALIVE, self.so_keepalive) SOL_SOCKET, SO_KEEPALIVE, self.so_keepalive)