IAP (인앱결제) 연동 가이드
Google Play와 App Store 인앱결제를 처음부터 끝까지 설정합니다.
시작 전 준비
| 항목 | 필요 조건 |
|---|---|
| Unveily 플랜 | Pro (IAP 기능 포함) |
| Android | Google Play 개발자 계정 + 결제 프로필 설정 완료 |
| iOS | Apple Developer Program 계정 + App Store Connect 앱 등록 |
| 앱 빌드 | Android: 릴리즈 서명된 AAB 업로드 완료 (최소 1회) |
IAP 활성화 게이트 — coming_soon
IAP 기능은 다음 두 조건이 모두 충족되어야 동작합니다.
- 라이선스에
iap기능이 포함되어 있어야 합니다 (Pro 플랜). - Unveily 서버 측
iapEnabled플래그가 켜져 있어야 합니다.
둘 중 하나라도 충족되지 않으면 queryProducts / purchase / restorePurchases는 결제창을 띄우지 않고 다음을 반환합니다.
{ "status": "coming_soon" }모든 결과 콜백에서 이 상태를 가장 먼저 확인해, 결제 UI를 숨기거나 안내 문구를 표시하세요.
function handleIapResult(result) {
if (result.status === "coming_soon") {
// 아직 IAP가 활성화되지 않음 — 결제 버튼 숨김 / 안내 표시
document.getElementById("iap-section").hidden = true;
return false;
}
return true;
}
// 예: 구매 결과에서 게이트 확인
window.onPurchaseResult = async function(result) {
if (!handleIapResult(result)) return; // coming_soon이면 여기서 종료
if (!result.success) return;
// ... 서버 검증 진행
};Android — Google Play 설정
Mission 1 — Google Play Console 앱 준비
1-1. 앱 생성 및 AAB 업로드
- Google Play Console 접속 → 앱 만들기
- 앱 이름, 기본 언어, 앱/게임 유형 선택 후 생성
- 내부 테스트 트랙에 릴리즈 서명된 AAB 파일 업로드
- 스토어 등록정보, 콘텐츠 등급, 정책 등 필수 항목 완료 후 검토 제출
릴리즈 키스토어
app/build.gradle.kts에 signingConfigs.release가 설정되어 있어야 합니다.
자세한 내용은 첫 빌드 문서를 참고하세요.
1-2. 결제 프로필 설정
Google Play 결제 기능을 사용하려면 판매자 계정이 필요합니다.
- Play Console → 설정 → 결제 프로필 → 판매자 계정 만들기
- 비즈니스 정보, 은행 계좌, 세금 정보 입력 완료
- 승인까지 최대 1–2 영업일 소요
Mission 2 — 인앱 상품 등록 (Android)
구독 상품 등록
- Play Console → 앱 선택 → 수익 창출 → 구독
- 구독 만들기 클릭
- 다음 항목 입력:
| 항목 | 예시 값 | 설명 |
|---|---|---|
| 상품 ID | monthly_pro | 앱 코드에서 사용하는 ID (변경 불가) |
| 이름 | Pro 월간 구독 | 사용자에게 표시되는 이름 |
| 청구 주기 | 1개월 | 구독 갱신 주기 |
| 가격 | ₩9,900 | 국가별 가격 설정 가능 |
- 활성화 클릭 → 상태가 활성으로 변경 확인
일회성 상품 등록
- 수익 창출 → 인앱 상품 → 상품 만들기
- 상품 ID, 이름, 설명, 가격 입력 후 활성화
Mission 3 — 테스트 계정 설정 (Android)
테스트 계정은 실제 결제 없이 구매 흐름을 테스트할 수 있습니다. 등록된 Gmail 계정으로 내부 테스트 앱을 설치해야 효과가 있습니다.
- Play Console → 앱 → 내부 테스트 → 테스터 탭
- 테스터 그룹에 Gmail 주소 추가
- Play Console → 설정 → 라이선스 테스터에도 동일한 Gmail 추가
- 라이선스 테스터로 등록된 계정은 구독 주기가 단축되어 빠른 갱신 테스트 가능
Mission 4 — Google Play 서비스 계정 설정 (Android 실시간 검증용)
Android 실시간 위변조 검증(storeApiVerified: true)을 켜려면 Google 서비스 계정이 필요합니다. 이 자격증명은 고객님의 웹 서버에만 두며, Unveily에 전달·저장하지 않습니다. 미설정 시 검증 요청은 storeApiVerified: false(DB 기록만)로 처리됩니다.
iOS는 Apple 서명(JWS)을 서버가 검증하므로 자격증명이 필요 없습니다. 이 Mission은 Android 전용입니다.
4-1. Google Cloud 서비스 계정 생성
- Google Cloud Console → 프로젝트 선택 (Play Console과 동일 Google 계정)
- API 및 서비스 → 사용 설정된 API →
Google Play Android Developer API활성화 - IAM 및 관리자 → 서비스 계정 → 서비스 계정 만들기
- 서비스 계정 생성 완료 후 키 탭 → 키 추가 → JSON → 다운로드
4-2. Google Play Console 연결
- Google Play Console → 설정 → API 액세스
- Google Cloud 프로젝트 연결
- 생성한 서비스 계정 확인 → 권한 부여
- 권한: 재무 데이터 보기 + 주문 관리 체크 후 저장
- 권한 반영까지 최대 24시간 소요
4-3. 서비스 계정 JSON을 "고객님 웹 서버"에 배치
다운로드한 JSON은 고객님의 웹 서버에만 보관합니다. 검증 시, 웹 서버가 이 JSON으로 ~1시간짜리 단기 access token을 만들어 Unveily에 전달합니다. (장기키인 JSON 자체는 서버 밖으로 내보내지 않습니다.)
Unveily에 자격증명을 저장하지 않습니다
보안을 위해 Unveily는 고객님의 서비스 계정을 저장하지 않습니다. JSON은 고객님 웹 서버에 두고 요청마다 단기 토큰만 전달하세요. 서버 측 relay 엔드포인트 구현(단기 토큰 생성 + Unveily 호출) 샘플은 IAP Bridge — 서버 검증을 참고하세요.
iOS — App Store 설정
Mission 5 — App Store Connect 앱 준비
5-1. In-App Purchase Capability 추가
- Xcode → 프로젝트 선택 → Signing & Capabilities 탭
- + Capability 클릭 → In-App Purchase 추가
5-2. App Store Connect 인앱 상품 등록
- App Store Connect → 앱 선택 → 수익화 → 인앱 구입
- + 클릭 → 상품 유형 선택 (자동 갱신 구독 또는 소모성/비소모성)
- 다음 항목 입력:
| 항목 | 예시 값 | 설명 |
|---|---|---|
| 참조 이름 | Pro Monthly | 내부 관리용 이름 |
| 상품 ID | monthly_pro | 앱 코드에서 사용하는 ID (Android와 동일하게 설정 가능) |
| 가격 | Tier 10 ($9.99) | App Store 가격 티어 선택 |
- 현지화 이름/설명 추가 후 저장 → 제출 준비 완료 상태 확인
5-3. 구독 그룹 설정
자동 갱신 구독은 반드시 구독 그룹에 속해야 합니다.
- 구독 그룹 → 구독 그룹 생성
- 그룹 이름 입력 (예: "Pro Plan")
- 생성한 구독 상품을 그룹에 추가
5-4. 샌드박스 테스터 설정
- App Store Connect → 사용자 및 접근 권한 → 샌드박스 테스터
- + 클릭 → 테스트용 Apple ID 생성 (실제 Apple ID가 아닌 테스트 전용 계정)
- 실기기에서 설정 → Apple ID 로그아웃 → 앱 실행 시 샌드박스 계정으로 로그인
Mission 6 — Unveily SDK 코드 연동 (iOS 공통)
iOS acknowledgePurchase 불필요
iOS(StoreKit 2)는 purchase() 완료 시 트랜잭션이 자동으로 finish 처리됩니다.
acknowledgePurchase 호출 없이 서버 검증(고객 웹 서버 경유) 완료 후 기능을 바로 활성화하세요.
let transactionId = null;
let purchasedProductId = null;
// 1. 상품 조회 (App Store Connect 상품 ID 사용)
function loadProducts() {
window.unveilyBridge.iap.queryProducts(
["monthly_pro"],
"subs",
"onProductsLoaded"
);
}
window.onProductsLoaded = function(result) {
if (result.error) return;
const product = result.products[0];
document.getElementById("price").textContent = product.price;
};
// 2. 구매 시작
function subscribe() {
window.unveilyBridge.iap.purchase("monthly_pro", "subs", "onPurchaseResult");
}
// 3. 구매 완료 → 고객님 웹 서버로 검증 (iOS는 signedTransaction(JWS) 전달)
window.onPurchaseResult = async function(result) {
if (!result.success) return;
const res = await fetch("/api/verify-iap", { // 고객님 웹 서버가 Unveily 호출
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({
platform: "ios",
productId: result.productId,
productType: "subs",
signedTransaction: result.signedTransaction, // Apple 서명 JWS
}),
});
const verify = await res.json();
if (!verify.success) {
alert("결제 검증 실패. 고객센터에 문의해 주세요.");
return;
}
// Pro 기능 활성화 (iOS는 acknowledgePurchase 불필요)
document.body.classList.add("pro-user");
};
// 5. 앱 재시작 시 구독 복원 (Transaction.currentEntitlements 기반)
// 'load'가 아니라 'unveilyGlueReady'를 사용하세요 — 페이지 로드 시점에는
// 글루가 아직 unveilyBridge를 주입하지 않아 경합(race)이 발생할 수 있습니다.
window.addEventListener("unveilyGlueReady", () => {
window.unveilyBridge.iap.restorePurchases("subs", "onRestoreResult");
});
window.onRestoreResult = function(result) {
const hasActive = (result.purchases || []).some(
p => p.transactionId || p.isAcknowledged
);
if (hasActive) document.body.classList.add("pro-user");
};서버 검증은 필수
서버 검증 없이 기능을 바로 활성화하지 마세요. 검증은 고객님 웹 서버 → Unveily 경유로 수행합니다(서버 검증). 영수증 위변조로 결제 없이 Pro 기능에 접근하는 공격에 취약해집니다.
Android — Unveily SDK 코드 연동
let purchaseToken = null;
let purchasedProductId = null;
// 1. 상품 조회 (가격 표시)
function loadProducts() {
window.unveilyBridge.iap.queryProducts(
["monthly_pro"],
"subs",
"onProductsLoaded"
);
}
window.onProductsLoaded = function(result) {
if (result.error) return;
const product = result.products[0];
document.getElementById("price").textContent = product.price;
};
// 2. 구매 시작
function subscribe() {
window.unveilyBridge.iap.purchase("monthly_pro", "subs", "onPurchaseResult");
}
// 3. 구매 완료 → 고객님 웹 서버로 검증 (Android는 purchaseToken 전달)
window.onPurchaseResult = async function(result) {
if (!result.success) return;
const res = await fetch("/api/verify-iap", { // 고객님 웹 서버가 Unveily 호출
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({
platform: "android",
productId: result.productId,
productType: "subs",
purchaseToken: result.purchaseToken,
}),
});
const verify = await res.json();
if (!verify.success) {
alert("결제 검증 실패. 고객센터에 문의해 주세요.");
return;
}
// Pro 기능 활성화
document.body.classList.add("pro-user");
// Acknowledge (3일 내 필수 — Android만)
window.unveilyBridge.iap.acknowledgePurchase(result.purchaseToken, "onAckResult");
};
window.onAckResult = function(result) {
if (result.success) console.log("구독 활성화 완료");
};
// 5. 앱 재시작 시 구독 복원
// 'load'가 아니라 'unveilyGlueReady'를 사용하세요 — 페이지 로드 시점에는
// 글루가 아직 unveilyBridge를 주입하지 않아 경합(race)이 발생할 수 있습니다.
window.addEventListener("unveilyGlueReady", () => {
window.unveilyBridge.iap.restorePurchases("subs", "onRestoreResult");
});
window.onRestoreResult = function(result) {
const hasActive = (result.purchases || []).some(p => p.isAcknowledged);
if (hasActive) document.body.classList.add("pro-user");
};구독 업그레이드 / 다운그레이드
기존 구독자가 다른 요금제로 전환할 때는 purchase의 오버로드를 사용해 기존 구매 토큰과 교체 모드를 전달합니다.
window.unveilyBridge.iap.purchase(
"yearly_pro", // 전환할 새 상품 ID
"subs",
{
oldPurchaseToken: currentPurchaseToken, // 기존 구독의 purchaseToken (Android)
replacementMode: 1, // 1=WITH_TIME_PRORATION (기본)
},
"onIAPPurchaseResult"
);replacementMode 값: 1=WITH_TIME_PRORATION(기본), 2=CHARGE_PRORATED_PRICE, 3=WITHOUT_PRORATION, 5=CHARGE_FULL_PRICE, 6=DEFERRED.
iOS는 옵션 객체를 무시합니다
iOS(StoreKit 2)에서는 세 번째 옵션 객체({oldPurchaseToken, replacementMode})가 조용히 무시됩니다. App Store는 구독 그룹을 통해 업그레이드/다운그레이드를 자동 처리하므로, 같은 그룹 내 다른 상품을 purchase하면 프로레이션이 적용됩니다. 옵션 객체를 사용하는 것은 Android 전용입니다.
완료 전 점검
Android
- Google Play 내부 테스트 링크로 앱 설치 (테스터 계정으로)
- 상품 조회 → 올바른 가격 표시 확인
- 구매 진행 → Google Play 결제 화면 표시 확인
- 서버 검증 응답 확인 (
success: true) - Acknowledge 완료 확인
- 앱 재시작 후 구독 복원 확인
iOS
- 샌드박스 테스터 계정으로 실기기에서 앱 설치
- 상품 조회 → App Store Connect 상품 정보 표시 확인
- 구매 진행 → App Store 결제 화면 표시 확인
- 서버 검증 응답 확인 (
success: true,transactionId포함) - 기능 활성화 확인 (acknowledgePurchase 없이)
- 앱 재시작 후 구독 복원 확인
다음 여정
- IAP Bridge API — 각 메서드의 파라미터/응답 상세
- 앱 정보 Bridge — 현재 플랜 확인
- 라이선스 설정 — 라이선스 키 설정 방법