django-iranian-payment
درگاههای پرداخت ایرانی برای Django، با معماری دو لایه:
- لایهی ساده (توصیهشده): یک اپ Django اختیاری که خودش رکورد پرداخت، ذخیرهی
authority، تأیید، و مدیریت وضعیت تراکنش را انجام میدهد. کاربر تقریباً هیچ منطقی نمینویسد. - لایهی هسته (پیشرفته): درگاههای بدون state که در هر محیطی (حتی غیر Django یا
async) قابل استفادهاند. کاربر خودش
authorityرا ذخیره و تأیید را مدیریت میکند.
واحد بانک همیشه ریال است، اما میتوانی واحد ورودی خودت را به تومان تغییر
دهی (IRANIAN_PAYMENT["currency"] = "toman")؛ پکیج خودکار به ریال تبدیل میکند.
بخش واحد پول را ببین.
درگاههای آماده و تستشده (registry عمومی)
این درگاهها راستیآزمایی شده و با get_gateway("slug") در دسترساند:
| درگاه | نوع | وضعیت | وابستگی اختیاری |
|---|---|---|---|
| زرینپال | REST/JSON | ✅ sandbox و تراکنش واقعی تستشده | — |
| زیبال | REST/JSON | ✅ sandbox و تراکنش واقعی تستشده | — |
| ملت | SOAP | ✅ تراکنش live واقعی تستشده (sandbox ندارد) | [soap] (zeep) |
| سامان (SEP) | REST/JSON | ✅ تراکنش واقعی با ترمینال واقعی تستشده (sandbox ندارد) | — |
✅ هر چهار درگاه با تراکنش واقعی تأیید شدهاند. زرینپال/زیبال هم در sandbox و هم با تراکنش واقعی تست شدند. ملت روی محیط عملیاتی (bpm.shaparak.ir) تست شد: تراکنش موفق (ResCode=0، SaleReferenceId، CardHolderPan، FinalAmount) و سناریوی کنسل کاربر (ResCode=17) هر دو تأیید شدند. سامان با تراکنش واقعی روی ترمینال واقعی تست و در نسخهی
1.0.0عمومی شد.⛔ سامان و ملت sandbox واقعی ندارند و
sandbox=Trueرا با خطا رد میکنند (جزئیات). فقط liveاند.
⚠️ ملت نیاز به
zeepدارد:pip install "django-iranian-payment[soap]". ملت دومرحلهای است؛ حالت پیشفرضverify_settle(تأیید+واریز اتمیک) توصیه میشود. برای واریز با تأخیر،settle_mode="verify_only"را در config بگذار و خودتsettle()را صدا بزن (وگرنه بانک در ۳ ساعت Autoreversal میزند). هدایت کاربر به ملت با فرم POST است (نه redirect ساده)؛ لایهی Django این فرم auto-submit را در viewgo_to_gatewayخودکار میسازد.
درگاههای تجربی (در core.experimental)
این درگاهها پیادهسازی کامل از مستند رسمی دارند و منطقشان با تست خودکار
(InMemoryTransport) پوشش داده شده، اما هنوز هیچ تست sandbox یا ترمینال واقعی
روی آنها انجام نشده — برخلاف زرینپال/زیبال که حداقل sandbox دارند، این درگاهها
بهدلیل محدودیتهای دسترسی (نیاز به قرارداد پذیرندگی، ثبت IP، کلید واقعی) حتی تست
script سندباکسشان هم انجام نشده است.
طبق قانون طلایی پروژه، تا تست واقعی موفق در registry عمومی قرار نمیگیرند و با
get_gateway در دسترس نیستند؛ فقط با import صریح:
from django_iranian_payment.core.experimental.irankish import IrankishGateway
from django_iranian_payment.core.experimental.nextpay import NextPayGateway
from django_iranian_payment.core.experimental.sadad import SadadGateway
from django_iranian_payment.core.experimental.digipay import DigipayGateway
| درگاه | نوع | رمزنگاری | وابستگی اختیاری |
|---|---|---|---|
| ایرانکیش | REST/JSON | AES + RSA | [irankish] |
| نکستپی | REST/JSON | — | — |
| سداد | REST/JSON (WebApi) | 3DES | [sadad] |
| دیجیپی | REST/JSON (OAuth2) | — | — |
ℹ️ ملت و سامان پیشتر تجربی بودند؛ پس از تست تراکنش واقعی به registry عمومی منتقل شدند (بالاتر را ببین). دیگر از
core.experimentalimport نمیشوند و مستقیم باget_gateway("mellat")/get_gateway("saman")در دسترساند.
ایرانکیش و سداد به وابستگی رمزنگاری نیاز دارند:
pip install "django-iranian-payment[irankish]"(شاملpycryptodomeوrsa) یاpip install "django-iranian-payment[sadad]"(شاملpycryptodome).
⚠️ سداد همان درگاهی است که از سایت بانک ملی به آن هدایت میشوید؛ بانک ملی درگاه مستقل خود را به سداد سپرده است.
⚠️ دیجیپی برخلاف بقیه احراز هویت دومرحلهای OAuth2 دارد: config آن پنج فیلد اجباری میخواهد (
username,password,client_id,client_secret,provider_id). در verify بهtrackingCode(که در نتیجهی پرداخت/callback برمیگردد) از طریقextraنیاز دارد. اگر پرداخت موفق را verify نکنی، پس از مدتی خودکار لغو و وجه مرجوع میشود.
درگاههای از کار افتاده (دیگر سرویس نمیدهند)
❌ پیآیآر (Pay.ir) و آیدیپی (IDPay) کلاً از دسترس خارج شدهاند و دیگر سرویسدهی نمیکنند. کدشان در
core.experimentalبهعنوان آرشیو باقی مانده ولی برای استفاده توصیه نمیشود وget_gateway("pay_ir")/get_gateway("idpay")کار نمیکند. تنها در صورتی که این سرویسها روزی بازگردند، با import صریح در دسترساند:from django_iranian_payment.core.experimental import PayIrGateway, IDPayGateway
نصب
pip install django-iranian-payment
# درگاههای SOAP (ملت و سایر درگاههای SOAP):
pip install "django-iranian-payment[soap]"
# درگاه ایرانکیش (AES+RSA):
pip install "django-iranian-payment[irankish]"
# درگاه سداد (3DES):
pip install "django-iranian-payment[sadad]"
تنظیمات
در settings.py:
IRANIAN_PAYMENT = {
"sandbox": False, # پیشفرض سراسری
"currency": "rial", # واحد ورودی مبلغ: "rial" (پیشفرض) یا "toman"
"return_url": "/payment/result/", # اختیاری (روش ۱): صفحهی نتیجه پس از verify
"gateways": {
"zarinpal": {"merchant_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"sandbox": True}, # sandbox مجزای همین درگاه
"zibal": {"merchant": "zibal"}, # زیبال sandbox را با merchant کنترل میکند
"mellat": {
"terminal_id": "1234567",
"username": "your-username",
"password": "your-password",
"settle_mode": "verify_settle", # یا "verify_only"
# بدون "sandbox" → از پیشفرض سراسری (False = live) پیروی میکند
},
},
}
واحد پول (ریال یا تومان)
بانکهای ایرانی همیشه با ریال کار میکنند؛ این واحد بانکِ پکیج است و
amount_to_send که به درگاه و verify میرود همیشه ریال است. اما میتوانی واحدی که
خودت ورودی میدهی را با IRANIAN_PAYMENT["currency"] بهصورت سراسری انتخاب کنی:
IRANIAN_PAYMENT = {
"currency": "toman", # حالا amount ها را به تومان میدهی
"gateways": {...},
}
# با تنظیم بالا (هر دو مسیر یکسان عمل میکنند):
services.start_payment("zarinpal", amount=15_000, ...) # ۱۵۰۰۰ تومان → بانک ۱۵۰۰۰۰ ریال
# مسیر toolkit هم واحد سراسری را رعایت میکند (get_gateway آن را تزریق میکند):
get_gateway("zarinpal").initiate(PaymentRequest(amount=15_000, ...)) # هم ۱۵۰۰۰۰ ریال
- پیشفرض
"rial"است (سازگار با قبل؛ هیچ تبدیلی انجام نمیشود). - واحد سراسری در هر دو مسیر اعمال میشود: هم
start_payment، همget_gateway(...).initiate(PaymentRequest(...)). در مسیر toolkit،get_gatewayواحد سراسری را در درخواستی کهcurrencyمشخص نکرده تزریق میکند. اگرcurrencyرا روی خودPaymentRequestبدهی، بر تنظیم سراسری اولویت دارد. - تبدیل ۱ تومان = ۱۰ ریال فقط یکبار و در همان ابتدای کار انجام میشود.
- مبالغ ذخیرهشده در مدل
Paymentو مقادیر بازگشتی verify همیشه ریالاند (واحد بانک)؛currencyفقط واحد ورودی را تعیین میکند. برای نمایش به کاربر در تومان، خودت بر ۱۰ تقسیم کن. - کارمزد:
rate_bpsواحدمستقل است؛ ولیfixedوmax_feeدر همان واحد ورودی تفسیر و خودکار به ریال تبدیل میشوند. - در لایهی هستهی خالص (بدون Django و بدون
get_gateway) واحد را روی خود درخواست بده:PaymentRequest(amount=15_000, currency="toman", ...). برای خواندن مقدار سراسری از settings همfrom django_iranian_payment import get_default_currencyهست.
sandbox مجزای هر درگاه
sandbox هر درگاه جداگانه تعیین میشود. کلید "sandbox" داخل config همان درگاه بر
مقدار سراسری اولویت دارد، پس میتوانی یک درگاه را sandbox و درگاه دیگری را live
داشته باشی همزمان. اولویت کامل:
get_gateway(..., sandbox=...) > config درگاه > "sandbox" سراسری > False
فقط درگاههایی که URL سندباکس جدا دارند (زرینپال، دیجیپی) به این فلگ واکنش میدهند. زیبال با مقدار
merchant="zibal"و ایرانکیش/نکستپی/سداد اصلاً URL سندباکس جدا ندارند (فلگsandboxبرایشان بیاثر است).⛔ سامان و ملت sandbox واقعی ندارند و
sandbox=Trueرا رد میکنند (خطایGatewayConfigurationError). اگرsandboxسراسری راTrueگذاشتهای، برای این دو صریحاً"sandbox": Falseبگذار.اگر اپ
django_iranian_payment.contrib.djangoدرINSTALLED_APPSباشد، این تناقض با یک Django system check در startup گرفته میشود:manage.py runserver(و هرmanage.py) با خطایiranian_payment.E001اجرا نمیشود تا اصلاحش کنی — نه اینکه بیصدا به live وصل شوی.حالت مدیریت دستی DB (اپ کامل را نصب نمیکنی): این system check اجرا نمیشود و
runserverبیصدا بالا میآید (خطا فقط هنگام اولینget_gateway). برای گارد startup اپ سبک زیر را اضافه کن — بدون model و migration، فقط همین چک:INSTALLED_APPS = [ # ... "django_iranian_payment.contrib.guard", ]
راهنمای کامل هر درگاه
برای هر درگاه یک راهنمای گامبهگام و خودکفا (هر دو حالت مدیریت دیتابیس) در
docs/gateways/ هست:
زرینپال ·
زیبال ·
ملت ·
سامان ·
ایرانکیش ·
نکستپی ·
سداد ·
دیجیپی.
توجه: درگاههای تجربی (ایرانکیش، نکستپی، سداد، دیجیپی) در registry عمومی نیستند. برای استفادهشان با
get_gatewayو لایهی Django باید یکبار صریحاً register شوند (روش بالا را ببین). درگاههای registry عمومی (زرینپال، زیبال، ملت، سامان) به این ثبت نیاز ندارند و مستقیم باget_gateway("slug")در دسترساند.
برای استفاده از لایهی ساده (مدل و ردیابی خودکار)، اپ را هم اضافه کن:
INSTALLED_APPS = [
# ...
"django_iranian_payment.contrib.django",
]
سپس migrate:
python manage.py migrate
و مسیرهای داخلی را در urls.py پروژه اضافه کن:
from django.urls import path, include
urlpatterns = [
# ...
path("payment/", include("django_iranian_payment.contrib.django.urls")),
]
روش ۱: لایهی ساده (توصیهشده)
پکیج خودش رکورد میسازد، authority را ذخیره میکند، و تأیید را انجام میدهد.
شروع پرداخت
from django.shortcuts import redirect
from django_iranian_payment.contrib.django import services
def start_payment(request, order):
payment, redirect_url = services.start_payment(
"zarinpal",
amount=order.amount, # ریال
# به بانک میرود: مسیر callback خود پکیج (نه view خودت)
callback_url="https://yoursite.com/payment/callback/zarinpal/",
# کاربر پس از verify به اینجا میرود (صفحهی نتیجهی خودت)
return_url="https://yoursite.com/orders/result/",
order_id=str(order.id),
description="پرداخت سفارش",
mobile="09120000000", # اختیاری
)
# نیازی به ذخیرهی authority نیست؛ پکیج خودش نگه میدارد.
# ملت و سامان فرم POST میخواهند: بهجای redirect_url، کاربر را به
# reverse("iranian_payment:go-to-gateway", kwargs={"payment_id": payment.id}) بفرست.
return redirect(redirect_url)
بازگشت از بانک (callback)
اگر مسیرهای داخلی را در urls.py اضافه کرده باشی، هیچ view ای برای callback لازم نیست.
بانک به callback_url (مسیر /payment/callback/zarinpal/ پکیج) برمیگردد، پکیج خودش
تأیید میکند و کاربر را به return_url با پارامتر payment_status
(success، failed یا pending) هدایت میکند:
https://yoursite.com/orders/result/?payment_status=success&order_id=123
callback_urlوreturn_urlدو آدرس جدا هستند: اولی به بانک میرود و باید مسیر callback پکیج باشد؛ دومی صفحهی نتیجهی سایت توست. (تا نسخهی1.2.4کاربر پس از verify بهcallback_url— یعنی دوباره به خود callback — برگردانده میشد و ۴۰۴ میگرفت.)- بهجای
return_urlدر هر فراخوانی میتوانی یکبارIRANIAN_PAYMENT["return_url"]بگذاری. اگر هیچکدام نباشد، صفحهی نتیجهی سادهی پکیج نمایش داده میشود (iranian_payment/result.html؛ درtemplates/پروژه overrideپذیر است). - view callback پکیج
csrf_exemptاست، چون ملت و سامان با POST از دامنهی بانک برمیگردند و توکن CSRF ندارند. اگر callback را با view خودت مینویسی (روش ۲)، آن را هم@csrf_exemptکن، وگرنهCsrfViewMiddlewareپاسخ ۴۰۳ میدهد. - template های پکیج (فرم POST ملت/سامان و صفحهی نتیجه) با
"APP_DIRS": TrueدرTEMPLATESپیدا میشوند (پیشفرض پروژهی Django). - جلوگیری از double spending: سامان یک رسید (
RefNum) را هر بار که ارائه شود دوباره تأیید میکند و ملت verify تکراری را «قبلاً تأیید شده» = موفق برمیگرداند، پس خریدار میتوانست رسید یک خرید را روی callback سفارش دیگری بفرستد. پکیج رسید را پیش از verify در فیلد یکتایreceipt_keyبه همان پرداخت میدهد و callbackی را که رسیدش مال پرداخت دیگری است (یا در ملتSaleOrderIdسفارش دیگری را نشان میدهد) بدون تماس با بانک رد میکند؛ این در log با سطح warning ثبت میشود.
اگر میخواهی منطق خودت را اجرا کنی، بهجای استفاده از callback داخلی، خودت
services.verify_payment(slug, authority) را صدا بزن.
تأیید مجدد تراکنشهای ناتمام (درگاه در دسترس نبود)
گاهی کاربر از بانک برمیگردد ولی درگاه هنگام verify بیپاسخ میدهد یا ۵۰۰ برمیگرداند (بیثباتی زرینپال و ...). پول ممکن است از کاربر کم شده باشد ولی تأیید نشده. این حالت خودکار مدیریت میشود:
- در callback، پیش از تماس با بانک، رکورد به
RETURN_FROM_BANKعلامت میخورد. اگر verify با خطای شبکه شکست بخورد، رکورد در همین حالت میماند (نه گم میشود، نهexpire_staleمنقضیاش میکند) و کاربر باpayment_status=pendingبرمیگردد. - یک job دورهای این رکوردها را دوباره verify میکند. verify دوباره امن است:
زرینپال کد ۱۰۱ (قبلاً تأیید شده) را
DUPLICATE= موفق برمیگرداند. - پاسخ قطعی بانک رکورد را پایانی میکند: موفق →
COMPLETE، ناموفق →FAILED، لغو کاربر →CANCEL_BY_USER. فقط رکوردی که verify آن به خطای شبکه خوردهRETURN_FROM_BANKمیماند، پس job دورهای پرداختهای ناموفق را دوباره امتحان نمیکند.
در cron یا celery اجرا کن:
from django_iranian_payment.contrib.django import services
services.reverify_pending() # رکوردهای returnedِ ناتمام را دوباره verify میزند
services.expire_stale(older_than_minutes=30) # رکوردهای خیلی قدیمیِ نرفته به درگاه
reverify_pendingخودش خطای در دسترس نبودن درگاه را میبلعد (رکورد returned میماند و اجرای بعدی دوباره تلاش میکند)، وextraلازم برای verify درگاههای شاپرکی (ملت و ...) را ازrawرکورد بازمیخواند. خطای دیگر یک رکورد (مثلاً تنظیمات درگاهش از settings حذف شده) log میشود و اجرا با رکورد بعدی ادامه مییابد.older_than_minutesرا روی ۳۰+ بگذار تا رکورد معلق فرصت reverify داشته باشد پیش از انقضا.
حالت مدیریت دستی دیتابیس (روش ۲): همین منطق را باید خودت پیاده کنی — نمونهی کامل
(callback با payment_status=pending + reverify_pending + management command) در
scripts/django_custom_db.py.
وضعیتهای رکورد
WAITING → REDIRECT_TO_BANK → RETURN_FROM_BANK → COMPLETE / FAILED / CANCEL_BY_USER
حالتهای پایانی: COMPLETE، FAILED (بانک verify را رد کرد)، CANCEL_BY_USER و
EXPIRE_GATEWAY_TOKEN (با expire_stale). RETURN_FROM_BANK یعنی «هنوز پاسخ قطعی
نگرفته» (مثلاً درگاه هنگام verify در دسترس نبود). رکورد FAILED با callback معتبرِ
بعدی هنوز میتواند COMPLETE شود؛ فقط COMPLETE جلوی verify دوباره را میگیرد.
روش ۲: لایهی هسته (بدون مدل، پیشرفته)
اگر نمیخواهی مدل و migration داشته باشی (یا خارج از Django کار میکنی)، مستقیم از
هسته استفاده کن. در این حالت خودت باید authority را ذخیره کنی.
from django_iranian_payment import get_gateway, PaymentRequest
def start_payment(request, order):
gw = get_gateway("zarinpal")
result = gw.initiate(PaymentRequest(
amount=order.amount, # ریال
callback_url="https://yoursite.com/verify/",
order_id=str(order.id),
description="پرداخت سفارش",
))
order.authority = result.authority
order.amount_sent = result.amount_to_send # این را برای verify ذخیره کن
order.save()
return redirect(result.redirect_url)
def verify_payment(request):
authority = request.GET.get("Authority")
order = Order.objects.get(authority=authority)
gw = get_gateway("zarinpal")
result = gw.verify(
authority=authority,
amount=order.amount_sent, # همان مبلغی که در initiate رفت (با کارمزد)
order_id=str(order.id),
)
if result.is_success:
order.mark_paid(result.reference_id)
return redirect("/success/")
return redirect("/failure/")
چرا
amount_sentو نهamount؟ بعضی درگاهها (زرینپال، پیپینگ) مبلغ را در callback برنمیگردانند و verify به همان مبلغ ارسالی نیاز دارد. اگر کارمزد روی مشتری اعمال شده باشد، مبلغ ارسالی با مبلغ پایه فرق دارد.
درگاههایی که در verify به دادهی بیشتری نیاز دارند (پارامتر extra)
برخی درگاههای شاپرکی در verify به دادهای فراتر از یک authority نیاز دارند که
در callbackِ POST از بانک برمیگردد. این دادهها از طریق extra پاس داده میشوند.
اگر از لایهی Django استفاده کنی، viewهای پکیج این مقادیر را خودکار از callback
استخراج و پاس میدهند.
# ملت (registry عمومی): به sale_reference_id و sale_order_id نیاز دارد.
# اگر کاربر کنسل کرده باشد، res_code از callback (=17) را هم بده تا بدون
# تماس SOAP وضعیت CANCELLED برگردد.
gw.verify(authority=ref_id, amount=amount, order_id=oid,
extra={"sale_reference_id": "...", "sale_order_id": "...",
"card_number": "...", "res_code": "0"})
# سامان (registry عمومی): به RefNum نیاز دارد
gw.verify(authority=token, amount=amount, order_id=oid,
extra={"ref_num": "...", "state": "OK"})
# ایرانکیش (تجربی): به token و reference_id نیاز دارد
gw.verify(authority=token, amount=amount, order_id=oid,
extra={"token": "...", "reference_id": "...", "result_code": "100"})
# دیجیپی (تجربی): به trackingCode نیاز دارد (providerId همان order_id است)
gw.verify(authority=ticket, amount=amount, order_id=oid,
extra={"tracking_code": "...", "result": "SUCCESS"})
⚠️ double spending (سامان و ملت): بانک رسید تکراری را دوباره تأیید میکند، پس در این لایه یکتایی رسید با توست (مستند سامان ۳.۶، ص ۱۴–۱۵). پیش از
verifyرسید (SamanGateway.receipt_from_extra(extra)/MellatGateway.receipt_from_extra(extra)) را در یک فیلد unique به همان سفارش بده و اگر مال سفارش دیگری بود verify نزن؛ در ملتMellatGateway.order_id_from_extra(extra)هم باید همان سفارش خودت باشد. نمونهی کامل:scripts/django_custom_db.py.
درگاههای ساده (زرینپال، زیبال) پارامتر extra را نادیده میگیرند.
کارمزد
کارمزد بهصورت اختیاری روی هر پرداخت قابل تنظیم است. میتوانی تعیین کنی کارمزد به مبلغ اضافه شود (مشتری بپردازد) یا نشود (پذیرنده بپردازد، مبلغ بانک تغییر نکند).
from django_iranian_payment import FeeConfig, FeePayer
fee = FeeConfig(
rate_bps=200, # ۲٪ (۲۰۰ واحد پایه). ۱٪ = ۱۰۰
fixed=0, # کارمزد ثابت به ریال (اختیاری)
who_pays=FeePayer.CUSTOMER, # CUSTOMER: به مبلغ اضافه میشود | MERCHANT: نمیشود
max_fee=None, # سقف کارمزد به ریال (اختیاری)
)
# لایهی ساده:
services.start_payment("zarinpal", amount=100_000, callback_url="...",
order_id="1", fee=fee)
# لایهی هسته:
PaymentRequest(amount=100_000, callback_url="...", order_id="1", fee=fee)
نکات کارمزد:
- نرخ به bps (واحد پایه) است تا محاسبهی پول با float انجام نشود (۲٪ = ۲۰۰).
- کارمزد همیشه رو به بالا گرد میشود تا پذیرنده کسری ضرر نکند.
- اگر
who_pays=CUSTOMER، مبلغ ارسالی به بانک = مبلغ پایه + کارمزد. همین مبلغ در verify هم استفاده میشود (در لایهی ساده خودکار است). - اگر
who_pays=MERCHANT، مبلغ بانک تغییر نمیکند و کارمزد فقط برای گزارش/حسابداری محاسبه میشود.
افزودن درگاه جدید / عمومیکردن یک درگاه تجربی
درگاههای core.experimental پس از تست با اطلاعات و ترمینال واقعی، با انتقال فایل
به core/gateways/ و افزودن یک خط به core/gateways/__init__.py عمومی میشوند. تا
آن لحظه با get_gateway در دسترس نیستند و فقط با import صریح قابل دسترسیاند:
from django_iranian_payment.core.experimental.sadad import SadadGateway
نمونهی واقعی این ارتقا: ملت و سامان که پس از تست تراکنش واقعی از
core.experimental به core.gateways منتقل شدند و حالا با get_gateway("mellat") /
get_gateway("saman") در دسترساند.
تاریخچهی تغییرات درگاهها
-
نسخهی
1.3.0— یک باگ امنیتی و چهار باگ لایهی Django (همه تست رگرسیون دارند). پس از ارتقاpython manage.py migrateلازم است (migration0002: حالتFAILEDو فیلدreturn_url؛0003: فیلد یکتایreceipt_keyو ثبت رسید پرداختهای قبلی).- امنیتی — double spending: رسید یک پرداخت سامان (
RefNum) یا ملت (SaleReferenceId) روی callback سفارش پرداختنشدهی دیگری آن را همCOMPLETEمیکرد (سامان رسید تکراری را دوباره تأیید میکند؛ ملت کد ۴۳ را موفق میدهد و مبلغ را چک نمیکند). حالا رسید پیش از verify درreceipt_keyیکتا ثبت میشود و در ملتSaleOrderIdباید همان سفارش باشد. migration رسید پرداختهای کاملشدهی قبلی را هم ثبت میکند و رسیدی را که پیشتر دو بار مصرف شده در log گزارش میدهد. - view callback
csrf_exemptنبود؛ ملت و سامان که با POST برمیگردند در پروژهای باCsrfViewMiddlewareخطای ۴۰۳ میگرفتند. - template ها (
post_redirect.html) در wheel نبودند چون package-data تعریف نشده بود؛go_to_gatewayملت/سامان در نصب از PyPITemplateDoesNotExistمیداد. callback_urlهم به بانک میرفت و هم مقصد redirect پس از verify بود، پس کاربر دوباره به خود callback برمیگشت (۴۰۴). پارامتر جدیدreturn_urlدرstart_paymentو کلید سراسریIRANIAN_PAYMENT["return_url"]؛ بدون هیچکدام صفحهی نتیجهی پکیج.- verify ناموفق رکورد را
RETURN_FROM_BANKنگه میداشت وreverify_pendingتا ابد دوباره امتحانش میکرد؛ کنسل ملت و State غیر OK سامان حتیreverify_pendingرا در هر اجرا باGatewayPaymentErrorمیشکستند. حالا پاسخ قطعی پایانی است (FAILED/CANCEL_BY_USER) و خطای یک رکورد بقیهی reverify را متوقف نمیکند. رکوردهای ناموفقِ قدیمی کهRETURN_FROM_BANKماندهاند در اولینreverify_pendingپس از ارتقا یکبار دیگر verify و نهایی میشوند.
- امنیتی — double spending: رسید یک پرداخت سامان (
-
نسخهی
0.7.0— انتخاب واحد پول (ریال/تومان). کلید سراسریIRANIAN_PAYMENT["currency"]("rial"پیشفرض یا"toman") اضافه شد. کاربر میتواند مبلغها را به تومان بدهد و پکیج خودکار به ریال (واحد بانک) تبدیل میکند (۱ تومان = ۱۰ ریال). تبدیل درPaymentRequest.resolve_amount()انجام میشود، پس هیچ درگاهی نیاز به تغییر نداشت. در لایهی هستهPaymentRequest(currency=...)و در settings کلیدcurrency. سازگار با قبل (پیشفرض ریال = بدون تبدیل). مبالغ ذخیره/بازگشتی همیشه ریالاند. تست:tests/test_currency.py. -
نسخهی
0.6.0— دو تغییر:- sandbox مجزای هر درگاه: کلید
"sandbox"داخل config هر درگاه بر مقدار سراسری اولویت دارد؛get_gateway(..., sandbox=...)هم اضافه شد. حالا میتوان یک درگاه را live و دیگری را sandbox داشت همزمان (سازگار با قبل: فقطsandboxسراسری مثل قبل کار میکند). - رفع باگ callback لایهی Django برای درگاههای شاپرکی: پیشتر view callback
پکیج رکورد را فقط با چند نام پارامتر محدود پیدا میکرد و برای نکستپی
(
trans_id)، سداد (Token)، سامان (ResNum— توکن در callback نیست) و دیجیپی (providerId— ticket در callback نیست) رکورد را نمییافت و ۴۰۴ میداد. اکنون یک جدول مشخصات (_CALLBACK_SPEC) درcontrib/django/views.pyبرای هر درگاه نام پارامتر درست و کلید پیدا کردن رکورد (authority یا order_id) را تعریف میکند. ملت همچنان باRefId(authority یکتا) پیدا میشود تا رفتار live تستشدهاش دستنخورده بماند. - راهنمای کامل هر درگاه برای هر دو حالت مدیریت دیتابیس در
docs/gateways/افزوده شد.
- sandbox مجزای هر درگاه: کلید
-
۱۴۰۵/۰۴/۰۳ (نسخهی
0.5.0) — درگاه ملت پس از تست تراکنش واقعی روی محیط عملیاتی (bpm.shaparak.ir) ازcore.experimentalبه registry عمومی منتقل شد و حالا باget_gateway("mellat")در دسترس است. در جریان تست live دو باگ کشف و رفع شد (هر دو تست رگرسیون دارند):- کنسل کاربر (
ResCode=17بدونSaleReferenceId) باعث crash میشد؛ حالا verify ابتداres_codeاز callback را بررسی و بدون تماس SOAP،CANCELLED/FAILEDبرمیگرداند. CardHolderPanاز callback ذخیره نمیشد. اکنون شمارهی کارت ماسکشده درcard_numberو مقادیرsale_order_id/sale_reference_id/final_amountدرraw(مدل Payment) ذخیره میشوند تا برایsettle/reverseبعدی در دسترس باشند.
- کنسل کاربر (
-
۱۴۰۵/۰۳/۲۷ — درگاههای مستقل بانک تجارت و بانک ملی حذف شدند. این بانکها درگاه مستقل خود را کنار گذاشتهاند و پرداخت را به PSPهای دیگر سپردهاند:
- بانک تجارت → ایرانکیش: از درگاه بانک تجارت به ایرانکیش هدایت
میشوید. بهجای درگاه تجارت از
IrankishGatewayاستفاده کنید. - بانک ملی → سداد: از سایت بانک ملی به درگاه سداد هدایت میشوید. بهجای
درگاه ملی از
SadadGatewayاستفاده کنید.
اسکلتهای
tejarat.pyوmelli.py(که هرگز پیادهسازی واقعی نداشتند و فقطTODOبودند) از پروژه حذف شدند تا فایل اضافی نماند. اگر روزی این بانکها درگاه مستقل خود را دوباره راهاندازی کنند، در نسخههای بعدی دوباره افزوده خواهند شد. - بانک تجارت → ایرانکیش: از درگاه بانک تجارت به ایرانکیش هدایت
میشوید. بهجای درگاه تجارت از
لایسنس
MIT
Metadata
Release files for django-iranian-payment 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django_iranian_payment-1.3.0.tar.gz | 104.6 kB | Without provenance | Details for django_iranian_payment-1.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_iranian_payment-1.3.0-py3-none-any.whl | Python 3 | none | any | Details for django_iranian_payment-1.3.0-py3-none-any.whl |
Total release size: 191.9 kB
Release files / django_iranian_payment-1.3.0.tar.gz
| Download URL | django_iranian_payment-1.3.0.tar.gz |
|---|---|
| Size | 104.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2e4e3661f73d4b79abac2379d9cabef43429b30f77b8c441a8a87cf7fd53e53b
|
|
BLAKE2b-256 checksum How to use checksums |
75b97d9ef096bb15912a4a02314fb814da5d0b86567b0d2d6ed9941d2bd2aa52
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.13
|
Release files / django_iranian_payment-1.3.0-py3-none-any.whl
| Download URL | django_iranian_payment-1.3.0-py3-none-any.whl |
|---|---|
| Size | 87.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b2f6603e16f216036867308b11598b53aad0107756c1bca9a9ee3113ca2494de
|
|
BLAKE2b-256 checksum How to use checksums |
469c5f10ed1b62d0718d832e26fbc0a21908be37df886e53015ed57d00883662
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.13
|
Security for django-iranian-payment 1.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.