---
title: "StoreKit 2 API 의 새로운 기능과 Apple이 인앱 구매 통합 을 간소화한 방법"
description: "StoreKit 2 리뷰: 새로운 기능, 이점 및 구현. Store Kit 2가 인앱 구매 통합을 간소화하는 방법 알아보기"
url: "https://adapty.io/ko/blog/storekit-2/"
language: "ko"
slug: "storekit-2"
category: "adapty-updates"
date: "2021-07-08T00:00:00.000Z"
date_modified: "2021-07-08T00:00:00.000Z"
author: "Kirill Potekhin"
---

# StoreKit 2 API 의 새로운 기능과 Apple이 인앱 구매 통합 을 간소화한 방법

Apple은최근에 열린 WWDC2021에서새로운 버전의 StoreKit2를선보였습니다.iOS에서구매를 담당하는 프레임워크입니다.인앱구매 (in-apppurchase) 및구독 (subscription)기능이있는 앱 (app)의점유율이 꾸준히 증가하고 있으며,Apple은StoreKit2를출시하여 인앱 구매를 앱에 통합하는 것을 크게간소화했습니다.오늘우리는 서버 측에서 StoreKit2로작업하는 것 즉,App Store Server API를사용하는 법을 알아볼 것입니다.

## 인증요청

현재API 버전에서는요청을 보내기 위해 공유 암호 (SharedSecret)가필요합니다.이것은App StoreConnect에서얻을 수 있는 고정 암호 문자열입니다.API의새 버전은 요청 인증을 위해 JSONWeb Token (JWT) 표준을사용합니다.

### 키생성

