StoreKit 2 & In-App Purchases

StoreKit 2 per Prodotti Consumabili ed In-App Purchases: Ricariche Minuti Telefonici ed Wallet in SwiftUI

A differenza degli abbonamenti ricorrenti, i **prodotti consumabili** (come i pacchetti di minuti per chiamate internazionali o i gettoni di ricarica wallet) possono essere acquistati ripetutamente dagli utenti. **StoreKit 2** su iOS fornisce un'API basata su async/await dotata di crittografia JWS (JSON Web Signature) nativa per la verifica immediata delle ricevute.

1. Identificativi dei Prodotti Consumabili ed Identificazione del Carrier

In un'app di telefonia come **Africall**, i pacchetti di minuti sono spesso legati all'operatore di destinazione (es. koracall.minutes.ml.orange.30 per 30 minuti Orange Mali). Definiamo gli ID in una struct Swift:

import Foundation
import StoreKit

enum StoreKitProducts {
    static let minutePacks: Set = [
        "koracall.minutes.ml.orange.30",
        "koracall.minutes.ml.orange.60",
        "koracall.minutes.sn.orange.120",
        "koracall.minutes.it.mobile.300"
    ]
}

2. Caricamento ed Esecuzione dell'Acquisto in Swift

StoreKit 2 rende l'invocazione dell'acquisto ed il controllo dei risultati estremamente compatto e sicuro:

import SwiftUI
import StoreKit

class WalletStoreManager: ObservableObject {
    @Published var availableProducts: [Product] = []
    @Published var isPurchasing: Bool = false

    func fetchProducts() async {
        do {
            let products = try await Product.products(for: StoreKitProducts.minutePacks)
            DispatchQueue.main.async {
                self.availableProducts = products.sorted(by: { $0.price < $1.price })
            }
        } catch {
            print("Errore caricamento prodotti StoreKit 2: \(error)")
        }
    }

    func purchaseProduct(_ product: Product) async throws {
        DispatchQueue.main.async { self.isPurchasing = true }
        
        let result = try await product.purchase()
        
        switch result {
        case .success(let verification):
            // Verifica crittografica del JWS inviato da Apple
            let transaction = try checkVerified(verification)
            
            // Accredito del pacchetto di minuti sul server backend / Firestore
            try await creditMinutesToUserWallet(transaction: transaction)
            
            // Notifica ad Apple che la transazione consumabile è stata consegnata
            await transaction.finish()
            
            DispatchQueue.main.async { self.isPurchasing = false }

        case .userCancelled, .pending:
            DispatchQueue.main.async { self.isPurchasing = false }
        @unknown default:
            DispatchQueue.main.async { self.isPurchasing = false }
        }
    }

    private func checkVerified(_ result: VerificationResult) throws -> T {
        switch result {
        case .unverified(_, let error):
            throw error
        case .verified(let safe):
            return safe
        }
    }

    private func creditMinutesToUserWallet(transaction: Transaction) async throws {
        print("Transazione verificata ID: \(transaction.id). Invio accredito al backend...")
    }
}

3. UI del Wallet ed In-App Storefront in SwiftUI

Possiamo costruire una schermata di ricarica wallet nativa in SwiftUI con i componenti ufficiali StoreView o card personalizzate:

struct WalletRechargeView: View {
    @StateObject private var store = WalletStoreManager()

    var body: some View {
        VStack {
            Text("Ricarica Wallet Minuti")
                .font(.title2)
                .bold()

            if store.isPurchasing {
                ProgressView("Elaborazione acquisto sicuro Apple...")
            }

            List(store.availableProducts, id: \.id) { product in
                HStack {
                    VStack(alignment: .leading) {
                        Text(product.displayName).bold()
                        Text(product.description).font(.caption).foregroundColor(.gray)
                    }
                    Spacer()
                    Button(product.displayPrice) {
                        Task {
                            try? await store.purchaseProduct(product)
                        }
                    }
                    .buttonStyle(.borderedProminent)
                }
            }
        }
        .task {
            await store.fetchProducts()
        }
    }
}

Conclusione

StoreKit 2 elimina le complessità dei vecchi ricevuti base64 garantendo transazioni consumabili a prova di contraffazione. Per assistenza nell'integrazione di StoreKit 2 nelle tue app iOS, scrivi a diallooyunus@gmail.com.

Yunus Diallo (DialloDev)

Fondatore di Dywtal Digital a Parma (Italia). Specializzato in monetizzazione mobile, StoreKit 2 ed architetture In-App Purchases.