티스토리 뷰

iOS/IAP

[ IAP ] 5. Purchase Validation

은조공주 2019. 12. 16. 16:33
반응형

안녕하세요, 은조공주🥰입니다.

벌써 5편이네요! 슬슬 끝이 보입니다. 이번 편에서는 영수증 검증 방식에 대해서 설명할 것입니다.

( 2021-03-16 기준 Apple document 업데이트 내용 반영되었습니다.)

 

[ 1.Choosing a Receipt Validation Technique ]

앱에 적합한 영수증 검증 방식을 선택

 

앱스토어 영수증에는, 앱 내의 앱 판매 또는 구매 기록이 들어있습니다. 앱이나 서버에, 영수증 검증 코드를 추가함으로써 구입한 컨텐츠를 인증하고 검증할 수 있습니다. 앱에 고유한? 적합한? 보안 솔루션을 채택하기 위해(?..), 영수증 검증에는 보안 코딩 기법의 이해가 필요합니다.

 

Choose a Validation Technique

영수증의 신뢰성을 확인하는 데에는 두 가지 방법이 있습니다.

  • 로컬 - 디바이스에서 영수증 검증을 하는 방법
    • IAP 영수증의 서명을 검증하기 위해 권장됨
  • 서버 사이드 - 서버가 AppStore를 통해 영수증 검증을 하는 방법
    • 구매 기록을 유지하고 관리하려 할 때 권장됨

여러 어프로치들을 비교해서, 앱의 구조에 적합한 방식을 선택해야 합니다. 필요에 따라 두 어프로치 모두를 채택할 수도 있습니다.

 

Consumable 타입 상품 구매는 (소모성 상품 - 코인이나 크리스탈 같은, ..) finishTransaction(_:)을 호출할 때까지만 영수증에 남아있습니다. 필요하다면 소모성 상품 구매 기록을 서버에서 관리해야 합니다. Non-consumable, ARS, NRS 상품 타입은 영수증에 영구적으로 기록이 남는다고 합니다. (제가 계속 줄여쓰고 있는데 혹시나 해서 추가합니다. ARS - Auto-Renewable Subscriptions, NRS - Non-Renewable Subscriptions 을 지칭합니다. ㅎㅎ)

ARS 상품의 경우, 서버측에서 영수증 유효성을 검증하는 것이 디바이스에서 검증하는 것보다 훨씬 장점이 많습니다.

 

On-device

validation

Server-side

validation

Validates authenticity of receipt

Y

Y

Includes renewal transactions

(there must be an internet connection to refresh the receipt.)

Y

Y

Includes additional user subscription information

N

Y

Handles renewals without client dependency

N

Y

Resistant to device clock change

N

Y

(참고 - 디바이스 상 영수증 검증에 갱신 트랜잭션을 포함하려면, 영수증을 리프레시하기 위해 네트워크에 연결되어 있어야 합니다.)

WWDC 2018 > Engineering Subscriptions - ARS 상품에 대한 영수증 검증을 더 알고싶다면 참고 ㅎㅎ

 

Verify Receipts

로컬에서 영수증을 검증하려면 PKCS #7 시그니처를 읽고 검증하는 코드가 필요하며, 서명된 페이로드를 파싱하고 유효성 검증을 하는 코드가 필요합니다. App Store 를 통해 검증하려면 앱과 서버간의 보안 연결이 필요하며, 서버에 AppStore를 통한 영수증 검증 코드가 있어야합니다.

일반적으로, 영수증은 구매가 완료됐거나 복구된 직후에 업데이트되지만, 앱이 실행 중이 아닐 때 변경사항이 발생할 수 있습니다. 필요한 경우 SKReceiptRefreshRequest를 호출하여, 백그라운드에서 구독이 갱신되는 경우 등 사용 중인 영수증이 최신인지 확인해야 합니다. (이 리프레시에는 네트워크 연결이 필요합니다.)

 

[ 2.Validating Receipts with the App Store ]

Secure한 서버에서, AppStore와 트랜잭션을 검증

 

AppStore 영수증은, 애플 인증서로 서명된 암호화된 이진 파일입니다. 이 암호화된 파일의 내용을 읽으려면, verifyReceipt를 접근해서 통해야 합니다. 이 API 응답은 앱에서 읽을 수 있는 JSON 바디를 돌려줍니다. AppStore와의 통신은 RFC 4627 에 정의된대로 JSON 딕셔너리로 구성됩니다. 그리고 바이너리 데이터는 RFC 4648 에 정의된대로 base64 인코딩 됩니다. Secure한 서버를 통해 앱스토어와 통신해서 영수증을 검증해야 합니다. (❗️중요 - 앱에서 직접 verifyReceipt 를 호출하지 않도록 하세요. 유저의 디바이스와 앱스토어간에 신뢰성 있는 연결을 직접 구축할 수 없기 때문입니다.❗️)

 

Fetch the Receipt Data

