Merge pull request #6 from kaisims/preAuth
More Features! SSL, PreAuthorisation and PartialCancellation And last_printout fix
This commit is contained in:
commit
aee6f4d9a5
3 changed files with 129 additions and 6 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue