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>
75 lines
3.7 KiB
Python
75 lines
3.7 KiB
Python
from rest_framework import serializers
|
||
|
||
|
||
class CreateOrderRequestSerializer(serializers.Serializer):
|
||
# این فراخوانی سرور-به-سرور از اپ Subscriptionه، نه از کلاینت؛ به همین
|
||
# دلیل userId اینجا مستقیماً در بدنه ارسال میشه (نه از توکن استخراج).
|
||
userId = serializers.CharField()
|
||
amount = serializers.IntegerField()
|
||
currency = serializers.CharField(required=False, default='IRT')
|
||
subscriptionId = serializers.CharField(required=False, allow_null=True, allow_blank=True)
|
||
# provider='bazaar' وقتی کاربر از همون ابتدا پرداخت درونبرنامهای بازار
|
||
# رو انتخاب کرده؛ در این حالت amount باید دقیقاً برابر قیمتِ محصولِ
|
||
# تعریفشده در پنل بازار برای همون bazaarProductSku باشه (بازار خودش
|
||
# قیمت رو کنترل میکنه، نه ما).
|
||
provider = serializers.ChoiceField(choices=['gateway', 'bazaar'], required=False, default='gateway')
|
||
bazaarProductSku = serializers.CharField(required=False, allow_blank=True)
|
||
|
||
|
||
class BazaarVerifyRequestSerializer(serializers.Serializer):
|
||
orderId = serializers.CharField()
|
||
productSku = serializers.CharField(required=False, allow_blank=True)
|
||
purchaseToken = serializers.CharField()
|
||
|
||
|
||
class BazaarVerifyResponseSerializer(serializers.Serializer):
|
||
orderId = serializers.CharField()
|
||
status = serializers.CharField()
|
||
valid = serializers.BooleanField()
|
||
|
||
|
||
class CreateOrderResponseSerializer(serializers.Serializer):
|
||
orderId = serializers.CharField()
|
||
status = serializers.CharField()
|
||
# provider='bazaar' یعنی paymentUrl نداریم (کلاینت با SDK بازار خودش
|
||
# خرید میکنه، نه با ریدایرکت به یک URL).
|
||
paymentUrl = serializers.CharField(allow_null=True)
|
||
provider = serializers.CharField()
|
||
|
||
|
||
class WebhookRequestSerializer(serializers.Serializer):
|
||
# کالبک از سمت درگاه پرداخت میاد، نه از اپ
|
||
orderId = serializers.CharField()
|
||
gateway = serializers.CharField(required=False, default='zarinpal')
|
||
gatewayRefId = serializers.CharField(required=False, allow_blank=True)
|
||
# ⚠️ اصلاح امنیتی: قبلاً status یک CharField آزاد بود و هر مقداری
|
||
# (حتی مقادیر پشتیبانینشده/اشتباه) رو مستقیم روی Order ذخیره میکرد.
|
||
# حالا فقط دو وضعیت نهاییِ معتبرِ کالبک درگاه پذیرفته میشه؛ 'pending'
|
||
# وضعیت اولیهی داخلیه و نباید از بیرون توسط webhook قابلتنظیم باشه.
|
||
status = serializers.ChoiceField(choices=['paid', 'failed'])
|
||
|
||
|
||
class WebhookResponseSerializer(serializers.Serializer):
|
||
received = serializers.BooleanField()
|
||
|
||
|
||
class ZarinpalCallbackResponseSerializer(serializers.Serializer):
|
||
"""فقط وقتی settings.ZARINPAL_REDIRECT_URL خالی باشه برگردونده میشه؛
|
||
وگرنه کاربر به همون URL ریدایرکت میشه، نه به این JSON."""
|
||
orderId = serializers.CharField()
|
||
status = serializers.CharField()
|
||
|
||
|
||
class TransactionSerializer(serializers.Serializer):
|
||
id = serializers.CharField()
|
||
gateway = serializers.CharField()
|
||
status = serializers.CharField()
|
||
paidAt = serializers.DateTimeField(allow_null=True)
|
||
|
||
|
||
class OrderDetailResponseSerializer(serializers.Serializer):
|
||
orderId = serializers.CharField()
|
||
status = serializers.CharField()
|
||
amount = serializers.IntegerField()
|
||
transactions = TransactionSerializer(many=True)
|