---
title: "iOS 인앱 구매, 4부: 서버 측 구매 검증"
description: "iOS 인앱 구매: 서버 측 영수증 검증 (receipt validation). 이 기사에서는 영수증 검증이 중요한 이유와 설정 방법에 대해 설명합니다"
url: "https://adapty.io/ko/blog/ios-in-app-purchases-part-4-server-side-purchase-validation/"
language: "ko"
slug: "ios-in-app-purchases-part-4-server-side-purchase-validation"
category: "adapty-updates"
date: "2021-08-26T00:00:00.000Z"
date_modified: "2021-08-26T00:00:00.000Z"
author: "Kirill Potekhin"
---

# iOS 인앱 구매, 4부: 서버 측 구매 검증

## 서버구매 검증이란 무엇입니까?

서버검증 ([서버측 영수증 검증 (receiptvalidation)](https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/choosing_a_receipt_validation_technique))은구매의 진위 여부를 확인하는 방법입니다.장치기반 검증과 달리,서버검증은 — 기대하세요 — 서버 측에서 이뤄집니다.검증은기기 또는 서버가 Apple의서버에 요청하여 구매가 실제로 발생했는지 여부와유효한지 여부를 확인하는 것을 의미합니다.

## 구매를검증하는 이유는 무엇입니까?

서버검증은 필수가 아니라는 점에 유의해야 합니다.인앱구매는 검증 없이도 이루어집니다.그러나,다음과같은 몇 가지 이점이 있습니다.

1. **고급 결제 분석** 은 구독에 특히 중요한데, 활성화 후에 발생하는 모든 일을 장치에서 처리하지는 않기 때문입니다. 서버 구매 처리가 없으면, 현재 구독 상태를 검색할 수 없고, 사용자가 구독을 갱신 또는 취소했는지, 결제 문제가 있는지 등의 여부를 알 수 없습니다.
2. **구매의 진위 여부를 확인할 수 있음.** 거래가 사기가 아니고 사용자가 실제로 제품에 대한 비용을 지불했음을 확인할 수 있습니다.
3. **플랫폼 간 구독.**사용자의 구독 상태를 실시간으로 확인할 수 있는 경우, 다른 플랫폼과 동기화할 수 있습니다. 예를 들어 iOS 기기에서 구독을 구매한 사용자는 Android, 웹 및 기타 플랫폼에서 구독을 사용할 수 있습니다.**
4. **서버 측에서 콘텐츠 접근을 제어할 수 있게 되어**, 단순히 서버에 대한 요청을 실행하는 것만으로 구독 없이 데이터에 접근하려는 사용자로부터 보호합니다.

경험에따르면,첫번째 이점만으로도 서버 구매 처리를 설정하기에충분합니다.

