1
Fork 0
mirror of https://github.com/Steffo99/greed.git synced 2024-11-21 21:44:19 +00:00

Merge branch 'master' into logging

This commit is contained in:
Steffo 2020-04-23 17:22:58 +02:00 committed by GitHub
commit 8d3b492822
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 1016 additions and 87 deletions

View file

@ -1,6 +1,6 @@
# greed # greed
A customizable Telegram shop bot, developed as a project for the final exam. A [customizable](/config/template_config.ini), [multilanguage](/strings) Telegram shop bot with [Telegram Payments support](https://core.telegram.org/bots/payments)!
![](https://img.shields.io/badge/version-beta-blue.svg) ![](https://img.shields.io/badge/maintenance-passively--maintained-yellowgreen) ![](https://img.shields.io/badge/version-beta-blue.svg) ![](https://img.shields.io/badge/maintenance-passively--maintained-yellowgreen)
@ -15,7 +15,7 @@ A customizable Telegram shop bot, developed as a project for the final exam.
## Installation ## Installation
1. Download the project files through `git clone https://github.com/Steffo99/greed.git` or [this link](https://github.com/Steffo99/greed/archive/master.zip). 1. Download the project files through `git clone https://github.com/Steffo99/greed.git` (recommended) or [this link](https://github.com/Steffo99/greed/archive/master.zip).
2. Install the project requirements with `pip install -r requirements.txt` 2. Install the project requirements with `pip install -r requirements.txt`
3. _Optional: run `pip install coloredlogs` to have colored logging output._ 3. _Optional: run `pip install coloredlogs` to have colored logging output._
3. Run `python -OO core.py` to generate the configuration file. 3. Run `python -OO core.py` to generate the configuration file.
@ -32,10 +32,30 @@ All the bot features are available through Telegram.
As the administrator, you can add new products, check the placed orders, create new transactions and generate .csv log files. As the administrator, you can add new products, check the placed orders, create new transactions and generate .csv log files.
Users will be able to add credit to their wallet, place orders and contact you in case they require assistance. Users will be able to add credit to their wallet, place orders and contact you in case they require assistance.
## Updating
### Through `git`
If you downloaded `greed` through `git`, you can update it by running:
```
git stash
git pull
git stash pop
```
### By redownloading the zip file
If you downloaded `greed` through the zip archive, you can update it by redownloading [the latest version](https://github.com/Steffo99/greed/archive/master.zip) and by moving your `config.ini` and `database.sqlite` (if applicable) files to the new folder.
## Documentation ## Documentation
`greed` currently does not have a documentation page, but you can try to read the [paper](https://docs.google.com/document/d/1f4MKVr0B7RSQfWTSa_6ZO0LM4nPpky_GX_qdls3EHtQ/edit?usp=sharing) (in Italian) I wrote for my final Scuola Superiore exam about it. `greed` currently does not have a documentation page, but you can try to read the [paper](https://docs.google.com/document/d/1f4MKVr0B7RSQfWTSa_6ZO0LM4nPpky_GX_qdls3EHtQ/edit?usp=sharing) (in Italian) I wrote for my final Scuola Superiore exam about it.
## Help!
If you find a bug, have an idea for a new feature or just require help with `greed`, please [post an issue](https://github.com/Steffo99/greed/issues/new) on GitHub, or, if GitHub is blocked in your country, join [our Telegram group](https://t.me/greed_project) and send a message there.
## Forks ## Forks
> Please note that @Steffo99, the developer of `greed`, does not endorse any of these forks. > Please note that @Steffo99, the developer of `greed`, does not endorse any of these forks.

View file

@ -5,18 +5,20 @@
# Config file parameters # Config file parameters
[Config] [Config]
; Config file version. DO NOT EDIT THIS! ; Config file version. DO NOT EDIT THIS!
version = 15 version = 17
; Set this to no when you are done editing the file ; Set this to no when you are done editing the file
is_template = yes is_template = yes
; Language code for string file ; Language code for string file
; Available languages: ; Available languages:
; it_IT - Italian, by Steffo ; it_IT - Italian, by Steffo
; en_US - English, by https://github.com/DarrenWestwood (incomplete, please improve it!) ; en_US - English, by https://github.com/DarrenWestwood (incomplete, please improve it!)
; ua_UK - Ukrainian, by https://github.com/pzhuk
; ru_RU - Russian, by https://github.com/pzhuk
language = it_IT language = it_IT
# Telegram bot parameters # Telegram bot parameters
[Telegram] [Telegram]
; Your bot token goes here. Get one from @BotFather! ; Your bot token goes here. Get one from https://t.me/BotFather!
token = 123456789:YOUR_TOKEN_GOES_HERE_______________ token = 123456789:YOUR_TOKEN_GOES_HERE_______________
; Time in seconds before a conversation (thread) with no new messages expires ; Time in seconds before a conversation (thread) with no new messages expires
; A lower value reduces memory usage, but can be inconvenient for the users ; A lower value reduces memory usage, but can be inconvenient for the users
@ -41,15 +43,23 @@ currency = EUR
; Currency exp parameter. You can find that on https://core.telegram.org/bots/payments/currencies.json. ; Currency exp parameter. You can find that on https://core.telegram.org/bots/payments/currencies.json.
; It has a value of 2 in most currencies (EUR, USD, GBP...) ; It has a value of 2 in most currencies (EUR, USD, GBP...)
currency_exp = 2 currency_exp = 2
; Currency symbol which is show to the client users when displaying prices and transaction values
; If not defined here, default language specific currency symbol from strings would be used
currency_symbol = "€"
# Credit card payment settings # Credit card payment settings
[Credit Card] [Credit Card]
; Provider token: get the token by linking the payment provider with @BotFather ; Telegram Payments provider token obtainable at https://t.me/BotFather in the bot's Payments menu
; If empty, credit card payments are disabled.
# credit_card_token =
credit_card_token = 123456789:YOUR_TOKEN_HERE_ credit_card_token = 123456789:YOUR_TOKEN_HERE_
; Minimum wallet payment accepted (in miniumum currency units, $1.00 = 100 units) ; Minimum wallet payment accepted (in miniumum currency units, $1.00 = 100 units)
min_amount = 1000 min_amount = 1000
; Maximum wallet payment accepted (in miniumum currency units, $1.00 = 100 units) ; Maximum wallet payment accepted (in miniumum currency units, $1.00 = 100 units)
max_amount = 10000 max_amount = 10000
; The preset selections that can be made when adding credit to the wallet with a credit card
; Presets are pipe-separated |, and should never be outside the bounds provided by the min_amount and max_amount options
payment_presets = 10.00 | 25.00 | 50.00 | 100.00
; Make the user pay a extra fee when adding credit to the wallet with a credit card ; Make the user pay a extra fee when adding credit to the wallet with a credit card
; The formula for determining the total cost is: ; The formula for determining the total cost is:
; cost = added_funds + added_funds * fee_percentage / 100 + fee_fixed ; cost = added_funds + added_funds * fee_percentage / 100 + fee_fixed
@ -69,6 +79,8 @@ phone_required = yes
; Display the full order information to the customers instead of the shortened version ; Display the full order information to the customers instead of the shortened version
; The full order information includes the order number and the timestamp of the order placement ; The full order information includes the order number and the timestamp of the order placement
full_order_info = no full_order_info = no
; Allow balance refill during the order checkout in case of unsufficient balance
refill_on_checkout = yes
# Exception reporting settings # Exception reporting settings
[Error Reporting] [Error Reporting]

View file

@ -12,11 +12,12 @@ log = logging.getLogger(__name__)
if not os.path.isfile("config/config.ini"): if not os.path.isfile("config/config.ini"):
log.debug("Creating config.ini from template_config.ini") log.debug("Creating config.ini from template_config.ini")
# Open the template file and create the config file # Open the template file and create the config file
with open("config/template_config.ini") as template_file, open("config/config.ini", "w") as config_file: with open("config/template_config.ini", encoding="utf8") as template_file, \
open("config/config.ini", "w", encoding="utf8") as config_file:
# Copy the template file to the config file # Copy the template file to the config file
config_file.write(template_file.read()) config_file.write(template_file.read())
with open("config/template_config.ini") as template_file: with open("config/template_config.ini", encoding="utf8") as template_file:
# Find the template version number # Find the template version number
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read_file(template_file) config.read_file(template_file)
@ -24,7 +25,7 @@ with open("config/template_config.ini") as template_file:
log.debug(f"Template is version {template_version}") log.debug(f"Template is version {template_version}")
# Overwrite the template config with the values in the config # Overwrite the template config with the values in the config
with open("config/config.ini") as config_file: with open("config/config.ini", encoding="utf8") as config_file:
config.read_file(config_file) config.read_file(config_file)
config_version = int(config["Config"]["version"]) config_version = int(config["Config"]["version"])
log.debug(f"Config is version {template_version}") log.debug(f"Config is version {template_version}")
@ -44,7 +45,7 @@ if template_version > config_version:
# Update the config version # Update the config version
config["Config"]["version"] = str(template_version) config["Config"]["version"] = str(template_version)
# Save the file # Save the file
with open("config/config.ini", "w") as config_file: with open("config/config.ini", "w", encoding="utf8") as config_file:
log.debug("Writing merged config file...") log.debug("Writing merged config file...")
config.write(config_file) config.write(config_file)
# Notify the user and quit # Notify the user and quit

View file

@ -76,7 +76,7 @@ def main():
# Skip the update # Skip the update
continue continue
# If the message is a start command... # If the message is a start command...
if isinstance(update.message.text, str) and update.message.text == "/start": if isinstance(update.message.text, str) and update.message.text.startswith("/start"):
log.info(f"Received /start from: {update.message.chat.id}") log.info(f"Received /start from: {update.message.chat.id}")
# Check if a worker already exists for that chat # Check if a worker already exists for that chat
old_worker = chat_workers.get(update.message.chat.id) old_worker = chat_workers.get(update.message.chat.id)

417
strings/ru_RU.py Normal file
View file

@ -0,0 +1,417 @@
# Strings / localization file for greed
# Can be edited, but DON'T REMOVE THE REPLACEMENT FIELDS (words surrounded by {curly braces})
# Part of the translation by https://github.com/pzhuk
# Currency symbol
currency_symbol = ""
# Positioning of the currency symbol
currency_format_string = "{value} {symbol}"
# Quantity of a product in stock
in_stock_format_string = "{quantity} доступно"
# Copies of a product in cart
in_cart_format_string = "{quantity} в корзине"
# Product information
product_format_string = "<b>{name}</b>\n" \
"{description}\n" \
"{price}\n" \
"<b>{cart}</b>"
# Order number, displayed in the order info
order_number = "Заказ #{id}"
# Order info string, shown to the admins
order_format_string = "Покупатель {user}\n" \
"Создано {date}\n" \
"\n" \
"{items}\n" \
"ИТОГО: <b>{value}</b>\n" \
"\n" \
"Сообщение: {notes}\n"
# Order info string, shown to the user
user_order_format_string = "{status_emoji} <b>Заказ {status_text}</b>\n" \
"{items}\n" \
"Итого: <b>{value}</b>\n" \
"\n" \
"Сообщение: {notes}\n"
# Transaction page is loading
loading_transactions = "<i>Загружаю транзакции...\n" \
"Это займет несколько секунд.</i>"
# Transactions page
transactions_page = "Страница <b>{page}</b>:\n" \
"\n" \
"{transactions}"
# transactions.csv caption
csv_caption = "Файл 📄 .csv сгенерирован, и содержит все транзакции из базы данных бота.\n" \
"Вы можете открыть этот файт с помощью LibreOffice Calc, чтобы просмотреть детали."
# Conversation: the start command was sent and the bot should welcome the user
conversation_after_start = "Привет!\n" \
"Добро пожаловать в greed!\n" \
"Это 🅱️ <b>Бета</b> версия программы.\n" \
"Программа полностью готова к использованию, но могут быть баги.\n" \
"Если нашли баг - сообщите тут: https://github.com/Steffo99/greed/issues."
# Conversation: to send an inline keyboard you need to send a message with it
conversation_open_user_menu = "Что бы Вы хотели сделать?\n" \
"💰 У вас в кошельке <b>{credit}</b>.\n" \
"\n" \
"<i>Выберите опцию из вариантов на клавиатуре.\n" \
"Если клавиатуры не видно - её можно активировать кнопкой с квардатами внизу</i>."
# Conversation: like above, but for administrators
conversation_open_admin_menu = "Вы 💼 <b>Менеджер</b> этого магазина!\n" \
"Что бы Вы хотели сделать?\n" \
"\n" \
"<i>Выберите опцию из вариантов на клавиатуре.\n" \
"Если клавиатуры не видно - её можно активировать кнопкой с квардатами внизу</i>."
# Conversation: select a payment method
conversation_payment_method = "Как бы Вы хотели пополнить ваш кошелек?"
# Conversation: select a product to edit
conversation_admin_select_product = "✏️ Какой продукт необходимо отредактировать?"
# Conversation: select a product to delete
conversation_admin_select_product_to_delete = "❌ Какой продукт необходимо удалить?"
# Conversation: select a user to edit
conversation_admin_select_user = "Выберите пользователя для редактирования."
# Conversation: click below to pay for the purchase
conversation_cart_actions = "<i>Добавьте продукты в корзину с помощью кнопки Добавить." \
" Когда сделаете Ваш выбор, возвращайтесь к этому сообщению" \
" и нажмите кнопку Готово.</i>"
# Conversation: confirm the cart contents
conversation_confirm_cart = "🛒 Продукты у Вас в корзине:\n" \
"{product_list}" \
"Итого: <b>{total_cost}</b>\n" \
"\n" \
"<i>Нажмите Готово, чтобы продолжить.\n" \
"Если передумали - выберите Отмена.</i>"
# Conversation: the user activated the live orders mode
conversation_live_orders_start = "Вы в режиме <b>Новые заказы</b>\n" \
"Все новые заказы появятся в этом чате в режиме реального времени," \
" и их можно отметить ✅ Выполнено" \
" или ✴️ Возвращено в случае возврата денег.\n" \
"\n" \
"<i>Нажмите Стоп в этом чате, чтобы остановить этот режим.</i>"
# Conversation: help menu has been opened
conversation_open_help_menu = "Чем могу Вам помочь?"
# Conversation: confirm promotion to admin
conversation_confirm_admin_promotion = "Вы уверены, что хотите повысить этого пользователя до 💼 Менеджера?\n" \
"Это действие невозможно отменить!"
# Conversation: switching to user mode
conversation_switch_to_user_mode = " Вы перешли в режим 👤 Покупателя.\n" \
"Если хотите вернутся в режим 💼 Менеджера, рестартуйте с помощью команды /start."
# Notification: the conversation has expired
conversation_expired = "🕐 За долгое время я не получил ни одного сообщения, поэтому я прекратил общение" \
" чтобы сохранить ресурсы.\n" \
"Чтобы начать снова, пришлите команду /start ."
# User menu: order
menu_order = "🛒 Заказать"
# User menu: order status
menu_order_status = "🛍 Мои заказы"
# User menu: add credit
menu_add_credit = "💵 Пополнить кошелек"
# User menu: bot info
menu_bot_info = " Информация о боте"
# User menu: cash
menu_cash = "💵 Наличными"
# User menu: credit card
menu_credit_card = "💳 Кредитной картой"
# Admin menu: products
menu_products = "📝️ Продукты"
# Admin menu: orders
menu_orders = "📦 Заказы"
# Menu: transactions
menu_transactions = "💳 Список транзакций"
# Menu: edit credit
menu_edit_credit = "💰 Создать транзакцию"
# Admin menu: go to user mode
menu_user_mode = "👤 Режим покупателя"
# Admin menu: add product
menu_add_product = "✨ Новый продукт"
# Admin menu: delete product
menu_delete_product = "❌ Удалить продукт"
# Menu: cancel
menu_cancel = "🔙 Отмена"
# Menu: skip
menu_skip = "⏭ Пропустить"
# Menu: done
menu_done = "✅️ Готово"
# Menu: pay invoice
menu_pay = "💳 Заплатить"
# Menu: complete
menu_complete = "✅ Готово"
# Menu: refund
menu_refund = "✴️ Возврат средств"
# Menu: stop
menu_stop = "🛑 Стоп"
# Menu: add to cart
menu_add_to_cart = " Добавить"
# Menu: remove from cart
menu_remove_from_cart = " Удалить"
# Menu: help menu
menu_help = "❓ Помощь"
# Menu: guide
menu_guide = "📖 Инструкция"
# Menu: next page
menu_next = "▶️ Следующая"
# Menu: previous page
menu_previous = "◀️ Предыдущая"
# Menu: contact the shopkeeper
menu_contact_shopkeeper = "👨‍💼 Контакты"
# Menu: generate transactions .csv file
menu_csv = "📄 .csv"
# Menu: edit admins list
menu_edit_admins = "🏵 Изменить менеджеров"
# Emoji: unprocessed order
emoji_not_processed = "*️⃣"
# Emoji: completed order
emoji_completed = ""
# Emoji: refunded order
emoji_refunded = "✴️"
# Emoji: yes
emoji_yes = ""
# Emoji: no
emoji_no = "🚫"
# Text: unprocessed order
text_not_processed = "ожидает"
# Text: completed order
text_completed = "выполнен"
# Text: refunded order
text_refunded = "возмещен"
# Add product: name?
ask_product_name = "Как назовем продукт?"
# Add product: description?
ask_product_description = "Каким будет описание продукта?"
# Add product: price?
ask_product_price = "Какова будет цена?\n" \
"Введите <code>X</code> если продукт сейчас недоступен."
# Add product: image?
ask_product_image = "🖼 Добавим фото продукта?\n" \
"\n" \
"<i>Пришлите фото, или Пропустите этот шаг.</i>"
ask_product_category = "Выберите категорию товара"
# Order product: notes?
ask_order_notes = "Оставить заметку к этом заказу?\n" \
"💼 Заметка будет доступна Менеджеру магазина.\n" \
"\n" \
"<i>Напишите Ваше сообщение, или выберите Пропустить," \
" чтобы не оставлять заметку.</i>"
# Refund product: reason?
ask_refund_reason = " Сообщите причину возврата средств.\n" \
" Причина будет видна 👤 Покупателю."
# Edit credit: notes?
ask_transaction_notes = " Добавьте сообщение к транзакции.\n" \
" Сообщение будет доступно 👤 Покупателю после пополнения/списания средств" \
" и 💼 Администратору в логах транзакций."
# Edit credit: amount?
ask_credit = "Вы хотите изменить баланс Покупателя?\n" \
"\n" \
"<i>Напишите сообщение и укажите сумму.\n" \
"Используйте </i><code>+</code><i> чтобы пополнить счет," \
" и знак </i><code>-</code><i> чтобы списать средства.</i>"
# Header for the edit admin message
admin_properties = "<b>Доступы пользователя {name}:</b>"
# Edit admin: can edit products?
prop_edit_products = "Редактировать продукты"
# Edit admin: can receive orders?
prop_receive_orders = "Получать заказы"
# Edit admin: can create transactions?
prop_create_transactions = "Управлять транзакциями"
# Edit admin: show on help message?
prop_display_on_help = "Показывать покупателям"
# Thread has started downloading an image and might be unresponsive
downloading_image = "Я загружаю фото!\n" \
"Это может занять некоторое время...!\n" \
"Я не смогу отвечать, пока идет загрузка."
# Edit product: current value
edit_current_value = "Текущее значение:\n" \
"<pre>{value}</pre>\n" \
"\n" \
"<i>Нажмите Пропустить, чтобы оставить значение без изменений.</i>"
# Payment: cash payment info
payment_cash = "Вы можете пополнить счет наличными в торговых точках.\n" \
"Рассчитайтесь и сообщение Менеджеру следующее значение:\n" \
"<b>{user_cash_id}</b>"
# Payment: invoice amount
payment_cc_amount = "На какую сумму пополнить Ваш кошелек?\n" \
"\n" \
"<i>Выберите сумму из предложеных значений, или введите вручную в сообщении.</i>"
# Payment: add funds invoice title
payment_invoice_title = "Пополнение"
# Payment: add funds invoice description
payment_invoice_description = "Оплата этого счета добавит {amount} в Ваш кошелек."
# Payment: label of the labeled price on the invoice
payment_invoice_label = "Платеж"
# Payment: label of the labeled price on the invoice
payment_invoice_fee_label = "Сбор за пополнение"
# Notification: order has been placed
notification_order_placed = "Получен новый заказ:\n" \
"{order}"
# Notification: order has been completed
notification_order_completed = "Выш заказ успешно выполнен!\n" \
"{order}"
# Notification: order has been refunded
notification_order_refunded = "Ваш заказ отменен. Средства возвращены в Ваш кошелек!\n" \
"{order}"
# Notification: a manual transaction was applied
notification_transaction_created = " Новая транзакция в Вашем кошельке:\n" \
"{transaction}"
# Refund reason
refund_reason = "Причина возврата:\n" \
"{reason}"
# Info: informazioni sul bot
bot_info = 'Этот бот использует <a href="https://github.com/Steffo99/greed">greed</a>,' \
' фреймворк разработан @Steffo для платежей Телеграм и выпущен под лицензией' \
' <a href="https://github.com/Steffo99/greed/blob/master/LICENSE.txt">' \
'Affero General Public License 3.0</a>.\n'
# Help: guide
help_msg = "Инструкция к greed доступна по этому адресу:\n" \
"https://docs.google.com/document/d/1f4MKVr0B7RSQfWTSa_6ZO0LM4nPpky_GX_qdls3EHtQ/"
# Help: contact shopkeeper
contact_shopkeeper = "Следующие сотрудники доступны сейчас и могут помочь:\n" \
"{shopkeepers}\n" \
"<i>Выберите одного из них и напишите в Телеграм чат.</i>"
# Success: product has been added/edited to the database
success_product_edited = "✅ Продукт успешно создан/обновлен!"
# Success: product has been added/edited to the database
success_product_deleted = "✅ Продукт успешно удален!"
# Success: order has been created
success_order_created = "✅ Заказ успешно создан!\n" \
"\n" \
"{order}"
# Success: order was marked as completed
success_order_completed = "✅ Ваш заказ #{order_id} был успешно выполнен."
# Success: order was refunded successfully
success_order_refunded = "✴️ Средства по заказу #{order_id} были возвращены."
# Success: transaction was created successfully
success_transaction_created = "✅ Транзакция успешно создана!\n" \
"{transaction}"
# Error: message received not in a private chat
error_nonprivate_chat = "⚠️ Этот бот работает только в частных чатах."
# Error: a message was sent in a chat, but no worker exists for that chat.
# Suggest the creation of a new worker with /start
error_no_worker_for_chat = "⚠️ Общение с ботом было прервано.\n" \
"Чтобы начать снова, воспользуйтесь командой /start "
# Error: add funds amount over max
error_payment_amount_over_max = "⚠️ Максимальная сумма одной транзакции {max_amount}."
# Error: add funds amount under min
error_payment_amount_under_min = "⚠️ Минимальная сумма одной транзакции {min_amount}."
# Error: the invoice has expired and can't be paid
error_invoice_expired = "⚠️ Время действия инвойса завершено. Если все еще хотите пополнить счет - выберите" \
" Пополнить счет в меню."
# Error: a product with that name already exists
error_duplicate_name = "️⚠️ Продукт с таким именем уже существует."
# Error: not enough credit to order
error_not_enough_credit = "⚠️ У Вас недостаточно средств, чтобы выполнить заказ."
# Error: order has already been cleared
error_order_already_cleared = "⚠️ Этот заказ уже был выполнен ранее."
# Error: no orders have been placed, so none can be shown
error_no_orders = "⚠️ Вы еще не сделали ни одного заказа, поэтому здесь пусто."
# Error: selected user does not exist
error_user_does_not_exist = "⚠️ Нет такого пользователя."
# Fatal: conversation raised an exception
fatal_conversation_exception = "☢️ Вот беда! <b>Ошибка</b> прервала наше общение\n" \
"Владельцу бота будет сообщено об этой ошибке.\n" \
"Чтобы начать общение заново, воспользуйтесь командой /start."

417
strings/uk_UA.py Normal file
View file

@ -0,0 +1,417 @@
# Strings / localization file for greed
# Can be edited, but DON'T REMOVE THE REPLACEMENT FIELDS (words surrounded by {curly braces})
# Part of the translation by https://github.com/pzhuk
# Currency symbol
currency_symbol = ""
# Positioning of the currency symbol
currency_format_string = "{value} {symbol}"
# Quantity of a product in stock
in_stock_format_string = "{quantity} наявні"
# Copies of a product in cart
in_cart_format_string = "{quantity} в кошику"
# Product information
product_format_string = "<b>{name}</b>\n" \
"{description}\n" \
"{price}\n" \
"<b>{cart}</b>"
# Order number, displayed in the order info
order_number = "Замовлення #{id}"
# Order info string, shown to the admins
order_format_string = "Користувач {user}\n" \
"Створено {date}\n" \
"\n" \
"{items}\n" \
"ЗАГАЛОМ: <b>{value}</b>\n" \
"\n" \
"Нотатка: {notes}\n"
# Order info string, shown to the user
user_order_format_string = "{status_emoji} <b>Замовлення {status_text}</b>\n" \
"{items}\n" \
"Загалом: <b>{value}</b>\n" \
"\n" \
"Нотатка: {notes}\n"
# Transaction page is loading
loading_transactions = "<i>Завантажую транзакції...\n" \
"Зачекайте кілька секунд.</i>"
# Transactions page
transactions_page = "Сторінка <b>{page}</b>:\n" \
"\n" \
"{transactions}"
# transactions.csv caption
csv_caption = "Файл 📄 .csv, який має всі транзакції з бази даних бота було сгенеровано.\n" \
"Можете відкрити файл за допомогою LibreOffice Calc, щоб переглянути деталі."
# Conversation: the start command was sent and the bot should welcome the user
conversation_after_start = "Привіт!\n" \
"Вітаю в greed!\n" \
"Це 🅱️ <b>Бета</b> версія програми.\n" \
"Програма повністю придатна до використання, але ще можуть бути баги.\n" \
"Якщо знайшли баг - повідомте на https://github.com/Steffo99/greed/issues."
# Conversation: to send an inline keyboard you need to send a message with it
conversation_open_user_menu = "Щоб ви хотіли зробити?\n" \
"💰 У вас <b>{credit}</b> в гаманці.\n" \
"\n" \
"<i>Виберіть опцію з варіантів на клавіатурі.\n" \
"Якщо клавіатури не видно - її можна активувати кнопкою з чотирма квадратами внизу</i>."
# Conversation: like above, but for administrators
conversation_open_admin_menu = "Ви є 💼 <b>Менеджером</b> цього магазину!\n" \
"Що б ви хотіли зробити?\n" \
"\n" \
"<i>Виберіть опцію з варіантів на клавіатурі.\n" \
"Якщо клавіатури не видно - її можна активувати кнопкою з чотирма квадратами внизу</i>."
# Conversation: select a payment method
conversation_payment_method = "Як би Ви хотіли поповнити гаманець?"
# Conversation: select a product to edit
conversation_admin_select_product = "✏️ Який продукт потрібно редагувати?"
# Conversation: select a product to delete
conversation_admin_select_product_to_delete = "❌ Який продукт потрібно видалит?"
# Conversation: select a user to edit
conversation_admin_select_user = "Виберіть користувача для редагування."
# Conversation: click below to pay for the purchase
conversation_cart_actions = "<i>Додайте продукти в кошик натисканням кнопки Додати." \
" Коли зробите Ваш вибір, повертайтесь до цього повідомлення" \
" і натисніть кнопку Готово.</i>"
# Conversation: confirm the cart contents
conversation_confirm_cart = "🛒 У вас в кошику наступні продукти:\n" \
"{product_list}" \
"Всього: <b>{total_cost}</b>\n" \
"\n" \
"<i>Щоб продовжити натисніть Готово.\n" \
"Якщо змінили свою думку - обирайте Відміна.</i>"
# Conversation: the user activated the live orders mode
conversation_live_orders_start = "Ви в режимі <b>Свіжі Замовлення</b>\n" \
"Всі нові замовення від покупців зʼявляться в цьому чаті в режимі живого часу," \
" і ви зможете помічати їх ✅ Виконано" \
" або ✴️ Повернути кошти покупцю.\n" \
"\n" \
"<i>Натисніть кнопку Стоп в цьому чаті, щоб зупинити цей режим.</i>"
# Conversation: help menu has been opened
conversation_open_help_menu = "Як можемо Вам допомогти?"
# Conversation: confirm promotion to admin
conversation_confirm_admin_promotion = "Ви впевнені, що хочете підвищити цього користувача до 💼 Менеджера?\n" \
"Цю дію неможливо відмінити!"
# Conversation: switching to user mode
conversation_switch_to_user_mode = " Ви перейшли в режим 👤 Замовника.\n" \
"Якщо хочете повернутись в меню 💼 Менеджера, рестартуйте розмову з /start."
# Notification: the conversation has expired
conversation_expired = "🕐 За довгий час я не отримав жодного повідомлення, тому я завершив розмову" \
" щоб зберегти ресурси.\n" \
"Щоб почату знову, надішліть команду /start ."
# User menu: order
menu_order = "🛒 Замовлення"
# User menu: order status
menu_order_status = "🛍 Мої замовлення"
# User menu: add credit
menu_add_credit = "💵 Поповнити гаманець"
# User menu: bot info
menu_bot_info = " Інформація про бот"
# User menu: cash
menu_cash = "💵 Готівкою"
# User menu: credit card
menu_credit_card = "💳 Кредитною картою"
# Admin menu: products
menu_products = "📝️ Продукти"
# Admin menu: orders
menu_orders = "📦 Замовлення"
# Menu: transactions
menu_transactions = "💳 Список транзакцій"
# Menu: edit credit
menu_edit_credit = "💰 Створити транзакцію"
# Admin menu: go to user mode
menu_user_mode = "👤 Режим замовника"
# Admin menu: add product
menu_add_product = "✨ Новий продукт"
# Admin menu: delete product
menu_delete_product = "❌ Видалити продукт"
# Menu: cancel
menu_cancel = "🔙 Відміна"
# Menu: skip
menu_skip = "⏭ Пропустити"
# Menu: done
menu_done = "✅️ Готово"
# Menu: pay invoice
menu_pay = "💳 Заплатити"
# Menu: complete
menu_complete = "✅ Готово"
# Menu: refund
menu_refund = "✴️ Повернення коштів"
# Menu: stop
menu_stop = "🛑 Стоп"
# Menu: add to cart
menu_add_to_cart = " Додати"
# Menu: remove from cart
menu_remove_from_cart = " Прибрати"
# Menu: help menu
menu_help = "❓ Допомога"
# Menu: guide
menu_guide = "📖 Інструкція"
# Menu: next page
menu_next = "▶️ Наступна"
# Menu: previous page
menu_previous = "◀️ Попередня"
# Menu: contact the shopkeeper
menu_contact_shopkeeper = "👨‍💼 Контакти магазину"
# Menu: generate transactions .csv file
menu_csv = "📄 .csv"
# Menu: edit admins list
menu_edit_admins = "🏵 Редагувати менеджерів"
# Emoji: unprocessed order
emoji_not_processed = "*️⃣"
# Emoji: completed order
emoji_completed = ""
# Emoji: refunded order
emoji_refunded = "✴️"
# Emoji: yes
emoji_yes = ""
# Emoji: no
emoji_no = "🚫"
# Text: unprocessed order
text_not_processed = "очікує"
# Text: completed order
text_completed = "завершено"
# Text: refunded order
text_refunded = "повернуто"
# Add product: name?
ask_product_name = "Як назвати продукт?"
# Add product: description?
ask_product_description = "Який буде опис продукту?"
# Add product: price?
ask_product_price = "Яка буде ціна?\n" \
"Введіть <code>X</code> Якщо продукт зараз не продається."
# Add product: image?
ask_product_image = "🖼 Яку картинку додати до продукта?\n" \
"\n" \
"<i>Надішліть фото, або Пропустіть цей крок.</i>"
ask_product_category = "Оберіть категорію товару"
# Order product: notes?
ask_order_notes = "Залишити повідомлення разом з цією покупкою?\n" \
"💼 Повідомлення буде доступне Менеджеру магазину.\n" \
"\n" \
"<i>Надішліть Ваше повідомлення, або натисність Пропустити" \
" щоб не залишати повідомлення.</i>"
# Refund product: reason?
ask_refund_reason = " Напишіть причину повернення коштів.\n" \
"👤 Причина буде доступна замовнику."
# Edit credit: notes?
ask_transaction_notes = " Додайте повідомлення до транзакції.\n" \
"👤 Повідомлення буде доступне замовнику після поповнення/списання" \
" і 💼 Адміністратору в логах транзакцій."
# Edit credit: amount?
ask_credit = "Як ви хочете змінити баланс замовника?\n" \
"\n" \
"<i>Надішліть повідомлення з сумою.\n" \
"Використовуйте </i><code>+</code><i> щоб поповнити рахунок," \
" і знак </i><code>-</code><i> щоб списати кошти.</i>"
# Header for the edit admin message
admin_properties = "<b>Доступи користувача {name}:</b>"
# Edit admin: can edit products?
prop_edit_products = "Редагувати продукти"
# Edit admin: can receive orders?
prop_receive_orders = "Отримувати замовлення"
# Edit admin: can create transactions?
prop_create_transactions = "Керувати транзакціями"
# Edit admin: show on help message?
prop_display_on_help = "Показувати замовнику"
# Thread has started downloading an image and might be unresponsive
downloading_image = "Я завантажую фото!\n" \
"Може зайняти деякий час... Майте терпіння!\n" \
"Я не зможу відповідати, поки йде завантаження."
# Edit product: current value
edit_current_value = "Поточне значення:\n" \
"<pre>{value}</pre>\n" \
"\n" \
"<i>Натисність Пропустити під цим повідомленням, щоб залишити значення таким.</i>"
# Payment: cash payment info
payment_cash = "Ви можете поповнити готівкою прямо в магазині.\n" \
"Розрахуйтесь і дайте цей id менеджеру:\n" \
"<b>{user_cash_id}</b>"
# Payment: invoice amount
payment_cc_amount = "На яку сумму ви хочете поповнити гаманець?\n" \
"\n" \
"<i>Виберіть сумму із запропонованих значень, або введіть вручну в повідомленні.</i>"
# Payment: add funds invoice title
payment_invoice_title = "Поповнення"
# Payment: add funds invoice description
payment_invoice_description = "Оплата цього рахунку додасть {amount} в ваш гаманець."
# Payment: label of the labeled price on the invoice
payment_invoice_label = "Платіж"
# Payment: label of the labeled price on the invoice
payment_invoice_fee_label = "Оплата за поповнення"
# Notification: order has been placed
notification_order_placed = "Отримано нове замовлення:\n" \
"{order}"
# Notification: order has been completed
notification_order_completed = "Ваше замовнення успішно завершено!\n" \
"{order}"
# Notification: order has been refunded
notification_order_refunded = "Ваше замовлення відмінено. Кошти повернуто!\n" \
"{order}"
# Notification: a manual transaction was applied
notification_transaction_created = " Нова транзакція в вашому гаманці:\n" \
"{transaction}"
# Refund reason
refund_reason = "Причина повернення:\n" \
"{reason}"
# Info: informazioni sul bot
bot_info = 'Цей бот використовує <a href="https://github.com/Steffo99/greed">greed</a>,' \
' фреймворк розроблений @Steffo для платежів Телеграм випущений під ліцензією' \
' <a href="https://github.com/Steffo99/greed/blob/master/LICENSE.txt">' \
'Affero General Public License 3.0</a>.\n'
# Help: guide
help_msg = "Інструкція по greed доступна за цією адресою:\n" \
"https://docs.google.com/document/d/1f4MKVr0B7RSQfWTSa_6ZO0LM4nPpky_GX_qdls3EHtQ/"
# Help: contact shopkeeper
contact_shopkeeper = "Наразі наступні працівники доступні і зможуть допомогти:\n" \
"{shopkeepers}\n" \
"<i>Виберіть когось одного і напишіть в Телеграм чат.</i>"
# Success: product has been added/edited to the database
success_product_edited = "✅ Продукт успішно створено/оновлено!"
# Success: product has been added/edited to the database
success_product_deleted = "✅ Продукт успішно видалено!"
# Success: order has been created
success_order_created = "✅ Замовлення успішно надіслано!\n" \
"\n" \
"{order}"
# Success: order was marked as completed
success_order_completed = "✅ Ваше замовлення #{order_id} було успішно проведено."
# Success: order was refunded successfully
success_order_refunded = "✴️ Кошти по замовленню #{order_id} було відшкодовано."
# Success: transaction was created successfully
success_transaction_created = "✅ Транзакцію успішно створено!\n" \
"{transaction}"
# Error: message received not in a private chat
error_nonprivate_chat = "⚠️ Цей бот працює тільки в приватних чатах."
# Error: a message was sent in a chat, but no worker exists for that chat.
# Suggest the creation of a new worker with /start
error_no_worker_for_chat = "⚠️ Спілкування з ботом було перервано.\n" \
"Щоб почати знову, надішліть боту команду /start "
# Error: add funds amount over max
error_payment_amount_over_max = "⚠️ Максимальна сума однієї транзакції {max_amount}."
# Error: add funds amount under min
error_payment_amount_under_min = "⚠️ Мінімальна сума однієї транзакції {min_amount}."
# Error: the invoice has expired and can't be paid
error_invoice_expired = "⚠️ Час дії інвойсу було вичерпано. Якщо все хочете додати кошти - виберіть Додати" \
" кошти в меню."
# Error: a product with that name already exists
error_duplicate_name = "️⚠️ Продукт з таким імʼям вже існує."
# Error: not enough credit to order
error_not_enough_credit = "⚠️ У вас недостатньо коштів, щоб виконати замовлення."
# Error: order has already been cleared
error_order_already_cleared = "⚠️ Це замовлення вже було опрацьовано раніше."
# Error: no orders have been placed, so none can be shown
error_no_orders = "⚠️ Ви ще не зробили жодного замовлення, тому тут пусто."
# Error: selected user does not exist
error_user_does_not_exist = "⚠️ Такого користувача не існує."
# Fatal: conversation raised an exception
fatal_conversation_exception = "☢️ Ой лишенько! <b>Помилка</b> перервала нашу розмову\n" \
"Про помилку було повідомлено власника бота.\n" \
"Щоб почати розмову знову, надішліть команду /start."

View file

@ -23,9 +23,13 @@ except ModuleNotFoundError:
if config["Error Reporting"]["sentry_token"] != \ if config["Error Reporting"]["sentry_token"] != \
"https://00000000000000000000000000000000:00000000000000000000000000000000@sentry.io/0000000": "https://00000000000000000000000000000000:00000000000000000000000000000000@sentry.io/0000000":
import raven import raven
import raven.exceptions
try:
release = raven.fetch_git_sha(os.path.dirname(__file__))
except raven.exceptions.InvalidGitRepository:
release = "Unknown"
sentry_client = raven.Client(config["Error Reporting"]["sentry_token"], sentry_client = raven.Client(config["Error Reporting"]["sentry_token"],
release=raven.fetch_git_sha(os.path.dirname(__file__)), release=release,
environment="Dev" if __debug__ else "Prod") environment="Dev" if __debug__ else "Prod")
else: else:
sentry_client = None sentry_client = None
@ -53,7 +57,7 @@ class Price:
return f"<Price of value {self.value}>" return f"<Price of value {self.value}>"
def __str__(self): def __str__(self):
return strings.currency_format_string.format(symbol=strings.currency_symbol, return strings.currency_format_string.format(symbol=(config["Payments"]["currency_symbol"] or strings.currency_symbol),
value="{0:.2f}".format( value="{0:.2f}".format(
self.value / (10 ** int(config["Payments"]["currency_exp"])))) self.value / (10 ** int(config["Payments"]["currency_exp"]))))

206
worker.py
View file

@ -166,8 +166,9 @@ class Worker(threading.Thread):
while True: while True:
# Get the next update # Get the next update
update = self.__receive_next_update() update = self.__receive_next_update()
# Ensure the update isn't a CancelSignal # If a CancelSignal is received...
if isinstance(update, CancelSignal): if isinstance(update, CancelSignal):
# And the wait is cancellable...
if cancellable: if cancellable:
# Return the CancelSignal # Return the CancelSignal
return update return update
@ -192,10 +193,15 @@ class Worker(threading.Thread):
while True: while True:
# Get the next update # Get the next update
update = self.__receive_next_update() update = self.__receive_next_update()
# Ensure the update isn't a CancelSignal # If a CancelSignal is received...
if cancellable and isinstance(update, CancelSignal): if isinstance(update, CancelSignal):
# Return the CancelSignal # And the wait is cancellable...
return update if cancellable:
# Return the CancelSignal
return update
else:
# Ignore the signal
continue
# Ensure the update contains a message # Ensure the update contains a message
if update.message is None: if update.message is None:
continue continue
@ -218,10 +224,15 @@ class Worker(threading.Thread):
while True: while True:
# Get the next update # Get the next update
update = self.__receive_next_update() update = self.__receive_next_update()
# Ensure the update isn't a CancelSignal # If a CancelSignal is received...
if cancellable and isinstance(update, CancelSignal): if isinstance(update, CancelSignal):
# Return the CancelSignal # And the wait is cancellable...
return update if cancellable:
# Return the CancelSignal
return update
else:
# Ignore the signal
continue
# Ensure the update contains a precheckoutquery # Ensure the update contains a precheckoutquery
if update.pre_checkout_query is None: if update.pre_checkout_query is None:
continue continue
@ -249,10 +260,15 @@ class Worker(threading.Thread):
while True: while True:
# Get the next update # Get the next update
update = self.__receive_next_update() update = self.__receive_next_update()
# Ensure the update isn't a CancelSignal # If a CancelSignal is received...
if cancellable and isinstance(update, CancelSignal): if isinstance(update, CancelSignal):
# Return the CancelSignal # And the wait is cancellable...
return update if cancellable:
# Return the CancelSignal
return update
else:
# Ignore the signal
continue
# Ensure the update contains a message # Ensure the update contains a message
if update.message is None: if update.message is None:
continue continue
@ -262,17 +278,22 @@ class Worker(threading.Thread):
# Return the photo array # Return the photo array
return update.message.photo return update.message.photo
def __wait_for_inlinekeyboard_callback(self, cancellable: bool = True) \ def __wait_for_inlinekeyboard_callback(self, cancellable: bool = False) \
-> Union[telegram.CallbackQuery, CancelSignal]: -> Union[telegram.CallbackQuery, CancelSignal]:
"""Continue getting updates until an inline keyboard callback is received, then return it.""" """Continue getting updates until an inline keyboard callback is received, then return it."""
log.debug("Waiting for a CallbackQuery...") log.debug("Waiting for a CallbackQuery...")
while True: while True:
# Get the next update # Get the next update
update = self.__receive_next_update() update = self.__receive_next_update()
# Ensure the update isn't a CancelSignal # If a CancelSignal is received...
if cancellable and isinstance(update, CancelSignal): if isinstance(update, CancelSignal):
# Return the CancelSignal # And the wait is cancellable...
return update if cancellable:
# Return the CancelSignal
return update
else:
# Ignore the signal
continue
# Ensure the update is a CallbackQuery # Ensure the update is a CallbackQuery
if update.callback_query is None: if update.callback_query is None:
continue continue
@ -428,17 +449,13 @@ class Worker(threading.Thread):
message_id=callback.message.message_id, message_id=callback.message.message_id,
caption=product.text(cart_qty=cart[callback.message.message_id][1]), caption=product.text(cart_qty=cart[callback.message.message_id][1]),
reply_markup=product_inline_keyboard) reply_markup=product_inline_keyboard)
# Create the cart summary
product_list = "" self.bot.edit_message_text(
total_cost = utils.Price(0) chat_id=self.chat.id,
for product_id in cart: message_id=final_msg.message_id,
if cart[product_id][1] > 0: text=strings.conversation_confirm_cart.format(product_list=self.__get_cart_summary(cart),
product_list += cart[product_id][0].text(style="short", cart_qty=cart[product_id][1]) + "\n" total_cost=str(self.__get_cart_value(cart))),
total_cost += cart[product_id][0].price * cart[product_id][1] reply_markup=final_inline_keyboard)
self.bot.edit_message_text(chat_id=self.chat.id, message_id=final_msg.message_id,
text=strings.conversation_confirm_cart.format(product_list=product_list,
total_cost=str(total_cost)),
reply_markup=final_inline_keyboard)
# If the Remove from cart button has been pressed... # If the Remove from cart button has been pressed...
elif callback.data == "cart_remove": elif callback.data == "cart_remove":
# Get the selected product, ensuring it exists # Get the selected product, ensuring it exists
@ -476,17 +493,13 @@ class Worker(threading.Thread):
message_id=callback.message.message_id, message_id=callback.message.message_id,
caption=product.text(cart_qty=cart[callback.message.message_id][1]), caption=product.text(cart_qty=cart[callback.message.message_id][1]),
reply_markup=product_inline_keyboard) reply_markup=product_inline_keyboard)
# Create the cart summary
product_list = "" self.bot.edit_message_text(
total_cost = utils.Price(0) chat_id=self.chat.id,
for product_id in cart: message_id=final_msg.message_id,
if cart[product_id][1] > 0: text=strings.conversation_confirm_cart.format(product_list=self.__get_cart_summary(cart),
product_list += cart[product_id][0].text(style="short", cart_qty=cart[product_id][1]) + "\n" total_cost=str(self.__get_cart_value(cart))),
total_cost += cart[product_id][0].price * cart[product_id][1] reply_markup=final_inline_keyboard)
self.bot.edit_message_text(chat_id=self.chat.id, message_id=final_msg.message_id,
text=strings.conversation_confirm_cart.format(product_list=product_list,
total_cost=str(total_cost)),
reply_markup=final_inline_keyboard)
# If the done button has been pressed... # If the done button has been pressed...
elif callback.data == "cart_done": elif callback.data == "cart_done":
# End the loop # End the loop
@ -505,22 +518,50 @@ class Worker(threading.Thread):
# Add the record to the session and get an ID # Add the record to the session and get an ID
self.session.add(order) self.session.add(order)
self.session.flush() self.session.flush()
# For each product added to the cart, create a new OrderItem and get the total value # For each product added to the cart, create a new OrderItem
value = 0
for product in cart: for product in cart:
# Add the price multiplied by the quantity to the total price
value -= cart[product][0].price * cart[product][1]
# Create {quantity} new OrderItems # Create {quantity} new OrderItems
for i in range(0, cart[product][1]): for i in range(0, cart[product][1]):
order_item = db.OrderItem(product=cart[product][0], order_item = db.OrderItem(product=cart[product][0],
order_id=order.order_id) order_id=order.order_id)
self.session.add(order_item) self.session.add(order_item)
# Ensure the user has enough credit to make the purchase # Ensure the user has enough credit to make the purchase
if self.user.credit + value < 0: credit_required = self.__get_cart_value(cart) - self.user.credit
# Notify user in case of insufficient credit
if credit_required > 0:
self.bot.send_message(self.chat.id, strings.error_not_enough_credit) self.bot.send_message(self.chat.id, strings.error_not_enough_credit)
# Suggest payment for missing credit value if configuration allows refill
if configloader.config["Credit Card"]["credit_card_token"] != "" \
and configloader.config["Appearance"]["refill_on_checkout"] == 'yes' \
and credit_required <= utils.Price(int(configloader.config["Credit Card"]["max_amount"])) \
and credit_required >= utils.Price(int(configloader.config["Credit Card"]["min_amount"])):
self.__make_payment(utils.Price(credit_required))
# If afer requested payment credit is still insufficient (either payment failure or cancel)
if self.user.credit < self.__get_cart_value(cart):
# Rollback all the changes # Rollback all the changes
self.session.rollback() self.session.rollback()
return else:
# User has credit and valid order, perform transaction now
self.__order_transaction(order=order, value=-int(self.__get_cart_value(cart)))
@staticmethod
def __get_cart_value(cart):
# Calculate total items value in cart
value = utils.Price(0)
for product in cart:
value += cart[product][0].price * cart[product][1]
return value
@staticmethod
def __get_cart_summary(cart):
# Create the cart summary
product_list = ""
for product_id in cart:
if cart[product_id][1] > 0:
product_list += cart[product_id][0].text(style="short", cart_qty=cart[product_id][1]) + "\n"
return product_list
def __order_transaction(self, order, value):
# Create a new transaction and add it to the session # Create a new transaction and add it to the session
transaction = db.Transaction(user=self.user, transaction = db.Transaction(user=self.user,
value=value, value=value,
@ -532,6 +573,10 @@ class Worker(threading.Thread):
self.user.recalculate_credit() self.user.recalculate_credit()
# Commit all the changes # Commit all the changes
self.session.commit() self.session.commit()
# Notify admins about new transation
self.__order_notify_admins(order=order)
def __order_notify_admins(self, order):
# Notify the user of the order result # Notify the user of the order result
self.bot.send_message(self.chat.id, strings.success_order_created.format(order=order.get_text(self.session, self.bot.send_message(self.chat.id, strings.success_order_created.format(order=order.get_text(self.session,
user=True))) user=True)))
@ -583,7 +628,8 @@ class Worker(threading.Thread):
self.bot.send_message(self.chat.id, strings.conversation_payment_method, self.bot.send_message(self.chat.id, strings.conversation_payment_method,
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True))
# Wait for a reply from the user # Wait for a reply from the user
selection = self.__wait_for_specific_message([strings.menu_cash, strings.menu_credit_card, strings.menu_cancel]) selection = self.__wait_for_specific_message([strings.menu_cash, strings.menu_credit_card, strings.menu_cancel],
cancellable=True)
# If the user has selected the Cash option... # If the user has selected the Cash option...
if selection == strings.menu_cash: if selection == strings.menu_cash:
# Go to the pay with cash function # Go to the pay with cash function
@ -594,7 +640,7 @@ class Worker(threading.Thread):
# Go to the pay with credit card function # Go to the pay with credit card function
self.__add_credit_cc() self.__add_credit_cc()
# If the user has selected the Cancel option... # If the user has selected the Cancel option...
elif selection == strings.menu_cancel: elif isinstance(selection, CancelSignal):
# Send him back to the previous menu # Send him back to the previous menu
return return
@ -602,11 +648,9 @@ class Worker(threading.Thread):
"""Add money to the wallet through a credit card payment.""" """Add money to the wallet through a credit card payment."""
log.debug("Displaying __add_credit_cc") log.debug("Displaying __add_credit_cc")
# Create a keyboard to be sent later # Create a keyboard to be sent later
keyboard = [[telegram.KeyboardButton(str(utils.Price("10.00")))], presets = list(map(lambda s: s.strip(" "), configloader.config["Credit Card"]["payment_presets"].split('|')))
[telegram.KeyboardButton(str(utils.Price("25.00")))], keyboard = [[telegram.KeyboardButton(str(utils.Price(preset)))] for preset in presets]
[telegram.KeyboardButton(str(utils.Price("50.00")))], keyboard.append([telegram.KeyboardButton(strings.menu_cancel)])
[telegram.KeyboardButton(str(utils.Price("100.00")))],
[telegram.KeyboardButton(strings.menu_cancel)]]
# Boolean variable to check if the user has cancelled the action # Boolean variable to check if the user has cancelled the action
cancelled = False cancelled = False
# Loop used to continue asking if there's an error during the input # Loop used to continue asking if there's an error during the input
@ -615,9 +659,9 @@ class Worker(threading.Thread):
self.bot.send_message(self.chat.id, strings.payment_cc_amount, self.bot.send_message(self.chat.id, strings.payment_cc_amount,
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True))
# Wait until a valid amount is sent # Wait until a valid amount is sent
selection = self.__wait_for_regex(r"([0-9]+(?:[.,][0-9]+)?|" + strings.menu_cancel + r")") selection = self.__wait_for_regex(r"([0-9]+(?:[.,][0-9]+)?|" + strings.menu_cancel + r")", cancellable=True)
# If the user cancelled the action # If the user cancelled the action
if selection == strings.menu_cancel: if isinstance(selection, CancelSignal):
# Exit the loop # Exit the loop
cancelled = True cancelled = True
continue continue
@ -627,31 +671,33 @@ class Worker(threading.Thread):
if value > utils.Price(int(configloader.config["Credit Card"]["max_amount"])): if value > utils.Price(int(configloader.config["Credit Card"]["max_amount"])):
self.bot.send_message(self.chat.id, self.bot.send_message(self.chat.id,
strings.error_payment_amount_over_max.format( strings.error_payment_amount_over_max.format(
max_amount=utils.Price(configloader.config["Payments"]["max_amount"]))) max_amount=utils.Price(configloader.config["Credit Card"]["max_amount"]))
)
continue continue
elif value < utils.Price(int(configloader.config["Credit Card"]["min_amount"])): elif value < utils.Price(int(configloader.config["Credit Card"]["min_amount"])):
self.bot.send_message(self.chat.id, self.bot.send_message(self.chat.id,
strings.error_payment_amount_under_min.format( strings.error_payment_amount_under_min.format(
min_amount=utils.Price(configloader.config["Payments"]["min_amount"]))) min_amount=utils.Price(configloader.config["Credit Card"]["min_amount"]))
)
continue continue
break break
# If the user cancelled the action... # If the user cancelled the action...
else: else:
# Exit the function # Exit the function
return return
# Issue the payment invoice
self.__make_payment(amount=value)
def __make_payment(self, amount):
# Set the invoice active invoice payload # Set the invoice active invoice payload
self.invoice_payload = str(uuid.uuid4()) self.invoice_payload = str(uuid.uuid4())
# Create the price array # Create the price array
prices = [telegram.LabeledPrice(label=strings.payment_invoice_label, amount=int(value))] prices = [telegram.LabeledPrice(label=strings.payment_invoice_label, amount=int(amount))]
# If the user has to pay a fee when using the credit card, add it to the prices list # If the user has to pay a fee when using the credit card, add it to the prices list
fee_percentage = float(configloader.config["Credit Card"]["fee_percentage"]) / 100 fee = int(self.__get_total_fee(amount))
fee_fixed = int(configloader.config["Credit Card"]["fee_fixed"]) if fee > 0:
total_fee = value * fee_percentage + fee_fixed prices.append(telegram.LabeledPrice(label=strings.payment_invoice_fee_label,
if total_fee > 0: amount=fee))
prices.append(telegram.LabeledPrice(label=strings.payment_invoice_fee_label, amount=int(total_fee)))
else:
# Otherwise, set the fee to 0 to ensure no accidental discounts are applied
total_fee = 0
# Create the invoice keyboard # Create the invoice keyboard
inline_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(strings.menu_pay, pay=True)], inline_keyboard = telegram.InlineKeyboardMarkup([[telegram.InlineKeyboardButton(strings.menu_pay, pay=True)],
[telegram.InlineKeyboardButton(strings.menu_cancel, [telegram.InlineKeyboardButton(strings.menu_cancel,
@ -659,7 +705,7 @@ class Worker(threading.Thread):
# The amount is valid, send the invoice # The amount is valid, send the invoice
self.bot.send_invoice(self.chat.id, self.bot.send_invoice(self.chat.id,
title=strings.payment_invoice_title, title=strings.payment_invoice_title,
description=strings.payment_invoice_description.format(amount=str(value)), description=strings.payment_invoice_description.format(amount=str(amount)),
payload=self.invoice_payload, payload=self.invoice_payload,
provider_token=configloader.config["Credit Card"]["credit_card_token"], provider_token=configloader.config["Credit Card"]["credit_card_token"],
start_parameter="tempdeeplink", start_parameter="tempdeeplink",
@ -669,7 +715,7 @@ class Worker(threading.Thread):
need_email=configloader.config["Credit Card"]["email_required"] == "yes", need_email=configloader.config["Credit Card"]["email_required"] == "yes",
need_phone_number=configloader.config["Credit Card"]["phone_required"] == "yes", need_phone_number=configloader.config["Credit Card"]["phone_required"] == "yes",
reply_markup=inline_keyboard) reply_markup=inline_keyboard)
# Wait for the invoice # Wait for the precheckout query
precheckoutquery = self.__wait_for_precheckoutquery(cancellable=True) precheckoutquery = self.__wait_for_precheckoutquery(cancellable=True)
# Check if the user has cancelled the invoice # Check if the user has cancelled the invoice
if isinstance(precheckoutquery, CancelSignal): if isinstance(precheckoutquery, CancelSignal):
@ -681,10 +727,11 @@ class Worker(threading.Thread):
successfulpayment = self.__wait_for_successfulpayment() successfulpayment = self.__wait_for_successfulpayment()
# Create a new database transaction # Create a new database transaction
transaction = db.Transaction(user=self.user, transaction = db.Transaction(user=self.user,
value=successfulpayment.total_amount - int(total_fee), value=int(successfulpayment.total_amount) - fee,
provider="Credit Card", provider="Credit Card",
telegram_charge_id=successfulpayment.telegram_payment_charge_id, telegram_charge_id=successfulpayment.telegram_payment_charge_id,
provider_charge_id=successfulpayment.provider_payment_charge_id) provider_charge_id=successfulpayment.provider_payment_charge_id)
if successfulpayment.order_info is not None: if successfulpayment.order_info is not None:
transaction.payment_name = successfulpayment.order_info.name transaction.payment_name = successfulpayment.order_info.name
transaction.payment_email = successfulpayment.order_info.email transaction.payment_email = successfulpayment.order_info.email
@ -694,6 +741,17 @@ class Worker(threading.Thread):
# Commit all the changes # Commit all the changes
self.session.commit() self.session.commit()
@staticmethod
def __get_total_fee(amount):
# Calculate a fee for the required amount
fee_percentage = float(configloader.config["Credit Card"]["fee_percentage"]) / 100
fee_fixed = int(configloader.config["Credit Card"]["fee_fixed"])
total_fee = amount * fee_percentage + fee_fixed
if total_fee > 0:
return total_fee
# Set the fee to 0 to ensure no accidental discounts are applied
return 0
def __bot_info(self): def __bot_info(self):
"""Send information about the bot.""" """Send information about the bot."""
log.debug("Displaying __bot_info") log.debug("Displaying __bot_info")
@ -774,9 +832,9 @@ class Worker(threading.Thread):
self.bot.send_message(self.chat.id, strings.conversation_admin_select_product, self.bot.send_message(self.chat.id, strings.conversation_admin_select_product,
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True))
# Wait for a reply from the user # Wait for a reply from the user
selection = self.__wait_for_specific_message(product_names) selection = self.__wait_for_specific_message(product_names, cancellable = True)
# If the user has selected the Cancel option... # If the user has selected the Cancel option...
if selection == strings.menu_cancel: if isinstance(selection, CancelSignal):
# Exit the menu # Exit the menu
return return
# If the user has selected the Add Product option... # If the user has selected the Add Product option...
@ -898,8 +956,8 @@ class Worker(threading.Thread):
self.bot.send_message(self.chat.id, strings.conversation_admin_select_product_to_delete, self.bot.send_message(self.chat.id, strings.conversation_admin_select_product_to_delete,
reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True)) reply_markup=telegram.ReplyKeyboardMarkup(keyboard, one_time_keyboard=True))
# Wait for a reply from the user # Wait for a reply from the user
selection = self.__wait_for_specific_message(product_names) selection = self.__wait_for_specific_message(product_names, cancellable = True)
if selection == strings.menu_cancel: if isinstance(selection, CancelSignal):
# Exit the menu # Exit the menu
return return
else: else:
@ -1229,7 +1287,7 @@ class Worker(threading.Thread):
callback = self.__wait_for_inlinekeyboard_callback() callback = self.__wait_for_inlinekeyboard_callback()
# Toggle the correct property # Toggle the correct property
if callback.data == "toggle_edit_products": if callback.data == "toggle_edit_products":
admin.edit_products = not admin.edit_products admin.edit_products = not admin.edit_products1
elif callback.data == "toggle_receive_orders": elif callback.data == "toggle_receive_orders":
admin.receive_orders = not admin.receive_orders admin.receive_orders = not admin.receive_orders
elif callback.data == "toggle_create_transactions": elif callback.data == "toggle_create_transactions":