Co nowego w StoreKit 2 API i jak Apple uprościło integrację zakupów w aplikacji
Updated: 20 marca, 2023
Apple wprowadziło nową wersję StoreKit 2 podczas wydarzenia WWDC 2021, które odbyło się stosunkowo niedawno. Jest to framework odpowiedzialny za dokonywanie zakupów w systemie iOS. Udział aplikacji z funkcjami zakupu w aplikacji (in-app purchase) i subskrypcji stale rośnie, a Apple znacznie uprościło integrację zakupów w aplikacji, wydając StoreKit 2. Dzisiaj rozważymy współpracę ze StoreKit 2 ze strony serwera, innymi słowy, za pomocą API serwera App Store.
Żądanie uwierzytelnienia
W bieżącej wersji interfejsu API, aby wysłać żądanie, potrzebujesz Shared Secret. Jest to tajny, stały ciąg, który można uzyskać w App Store Connect. Nowa wersja API używa standardu JSON Web Token (JWT) do uwierzytelniania żądań.
Generowanie kluczy
Przede wszystkim, utwórz klucz prywatny który zostanie wykorzystany do autoryzacji żądań. Otwórz App Store Connect i przejdź do sekcji użytkownicy i dostęp (Users and Access), a następnie do zakładki klucze (Keys). Wybierz typ klucza „In-App Purchase”. Pobierz nowy klucz. Potrzebny będzie również jego identyfikator-możesz go skopiować na tej samej stronie, co Issue ID, który można znaleźć w zakładce API w App Store Connect.
Tworzenie tokena
Następnym krokiem jest utworzenie tokena, który będzie używany do autoryzacji żądań. Proces ten jest szczegółowo opisany w dokumentacji, nie ma więc powodu, by poświęcać temu zbyt wiele uwagi. Oto przykład gotowej implementacji dla Pythona. Warto zauważyć, że nie ma sensu generować nowego tokena dla każdego nowego żądania. Podczas tworzenia tokena ustawiasz jego żywotność na maksymalnie 60 minut i używasz tego samego tokena przez ten czas.
import time, uuid
from authlib.jose import jwt
BUNDLE_ID = 'com.adapty.sample_app'
ISSUER_ID = '4336a124-f214-4d40-883b-6db275b5e4aa'
KEY_ID = 'J65UYBDA74'
PRIVATE_KEY = '''
-----BEGIN PRIVATE KEY-----
MIGTAgMGByqGSMBHkAQQgR/fR+3Lkg4...
-----END PRIVATE KEY-----
'''
issue_time = round(time.time())
expiration_time = issue_time + 60 * 60 # 1 hour expiration
header = {
'alg': 'ES256',
'kid': KEY_ID,
'typ': 'JWT'
}
payload = {
'iss': ISSUER_ID,
'iat': issue_time,
'exp': expiration_time,
'aud': 'appstoreconnect-v1',
'nonce': str(uuid.uuid4()),
'bid': BUNDLE_ID
}
token_encoded = jwt.encode(header, payload, PRIVATE_KEY)
token_decoded = token_encoded.decode()
authorization_header = {
'Authorization': f'Bearer {token_decoded}'
}
Podpisane transakcje
W nowej wersji API wszystkie transakcje zwracane są w standardzie JSON Web Signature (JWS). Jest to ciąg składający się z trzech części podzielonych kropkami.
- Nagłówek Base64.
- Zawartość transakcji Base64.
- Podpis transakcji.
Base64(header) + "." + Base64(payload) + "." + sign(Base64(header) + "." + Base64(payload))
Nagłówek transakcji
Nagłówek jest potrzebny, aby upewnić się, że transakcja jest autentyczna. Klucz Alg zawiera algorytm szyfrowania, klucz x5c zawiera łańcuch certyfikatów.
{
"kid": "AMP/DEV",
"alg": "ES256",
"x5c": [
"MIIEO...",
"MIIDK..."
]
}
Zawartość transakcji
{
"transactionId": "1000000831360853",
"originalTransactionId": "1000000806937552",
"webOrderLineItemId": "1000000063561721",
"bundleId": "com.adapty.sample_app",
"productId": "basic_subscription_1_month",
"subscriptionGroupIdentifier": "27636320",
"purchaseDate": 1624446341000,
"originalPurchaseDate": 1619686337000,
"expiresDate": 1624446641000,
"quantity": 1,
"type": "Auto-Renewable Subscription",
"appAccountToken": "fd12746f-2d3a-46c8-bff8-55b75ed06aca",
"inAppOwnershipType": "PURCHASED",
"signedDate": 1624446484882,
"offerType": 2,
"offerIdentifier": "basic_subscription_1_month.pay_as_you_go.3_months"
}
Apple zmieniło i rozszerzyło format transakcji. Z mojego punktu widzenia praca z nimi jest teraz wygodniejsza. Szczegóły dotyczące nowego formatu można znaleźć w dokumentacji. Poniżej opiszę najważniejsze zmiany.
- Apple dodał pole appAccountToken, które zawiera identyfikator użytkownika systemu. Ten identyfikator musi być w formacie UUID, jest ustawiany w aplikacji mobilnej podczas inicjowania zakupu. Jeśli jest ustawiony, zostanie zwrócony we wszystkich transakcjach w tym łańcuchu (odnowienie, problemy z rozliczeniami itp.), dzięki czemu łatwo zrozumiesz, który użytkownik dokonał zakupu.
- Apple dodał również pola offerType i offerIdentifier, które zawierają informacje o wykorzystywanej ofercie (jeśli dotyczy to danej transakcji). Oto wartości dla pola offerType:
- 1 – oferta początkowa (dostępna tylko dla użytkowników bez aktywnych lub wygasłych subskrypcji)
- 2 – oferta promocyjna – promo offer (dostępna tylko dla aktualnych i wygasłych subskrypcji)
- 3 – kod promocyjny
Jeśli użyto oferty promocyjnej lub kodu promocyjnego, klucz offerIdentifier będzie zawierał identyfikator użytej oferty. W przeszłości nie można było śledzić korzystania z oferty po stronie serwera, co pogarszało analitykę. Teraz możesz używać kodów ofert do analizy.
- Apple dodał pole inAppOwnershipType, które pomaga zrozumieć, czy użytkownik kupił produkt lub uzyskał do niego dostęp dzięki subskrypcji rodzinnej. Możliwe wartości:
- PURCHASED
- FAMILY_SHARED
- Kolejne nowe pole – type – zawiera typ transakcji. Możliwe wartości:
- Auto-Renewable Subscription
- Non-Consumable
- Consumable
- Non-Renewing Subscription
- Pola cancellation_date i cancellation_reason mają teraz nowe nazwy: revocationDate i revocationReason. Przypominamy, że zawierają datę i powód odwołania subskrypcji, skutkującego zwrotem pieniędzy, więc nowa nazwa wygląda bardziej logicznie.
- Wszystkie klucze zwracane są w formacie camelCase (tak jak we wszystkich żądaniach API serwera App Store).
- Wszystkie daty są wyświetlane w formacie Unix timestamp w milisekundach.
Status subskrypcji użytkownika
Aby sprawdzić aktualny status subskrypcji użytkownika, wyślij zapytanie do https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{originalTransactionId}, gdzie {originalTransactionId} jest identyfikatorem dowolnego łańcucha transakcji użytkownika. W zamian otrzymasz transakcje ze statusami dla każdej grupy subskrypcji.
{
"environment": "Sandbox",
"bundleId": "com.adapty.sample_app",
"data": [
{
"subscriptionGroupIdentifier": "39636320",
"lastTransactions": [
{
"originalTransactionId": "1000000819078552",
"status": 2,
"signedTransactionInfo": "eyJraWQiOi...",
"signedRenewalInfo": "eyJraWQiOi..."
}
]
}
]
}
Klucz statusu wyświetla aktualny status subskrypcji, na jego podstawie możesz zdecydować, czy powinieneś zapewnić użytkownikowi dostęp do płatnych funkcji aplikacji. Możliwe wartości:
- 1 – subskrypcja jest aktywna, użytkownik musi posiadać dostęp do płatnych funkcji.
- 2 – subskrypcja wygasła, użytkownik nie może mieć dostępu do płatnych funkcji.
- 3 – status subskrypcji to próba płatności (Billing Retry), co oznacza, że użytkownik nie anulował subskrypcji, ale ma problemy z zapłaceniem. Apple będzie próbować obciążać kartę przez 60 dni. Użytkownik nie może mieć dostępu do płatnych funkcji.
- 4 – status subskrypcji to okres karencji (Grace Period), co oznacza, że użytkownik nie anulował subskrypcji, ale doświadczył problemów z płatnością. Okres karencji jest włączony w App Store Connect, więc użytkownik musi mieć dostęp do płatnych funkcji.
- 5 – subskrypcja została anulowana w wyniku zwrotu pieniędzy, użytkownik nie może mieć dostępu do płatnych funkcji.
Klucz SignedTransactionInfo zawiera informacje o ostatniej transakcji w łańcuchu. Szczegóły dotyczące jego formatu można znaleźć powyżej.
Informacje o odnowieniu subskrypcji (subscription renewal)
Klucz SignedRenewalInfo zawiera informacje o odnowieniu subskrypcji.
{
"expirationIntent": 1,
"originalTransactionId": "1000000819078552",
"autoRenewProductId": "basic_subscription_1_month",
"productId": "basic_subscription_1_month",
"autoRenewStatus": 0,
"isInBillingRetryPeriod": false,
"signedDate": 1624520884048
}
Te informacje pozwalają nam zrozumieć, co stanie się z subskrypcją w następnym okresie płatności. Na przykład, jeśli zauważysz, że użytkownik anulował automatyczne odnawianie, możesz zaoferować mu przejście na inny plan subskrypcji lub zaoferować mu ofertę promocyjną. Wygodne jest śledzenie tego rodzaju zdarzeń za pomocą powiadomień serwera, o których opowiem wkrótce.
Historia transakcji użytkownika
Aby uzyskać historię transakcji użytkownika, wyślij zapytanie GET do https://api.storekit.itunes.apple.com/inApps/v1/history/{originalTransactionId}, gdzie {originalTransactionId} jest identyfikatorem dowolnego łańcucha transakcji użytkownika. W zamian otrzymasz tablicę transakcji posortowanych według czasu.
{
"revision": "1625872984000_1000000212854038",
"bundleId": "com.adapty.sample_app",
"environment": "Sandbox",
"hasMore": true,
"signedTransactions": [
"eyJraWQiOiJ...",
"joiRVMyNeyX...",
"5MnkvOTlOZl...",
...
]
}
Żądanie może zawierać nie więcej, niż 20 transakcji. Jeśli użytkownik ma więcej, wartość oznaczenia hasMore będzie prawdziwa. Jeśli potrzebujesz następnej strony transakcji, wyślij żądanie ponownie z parametrem revision GET. Będzie ono zawierać wartość z tego samego klucza.
Serwerowe powiadomienia o transakcjach
Powiadomienia serwera pomagają uzyskać informacje o nowych zakupach, odnowieniach, problemach z płatnościami itp. Pomaga to budować dokładniejsze analizy, a także upraszcza zarządzanie statusem subskrybenta.
Istniejące powiadomienia serwera (V1) mogą rozwiązać większość problemów, ale czasami są niewygodne. Głównie chodzi o sytuację, w której otrzymujesz kilka powiadomień, związanych z pojedynczą akcją użytkownika. Na przykład teraz, gdy użytkownik aktualizuje subskrypcję, Apple wysyła dwa powiadomienia: DID_CHANGE_RENEWAL_STATUS i INTERACTIVE_RENEWAL. Aby obecnie przetworzyć ten przypadek, musisz jakoś zapisać status i sprawdzić, czy drugie powiadomienie zostało wysłane. W nowej wersji powiadomień serwerowych (server notifications) (V2) jest już tylko jedno powiadomienie dla jednej akcji użytkownika. Jest to o wiele wygodniejsze.
Druga wersja powiadomień serwerowych zawiera nowe zdarzenia – OFFER_REDEEMED, EXPIRED i GRACE_PERIOD_EXPIRED. Znacznie ułatwiają one zarządzanie statusem subskrybenta. Zdarzenia SUBSCRIBED i PRICE_INCREASE są ulepszonymi zdarzeniami z pierwszej wersji.
Typy powiadomień
Powiadomienia posiadają teraz typy, więc jedno powiadomienie o każdej akcji użytkownika wystarczy, aby zrozumieć, co się stało.
Typy powiadomień
{
"notificationType": "SUBSCRIBED",
"subtype": "INITIAL_BUY",
"version": 2,
"data": {
"environment": "Sandbox",
"bundleId": "com.adapty.sample_app",
"appAppleId": 739104078,
"bundleVersion": 1,
"signedTransactionInfo": "eyJraWQiOi...",
"signedRenewalInfo": "eyJraWQiOi..."
}
}
Powiadomienia serwera zawierają informacje o transakcji i odnowieniu w formacie JWS, który opisałem wcześniej.
Praca ze środowiskiem sandbox
Aby przetestować zakupy, musisz użyć adresu URL środowiska sandbox: https://api.storekit-sandbox.itunes.apple.com.
Nowa wersja powiadomień serwera nie jest jeszcze dostępna do testowania. Gdy zostanie udostępniona, będzie można określić różne adresy URL dla powiadomień produkcyjnych i sandboxowych. Możesz wybrać V2 dla sandboxa i V1 dla produkcji w celach testowych.
Ponadto App Store Connect pozwala teraz na:
- Czyszczenie historii zakupów dla użytkownika sandbox, co oznacza, że nie musisz już tworzyć nowego konta, aby to zrobić.
- Zmieniać kraj sklepu dla użytkownika sandbox.
- Zmień okres odnowienia subskrypcji w sanboxie: na przykład możesz dokonać miesięcznego zakupu, który trwa 1 godzinę zamiast 5 minut.
Wnioski
Firma Apple znacznie usprawniła pracę z zakupami i subskrypcjami w aplikacji po stronie serwera. Z mojego punktu widzenia, najbardziej przydatne nowe funkcje to:
- Pełne wsparcie ofert promocyjnych i kodów promocyjnych;
- Prostsze i bardziej pełne powiadomienia serwerowe;
- Możliwość zapoznania się z aktualnym stanem subskrypcji za pomocą prostego wywołania API;
- Czyszczenie historii zakupów w sandboxie użytkownika.
Przejście na nowe API nie będzie trudne, wystarczy uzyskać originalTransactionId dla każdego potwierdzenia zakupu. Prawdopodobnie jest ono już w Twojej bazie danych.
W każdym razie najtrudniejszą częścią integracji subskrypcji z aplikacją mobilną jest zbudowanie systemu analitycznego i optymalizacja w sensie ekonomicznym. Adapty może dobrze rozwiązać te problemy:
- Wbudowana analiza pozwala nam szybko zrozumieć główne wskaźniki aplikacji.
- Analiza kohortowa pomaga zrozumieć, czy istnieją jakieś problemy ekonomiczne.
- Testy A/B zwiększają rentowność aplikacji.
- Integracja z systemami zewnętrznymi pozwala na przesyłanie transakcji do usług atrybucji i analizy produktu.
- Kampanie promocyjne zmniejszają utratę odbiorców.
- Open-source SDK pozwala integrować subskrypcje z aplikacją w ciągu kilku godzin.
Dowiedz się więcej o tych funkcjach, aby szybciej wdrażać subskrypcje w aplikacji i zarabiać szybciej i lepiej.