인앱 구독 결제 (In-App Purchase: IAP) 서버사이드 검증 1 - Apple

공부는 혼자하는 거·2024년 7월 1일
0

업무

목록 보기
16/17
post-custom-banner

IOS 모바일에서 결제를 터뜨리고 난 후 영수증을 받고 영수증을 서버에게 넘겨주면 검증하는 절차를 정리해보려고 한다.

  1. SpringBoot 3
  2. Apple storekit V2 API

구매 검증

Transaction 검증 storekit API

위 링크 보다시피, transactionId를 통해서 구매검증을 진행할 수 있다. (기존에는 verifyReceipt api (deprecated) 를 사용했다.) 이 API를 호출하기 위해서 권한이 필요한데, JWT를 만들어야 한다.

JWT 생성

필요한 JWT부터 만들어주겠다. JWT를 만드는데 필요한 변수는 여기서 얻을 수 있다. app store connect

  1. 사용자 및 액세스를 선택한 다음, 키 탭을 선택합니다.

  2. Key Type(키 유형)에서 In-App Purchase(인앱 구매)를 선택합니다.

  3. API 키 생성 또는 추가(+) 버튼을 클릭합니다.

    implementation("io.jsonwebtoken:jjwt-api:0.12.6")
    runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
    runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6")

API 요청에 대한 JSON 웹 토큰 생성

얻어낸 privateKey 형식 중에서 "+" "/" "\n" 같은 특수문자가 포함되어있으면, 파싱해주는 과정이 필요하다. 이거 때문에 삽질 오래했으니, 이거 보는 사람은 나같은 삽질 안 하길 바란다.

이렇게 얻어낸 JWT를 Bearer Token으로 보내주면 인증과정은 끝났다.

appleClient.get()
        .uri("/inApps/v1/transactions/${purchaseId}")
        .accept(MediaType.ALL)
        .header("Authorization", "Bearer $code")
        .retrieve()
        .toEntity(String::class.java)

이렇게 호출해서 응답을 받으면, signedTransactionInfo를 받을 수 있을 것이다.

이제 이 signedTransactionInfo는 JWT 형식이므로 Base64 디코딩해서 내가 원하는 정보를 손쉽게 얻을 수 있다.

정상적으로 디코딩 되었다면, 대략 아래와 같은 형태의 JSON 문자열로 변환할 수 있을 것이다.

{
  "bundleId": "test_30943ac3bc74",
  "currency": "test_d935a9f40f64",
  "environment": "test_e10158055909",
  "expiresDate": 42,
  "inAppOwnershipType": "test_20450bb42e39",
  "offerDiscountType": "test_7dbb7b7cd99c",
  "offerType": 19,
  "originalPurchaseDate": 17,
  "originalTransactionId": "test_ccf6ed159e63",
  "price": 22,
  "productId": "test_f86e2d9e6492",
  "purchaseDate": 6,
  "quantity": 99,
  "signedDate": 51,
  "storefront": "test_6b13f0b6fb93",
  "storefrontId": "test_7130b504bb19",
  "subscriptionGroupIdentifier": "test_533b068c896a",
  "transactionId": "test_5e41646f0e11",
  "transactionReason": "test_a1f025ff952e",
  "type": "test_a011716346be",
  "webOrderLineItemId": "test_abb51a6690bd"
}

이제 우리가 할 일은 간단하다. 파싱된 expiresDate 를 보고 구독이 만료되었는지 아닌지 판단하는 것이다. 만료되지 않고, 파싱이 제대로 되었다면, 구매가 정상적으로 이루어졌다고 판단하고, DB에 반영하면 된다.

만약 구매가 이미 이루어졌는데, 서버에서 받지 못했고 뒤늦게 다시 터뜨릴 경우 Auto-renewal 결제 데이터가 아래와 같은 형태로 뽑혀져 나올건데, 이럴 경우도, originalTransactionId는 동일하다. 리뉴얼 결제들은 매 결제 건 마다 transctionId가 새로 부여되지만 originalTransactionId는 동일하다.
따라서 이 originalTransctionId 기준으로 모든 결제건을 보고 signedDate 를 가지고 가장 최신 결제건을 뽑아낸 다음, expiresDate로 만료여부를 판단하면 된다.

{
  "bundleId": "test_3072f8d1fce1",
  "currency": "test_641bdd31d52f",
  "environment": "test_5d8bbfec06ea",
  "expiresDate": 73,
  "inAppOwnershipType": "test_475fa6a3198b",
  "originalPurchaseDate": 3,
  "originalTransactionId": "test_c1e9cf838e5e",
  "price": 30,
  "productId": "test_b8cacbec0a3d",
  "purchaseDate": 98,
  "quantity": 82,
  "signedDate": 96,
  "storefront": "test_9bda2e3e5ac0",
  "storefrontId": "test_c429084cb0ad",
  "subscriptionGroupIdentifier": "test_cbeae4080e67",
  "transactionId": "test_c844381dec9b",
  "transactionReason": "test_31a7d174dce2",
  "type": "test_e3ac9f1a4de1",
  "webOrderLineItemId": "test_73e5d7dd8e72"
}

최초 구독 결제냐, 아니면 재결제냐에 따라서 JSON Data 양식이 달라지기 때문에 파싱하는 부분을 신경써줘야 된다.

서버 Notification2

구독 검증 말고도 안전장치를 하나 더 마련해줘야 된다. Apple은 이를 위해서 Server to Server Notification 을 지원해준다.

App Store 서버 알림 활성화

App Store Connect > 나의 앱 > 앱 선택 > 일반 정보 > 앱 정보 > App Store 서버 알림

{
  "signedPayload": ""
}

signedPayload 로 형태로 JWT 형식의 데이터가 올 건데, 마찬가지로 payload 부분만 Base64로 디코딩해주고, 데이터를 확인해보면 아래와 같은 형식으로 올 것이다. 이 정보를 가지고 (notificationType) 구독상태를 최신으로 반영해주면 된다. transactionId의 경우, signedTransactionInfo를 가지고 다시 디코딩해주면 얻을 수 있을 것이다.

{
  "data": {
    "appAppleId": 0,
    "bundleId": "",
    "bundleVersion": "",
    "environment": "",
    "signedRenewalInfo": "",
    "signedTransactionInfo": "",
    "status": 0
  },
  "expirationDate": 0,
  "notificationType": "",
  "notificationUUID": "",
  "subtype": "",
  "signedDate": 0,
  "version": ""
}

주의할 점은, 결제 상태가 변경됨에 따라 Callback이 오는데 순서가 동기적이지 않다는 것이다. 따라서 , signedDate를 가지고 필터링해주는 과정이 필요하다.

다음은 구글 인앱 구독 결제 검증을 다뤄보겠다.

참고

https://developer.apple.com/account/resources/authkeys/list

iOS 인 앱 구매 (In App Purchase) 구현 가이드라인

(신)영수증 검증+Notification V2

iOS 인앱 정기결제(IAP Auto-renewable Subscription) 튜토리얼

profile
시간대비효율
post-custom-banner

0개의 댓글