Subscription Summary
💡 구독 결제 서비스 개발 관련 주요 내용 요약
용어 정리
- Subscription(구독)
- 앱 내 결제 유형 중 하나로 정기 결제 상품을 의미합니다.
1. 정기 결제 수명 주기와 구독 상태
(1) 정기 결제 수명 주기
📚 참고 문서 Android : Google Play 결제 시스템 iOS : apple developer api - Subscription.RenewalState
State
현재 네 가지 상태를 기준으로 구독 서비스를 처리하고 있습니다.
- Active(활성 상태)
- Canceled(취소 상태)
- 구독 활성화 상태의 유저가 구독을 취소한 상태
- OnHold(보류 상태)
- 사용자의 결제 수단으로 결제하는 데 실패한 후 결제 문제가 해결되지 않은 상태
- Terminated(만료 상태)
각 플랫폼에서는 InGracePeriod(iOS/android), Paused(android), revoke(iOS) 같은 더 디테일한 구독 상태도 제공하는데 프론트에서는 따로 핸들링하고 있지 않습니다.
(2) 구독 상태 예시
case A
|——————————————————|—————————————|
3/10 구독 시작 3/20 구독 취소 4/10 구독 만료
유저의 구독 상태
3/10 ~ 3/20 : Active
3/20 ~ 4/10 : Canceled
4/10 ~: Expired
서비스 제공 기간
- 3/10 ~ 4/10 까지 유저는 구독 서비스 혜택을 누릴 수 있음.
- 4/11부터 혜택 제공하지 않음.
case B
|———————————————————————————————-|——————————————
3/10 구독 시작 4/10 구독 갱신 실패
유저의 구독 상태
3/10 ~ 4/10 : Active
4/10 ~ : onHold (안드로이드는 보류 기간 최대 30일, iOS는 최대 60일)
- 사용자의 결제 수단으로 결제 시도했으나 실패한 경우 onHold 상태로 변경됩니다.
- 보류 기간 동안 결제 수단 갱신이 없으면 Expired 상태로 변경됩니다.
- 보류 기간 동안 결제 수단 갱신이 이루어져서 정상적으로 정기 결제가 복구되었다면 Active 상태로 변경됩니다.
서비스 제공 기간
- 3/10 ~ 보류 기간이 종료될 때 까지 구독 서비스의 혜택을 누릴 수 있습니다.
- 보류 기간이 종료된 이후에는 만료 상태로 변경되어 혜택 누릴 수 없습니다.
- 만약 4/10일 이후에 유저가 유효한 결제 수단으로 갱신한 경우, 구독 상태는 Active로 변경되고 유저는 계속해서 구독 서비스 혜택을 누릴 수 있습니다.
- 결제일은 결제 수단이 갱신되어서 결제가 이루어진 날짜로 변경됩니다. (4/15일날 결제 수단을 갱신해서 정상적으로 정기 결제가 복구되었다면 다음 결제일은 5/15일)
즉, Active / Canceled / onHold 상태일 때는 구독 혜택 제공하고 Expired 상태일 때에만 구독 혜택 제공하지 않습니다.
2. 스토어 ↔ 서버 ↔ 프론트
프론트는 서버에서 전달해주는 유저 상태를 신뢰합니다.
- 현재 스토어를 통해 유저의 구독 상태를 확인하는 로직은 없습니다.
- 스토어에서 서버로 구독 갱신 노티 전달 → 서버 데이터 갱신 → 프론트 데이터 갱신 순서로 상태 변경이 이루어집니다.
프론트에서는 최초 구독시 스토어를 통해 결제 처리해주는 것 이외에는 스토어 api를 사용하는 경우가 없습니다.
- 현재 구독 취소 요청도 스토어 api를 사용하지 않고있습니다. 유저를 스토어로 랜딩시켜 유저가 스토어를 통해 구독 취소하도록 유도하고 있습니다.
신규 구독 요청에 대해 서버에 검증 요청 보냅니다. (POST /me/subscription)
- 요청 보낼 때 같이 전달하는 param은 다음과 같습니다.
sku(iOS/android)
revenue(iOS/android)
currencyCode(iOS/android)
timezoneOffsetMinutes(iOS/android)
- 체크인 포인트는 유저의 타임존 기준으로 하루에 한번 지급합니다. ‘하루’라는 개념을 처리하려면 유저의 타임존을 알고있어야하기 때문에 서버에 유저의 타임존을 전달합니다.
originalTransactionId(only iOS)
- 가끔 스토어에서 originalTransactionId가 null로 넘어올때가 있습니다. 굉장히 적은 케이스이긴한데 꾸준히 발생하고 있습니다.
- 해당 필드가 null로 넘어오면 대신 transactionId를 서버에 넘겨줬었는데 이 부분 때문에 서버에서 구매 이력 추적에 어려움을 겪으셨던 히스토리가 있습니다. 그래서 현재는 null로 넘어올때 따로 처리하지 않고 null 그대로 서버로 넘겨주고 있습니다.
transactionId(only iOS)
receiptData(only iOS)
- iOS는 영수증이 암호화되어있습니다. 암호화된 영수증 그대로 서버에 전달합니다.
orderId(only android)
purchaseToken(only android)
서버 검증이 완료되었으면 스토어 측에 구매 확정 처리를 해줘야 구매가 완료됩니다.
- 구독 상품은 비소모성 상품입니다.
- 스토어에 구매 확정 요청 보낼 때(
finishPurchase()) 해당 상품이 소모성 상품인지, 비소모성 상품인지를 구분해서 요청 보내야합니다. 포인트 상품은 소모성 상품이고 구독 상품은 비소모성 상품입니다.
- 구매 확정처리가 안되면 추후에 환불처리됩니다.
4. Sandbox
- 샌드박스 환경에서 구독 갱신되는 주기를 설정할 수 있습니다. 저는 디폴트값으로 두고 개발했습니다.
- 샌드박스 환경에서 구독 상품을 구매하면 구매 정보가 스토어에 기록되지 않습니다.