[![demo page 5 5 1024x286](https://adapty.io/wp-content/uploads/2023/10/demo-page-5-5.png)](https://adapty.io/schedule-demo/)

## 구매검증

일반적인iOS 영수증검증 절차는 다음과 같습니다.

![612627f6144f7907460d97e6 server side receipt validation on ios 3 scaled](https://adapty.io/wp-content/uploads/2023/03/612627f6144f7907460d97e6_server-side-receipt-validation-on-ios-3-scaled.jpg)

### 공유암호 (sharedsecret) 생성

결제검증 요청을 보내려면,요청을승인하는 공유 암호를 포함시켜야 합니다.App Store Connect에서공유 암호를 생성할 수 있습니다.

공유암호는 특정 앱 (앱별암호)또는계정의 모든 앱 (주암호)에대해 생성할 수 있습니다.

앱별암호를 생성하려면 AppStore Connect에서앱 페이지를 열고 In-AppPurchases → Manage로이동하여 App-SpecificShared Secret을클릭합니다.열리는창에서 새 토큰을 생성하거나 기존 토큰을 복사할 수있습니다.

![61262b6bbe361198f2770efe ios 4 receipt validation 2 3](https://adapty.io/wp-content/uploads/2023/03/61262b6bbe361198f2770efe_ios-4-receipt-validation-2-3.png)
*앱별공유 암호 생성*

![61262bb30e6a8b0f91157699 ios 4 receipt validation 1 3](https://adapty.io/wp-content/uploads/2023/03/61262bb30e6a8b0f91157699_ios-4-receipt-validation-1-3.jpg)

계정의모든 앱에 대한 암호를 받으려면 Usersand Access 페이지를열고 SharedSecret 탭을선택합니다.

![61262bf72df8961e56fccd9d ios 4 receipt validation 3 3](https://adapty.io/wp-content/uploads/2023/03/61262bf72df8961e56fccd9d_ios-4-receipt-validation-3-3.png)
*모든앱에 대한 공유 암호를 찾는 방법*

### 결제검증 요청

공유암호를 받으면,Apple 서버에서검증받기 위해 영수증을 보낼 수 있습니다.이작업은 [verifyReceipt](https://developer.apple.com/documentation/appstorereceipts/verifyreceipt)요청을통해 이뤄집니다.POST 요청을[https://buy.itunes.apple.com/verifyReceipt](https://buy.itunes.apple.com/verifyReceipt)로보내야 합니다.요청의JSON 바디에서공유 암호를 *password* 필드로,영수증은*receipt-data* 필드로넘깁니다.또한*exclude-old-transactions* 매개변수도선택할 수 있습니다.만약*참* 값을 갖는다면,자동갱신 가능한 각 구독에 대해 전체 갱신 내역 대신 마지막거래만 받게 됩니다.

구매검증 요청의 페이로드는 다음과 같습니다.

```json
{
  "password": "f4d35830e3...52aae",
  "receipt-data": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
  "exclude-old-transactions": false
}
```

샌드박스(Sandbox)환경에서작업하는 경우 즉,구매를테스트하는 경우,[https://sandbox.itunes.apple.com/verifyReceipt](https://sandbox.itunes.apple.com/verifyReceipt)으로검증 요청을 보냅니다.공유암호,페이로드및 응답 포맷은 동일하게 유지됩니다.

*샌드박스환경에서 생성된 영수증은 프로덕션 서버 (Productionserver)에서검증할 수 없으며,그반대의 경우도 마찬가지임을 꼭 알아두십시오.* 그렇기때문에 실제 시스템에서 가장 좋은 방법은,상태키가 21007오류코드를 반환한 경우,첫번째 요청을 프로덕션 서버로 보내고 샌드박스 서버로리다이렉트하는 것입니다.이동작은 Apple직원이구매를 테스트하는 동시에 앱의 실제 사용자가 구매할수 있게 하므로,앱을리뷰하는 중에 필수적입니다.

다른[오류](https://developer.apple.com/documentation/appstorereceipts/status)중에서도,잘못된암호를 사용하고 있음을 의미하는 *21004* 오류코드가 중요합니다.사용자경험과 분석 정확도 모두에 영향을 미치므로,놓쳐서는안 됩니다.최악의경우,사용자가프리미엄 기능에 대한 비용을 지불한 후 접근 권한을받지 못하면,App Store에서앱이 제거될 수 있습니다.

검증에공한 경우 (*status*=0),응답에는사용자 거래의 세부 정보가 포함됩니다.

다음은결제 검증 요청 응답입니다.

```json
{
  "environment": "Production",
  "receipt": {
    "receipt_type": "Production",
    "adam_id": 123,
    "app_item_id": 123,
    "bundle_id": "com.adapty.sample_app",
    "application_version": "1",
    "download_id": 123,
    "version_external_identifier": 123,
    "receipt_creation_date": "2021-04-28 19:42:01 Etc/GMT",
    "receipt_creation_date_ms": "1619638921000",
    "receipt_creation_date_pst": "2021-04-28 12:42:01 America/Los_Angeles",
    "request_date": "2021-08-09 18:26:02 Etc/GMT",
    "request_date_ms": "1628533562696",
    "request_date_pst": "2021-08-09 11:26:02 America/Los_Angeles",
    "original_purchase_date": "2017-04-09 21:18:41 Etc/GMT",
    "original_purchase_date_ms": "1491772721000",
    "original_purchase_date_pst": "2017-04-09 14:18:41 America/Los_Angeles",
    "original_application_version": "1",
    "in_app": [
      {
        "quantity": "1",
        "product_id": "basic_subscription_1_month",
        "transaction_id": "1000000831360853",
        "original_transaction_id": "1000000831360853",
        "purchase_date": "2021-04-28 19:41:58 Etc/GMT",
        "purchase_date_ms": "1619638918000",
        "purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
        "original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
        "original_purchase_date_ms": "1619638918000",
        "original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
        "expires_date": "2021-05-05 19:41:58 Etc/GMT",
        "expires_date_ms": "1620243718000",
        "expires_date_pst": "2021-05-05 12:41:58 America/Los_Angeles",
        "web_order_line_item_id": "230000397200750",
        "is_trial_period": "true",
        "is_in_intro_offer_period": "false",
        "in_app_ownership_type": "PURCHASED"
      }
    ]
  },
  "latest_receipt_info": [
    {
      "quantity": "1",
      "product_id": "basic_subscription_1_month",
      "transaction_id": "230001020690335",
      "original_transaction_id": "1000000831360853",
      "purchase_date": "2021-08-04 19:41:58 Etc/GMT",
      "purchase_date_ms": "1628106118000",
      "purchase_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
      "original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
      "original_purchase_date_ms": "1619638918000",
      "original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
      "expires_date": "2021-08-11 19:41:58 Etc/GMT",
      "expires_date_ms": "1628710918000",
      "expires_date_pst": "2021-08-11 12:41:58 America/Los_Angeles",
      "web_order_line_item_id": "230000438372383",
      "is_trial_period": "false",
      "is_in_intro_offer_period": "false",
      "in_app_ownership_type": "PURCHASED",
      "subscription_group_identifier": "272394410"
    },
    {
      "quantity": "1",
      "product_id": "basic_subscription_1_month",
      "transaction_id": "230001017218955",
      "original_transaction_id": "1000000831360853",
      "purchase_date": "2021-07-28 19:41:58 Etc/GMT",
      "purchase_date_ms": "1627501318000",
      "purchase_date_pst": "2021-07-28 12:41:58 America/Los_Angeles",
      "original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
      "original_purchase_date_ms": "1619638918000",
      "original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
      "expires_date": "2021-08-04 19:41:58 Etc/GMT",
      "expires_date_ms": "1628106118000",
      "expires_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
      "web_order_line_item_id": "230000849023623",
      "is_trial_period": "false",
      "is_in_intro_offer_period": "false",
      "in_app_ownership_type": "PURCHASED",
      "subscription_group_identifier": "272394410"
    }
  ],
  "latest_receipt": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
  "pending_renewal_info": [
    {
      "auto_renew_product_id": "basic_subscription_1_month",
      "product_id": "basic_subscription_1_month",
      "original_transaction_id": "1000000831360853",
      "auto_renew_status": "1"
    }
  ],
  "status": 0
}
```

응답이상당히 복잡하여,[AppStore 서버API](https://habr.com/ru/company/adapty/blog/565442/)새버전에서 단순화되었으나,현재의구현은 파악이 용이합니다.

### 구독상태 및 거래 내역 검색

사용자가앱의 프리미엄 기능에 접근 권한을 갖는지 확인하려면구독 상태를 확인할 방법이 필요합니다.현재버전의 API에는구독 상태를 검색하기 위한 전용 요청이 없으므로,어떤경우에든 거래 내역을 처리해야 합니다.

기본적으로[*latest_receipt_info*](https://developer.apple.com/documentation/appstorereceipts/responsebody/latest_receipt_info)어레이는앱 측에서 완료되는 소모품을 제외하고 특정 사용자의모든 인앱 구매 거래를 포함합니다.이렇게하면 사용자의 전체 구매 내역을 검색할 수 있습니다.이는분석과 현재 구독 상태를 파악하는 데 상당히 유용합니다.

거래는항상 최신순으로 정렬되어 있는 것 같습니다.그래도확실히 하기 위해,거래에대한 날짜별 정렬을 구현하는 것이 좋습니다.

거래페이로드:

```json
{
  "quantity": "1",
  "product_id": "basic_subscription_1_month",
  "transaction_id": "1000000831360853",
  "original_transaction_id": "1000000831360853",
  "purchase_date": "2021-04-28 19:41:58 Etc/GMT",
  "purchase_date_ms": "1619638918000",
  "purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
  "original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
  "original_purchase_date_ms": "1619638918000",
  "original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
  "expires_date": "2021-05-05 19:41:58 Etc/GMT",
  "expires_date_ms": "1620243718000",
  "expires_date_pst": "2021-05-05 12:41:58 America/Los_Angeles",
  "web_order_line_item_id": "230000397200750",
  "is_trial_period": "true",
  "is_in_intro_offer_period": "false",
  "in_app_ownership_type": "PURCHASED",
  "subscription_group_identifier": "272394410"
}
```

현재구독 상태를 확인하려면,내역의최신 거래를 검색하여 *expires_date* 속성을확인하기만 하면 됩니다.예외적인경우는 유예 기간으로,이내용은 뒤에 다룰 것입니다.

분석을위해 다음 속성을 저장하는 것이 좋습니다.

- *product_id*: 구매한 제품의 텍스트 식별자.
- *transaction_id*: 거래의 고유 숫자 식별자. 각 구매 또는 갱신에는 해당 거래가 이전에 처리되었는지 여부를 확인하는 데 사용할 수 있는 고유한 식별자가 있습니다.
- *original_transaction_id*: 거래 내역의 고유 숫자 식별자. 이 식별자는 구독이나 평가판 활성화 시의 *transaction_id* 와 일치합니다. 그러나 추가 구독 갱신 시 *transaction_id* 는 변경되는 반면, *original_transaction_id* 는 그대로 유지됩니다. 이것은 구독이 갱신된 횟수를 추적하는 데 유용합니다.*
- *purchase_date* & *original_purchase_date*: 거래일 및 최초 거래일. 이것은 이전 속성과 동일한 논리 하에 작동합니다. *
- *expires_date*: 구독 만료일.
- *cancellation_date*: 이름을 보고 알기는 어렵지만, 구독 취소가 아닌 환불 날짜입니다. 응답에 이 필드가 있으면, 구독에 대한 사용자의 접근 권한을 만료시키고 분석에서 취소 처리할 수 있음을 의미합니다. 이 거래에서는 지불을 받지 않습니다.
- *is_in_intro_offer_period*: 구독 활성화 시 출시 할인 (intro offer)이 사용되었는지 여부를 나타내는 플래그.
- *is_trial_period*: 구독 활성화 시 평가판 기간이 있었는지 여부를 나타내는 플래그.
- *offer_code_ref_name*: 구독 활성화 시 사용된 할인 코드.
- *promotional_offer_id*: 현재 청구 기간을 입력할 때 사용된 프로모션 할인의 텍스트 식별자.
- *in_app_ownership_type:* 구독 소유권 유형. 사용자가 제품을 직접 구매했는지 아니면 가족 구독의 일부로 받은 것인지 표시합니다. 몇 가지 가능한 값은 다음과 같습니다.
- *PURCHASED: 사용자가 제품을 직접 구매했습니다.*
- *FAMILY_SHARED: 사용자가 가족 구독의 일부로 제품을 받았습니다.*

Apple이WWDC2021에서[발표한내용을 보면](https://developer.apple.com/videos/play/wwdc2021/10174)거래 정보에 *appAccountToken* 필드를추가할 계획입니다.여기에는시스템에 있는 사용자의 식별자가 포함됩니다.이식별자는 UUID형식이어야하고,구매가초기화될 때 앱 측에서 정의됩니다.정의된경우, 이내역의 모든 거래 (구독갱신 (subscriptionrenewal), 결제문제 (billingissue) 등)에서반환되므로,어떤사용자가 구매했는지 쉽게 파악할 수 있습니다.

또한*subscription_group_identifier* 매개변수를잘 알고 있어야 합니다.사용자가이전에 활성 평가판이나 출시 할인을 받은 거래를했다면,동일한구독 그룹에 접근 권한을 갖지 않아야 합니다.이것은서버 측에서 추적되어야 합니다.

### 구독갱신 정보,유예기간 (graceperiod) 및결제 문제

[*pending_renewal_info*](https://developer.apple.com/documentation/appstorereceipts/responsebody/pending_renewal_info)어레이는구독 갱신 데이터를 저장합니다.다음청구 기간에 구독에 어떤 일이 발생할지 알 수 있게해줍니다.예를들어,사용자가자동 갱신 선택을 해제한 것을 알게된 경우,다른요금제로 전환하도록 제안하거나 프로모션 할인을제시할 수 있습니다.이러한이벤트는 서버 알림 (servernotifications)으로쉽게 추적할 수 있는데,이에대해서는 곧 다루겠습니다.

구독갱신 데이터:

```json
{
  "auto_renew_product_id": "basic_subscription_1_month",
  "product_id": "basic_subscription_1_month",
  "original_transaction_id": "1000000831360853",
  "auto_renew_status": "1"
}
```

- **product_id:** 구매한 제품의 텍스트 식별자.
- **auto_renew_product_id:** 다음 결제 기간에 활성화될 제품의 텍스트 식별자. 현재 (product_id)와 다르다면, 사용자가 구독 유형을 변경했음을 의미합니다.
- **auto_renew_status:** 다음 청구 기간까지 구독을 계속할지 여부를 나타내는 플래그.
- **expiration_intent:** 구독 만료 이유. 몇 가지 가능한 값은 다음과 같습니다.
- **1 —** 사용자가 직접 구독을 취소했습니다.
- **2 —** 청구 문제로 인해 구독이 취소되었습니다.
- **3 —** 사용자가 가격 인상에 동의하지 않았습니다,
- **4 —** 구독 제품을 갱신할 수 없었습니다. (예: App Store Connect에서 제거된 경우)
- **5 —** 알 수 없는 이유.
- **Grace_period_expires_date:** 앱에 유예 기간이 있는 경우, 유예 기간의 만료 일자. 이 경우, 사용자는 거래 자체의 만료일이 아니라 여기에 지정된 일자까지 프리미엄 기능에 접근 권한을 가져야 합니다. 이 키가 있는 경우, 사용자에게 카드 정보를 업데이트하거나 잔액을 충전해야 함을 알릴 수 있습니다.
- **is_in_billing_retry_period:** 구독이 결제 재시도 상태 (billing retry status)인지 여부를 나타내는 플래그. 구독이 취소되지 않았지만 Apple에서 갱신 결제를 청구할 수 없었으며, 60일간 계속 결제를 시도할 것임을 뜻합니다.
- **offer_code_ref_name**: 다음 결제 기간에 사용할 제안 코드.
- **promotional_offer_id:** 다음 결제 기간에 사용할 프로모션 할인의 텍스트 식별자.
- **price_consent_status:** 사용자가 향후 구독 가격 인상에 동의했는지 여부를 나타내는 플래그. 값이 0인 경우, 해당 사용자가 구독을 취소하지 않도록 다른 제품이나 프로모션 할인을 제공해야 합니다.

### 소모성,비소모성및 비갱신형 구독

사용자에게자동 갱신 가능 제품 (auto-renewableproduct)이없는 경우 *latest_receipt_info* 그리고*pending_renewal_info* 키는반환되지 않습니다.이경우,거래는*receipt*→ [*in_app*](https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt/in_app)에서찾을 수 있습니다.거래양식은 자동 갱신 거래와 유사하지만,자동갱신 거래에만 해당되는 만료,갱신,할인및 기타 속성에 대한 필드가 없습니다.

유의할점은 *receipt*→ *in_app* 도자동 갱신 거래에 이를 수 있지만,더나은 방법은 *latest_receipt_info* 를사용하는 것인데,여기에포함된 구독 데이터가 가장 최신 상태이기 때문입니다.

## 서버거래 알림

얼마전까지는 구독 상태 변경을 추적하기 위해 복잡한시스템을 고안해야만 했습니다.예를들어,구독이갱신되었는지 여부를 파악하려면 구독 만료 24시간전부터 매시간 Apple서버에구독 상태 요청을 보내야 했습니다.Apple은시간이 지남에 따라 점점 더 많은 서버 알림을 추가하고있었고,[이알림이 이제 커버하는](https://developer.apple.com/documentation/appstoreservernotifications)범위는 거의 모든 구독 관련 중요 이벤트를 망라합니다.매우유용하죠.**Apple측에서변경 사항이 있으면,귀하의자체 서버에서 이에 대한 알림을 받게 됩니다.**.즉,새로운구매,구독갱신,결제문제 등에 대한 알림을 받게 됩니다.이를통해 훨씬 더 정확한 분석을 수집할 뿐 아니라,구독상태를 훨씬 쉽게 관리할 수 있습니다.

AppStore Connect에서서버 알림을 활성화할 수 있습니다.앱페이지를 열고 General-> App information으로이동합니다.그런다음 AppStore Server Notifications 필드의URL에링크를 입력하고 변경 사항을 저장합니다.

![61262e81750f790e298f2a77 ios 4 receipt validation 4 3](https://adapty.io/wp-content/uploads/2023/03/61262e81750f790e298f2a77_ios-4-receipt-validation-4-3.png)

서버알림:

```json
{
  "notification_type": "DID_RENEW",
  "password": "f4d35830e3...52aae",
  "environment": "PROD",
  "auto_renew_product_id": "basic_subscription_1_month",
  "auto_renew_status": "true",
  "unified_receipt": {
    "status": 0,
    "environment": "Production",
    "latest_receipt_info": [
      {
        "quantity": "1",
        "product_id": "basic_subscription_1_month",
        "transaction_id": "230001020690335",
        "original_transaction_id": "1000000831360853",
        "purchase_date": "2021-08-04 19:41:58 Etc/GMT",
        "purchase_date_ms": "1628106118000",
        "purchase_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
        "original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
        "original_purchase_date_ms": "1619638918000",
        "original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
        "expires_date": "2021-08-11 19:41:58 Etc/GMT",
        "expires_date_ms": "1628710918000",
        "expires_date_pst": "2021-08-11 12:41:58 America/Los_Angeles",
        "web_order_line_item_id": "230000438372383",
        "is_trial_period": "false",
        "is_in_intro_offer_period": "false",
        "in_app_ownership_type": "PURCHASED",
        "subscription_group_identifier": "272394410"
      },
      {
        "quantity": "1",
        "product_id": "basic_subscription_1_month",
        "transaction_id": "230001017218955",
        "original_transaction_id": "1000000831360853",
        "purchase_date": "2021-07-28 19:41:58 Etc/GMT",
        "purchase_date_ms": "1627501318000",
        "purchase_date_pst": "2021-07-28 12:41:58 America/Los_Angeles",
        "original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
        "original_purchase_date_ms": "1619638918000",
        "original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
        "expires_date": "2021-08-04 19:41:58 Etc/GMT",
        "expires_date_ms": "1628106118000",
        "expires_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
        "web_order_line_item_id": "230000849023623",
        "is_trial_period": "false",
        "is_in_intro_offer_period": "false",
        "in_app_ownership_type": "PURCHASED",
        "subscription_group_identifier": "272394410"
      }
    ],
    "latest_receipt": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
    "pending_renewal_info": [
      {
        "auto_renew_status": "1",
        "auto_renew_product_id": "basic_subscription_1_month",
        "product_id": "basic_subscription_1_month",
        "original_transaction_id": "1000000831360853"
      }
    ]
  },
  "bid": "com.adapty.sample_app",
  "bvrs": "0"
}
```

서버알림 [포맷](https://developer.apple.com/documentation/appstoreservernotifications/responsebody)은결제 검증 응답과 유사합니다.거래상세 정보는 *unified_receipt*→ *latest_receipt_info* 에저장됩니다.*Password* 키는앱의 공유 암호를 저장하므로,요청의진위를 확인할 수 있습니다.*Notification_type* 키는[이벤트유형](https://developer.apple.com/documentation/appstoreservernotifications/notification_type)을저장합니다.이중 가장 유용하다고 생각되는 것들입니다.

- DID_CHANGE_RENEWAL_STATUS: 사용자가 구독 자동 갱신을 끄거나 켰음을 의미합니다 (후자는 훨씬 더 드뭅니다). 사용자가 자동 갱신을 선택 해제한 경우, 다시 활성 구독자로 가입하도록 권할 수 있습니다.
- DID_FAIL_TO_RENEW: 결제 문제로 인해 구독을 갱신할 수 없습니다. 구독이 자동으로 취소되지 않도록 사용자에게 알려야 합니다.
- DID_RENEW: 구독이 성공적으로 갱신되었습니다.
- REFUND: 구독이 환불되었습니다. 이 구매가 부여하는 프리미엄 기능에 대한 사용자의 접근 권한을 제한하고, 분석에서 환불 처리해야 합니다 (즉, 돈을 잃는 것이죠).

## 결론

서버검증은 앱에서 수집한 데이터에 대한 분석을 강화할수 있습니다.또한사기꾼이 프리미엄 콘텐츠에 접근하는 것을 더 어렵게만들고,플랫폼간 구독을 할 수 있게 해줍니다.동시에,특히매우 정확한 데이터가 필요한 경우,서버검증 구현에는 상당한 시간이 걸릴 수 있습니다.이를위해서는 구독 업그레이드,구독크로스그레이드,평가판기간,프로모션/출시할인,유예기간,환불,가족구독 등 다양한 부수적인 경우를 고려해야 합니다.또한,Apple은1년이상 정기적으로 갱신되는 구독에 대해 수수료를30%에서15%로낮추는 정책을 가지고 있는 등의 여러 세부적인 점들을알고 처리해야 합니다.
