Money review: Bazaar rejection no longer burns the purchase token, read-only order admin, pack grant from order snapshot

- Bazaar verify: a rejected token is not stored on the order or the failed transaction, so the
  same purchase can still pay the right order; a refunded order refuses a new token (409).
- OrderAdmin: every field read-only, no add/delete; Transaction admin read-only.
- grant_pack uses the order's snapshot when the plan was deleted, logs ERROR when nothing is granted.
- Pay link: authority is stored only if the order amount did not change during PaymentRequest.
- PlanAdmin accepts only IRT (the app always renders toman).
- reconcile_pending_order logs unexpected exceptions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 09:26:26 +03:30
parent eb4e97753b
commit fedd10ff22
7 changed files with 268 additions and 22 deletions

View File

@@ -4,6 +4,8 @@
جنگویی‌ه (نه میکروسرویس واقعی)، «فراخوانی سرور-به-سرور» با یک import مستقیم
پیاده‌سازی شده، نه یک HTTP call به خودمون.
"""
import logging
from django.conf import settings
from django.core import signing
from django.db import IntegrityError
@@ -16,6 +18,8 @@ from statuslog.signals import status_changed
from .models import Order, Transaction
logger = logging.getLogger(__name__)
class IdempotencyKeyConflict(APIException):
"""اگر Idempotency-Key موجود متعلق به کاربر یا پارامترهای دیگری باشد، 409 برمی‌گرداند."""
@@ -255,7 +259,10 @@ def reconcile_pending_order(order: Order) -> bool:
result = zarinpal.verify_payment(
authority=order.gateway_authority, amount=order.amount, currency=order.currency,
)
except Exception: # noqa: BLE001 — درگاه در دسترس نیست؛ دفعه‌ی بعد
except zarinpal.ZarinpalAPIError: # درگاه در دسترس نیست (خودِ zarinpal لاگ کرده)؛ دفعه‌ی بعد
return False
except Exception: # noqa: BLE001 — صفحه‌ی وضعیت نباید با خطای بازسنجی ۵۰۰ بدهد؛ ولی دیده شود
logger.exception('reconcile: بازسنجیِ سفارشِ %s شکست خورد', order.id)
return False
if not result.is_valid:
return False