אימות קבלות בצד השרת (אימות רכישה)
לקוח דיווח על באג: משתמש רכש מנוי Premium, קיבל אסימון עסקה, שחזר את האפליקציה מגיבוי במכשיר אחר — ו-Premium היה פעיל שוב ללא תשלום. קלאסי. הסיבה היא אימות בצד הלקוח בלבד: האפליקציה בודקת קבלה מקומית או דגל אמון של StoreKit מבלי להתייעץ עם השרת. אנחנו צוות פיתוח מובייל עם ניסיון של למעלה מ-7 שנים ביישום רכישות בתוך האפליקציה. במהלך תקופה זו, יישמנו אימות בצד השרת עבור 30+ פרויקטים ולמדנו שבלעדיו, הפסדי הונאה יכולים להגיע ל-15% מההכנסות. החיסכון הממוצע ללקוחותינו נע בין 15% ל-20% מההכנסות — נתון משמעותי.
מהו אימות קבלות?
אימות קבלות הוא התהליך של בדיקת האותנטיות של קבלת רכישה בצד השרת באמצעות ממשקי API רשמיים של חנות האפליקציות. רק אימות בצד השרת מבטיח שרכישה אכן בוצעה ולא שונתה. בלעדיו, כל אפליקציה חשופה להתקפות.
מדוע אימות בצד הלקוח אינו מאובטח?
ב-iOS, StoreKit 2 מחזיר Transaction עם חתימת Apple. ניתן לאמת את החתימה מקומית באמצעות Transaction.verificationResult, אך זה לא מגן מפני התקפות replay: תוקף מיירט קבלה תקפה ממשתמש אחד ומחליף אותה בחשבון אחר. המצב דומה ב-Android — BillingClient.queryPurchasesAsync() מחזיר אובייקטים מסוג Purchase שהלקוח לא צריך להתייחס אליהם כאישור ללא אימות בצד השרת של purchaseToken.
תוכנית ההונאה הנפוצה ביותר היא שיתוף קבלות: קבלה אחת מופצת בין משתמשים דרך פורומים. ללא מסד נתונים בצד השרת שמתעד אילו originalTransactionId (iOS) או orderId (Android) כבר נעשה בהם שימוש, לא ניתן לתפוס זאת.
כיצד להגן מפני שיתוף קבלות?
צור טבלת purchase_receipts עם אינדקס ייחודי על original_transaction_id ו-product_id. בכל אימות, בדוק אם הקבלה כבר נעשה בה שימוש על ידי משתמש אחר. אם כן, דחה את הבקשה. זה חוסם את התפשטות קבלה אחת על פני מספר חשבונות. בנוסף, השתמש בהודעות שרת כדי לעקוב אחר החזרים כספיים וביטולים.
מדוע הודעות שרת קריטיות?
App Store Server Notifications V2 ו-Real-time Developer Notifications מ-Google שולחות אירועים בזמן אמת: DID_RENEW, EXPIRED, REFUND, GRACE_PERIOD_EXPIRED. בלעדיהם, השרת שלך לומד על שינויים בסטטוס המנוי באיחור של עד 24 שעות, מה שמאפשר למשתמשים עם מנויים מבוטלים להמשיך לגשת לתוכן. הגדרת handler ל-webhook היא צעד חובה כדי להגן מפני נתונים מיושנים.
כיצד עובד אימות רגיל בצד השרת
צד iOS (App Store Server API). הגישה הישנה — POST אל https://buy.itunes.apple.com/verifyReceipt עם receipt-data מקודד ב-base64 — הוצאה משימוש. Apple מקדמת את App Store Server API v1: הלקוח שולח לשרת transactionId מ-Transaction.id (StoreKit 2), השרת מבצע GET /inApps/v1/history/{transactionId} עם אסימון JWT (חתום עם מפתח ES256 מ-App Store Connect). התשובה היא JWSTransaction שיש לפענח ולאמת את החתימה באמצעות Apple Root CA.
במקביל, יש להירשם ל-App Store Server Notifications V2: Apple דוחפת אירועים לנקודת הקצה שלך. פרטים נוספים ב-App Store Server Notifications V2.
צד Android (Google Play Developer API). לרכישות חד-פעמיות — purchases.products.get עם packageName, productId, purchaseToken. למנויים — purchases.subscriptions.v2.get. הרשאה באמצעות Service Account עם התפקיד Financial data viewer — זו ההרשאה המינימלית הנדרשת. התשובה מכילה purchaseState (0 = נרכש, 1 = בוטל, 2 = בהמתנה) ו-acknowledgementState — אם הוא 0, יש לקרוא ל-purchases.products.acknowledge, אחרת Google יחזיר כסף אוטומטית תוך 3 ימים. פרטים נוספים ב-Google Play Developer API.
Idempotency והגנה מפני replay. במסד הנתונים, שמור טבלת purchase_receipts עם אינדקס ייחודי על original_transaction_id + product_id. בכל בקשת אימות, בדוק תחילה אם קיים רשומה — אם כבר אומת עבור user_id שונה, החזר שגיאה.
purchase_receipts
id uuid PK
user_id uuid FK
platform enum('ios','android')
original_transaction_id varchar UNIQUE (per product)
product_id varchar
purchase_state smallint
expires_at timestamptz -- для подписок
raw_payload jsonb -- оригинальный ответ от Apple/Google
verified_at timestamptz
טכנולוגיות ואינטגרציה
הצד השרת הוא לרוב Node.js (ספריית purchase_receipts id uuid PK user_id uuid FK platform enum('ios','android') original_transaction_id varchar UNIQUE (per product) product_id varchar purchase_state smallint expires_at timestamptz -- для подписок raw_payload jsonb -- оригинальный ответ от Apple/Google verified_at timestamptz ) או Python (app-store-server-api + google-auth). עבור Node, החבילה googleapiclient נוחה לנקודת הקצה הישנה, אך עדיף להשתמש ב-node-apple-receipt-verify מ-Apple ישירות — היא תומכת בהרשאת JWT ואימות JWS ישירות מהקופסה.
בצד הלקוח, קוד iOS הוא מינימלי: קבל app-store-server-api מ-Transaction.id או מזרם Transaction.all, שלח אותו לשרת. אל תשלח את כל updates — זה legacy והקובץ עלול להיות לא תקין בסימולטור.
ב-Android, הלקוח שולח appStoreReceiptURL ו-purchaseToken מ-productId. חשוב: האסימון עשוי להיות זהה עבור מספר Purchase.purchaseToken במהלך שדרוגי מנוי — קח זאת בחשבון בלוגיקה שלך.
השוואה בין גישות iOS ו-Android
| קריטריון | iOS (App Store Server API) | Android (Google Play Developer API) |
|---|---|---|
| פרוטוקול | REST עם הרשאת JWT | REST עם OAuth2 (Service Account) |
| סוג מפתח | ES256 (App Store Connect) | מפתח JSON של Service Account |
| אימות חתימה | JWS (Apple Root CA) | תשובת API (HTTPS) |
| הודעות | Server Notifications V2 | Real-time Developer Notifications |
| הגנה מפני replay | productId ייחודי |
transactionId ייחודי |
השוואה בין אימות בצד הלקוח לאימות בצד השרת
| קריטריון | אימות בצד הלקוח | אימות בצד השרת |
|---|---|---|
| אבטחה | פגיע ל-replay ולשינויים | גבוהה, עם אימות בצד החנות |
| הגנה מפני שיתוף | בלתי אפשרית | חסומה על ידי ייחודיות עסקה |
| סנכרון סטטוס | מכשיר בלבד | כל המכשירים דרך השרת |
| הודעות שינוי | אין | Server Notifications |
תהליך העבודה
אנו מתחילים בסקר של תוכנית האימות הנוכחית — היכן בדיוק נבדקת הקבלה, האם קיים מסד נתונים של רכישות בצד השרת, והאם מעובדות Server Notifications. לאחר מכן אנו מתכננים את סכמת מסד הנתונים ונקודות קצה API, מיישמים אימות לכל פלטפורמה, מגדירים handler ל-webhook עבור Server Notifications, ומכסים בבדיקות באמצעות תשובות מדומות מ-Apple/Google. שלב נפרד הוא בדיקת עומסים של נקודת קצה האימות, כי במהלך השקות שיא (קידום, תכונה בראש App Store) היא מקבלת הכל בבת אחת.
מה כלול בעבודה
- סקר של תוכנית האימות הנוכחית ופגיעויות.
- תכנון מסד נתונים של רכישות ונקודות קצה API.
- יישום אימות עבור iOS (App Store Server API v1) ו-Android (Google Play Developer API).
- הגדרת Server Notifications (App Store Server Notifications V2 ו-Real-time Developer Notifications).
- כתיבת בדיקות עם תשובות מדומות מ-Apple/Google.
- בדיקות אינטגרציה ועומסים.
- תיעוד API והוראות למפתחים.
לוחות זמנים — בין 2 ל-5 ימים, תלוי בזמינות תשתית השרת ובמספר סוגי הרכישות (חד-פעמיות, מנויים, consumable, non-consumable). אם כבר קיים שרת ורק צריך להוסיף אימות — קרוב ל-2 ימים. ארכיטקטורה מלאה מאפס בתוספת העברת משתמשים קיימים — עד 5 ימים.
צור קשר כדי לדון בפרטי הפרויקט שלך. הזמן סקר של תוכנית האימות הנוכחית שלך — נזהה פגיעויות ונציע פתרון אופטימלי. קבל ייעוץ על הפרויקט שלך — נעריך לוחות זמנים ונציע את הפתרון הטוב ביותר.







