Skip to content

Commit

Permalink
Merge pull request #147 from fau-fablab/faucard_patch1
Browse files Browse the repository at this point in the history
Faucard patch1
  • Loading branch information
BDrescher committed May 8, 2017
2 parents 7b0c1d2 + c10ed8e commit 624efbf
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 48 deletions.
13 changes: 8 additions & 5 deletions FabLabKasse/UI/FAUcardPaymentDialogCode.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .uic_generated.FAUcardPaymentDialog import Ui_FAUcardPaymentDialog

from ..faucardPayment.faucardStates import Status, Info
from decimal import Decimal


class FAUcardPaymentDialog(QtGui.QDialog, Ui_FAUcardPaymentDialog):
Expand All @@ -29,15 +30,17 @@ def __init__(self, parent, amount):
:param parent: parent of the Dialog
:type parent: QObject
:param amount: Amount the user has to pay (only used to display)
:type amount: float
:type amount: Decimal
"""
QtGui.QDialog.__init__(self, parent)
logging.info("FAUcardPayment: started")
self.setupUi(self)

assert isinstance(amount, Decimal), "PayupFAUCard: Amount to pay not Decimal or float"

# Set up member variables and fill GUI
self.amount = amount
self.label_betrag.setText(u'{:.2f} €'.format(self.amount).replace('.', ','))
self.label_betrag.setText(u'{} €'.format(unicode(self.amount)).replace('.', ','))
self.label_status.setText(u'Starte FAUcard-Zahlung\n')
self.counter = 0
self.thread_aborted = False
Expand Down Expand Up @@ -132,7 +135,7 @@ def update_gui(self, response):
return
# Inform the user about the reestablished connection to the MagnaBox
elif response[0] == Info.con_back:
self.label_status.setText(u'Verbindung zum Terminal wieder da.\nBuche {:.2f}€ ab\n'.format(self.amount).replace('.', ','))
self.label_status.setText(u'Verbindung zum Terminal wieder da.\nBuche {}€ ab\n'.format(unicode(self.amount)).replace('.', ','))
logging.warning("FAUcardPayment: Verbindung zur MagnaBox wieder aufgebaut.")
return

Expand All @@ -147,12 +150,12 @@ def update_gui(self, response):
self.response_ack.emit(False)
# Card and Balance read: Check if balance is enough and inform user the balance will be decreased
elif response[0] == Status.decreasing_balance: # Card and Balance recognized
self.label_status.setText(u'Buche {:.2f}€ ab\n'.format(self.amount).replace('.', ','))
self.label_status.setText(u'Buche {}€ ab\n'.format(unicode(self.amount)).replace('.', ','))
self.response_ack.emit(False)
# Successfully decreased: Inform the user the payment is done and close after 2 seconds
elif response[0] == Status.decreasing_done:
self.utimer.stop()
self.label_status.setText(u"Vielen Dank für deine Zahlung von {:.2f}.\nBitte das Aufräumen nicht vergessen!".format(self.amount))
self.label_status.setText(u"Vielen Dank für deine Zahlung von {}.\nBitte das Aufräumen nicht vergessen!".format(unicode(self.amount)))
self.utimer.singleShot(5000, self.accept)
self.response_ack.emit(False)
self.pushButton_abbrechen.hide()
Expand Down
40 changes: 24 additions & 16 deletions FabLabKasse/faucardPayment/FAUcardPaymentThread.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from faucardStates import Status, Info
from MagPosLog import MagPosLog
from ..shopping.backend.abstract import float_to_decimal


try: # Test if interface is available
Expand Down Expand Up @@ -64,21 +65,24 @@ def __init__(self, dialog, amount, thread=QtCore.QThread.currentThread()):
:param dialog: GUI Dialog guiding the User
:type dialog: FAUcardPaymentDialog
:param amount: Amount to be paid
:type amount: float
:type amount: Decimal
:param thread: Thread the process should work in
:type thread: Qt.QThread
"""
self.cfg = scriptHelper.getConfig()
QtCore.QObject.__init__(self)
logging.info("FAU-Terminal: thread is being initialized")

assert isinstance(amount, (Decimal)), "PayupFAUCard: Amount to pay not Decimal"

# Initialize class variables
self.status = Status.initializing
self.info = Info.OK
self.card_number = 0
self.old_balance = 0
self.new_balance = 0
self.amount = int(amount * 100)
self.amount = amount
self.amount_cents = int(float_to_decimal(amount * 100, 0)) # Floating point precision causes error -> round with float_to_decimal.
self.cancel = False
self.ack = False
self.sleep_counter = 0
Expand Down Expand Up @@ -218,9 +222,12 @@ def run(self):
self.con = sqlite3.connect(self.cfg.get('magna_carta', 'log_file'))
self.cur = self.con.cursor()
self.con.text_factory = unicode
self.log = MagPosLog(float(self.amount)/100, self.cur, self.con)
self.log = MagPosLog(self.amount, self.cur, self.con)

try:
# 0. Check log file if last entry was not booked
MagPosLog.check_last_entry(self.cur, self.con)