가장먼저,요청을승인하는 데 사용되는 [개인키를 생성](https://developer.apple.com/documentation/appstoreserverapi/creating_api_keys_to_use_with_the_app_store_server_api)합니다.App Store Connect를열고 Usersand Access 섹션으로이동한 다음 Keys탭으로이동합니다.인앱구매 키 유형을 선택합니다.새키를 다운로드합니다.또한ID가필요한데,App Store Connect API 탭에서찾을 수 있는 IssueID와동일한 페이지에서 복사할 수 있습니다.

![Creating a private key to work with StoreKit 2](https://adapty.io/wp-content/uploads/2023/03/60e45f2a6b787b58ee716273_ifcbx8vy24m3b4vkjvuqzriaa1rjd_d2vnqx-sx7jktuduuvx83lvvmec1btr_jbwjnh5caa8okqxaqkrggopw5oycrgv5i1qweg97ri08zjya_ah97ak_pmx6191mij_md5mew-1.png)
*AppStore Server API 작업을위한 개인 키 생성*

### 토큰생성하기

다음단계는 요청을 승인하는 데 사용할 토큰을 만드는것입니다.이과정은 [문서](https://developer.apple.com/documentation/appstoreserverapi/generating_tokens_for_api_requests)에자세히 설명되어 있으므로,크게신경쓸 필요는 없습니다.다음은파이톤을 위한 기성품 구현의 예입니다.모든새 요청에 대해 새 토큰을 생성하는 것은 의미가없습니다.토큰을생성할 때 기한을 최대 60분으로설정하고,이기간 동안 동일한 토큰을 사용합니다.

```python
import time, uuidnfrom authlib.jose import jwtnnBUNDLE_ID = 'com.adapty.sample_app'nISSUER_ID = '4336a124-f214-4d40-883b-6db275b5e4aa'nKEY_ID = 'J65UYBDA74'nPRIVATE_KEY = '''nu002du002du002du002d-BEGIN PRIVATE KEYu002du002du002du002d-nMIGTAgMGByqGSMBHkAQQgR/fR+3Lkg4...nu002du002du002du002d-END PRIVATE KEYu002du002du002du002d-n'''nnissue_time = round(time.time())nexpiration_time = issue_time + 60 * 60 # 1 hour expirationnnnheader = {n 'alg': 'ES256',n 'kid': KEY_ID,n 'typ': 'JWT'n}nnpayload = {n 'iss': ISSUER_ID,n 'iat': issue_time,n 'exp': expiration_time,n 'aud': 'appstoreconnect-v1',n 'nonce': str(uuid.uuid4()),n 'bid': BUNDLE_IDn}nntoken_encoded = jwt.encode(header, payload, PRIVATE_KEY)ntoken_decoded = token_encoded.decode()nnauthorization_header = {n 'Authorization': f'Bearer {token_decoded}'n}
```

## 서명된거래

새버전의 API에서는모든 거래가 JSONWeb Signature (JWS) 표준으로반환됩니다.이것은점으로 나누어진 세 부분으로 구성된 문자열입니다.

1. Base64 헤더.
2. Base64 거래 페이로드.
3. 거래 서명.

```json
Base64(header) + "." + Base64(payload) + "." + sign(Base64(header) + "." + Base64(payload))
```

### 거래헤더

거래가인증되었는지 확인하려면 헤더가 필요합니다.Alg 키는암호화 알고리즘을 포함하고,x5c 키는인증서 체인을 포함합니다.

```json
{
  "kid": "AMP/DEV",
  "alg": "ES256",
  "x5c": [
    "MIIEO...",
    "MIIDK..."
  ]
}
```

### 거래페이로드

```json
{
  "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은거래 형식을 변경 및 확장했습니다.지금은그들과 함께 일하는 것이 더 편리하다고 생각됩니다.새형식에 대한 자세한 내용은 [문서](https://developer.apple.com/documentation/appstoreserverapi/jwstransactiondecodedpayload)에나와있습니다.아래에서가장 중요한 변경 사항에 대해 설명하겠습니다.

- 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 타임스탬프 형식으로 표시됩니다.

## 사용자구독 상태

현재사용자의 [구독상태](https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses)를확인하려면,GET 요청을`https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{originalTransactionId}로보냅니다.여기서{originalTransactionId}`는사용자의 거래 체인 ID입니다.응답으로모든 구독 그룹에 대한 상태가 포함된 거래를 받게됩니다.

```json
{
  "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키에는구독[갱신](https://developer.apple.com/documentation/appstoreserverapi/jwsrenewalinfodecodedpayload)에대한 정보가 포함되어 있습니다.

```json
{
  "expirationIntent": 1,
  "originalTransactionId": "1000000819078552",
  "autoRenewProductId": "basic_subscription_1_month",
  "productId": "basic_subscription_1_month",
  "autoRenewStatus": 0,
  "isInBillingRetryPeriod": false,
  "signedDate": 1624520884048
}
```

이정보를 통해 다음 지불 기간 동안 구독이 어떻게 되는지이해할 수 있습니다.예를들어,사용자가자동 갱신을 취소한 것을 보게 되면 다른 구독 플랜으로전환하도록 제안하거나 할인을 제공할 수 있습니다.서버알림 (servernotifications)을통해 이러한 이벤트 (events)를추적하는 것이 편리합니다.이에대해서는 곧 알려 드리겠습니다.

## 사용자의거래 내역

사용자의[거래내역](https://developer.apple.com/documentation/appstoreserverapi/get_transaction_history)을얻으려면,GET 요청을`https://api.storekit.itunes.apple.com/inApps/v1/history/{originalTransactionId}로보냅니다.여기서{originalTransactionId}`는사용자의 거래 체인 ID입니다.응답으로시간별로 정렬된 거래 어레이를 얻습니다.

```json
{nt"revision": "1625872984000_1000000212854038",nt"bundleId": "com.adapty.sample_app",nt"environment": "Sandbox",nt"hasMore": true,nt"signedTransactions": [ntt"eyJraWQiOiJ...",ntt"joiRVMyNeyX...",ntt"5MnkvOTlOZl...",ntt...nt]
}
```

요청에는20개이하의 거래만 포함할 수 있습니다.사용자가더 많이 갖고 있다면,hasMore 플래그의값은 true가될 것입니다.다음거래 페이지가 필요하면 수정 GET매개변수가포함된 요청을 다시 보내십시오.동일한키의 값이 포함됩니다.

## 서버거래 알림

서버알림은 신규 구매,갱신,결제문제 등에 대한 정보를 얻는 데 도움이 됩니다.이를통해 보다 정확한 분석을 구축하고,구독자상태 관리를 간소화할 수 있습니다.

기존서버 알림 (V1)은대부분의 문제를 해결할 수 있지만 가끔 불편합니다.주로사용자의 한 동작에 대해 여러 알림을 받는 상황에관한 것입니다.예를들면,이제사용자가 구독을 업그레이드할 때 Apple은두 가지 알림을 보냅니다.DID_CHANGE_RENEWAL_STATUS 및INTERACTIVE_RENEWAL.현재는이 케이스를 처리하려면,어떻게든상태를 저장하고 두 번째 알림이 전송되었는지 확인해야합니다.새로운버전의 서버 알림 (V2)에서는사용자의 하나의 작업에 대해 하나의 알림만 있습니다.이것은훨씬 더 편리합니다.

두번째 버전의 서버 알림에는 새로운 이벤트인OFFER_REDEEMED,EXPIRED 및GRACE_PERIOD_EXPIRED가있습니다.구독자상태를 훨씬 쉽게 관리할 수 있습니다.SUBSCRIBED 및PRICE_INCREASE이벤트는첫 번째 버전에서 개선된 이벤트입니다.

![Transaction notifications in StoreKit 2 feature new events](https://adapty.io/wp-content/uploads/2023/03/60e45f2b5e95667e75ed3de3_inu1ytxwiyzwcicaojvveqfrprxa-jgwsrxh5suslcb9s765h6ih0yhhmxkj1ccxy1buvg-occjqmws0qh_v6lb05esbhsplz7nhxvzd9cg-keggzpeznviwovzbrw0b0lv0ntm-1.png)

### 알림유형

알림에는이제 유형이 있으므로,사용자의모든 작업에 대한 알림 하나만으로 발생한 상황을이해할 수 있습니다.

![Notifications in StoreKit 2 have types](https://adapty.io/wp-content/uploads/2023/03/60e45f2bb1b7fd418d73ce43_-ep2_vgedh1wjnfrpjuxyqsdjr-ifu4z_jcqiw0jjmuzycenjgumctezrk4xaozvgf3wpbh-cw-fdumwqatbpiqktueow5ecivpvp7-_jw7saewzdeqwagcysxkh6hw4ryibqr4-1.png)

알림유형

```json
{
  "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](https://adapty.io/)는이러한 문제를 잘 해결할 수 있습니다.

- 내장된 분석을 통해 주요 앱의 수치를 빠르게 파악할 수 있습니다.
- 코호트 분석 (cohort analysis)은 [수익에 문제가 있는지](https://adapty.io/blog/cohort-analysis-mobile-subscriptions/) 이해하는 데 도움이 됩니다.
- A/B 테스트 (A/B testing)는 앱의 수익성을 높입니다.
- 외부 시스템과의 통합을 통해 어트리뷰션 (attribution) 및 제품 분석 서비스에 거래를 보낼 수 있습니다.
- 프로모션 캠페인은 고객 손실을 줄입니다.
- 오픈 소스 SDK를 사용하면 몇 시간 내에 구독을 앱에 통합할 수 있습니다.

[이러한기능에 대해 자세히 알아보셔서](https://adapty.io/),앱에더 빨리 구독을 구현하고 더 빨리 앱에서 수익을창출하세요.
