Slik ville jeg satt opp subscriptions for App Store i dag
Subscriptions i App Store er sjelden vanskelige fordi StoreKit er så komplisert. De blir vanskelige fordi små ting rundt review, metadata, sandbox og kjøpsflyt ikke er helt på plass samtidig.
Pencil ble nylig godkjent med subscriptions. Det var fint å få den gjennom, men det bekreftet også noe jeg har tenkt lenge: Apple stopper deg sjelden bare fordi “kjøpet ikke virker”. De stopper deg oftere fordi helheten ikke er tydelig nok.
Det er egentlig ganske logisk.
De reviewer ikke bare et API-kall. De reviewer en kjøpsopplevelse.
Det Apple faktisk ser etter
Når du bygger subscriptions, er det lett å tenke at jobben er gjort når du får hentet produkter og kan trigge et kjøp. Men for Apple er det bare en liten del av bildet.
Det de egentlig vurderer er noe mer praktisk:
finner vi abonnementene i appen?
er det tydelig hva brukeren får?
fungerer restore?
er dette satt opp på en ryddig måte?
finnes personvern, vilkår og support der de skal?
samsvarer App Store Connect med det appen viser?
ser sandbox-opplevelsen riktig ut?
Hvis ett av de punktene glipper, kan du fort havne i en ekstra review-runde selv om selve StoreKit-koden i praksis fungerer.
Det er derfor jeg mener subscriptions må behandles som et produktområde, ikke bare som en teknisk integrasjon.
Min viktigste læring: review ser ikke “din app”, de ser sin testflyt
Dette er kanskje det viktigste punktet i hele oppsettet.
Apple tester ikke appen slik du ser den når du sitter i ditt eget utviklingsmiljø. De tester den i sin egen flyt, i sandbox, med sine egne forventninger.
Det betyr at du må være ekstremt bevisst på alt som kan endre opplevelsen mellom production og testmiljø.
I Pencil hadde vi blant annet ekstra logikk for tidligere betalende brukere. Det er en helt legitim ting å ha når en app går fra betalt til freemium. Men den typen logikk kan fort bli farlig hvis den ikke er eksplisitt avgrenset mot sandbox.
For hvis review-teamet havner i en tilstand der de automatisk får premium-tilgang, så ser de plutselig ikke subscription-knappene i det hele tatt.
Og da blir tilbakemeldingen fra Apple helt forståelig: de finner ikke kjøpene.
Dette er en sånn feil som er lett å lage hvis man tenker “dette virker jo her hos meg”. Derfor mener jeg at sandbox må behandles som et eget miljø med egne sikkerhetsregler, ikke som en slags uferdig production.
Det jeg ville satt opp fra start
Hvis jeg skulle bygget en ny app med subscriptions i dag, ville jeg gjort det med én gang på denne måten.
Én sentral entitlement-manager
Jeg ville hatt én sentral komponent som eier alt rundt kjøp og tilgang.
Ikke views.
Ikke settings-skjermen.
Ikke tilfeldige lokale flagg.
Ikke “midlertidige” hacks.
Den komponenten skal gjøre noen få ting, men gjøre dem ordentlig:
laste produkter
refreshe entitlements ved oppstart
lytte på Transaction.updates
håndtere kjøp
håndtere restore
publisere én tydelig access state til resten av appen
Det høres kanskje litt overstrukturert ut hvis appen er liten, men det sparer deg for mye rot senere. Både i review, i debugging og når du kommer tilbake til koden om noen måneder.
Eksempelkode
Denne modellen gjør det enklere å holde hele appen samlet rundt én tydelig tilgangsstatus.
struct EntitlementState: Equatable {
enum AccessLevel: String {
case free
case legacyPro
case subscriptionPro
}
let accessLevel: AccessLevel
let hasActiveSubscription: Bool
let hasLegacyLifetime: Bool
}
En tydelig tilgangsmodell
Jeg ville alltid definert tilgang eksplisitt.
For eksempel:
free
subscriptionPro
lifetimePro
eller legacyPro, hvis appen trenger migrering fra gammel modell
Det viktige er ikke akkurat navnene. Det viktige er at hele appen bruker samme modell.
Og så ville jeg satt en helt tydelig prioritet. Typisk:
aktiv subscription
permanent premium-tilgang
free
Poenget er å unngå at forskjellige steder i appen “tror” ulike ting om samme bruker.
Det er den typen uklarhet som skaper rare feil, og som gjør support og review mer tungvint enn det trenger å være.
Eksempelkode
Poenget her er at tilgang alltid bør avgjøres i samme rekkefølge, slik at appen ikke havner i uklare mellomtilstander.
func resolveAccess(
hasActiveSubscription: Bool,
hasLegacyLifetime: Bool
) -> EntitlementState.AccessLevel {
if hasActiveSubscription {
return .subscriptionPro
}
if hasLegacyLifetime {
return .legacyPro
}
return .free
}
Restore som bare gjør restore
Jeg ville også holdt restore-flyten veldig ren.
En god restore-flyt er kjedelig på den riktige måten:
kall AppStore.sync()
refresh entitlements
oppdater UI
Det er alt.
Jeg ville ikke brukt restore som et sted for spesiallogikk eller backup-løsninger for premiumtilgang. Restore bør bare være en måte å hente sannheten fra App Store på én gang til.
Det gjør flyten mer robust, og det gjør at du slipper å forklare Apple hvorfor restore gjør mer enn restore.
Eksempelkode
Restore trenger ikke være komplisert. Den bør i praksis bare synkronisere App Store-status og så oppdatere entitlements på nytt.
func restorePurchases() async {
do {
try await AppStore.sync()
await refreshEntitlements()
} catch {
statusMessage = "Kunne ikke gjenopprette kjøp akkurat nå."
}
}
Skjul alt som ikke er klart
Dette punktet er viktigere enn mange tror.
Hvis du har et produkt i koden som ikke er klart, ikke er godkjent eller ikke skal være med i den releasen som sendes inn, så bør det ikke vises i UI.
Ikke som en disabled knapp.
Ikke som “kommer snart”.
Ikke som “ikke tilgjengelig akkurat nå”.
Bare skjul det.
Det reduserer spørsmål, reduserer støy og gjør kjøpsskjermen mye tydeligere for både review-teamet og brukeren.
Sørg for at metadata er like ryddig som koden
Jeg tror mange utviklere undervurderer hvor viktig dette er.
Du kan ha en pen og riktig implementasjon i appen, men fortsatt få friksjon hvis App Store Connect ikke er satt opp skikkelig.
For subscriptions ville jeg alltid sørget for at dette er på plass før innsending:
Privacy Policy URL
vilkår og/eller EULA
support-lenke
korrekte produkter i App Store Connect
tydelig beskrivelse av hva abonnementet gir
en review note som forklarer hvor Apple finner kjøpsskjermen
Dette er ikke “ekstraarbeid”. Det er en del av leveransen.
Og ja, jeg ville også hatt disse lenkene synlige i selve appen. Ikke bare fordi Apple liker det, men fordi brukeren faktisk fortjener å finne dem uten å måtte lete.
En god paywall er mye enklere enn mange tror
Jeg tror det er fort gjort å overdesigne kjøpsskjermen.
Men Apple sitter ikke og vurderer om paywallen din er “moderne” eller “premium”. De vurderer om den er tydelig.
For meg er en god paywall en skjerm som raskt svarer på disse spørsmålene:
hva får jeg gratis?
hva får jeg med premium?
hvilke valg har jeg?
hva koster det?
fornyes dette automatisk?
hvordan gjenoppretter jeg kjøp?
hvor finner jeg vilkår, personvern og support?
Hvis alt dette er lett å se, er du allerede langt på vei.
Det er også her det er lurt å være ærlig og enkel i språket. Jo mindre “salgsspråk” og jo mer faktisk informasjon, jo bedre fungerer det ofte både for brukeren og for review.
Sandbox må være strengere enn du tror
Hvis jeg skulle trukket fram én teknisk tommelfingerregel som har stor effekt, er det denne:
Hvis en entitlement bare skal gjelde i production, så bør den være eksplisitt av i sandbox.
Det gjelder særlig ting som:
legacy access
grandfathered premium
gammel betalt app som nå er freemium
regler basert på tidligere versjoner
spesialtilgang du bare vil gi til bestemte produksjonsbrukere
Det er mye bedre at sandbox er for streng enn at den er for raus.
For det review trenger å se, er ikke alle tenkelige edge cases. De trenger å se at subscription-produktene er tilgjengelige, forståelige og testbare.
Sjekklisten jeg selv ville brukt før innsending
Hvis jeg startet fra scratch i dag, ville jeg ikke sendt inn en subscription-app før jeg hadde sjekket dette:
kjøpsskjermen er lett å finne
bare live produkter vises
restore fungerer
sandbox viser riktig kjøpsopplevelse
juridiske lenker er på plass
metadata i App Store Connect er komplett
review note er skrevet
oppstart, kjøp og restore er testet flere ganger fra ren tilstand
Ikke fordi Apple nødvendigvis avviser deg på alt som mangler, men fordi disse punktene er de som oftest skaper unødvendig friksjon.
Det jeg ville gjort hvis jeg startet helt på nytt i dag
Hvis jeg skulle bygget en ny app med subscriptions fra bunnen av i dag, ville jeg gjort tre ting med én gang:
Først ville jeg designet entitlement-modellen før jeg lagde paywall. Ikke motsatt. Det er mye enklere å bygge en god kjøpsopplevelse når tilgangsreglene allerede er tydelige.
Deretter ville jeg gjort sandbox-reglene eksplisitte veldig tidlig. Ikke vente til review begynner å stille spørsmål. Hvis en regel bare gjelder i production, så bør den være definert slik fra starten.
Til slutt ville jeg behandlet App Store Connect som en del av implementasjonen, ikke som noe man “fikser på slutten”. Mange review-problemer oppstår fordi koden er ferdig, men metadata, juridiske lenker og produktsynlighet ikke er det.
Avslutning
Det jeg sitter igjen med etter å ha fått dette gjennom hos Apple, er egentlig ganske enkelt:
Subscriptions blir håndterbare når du slutter å tenke på dem som bare betalingskode.
Det er en hel flyt.
En hel modell.
En hel review-opplevelse.
Når entitlement-logikken er samlet, sandbox er kontrollert, produktene er tydelige, restore er ekte, og App Store Connect er like ryddig som koden, så blir hele prosessen mye mindre dramatisk.
Og det er egentlig det jeg ville optimalisert for:
ikke bare at kjøpene fungerer, men at hele oppsettet er så ryddig at verken brukeren eller Apple trenger å gjette.
Relevante Apple-kilder
App Review Guidelines
https://developer.apple.com/app-store/review/guidelines/
StoreKit documentation
https://developer.apple.com/documentation/storekit
AppTransaction.originalAppVersion
https://developer.apple.com/documentation/storekit/apptransaction/originalappversion
Offer auto-renewable subscriptions
https://developer.apple.com/help/app-store-connect/manage-subscriptions/offer-auto-renewable-subscriptions
App information / Privacy Policy URL
https://developer.apple.com/help/app-store-connect/reference/app-information/app-information
Provide a custom license agreement
https://developer.apple.com/help/app-store-connect/manage-app-information/provide-a-custom-license-agreement




