StoreKit для Swift: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое StoreKit и зачем он нужен?

StoreKit — это встроенный фреймворк от Apple, предназначенный для реализации In-App Purchases (внутриигровых покупок) и подписок в приложениях под iOS, macOS, watchOS и tvOS. Он предоставляет разработчикам единый интерфейс для взаимодействия с App Store: запроса продуктов, обработки транзакций, проверки прав доступа и управления подписками.

Основные задачи, которые решает StoreKit:

  • Монетизация — продажа цифровых товаров (премиум-доступ, виртуальная валюта, контент).
  • Управление подписками — автоматические продления, статусы, пробные периоды.
  • Безопасность — обработка платежей через App Store без передачи данных банковских карт разработчику.
  • Восстановление покупок — пользователь может перенести покупки на другое устройство.

Важно понимать: StoreKit — это не сторонняя библиотека, а часть Xcode SDK. Его не нужно устанавливать отдельно, но требуется настроить в App Store Connect и добавить в проект.

Установка и настройка

Так как StoreKit — это системный фреймворк, его установка сводится к подключению в Xcode-проекте:

  1. Откройте ваш проект в Xcode.
  2. Выберите таргет (ваше приложение) → вкладка "General".
  3. Прокрутите до раздела "Frameworks, Libraries, and Embedded Content".
  4. Нажмите "+" и выберите StoreKit.framework.
  5. Убедитесь, что статус "Embed" установлен как "Do Not Embed".

После этого импортируйте фреймворк в нужных файлах:

import StoreKit

Также обязательно настройте продукты в App Store Connect:

  • Создайте идентификаторы продуктов (например, com.example.premium).
  • Укажите цену, локализацию, тип (потребляемый/непотребляемый/подписка).
  • Для подписок настройте группы и пробные периоды.

Быстрый старт с примером кода

Минимальный рабочий пример для запроса и покупки продукта:

import StoreKit

class StoreManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
    
    static let shared = StoreManager()
    private var products: [SKProduct] = []
    
    override private init() {
        super.init()
        SKPaymentQueue.default().add(self)
    }
    
    // 1. Запрос продуктов
    func fetchProducts(productIDs: Set<String>) {
        let request = SKProductsRequest(productIdentifiers: productIDs)
        request.delegate = self
        request.start()
    }
    
    // 2. Делегат получения продуктов
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        self.products = response.products
        for product in products {
            print("Найден продукт: \(product.localizedTitle) — \(product.price)")
        }
    }
    
    // 3. Покупка
    func purchase(product: SKProduct) {
        let payment = SKPayment(product: product)
        SKPaymentQueue.default().add(payment)
    }
    
    // 4. Обработка транзакций
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch transaction.transactionState {
            case .purchased:
                // Разблокировать контент
                print("Покупка успешна: \(transaction.payment.productIdentifier)")
                SKPaymentQueue.default().finishTransaction(transaction)
            case .failed:
                if let error = transaction.error {
                    print("Ошибка покупки: \(error.localizedDescription)")
                }
                SKPaymentQueue.default().finishTransaction(transaction)
            case .restored:
                print("Покупка восстановлена")
                SKPaymentQueue.default().finishTransaction(transaction)
            default:
                break
            }
        }
    }
}

Использование:

StoreManager.shared.fetchProducts(productIDs: ["com.example.premium"])

Основные методы и функции StoreKit

1. SKProductsRequest

Что делает: Запрашивает у App Store информацию о продуктах по их идентификаторам.

Параметры:

  • productIdentifiers: Set<String> — набор строковых идентификаторов продуктов.

Пример:

let request = SKProductsRequest(productIdentifiers: ["com.example.coins100", "com.example.premium"])
request.delegate = self
request.start()

2. SKProductsResponse

Что делает: Содержит ответ от App Store: список доступных продуктов и список невалидных идентификаторов.

Свойства:

  • products: [SKProduct] — валидные продукты.
  • invalidProductIdentifiers: [String] — нераспознанные ID.

Пример:

func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    for product in response.products {
        print("\(product.localizedTitle) — \(product.priceLocale.currencySymbol ?? "")\(product.price)")
    }
}

3. SKPaymentQueue.default().add(_:)

Что делает: Добавляет платеж в очередь покупок. Запускает процесс покупки.

Параметры:

  • SKPayment — объект платежа, созданный из SKProduct.

Пример:

let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)

4. SKPaymentQueue.default().finishTransaction(_:)

Что делает: Завершает транзакцию и удаляет её из очереди. Обязательно вызывать после обработки покупки.

Параметры:

  • SKPaymentTransaction — транзакция, которую нужно завершить.

Пример:

