اتصال درگاه پرداخت اینترنتی بهصورت قابلتعویض
6 دقیقه مطالعه

یک واسط داخلی برای provider پرداخت تعریف کنید، مبلغ را از دیتابیس verify کنید، verify تکراری را درست مدیریت کنید و تطبیق مالی بسازید؛ نمونه از مستند رسمی زرینپال.
اتصال مستقیم کد فروشگاه به یک درگاه پرداخت سریعترین راه برای فروش اولین سفارش است، و پرهزینهترین راه برای روزی که باید درگاه را عوض کنید، یا یک درگاه دوم برای پشتیبان اضافه کنید، یا تراکنشی را که «پولش کم شد ولی سفارش ثبت نشد» پیگیری کنید. این مقاله یک قرارداد داخلی کوچک برای «ارائهدهندهٔ پرداخت» پیشنهاد میدهد و نشان میدهد نمونهٔ واقعی (مستندات رسمی زرینپال) چطور در آن مینشیند.
چند مرز صریح: ما درگاه خاصی را توصیه نمیکنیم و زرینپال فقط بهعنوان نمونهٔ یک جریان مستند آمده است؛ نام پارامترها و آدرسها را از مستند روز همان درگاه بگیرید. دربارهٔ الزامات قراردادی، مجوز، مالیات و حقوقی درگاهها هیچ ادعایی نمیکنیم. بخشهایی که «توصیهٔ نویسنده» نامیدهایم، تجربهٔ مهندسی عمومیاند و منبع مستقل ندارند.
جریان رایج: درخواست، ریدایرکت، بازگشت، تأیید
مستند زرینپال جریان را چنین میگوید: ابتدا پذیرنده از سرور خود درخواست پرداخت میفرستد و code و authority میگیرد؛ سپس خریدار را به آدرس StartPay همراه authority هدایت میکند؛ پس از پایان کار، درگاه خریدار را به callback_url بازمیگرداند و یک Status با مقدار OK یا NOK در QueryString میآید. NOK یعنی تراکنش ناموفق بوده یا خریدار لغو کرده است و متد verify فقط وقتی فراخوانی میشود که Status برابر OK باشد.
این الگوی «درخواست سمت سرور، ریدایرکت مرورگر، بازگشت مرورگر، تأیید سمت سرور» در بسیاری از درگاهها مشابه است، هرچند جزئیات فرق دارد. نکتهٔ مهم برای معماری: بازگشت مرورگر مدرک پرداخت نیست؛ تأیید سمت سرور مدرک است.
قرارداد داخلی provider
توصیهٔ نویسنده: کد فروشگاه نباید نام authority یا StartPay را بشناسد. یک واسط داخلی تعریف کنید که چهار کار انجام دهد:
| عملیات داخلی | ورودی | خروجی | نگاشت در نمونهٔ زرینپال |
|---|---|---|---|
start(order) | شناسهٔ سفارش، مبلغ، آدرس بازگشت | شناسهٔ مرجع پرداخت + آدرس ریدایرکت | درخواست پرداخت ← authority + آدرس StartPay |
parse_callback(request) | درخواست بازگشت مرورگر | شناسهٔ مرجع + نتیجهٔ اولیه (موفق/ناموفق/لغو) | Authority و Status از QueryString |
verify(ref, amount) | شناسهٔ مرجع، مبلغ مورد انتظار | نتیجهٔ نهایی، شمارهٔ پیگیری، نشانگر «قبلاً تأیید شده» | verify ← code 100 / 101 + ref_id |
inquire(ref) | شناسهٔ مرجع | وضعیت فعلی | طبق مستند همان درگاه؛ ما بررسی نکردیم |
خروجی را به وضعیتهای خودتان نگاشت کنید (paid، failed، canceled، pending، already_verified) تا بقیهٔ سیستم هرگز کد خطای خام درگاه را نبیند. وضعیتهای سفارش و پرداخت را هم با ماشین حالت جدا نگه دارید.

