diff --git a/app/webapi/routes/miniapp.py b/app/webapi/routes/miniapp.py index 864c1fee..bc8b6045 100644 --- a/app/webapi/routes/miniapp.py +++ b/app/webapi/routes/miniapp.py @@ -39,7 +39,6 @@ from app.services.remnawave_service import ( RemnaWaveService, ) from app.services.promo_offer_service import promo_offer_service -from app.services.promocode_service import PromoCodeService from app.services.subscription_service import SubscriptionService from app.utils.subscription_utils import get_happ_cryptolink_redirect_link from app.utils.telegram_webapp import ( @@ -55,9 +54,6 @@ from ..schemas.miniapp import ( MiniAppFaq, MiniAppFaqItem, MiniAppLegalDocuments, - MiniAppPromoCode, - MiniAppPromoCodeActivationRequest, - MiniAppPromoCodeActivationResponse, MiniAppPromoGroup, MiniAppPromoOffer, MiniAppPromoOfferClaimRequest, @@ -74,8 +70,6 @@ logger = logging.getLogger(__name__) router = APIRouter() -promo_code_service = PromoCodeService() - def _format_gb(value: Optional[float]) -> float: if value is None: @@ -1053,109 +1047,6 @@ async def get_subscription_details( ) -@router.post( - "/promo-codes/activate", - response_model=MiniAppPromoCodeActivationResponse, -) -async def activate_promo_code( - payload: MiniAppPromoCodeActivationRequest, - db: AsyncSession = Depends(get_db_session), -) -> MiniAppPromoCodeActivationResponse: - try: - webapp_data = parse_webapp_init_data(payload.init_data, settings.BOT_TOKEN) - except TelegramWebAppAuthError as error: - raise HTTPException( - status.HTTP_401_UNAUTHORIZED, - detail={"code": "unauthorized", "message": str(error)}, - ) from error - - telegram_user = webapp_data.get("user") - if not isinstance(telegram_user, dict) or "id" not in telegram_user: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail={"code": "invalid_user", "message": "Invalid Telegram user payload"}, - ) - - try: - telegram_id = int(telegram_user["id"]) - except (TypeError, ValueError): - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail={"code": "invalid_user", "message": "Invalid Telegram user identifier"}, - ) from None - - user = await get_user_by_telegram_id(db, telegram_id) - if not user: - raise HTTPException( - status.HTTP_404_NOT_FOUND, - detail={"code": "user_not_found", "message": "User not found"}, - ) - - code = (payload.code or "").strip().upper() - if not code: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, - detail={"code": "invalid", "message": "Promo code must not be empty"}, - ) - - result = await promo_code_service.activate_promocode(db, user.id, code) - if result.get("success"): - promocode_data = result.get("promocode") or {} - - try: - balance_bonus = int(promocode_data.get("balance_bonus_kopeks") or 0) - except (TypeError, ValueError): - balance_bonus = 0 - - try: - subscription_days = int(promocode_data.get("subscription_days") or 0) - except (TypeError, ValueError): - subscription_days = 0 - - promo_payload = MiniAppPromoCode( - code=str(promocode_data.get("code") or code), - type=promocode_data.get("type"), - balance_bonus_kopeks=balance_bonus, - subscription_days=subscription_days, - max_uses=promocode_data.get("max_uses"), - current_uses=promocode_data.get("current_uses"), - valid_until=promocode_data.get("valid_until"), - ) - - return MiniAppPromoCodeActivationResponse( - success=True, - description=result.get("description"), - promocode=promo_payload, - ) - - error_code = str(result.get("error") or "generic") - status_map = { - "user_not_found": status.HTTP_404_NOT_FOUND, - "not_found": status.HTTP_404_NOT_FOUND, - "expired": status.HTTP_410_GONE, - "used": status.HTTP_409_CONFLICT, - "already_used_by_user": status.HTTP_409_CONFLICT, - "server_error": status.HTTP_500_INTERNAL_SERVER_ERROR, - } - message_map = { - "invalid": "Promo code must not be empty", - "not_found": "Promo code not found", - "expired": "Promo code expired", - "used": "Promo code already used", - "already_used_by_user": "Promo code already used by this user", - "user_not_found": "User not found", - "server_error": "Failed to activate promo code", - } - - http_status = status_map.get(error_code, status.HTTP_400_BAD_REQUEST) - message = message_map.get(error_code, "Unable to activate promo code") - - raise HTTPException( - http_status, - detail={"code": error_code, "message": message}, - ) - - @router.post( "/promo-offers/{offer_id}/claim", response_model=MiniAppPromoOfferClaimResponse, diff --git a/app/webapi/schemas/miniapp.py b/app/webapi/schemas/miniapp.py index 1988e34b..8b80f82c 100644 --- a/app/webapi/schemas/miniapp.py +++ b/app/webapi/schemas/miniapp.py @@ -123,27 +123,6 @@ class MiniAppPromoOfferClaimResponse(BaseModel): code: Optional[str] = None -class MiniAppPromoCode(BaseModel): - code: str - type: Optional[str] = None - balance_bonus_kopeks: int = 0 - subscription_days: int = 0 - max_uses: Optional[int] = None - current_uses: Optional[int] = None - valid_until: Optional[datetime] = None - - -class MiniAppPromoCodeActivationRequest(BaseModel): - init_data: str = Field(..., alias="initData") - code: str - - -class MiniAppPromoCodeActivationResponse(BaseModel): - success: bool = True - description: Optional[str] = None - promocode: Optional[MiniAppPromoCode] = None - - class MiniAppFaqItem(BaseModel): id: int title: Optional[str] = None diff --git a/miniapp/index.html b/miniapp/index.html index ae58071d..0a87a059 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -1079,163 +1079,6 @@ letter-spacing: 0.5px; } - .promo-code-card { - padding: 20px; - display: flex; - flex-direction: column; - gap: 16px; - } - - .promo-code-header { - display: flex; - flex-direction: column; - gap: 6px; - } - - .promo-code-title { - font-size: 18px; - font-weight: 700; - color: var(--text-primary); - } - - .promo-code-subtitle { - font-size: 14px; - color: var(--text-secondary); - } - - .promo-code-form { - display: flex; - flex-direction: column; - gap: 12px; - } - - .promo-code-input-group { - display: flex; - align-items: center; - gap: 12px; - padding: 12px 14px; - background: rgba(var(--primary-rgb), 0.04); - border: 2px solid rgba(var(--primary-rgb), 0.12); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-sm); - transition: border-color 0.2s ease, box-shadow 0.2s ease; - } - - .promo-code-input-group:focus-within { - border-color: rgba(var(--primary-rgb), 0.45); - box-shadow: 0 6px 20px rgba(var(--primary-rgb), 0.18); - } - - .promo-code-input { - flex: 1; - border: none; - background: transparent; - color: var(--text-primary); - font-size: 16px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 1.2px; - } - - .promo-code-input::placeholder { - color: var(--text-secondary); - opacity: 0.75; - } - - .promo-code-input:focus { - outline: none; - } - - .promo-code-button { - border: none; - border-radius: var(--radius); - background: var(--primary); - color: var(--tg-theme-button-text-color); - font-weight: 700; - font-size: 14px; - padding: 10px 18px; - cursor: pointer; - transition: transform 0.2s ease, box-shadow 0.2s ease; - white-space: nowrap; - } - - .promo-code-button:hover:not(:disabled) { - transform: translateY(-1px); - box-shadow: 0 8px 20px rgba(var(--primary-rgb), 0.35); - } - - .promo-code-button:disabled { - opacity: 0.6; - cursor: not-allowed; - box-shadow: none; - } - - .promo-code-feedback { - font-size: 14px; - border-radius: var(--radius); - padding: 12px 14px; - line-height: 1.5; - } - - .promo-code-feedback.hidden { - display: none; - } - - .promo-code-feedback.error { - background: rgba(239, 68, 68, 0.12); - color: #b91c1c; - border: 1px solid rgba(239, 68, 68, 0.3); - } - - .promo-code-feedback.success { - background: rgba(16, 185, 129, 0.12); - color: #047857; - border: 1px solid rgba(16, 185, 129, 0.3); - } - - .promo-code-result { - display: flex; - flex-direction: column; - gap: 10px; - border-radius: var(--radius); - padding: 14px 16px; - background: rgba(var(--primary-rgb), 0.06); - border: 1px solid rgba(var(--primary-rgb), 0.1); - } - - .promo-code-result.hidden { - display: none; - } - - .promo-code-result-title { - font-size: 13px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.5px; - color: var(--text-secondary); - } - - .promo-code-result-list { - list-style: none; - display: flex; - flex-direction: column; - gap: 6px; - margin: 0; - padding: 0; - } - - .promo-code-result-item { - display: flex; - align-items: center; - gap: 10px; - font-size: 15px; - color: var(--text-primary); - } - - .promo-code-result-icon { - font-size: 18px; - } - /* Transaction History */ .history-list { list-style: none; @@ -2173,29 +2016,6 @@ border-color: rgba(148, 163, 184, 0.25); } - :root[data-theme="dark"] .promo-code-input-group { - background: rgba(15, 23, 42, 0.85); - border-color: rgba(148, 163, 184, 0.35); - box-shadow: none; - } - - :root[data-theme="dark"] .promo-code-feedback.error { - color: #fca5a5; - border-color: rgba(239, 68, 68, 0.4); - background: rgba(239, 68, 68, 0.18); - } - - :root[data-theme="dark"] .promo-code-feedback.success { - color: #34d399; - border-color: rgba(16, 185, 129, 0.4); - background: rgba(16, 185, 129, 0.18); - } - - :root[data-theme="dark"] .promo-code-result { - background: rgba(37, 99, 235, 0.12); - border-color: rgba(59, 130, 246, 0.25); - } - :root[data-theme="dark"] .btn-primary { box-shadow: 0 10px 30px rgba(37, 99, 235, 0.45); } @@ -2205,23 +2025,14 @@ .logo { font-size: 24px; } - + .balance-amount { font-size: 28px; } - + .stats-grid { grid-template-columns: 1fr; } - - .promo-code-input-group { - flex-direction: column; - align-items: stretch; - } - - .promo-code-button { - width: 100%; - } } @@ -2407,34 +2218,6 @@ -
-
-
Activate promo code
-
Enter a promo code to unlock rewards instantly.
-
-
-
- - -
-
- - -
-