앱 인앱결제 붙이며 배운 것 — 결제창이 영구히 잠긴 이유

SUMMARY

결제창이 떴다고 결제가 끝난 게 아니었어요. 중단된 시도의 만료, 서버 가격 확인, 구매 권한 귀속, 첫 인앱 상품 심사까지 실제로 막힌 순서대로 적었어요.

인앱결제를 붙이면서 가장 크게 데인 곳은 결제창을 띄우는 부분이 아니었습니다. 결제가 실패하거나 중간에 끊긴 뒤였어요.

결제 버튼을 눌렀는데 창이 열리지 않았습니다. 처음엔 휴대폰 문제인 줄 알았어요. 앱을 껐다 켜고, 계정을 바꿔 보고, 상품 설정도 다시 봤고요. 그런데 원인은 그날보다 앞에 있었습니다. 이전 결제 시도가 중간에서 멈춘 채 "진행 중"으로 남아 있었고, 그게 새 시도를 막고 있었던 겁니다.

사용자는 돈을 내지도 못했고 다시 눌러 볼 수도 없었어요. 결제를 붙였는데 매출로 가는 문을 제가 잠근 셈이죠. 그때 알았습니다. 결제 기능의 완성은 창이 뜨는 순간이 아니라, 성공·실패·중단 뒤에 사용자가 정상 상태로 돌아오는 순간이라는 걸요.

화면에서 결제창을 한 번 띄우는 데 성공하면 마음이 놓입니다. 저도 그랬어요. 실제로는 가격이 어디서 확정되는지, 산 권한이 누구에게 붙는지, 영수증을 다시 가져왔을 때 어떻게 되는지, 중간에 앱을 닫으면 무엇이 남는지를 전부 따로 확인해야 했습니다.

중복 방지가 영구 잠금으로 바뀐 과정

중복 결제를 막으려고 "이미 진행 중인 시도가 있으면 새 결제를 열지 않는다"는 장치를 뒀습니다. 방향 자체는 맞았어요. 버튼을 여러 번 눌렀다고 결제 요청이 겹치면 훨씬 큰 문제가 생기니까요.

빠진 건 끝나는 조건이었습니다. 결제창을 연 뒤 사용자가 앱을 닫거나 네트워크가 끊기면 성공도 실패도 돌아오지 않을 수 있어요. 그런데 진행 기록에 만료가 없으면 그 상태가 계속 살아 있고, 앱은 다음 시도를 중복으로 보고 막습니다. 보호장치가 잠금 장치로 바뀐 거죠.

그래서 데이리플에는 중단된 시도가 스스로 풀리는 길을 넣었어요. 2026년 8월 구현 기준으로 결제 시도는 24시간이 지나면 만료되도록 했습니다. 이 시간이 모든 앱의 정답이라는 뜻은 아니에요. 중요한 건 무한히 남지 않는다는 점이고, 앱 성격과 결제 수단에 따라 더 짧을 수도 길 수도 있습니다.

“사용자가 결제창을 닫고 돌아왔을 때, 언제 다시 살 수 있나요?”

이 질문에 답이 없으면 중복 방지만 있고 회복은 없는 설계입니다.

화면이 보낸 금액은 근거가 아닙니다

결제 화면에는 상품명과 가격이 보입니다. 그러니 화면이 "이 상품은 이 금액"이라고 서버에 보내면 될 것 같아요. 간단하고요.

그런데 사용자의 기기에서 오는 값은 바뀔 수 있다고 전제해야 합니다. 화면의 숫자를 그대로 결제 기준으로 쓰면 표시를 조작해 더 낮은 금액을 보낼 길이 열려요. 서버는 상품 식별자만 받고, 저장해 둔 상품 정보에서 가격을 다시 확인해야 합니다. 이 원칙은 앱스토어 결제든 웹 결제든 똑같아요. AI에게 맡기면 안 되는 일에서 결제 검증을 따로 떼어 말한 이유가 이겁니다. 화면은 요청을 시작하는 곳이지 돈의 진실을 정하는 곳이 아니에요.

가격을 서버에서 확인한다고 끝도 아니었습니다. 무엇을 샀는지도 맞아야 하거든요. 데이리플의 평생권은 특정 공유 공간에 붙는 권한입니다. 결제 도중 사용자가 보고 있는 공간을 바꾸거나 오래된 화면이 잘못된 공간 번호를 보내면, 엉뚱한 공간에 권한이 붙을 수 있어요.

그래서 결제를 시작할 때 서버가 대상과 연결된 식별자를 만들고, 완료 때 그 식별자로 귀속을 확인하도록 했습니다. 영수증은 돈을 냈다는 사실을 보여 주고, 서버 식별자는 어디에 그 권한을 붙여야 하는지를 보여 줘요. 둘 중 하나만 있으면 반쪽입니다.

