مسئله: پول جابهجا کن و هرگز اشتباه نکن. اینجا اولویتها برعکس فید است: درستی > دسترسپذیری > تأخیر. هر تصمیم حول یک سؤال: «اگر وسط کار قطع شود چه؟»
۱-۳) نیازمندی و طرح
- FR: پرداخت با درگاه/کارت، کیف پول، برگشت وجه، صورتحساب و مغایرتگیری.
- NFR: صفر گمشدگی/دوبارهکاری؛ audit کامل؛ سازگاری Strong روی مانده.
- هسته: Ledger (دفتر کل) append-only با ثبت دوطرفه — هر تراکنش دو خط: بدهکار A، بستانکار B؛ جمع همیشه صفر. مانده = تجمیع (با snapshot) — نه یک عدد قابل UPDATE.
DIAGRAMپرداخت با درگاه بیرونی
Client
→
Payment svc — state machine
→
PSP / بانک
→
Ledger + Outbox
→
Webhook نتیجه
۴) عمق — جایی که سیستمها میسوزند
- Idempotency همهجا: کلید از کلاینت تا PSP عبور میکند؛ retry هرگز پرداخت دوم نمیسازد.
- State machine صریح: initiated → pending → succeeded/failed؛ هیچ گذاری ضمنی نیست؛ timeout یعنی «نامعلوم» نه «شکست».
- حالت نامعلوم: پاسخ PSP نیامد — تراکنش pending میماند و job مغایرتگیر با استعلام وضعیت (query API) تکلیفش را روشن میکند. حدس ممنوع.
- Webhook های PSP: تکراری و بیترتیب میآیند — با event_id ،idempotent مصرف کن و امضایشان را چک کن.
- مغایرتگیری (Reconciliation) روزانه با فایل بانک: هر اختلاف = هشدار. سیستم پرداختِ بدون reconciliation، بمب ساعتی است.
- دقت اعشار: پول را integer در خردترین واحد (ریال/سنت) نگه دار — float هرگز.
به زبان ساده
سیستم پرداخت یعنی: هیچ پولی نه دوبار برود نه گم شود؛ هر عملیات idempotent، هر وضعیت در state machine و هر رویداد در دفتر (ledger) ثبت است.
مثال واقعی
اگر درگاه پاسخ ندهد، نه «ناموفق» است نه «موفق» — وضعیت pending میماند تا استعلام (reconciliation) تکلیفش را روشن کند؛ پول حدس زده نمیشود.