반응형

이번 포스트에서는 Android 인앱결제 구독 동기화를 위한 Google RTDN (Real-time Developer Notifications) 개발 과정과 고민했던 내용들을 정리해보았습니다.

이전 글에서 공유한 iOS ASN 개발기에 이어, Android에서도 실시간 구독 상태 동기화가 필요해 RTDN을 도입하게 되었어요.


왜 RTDN을 도입했을까?

Google API를 통해 수동으로 구독 상태를 검증하고 있었습니다. 하지만 이 방식은 앱 접속 시점에만 동기화가 가능해 실시간성이 부족했습니다.

  • 앱을 켜지 않으면 구독 상태 동기화 불가능
  • 자동갱신/ 유예/ 취소 등의 상태 반영 지연 같은 문제로 실시간성이 떨어졌습니다.

이를 해결하고자 Google에서 제공하는 RTDN을 도입하게 되었습니다.


인프라 구성: Google RTDN → Pub/Sub → Backend

Google은 RTDN을 Pub/Sub 방식으로 제공합니다. 이 메시지를 백엔드에 안전하게 전달하기 위해 아래 구조로 구성했습니다:

Google Play RTDN → Google Cloud Pub/Sub → Backend 서버
  • Pub/Sub: Google이 메시지를 발행하는 통로
  • Backend: 구독 상태 업데이트 및 DB 반영

개발 중 겪었던 고민들

1. Pub/Sub 환경 구성

  • IOS와 아키텍처를 통일해서 Pub/Sub  AWS API Gateway → SQS를 구조를 구성해야 하나 고민을 하였지만 Pub/Sub도 충분 히 신뢰성 있는 메시징 서비스이고, 데드 레터 처리가 가능해서 Gateway → SQS 구조를 과감하게 배제

2. 환불(Refund) RTDN 이슈

  • voidedPurchaseNotification 값 뿐만아니라 SUBSCRIPTION_REVOKED 상태값에서도 환불이 들어옴

3. 테스트 환경 RTDN 수신 문제

  • Google은 Push 구독의 엔드포인트를 한 개만 운영 등록 가능하기 때문에, 개발 환경에서는 RTDN 수신 테스트가 어려움.
  • Pub/Sub의 구독을 개발용으로 생성해서 개발 엔드포인트에도 동시에 푸시 받는 구조로 해결.

구현 포인트 정리

  • notificationType 기반으로 상태 분기 처리
    → SUBSCRIPTION_RENEWED, CANCELED, EXPIRED 등
  • subscriptionState 기반으로 보조 판단
    → 실제 상태가 IN_GRACE_PERIOD 인데 RENEWED라고 오면 이중 체크
  • voidedPurchaseNotification 수신 시 환불 처리
  • 구독권 비교 시 orderId 기준으로 최신 구독 여부 판단
    → 기존 DB orderId ≠ RTDN orderId → 상태 재조회 + 동기화
  • 테스트 결제 구분 (res.testPurchase 확인 후 production 에선 무시)

마무리

향후에 메시징 서비스를 한쪽으로 통합해서 IOS, AOS 결제를 한 번 가공해서 백엔드 엔드 포인트로 넘기는 구조로 개선한다면 백엔드 비즈니스 코드가 유지 보수성이 개선될 수도 있을 것 같습니다!

728x90
반응형
반응형


이번 포스트에서는 제가 실제로 경험한 iOS 인앱결제(IAP)와 Apple Server Notification(ASN) 개발 과정에서의 고민과 해결 방법을 정리해 보려고 합니다. 특히, 실시간 구독 상태를 동기화하기 위해 ASN을 백엔드에 통합하는 과정에 집중했습니다.


왜 Apple Server Notification(ASN)을 도입했을까?

기존에는 앱에서 유저가 접속할 때마다 Apple receipt를 백엔드로 전송해서 유효성을 검증했는데요,
이 방식은 실시간성이 떨어지고, 사용자가 앱을 켜지 않으면 구독 상태가 반영되지 않는 문제가 있었습니다.

이를 해결하고자 애플에서 제공하는 실시간 알림 시스템인 ASN을 도입하게 되었습니다.


인프라 구성: Apple → API Gateway → SQS → Backend

애플은 ASN을 외부 HTTP(S) 엔드포인트로 전송하므로, 이를 안정적으로 처리하기 위해 아래와 같은 구조를 구성했습니다.

Apple ASN → API Gateway (HTTPS endpoint) → AWS SQS → Backend 서버
  • API Gateway: Apple에서 직접 호출할 수 있는 HTTPS 엔드포인트 제공
  • SQS: Apple의 긴 재시도 정책 대응 (ex: 수시간 간격으로 재시도됨)
  • Backend: SQS에서 메시지를 받아 구독 상태 업데이트 처리

이 구조 덕분에 유실 없이 비동기 안정 처리가 가능했습니다.


ASN 서명 검증: SignedDataVerifier

Apple ASN은 JWT 포맷의 서명된 데이터를 전송합니다. 이를 검증하기 위해 SignedDataVerifier를 사용하여 ASN의 유효성을 검증하고, payload를 디코딩했습니다.

주의할 점:

  • 로컬 테스트에서는 Apple이 메시지를 직접 전송하지 않기 때문에 테스트가 어렵습니다.
  • 실제 환경에서는 Apple에서 직접 HTTPS로 메시지를 보내기 때문에 반드시 정식 도메인/SSL 인증서가 필요합니다.

개발 중 겪었던 주요 고민

1. 테스트 어려움

Apple은 로컬 테스트 환경에서는 ASN을 전송하지 않기 때문에, 초기 테스트에 큰 제약이 있었습니다.
API Gateway + SQS 구조를 미리 만들어두고, Apple 콘솔에 등록해서 테스트해야 했습니다.

2. receipt-data 미포함 이슈

ASN에는 receipt-data가 포함되지 않아, 서버에서 별도로 Apple API를 호출해 구독 상태를 확인해야 합니다.

3. Apple의 재시도 정책 대응

ASN 전송 실패 시 Apple은 몇 시간 단위로 재시도하기 때문에, 안정적인 처리를 위해 SQS와 Dead-Letter Queue(DLQ)도 설정해두었습니다.


구현 포인트 정리

  • verifySignedData()로 ASN 서명 검증
    - Apple에서 보낸 ASN인지 검증하는 함수
  • SQS Lambda consumer에서 메시지 처리
    - SQS 쌓인 메시지를 읽고 백엔드로 전달하는 로직
  • 구독 상태(AUTO_RENEW_DISABLED, RENEWAL, CANCEL, EXPIRE 등)에 따라 DB 상태 업데이트

마무리

iOS ASN 개발은 쉽지 않았지만, 앱에서 유저가 앱을 열지 않아도 실시간으로 구독 상태를 반영할 수 있다는 점에서 큰 효과가 있었습니다.

728x90
반응형

+ Recent posts