SubSovereign na iOS — przewodnik integracji
Ten przewodnik przeprowadzi twoją aplikację Apple (iPhone, iPad, Apple TV lub Mac) od stanu „nie mam pojęcia, kto mi zapłacił” do „moja aplikacja odblokowuje odpowiednie funkcje dla odpowiedniego użytkownika, zweryfikowane na moim własnym serwerze”. Został napisany tak, aby można go było stosować od początku do końca — nie zakłada się wcześniejszego doświadczenia z narzędziami subskrypcyjnymi.
Co SubSovereign robi za Ciebie
Twoi użytkownicy subskrybują przez App Store. SubSovereign wiarygodnie odpowiada na jedno pytanie dla twojej aplikacji: co ten użytkownik faktycznie zapłacił?
- Twoja aplikacja pyta SubSovereign o uprawnienie użytkownika — dostęp, który został odblokowany.
- Weryfikacja paragonu odbywa się na serwerze, bezpośrednio z Apple (App Store Server API), dzięki czemu zmodyfikowana aplikacja nie może sfałszować subskrypcji.
- Płatna ścianka — ekran oferujący twoje plany — jest konfigurowany zdalnie, dzięki czemu możesz zmieniać ceny, okresy próbne i tekst bez wysyłania nowej wersji aplikacji.
- Funkcja odstąpienia od umowy — prawny mechanizm rezygnacji — jest również renderowana przez SDK w języku klienta, dzięki czemu informacja prawna nigdy nie zostanie złagodzona do celów marketingowych.
- Jest samodzielnie hostowany: SubSovereign działa na twojej infrastrukturze, dane twoich użytkowników pozostają u Ciebie, a nie pobieramy żadnej prowizji od przychodów — zatrzymujesz 100% tego, co płacą twoi użytkownicy.
Nie ufasz urządzeniu. Urządzenie pyta; serwer decyduje. Ten sam kod działa na iOS, tvOS (Apple TV) i macOS.
Zanim zaczniesz
Będziesz potrzebować:
- Działającego serwera SubSovereign (twoje samodzielnie hostowane wdrożenie) — SDK wskazuje na jego adres URL.
- Zarejestrowanej aplikacji w panelu sterowania, która daje ci
appIdi klucz API (poświadczenie, którego twoja aplikacja używa do komunikacji z serwerem). - Utworzonych poziomów dostępu — poziomów dostępu (tierów), które twoja aplikacja przyznaje, np.
pro, każdy powiązany w panelu sterowania z identyfikatorami produktów App Store kupowanymi przez twoich użytkowników. - Konfiguracji StoreKit 2. SubSovereign weryfikuje i śledzi zakupy; nie zastępuje StoreKit. Obsługuj zakupy za pomocą StoreKit 2 w normalny sposób — SubSovereign działa tuż po pomyślnej transakcji, aby ją zweryfikować i zapisać.
Po stronie kodu potrzebujesz konkurentnego Swifta (async/await) i docelowej wersji iOS 15 / tvOS 15 / macOS 12 lub nowszej.
Krok 1 — Dodaj SDK
Dodaj SubSovereign za pomocą Swift Package Manager — w Xcode wybierz File ▸ Add Packages… i wskaż pakiet SDK SubSovereign (lub dodaj go do zależności w Package.swift). Następnie zaimportuj go:
import SubSovereign
Krok 2 — Konfiguracja raz, podczas uruchamiania aplikacji
Skonfiguruj SDK jeden raz — dobrym miejscem jest init twojej aplikacji lub tuż po zalogowaniu użytkownika. Przekaż mu swój klucz API, identyfikator aplikacji, adres URL serwera, stabilny identyfikator dla tego użytkownika oraz jego język.
SubSovereign.shared.configure(
SubSovereignConfig(
apiKey: "YOUR_APP_API_KEY", // from the dashboard
appId: "your-app-id", // from the dashboard
baseURL: "https://subs.yourdomain.com/api/v1", // YOUR self-hosted server
userId: currentUser.id, // your own stable user id
locale: Locale.current.identifier // the user's language
)
)
Warto wiedzieć kilka rzeczy:
userIdjest twój. Użyj dowolnego stabilnego identyfikatora, którego już używasz dla zalogowanego użytkownika, i tej samej wartości za każdym razem, aby dostęp podążał za nim między urządzeniami. Jeśli zaloguje się inny użytkownik, ponownie wywołajconfigurez nowymuserId.baseURLwskazuje na twój serwer — domyślna wartość SDK to tylko placeholder; ustaw własne wdrożenie.- SDK jest anotowane jako
@MainActor, więc wywołuj je z głównego aktora (widoki SwiftUI i.tasksą w porządku).
Krok 3 — Sprawdź, do czego użytkownik ma dostęp
Wywołaj checkEntitlements(), aby dowiedzieć się, co użytkownik odblokował. Zrób to podczas uruchamiania aplikacji i ponownie tuż po zakupie. To wywołanie async, które może throw, więc owin je w do/catch:
do {
let result = try await SubSovereign.shared.checkEntitlements()
if result.hasAccess {
unlockProFeatures() // the user has paid access
} else {
showFreeExperience() // free tier / show a paywall
}
} catch {
// Network hiccup or server error. Fail gracefully — usually keep the user on
// whatever access they last had, and try again later.
print("Entitlement check failed: \(error.localizedDescription)")
}
result.hasAccess to szybka odpowiedź tak/nie na podstawie wyniku, który już pobrałeś. Aby zablokować funkcję w jednej linii, użyj metody o tej samej nazwie — zwraca false przy każdym błędzie, dzięki czemu chwilowy problem z siecią nie otworzy niczego:
if await SubSovereign.shared.hasAccess(accessLevelName: "pro") {
unlockProFeatures()
}
Użyj jej do taniego zablokowania pojedynczej funkcji. Do decyzji o dostępie dla całej aplikacji użyj checkEntitlements() i porady dotyczącej Obsługi błędów poniżej — podczas awarii chcesz, aby płacący klient zachował ostatnio znany dostęp, a nie został zablokowany.
Jeśli twoja aplikacja ma więcej niż jeden poziom, zajrzyj do result.entitlements — każdy z nich nazywa aktywny poziom dostępu. Dopasuj się do nazwy, którą nadałeś poziomowi w panelu sterowania; accessLevelId to wewnętrzny identyfikator serwera, a nie ta nazwa:
let isPro = result.entitlements.contains { $0.isActive && $0.accessLevelName.lowercased() == "pro" }
Każde uprawnienie zawiera również expiresAt, willRenew oraz store, z którego pochodzi. Wynik zawiera również fromCache, który informuje, czy serwer odpowiedział ze swojej pamięci podręcznej — sam SDK nie przechowuje pamięci podręcznej, a każde wywołanie trafia do twojego serwera.
Krok 4 — Sprzedaj subskrypcję
Pokaż płatną ściankę
Pobierz płatną ściankę z serwera zamiast kodować na stałe ceny, abyś mógł przeprowadzić promocję lub zmienić okres próbny bez wydania nowej wersji:
if let paywall = try? await SubSovereign.shared.getPaywallConfig() {
renderPaywall(paywall) // headline, features, products…
} else {
renderFallbackPaywall() // your built-in default
}
PaywallConfig daje ci headline, subheadline, listę features, products do zaoferowania (każdy z displayPrice, period, trialDays i opcjonalnym badge), tekst wezwania do działania oraz tekst stopki. Ty budujesz rzeczywisty ekran — SubSovereign mówi mu, co ma powiedzieć.
Ukończ zakup, a następnie zweryfikuj go
Przeprowadź zakup przez StoreKit 2 w normalny sposób. Gdy otrzymasz zweryfikowaną transakcję Transaction, przekaż ją do SubSovereign, aby serwer mógł ją zweryfikować bezpośrednio z Apple i przyznać poziom dostępu:
func buy(_ product: Product, accessLevelId: String) async throws {
let result = try await product.purchase()
guard case .success(let verification) = result else { return } // cancelled, or pending approval
guard case .verified(let transaction) = verification else {
// .unverified means StoreKit could not trust the signature — a tampered receipt.
// Never grant access, and never let this pass silently.
print("Refused an unverified transaction")
return
}
let granted = try await SubSovereign.shared.validateApplePurchase(
transaction: transaction,
accessLevelId: accessLevelId // required; the SERVER grants whatever tier your
// dashboard maps this product to, not this value
)
if granted {
await SubSovereign.shared.finishTransaction(transaction) // tell StoreKit it's done
_ = try await SubSovereign.shared.checkEntitlements() // re-check, then unlock
}
}
To cały model zaufania: zakup jest realny dopiero wtedy, gdy serwer potwierdzi go z Apple.
Krok 5 — Funkcja odstąpienia od umowy (Compliance Passport)
Jeśli sprzedajesz subskrypcje konsumentom z UE, Dyrektywa (UE) 2023/2673 wymaga wyraźnie oznaczonej funkcji odstąpienia — kontroli anulowania. SDK dostarcza ją gotową, a jest renderowana w dwóch częściach: pobierz ustawienia dla twojej aplikacji, a następnie narysuj kontrolkę z nimi.
struct CancelSubscription: View {
let subscriptionId: String
@State private var settings: WithdrawalConfig?
var body: some View {
Group {
if settings?.enabled == false {
EmptyView() // switched off for this app in the console
} else {
WithdrawalView(
subscriptionId: subscriptionId,
strings: settings?.strings, // ← without this the notice is ENGLISH for everyone
appearance: .auto // .light, .dark or .auto — and nothing else
)
}
}
.task {
do {
settings = try await SubSovereign.shared.getWithdrawalConfig()
} catch {
// Log it: a silent failure here is how you ship English to German customers
// and never find out.
print("Withdrawal settings unavailable: \(error.localizedDescription)")
}
}
}
}
Nie możesz zmienić tekstu. Słowa pochodzą z twojego serwera SubSovereign w języku klienta, a nie ma możliwości przekazania własnego tekstu: parametr label, który kiedyś przyjmował tekst dowolny, jest teraz ignorowany i do konsoli integratora trafia ostrzeżenie. Prawna informacja, którą można przepisać, to taka, którą można złagodzić do celów marketingowych — to samo powód, dla którego nie możesz przekazać koloru.
🚩 Przekaż strings, w przeciwnym razie informacja prawna będzie po angielsku dla każdego klienta. Kontrolka renderuje to, co otrzyma, i nie pobiera tekstów sama — ten sam kontrakt musi obowiązywać na każdej platformie, w tym na tej, gdzie komponent UI nie może wykonywać sieci. Język, który otrzymasz, jest określany przez locale, którego użyłeś w Kroku 2, ponieważ wywołanie ustawień wysyła go. Jeśli pobranie się nie powiedzie, kontrolka nadal się pojawi — po angielsku, a nie wcale: nieumieszczenie w aplikacji funkcji odstąpienia przed konsumentem jest twoim naruszeniem; pokazanie jej w złym języku — nie. Zaloguj błąd; nigdy nie pokaż go klientowi.
Wygląd — .light, .dark lub .auto (domyślnie), i nic więcej. Kontrolka maluje swoją własną kartę i własny tekst, dzięki czemu jest czytelna na każdym ekranie. .auto podąża za ustawieniami wyglądu urządzenia na iPhone, iPadzie lub Macu i jest zawsze ciemna na Apple TV. Celowo nie ma możliwości przekazania koloru.
Dwa ograniczenia dotyczące Apple obecnie. Etykieta wybrana w konsoli nie jest jeszcze stosowana tutaj, a data na potwierdzeniu jest formatowana według ustawień regionalnych urządzenia, a nie klienta.
Pokazuje oznaczoną przyciskiem kontrolkę, potwierdza raz — bez żadnej oferty zatrzymania lub ankiety przed nią, zgodnie z wymogami prawa — przesyła idempotentnie (naciśnięcie Spróbuj ponownie po niepowodzeniu ponownie używa tego samego identyfikatora, dzięki czemu ponowienie nie może utworzyć drugiego odstąpienia), i pokazuje potwierdzenie z datą. To, czy kontrolka jest włączona, zależy od Ciebie, jak wyżej; sama kontrolka nie wykonuje żadnej sieci poza przesłaniem.
Krok 6 — Prywatność i RODO
Zapisz zgodę tam, gdzie ją zbierasz, i respektuj oczekiwania Apple dotyczące praw do danych — SDK udostępnia zgodę, przełącznik CCPA „nie sprzedawaj moich danych”, usunięcie oraz eksport:
try await SubSovereign.shared.recordConsent(purpose: "analytics", granted: true)
// CCPA "do not sell" / Global Privacy Control.
// `true` turns the customer's OPT-OUT on; pass `false` when they opt back in.
try await SubSovereign.shared.setDoNotSell(enabled: true)
// If the user asks to be forgotten / to get their data:
try await SubSovereign.shared.requestErasure()
let myData = try await SubSovereign.shared.exportMyData()
purpose musi być jednym z: analytics, marketing, personalisation lub consumption_data_sharing — dokładnie tak, małymi literami. Cokolwiek innego powoduje .invalidArgument i nic nie jest zapisywane: zapis zgody to zapis prawny, a SDK nie zapisze takiej zgody w przypadku celu, o który klienta nigdy nie zapytano. jurisdiction domyślnie ma wartość "GDPR", a policyVersion — "1.0" — przekaż własne, jeśli są inne.
Obsługa błędów
Każde wywołanie z możliwością rzucenia błędu kończy się typowym SubSovereignError — .notConfigured, .networkError(Error), .serverError(code, message) lub .invalidArgument(message). Złap go i zdecyduj, co zrobić, zamiast dopuszczać do awaryjnego zatrzymania programu:
- Sukces — użyj wartości.
- Niepowodzenie — zaloguj to, utrzymuj użytkownika na jego ostatnio znanym dostępie, spróbuj ponownie później. Nigdy nie blokuj płacącego użytkownika z powodu chwilowego problemu z siecią.
Najlepsze praktyki
- Sprawdzaj podczas uruchamiania. Wywołaj
checkEntitlements()podczas uruchamiania aplikacji (np. z.taskSwiftUI), aby blokowanie było prawidłowe, zanim użytkownik trafi na zablokowaną funkcję. - Sprawdzaj ponownie po zakupie. Tuż po pomyślnym
validateApplePurchaseponownie wywołajcheckEntitlements(), aby interfejs zaktualizował się natychmiast. - Nigdy nie ufaj klientowi. Nie przechowuj „jest pro” w aplikacji i nie traktuj tego jako prawdy — pytaj serwer; serwer zweryfikował transakcję z Apple.
- Jeden
userIdna rzeczywistego użytkownika. Utrzymuj go stabilnym, aby dostęp podążał za użytkownikiem między jego urządzeniami Apple, i ponownie skonfiguruj, gdy zmieni się zalogowany użytkownik.
Szybki przegląd
| Chcesz… | Wywołaj |
|---|---|
| skonfigurować SDK | SubSovereign.shared.configure(config) |
| zobaczyć, co użytkownik odblokował | try await checkEntitlements() → EntitlementResult |
| pokazać zdalną płatną ściankę | try await getPaywallConfig() → PaywallConfig |
| zweryfikować zakup StoreKit 2 | try await validateApplePurchase(transaction:accessLevelId:) |
| ukończyć transakcję | await finishTransaction(transaction) |
| zablokować funkcję, fail-closed (zamknięcie awaryjne) | await hasAccess(accessLevelName:) → Bool |
| pobrać ustawienia odstąpienia | try await getWithdrawalConfig() → WithdrawalConfig |
| narysować prawną kontrolkę anulowania | WithdrawalView(subscriptionId:strings:appearance:) |
| zapisać zgodę RODO | try await recordConsent(purpose:granted:) |
| respektować żądanie CCPA „nie sprzedawaj moich danych” | try await setDoNotSell(enabled:) |
| usunąć / wyeksportować dane użytkownika | try await requestErasure() / exportMyData() |
Następne kroki
- Zrób to samo na innych platformach — Android, Web/JavaScript i Roku SDK mają identyczny kształt.
- Nowy w tych koncepcjach? Przeczytaj Jak działa SubSovereign.