앱스토어 출시, 코딩이 끝이 아닙니다 — Gemini 3.0으로 만든 iOS 앱의 심사 통과와 수익금 입금까지 전 과정

개요

항목 내용
주제 '바이브코딩으로 iOS 앱 개발 및 출시' 시리즈 5편(마지막 편) — KindVerb 앱의 App Store Connect 등록, 인앱결제 심사, 수익금 정산
핵심 관점 AI가 코드를 다 써주는 시대에도 출시의 병목은 코드가 아니라 행정 절차다
핵심 키워드 바이브코딩 · Gemini 3.0 · Firebase App Check · In-App Purchase · Bundle ID 동기화 · ASO · 재무 보고서
대상 독자 유료 앱 / 구독 / 인앱결제를 준비 중인 1인 개발자, 1인 창업가
분석 데이터 발화 전사 204행, 화면 프레임 59장 (총 263행)

'바이브코딩 ⑤ (마지막편) 앱스토어 출시 및 수익확인 - feat. Gemini3.0' 시리즈 썸네일


핵심 메시지 3가지

① Gemini 3.0은 "여러 번 대화"를 "한 번의 대화"로 줄였다

과거 버전에서는 앱 하나를 완성하려면 반복적인 대화와 수정을 여러 번 거쳐야 했지만, 이번 KindVerb 프로젝트에서는 단일 대화 세션만으로 핵심 기능 구현이 끝났다. 개발 속도가 아니라 대화 왕복 수가 줄었다는 점이 체감상의 변화다.

② 싱글톤 금지 + 의존성 주입 — 아키텍처 규칙을 지킨 AI

이번 개발에서는 싱글톤 패턴 사용을 금지하고 의존성 주입(Dependency Injection) 방식으로 프로젝트 구조를 재구축했다. 파일 트리만 봐도 그 의도가 드러난다 — Protocols/AIServiceProtocol.swift, IAPServiceProtocol.swift 아래에 Services/AIService.swift, IAPService.swift가 분리되어 있고, ViewModels/PaywallViewModel.swift가 결제 상태를 별도 계층으로 관리한다.

③ 심사에서 떨어지는 이유는 기능이 아니라 "상품과 앱의 동시 제출"이다

무료 앱은 심사가 매끄럽게 지나가지만, 인앱결제·구독을 함께 출시하면 심사가 까다로워진다. 심사관은 앱 내 구입과 앱 심사를 별개로 볼 수 있어, "앱만 테스트했는데 상품이 왜 테스트가 안 되냐"는 사유로 리젝이 발생한다.


1. 완성된 앱 — 무료/Pro 권한 차등 구조가 핵심 설계

KindVerb는 관계 유형별 갈등 상황을 다루는 NVC(비폭력 대화) 기반 마음 편지 생성 앱이다. 결제 로직은 단순한 잠금 해제가 아니라 관계 타입을 권한 단위로 나눈 구조다.

구분 이용 가능 관계 카드 테마
Free 친구/동료, 배우자/연인 (2종) 차분한 블루/틸 계열 (E0F7FA, B2EBF2)
Pro + 부모자녀, 형제자매, 시댁/처가 (총 5종) 고급스러운 퍼플/핑크 계열 (F3E5F5, CE93D8)

이 구조는 ConflictCard.swift의 switch conflictType 분기 하나로 UI 색상과 아이콘까지 함께 갈라지도록 설계되어 있다. 즉 권한 모델 = 데이터 모델 = 디자인 모델이 한 파일에 수렴한다.

검증 로그 (터미널 / Xcode 콘솔)

Purchase successful: kindverb.pro...
Pro status updated to: true
Daily usage updated: 19/100
ExecutionEnv updated: kindverb.pro...

결제 → 권한 부여 → 사용량 차감이 한 흐름으로 찍히는 것을 콘솔로 확인하는 것이 이 단계의 검증 방법이다.

좌측 Xcode의 KindVerb 프로젝트와 우측 Gemini '내 Gems' 목록 — KindVerb(Gemini3.0) 커스텀 Gem이 개발 파트너로 상주하는 구조

기술 스택과 디버깅 관문

항목 값
AI 모델 Gemini 3.0 (커스텀 Gem: KindVerb(Gemini3.0))
개발 환경 Xcode 26.1 / iOS 17 / ChatGPT 5.1 병행
패키지 의존성 Firebase 12.0.0, AppCheck 11.2.0, Crashlytics 12.6.0
데이터 계층 SwiftData (SwiftData ModelContainer initialized successfully)

