Irbisa · Шпаргалка
StoreKit и релиз в App Store
StoreKit 2, подписки и офферы, права доступа, серверные уведомления, возвраты, тестирование покупок, App Review, поэтапный релиз, принудительное обновление
Какие покупки обязаны идти через встроенные покупки Apple, а какие можно провести через Apple Pay или свою оплату?
Какие четыре типа встроенных покупок существуют и что каждый из них обязывает поддерживать в приложении?
Опишите покупку в StoreKit 2: какие вызовы происходят между загрузкой продукта и открытием контента?
import StoreKit @MainActorfinal class PaywallModel { private(set) var products: [Product] = [] var unlock: (String) -> Void = { _ in }…Чем отличаются
CFBundleShortVersionStringиCFBundleVersionи что из них должно меняться при каждой загрузке?Как попросить оценку в App Store через
requestReviewи почему приложение не может узнать, показался ли диалог?import StoreKitimport SwiftUI struct OrderPlacedView: View { @Environment(\.requestReview) private var requestReview let completedOrders: Int…Почему приложение должно начинать слушать
Transaction.updatesпри запуске и какие покупки теряются без этого?import StoreKit @MainActorfinal class Store { static let shared = Store() private(set) var ownedProductIDs: Set<String> = []…Найдите баг: если бэкенд лежит во время покупки, деньги списываются, а монеты так и не приходят, даже когда бэкенд поднялся.
func handle(_ result: VerificationResult<Transaction>) async { guard case .verified(let transaction) = result else { return } await transaction.finish() do { // The backend verifies the JWS and adds the coins to the account try await api.creditCoins(signedTransaction: result.jwsRepresentation) await wallet.reload() } catch { logger.error("credit failed: \(error)") showToast("Something went wrong. Please try again.") }}// Called for purchase() results and for every Transaction.updates elementКак определить, чем владеет пользователь, через
Transaction.currentEntitlementsи почему флагisPremiumвUserDefaultsне годится как источник правды?import StoreKit enum Tier: Comparable { case free, plus, pro } @MainActorfinal class Entitlements {…Что делает Restore Purchases в StoreKit 2, почему App Review всё ещё ждёт эту кнопку и когда вызывать
AppStore.sync()?import StoreKitimport SwiftUI struct RestoreButton: View { let refresh: () async -> Bool // recomputes entitlements; true if anything is owned @State private var note: String?…Как работают группы и уровни подписок и что происходит при апгрейде, даунгрейде и кроссгрейде?
Как показать бесплатный пробный период только тем, кому он положен, и чем отличаются introductory offers, promotional offers и offer codes?
import StoreKit // The line under a plan on the paywallfunc offerLine(for product: Product) async -> String { guard let subscription = product.subscription, let intro = subscription.introductoryOffer,…У подписчика не прошла оплата при продлении. Что такое billing retry и billing grace period и что приложение должно показывать в каждом состоянии?
import StoreKit enum BillingBanner { case none case fixPayment(accessUntil: Date?) // grace period: still premium case paymentFailed // billing retry: access is off, Apple keeps retrying…Пользователь получил возврат денег от Apple. Как об этом узнают приложение и ваш сервер и что позволяет предложить в приложении
beginRefundRequest?import StoreKitimport SwiftUI struct PurchaseRow: View { let transactionID: StoreKit.Transaction.ID // SwiftUI has its own Transaction type @State private var showRefund = false…Как тестировать встроенные покупки с помощью файла конфигурации StoreKit, песочницы и TestFlight и чего не ловит каждый из способов?
import StoreKitimport StoreKitTestimport XCTest final class EntitlementTests: XCTestCase { func testRefundRemovesTheEntitlement() async throws {…App Review отклонил вашу сборку. Как ответить в App Store Connect, когда подавать апелляцию и когда просить ускоренное ревью?
В App Store нет принудительного обновления. Как перевести пользователей на минимальную поддерживаемую версию и что должно продолжать работать в старой сборке?
import Foundation // Served by your backend or remote config, cached on disk, fetched at launchstruct VersionPolicy: Decodable { let minimum: String // below it: a blocking screen let recommended: String // below it: a banner the user can dismiss…Как ваш бэкенд узнаёт о продлениях, возвратах и отменах и почему доверять подписанной Apple JWS-транзакции, а не слову приложения?
{ "notificationType": "DID_FAIL_TO_RENEW", "subtype": "GRACE_PERIOD", "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", "version": "2.0", "signedDate": 1761292800000,…Пользователь оформил подписку на iPhone, а потом вошёл на Android и на сайте. Где живёт право доступа и как
appAccountTokenсвязывает покупку с вашим аккаунтом?import StoreKit struct Account { let id: String; let storeToken: UUID } // storeToken is created by your server func buy(_ product: Product, for account: Account) async throws -> Product.PurchaseResult { // The UUID comes back in the signed transaction and in every server notification…Релиз падает при запуске у трети пользователей, а отката в App Store нет. Что можно сделать в первый час и что должно было существовать заранее?