feat(subscription): improve subscription sync and renewal logic.

This commit is contained in:
2026-06-17 18:10:56 +07:00
parent 7ab0e71108
commit 245741b182
8 changed files with 88 additions and 32 deletions

View File

@@ -99,13 +99,13 @@ async def notify_subscription_renewal(
bot,
settings.admin_log_subscriptions_topic_id,
(
"<b>Продление подписки</b>",
f"Пользователь: <code>{user_id}</code>",
f"Стоимость: <b>{amount}₽</b>",
f"Баланс: {balance_before}₽ -> {balance_after}",
f"Устройства: {devices}",
f"Срок: {duration_days} дн.",
f"Whitelist: {'да' if whitelists else 'нет'}",
f"Новая дата окончания: {escape(end_date)}",
"<b>Продление подписки</b>\n\n"
f"Пользователь: <code>{user_id}</code>\n"
f"Стоимость: <b>{amount}₽</b>\n"
f"Баланс: {balance_before}₽ -> {balance_after}\n"
f"Устройства: {devices}\n"
f"Срок: {duration_days} дн.\n"
f"Whitelist: {'да' if whitelists else 'нет'}\n"
f"Новая дата окончания: {escape(end_date)}"
),
)

View File

@@ -49,7 +49,9 @@ class RenewalService:
)
return utils.calculate_price(plan)
async def _renew_subscription_in_rw(self, subscription: Subscription) -> None:
async def _renew_subscription_in_rw(
self, subscription: Subscription, expire_at: datetime.datetime
) -> None:
rw_user = await rw_service.get_user_by_telegram_id(self.rw_sdk, subscription.user_id)
if not rw_user:
raise RenewalServiceError(f"RW user not found for user_id={subscription.user_id}")
@@ -58,7 +60,7 @@ class RenewalService:
if subscription.whitelists:
squads.append(InternalSquads.WHITELISTS)
await rw_service.add_days(self.rw_sdk, rw_user.uuid, subscription.duration_days)
await rw_service.update_expire_at(self.rw_sdk, rw_user.uuid, expire_at)
await rw_service.update_squads(self.rw_sdk, rw_user.uuid, squads)
await rw_service.set_hwid_limit(self.rw_sdk, rw_user.uuid, subscription.devices)
@@ -89,7 +91,9 @@ class RenewalService:
await self.subscription_repository.update_subscription_autorenew(
session, user_id, False
)
await self._notify_user(user_id, AUTORENEWAL_INSUFFICIENT_FUNDS)
await self._notify_user(
user_id, AUTORENEWAL_INSUFFICIENT_FUNDS.format(price=price, balance=user.balance)
)
return False
renewed_user = await self.user_repository.decrease_user_balance(
@@ -104,8 +108,14 @@ class RenewalService:
logger.error("Failed to decrease balance for user %d", user_id)
return False
now_utc = datetime.datetime.now(datetime.UTC)
if subscription.end_date > now_utc:
new_expire = subscription.end_date + datetime.timedelta(days=subscription.duration_days)
else:
new_expire = now_utc + datetime.timedelta(days=subscription.duration_days)
try:
await self._renew_subscription_in_rw(subscription)
await self._renew_subscription_in_rw(subscription, new_expire)
except RenewalServiceError:
logger.exception("Failed to renew subscription in RW for user %d", user_id)
await self.user_repository.increase_user_balance(
@@ -117,11 +127,10 @@ class RenewalService:
)
return False
new_end_date = subscription.end_date + datetime.timedelta(days=subscription.duration_days)
await self.subscription_repository.update_sub(
session,
user_id,
end_date=new_end_date,
end_date=new_expire,
autorenew=True,
status=SubscriptionStatus.ACTIVE,
devices=subscription.devices,
@@ -134,7 +143,7 @@ class RenewalService:
user_id,
AUTORENEWAL_SUCCESS.format(
price=price,
end_date=new_end_date.strftime("%d.%m.%Y"),
end_date=new_expire.strftime("%d.%m.%Y"),
),
)
await notify_subscription_renewal(
@@ -146,7 +155,7 @@ class RenewalService:
devices=subscription.devices,
duration_days=subscription.duration_days,
whitelists=subscription.whitelists,
end_date=new_end_date.strftime("%d.%m.%Y"),
end_date=new_expire.strftime("%d.%m.%Y"),
)
logger.info("Successfully renewed subscription for user %d", user_id)

View File

@@ -372,5 +372,15 @@ async def delete_hwid(sdk: RemnawaveSDK, user_uuid: str, hwid: str) -> bool:
resp = await sdk.hwid.delete_hwid_to_user(body)
return isinstance(resp, DeleteUserHwidDeviceResponseDto)
except Exception:
logger.exception("failed to fetch hwid list for user=%s", user_uuid)
logger.exception("failed to delete hwid=%s for user=%s", hwid, user_uuid)
return False
async def update_expire_at(sdk: RemnawaveSDK, user_uuid: str, expire_at: datetime):
dto = UpdateUserRequestDto(uuid=user_uuid, expire_at=expire_at) # type: ignore
try:
await sdk.users.update_user(body=dto)
return True
except Exception:
logger.exception("failed to update expire_at=%s for user=%s", str(expire_at), user_uuid)
return False

View File

@@ -8,6 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from bot.texts import NO_SUBSCRIPTION, NOTHING_PLACEHOLDER, SUBSCRIPTION_INFORMATION
from config import settings
from db.models import Subscription
from db.models.users import User
from misc import utils
from repositories import UserRepository
@@ -122,7 +123,7 @@ class UserService:
session,
user.id,
end_date=rw_user.expire_at + duration,
autorenew=False,
autorenew=db_subscription.autorenew,
status=SubscriptionStatus.ACTIVE,
devices=int(subscription.devices),
whitelists=subscription.whitelists,
@@ -179,7 +180,7 @@ class UserService:
session,
user.id,
end_date=expire,
autorenew=False,
autorenew=db_subscription.autorenew,
status=SubscriptionStatus.ACTIVE,
devices=int(subscription.devices),
whitelists=subscription.whitelists,
@@ -239,3 +240,14 @@ class UserService:
logger.exception("caught an exception while launching promo, skipping user...")
return sent
def validate_db_subscription(self, subscription: Subscription, rw_user: rw.RWUserInfo):
return all(
[
(rw_user.status != "ACTIVE" and subscription.status == SubscriptionStatus.EXPIRED)
or (
rw_user.status == "ACTIVE" and subscription.status == SubscriptionStatus.ACTIVE
),
rw_user.expire_at == subscription.end_date,
]
)