Compare commits

...

4 Commits

7 changed files with 76 additions and 9 deletions

View File

@@ -27,6 +27,7 @@ class Settings(BaseSettings):
remnawave_default_squads_raw: str = Field(alias="REMNAWAVE_DEFAULT_SQUADS_UUIDS") remnawave_default_squads_raw: str = Field(alias="REMNAWAVE_DEFAULT_SQUADS_UUIDS")
minimal_deposit: int = Field() minimal_deposit: int = Field()
referal_bonus: int = Field(30)
@computed_field @computed_field
@property @property

4
external/pally.py vendored
View File

@@ -30,8 +30,8 @@ class Currency(StrEnum):
class BillType(StrEnum): class BillType(StrEnum):
NORMAL = "normal" NORMAL = "NORMAL"
MULTI = "multi" MULTI = "MULTI"
class BillStatus(StrEnum): class BillStatus(StrEnum):

View File

@@ -41,6 +41,7 @@ async def signup(req: UserRegistration, session: AsyncSession = Depends(get_db))
username=user.username, username=user.username,
telegram_id=user.telegram_id, telegram_id=user.telegram_id,
referal_code=user.referal_code, referal_code=user.referal_code,
bonus_balance=user.balance,
).model_dump(), ).model_dump(),
status_code=201, status_code=201,
) )

View File

@@ -6,7 +6,11 @@ from fastapi.routing import APIRouter
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from core.deps import get_db from core.deps import get_db
from db.models.transactions import BalanceTransaction, BalanceTxType
from external.pally import BillStatus from external.pally import BillStatus
from repositories.invoices import InvoiceRepository
from repositories.users import UserRepository
from schemas.invoices import InvoiceStatus
from services.payments import process_subscription_purchase, validate_pally_signature from services.payments import process_subscription_purchase, validate_pally_signature
router = APIRouter(prefix="/payments/pally") router = APIRouter(prefix="/payments/pally")
@@ -82,11 +86,69 @@ async def pally_callback(
amount = int(float(BalanceAmount)) if BalanceAmount is not None else int(float(OutSum)) amount = int(float(BalanceAmount)) if BalanceAmount is not None else int(float(OutSum))
await process_subscription_purchase( try:
session, await process_subscription_purchase(
invoice_id=int(invoice_id_str), session,
trs_id=TrsId, invoice_id=int(invoice_id_str),
amount=amount, trs_id=TrsId,
) amount=amount,
)
except Exception:
# The payment is confirmed, so never leave the user without the money
# if order/subscription provisioning fails.
logger.exception(
"Subscription provisioning failed for bill %s (TrsId: %s); "
"crediting the user's bonus balance",
invoice_id_str,
TrsId,
)
await session.rollback()
invoice_repo = InvoiceRepository(session)
invoice = await invoice_repo.get_by_id(int(invoice_id_str))
if invoice is None:
logger.critical(
"Cannot credit fallback balance: bill %s was not found (TrsId: %s)",
invoice_id_str,
TrsId,
)
raise
if invoice.status == InvoiceStatus.PAID:
return "OK"
user = await UserRepository(session).get_user_by_id(invoice.creator_id)
if user is None:
logger.critical(
"Cannot credit fallback balance: user %s was not found " "for bill %s (TrsId: %s)",
invoice.creator_id,
invoice_id_str,
TrsId,
)
raise
balance_before = user.balance
user.balance += amount
invoice.status = InvoiceStatus.PAID
session.add(
BalanceTransaction(
user_id=user.id,
amount=amount,
tx_type=BalanceTxType.DEPOSIT,
balance_before=balance_before,
balance_after=user.balance,
description=(
f"fallback payment credit for invoice {invoice.id} " f"(TrsId: {TrsId})"
),
)
)
await session.commit()
logger.info(
"Fallback payment credit processed: user_id=%s, amount=%s, " "invoice_id=%s, TrsId=%s",
user.id,
amount,
invoice.id,
TrsId,
)
return "OK" return "OK"

View File

@@ -16,6 +16,7 @@ async def get_me(ctx: AuthContext = Depends(get_auth_context)):
username=ctx.user.username, username=ctx.user.username,
telegram_id=ctx.user.telegram_id, telegram_id=ctx.user.telegram_id,
referal_code=ctx.user.referal_code, referal_code=ctx.user.referal_code,
bonus_balance=ctx.user.balance,
) )
@@ -36,7 +37,7 @@ async def get_subscription(ctx: AuthContext = Depends(get_auth_context)):
has_subscription=True, has_subscription=True,
devices=sub.devices, devices=sub.devices,
expires_at=sub.expires_at, expires_at=sub.expires_at,
addon_ids=[a.id for a in sub.addons], addon_ids=[a.addon_id for a in sub.addons],
subscription_link=build_subscription_link(rw_user.short_uuid), subscription_link=build_subscription_link(rw_user.short_uuid),
) )

View File

@@ -15,3 +15,4 @@ class UserInfo(BaseModel):
username: str | None = Field(None) username: str | None = Field(None)
telegram_id: str | None = Field(None) telegram_id: str | None = Field(None)
referal_code: str = Field() referal_code: str = Field()
bonus_balance: float = Field()

View File

@@ -22,6 +22,7 @@ async def authorize_user(
username=user.username, username=user.username,
telegram_id=user.telegram_id, telegram_id=user.telegram_id,
referal_code=user.referal_code, referal_code=user.referal_code,
bonus_balance=user.balance,
), ),
expires_at=key_pair.expires_at, expires_at=key_pair.expires_at,
) )