refactor(payments): enhance payment processing logic and error handling

Refactor the `process_successful_payment` function to improve the handling of payment statuses during processing. Introduce a new method to atomically claim payments for processing and rollback payment statuses in case of activation failures. Enhance logging for various scenarios, including payment status checks and rollback actions, to provide clearer insights into payment processing flows. This improves the robustness and reliability of the payment handling logic.
This commit is contained in:
kavore
2026-02-12 15:58:16 +03:00
parent f1620a784b
commit b164fbd9bb
3 changed files with 148 additions and 19 deletions
+1
View File
@@ -11,6 +11,7 @@ from sqlalchemy.ext.asyncio import AsyncEngine
from config.settings import Settings
import os
_BASELINE_REVISION = "0001_initial_schema"
+74
View File
@@ -208,6 +208,80 @@ async def mark_provider_payment_succeeded_once(
return updated
async def mark_provider_payment_processing_once(
session: AsyncSession,
payment_db_id: int,
provider_payment_id: str,
expected_status_prefix: Optional[str] = None) -> bool:
"""Atomically claim payment for processing exactly once.
Returns True only when status is changed from a non-terminal state to
"processing". This prevents duplicate activation when concurrent
webhooks arrive for the same payment.
"""
conditions = [
Payment.payment_id == payment_db_id,
Payment.status != "succeeded",
Payment.status != "processing",
]
if expected_status_prefix:
conditions.append(Payment.status.like(f"{expected_status_prefix}%"))
stmt = (
update(Payment)
.where(*conditions)
.values(
status="processing",
provider_payment_id=provider_payment_id,
updated_at=func.now(),
)
)
result = await session.execute(stmt)
updated = (result.rowcount or 0) > 0
if updated:
logging.info(
"Payment record %s atomically marked as processing (provider id %s).",
payment_db_id,
provider_payment_id,
)
return updated
async def rollback_provider_payment_processing(
session: AsyncSession,
payment_db_id: int,
rollback_status: str,
provider_payment_id: Optional[str] = None) -> bool:
"""Atomically rollback temporary processing status.
Returns True only if payment is currently in "processing" state.
"""
values = {
"status": rollback_status,
"updated_at": func.now(),
}
if provider_payment_id:
values["provider_payment_id"] = provider_payment_id
stmt = (
update(Payment)
.where(
Payment.payment_id == payment_db_id,
Payment.status == "processing",
)
.values(**values)
)
result = await session.execute(stmt)
updated = (result.rowcount or 0) > 0
if updated:
logging.info(
"Payment record %s rolled back from processing to %s.",
payment_db_id,
rollback_status,
)
return updated
async def update_payment_discount_info(
session: AsyncSession,
payment_db_id: int,