# 1. Check last Transaction
if not self.check_last_transaction(self.cur, self.con):
raise self.CheckLastTransactionFailed
Expand Down Expand Up @@ -293,7 +300,7 @@ def run(self):
def check_last_transaction(cur, con):
"""
Prüfe auf Fehler bei Zahlung mit der FAU-Karte und speichere das Ergebnis in MagPosLog
:return: True on success, False otherwise
:return: True if the check could be performed, False otherwise (does not take result of check into account)
:rtype: bool
:param cur: database cursor
:type cur: sqlite3.Cursor
Expand All @@ -316,15 +323,16 @@ def check_last_transaction(cur, con):
logging.error("CheckTransaction: {}".format(e))
return False

# Choose logging or nop based on check result
if value[0] is magpos.codes.OK: # Last transaction was successful
logging.warning("CheckTransaction: Kassenterminal vor erfolgreicher Buchung abgestürzt.")
MagPosLog.save_transaction_result(cur, con, value[1], float(value[2])/100, Info.transaction_ok.value)
logging.warning(u"CheckTransaction: Buchung für Karte {0} über Betrag {1:.2f}€ fehlt".format(value[1], float(value[2])/100))
logging.error("CheckTransaction: Kassenterminal vor erfolgreicher Buchung abgestürzt.")
MagPosLog.save_transaction_result(cur, con, value[1], Decimal(value[2])/100, Info.transaction_ok.value)
logging.error(u"CheckTransaction: Buchung für Karte {0} über Betrag {1} EURCENT fehlt".format(value[1], value[2]) )
elif value[0] is 0 and value[1] is 0 and value[2] is 0: # Last transaction was acknowledged
return True
pass
else: # Failure during last transaction
logging.warning("CheckTransaction: Letzter Bezahlvorgang nicht erfolgreich ausgeführt.")
MagPosLog.save_transaction_result(cur, con, value[1], float(value[2])/100, Info.transaction_error.value)
MagPosLog.save_transaction_result(cur, con, value[1], Decimal(value[2])/100, Info.transaction_error.value)

return True

Expand Down Expand Up @@ -392,7 +400,7 @@ def _read_card(self):
raise e

# 4. Check if enough balance on card
if old_balance < self.amount:
if old_balance < self.amount_cents:
raise self.BalanceUnderflowError()

# Update GUI and wait for response
Expand All @@ -403,7 +411,7 @@ def _read_card(self):

def _decrease_balance(self):
"""
Decreases card balance by self.amount by following steps:
Decreases card balance by self.amount_cents by following steps:
1. Try to decrease balance
-> a: Successful
2. Continue with step 5
Expand Down Expand Up @@ -440,7 +448,7 @@ def _decrease_balance(self):
# 1. Try to decrease balance
try:
self.check_user_abort("decreasing balance: User Aborted") # Will only be executed if decrease command has not yet been executed
value = self.pos.decrease_card_balance_and_token(self.amount, self.card_number)
value = self.pos.decrease_card_balance_and_token(self.amount_cents, self.card_number)

# Catch ResponseError if not Card on Reader and retry
except magpos.ResponseError as e:
Expand Down Expand Up @@ -492,12 +500,12 @@ def _decrease_balance(self):
self.response_ready.emit([Info.con_back])

# 3. Check last payment details
if value[1] == self.card_number and value[2] == self.amount:
if value[1] == self.card_number and value[2] == self.amount_cents:
if value[0] == magpos.codes.OK:
# 3.a The payment was successfully executed
value[0] = value[1]
value[1] = self.old_balance
value[2] = self.old_balance - self.amount
value[2] = self.old_balance - self.amount_cents
break
else:
# 3.b The payment aborted on error
Expand All @@ -511,13 +519,13 @@ def _decrease_balance(self):
continue

# 5. Check if payment was correct and log it
if value[0] == self.card_number and value[1] == self.old_balance and (value[2]+self.amount) == self.old_balance:
if value[0] == self.card_number and value[1] == self.old_balance and (value[2]+self.amount_cents) == self.old_balance:
self.status = Status.decreasing_done
self.info = Info.OK
self.log.set_status(self.status, self.info)
new_balance = value[2]
else:
raise magpos.TransactionError(value[0], value[1], value[2], self.amount)
raise magpos.TransactionError(value[0], value[1], value[2], self.amount_cents)


self.timestamp_payed = datetime.now()
Expand Down
50 changes: 42 additions & 8 deletions FabLabKasse/faucardPayment/MagPosLog.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from decimal import Decimal

from faucardStates import Status, Info

import logging