تأیید سمت سرور: مبلغ را از دیتابیس بخوانید
مستند SDK زرینپال تأکید میکند که amount در verify باید با مبلغ اصلی تراکنش مطابقت داشته باشد و از دیتابیس استخراج شود، نه از ورودی کاربر. دلیلش روشن است: کسی که پارامترهای بازگشت را دستکاری میکند نباید بتواند مبلغ کمتر را «تأیید» کند. پارامترهای verify شامل merchant_id، amount (به ریال) و authority است و پاسخ code، ref_id، card_pan (ماسکشده)، card_hash و fee را دربرمیگیرد.
پس رفتار صحیح این است:
- سفارش را با شناسهٔ مرجع پرداخت از دیتابیس پیدا کنید.
- مبلغ مورد انتظار را از همان رکورد بخوانید.
- verify را با آن مبلغ صدا بزنید.
- فقط در صورت نتیجهٔ موفق، سفارش را تأیید کنید و شمارهٔ پیگیری (
ref_id) را ذخیره کنید.
verify تکراری: کد ۱۰۱ را خطا نگیرید
در مستند زرینپال، با verify موفق فقط یک بار code=100 برمیگردد و در دفعات بعدی همان تراکنش code=101 میآید که یعنی «موفق و قبلاً verify شده». اگر کد شما ۱۰۱ را خطا بگیرد، کاربری که صفحهٔ بازگشت را رفرش میکند پیام خطا میبیند، در حالی که پولش کسر شده است. اگر ۱۰۱ را موفقیت جدید بگیرد، ممکن است سفارش را دوباره پردازش کنید و موجودی را دوبار کم کنید. راه درست: ۱۰۱ را «موفق، بدون اثر جدید» بنگارید و وضعیت سفارش را از دیتابیس خودتان بخوانید.
همین منطق در Stripe هم هست: کلید idempotency اجازه میدهد درخواست پس از خطای اتصال بیخطر تکرار شود و پاسخ اولین درخواست برمیگردد. در کد خودتان عملیات «تأیید سفارش» را با شرط وضعیت انجام دهید: فقط اگر سفارش الان در انتظار پرداخت است، به پرداختشده تغییر کند، در همان تراکنش دیتابیس.
تعویض provider در عمل
چه چیزهایی باید برای تعویض یا افزودن درگاه دوم آماده باشد (توصیهٔ نویسنده):
- جدول پرداختها با ستون
providerوprovider_reference، تا هر تراکنش به درگاهش وصل باشد. - پیکربندی هر provider (کلیدها، آدرسها) جدا از کد و قابلتغییر بدون انتشار.
- تست قرارداد: یک مجموعه تست مشترک که هر provider باید پاس کند (شروع، بازگشت موفق، بازگشت لغو، verify تکراری، مبلغ ناهماهنگ).
- نمونهٔ مستند زرینپال برای تست از sandbox استفاده میکند؛ هر درگاه را در محیط آزمایشی خودش تست کنید، اگر دارد.
- سوئیچ آگاهانه: انتخاب provider برای هر سفارش در لحظهٔ
startثبت شود تا سفارشهای در جریان با درگاه قبلی تمام شوند.
تطبیق مالی
ref_id، یعنی شمارهٔ پیگیری درگاه، به نوشتهٔ مستند برای پیگیریهای مالی است؛ آن را کنار سفارش ثبت کنید. توصیهٔ نویسنده: یک گزارش دورهای بسازید که سفارشهای پرداختشدهٔ شما را با گزارش تسویهٔ درگاه مقایسه کند و تفاوتها (پرداختشده بدون سفارش، سفارش بدون پرداخت، مبلغ نامساوی) را نشان دهد. جزئیات تسویه و کارمزد مطابق قرارداد شما با درگاه است و این مقاله بررسی نکرده است. برای پایش و هشدار روی خطاهای verify، Monitoring چیست؟ را ببینید.
سناریوهای شکست برای تست
- کاربر پس از پرداخت مرورگر را میبندد و callback هرگز نمیرسد. (راهحل: کار دورهای که سفارشهای در انتظار را استعلام میکند، اگر درگاه استعلام دارد.)
- callback دوبار میرسد.
- Status=NOK یا مقدار نامعتبر.
- authority ناشناخته یا قبلاً مصرفشده.
- verify با timeout روبهرو میشود. (نتیجه ناشناخته است؛ سفارش را «نامشخص» علامت بزنید و دوباره استعلام کنید، نه «ناموفق».)
- مبلغ سفارش بین شروع و تأیید تغییر کرده است.
چکلیست
- کد فروشگاه فقط با واسط داخلی کار میکند.
- بازگشت مرورگر بهتنهایی سفارش را تأیید نمیکند.
- مبلغ verify از دیتابیس میآید.
- verify تکراری (مثل ۱۰۱) بیاثر و بیخطاست.
- هر پرداخت provider و شمارهٔ مرجع دارد.
- تستهای قرارداد برای هر provider وجود دارد.
- تطبیق دورهای با گزارش درگاه و هشدار تفاوت.
- الزامات حقوقی و قراردادی از منبع رسمی روز بررسی شده، نه از این مقاله.
برای تصمیمهای معماری بزرگتر، انتخاب معماری اپلیکیشن وب را ببینید.
منابع و مراجع
- راهنمای اتصال به درگاه اینترنتی زرینپال — ZarinPal (مستندات رسمی).
- درگاه پرداخت (اطلاعات فنی سیستم) — ZarinPal (مستندات رسمی).
- تأیید پرداخت (Payment Verification) — SDK Node.js — ZarinPal (مستندات رسمی).
- Idempotent requests (Stripe API) — Stripe.
نویسنده
سا
سهیل ابراهیمپور بنیانگذار FutureForge است. روی طراحی محصول، معماری و استقرار نرمافزار سفارشی کار میکند.
یادداشتهای مرتبط
برای انتخاب مسیر فنی پروژهتان کمک میخواهید؟
از مسئله و محدودیتهایتان بگویید؛ اگر بتوانیم همراهیتان کنیم، برای گفتوگو هماهنگ میکنیم.




