רכישות חד-פעמיות שאינן מתכלות (Non-consumable IAP) הן קטגוריה שבה כל טעות בלוגיקת שחזור הרכישה הופכת לתלונה ב-App Store ולהחזר כספי. משתמש רכש "מצב בלתי מוגבל" או "הסרת פרסומות", התקין מחדש את האפליקציה, ולא קיבל את מה ששילם עליו. תמיכת אפל לא תעזור: שחזור רכישות שאינן מתכלות הוא באחריות המפתח. חמש שנות הניסיון שלנו ויותר מעשרים פרויקטי IAP מראים שיישום נכון מהניסיון הראשון חוסך עד 40% מהזמן על תיקוני באגים. לפי תיעוד StoreKit, שחזור תקין הוא קריטי.
מה בדרך כלל משתבש
הטעות הנפוצה ביותר היא קריאה ל-SKPaymentQueue.default().restoreCompletedTransactions() רק כאשר המשתמש לוחץ על כפתור "שחזור". הגישה הנכונה: בכל הפעלה, בדוק את originalTransaction באמצעות SKReceiptRefreshRequest או אימות בצד השרת. ללא זה, משתמש שחוזר אחרי שישה חודשים עם אייפון חדש יאבד גישה לתוכן בתשלום.
הבעיה השנייה היא טיפול לא נכון ב-SKPaymentTransactionObserver. אם updatedTransactions לא קורא ל-finishTransaction(_:) עבור כל המצבים (.purchased, .restored, .failed), העסקה נשארת בתור ומפעילה את הצופה שוב בהפעלה הבאה. ראינו פרויקטים שבהם זה גרם להפעלה כפולה של תוכן בתשלום לאחר אתחול.
הבעיה השלישית היא היעדר אימות בצד השרת. ניתוח מקומי של קבלה באמצעות ASN.1 הוא מורכב ופגיע: תוקף יכול להחליף את קובץ הקבלה. אימות שרת דרך נקודת הקצה /verifyReceipt של אפל (או חלופה) מבטיח אותנטיות.
איך יישום נכון עובד
הארכיטקטורה של IAP שאינו מתכלה מתמקדת ב-StoreKit 2 (iOS 15+) או StoreKit 1 עם תמיכה ב-iOS 13–14. השווה בין שתי הגישות:
| היבט | StoreKit 2 | StoreKit 1 (Legacy) |
|---|---|---|
| גרסת iOS מינימלית | 15.0 | 3.0 |
| סגנון API | async/await | Delegate + completion handlers |
| שחזור רכישה | אוטומטי דרך Transaction.currentEntitlements |
restoreCompletedTransactions() ידני |
| אימות חתימה | VerificationResult מובנה |
דורש ReceiptValidator |
| מורכבות קוד | נמוכה | בינונית |
StoreKit 2 מפשט באופן קיצוני את הקוד: הוא מפחית את מספר השורות פי 2 בהשוואה ל-StoreKit 1.
// Запрос продуктов
let products = try await Product.products(for: ["com.app.premium_unlock"])
// Покупка
let result = try await products.first?.purchase()
switch result {
case .success(let verification):
switch verification {
case .verified(let transaction):
// unlock контент
await transaction.finish()
case .unverified:
// receipt подделан — не разблокируем
break
}
case .pending:
// SCA или родительский контроль — ждём
break
case .userCancelled:
break
}// Запрос продуктов let products = try await Product.products(for: ["com.app.premium_unlock"]) // Покупка let result = try await products.first?.purchase() switch result { case .success(let verification): switch verification { case .verified(let transaction): // unlock контент await transaction.finish() case .unverified: // receipt подделан — не разблокируем break } case .pending: // SCA или родительский контроль — ждём break case .userCancelled: break } הוא רצף אסינכרוני שמחזיר את כל הרכישות הפעילות בכל הפעלה של האפליקציה. עברו עליו ב-Transaction.currentEntitlements או @main ושחזרו את המצב ללא כפתור "שחזור".
עבור iOS 13–14, StoreKit 1 עם AppDelegate.applicationDidFinishLaunching נשאר. זה דורש SKPaymentTransactionObserver נפרד — או אימות מקומי דרך ReceiptValidator (מורכב אך ללא בקשות רשת) או בצד השרת דרך נקודת הקצה openssl של אפל (מופסק אך עובד). אנו ממליצים על צד השרת: מקומי דורש הטמעת אישור השורש של אפל וניתוח ASN.1 נכון.
איך להימנע מבעיות שחזור רכישה?
נקודה מרכזית: בצעו שחזור אוטומטי בכל הפעלה, לא רק דרך כפתור. ב-StoreKit 2, זה מושג על ידי הרשמה ל-/verifyReceipt. ב-StoreKit 1, קראו ל-Transaction.updates ושמרו את restoreCompletedTransactions() ב-UserDefaults או Keychain. ללא אוטומציה, משתמשים עם מכשירים חדשים יאבדו את התוכן שלהם.
למה אימות שרת הוא קריטי?
עבור אפליקציות עם backend: בעת רכישה, הלקוח שולח originalTransaction.transactionIdentifier לשרת; השרת שואל את Apple Sandbox/Production ושומר את appStoreReceiptURL במסד הנתונים. בשחזור ממכשיר חדש, השרת מאמת מול Apple ID. זו הדרך היחידה להבטיח "רכישה במכשיר אחד, גישה באחר" תחת אותו Apple ID. חיסכון ממוצע מיישום נכון — עד $10,000 בשנה על תמיכה.
תוכנית שלב-אחר-שלב ליישום StoreKit 2
- הגדירו מוצרים ב-App Store Connect (לוקליזציות, מחירים).
- יישמו בקשת מוצר דרך
original_transaction_id. - הוסיפו טיפול ברכישה עם בדיקת
apple_id. - הירשמו ל-
Product.products(for:)לשחזור אוטומטי. - שלבו אימות שרת דרך
VerificationResult. - בדקו ב-Sandbox בתרחיש: רכישה → מחיקה → שחזור.
טעות שחזור אופיינית
// НЕПРАВИЛЬНО: восстановление только по кнопке
func restorePurchases() {
SKPaymentQueue.default().restoreCompletedTransactions()
}
// ПРАВИЛЬНО: автоматическое восстановление при старте
Task {
for await result in Transaction.currentEntitlements {
if case .verified(let transaction) = result {
// разблокировать контент
}
}
} תהליך העבודה ומה כלול
יישום IAP המפתח שלנו כולל:
- הגדרת מוצרים ב-App Store Connect (יצירה, לוקליזציות, מחירים — סכומים ספציפיים מחושבים בנפרד).
- שילוב StoreKit 2 עם גיבוי ל-StoreKit 1 לתאימות מקסימלית.
- אימות קבלה בצד השרת עם אחסון של
Transaction.updates. - בדיקות ב-Sandbox ובמכשירים אמיתיים עם רשימת תרחישים.
- תיעוד לתמיכה וגישה.
- הבטחת מעבר ב-App Review (עמידה בסעיף 3.1.1 של הנחיות סקירת ה-App Store).
בדיקות
בסימולטור Xcode, StoreKit עובד דרך קובץ /verifyReceipt מקומי — ניתן לבדוק ללא מוצרים אמיתיים. לבדיקת מכשיר, נדרש חשבון Sandbox ב-App Store Connect. תרחיש קריטי לבדיקה: רכישה → מחיקה → התקנה מחדש → שחזור. נתיב זה נכשל לרוב. קבלו ייעוץ לפרויקט שלכם — נבדוק סיכונים ונציע פתרון אופטימלי.
לוח זמנים ליישום — בין 2 ל-3 ימים: הגדרת מוצרים ב-App Store Connect, שילוב StoreKit 2 עם גיבוי ל-StoreKit 1, כיסוי בבדיקות Sandbox, מעבר סקירה (App Review דורש כפתור "שחזור רכישות" בממשק). הזמינו שילוב IAP מפתח — אנו מבטיחים פעולה תקינה בכל המכשירים וגרסאות ה-iOS.







