본문은 인앱 결제(In-app Purchase) 검증 서버를 개발을 위한 전반적인 프로세스를 다루고 있습니다.
Nest.js 를 통해 개발하였으므로, Nest.js 에서 검증 서버를 개발하는 방법에 대해 다루나, 다른 개발 환경에서도 유사한 방식을 통해 개발할 수 있습니다.
서버 개발자의 관점에서 서술하므로, 클라이언트 사이드에서 발생하는 이슈들에 대한 상세한 내용은 다루지 않습니다.
해당 글을 읽으시는 시점에 Apple, Google 의 공식 문서가 수정되었을 수도 있으므로 (개발 기간 중 실제로 수정이 있었습니다) 공식 문서를 최우선적으로 정독하시는 것을 추천드립니다.
Google billing API 공식 문서
Apple swift Billing API 공식 문서
Apple Server API 공식 문서
현재 개발중인 프로젝트에서, 신규 BM 을 도입하기 위하여 인앱 결제가 요구되는 시점이 찾아왔습니다. 팀 내의 누구도 인앱 결제와 관련한 프로세스를 경험해본 팀원이 없었기에 해당 프로세스에 대한 리서치를 진행하였고, 인앱 결제와 관련된 국내 레퍼런스가 많지 않다는 것을 인지하였습니다.
해당 프로세스의 중요도에 비해 레퍼런스가 많지 않았기에, 또 다른 누군가는 저와 같은 시행착오를 겪지 않으셨으면 좋겠다는 마음에 회고를 작성하였습니다.
쓸때는 굉장히 쉬웠는데..
유저의 입장에서 겪어온 인앱 결제의 프로세스는 다음과 같습니다.
결제 버튼을 누른다 -> 인증을 시도한다 -> 결제가 완료된다 -> 지급이 완료된다
리서치를 진행하기 전까지는 유저의 입장에서만 인앱 결제를 경험해보았으므로, 인앱 결제의 프로세스는 위와 같이 굉장히 단순할 것이라고 생각하였습니다. 하지만 리서치를 진행하다 보니, 단순한 문제가 아님을 깨닫게 되었습니다.
일반적으로 개발사의 서버가 개입하는 결제 검증 과정이 요구되는 단계는 결제가 완료된다 -> 지급이 완료된다
의 단계이나, 개발자의 시선에서 전체 프로세스를 이해하여야 결제 검증 과정을 이해할 수 있으므로, 본문에서는 전체 결제 프로세스를 안내합니다.
실제 인앱 결제 레퍼런스들을 참고함에 있어, 상품의 종류들을 설명하지 않은 채로 프로세스를 설명하는 레퍼런스가 많아 이해가 어려운 내용이 간혹 등장하였기에 우선적으로 인앱 결제에서 사용되는 상품의 종류에 대해 먼저 인지하고 있어야 했습니다.
구글, 애플의 인앱 결제를 통해 구매 가능한 상품의 종류를 표로 간단하게 요약한다면, 다음과 같습니다.
위 표에서의 영수증은 인앱 결제에서
클라이언트 - 인앱 서버
간의 결제 트랜잭션에서 인앱 서버가 클라이언트로 제공하는 receipt-data(IOS), Product(AOS) 객체이며 이와 관련된 설명은 추후에 상세히 다루도록 하겠습니다.
위 표로만 모든 것을 이해하는 것은 어려우므로, 바로 아래에서 각 상품 종류들에 대한 상세한 설명들을 추가로 다루겠습니다.
재화의 구입 과정까지만 각 스토어가 관여하며, 재화의 관리는 개발사의 외부 서버 등에서 관리하여야 합니다. 재화가 환불되었을 경우, 이후의 과정에 대한 관리 또한 개발사의 몫입니다. 사용 만료기한이 없어야 하며, 선물 기능을 구현할 수 있습니다.
예시
네이버 웹툰의 쿠키
쿠키를 구입하는 과정까지만 각 스토어가 관리하며, 쿠키를 통해 컨텐츠를 구입하는 과정은 네이버 웹툰에서 관리합니다.
모바일 게임의 캐쉬
캐쉬를 구입하는 과정까지만 각 스토어가 관리하며, 해당 캐쉬를 통해 아이템을 구입하는 과정은 각 모바일 게임이 관리합니다.
제품에 대한 관리(등록, 수정 등)를 각 스토어의 콘솔에서 수행하여야 하며, 각 계정에 대한 구매 항목을 각 스토어에서 관리합니다. 다른 기기로 이전하였을 경우, 구매 항목을 복원할 수 있는 버튼이나 메뉴가 앱에 존재하여야 합니다. 구매 항목을 복원할 경우, 추가 비용없이 바로 복원이 가능해야 합니다. ~일 체험 제품을 만들수 도 있습니다.
예시
무료 게임 광고 제거
구매 이후의 광고 제거 기능 등을 개발사에서 직접 구현하며, 환불 내역, 구매 내역 등은 각 스토어에서 관리합니다. 환불에 대한 이벤트를 구독함으로써, 별도의 액션을 취할 수 있습니다. (환불이 일어난 경우, 웹 훅등과 같은 방법을 통해 등록되어 있는 리소스로 이벤트를 알려주기 때문에, 이를 구독하여 환불이 된 경우 다시 광고를 표시하는 등의 액션을 취해야 한다는 뜻입니다)
특정 기간 동안 추가적인 기능을 사용할 수 있도록 하며, 특정 주기별로 자동 결제되는 상품입니다. 최소 구독 기간은 7일이어야 합니다. 첫 번째 결제에 한해서만 클라이언트에서 결제를 진행하며, 이후의 결제는 등록된 리소스를 통해 이벤트를 발생시킵니다.
예시
유튜브 프리미엄
구독이 되어있지 않은 상태에서는 광고를 표시하지만, 구독이 되어 있는 경우 광고가 표시되지 않습니다. 비소모성 제품으로도 구현할 수 있지만, 각 회사의 BM 에 따라 자동 갱신 구독 상품으로도 만들 수 있습니다.
넷플릭스
구독이 되어 있는 경우에만 넷플릭스의 컨텐츠를 시청할 수 있습니다.
특정 기간동안 추가적인 기능을 사용할 수 있도록 하며, 기간이 만료되었다면 유저가 직접 다시 결제하여야 합니다. 앱 로그인을 필수로 요구하는 경우, reject 의 가능성이 있습니다.
예시
유튜브 영화 대여 서비스
7일 대여, 30일 대여 등의 기간동안 해당 영화를 시청할 수 있도록 하며, 기간이 만료되었다면 다시 대여합니다.
인앱 결제의 모든 상품에 대한 수수료는 30% 입니다. 구독형 서비스인 경우, 1년 이상 지속적으로 결제되는 경우에 15%로 수수료가 인하됩니다.
또한, 확률형 아이템의 경우, 각 보상의 확률을 무조건 공개해야 합니다.
저의 경우에는 모바일 게임 플랫폼 개발을 하고 있기 때문에, 저에게 필요한 부분만 찾아보았으나 상품과 관련하여 앱 reject 사유가 다양하게 존재하므로, 꼭 본인의 여건에 맞게 각 스토어의 공식 레퍼런스를 참고해야 합니다.
결제 프로세스와 관련하여 각 회사의 BM 에 따라, 시스템에 따라 많은 것들이 변경될 수 있으나 모든 경우에서 무조건적으로 수행되어야 할 가장 기본적인 프로세스에 대해 안내합니다. 결제 프로세스를 다이어그램으로 표현하면 다음과 같습니다.
다양한 서비스 로직에 따라, 이미 구매가 완료된 컨텐츠라면 결제를 발생시키지 않는 등의 처리 과정을 수행합니다. 각 앱의 BM 과 시스템에 맞게 별도의 로직을 구현하여야 합니다.
App Store 혹은 Play Store 에서 제공하는 Store Kit SDK 혹은 기타 라이브러리를 통해 구매 이벤트를 발생시킵니다. 해당 SDK 혹은 라이브러리를 통해 인앱 서버와 통신하여 결제를 발생시킬 수 있으며, 성공 시 인앱 서버는 암호화된 영수증을 응답합니다.
상품의 종류 단락의 표에서 나왔던 영수증이 해당 과정에서 등장합니다. 이 영수증을 통해 결제 검증 과정을 수행하고, 각 결제 트랜잭션을 식별하게 되므로 해당 객체는 분실되지 않도록 유의하여야 합니다.
구매 요청에 대한 응답으로 인앱 서버에서 제공해준 영수증을 개발사의 결제 검증 서버(저희 회사에서는 Payment Server라고 지칭하므로, Payment Server 라고 표현하겠습니다)로 전송하여, Payment Server 에서 이를 각 스토어의 검증 서버에 요청을 보냄으로써, 실제 결제가 발생하였는지와 관련해 검증을 진행합니다.
여기서 3번 과정에 대한 다음과 같은 의문이 생기실 수도 있습니다.
"클라이언트와 인앱 서버간의 통신을 통해 결제가 끝이났는데, 왜 굳이 또 한번의 검증 과정을 거쳐야하는가?"
실제로 과거 해당 과정이 없었던 시절에 탈옥, 위변조된 앱 파일 등을 통해 구매 과정을 위변조하여 실제 결제가 일어나지 않았음에도 결제 트랜잭션이 완료된 것으로 처리하여 상품에 대한 지급을 받을 수 있도록 하는 경우가 빈번했습니다. 이를 막기 위해 클라이언트에서 모든 결제 프로세스를 마치는 것이 아닌, 개발사의 서버에서 추가적인 검증 과정을 통해 해당 결제가 정상적으로 수행된 것인지를 판단할 수 있도록 하기 위해 해당 과정을 추가하게 되었다고 합니다.
결제 검증 과정 이후, 각 결제에 대해 수행하여야 할 서비스 로직을 수행합니다. 각 스토어 관리 페이지에서 상품을 등록하는 것으로 지급 처리 과정을 스토어에 위임할 수 있습니다. 하지만 이 경우, 모든 상품을 스토어에 수동으로 등록하여야 한다는 단점이 있습니다.
예시
사용자 DB 에 300 캐쉬 추가, 특정 사용자 광고 제거 등
지급 처리 과정까지 모두 완료되었을 경우, 결제가 최종적으로 완료되었음을 의미하는 ack 메시지를 인앱 서버에 전송하는 과정을 수행하여야 합니다. 해당 과정이 수행되지 않을 경우, 각 스토어에서 명시한 기간 뒤 자동적으로 환불 처리를 진행하므로 무조건적으로 지급 완료 요청을 전송하여야 합니다.
해당 과정이 필요한 이유는 비즈니스 로직에 따라 지급 처리 과정이 인앱 서버에서 진행되지 않고, 개발사의 서버에서 이루어질 수 있기 때문에, 인앱 서버에서는 실제로 구매 프로세스가 완료되었는지를 알 수 없기 때문입니다. 이로 인하여, 최종적으로 결제 프로세스가 완료되어 구매가 정상적으로 완료되었음을 명시해주어야 합니다.
또한, 구매 과정까지는 완료되었으나 네트워크 오류 혹은 기타 이슈로 인해 지급 처리가 완료되지 않은 경우(돈은 냈으나, 상품을 지급받지 못함) 이슈가 해결 된 뒤 다시 상품을 지급하기 위한 이유도 있습니다.
각 OS 에서의 지급 완료 요청은 다음과 같습니다.
iOS
StoreKit 의 finishTransaction() 메소드
Android
소모성 제품 : consumeAsync()
비소모성 제품 및 구독형 제품 : acknowledgePurchase()
또한, 지급 완료가 되지 않은 결제 내역은 다음과 같이 조회할 수 있습니다.
iOS
StoreKit 의 paymentQueue() 메소드
Android
queryPurchases() 메소드
각 스토어에서 시행하고 있는 환불 정책은 다음과 같습니다.
구글
환불의 기준은 48시간 이내이며, 시간이 지난 이후에도 환불 신청이 가능합니다. 판매자의 승인 절차가 요구되지 않으며, 구글의 판단하에 환불이 이루어 집니다.
애플
애플의 하드웨어 정책과 동일하게, 14일 이내의 결제 건의 경우 묻지마 환불이 가능하며, 판매자의 승인 절차가 요구되지 않습니다.
14일이 지난 결제 건의 경우, 애플의 자체 판단에 의한 일방적 환불을 진행할 수 있습니다.
구글과 애플 두 플랫폼 다 판매자의 승인 절차가 없기 때문에, iOS 에서는 환불 내역을 확인 할 수 있는 별도의 엔드포인트를 제공하거나, Server Notification 을 수신할 엔드포인트를 별도로 등록할 수 있으며, Android 에서는 RTDN(Real-Time Developer Notifications)를 제공합니다.
Apple Server Notification
Android RTDN
RTDN의 경우 GCP 의 cloud pub/sub 을 통해 MQ 를 구성할 수 있으며, iOS 또한 RTDN 과 유사한 방식으로 구성이 가능합니다.
이를 통해 게시(publish) 되는 환불 메시지들을 구독자(subscriber)들이 수신하여 환불과 관련된 서비스 로직을 수행하도록 시스템을 구성하여야 합니다. (허용되는 컨텐츠 롤백, 서비스 내 재화 차감 등)
이렇게나 쉽게 환불이 된다고? 라는 생각이 들 정도로 인앱 결제 프로세스를 조사하며 가장 당황스러웠던 부분입니다. 물론 각 개발사가 환불을 쉽게 해주지 않기 위해 환불에 대한 절차를 명확히 앱에 명시하지 않기 때문도 있지만, 이를 악용하는 유저 또한 분명히 있을 것이기 때문에, 환불 절차를 명확하고 쉽게 앱에 명시함과 동시에 악용하는 유저들에 대한 적절한 조치 또한 마련하여야 할 것 같다고 생각하였습니다.
인앱 결제 검증 서버 개발 A to Z
는 시리즈로 작성할 예정이며 인앱 결제 프로세스의 이해 편
을 통해 인앱 결제 프로세스에 대한 전반적인 내용에 대해 다뤘으므로, 다음편에서는 각 플랫폼의 검증 서버를 개발함에 있어서 발생했던 시행 착오와 과정을 작성하고자 합니다.
틀린 부분이 있다면 언제든 댓글을 통해 말씀해주세요.
감사합니다.