디바이스에서 영수증 데이터를 가져오려면, appStoreReceiptURL 메소드를 사용합니다. NSBundleappStoreReceiptURL을 가져와서 앱의 영수증 데이터를 찾고, 이를 Base64 로 인코딩합니다. 이 Base64-encoded 데이터를 서버로 보내야 합니다.

// Get the receipt if it's available

if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,

    FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {

    do {

        let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)

        print(receiptData)


        let receiptString = receiptData.base64EncodedString(options: [])

        // Read receiptData

    }

    catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
}

Send the Receipt Data to the App Store

서버에서는 1)receipt-data, 2)password (만약 영수증이 ARS 를 포함하고 있으면), 3)exclude-old-transactions 키값들을 가지고 JSON 객체를 생성합니다. HTTP POST 요청의 페이로드에 이 JSON 객체를 실어보내야 합니다. 샌드박스 테스트 중이거나, 앱이 아직 in review 상태라면 test URL 을 이용합니다. ( https://sandbox.itunes.apple.com/verifyReceipt )

프로덕션 환경 (마켓앱) 에서는 production URL 을 이용합니다. ( https://buy.itunes.apple.com/verifyReceipt )

(먼저 production URL로 영수증을 검증한 후, 응답 status code가 21007 이면 sandbox URL을 사용해서 검증해야 한다. 이 방법을 사용하면, 테스트 중인지 In Review 상태인지 등의 상태 분기 필요 없이 호출할 수 있다.)

Parse the Response

AppStore 의 응답 페이로드는 키값을 포함하는 JSON 객체입니다. in_app 배열에는 이전에 유저가 구입한 non-consumable, NRS, ARS 상품들이 포함되어 있습니다. 필요에 따라 이 응답필드를 사용해서 트랜잭션을 검증하면 됩니다. ARS 상품의 경우, 응답값을 파싱해서 현재 활성화되어 있는 구독 기간을 알 수 있습니다. 구독에 대한 영수증을 검증할 때, latest_receipt 필드는 가장 최근에 인코딩된 영수증을 포함한다. (리퀘스트의 receipt-data 와 동일한 값) 그리고 latest_receipt_info 필드는 최초 구독 트랜잭션과 그 이후 갱신 트랜잭션을 포함한 모든 트랜잭션에 대한 정보를 가지고 있지만, 복구 트랜잭션에 대한 정보는 가지고 있지 않습니다.

 

이 값들을 사용해서 ARS 가 만료되었는지 확인할 수 있습니다. 그리고 이 값들과 함께, expiration_intent 필드를 사용해서 구독 만료 원인도 알 수 있습니다. (계좌 잔액 부족 등의 비자발적 해지라거나, 이용자 직접 해지 등의 자발적 해지..☺️)

 

 

[ 3.SKReceiptRefreshRequest ]

앱 내에서의 유저 트랜잭션을 나타내는 - 영수증에 대한 리프레시 요청

class SKReceiptRefreshRequest : SKRequest

영수증이 유효하지 않거나 누락된 경우 이 API 를 사용하여 새로운 영수증을 요청합니다. 샌드박스 환경에서는 모든 프로퍼티 조합이 포함된 영수증을 요청할 수 있습니다.

SKReceiptRefreshRequest 객체를 사용하려면 이를 초기화하고, delegate 를 붙인 다음, request 의 start() 메소드를 호출합니다. 이 요청이 완료되면 delegate 에서 SKReceiptRefreshRequest 객체를 받게 됩니다. 

 

Initializing Request Refresh Requests

init(receiptProperties: [String : Any]?) // 영수증 리프레시 요청 생성

Receipt Properties and Keys

var receiptProperties: [String : Any]? // 영수증의 프로퍼티들

let SKReceiptPropertyIsExpired: String // 영수증이 만료되었는지 여부를 나타내는 값을 가진 키

let SKReceiptPropertyIsRevoked: String // 영수증이 취소되었는지 여부를 나타내는 값을 가진 키

let SKReceiptPropertyIsVolumePurchase: String // 영수증이 VPP 영수증인지 나타내는 값을 가진 키

( VPP 는 Volume Purchase Plan 인데요, 저도 정확하게 잘 알고 있지는 않아서 이곳에 설명을 쓰기는 조금 애매할 것 같습니다.. ㅎㅎ 무엇인지 궁금하신 분들은 https://vpp.itunes.apple.com/?l=en , https://www.cio.com/article/2957315/5-things-you-need-to-know-about-apples-volume-purchase-program.html 요 두 링크를 조금 읽어보시면 좋을 것 같아요. )

 

 

[ 참조 문서 ]

https://developer.apple.com/documentation/storekit/in-app_purchase/choosing_a_receipt_validation_technique

https://developer.apple.com/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store

https://developer.apple.com/documentation/storekit/skreceiptrefreshrequest

 

 

반응형

'iOS > IAP' 카테고리의 다른 글

[ IAP ] 4. Purchases  (3) 2019.12.12
[ IAP ] 3. Storefronts  (0) 2019.12.12
[ IAP ] 2. Product Information  (1) 2019.12.12
[ IAP ] 1. Essentials  (2) 2019.12.11
[ IAP ] Overview  (0) 2019.11.12
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함