"복원"은 버튼 하나가 아니었어요

휴대폰을 바꾸거나 앱을 다시 설치한 사용자는 이미 산 상품을 복원해야 합니다. 복원 버튼 만드는 일은 쉬워 보이죠. 영수증을 읽고 유료 상태로 바꾸면 될 것 같으니까요.

문제는 과거 영수증에 지금 필요한 대상 정보가 없을 수 있다는 데 있었습니다. 돈을 냈다는 사실은 확인되는데 어느 공유 공간에 붙였는지는 알 수 없다면, 임의로 현재 공간에 적용하면 안 돼요. 잘못하면 한 번 산 권한이 관계가 전혀 다른 여러 공간으로 복제됩니다.

그래서 모르는 것은 모른다고 처리했어요. 자동 귀속할 근거가 없으면 어느 공간에도 함부로 붙이지 않고 확인 가능한 경로로 보냅니다. 사용자에게는 불편하지만 잘못된 권한 부여보다는 훨씬 안전하죠. 기능보다 중요한 것에서 말한 신뢰와 같은 이야기입니다. 결제에서 친절한 척 추측하는 건 친절이 아니에요.

테스트 계정인데 실제 돈이 나갈 수 있었습니다

Google Play 내부 테스트에 참여했다고 모든 구매가 자동으로 무료 테스트가 되지는 않아요. Google의 공식 결제 테스트 문서는 라이선스 테스터가 테스트 결제 수단을 쓸 수 있다고 설명하면서, 일반 테스트 트랙 사용자는 라이선스 테스터가 아니면 실제 결제가 될 수 있다고 경고합니다. 2026년 8월 기준으로 다시 확인한 내용이에요.

여기서 중요한 건 "내부 테스트"와 "결제 테스트"가 다른 말이라는 겁니다. 앱을 설치할 자격과 돈을 안 내고 구매할 자격은 따로 설정돼요.

그러니 실기기에서 누르기 전에 계정이 라이선스 테스터인지, 결제창에 테스트 결제 안내가 보이는지 확인해야 합니다. 테스트니까 괜찮겠지 하고 넘어가면 실제 카드가 승인됩니다.

첫 인앱 상품은 앱과 같이 심사를 받습니다

처음 만든 인앱 상품을 상품만 따로 심사에 보내려고 했어요. 그런데 Apple의 공식 인앱결제 설정 안내는 첫 인앱 구매 상품을 새 앱 버전과 함께 제출해야 한다고 적고 있습니다. 이후 상품은 앱 버전과 별도로 제출할 수 있지만 첫 상품은 다르다는 거죠. 이것도 2026년 8월 기준입니다.

이 차이를 모르면 상품 상태만 들여다보며 계속 기다리게 돼요. 실제로는 앱 버전 제출 묶음 안에 상품을 넣어야 심사가 시작됩니다.

심사용 화면과 실결제 성공도 구분해야 했습니다. 심사자가 상품과 구매 흐름을 확인할 자료가 있다고 해서 운영 환경의 결제·취소·복원까지 증명되는 건 아니니까요. 심사 통과는 문 하나가 열린 것이고, 실제 돈의 왕복은 별도로 확인해야 합니다. 앱스토어 심사가 가르쳐 준 것에서도 같은 말을 했어요. 심사 상태를 제품 완성의 대리 지표로 쓰면 빈칸이 생깁니다.

붙이고 나서 이 순서로 확인했습니다

먼저 상품과 가격의 출처를 봅니다. 화면 값이 아니라 서버와 스토어의 현재 상품이 기준인지 확인해요. 그다음이 귀속입니다. 결제한 계정과 권한을 받을 공간이 시작부터 완료까지 같은지 보고요. 세 번째는 회복. 사용자가 취소하거나 앱을 닫아도 다시 살 수 있는지, 중단 상태가 만료되는지 확인합니다. 마지막이 복원과 재설치예요. 이미 산 사람이 새 기기에서 다시 결제하지 않아도 되는지, 근거 없는 공간에는 권한이 붙지 않는지 봅니다.

혼자 만드는 앱의 검수에서 말하는 "다음 실행" 검수가 결제에서는 특히 중요했어요. 한 번 성공한 화면보다 두 번째 실행이 훨씬 많은 걸 말해 줍니다.

여기서 자주 미끄러집니다

"결제창이 열리면 연동은 끝났다." 창은 시작이에요. 성공, 취소, 네트워크 끊김, 앱 강제 종료, 복원, 중복 탭 뒤의 상태까지 봐야 합니다. 모든 실패를 미리 막으라는 뜻이 아니라, 실패 뒤 다시 시도할 수 있고 돈과 권한이 어긋나지 않게 만들라는 뜻이에요.

