هر پلتفرم درآمدزایی API یک شکاف صورتحساب دارد: فاصله بین لحظهای که سرویس تحویل داده میشود و لحظهای که همان مصرف به درآمد قابل پیگیری تبدیل میشود. در بیشتر معماریها، این شکاف باگ استقرار نیست؛ از طراحی سیستم میآید.
درگاه درخواست را سرو میکند. صف رویداد، مصرف را جای دیگری میبرد. سرویس صورتحساب رویدادها را جمع میکند. Stripe یا تیم حسابداری پول را وصول میکند. پایگاه داده داشبورد هم در نهایت وضعیت را نشان میدهد.
هر مرحله ناهمگام است. هر مرحله هم جداگانه میتواند خراب شود. وقتی تعداد درخواستها به میلیونها میرسد، همین «در نهایت» همان جایی است که درآمد گم میشود.
معماری رایج چه شکلی است؟
یک پلتفرم API مبتنی بر مصرف معمولاً چنین مسیری دارد:
کلاینت → درگاه API → ارائهدهنده بالادست
↓
رویداد مصرف (Kafka/SQS)
↓
پردازشگر صورتحساب → Stripe / صدور فاکتور
↓
تطبیق (شبانه)
وظیفه درگاه پروکسی کردن است. وظیفه صورتحساب هم شارژ کردن در مرحله بعد. این دو سیستم جدا هستند، هرکدام هم حالتهای خرابی خودشان را دارند. نقطه اتصال بین آنها همان جایی است که شکاف باز میشود.
علائم آشنا:
- درخواست موفق، شارژ گمشده. بالادست پاسخ 200 داده، اما رویداد مصرف هرگز نرسیده است؛ به خاطر فشار صف، پیام خراب، یا استقرار نیمهکاره وسط پردازش دستهای.
- تأخیر در صورتحساب. واحد مالی مصرف دیروز را امروز میبیند. پشتیبانی قبل از اینکه دفتر کل از آن فراخوانی خبر داشته باشد، باید اختلاف را حل کند.
- تطبیق بهعنوان بخشی از محصول. کار شبانه لاگ درگاه را با ردیفهای صورتحساب مقایسه میکند. اختلافها تبدیل به تیکت میشوند. هر مورد خاص هم زمان مهندسی میگیرد، هم حاشیه سود را کم میکند.
- مصرف اضافه بدون هشدار. بدون بررسی همزمان موجودی، مشتری میتواند قبل از شارژ کیف پول یا بررسی اعتبار، مصرف زیادی روی هم جمع کند. در پایان یا شما ضرر میکنید، یا با پیگیری فاکتورهای عقبافتاده اعتماد مشتری را از بین میبرید.
این شکاف میتواند از چند میلیثانیه تا چند دقیقه طول بکشد. در ۱۰ میلیون درخواست در ماه، حتی ۰٫۰۱٪ نرخ شکست یعنی ۱٬۰۰۰ فراخوانی بدون صورتحساب؛ تازه اگر اصلاً متوجه آنها بشوید.
یک سناریوی شکست مشخص
یک پروکسی LLM را در نظر بگیرید که توکنها را اندازهگیری میکند:
- توسعهدهنده
POST /v1/chatبا کلید API معتبر میفرستد. - درگاه به مدل بالادست فوروارد میکند. پاسخ: ۴٬۸۰۰ توکن خروجی.
- درگاه رویداد مصرف را در صف میگذارد.
- سرویس صورتحساب وسط بهروزرسانی از کار میافتد. پیام دوباره در صف قرار میگیرد، اما سیستم طوری طراحی نشده که همان رویداد را دوباره نشمارد.
- توسعهدهنده همان prompt را دوباره میفرستد. درگاه دوباره سرو میکند. دو پاسخ موفق، یک شارژ.
هزینه فراخوانی دوم را چه کسی میدهد؟ حاشیه شما.
حالا مقیاسپذیری خودکار، قطعی جزئی، و مشتریانی را تصور کنید که شبانهروز با اسکریپت درخواست میفرستند. شکاف صورتحساب یک مورد خاص نیست؛ نشت ساختاری است.
مدل فینو: شارژ در همان فرآیندی که پروکسی میکند
فینو صورتحساب را «مصرفکننده رویدادهای درگاه» نمیبیند. پروکسی و دفتر کل با هم یک ماشین حالت همگامشده میسازند. هر شارژ در مسیر همان درخواست، در دو مرحله ثبت میشود:
فیلتر درخواست
├─ احراز هویت مشتری
├─ انتخاب منبع پرداخت (سهمیه → کیف پول → اعتبار)
├─ محاسبه بدترین حالت هزینه از قوانین قیمت
└─ رزرو (Reserve) — 402 اگر موجودی کافی نباشد
│
فراخوانی بالادست (۱۰–۱۰۰۰ ms — بخش کند)
│
ثبت نهایی
├─ اندازهگیری مصرف واقعی (بایت، توکن، فراخوانی)
├─ محاسبه قیمت از بسته قیمتی مشتری
└─ نهاییسازی (Finalize) — بازگشت مازاد رزرو
│
پاسخ برگردانده میشود (ورودی پایدار Raft)
رزرو قبل از تماس با بالادست انجام میشود. اگر موجودی مشتری حتی بدترین حالت هزینه را پوشش ندهد، 402 Payment Required میگیرد و ارائهدهنده اصلاً فراخوانی نمیشود. در نتیجه هزینه inference را از جیب خودتان نمیدهید.
نهاییسازی بعد از اندازهگیری پاسخ واقعی انجام میشود. اگر ۰٫۰۱ دلار رزرو شده باشد و فراخوانی در نهایت ۰٫۰۰۳ دلار هزینه داشته باشد، مابهالتفاوت همانجا برگردانده میشود؛ بدون کار جداگانه برای «تعدیل» بعدی.
پایداری قبل از برگشت پاسخ HTTP در لاگ Raft نهایی میشود. مرحلهای به نام «شب تطبیق میکنیم» وجود ندارد؛ چون چیزی برای تطبیق باقی نمیماند: یا شارژ همراه درخواست ثبت شده، یا درخواست اصلاً سرو نشده است.
بازیابی پس از خرابی، بدون راهحل دستی
اگر گره بین رزرو و نهاییسازی از کار بیفتد، مبلغ رزرو شده با زمان انقضا در حافظه موقت میماند. کار پسزمینه آن را لغو میکند و مبلغ را برمیگرداند. با کلید یکتایی روی API، تکرار درخواست از سمت کلاینت امن است؛ دوباره شارژ نمیشود، چون همان کلید همان نتیجه ذخیرهشده را برمیگرداند.
این یک وصله موقت برای صورتحساب نیست. همان دفتر کلی است که با قاعدههای ثابت مالی کار میکند و واحد مالی انتظارش را دارد، فقط روی تکتک دستورها اعمال میشود.
چرا واحد مالی باید به معماری اهمیت دهد
بیشتر نوشتههای مهندسی در «ما سریعیم» تمام میشوند. اما برای مدیران، قابل توضیح بودن به همان اندازه مهم است:
- هر شارژ به یک ورودی ثبتشده در لاگ وصل است؛ نه به عددی که بعداً از روی لاگها بازسازی شده.
- شش نوع حساب دوطرفه پس از هر دستور توازن جبری خود را حفظ میکنند. دفترها از همان ابتدا متعادل میمانند، نه با صفحهگسترده ماه بعد.
- حساب دریافتنی و درآمد همزمان با پاسخ API ثبت میشوند. مدیر مالی وضعیت واقعی را همان لحظه میبیند.
وقتی ممیزی میپرسد «ثابت کن این مشتری برای فراخوانی X شارژ شده»، به ورودی دفتر کل همان درخواست اشاره میکنید؛ نه به اتصال ناقص بین لاگ سرور و خروجی Stripe.
چه چیزی برای پلتفرم شما عوض میشود
| قبل (سیستم جدا) | بعد (فینو) |
|---|---|
| درآمد از روی لاگ بازسازی میشود | درآمد در لحظه تحویل ثبت میشود |
| سهمیه اینجا اعمال میشود؛ پول جای دیگری صورتحساب میشود | سهمیه، کیف پول و اعتبار در یک ترتیب پرداخت مشترک |
| کار تطبیق شبانه | توازن پس از هر دستور |
| 402 با پلاگین سفارشی | 402 بومی — موجودی ناکافی قبل از بالادست |
همچنان برای شارژ کیف پول و فاکتور از Stripe استفاده میکنید. همچنان داشبورد و گزارش مالی دارید. تفاوت این است که درگاه مرجع صورتحساب است؛ نه منبعی که فقط مصرف را گزارش میدهد و امیدوارید صورتحساب بعداً خودش را برساند.
شکاف صورتحساب مشکل تنظیم عملکرد نیست. مشکل از این نگاه میآید که اول پروکسی کنیم و بعداً سراغ درآمد برویم. فینو این شکاف را با یک قاعده ساده میبندد: درخواست فقط وقتی سرو میشود که درآمد آن قابل پیگیری و قابل ثبت باشد؛ نه اینکه بعداً با کار پیگیری جبران شود.
یکبار مستقر کنید. درآمد را از همان مسیر درخواست ثبت کنید.