Firebase App Check 연동이 이 프로젝트의 최대 난관이었다. 시뮬레이터에서 error.appCheck.title 알림과 No active account 오류가 발생했고, 해결책은 Firebase 콘솔 → App Check 메뉴에서 디버그 토큰을 등록하는 것이었다.

class KindVerbAppCheckProviderFactory {
    func createProvider(with app: FirebaseApp) -> AppCheckProvider? {
        #if DEBUG
        return AppCheckDebugProvider(app: app)   // 디버그: 토큰 기반 빠른 테스트
        #else
        return AppAttestProvider(app: app)       // 릴리스: App Attest로 보안 강화
        #endif
    }
}

핵심 로그: App Check debug token: A385ED9E-… / App initialized in DEBUG mode with App Check DebugProvider

디버그와 릴리스의 Provider를 조건부 컴파일로 분리해두면, 개발 중에는 토큰 등록만으로 즉시 테스트하고 출시 시점에는 App Attest로 자동 전환된다. 이 한 줄 분기가 인앱결제 테스트 전체의 성패를 갈랐다.


2. 출시 절차 ① — Bundle ID는 "두 곳"에 동시에 존재해야 한다

앱스토어 등록에서 가장 흔하게 막히는 지점이다. 절차는 단순하지만 조건이 하나 더 붙는다.

① Xcode → Signing & Capabilities 탭에서 App Attest, In-App Purchase Capability 활성화
↓
② Apple Developer 사이트 → Certificates, Identifiers & Profiles → Identifiers에 App ID 등록
↓
③ App Store Connect → 신규 앱 생성 모달에서 해당 번들 ID 선택

위치 확인 값
Xcode Bundle Identifier com.beupgo.KindVerb
Apple Developer Identifiers KindVerb(com.beupgo.KindVerb), MetaLens(net.johjo.MetaLens), SaveFood(com.day1st.SaveFood), DeepCheck(net.johjo.DeepCheck) 등
App Store Connect SKU com.beupgo.kindverb.001

실패 증상: 신규 앱 모달의 번들 ID 드롭다운에 선택 번들이 없습니다. 인앱서, 설정자 앱 프로젝트에서 해당 ID를 등록하십시오. 오류 표시

원인은 명확하다. 개발자 계정과 Xcode 프로젝트 양쪽에 Identifier가 존재해야 App Store Connect가 그 ID를 인식한다. 개발자 사이트에 등록만 하고 Xcode에 추가하지 않거나, 그 반대인 경우 모두 선택 목록에 뜨지 않는다.

Xcode Signing & Capabilities(Team / Bundle Identifier / App Attest / In-App Purchase)와 우측 Identifiers 목록이 나란히 열린 검증 화면


3. 출시 절차 ② — 인앱결제 상품 등록과 "심사관에게 편지 쓰기"

수익화 메뉴의 앱 내 구입에서 상품을 등록한다. KindVerb의 상품은 다음과 같다.

항목 값
상품명 KindVerb Pro Lifetime
제품 ID kindverb.pro.lifetime
Apple ID 755821593
유형 비소모품
상태 초안 — 메타데이터 누락

상품을 등록해놔야 Xcode를 실행해 결제 흐름을 테스트할 수 있다. 즉 상품 등록이 테스트의 선행 조건이다.

리젝를 부르는 구조적 함정

App Store Connect 가이드에는 이렇게 적혀 있다.

"첫 번째 앱 내 구입은 새로운 앱 버전과 함께 제출해야 합니다."

이 문장이 실무에서 이렇게 작동한다.

상황 발생 문제 대응
앱 + 상품을 함께 제출 심사관이 앱만 테스트 → "상품은 왜 테스트가 안 되나" → 리젝 심사 정보에 테스트 방법을 사전 서술
리젝 후 재제출 "상품 승인이 먼저 나와야 테스트 가능" 역추적 심사관 요청란에 해당 논리를 그대로 재제출
상품만 별도 승인 앱 버전과 분리되어 재심사 상품과 앱을 같은 버전 번들로 묶어 제출

해결책은 선제적 설명이다. 개발자는 심사 정보에 이미지까지 미리 등록해두고 "심사할 때 이 부분을 고려해 주세요"라는 일종의 편지 형식 설명문을 작성했다.

