Что такое StoreKit и зачем он нужен?
StoreKit — это встроенный фреймворк от Apple, предназначенный для реализации In-App Purchases (внутриигровых покупок) и подписок в приложениях под iOS, macOS, watchOS и tvOS. Он предоставляет разработчикам единый интерфейс для взаимодействия с App Store: запроса продуктов, обработки транзакций, проверки прав доступа и управления подписками.
Основные задачи, которые решает StoreKit:
- Монетизация — продажа цифровых товаров (премиум-доступ, виртуальная валюта, контент).
- Управление подписками — автоматические продления, статусы, пробные периоды.
- Безопасность — обработка платежей через App Store без передачи данных банковских карт разработчику.
- Восстановление покупок — пользователь может перенести покупки на другое устройство.
Важно понимать: StoreKit — это не сторонняя библиотека, а часть Xcode SDK. Его не нужно устанавливать отдельно, но требуется настроить в App Store Connect и добавить в проект.
Установка и настройка
Так как StoreKit — это системный фреймворк, его установка сводится к подключению в Xcode-проекте:
- Откройте ваш проект в Xcode.
- Выберите таргет (ваше приложение) → вкладка "General".
- Прокрутите до раздела "Frameworks, Libraries, and Embedded Content".
- Нажмите "+" и выберите StoreKit.framework.
- Убедитесь, что статус "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 на сервере — для защиты от взлома отправ