feat(admin): add admin notification system for deposits, purchases, and renewals

This commit is contained in:
2026-06-04 12:35:32 +07:00
parent 5ec9491e22
commit 4c5a16a3ca
11 changed files with 249 additions and 59 deletions

View File

@@ -1,4 +1,6 @@
# ruff: noqa: N803
import hashlib
import hmac
import logging
import math
@@ -14,6 +16,7 @@ from db.models.bills import DepositStatus
from db.models.transactions import BalanceTxType
from misc.pally import BillStatus
from schemas.di import APIDependenciesDTO
from services.admin_notifications import notify_deposit
router = APIRouter(prefix="/pally")
@@ -21,7 +24,7 @@ logger = logging.getLogger(__name__)
@router.post("/result")
async def pally_callback(
async def pally_callback( # noqa: PLR0911, PLR0912
InvId: str = Form(...),
OutSum: str = Form(...),
Commission: str = Form(...),
@@ -48,31 +51,43 @@ async def pally_callback(
# FIXED: Use InvId (which contains the order_id from bill creation) instead of custom field
# The InvId field contains the db_bill.id that was passed as order_id during bill creation
bill_id_str = InvId
logger.info(
"Pally webhook received - InvId: %s, OutSum: %s, Commission: %s, TrsId: %s, Status: %s, "
"CurrencyIn: %s, custom: %s, BalanceAmount: %s, SignatureValue: %s",
InvId, OutSum, Commission, TrsId, Status, CurrencyIn, custom, BalanceAmount, SignatureValue
InvId,
OutSum,
Commission,
TrsId,
Status,
CurrencyIn,
custom,
BalanceAmount,
SignatureValue,
)
# Enhanced logging for failed payments
if Status != BillStatus.SUCCESS:
logger.warning(
"Non-success payment received - Status: %s, ErrorCode: %s, ErrorMessage: %s, TrsId: %s",
Status, ErrorCode, ErrorMessage, TrsId
Status,
ErrorCode,
ErrorMessage,
TrsId,
)
# Validate signature
raw_string = f"{OutSum}:{InvId}:{settings.pally_token}"
expected_signature = hashlib.md5(raw_string.encode("utf-8")).hexdigest().upper()
logger.debug("Signature validation - Expected: %s, Received: %s, Raw: %s",
expected_signature, SignatureValue, raw_string)
if SignatureValue != expected_signature:
logger.debug("Signature validation for TrsId %s", TrsId)
if not hmac.compare_digest(SignatureValue, expected_signature):
logger.critical(
"SECURITY ALERT: Invalid signature for TrsId %s - Expected: %s, Received: %s, Raw: %s",
TrsId, expected_signature, SignatureValue, raw_string
"SECURITY ALERT: Invalid signature for TrsId %s - Expected: %s, Received: %s",
TrsId,
expected_signature,
SignatureValue,
)
raise HTTPException(403, detail="Invalid signature.")
@@ -86,8 +101,7 @@ async def pally_callback(
# Validate bill ID (from InvId field, which contains the order_id from bill creation)
if not bill_id_str or not bill_id_str.isdigit():
logger.critical(
"Invalid or non-numeric bill ID in InvId field for TrsId %s: '%s'",
TrsId, bill_id_str
"Invalid or non-numeric bill ID in InvId field for TrsId %s: '%s'", TrsId, bill_id_str
)
return "OK"
@@ -100,8 +114,9 @@ async def pally_callback(
# Check if already processed
if bill.status != DepositStatus.PENDING:
logger.warning("Bill %s (TrsId: %s) is already processed with status: %s",
bill.id, TrsId, bill.status)
logger.warning(
"Bill %s (TrsId: %s) is already processed with status: %s", bill.id, TrsId, bill.status
)
return "OK"
try:
@@ -111,43 +126,59 @@ async def pally_callback(
# Customer pays fees - BalanceAmount is the net amount credited to merchant
credited_amount = int(float(BalanceAmount))
gross_amount = int(float(OutSum))
logger.info(
"Customer-pays-fees payment: bill_id=%s, expected=%s, gross_paid=%s, net_credited=%s",
bill.id, bill.amount, gross_amount, credited_amount
bill.id,
bill.amount,
gross_amount,
credited_amount,
)
# Validate that the net credited amount matches our bill amount
if bill.amount != credited_amount:
logger.error(
"Net amount mismatch for bill %s (TrsId: %s) - Expected: %s, Net credited: %s, Gross paid: %s",
bill.id, TrsId, bill.amount, credited_amount, gross_amount
bill.id,
TrsId,
bill.amount,
credited_amount,
gross_amount,
)
return "OK"
amount = credited_amount # Credit the net amount (without fees)
else:
# Standard payment - OutSum should match bill amount exactly
amount = int(float(OutSum))
logger.info(
"Standard payment: bill_id=%s, expected=%s, received=%s",
bill.id, bill.amount, amount
bill.id,
bill.amount,
amount,
)
if bill.amount != amount:
logger.error(
"Amount mismatch for bill %s (TrsId: %s) - Expected: %s, Received: %s",
bill.id, TrsId, bill.amount, amount
bill.id,
TrsId,
bill.amount,
amount,
)
return "OK"
logger.info("Processing payment: bill_id=%s, user_id=%s, amount=%s",
bill.id, bill.creator_id, amount)
logger.info(
"Processing payment: bill_id=%s, user_id=%s, amount=%s",
bill.id,
bill.creator_id,
amount,
)
# Credit user balance
await deps.user_repository.increase_user_balance(
credited_user = await deps.user_repository.increase_user_balance(
session,
bill.creator_id,
amount=amount,
@@ -155,6 +186,16 @@ async def pally_callback(
description=f"payment via PALLY (TrsId: {TrsId})",
)
await successful_payment_notification(bot, bill.creator_id, amount)
if credited_user:
await notify_deposit(
bot,
user_id=bill.creator_id,
amount=amount,
balance_before=credited_user.balance - amount,
balance_after=credited_user.balance,
bill_id=bill.id,
payment_id=TrsId,
)
# Process referral bonus
user = await deps.user_repository.get_user_by_id(session, bill.creator_id)
@@ -169,14 +210,19 @@ async def pally_callback(
description=f"referral reward for user {bill.creator_id} (TrsId: {TrsId})",
)
await successful_payment_notification(bot, referal, referal_amount)
logger.info("Referral bonus processed: referrer_id=%s, amount=%s", referal, referal_amount)
logger.info(
"Referral bonus processed: referrer_id=%s, amount=%s", referal, referal_amount
)
logger.info("Payment processing completed successfully for TrsId %s", TrsId)
except Exception as e:
logger.exception(
"CRITICAL ERROR processing payment for TrsId %s, bill_id %s, user_id %s: %s",
TrsId, bill.id, bill.creator_id, str(e)
TrsId,
bill.id,
bill.creator_id,
str(e),
)
# Don't return early - still mark as success to prevent retries
# The balance operation might have partially succeeded
@@ -185,7 +231,7 @@ async def pally_callback(
await deps.bills_repository.update_bill_status(
session, int(bill.id), status=DepositStatus.SUCCESS
)
logger.info("Bill %s marked as SUCCESS for TrsId %s", bill.id, TrsId)
return "OK"