From c26621223d6c83dc11e485965a3d022887a345d1 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Wed, 10 Jan 2018 11:29:02 +0100 Subject: [PATCH] Recover from corrupt repo --- config/template_config.ini | 29 ++++++++++-------- core.py | 2 +- database.py | 31 ++++++++++---------- strings.py | 1 - worker.py | 60 +++++++++++++++++++++++++++++++++----- 5 files changed, 86 insertions(+), 37 deletions(-) diff --git a/config/template_config.ini b/config/template_config.ini index 467f40e..5f2b4b6 100644 --- a/config/template_config.ini +++ b/config/template_config.ini @@ -3,7 +3,7 @@ # Config file parameters [Config] ; Config file version. DO NOT EDIT THIS! -version = 7 +version = 8 ; Set this to no when you are done editing the file is_template = yes @@ -22,19 +22,24 @@ long_polling_timeout = 30 ; The database engine you want to use. Refer to http://docs.sqlalchemy.org/en/latest/core/engines.html for the possible settings. engine = sqlite:// -# Enabled payment methods -# To disable, leave the row empty -[Payment Methods] -# Cash payment is always enabled -# Credit card: get the token at @BotFather -credit_card_token = 123456789:YOUR_TOKEN_HERE_ - +# General payment settings [Payments] +# ISO currency code +currency = EUR +# Currency exp parameter. You can find that on https://core.telegram.org/bots/payments/currencies.json +currency_exp = 2 + +# Credit card payment settings +[Credit Card] +# Provider token: get the token at @BotFather +credit_card_token = 123456789:YOUR_TOKEN_HERE_ # Minimum wallet payment accepted min_amount = 10 # Maximum wallet payment accepted max_amount = 100 -# ISO currency code -currency = EUR -# Currency exp parameter. You can find that on https://core.telegram.org/bots/payments/currencies.json -currency_exp = 2 \ No newline at end of file +# Require the name of the credit card owner +name_required = yes +# Require the email of the credit card owner +email_required = yes +# Require the phone number of the credit card owner +phone_required = yes diff --git a/core.py b/core.py index 5ecbfb9..2aee9f6 100644 --- a/core.py +++ b/core.py @@ -116,7 +116,7 @@ def main(): # Forward the update to the corresponding worker receiving_worker = chat_workers.get(update.pre_checkout_query.from_user.id) # Check if it's the active invoice for this chat - if receiving_worker is None or update.pre_checkout_query.payload != receiving_worker.invoice_payload: + if receiving_worker is None or update.pre_checkout_query.invoice_payload != receiving_worker.invoice_payload: # Notify the user that the invoice has expired try: bot.answer_pre_checkout_query(update.pre_checkout_query.id, ok=False, error_message=strings.error_invoice_expired) diff --git a/database.py b/database.py index 661acbc..ed415b7 100644 --- a/database.py +++ b/database.py @@ -1,6 +1,6 @@ from sqlalchemy import create_engine, Column, ForeignKey, UniqueConstraint, CheckConstraint from sqlalchemy import Integer, BigInteger, String, Numeric, Text -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy.ext.declarative import declarative_base import configloader import telegram @@ -28,7 +28,7 @@ class User(TableDeclarativeBase): username = Column(String) # Current wallet credit - credit = Column(Numeric, nullable=False) + credit = Column(Integer, nullable=False) # Extra table parameters __tablename__ = "users" @@ -65,7 +65,7 @@ class Product(TableDeclarativeBase): # Product description description = Column(Text) # Product price, if null product is not for sale - price = Column(Numeric) + price = Column(Integer) # Image filename image = Column(String) # Stock quantity, if null product has infinite stock @@ -94,26 +94,32 @@ class Transaction(TableDeclarativeBase): Wallet credit ISN'T calculated from these, but they can be used to recalculate it.""" # The internal transaction ID - transaction_id = Column(BigInteger, primary_key=True) + transaction_id = Column(Integer, primary_key=True) # The user whose credit is affected by this transaction user_id = Column(BigInteger, ForeignKey("users.user_id"), nullable=False) + user = relationship("User") # The value of this transaction. Can be both negative and positive. - value = Column(Numeric, nullable=False) + value = Column(Integer, nullable=False) # Extra notes on the transaction notes = Column(Text) + # Payment provider provider = Column(String) + # Transaction ID supplied by Telegram + telegram_charge_id = Column(String) # Transaction ID supplied by the payment provider - provider_id = Column(BigInteger) - + provider_charge_id = Column(String) # Extra transaction data, may be required by the payment provider in case of a dispute payment_name = Column(String) - payment_address = Column(String) + payment_phone = Column(String) payment_email = Column(String) + # Order ID + order_id = Column(Integer) + # Extra table parameters __tablename__ = "transactions" - __table_args__ = (UniqueConstraint("provider", "provider_id"),) + __table_args__ = (UniqueConstraint("provider", "provider_charge_id"),) def __str__(self): """Return the correctly formatted transaction value""" @@ -128,17 +134,12 @@ class Transaction(TableDeclarativeBase): return f"" -# TODO -# class Order(TableDeclarativeBase): -# """A product order.""" -# pass - - class Admin(TableDeclarativeBase): """A greed administrator with his permissions.""" # The telegram id user_id = Column(BigInteger, ForeignKey("users.user_id"), primary_key=True) + user = relationship("User") # Permissions # TODO: unfinished diff --git a/strings.py b/strings.py index 198fe2d..e1d1f18 100644 --- a/strings.py +++ b/strings.py @@ -31,7 +31,6 @@ conversation_expired = "🕐 Il bot non ha ricevuto messaggi per un po' di tempo # User menu: order menu_order = "🛍 Ordina" - # User menu: order status menu_order_status = "❓ Stato ordini" diff --git a/worker.py b/worker.py index 8efcf50..a512d37 100644 --- a/worker.py +++ b/worker.py @@ -1,5 +1,7 @@ import threading import typing +import uuid + import telegram import strings import configloader @@ -28,8 +30,8 @@ class ChatWorker(threading.Thread): # Open a new database session self.session = db.Session() # Get the user db data from the users and admin tables - self.user = self.session.query(db.User).filter(db.User.user_id == self.chat.id).one_or_none() - self.admin = self.session.query(db.Admin).filter(db.Admin.user_id == self.chat.id).one_or_none() + self.user = None + self.admin = None # The sending pipe is stored in the ChatWorker class, allowing the forwarding of messages to the chat process self.queue = queuem.Queue() # The current active invoice payload; reject all invoices with a different payload @@ -40,6 +42,9 @@ class ChatWorker(threading.Thread): # TODO: catch all the possible exceptions # Welcome the user to the bot self.bot.send_message(self.chat.id, strings.conversation_after_start) + # Get the user db data from the users and admin tables + self.user = self.session.query(db.User).filter(db.User.user_id == self.chat.id).one_or_none() + self.admin = self.session.query(db.Admin).filter(db.Admin.user_id == self.chat.id).one_or_none() # If the user isn't registered, create a new record and add it to the db if self.user is None: # Create the new record @@ -116,17 +121,31 @@ class ChatWorker(threading.Thread): return match.group(1) def __wait_for_precheckoutquery(self) -> telegram.PreCheckoutQuery: - """Continue getting updates until a precheckoutquery is received.""" + """Continue getting updates until a precheckoutquery is received. + The payload is checked by the core before forwarding the message.""" while True: # Get the next update update = self.__receive_next_update() # Ensure the update contains a precheckoutquery if update.pre_checkout_query is None: continue - # TODO: something payload # Return the precheckoutquery return update.pre_checkout_query + def __wait_for_successfulpayment(self) -> telegram.SuccessfulPayment: + """Continue getting updates until a successfulpayment is received.""" + while True: + # Get the next update + update = self.__receive_next_update() + # Ensure the update contains a message + if update.message is None: + continue + # Ensure the message is a successfulpayment + if update.message.successful_payment is None: + continue + # Return the successfulpayment + return update.message.successful_payment + def __user_menu(self): """Function called from the run method when the user is not an administrator. Normal bot actions should be placed here.""" @@ -175,7 +194,7 @@ class ChatWorker(threading.Thread): # Cash keyboard.append([telegram.KeyboardButton(strings.menu_cash)]) # Telegram Payments - if configloader.config["Payment Methods"]["credit_card_token"] != "": + if configloader.config["Credit Card"]["credit_card_token"] != "": keyboard.append([telegram.KeyboardButton(strings.menu_credit_card)]) # Keyboard: go back to the previous menu keyboard.append([telegram.KeyboardButton(strings.menu_cancel)]) @@ -224,17 +243,42 @@ class ChatWorker(threading.Thread): self.bot.send_message(self.chat.id, strings.error_payment_amount_under_min.format(min_amount=strings.currency_format_string.format(symbol=strings.currency_symbol, value=configloader.config["Payments"]["min_amount"]))) continue break + # Set the invoice active invoice payload + self.invoice_payload = str(uuid.uuid4()) # The amount is valid, send the invoice self.bot.send_invoice(self.chat.id, title=strings.payment_invoice_title, description=strings.payment_invoice_description.format(amount=strings.currency_format_string.format(symbol=strings.currency_symbol, value=selection)), - payload="temppayload", # TODO: how should I use the payload? - provider_token=configloader.config["Payment Methods"]["credit_card_token"], + payload=self.invoice_payload, + provider_token=configloader.config["Credit Card"]["credit_card_token"], start_parameter="tempdeeplink", # TODO: no idea on how deeplinks should work currency=configloader.config["Payments"]["currency"], - prices=[telegram.LabeledPrice(label=strings.payment_invoice_label, amount=int(selection * (10 ** int(configloader.config["Payments"]["currency_exp"]))))]) + prices=[telegram.LabeledPrice(label=strings.payment_invoice_label, amount=int(selection * (10 ** int(configloader.config["Payments"]["currency_exp"]))))], + need_name=configloader.config["Credit Card"]["name_required"] == "yes", + need_email=configloader.config["Credit Card"]["email_required"] == "yes", + need_phone_number=configloader.config["Credit Card"]["phone_required"] == "yes") # Wait for the invoice precheckoutquery = self.__wait_for_precheckoutquery() + # TODO: ensure the bot doesn't die here! + # Accept the checkout + self.bot.answer_pre_checkout_query(precheckoutquery.id, ok=True) + # Wait for the payment + successfulpayment = self.__wait_for_successfulpayment() + # Create a new database transaction + transaction = db.Transaction(user=self.user, + value=successfulpayment.total_amount, + provider="Credit Card", + telegram_charge_id=successfulpayment.telegram_payment_charge_id, + provider_charge_id=successfulpayment.provider_payment_charge_id) + if successfulpayment.order_info is not None: + transaction.payment_name = successfulpayment.order_info.name + transaction.payment_email = successfulpayment.order_info.email + transaction.payment_phone = successfulpayment.order_info.phone_number + # Add the credit to the user account + self.user.credit += successfulpayment.total_amount + # Add and commit the transaction + self.session.add(transaction) + self.session.commit() def __bot_info(self): """Send information about the bot."""