In-App Purchase (Pro Lifetime) Testing Instructions
App Access & Login  ← 심사관용 샌드박스 계정 제공

"이 In-App 결제를 테스트할 때는 이 계정을 써주세요라고 같이 보내야 돼요."

샌드박스 테스트 계정(test@beupgo.com)과 결제 시나리오를 함께 넘기는 것 — 이것이 인앱결제 앱의 심사 통과 여부를 좌우하는 실질 변수다.

App Store Connect '앱 내 구입' 화면 — KindVerb Pro Lifetime 비소모품 상품이 '메타데이터 누락' 초안 상태로 등록된 모습


4. 출시 절차 ③ — 심사를 통과해도 끝이 아니다 (현지화·ASO·개인정보)

다국어 현지화 = 이미지 작업까지 포함

앱스토어에 올릴 스크린샷은 언어마다 따로 만들어야 한다. DeepCheck 앱의 실제 언어 설정이다.

현지화 대상 언어 (10종)
영어(미국) · 영어(영국) · 독일어 · 프랑스어 · 프랑스어(캐나다) · 이탈리아어 · 일본어 · 중국어(간체) · 베트남어 · 한국어

스크린샷은 단순 캡처가 아니라 마케팅 카피가 박힌 이미지다.

순서 스크린샷 카피
1 New or Used, Check DeepCheck Before You Deal.
2 26 Core Checks, Nothing Gets Missed.
3 The Results Don't Lie. See It All at a Glance.
4 Perfect Proof for Sellers, Peace of Mind for Buyers.
5 No Dead Pixels! Testing Every Screen.

크기 규격은 iPhone 6.5 디스플레이(1242 × 2688px) 기준이며, 이 5장을 언어 10개 조합으로 만들어야 한다. 개발자는 이 반복 작업을 위해 AppLaunchpad(theapplaunchpad.com) 를 활용했다.

"자기가 출시하려는 나라에 맞춰서… 굉장히 또 이런 거에는 노가다가 또 들어가겠죠. 그러니까 개발하는 게 끝이 아니라고 말씀을 드리고 싶어요."

그리고 진짜 승부처 — ASO

"심사를 제출해서 심사가 통과해요. 앱이 앱스토어에 올라갑니다. 아무도 따면 안 받습니다."

그래서 ASO(App Store Optimization) 가 중요하고, 마케팅과 홍보가 뒤따라야 한다. 심사 통과와 다운로드 사이에는 아무 인과관계가 없다.

개인정보 처리방침 — 로컬라이즈의 숨은 비용

'앱이 수집하는 개인정보' 메뉴에서 개인정보 처리방침을 항상 제공해야 하며, 이 역시 현지화한 언어마다 다 제공해야 한다.

항목 값
상태 데이터가 수집되지 않음
처리방침 URL https://ioho.net/entry/deepcheck-privacy-policy
효력 발생일 2025년 9월 11일
구성 1. 개인정보 수집에 대한 회사의 핵심 원칙 / 2. 앱 기능 수행을 위한 접근 권한 및 목적

개발자의 회피 전략은 블로그 카테고리 고정이다.

"어차피 웹으로 제공을 하고 있어서 제 블로그에 이렇게 앱을 만들 때마다 이 카테고리를 만들어가지고 여기에 다 개인정보 처리 방침을 넣어놨습니다."

앱을 늘릴 때마다 문서를 새로 쓰지 않고, 하나의 카테고리에 누적 → URL만 연결하는 방식이다.

App Store Connect '미리보기 및 스크린샷' 섹션과 언어 드롭다운 — 현지화 언어별로 스크린샷을 별도 제작해야 함을 보여주는 화면


5. 수익금은 언제, 어떻게 들어오는가

전제: 원래 목표는 KindVerb를 출시한 뒤 한 달을 기다려 수익금 지급까지 함께 확인하는 것이었으나, 정산 주기가 너무 길어 이미 서비스 중인 다른 앱의 데이터로 대체해 보여준다.

정산 주기 — 최소 1개월, 최대 2개월

수익 발생 기간 지급일 소요
2025년 9월 1일 ~ 9월 30일 2025년 10월 30일 익월 말일
2025년 10월 1일 ~ 10월 31일 2025년 12월 4일 다다음월 초

