diff --git a/api/routes/pally.py b/api/routes/pally.py index 8178606..d7dee13 100644 --- a/api/routes/pally.py +++ b/api/routes/pally.py @@ -30,57 +30,100 @@ async def pally_callback( CurrencyIn: str = Form(...), custom: str | None = Form(None), SignatureValue: str = Form(...), + # Optional fields for additional information + AccountType: str | None = Form(None), + AccountNumber: str | None = Form(None), + BalanceAmount: str | None = Form(None), + BalanceCurrency: str | None = Form(None), + PayerPhone: str | None = Form(None), + PayerEmail: str | None = Form(None), + PayerName: str | None = Form(None), + PayerComment: str | None = Form(None), + ErrorCode: int | None = Form(None), + ErrorMessage: str | None = Form(None), session: AsyncSession = Depends(get_db), bot: Bot = Depends(get_bot), deps: APIDependenciesDTO = Depends(get_deps), ): oid = custom or "" - logger.info("invid: %s, outsum: %s, commission: %s, TrsId: %s, status: %s, currencyin: %s, custom: %s, sign: %s", InvId, OutSum, Commission, TrsId, Status, CurrencyIn, custom, SignatureValue) + logger.info( + "Pally webhook received - InvId: %s, OutSum: %s, Commission: %s, TrsId: %s, Status: %s, " + "CurrencyIn: %s, custom: %s, SignatureValue: %s", + InvId, OutSum, Commission, TrsId, Status, CurrencyIn, custom, 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 + ) + + # 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.critical("Invalid signature for TrsId %s", TrsId) + logger.critical( + "SECURITY ALERT: Invalid signature for TrsId %s - Expected: %s, Received: %s, Raw: %s", + TrsId, expected_signature, SignatureValue, raw_string + ) raise HTTPException(403, detail="Invalid signature.") + # Only process successful payments if Status != BillStatus.SUCCESS: logger.info("Bill %s skipped: status=%s", TrsId, Status) return "OK" - logger.info("Received successfully paid bill %s", TrsId) + logger.info("Processing successfully paid bill %s", TrsId) - if not oid.isdigit(): - logger.critical("Invalid custom field format for TrsId %s: %s", TrsId, custom) + # Validate custom field (order ID) + if not oid or not oid.isdigit(): + logger.critical("Invalid or missing custom field for TrsId %s: '%s'", TrsId, custom) return "OK" + # Find bill in database bill = await deps.bills_repository.get_bill_by_id(session, int(oid)) if not bill: - logger.critical("Bill %s is not found in db", TrsId) + logger.critical("Bill %s not found in database for TrsId %s", oid, TrsId) return "OK" + # Check if already processed if bill.status != DepositStatus.PENDING: - logger.warning("bill=%s is already paid according to the db", bill.id) + logger.warning("Bill %s (TrsId: %s) is already processed with status: %s", + bill.id, TrsId, bill.status) return "OK" try: amount = int(float(OutSum)) + # Validate payment amount if bill.amount != amount: - logger.warning("requested amount for bill=%s is not equal to db entry", bill.id) + logger.error( + "Amount mismatch for bill %s (TrsId: %s) - Expected: %s, Received: %s", + 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) + + # Credit user balance await deps.user_repository.increase_user_balance( session, bill.creator_id, amount=amount, tx_type=BalanceTxType.DEPOSIT, - description="personal balance deposit via PALLY", + description=f"payment via PALLY (TrsId: {TrsId})", ) await successful_payment_notification(bot, bill.creator_id, amount) + # Process referral bonus user = await deps.user_repository.get_user_by_id(session, bill.creator_id) - referal = user.referal_id if user else None if referal is not None: referal_amount = math.floor(amount * (settings.referal_bonus / 100)) @@ -89,19 +132,26 @@ async def pally_callback( referal, referal_amount, tx_type=BalanceTxType.REFERRAL_BONUS, - description=f"referal reward for referal_id={bill.creator_id}", + 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) - except Exception: + logger.info("Payment processing completed successfully for TrsId %s", TrsId) + + except Exception as e: logger.exception( - "Exception caught while processing balances for TrsId %s (Creator: %s)", - TrsId, - bill.creator_id, + "CRITICAL ERROR processing payment for TrsId %s, bill_id %s, user_id %s: %s", + 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 + # Update bill status to success 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" diff --git a/tests/send_pally_webhook.py b/tests/send_pally_webhook.py index 0c19582..a9407e5 100644 --- a/tests/send_pally_webhook.py +++ b/tests/send_pally_webhook.py @@ -39,32 +39,35 @@ def main(): parser.add_argument( "--status", default="SUCCESS", - choices=["SUCCESS", "FAIL", "PROCESS"], + choices=["SUCCESS", "FAIL", "UNDERPAID", "OVERPAID"], help="Статус платежа (по умолчанию SUCCESS)", ) args = parser.parse_args() pally_token = os.environ["PALLY_TOKEN"] - # Эти поля могут быть любыми строками — вебхук их не использует содержательно, - # только включает в подпись. Главное чтобы совпадали с тем, что пойдёт в подпись. - payment_id = "test_payment_001" - bill_id = f"test_bill_{args.bill_id}" + # Корректные поля согласно документации pally.info + inv_id = f"payment-{args.bill_id}" # InvId - идентификатор платежа + trs_id = f"LXZv3R{args.bill_id}" # TrsId - уникальный идентификатор счета currency = "RUB" - order_id = str(args.bill_id) # order_id == id счёта в нашей БД + custom = str(args.bill_id) # custom field содержит ID счета в нашей БД + commission = "0.00" # Комиссия + # ИСПРАВЛЕНО: используем inv_id вместо payment_id для сигнатуры signature = calculate_signature( - payment_id, args.status, str(args.amount), currency, order_id, pally_token + str(args.amount), inv_id, pally_token ) + # ИСПРАВЛЕНО: используем правильные имена полей согласно документации payload = { - "id": payment_id, - "bill_id": bill_id, - "status": args.status, - "req_amount": str(args.amount), - "currency": currency, - "order_id": order_id, - "signature": signature, + "InvId": inv_id, + "OutSum": str(args.amount), + "Commission": commission, + "TrsId": trs_id, + "Status": args.status, + "CurrencyIn": currency, + "custom": custom, + "SignatureValue": signature, } print(f"{args.host}{ENDPOINT}")