feat: implement billing system with Pally integration

- Added billing models and repository for handling bills.
- Introduced BillingService to manage payment initiation and bill creation.
- Updated user service to remove ticket-related functionality.
- Refactored settings to include new billing-related fields.
- Removed ticket-related handlers and schemas.
- Added new billing handlers for processing payments.
- Updated database schema with new bills table and status enum.
- Enhanced utility functions to calculate prices considering discounts.
- Introduced prehandling for non-private messages.
- Updated main application to initialize new billing services and repositories.
This commit is contained in:
2026-04-23 19:55:51 +07:00
parent 8290d18235
commit d98383285e
29 changed files with 705 additions and 305 deletions

View File

@@ -18,8 +18,9 @@ from misc.utils import (
convert_int_to_duration,
get_discount,
)
from schemas.billing import BillingContext
from schemas.subscriptions import SubscriptionPlan
from states.buy import SubscriptionStorage
from states.buy import BillingStorage, SubscriptionStorage
from texts import (
CALLBACK_FALLBACK,
CHECKOUT_PAYMENT_CHOICE,
@@ -102,9 +103,10 @@ async def select_duration_init(cb: CallbackQuery, state: FSMContext):
try:
discount = get_discount(convert_duration_to_int(ctx.duration))
ctx.discount = 1 - discount / 100
await cb.message.edit_text(
SUBSCRIPTION_DURATION_SELECTOR.format(
price=math.ceil(calculate_price(ctx) * (1 - discount / 100)),
price=calculate_price(ctx),
discount=math.floor(discount),
),
reply_markup=duration_selector(current=ctx.duration, back_cb=f"devices:{ctx.devices}"),
@@ -135,9 +137,10 @@ async def select_duration(cb: CallbackQuery, state: FSMContext):
ctx.duration = convert_int_to_duration(duration)
try:
discount = get_discount(convert_duration_to_int(ctx.duration))
ctx.discount = 1 - discount / 100
await cb.message.edit_text(
SUBSCRIPTION_DURATION_SELECTOR.format(
price=math.ceil(calculate_price(ctx) * (1 - discount / 100)),
price=calculate_price(ctx),
discount=math.floor(discount),
),
reply_markup=duration_selector(current=ctx.duration, back_cb=f"devices:{ctx.devices}"),
@@ -168,3 +171,6 @@ async def proceed_to_checkout(cb: CallbackQuery, state: FSMContext):
reply_markup=payment_gateways,
) # FIXME: temp solution that's incredibly stupid
await state.set_state(BillingStorage.pending)
billing_context = BillingContext(amount=calculate_price(ctx))
await state.set_data({"ctx": billing_context})