StoreKit 2 API 의 새로운 기능과 Apple이 인앱 구매 통합 을 간소화한 방법
Updated: 3월 20, 2023
Apple은최근에 열린 WWDC2021에서새로운 버전의 StoreKit2를선보였습니다.iOS에서구매를 담당하는 프레임워크입니다.인앱구매 (in-apppurchase) 및구독 (subscription)기능이있는 앱 (app)의점유율이 꾸준히 증가하고 있으며,Apple은StoreKit2를출시하여 인앱 구매를 앱에 통합하는 것을 크게간소화했습니다.오늘우리는 서버 측에서 StoreKit2로작업하는 것 즉,App Store Server API를사용하는 법을 알아볼 것입니다.
인증요청
현재API 버전에서는요청을 보내기 위해 공유 암호 (SharedSecret)가필요합니다.이것은App StoreConnect에서얻을 수 있는 고정 암호 문자열입니다.API의새 버전은 요청 인증을 위해 JSONWeb Token (JWT) 표준을사용합니다.
키생성
가장먼저,요청을승인하는 데 사용되는 개인키를 생성합니다.App Store Connect를열고 Usersand Access 섹션으로이동한 다음 Keys탭으로이동합니다.인앱구매 키 유형을 선택합니다.새키를 다운로드합니다.또한ID가필요한데,App Store Connect API 탭에서찾을 수 있는 IssueID와동일한 페이지에서 복사할 수 있습니다.
토큰생성하기
다음단계는 요청을 승인하는 데 사용할 토큰을 만드는것입니다.이과정은 문서에자세히 설명되어 있으므로,크게신경쓸 필요는 없습니다.다음은파이톤을 위한 기성품 구현의 예입니다.모든새 요청에 대해 새 토큰을 생성하는 것은 의미가없습니다.토큰을생성할 때 기한을 최대 60분으로설정하고,이기간 동안 동일한 토큰을 사용합니다.
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}' }
서명된거래
새버전의 API에서는모든 거래가 JSONWeb Signature (JWS) 표준으로반환됩니다.이것은점으로 나누어진 세 부분으로 구성된 문자열입니다.
1. Base64 헤더.
2. Base64 거래 페이로드.
3. 거래 서명.
Base64(header) + "." + Base64(payload) + "." + sign(Base64(header) + "." + Base64(payload))
거래헤더
거래가인증되었는지 확인하려면 헤더가 필요합니다.Alg 키는암호화 알고리즘을 포함하고,x5c 키는인증서 체인을 포함합니다.
{ "kid": "AMP/DEV", "alg": "ES256", "x5c": [ "MIIEO...", "MIIDK..." ] }
거래페이로드
{ "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은거래 형식을 변경 및 확장했습니다.지금은그들과 함께 일하는 것이 더 편리하다고 생각됩니다.새형식에 대한 자세한 내용은 문서에나와있습니다.아래에서가장 중요한 변경 사항에 대해 설명하겠습니다.
- Apple은 appAccountToken 필드를 추가했는데, 시스템의 사용자 Id가 포함되어 있습니다. 이 ID는 UUID 형식이어야 하며, 구매가 초기화될 때 모바일 앱에서 설정됩니다. 설정된 경우, 이 체인의 모든 거래 (갱신, 결제 문제 등 (billing issue))에서 반환되므로, 어떤 사용자가 구매했는지 쉽게 파악할 수 있습니다.
- Apple은 또한 사용된 할인이 있다면, 그에 대한 정보가 포함된 offerType 및 offerIdentifier 필드를 추가했습니다. 다음은 offerType 필드의 값입니다.
- 1 — 출시 할인 (intro offer) (활성 또는 만료된 구독이 없는 사용자만 사용 가능)
- 2 — 프로모션 할인 (promo offer) (현재 및 만료된 구독에만 사용 가능)
- 3 — 할인 코드
프로모션할인 또는 할인 코드가 사용된 경우,offerIdentifier 키에사용된 할인의 ID가포함됩니다.과거에는서버 측에서 할인 사용을 추적하는 것이 불가능하여분석을 악화시켰습니다.이제분석에 할인 코드를 사용할 수 있습니다.
- Apple은 inAppOwnershipType 필드를 추가했는데, 이는 사용자가 제품을 구입했는지 또는 가족 구독 덕분에 제품에 액세스했는지 여부를 이해하는 데 도움을 줍니다. 가능한 값:
- PURCHASED
- FAMILY_SHARED
- 또 다른 새로운 필드인 type에는 거래 유형이 포함됩니다. 가능한 값:
- 자동 갱신 구독
- 비소모품
- 소모품
- 비갱신 구독
- Cancellation_date 및 cancellation_reason 필드는 이제 revocationDate 및 revocationReason이라는 새 이름을 갖습니다. 참고로, 환불로 인한 구독 철회 (subscription revocation) 사유와 날짜가 포함되어 있으므로, 새 이름이 더 논리적으로 보입니다.
- 모든 키는 camelCase 형식으로 반환됩니다 (모든 App Store Server API 요청과 마찬가지입니다).
- 모든 날짜는 밀리초 단위의 Unix 타임스탬프 형식으로 표시됩니다.
2024 subscription benchmarks and insights
Get your free copy of our latest subscription report to stay ahead in 2024.
사용자구독 상태
현재사용자의 구독상태를확인하려면,GET 요청을https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{originalTransactionId}로보냅니다.여기서{originalTransactionId}는사용자의 거래 체인 ID입니다.응답으로모든 구독 그룹에 대한 상태가 포함된 거래를 받게됩니다.
{ "environment": "Sandbox", "bundleId": "com.adapty.sample_app", "data": [ { "subscriptionGroupIdentifier": "39636320", "lastTransactions": [ { "originalTransactionId": "1000000819078552", "status": 2, "signedTransactionInfo": "eyJraWQiOi...", "signedRenewalInfo": "eyJraWQiOi..." } ] } ] }
상태키는 현재 구독 상태를 표시하며,이를기반으로 사용자에게 앱의 유료 기능에 대한 접근권한을 제공할지 여부를 결정할 수 있습니다.가능한값:
- 1 — 구독이 활성 상태이며, 사용자는 유료 기능에 접근할 수 있어야 합니다.
- 2 — 구독이 만료되었습니다. 사용자는 유료 기능에 접근할 수 없어야 합니다.
- 3 — 구독 상태가 결제 재시도 (Billing Retry)입니다. 즉, 사용자가 구독을 취소하지 않았지만 결제에 문제가 있음을 의미합니다. Apple은 60일 동안 카드 청구를 시도합니다. 사용자는 유료 기능에 접근할 수 없어야 합니다.
- 4 — 구독 상태는 유예 기간 (Grace Period)이며, 이는 사용자가 구독을 취소하지 않았지만 결제 문제가 발생했음을 의미합니다. App Store Connect에서 유예 기간이 설정되어 있으므로 사용자는 유료 기능에 접근할 수 있어야 합니다.
- 5 — 환불로 인해 구독이 취소되었습니다. 사용자는 유료 기능에 접근할 수 없어야 합니다..
SignedTransactionInfo키는체인의 마지막 거래에 대한 정보를 포함합니다.위에서형식에 대한 세부 정보를 찾을 수 있습니다.
구독갱신 (subscription renewal)에대한 정보
SignedRenewalInfo키에는구독갱신에대한 정보가 포함되어 있습니다.
{ "expirationIntent": 1, "originalTransactionId": "1000000819078552", "autoRenewProductId": "basic_subscription_1_month", "productId": "basic_subscription_1_month", "autoRenewStatus": 0, "isInBillingRetryPeriod": false, "signedDate": 1624520884048 }
이정보를 통해 다음 지불 기간 동안 구독이 어떻게 되는지이해할 수 있습니다.예를들어,사용자가자동 갱신을 취소한 것을 보게 되면 다른 구독 플랜으로전환하도록 제안하거나 할인을 제공할 수 있습니다.서버알림 (servernotifications)을통해 이러한 이벤트 (events)를추적하는 것이 편리합니다.이에대해서는 곧 알려 드리겠습니다.
사용자의거래 내역
사용자의거래내역을얻으려면,GET 요청을https://api.storekit.itunes.apple.com/inApps/v1/history/{originalTransactionId}로보냅니다.여기서{originalTransactionId}는사용자의 거래 체인 ID입니다.응답으로시간별로 정렬된 거래 어레이를 얻습니다.
{ "revision": "1625872984000_1000000212854038", "bundleId": "com.adapty.sample_app", "environment": "Sandbox", "hasMore": true, "signedTransactions": [ "eyJraWQiOiJ...", "joiRVMyNeyX...", "5MnkvOTlOZl...", ... ] }
요청에는20개이하의 거래만 포함할 수 있습니다.사용자가더 많이 갖고 있다면,hasMore 플래그의값은 true가될 것입니다.다음거래 페이지가 필요하면 수정 GET매개변수가포함된 요청을 다시 보내십시오.동일한키의 값이 포함됩니다.
서버거래 알림
서버알림은 신규 구매,갱신,결제문제 등에 대한 정보를 얻는 데 도움이 됩니다.이를통해 보다 정확한 분석을 구축하고,구독자상태 관리를 간소화할 수 있습니다.
기존서버 알림 (V1)은대부분의 문제를 해결할 수 있지만 가끔 불편합니다.주로사용자의 한 동작에 대해 여러 알림을 받는 상황에관한 것입니다.예를들면,이제사용자가 구독을 업그레이드할 때 Apple은두 가지 알림을 보냅니다.DID_CHANGE_RENEWAL_STATUS 및INTERACTIVE_RENEWAL.현재는이 케이스를 처리하려면,어떻게든상태를 저장하고 두 번째 알림이 전송되었는지 확인해야합니다.새로운버전의 서버 알림 (V2)에서는사용자의 하나의 작업에 대해 하나의 알림만 있습니다.이것은훨씬 더 편리합니다.
두번째 버전의 서버 알림에는 새로운 이벤트인OFFER_REDEEMED,EXPIRED 및GRACE_PERIOD_EXPIRED가있습니다.구독자상태를 훨씬 쉽게 관리할 수 있습니다.SUBSCRIBED 및PRICE_INCREASE이벤트는첫 번째 버전에서 개선된 이벤트입니다.
알림유형
알림에는이제 유형이 있으므로,사용자의모든 작업에 대한 알림 하나만으로 발생한 상황을이해할 수 있습니다.
알림유형
{ "notificationType": "SUBSCRIBED", "subtype": "INITIAL_BUY", "version": 2, "data": { "environment": "Sandbox", "bundleId": "com.adapty.sample_app", "appAppleId": 739104078, "bundleVersion": 1, "signedTransactionInfo": "eyJraWQiOi...", "signedRenewalInfo": "eyJraWQiOi..." } }
서버알림에는 앞에서 설명한 JWS형식의거래 및 갱신에 대한 정보가 포함됩니다.
샌드박스(Sandbox)환경에서작업하기
구매를테스트하려면 샌드박스 환경의 URL인https://api.storekit-sandbox.itunes.apple.com을사용해야 합니다.
새버전의 서버 알림은 아직 테스트할 수 없습니다.사용가능하게 되면 프로덕션 및 샌드박스 알림에 대해 다른URL을지정할 수 있습니다.샌드박스의경우 V2를선택하고 테스트용 프로덕션의 경우 V1을선택할 수 있습니다.
또한App StoreConnect는이제 다음을 허용합니다.
- 샌드박스 사용자의 구매 내역을 지웁니다. 더 이상 새 계정을 만들 필요가 없다는 뜻입니다.
- 샌드박스 사용자의 스토어 국가를 변경합니다.
- 샌드박스 구독 갱신 기간을 변경합니다. 예를 들면, 5분이 아닌 1시간 동안 지속되는 월간 구매를 할 수 있습니다.
결론
Apple은서버 측에서 인앱 구매 및 구독 작업을 크게 개선했습니다.제가볼 때 가장 유용한 새 기능은 다음과 같습니다.
- 본격적인 프로모션 할인 및 할인 코드 지원,
- 더 간단하고 유용한 서버 알림,
- 간단한 API 호출로 현재 구독 상태를 알 수 있는 기회,
- 사용자의 샌드박스 구매 내역 삭제.
새API로전환하는 것은 어렵지 않습니다.모든영수증의 originalTransactionId만있으면 됩니다.이미가지고 있을 가능성이 높죠.
어쨌든구독을 모바일 앱에 통합할 때 가장 어려운 부분은분석 시스템을 구축하고 경제적인 문제를 최적화하는것입니다.Adapty는이러한 문제를 잘 해결할 수 있습니다.
- 내장된 분석을 통해 주요 앱의 수치를 빠르게 파악할 수 있습니다.
- 코호트 분석 (cohort analysis)은 수익에 문제가 있는지 이해하는 데 도움이 됩니다.
- A/B 테스트 (A/B testing)는 앱의 수익성을 높입니다.
- 외부 시스템과의 통합을 통해 어트리뷰션 (attribution) 및 제품 분석 서비스에 거래를 보낼 수 있습니다.
- 프로모션 캠페인은 고객 손실을 줄입니다.
- 오픈 소스 SDK를 사용하면 몇 시간 내에 구독을 앱에 통합할 수 있습니다.
이러한기능에 대해 자세히 알아보셔서,앱에더 빨리 구독을 구현하고 더 빨리 앱에서 수익을창출하세요.