class MagPosLog:
""" MagPosLog
Expand All @@ -19,12 +19,15 @@ def __init__(self, amount, cur, con):
"""
Initializes the MagPosLog by creating the sql table if it does not exist and setting the member variables.
:param amount: Amount which the payment is about
:type amount: float
:type amount: Decimal
:param cur: Cursor of the sql connection
:type cur: sqlite3.Cursor
:param con: Connection to the sql database
:type con: sqlite3.Connection
"""

assert isinstance(amount, Decimal), u"MagPosLog: Amount to pay not Decimal"

# Set up member variables
self.id = 0
self.cardnumber = 0
Expand All @@ -34,13 +37,13 @@ def __init__(self, amount, cur, con):
self.payed = False
self.cur = cur
self.con = con
self.amount = float(amount)
self.amount = amount
self.timestamp_payed = None
self.oldbalance = 0
self.newbalance = 0

# Create table if it does not exist
self.cur.execute("CREATE TABLE IF NOT EXISTS MagPosLog(id INTEGER PRIMARY KEY AUTOINCREMENT, datum, cardnumber INT, amount FLOAT, oldbalance INT, newbalance INT, timestamp_payed, status INT, info INT, payed)")
self.cur.execute("CREATE TABLE IF NOT EXISTS MagPosLog(id INTEGER PRIMARY KEY AUTOINCREMENT, datum, cardnumber INT, amount TEXT, oldbalance INT, newbalance INT, timestamp_payed, status INT, info INT, payed)")
con.commit()

def set_status(self, new_status, new_info=Info.OK):
Expand Down Expand Up @@ -125,7 +128,7 @@ def _store(self):
# Grab ID if instance has none
if self.id is 0 or self.id is None:
self.cur.execute("INSERT INTO MagPosLog (cardnumber, amount, datum, status, info, payed) VALUES (?,?,?,?,?,?)",
(self.cardnumber, self.amount, datetime.now(), self.status, self.info, self.payed))
(self.cardnumber, unicode(self.amount), datetime.now(), self.status, self.info, self.payed))
self.cur.execute("SELECT id from MagPosLog ORDER BY id DESC LIMIT 1")
temp = self.cur.fetchone()

Expand All @@ -135,7 +138,7 @@ def _store(self):

# Update Database entry
self.cur.execute("UPDATE MagPosLog SET cardnumber = ?, amount = ?, datum = ?, status = ?, info = ?, payed = ? WHERE id = ?",
(self.cardnumber, self.amount, datetime.now(), self.status, self.info, self.payed, self.id))
(self.cardnumber, unicode(self.amount), datetime.now(), self.status, self.info, self.payed, self.id))
self.con.commit()

@staticmethod
Expand All @@ -157,6 +160,37 @@ def save_transaction_result(cur, con, kartennummer, betrag, info):
info = new_info.value

cur.execute("INSERT INTO MagPosLog (cardnumber, amount, datum, status, info, payed) VALUES (?,?,?,?,?,?)",
(kartennummer, betrag, datetime.now(),
(kartennummer, unicode(betrag), datetime.now(),
Status.transaction_result.value, info, info == Info.transaction_ok.value))
con.commit()
con.commit()

@staticmethod
def check_last_entry(cur, con):
"""
Static function to check the last entry for errors
:param cur: Database cursor
:type cur: sqlite3.Cursor
:param con: Database connection
:type con: sqlite3.Connection
:return: True if nothing found, False otherwise
:rtype: Bool
"""

cur.execute("SELECT ID, Status,Info,Payed from MAGPOSLOG ORDER BY ID Desc LIMIT 1;")
for row in cur.fetchall(): # might be no entry yet
entry_id = row[0]
entry_payed = row[3] == 1
entry_status = None
entry_info = None
try:
entry_status = Status(row[1])
entry_info = Info(row[2])
except ValueError as e:
logging.error(u"MagPosLog: Last entry with ID {} has invalid Status or Info code".format(entry_id))
return False

if entry_payed and entry_status == Status.decreasing_done:
logging.error(u"MagPosLog: Entry with ID{0} was not booked. Please review it for further action".format(entry_id))
return False

return True
9 changes: 5 additions & 4 deletions FabLabKasse/faucardPayment/faucard.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ def __init__(self, parent, amount):
:param parent: Parent QObject
:type parent: QtCore.QObject
:param amount: amount to pay
:type amount: Decimal float
:type amount: Decimal
"""
QtCore.QObject.__init__(self)
assert isinstance(amount, (Decimal, float)), "PayupFAUCard: Amount to pay not Decimal or float"
assert isinstance(amount, Decimal), "PayupFAUCard: Amount to pay not Decimal"
assert amount > 0, "PayupFAUCard: amount is negativ"

self.amount = float(amount)
self.amount = amount
self.thread = QtCore.QThread()
self.dialog = FAUcardPaymentDialog(parent=parent, amount=self.amount)
self.dialog.request_termination.connect(self.threadTerminationRequested, type= QtCore.Qt.DirectConnection)
Expand Down Expand Up @@ -128,7 +128,8 @@ def finish_log(info = Info.OK):
Part
Finishes last MagPosLog Entry by setting its state to Status.booking_done after the internal booking was done
"""
con = sqlite3.connect("magposlog.sqlite3")
cfg = scriptHelper.getConfig()
con = sqlite3.connect(cfg.get('magna_carta', 'log_file'))
cur = con.cursor()
con.text_factory = unicode

Expand Down

0 comments on commit 624efbf

Please sign in to comment.