# Twisted/Klein imports from twisted.logger import Logger # Other library imports from json import dumps from random import choices from string import ascii_uppercase # Project imports from db import r def convert(data): """ Recursively convert a dictionary. """ if isinstance(data, bytes): return data.decode("ascii") if isinstance(data, dict): return dict(map(convert, data.items())) if isinstance(data, tuple): return map(convert, data) return data class Transactions(object): """ Handler class for incoming Revolut transactions. """ def __init__(self): """ Initialise the Transaction object. Set the logger. """ self.log = Logger("transactions") def set_agora(self, agora): self.agora = agora def set_irc(self, irc): self.irc = irc def transaction(self, data): """ Store details of transaction. :param data: details of transaction :type data: dict """ event = data["event"] ts = data["timestamp"] inside = data["data"] txid = inside["id"] txtype = inside["type"] state = inside["state"] if "reference" in inside: reference = inside["reference"] else: reference = "not_given" leg = inside["legs"][0] if "counterparty" in leg: account_type = leg["counterparty"]["account_type"] else: account_type = "not_given" amount = leg["amount"] currency = leg["currency"] description = leg["description"] to_store = { "event": event, "ts": ts, "txid": txid, "txtype": txtype, "state": state, "reference": reference, "account_type": account_type, "amount": amount, "currency": currency, "description": description, } self.log.info("Transaction processed: {formatted}", formatted=dumps(to_store, indent=2)) r.hmset(f"tx.{txid}", to_store) self.irc.client.msg( self.irc.client.channel, f"AUTO Incoming transaction: {amount}{currency} ({reference}) - {state} - {description}" ) stored_trade = r.hgetall(f"trade.{reference}") if not stored_trade: self.log.info(f"No reference in DB for {reference}") return stored_trade = convert(stored_trade) amount = float(amount) stored_trade["amount"] = float(stored_trade["amount"]) if not stored_trade["currency"] == currency: self.irc.client.msg(self.irc.client.channel, f"Currency mismatch, Agora: {stored_trade['currency']} / Revolut: {currency}") return if not stored_trade["amount"] == amount: self.irc.client.msg(self.irc.client.channel, f"Amount mismatch, Agora: {stored_trade['amount']} / Revolut: {amount}") return if not account_type == "revolut": self.irc.client.msg(self.irc.client.channel, f"Account type is not Revolut: {account_type}") return self.irc.client.msg(self.irc.client.channel, f"All checks passed, would release funds for {stored_trade['id']}") def new_trade(self, trade_id, buyer, currency, amount, amount_xmr): reference = "".join(choices(ascii_uppercase, k=5)) reference = f"XMR-{reference}" existing_ref = r.get(f"trade.{trade_id}.reference") if existing_ref: self.irc.client.msg(self.irc.client.channel, f"Existing reference for {trade_id}: {existing_ref.decode('utf-8')}") else: r.set(f"trade.{trade_id}.reference", reference) to_store = { "id": trade_id, "buyer": buyer, "currency": currency, "amount": amount, "amount_xmr": amount_xmr, "reference": reference, } self.log.info(f"Storing trade information: {str(to_store)}") r.hmset(f"trade.{reference}", to_store) self.irc.client.msg(self.irc.client.channel, f"Generated reference for {trade_id}: {reference}") self.agora.agora.contact_message_post(trade_id, f"Hi! When sending the payment please user reference code: {reference}") def find_tx(self, reference, amount): """ Find transactions that match the given reference and amount. :param reference: transaction reference in Revolut :param amount: transaction amount :type reference: string :type amount: int :return: transaction details or AMOUNT_INVALID, or False :rtype: dict or string or bool """ all_transactions = r.scan(0, match="tx.*") for tx_iter in all_transactions[1]: tx_obj = r.hgetall(tx_iter) if tx_obj[b"reference"] == str.encode(reference): if tx_obj[b"amount"] == str.encode(amount): return convert(tx_obj) else: return "AMOUNT_INVALID" return False