"앱스토어가 영수증을 주니 서버는 필요 없다." 스토어 영수증은 구매 사실을 확인하는 핵심 근거가 맞습니다. 다만 우리 서비스의 어느 사용자, 어느 공간에 권한을 붙일지는 스토어가 대신 정해 주지 않아요. 공유 공간처럼 서비스 내부 대상이 있으면 서버의 귀속 기록이 반드시 필요합니다.

"평생권은 구독보다 구현이 간단하다." 자동 갱신이 없으니 반복 청구는 단순하죠. 대신 복원과 영구 권한, 서비스가 오래 지속될 책임이 남습니다. 한 번 결제라고 한 번만 생각하면 안 돼요. 기기를 바꿔도 산 권리는 남아 있어야 하니까요. 비용 쪽 선택은 평생권과 구독 계산에서 따로 비교했습니다.

"테스트 결제는 실패해도 상관없다." 실패할 수 있어야 하는 건 맞아요. 다만 실패 뒤 잠김이 풀리는지, 중복 권한이 생기지 않는지를 보는 게 테스트의 목적입니다. 실패 화면만 보고 "테스트니까"라며 닫으면 가장 중요한 증거를 놓쳐요.

한편 이 글의 방식을 그대로 복사하면 곤란한 것도 있습니다. 24시간 만료가 대표적이에요. 결제 수단이 늦게 확정될 수 있는 서비스라면 너무 짧고, 즉시 결과가 오는 상품에는 너무 깁니다. 이 숫자는 잠금이 영구히 남지 않도록 둔 현재 기준일 뿐이에요. 오래된 영수증을 무조건 거절하라는 뜻도 아니고요. 구매 사실과 귀속 대상을 안전하게 연결할 다른 자료가 있다면 복원해야 합니다. 모르는 상태에서 임의로 붙이지 말라는 이야기죠. 그리고 결제 오류를 전부 보안 사고로 부풀릴 필요도 없습니다. 중단 상태가 남는 건 가용성 문제고 가격 조작을 믿는 건 금액 검증 문제예요. 위험의 종류를 나눠야 고치는 순서가 정확해집니다.

확인 기준과 한계

데이리플의 현재 상품과 가격은 2026년 8월 기준 공식 App Store 페이지에서 공간 평생권 9,900원으로 확인했습니다. 결제 시도 24시간 만료는 스토어 공통 규칙이 아니라 데이리플의 현재 구현 기준이에요.

가격과 스토어 정책은 바뀝니다. 이 글을 나중에 읽으신다면 결제 화면과 Apple·Google 공식 문서를 다시 확인하셔야 해요. 결제 전환율이나 장애 비율은 공개할 수 있는 집계가 없어 쓰지 않았습니다. 여기서 말할 수 있는 건 직접 확인한 실패 경로와 공식 정책까지입니다.

짧은 문답

결제창이 안 열리면 무엇부터 확인하나요?

이전 시도가 진행 중으로 남았는지, 상품이 현재 판매 가능한 상태인지, 테스트 계정이 맞는지부터 봅니다. 화면 코드를 고치기 전에 상태와 환경을 확인해야 없는 버그를 만들지 않아요.

결제 금액은 어디서 확정해야 하나요?

사용자 화면이 보낸 숫자가 아니라 신뢰할 수 있는 상품 정보에서 다시 확인해야 합니다. 화면은 어떤 상품을 사고 싶은지 전달하는 역할이고, 가격의 기준은 서버와 스토어에 둡니다.

평생권을 샀는데 공간을 바꾸면 어떻게 되나요?

상품 약관과 앱 설계에 따라 달라요. 데이리플처럼 공간에 붙는 권한이라면 어느 공간의 구매인지 시작할 때 확정하고, 완료 뒤 임의로 다른 공간에 복제하지 않는 게 핵심입니다.

테스트 트랙이면 실제 결제가 되지 않나요?

그렇게 단정하면 안 됩니다. Google 공식 문서상 라이선스 테스터가 아닌 테스트 트랙 사용자는 실제 결제가 될 수 있어요. 계정과 결제창의 테스트 표시를 확인한 뒤에 누르셔야 합니다.

한 번 결제 뒤에도 안심하도록

결제창보다 중요한 것은 돈과 공간 권한이 맞게 붙고, 중단돼도 다시 시도할 수 있는 구조였어요.

데이리플 보기 →
← PREV
재접촉 타이밍 설계 — 언제 연락할지 상담 안에서 정하세요
NEXT →
상담 후 24시간, 무엇을 보내야 할까 — 재촉 없이 약속을 이어가는 문자