"한 달은 다음 달의 말일, 또 한 달은 그 다음 달의 4일, 뭐 이런 식으로 돈이 지급되는 것 같고요."

실제 입금 데이터 (App Store Connect → 지불 및 재무 보고서)

거래 ID 금액 판매 제품 수 지급 상태
358177061 39,004.00 KRW 6개 2025년 10월 30일 지급
356761597 95,370.00 KRW 17개 2025년 10월 30일 지급
  • 계정: 조현성 HYUNSUNG JO / 공급업체 #93400499
  • 데이터 입력일: 2025년 10월 29일 (지급 하루 전)
  • 두 항목 합계: 39,004 + 95,370 = 134,374.00 KRW, 총 판매 제품 23개

입금 계좌에서 보이는 두 갈래

입금 표기 의미
DBSLAASF 대한민국에서 발생한 수익금
Apple Inc. 해외에서 발생한 수익금

"대한민국 수익금을 보시면 세금 및 조정액이라는 게 있죠. 세금 처리까지 다 돼서 입금이 되는 거고 국세청에도 신고가 들어갑니다."

이 구조에서 실무 결론이 하나 나온다.

"이 유료 앱을 판매 하시려는 분들은 반드시 사업자 등록을 하셔야 돼요."

국내 수익금은 원천세 처리 후 순액으로 입금되고 국세청 신고까지 자동 연결되므로, 세금·조정액 라인을 해석할 수 있는 계정 상태(사업자 등록) 가 전제되어야 한다는 뜻이다.

App Store Connect '지불 및 재무 보고서' — 2025년 9월 기간의 39,004.00 KRW / 95,370.00 KRW 두 항목이 '2025년 10월 30일 지급'으로 표시된 화면


관점 포인트 — 실무에서 짚을 것 7가지

  1. Bundle ID는 3단 동기화 대상이다. Xcode ↔ Apple Developer ↔ App Store Connect. 두 곳에만 있어도 신규 앱 생성이 막힌다.
  2. 인앱결제 상품은 테스트의 선행 조건이다. 상품 등록 전에는 Xcode에서 결제 흐름을 검증할 수 없다.
  3. '메타데이터 누락' 상태를 방치하지 마라. 초안 상태의 상품은 심사 대상에서 빠지거나 앱만 심사되는 사고를 만든다.
  4. 심사 정보란은 '변명 공간'이 아니라 '테스트 안내서'다. 샌드박스 계정 + 결제 시나리오 + 이미지 사전 등록을 함께 넣는다.
  5. App Check는 #if DEBUG로 Provider를 분리한다. 디버그 토큰과 App Attest를 한 파일에서 조건부로 갈아끼우는 패턴이 표준 해법이다.
  6. 현지화는 텍스트가 아니라 이미지 작업이다. 언어 10종 × 스크린샷 5장 = 50장의 제작물. 개인정보 처리방침도 같은 배수다.
  7. 정산은 1~2개월 뒤, 국내분은 세액 차감 후 입금된다. 현금흐름 계획에 이 시차를 반드시 반영하고, 유료 판매는 사업자 등록 이후로 잡는다.

최종 핵심 정리 — 확인해야 할 질문

질문 답
Gemini 3.0이 만든 가장 큰 변화는? 반복 대화 없이 단일 세션으로 앱 완성
아키텍처에서 지킨 규칙은? 싱글톤 금지 + 의존성 주입
인앱결제 테스트가 막혔을 때 첫 조치? Firebase 콘솔 App Check 디버그 토큰 등록
앱 등록이 안 될 때 확인할 것? 개발자 계정과 Xcode 양쪽의 Identifier
리젝를 막는 제출 원칙? 앱 내 구입을 새 앱 버전과 함께 제출 + 심사관용 테스트 계정 동봉
심사 통과 후 해야 할 일? ASO / 마케팅 / 홍보
수익금 지급 시점? 9월분 → 10월 30일, 10월분 → 12월 4일
국내·해외 수익금 구분 DBSLAASF(국내) / Apple Inc.(해외)
유료 앱 판매 전제 조건 사업자 등록 (세금·조정액 처리 및 국세청 신고 연동)

한 줄 요약: 바이브코딩은 앱이 "만들리는 속도"를 혁신하지만, 앱스토어에서 돈이 "들어오는 속도"는 여전히 Bundle ID 동기화·인앱결제 동시 제출·현지화·사업자 등록이라는 구식한 행정 절차가 결정한다.