connect Zarinpal as the real payment gateway

payment.services.call_payment_gateway was a stub returning a fake
payment.gateway/pay/{id} URL. It's replaced with a real Zarinpal
integration (payment/zarinpal.py, Payment API v4):

- create_order/CreateOrderView still never call Zarinpal directly —
  paymentUrl points at our own GET /api/payment/orders/{id}/pay
  (PaymentRedirectView), which requests the real Zarinpal authority on
  first visit and redirects to StartPay. This keeps order creation from
  depending on Zarinpal's uptime/latency, and means none of the existing
  purchase/idempotency tests needed to change.
- GET /api/payment/zarinpal/callback (ZarinpalCallbackView) is where
  Zarinpal redirects the user back to; it always re-verifies
  server-to-server via zarinpal.verify_payment before marking an order
  paid, so a forged Status=OK in the query string alone does nothing.
- zarinpal.py fails closed (503 / invalid) when ZARINPAL_MERCHANT_ID
  isn't configured, matching payment/bazaar.py's existing convention —
  never a fake successful payment.

Adds Order.gateway_authority, PUBLIC_BASE_URL/ZARINPAL_* settings, and
a payment_pay throttle scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-06 13:27:13 +03:30
parent a275eac7fd
commit b5476e1c5d
11 changed files with 630 additions and 14 deletions

View File

@@ -4,12 +4,16 @@ from .views import (
BazaarVerifyPurchaseView,
CreateOrderView,
OrderDetailView,
PaymentRedirectView,
PaymentWebhookView,
ZarinpalCallbackView,
)
urlpatterns = [
path('payment/orders', CreateOrderView.as_view(), name='payment-create-order'),
path('payment/webhook', PaymentWebhookView.as_view(), name='payment-webhook'),
path('payment/zarinpal/callback', ZarinpalCallbackView.as_view(), name='payment-zarinpal-callback'),
path('payment/bazaar/verify', BazaarVerifyPurchaseView.as_view(), name='payment-bazaar-verify'),
path('payment/orders/<int:order_id>', OrderDetailView.as_view(), name='payment-order-detail'),
path('payment/orders/<int:order_id>/pay', PaymentRedirectView.as_view(), name='payment-order-pay'),
]