SKPaymentQueue.default().finishTransaction(transaction)

5. SKPaymentQueue.default().restoreCompletedTransactions()

Что делает: Восстанавливает все ранее совершённые покупки (непотребляемые и подписки). Пользователь должен подтвердить действие через Face ID/Touch ID.

Параметры: нет.

Пример:

SKPaymentQueue.default().restoreCompletedTransactions()

6. SKProduct.localizedTitle

Что делает: Возвращает локализованное название продукта (например, "100 Gold Coins").

Тип: String

Пример:

let title = product.localizedTitle
print("Название: \(title)")

7. SKProduct.price

Что делает: Возвращает цену продукта как NSDecimalNumber.

Тип: NSDecimalNumber

Пример:

let price = product.price
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = product.priceLocale
let priceString = formatter.string(from: price) ?? ""
print("Цена: \(priceString)")

8. SKProduct.subscriptionPeriod

Что делает: Возвращает период подписки (только для продуктов типа подписка).

Тип: SKProductSubscriptionPeriod?

Пример:

if let period = product.subscriptionPeriod {
    let unit = period.unit // .day, .week, .month, .year
    let count = period.numberOfUnits
    print("Период: \(count) \(unit)")
}

9. SKPaymentTransaction.transactionState

Что делает: Возвращает текущее состояние транзакции.

Возможные значения:

  • .purchasing — в процессе.
  • .purchased — успешно куплено.
  • .failed — ошибка.
  • .restored — восстановлено.
  • .deferred — ожидает подтверждения (например, родительский контроль).

Пример:

switch transaction.transactionState {
case .purchased:
    print("Куплено!")
case .failed:
    print("Ошибка")
default:
    break
}

10. SKPaymentTransaction.transactionIdentifier

Что делает: Уникальный идентификатор транзакции, присвоенный App Store.

Тип: String?

Пример:

if let transactionID = transaction.transactionIdentifier {
    print("ID транзакции: \(transactionID)")
}

11. SKPaymentQueue.default().add(_ observer:)

Что делает: Регистрирует наблюдателя для обработки транзакций.

Параметры:

  • observer: SKPaymentTransactionObserver — объект, реализующий протокол.

Пример:

SKPaymentQueue.default().add(self)

Реальный практический пример использования

Рассмотрим сценарий: приложение "Премиум-фоторедактор" продаёт подписку на месяц и набор фильтров как непотребляемую покупку.

import StoreKit

class PurchaseManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
    
    static let shared = PurchaseManager()
    private var products: [String: SKProduct] = [:]
    private var completionHandler: ((Bool) -> Void)?
    
    private override init() {
        super.init()
        SKPaymentQueue.default().add(self)
    }
    
    func loadProducts() {
        let ids: Set<String> = ["com.photo.monthly", "com.photo.filters"]
        let request = SKProductsRequest(productIdentifiers: ids)
        request.delegate = self
        request.start()
    }
    
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        for product in response.products {
            products[product.productIdentifier] = product
        }
    }
    
    func buy(productID: String, completion: @escaping (Bool) -> Void) {
        guard let product = products[productID] else {
            completion(false)
            return
        }
        completionHandler = completion
        let payment = SKPayment(product: product)
        SKPaymentQueue.default().add(payment)
    }
    
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch transaction.transactionState {
            case .purchased:
                unlockContent(for: transaction.payment.productIdentifier)
                SKPaymentQueue.default().finishTransaction(transaction)
                completionHandler?(true)
            case .failed:
                SKPaymentQueue.default().finishTransaction(transaction)
                completionHandler?(false)
            case .restored:
                unlockContent(for: transaction.payment.productIdentifier)
                SKPaymentQueue.default().finishTransaction(transaction)
                completionHandler?(true)
            default:
                break
            }
        }
    }
    
    private func unlockContent(for productID: String) {
        UserDefaults.standard.set(true, forKey: productID)
    }
    
    func isContentUnlocked(productID: String) -> Bool {
        return UserDefaults.standard.bool(forKey: productID)
    }
}

Использование в UI:

PurchaseManager.shared.buy(productID: "com.photo.monthly") { success in
    if success {
        print("Подписка активирована!")
    } else {
        print("Покупка не удалась")
    }
}

Советы и типичные ошибки

Советы:

  • Всегда тестируйте на Sandbox-аккаунте — используйте тестовые пользователи в App Store Connect.
  • Не блокируйте UI — транзакции обрабатываются асинхронно, не используйте синхронные ожидания.
  • Храните статус покупок локально — используйте UserDefaults или Keychain для быстрой проверки.
  • Проверяйте receipt на сервере — для защиты от взлома отправ

Рекомендаций