본문으로 건너뛰기

인앱 결제 구현 가이드

실제 게임에 크레딧 결제를 추가하는 전체 코드 예시입니다.

최소 구현

<script>
// 1. 유저 정보 수신
let userCredits = 0;

window.addEventListener('message', (e) => {
const data = e.data;
if (!data?.type) return;

if (data.type === 'user-info') {
userCredits = data.credits;
}

if (data.type === 'purchase-result') {
if (data.success) {
userCredits = data.balance;
grantItem(pendingItem); // 게임 로직에서 아이템 지급
} else {
alert('구매 실패: ' + data.error);
}
pendingItem = null;
}
});

// 2. 구매 요청
let pendingItem = null;

function buyItem(item) {
pendingItem = item;
window.parent.postMessage({
type: 'purchase-item',
itemId: item.id,
itemName: item.name,
price: item.price,
}, '*');
}

// 3. 크레딧 부족 시 충전 유도
function openCreditShop() {
window.parent.postMessage({ type: 'credit-shop' }, '*');
}
</script>

전체 예시: 코인 클리커 상점

아래는 크레딧으로 업그레이드를 구매하는 코인 클리커 게임의 상점 구현입니다.

// 상점 아이템 정의
const shopItems = [
{
id: 'boost-1',
name: 'Click Boost x2',
price: 5, // 5 크레딧
desc: '클릭당 코인 2배',
icon: '👆',
apply: () => { coinsPerClick *= 2; }
},
{
id: 'auto-1',
name: 'Auto Clicker',
price: 10,
desc: '초당 +1 코인 자동 생성',
icon: '🤖',
apply: () => { coinsPerSecond += 1; }
},
{
id: 'skin-gold',
name: 'Gold Skin',
price: 100,
desc: '황금 스킨 (장식용)',
icon: '✨',
apply: () => { player.skin = 'gold'; }
},
];

// 유저 상태
let userInfo = { userId: null, name: 'Guest', credits: 0 };
let pendingPurchase = null;

// 메시지 수신 핸들러
window.addEventListener('message', (e) => {
const data = e.data;
if (!data?.type) return;

switch (data.type) {
case 'user-info':
userInfo = {
userId: data.userId,
name: data.name || 'Guest',
credits: data.credits ?? 0,
};
updateUI();
break;

case 'purchase-result':
if (data.success) {
userInfo.credits = data.balance;
if (pendingPurchase) {
pendingPurchase.apply(); // 아이템 효과 적용
showToast(`${pendingPurchase.name} 구매 완료!`);
}
} else {
showToast(`❌ 구매 실패: ${data.error}`);
}
pendingPurchase = null;
updateUI();
break;
}
});

// 구매 요청
function requestPurchase(item) {
// 로그인 확인
if (!userInfo.userId) {
window.parent.postMessage({ type: 'login-required' }, '*');
return;
}

// 잔액 확인 (UX 최적화 — 실제 검증은 서버에서 수행)
if (userInfo.credits < item.price) {
window.parent.postMessage({ type: 'credit-shop' }, '*');
return;
}

pendingPurchase = item;
window.parent.postMessage({
type: 'purchase-item',
itemId: item.id,
itemName: item.name,
price: item.price,
}, '*');
}

상점 UI 렌더링

function renderShop() {
const shopEl = document.getElementById('shop');
shopEl.innerHTML = shopItems.map(item => `
<button onclick="requestPurchase(shopItems[${shopItems.indexOf(item)}])"
style="display:flex; align-items:center; gap:10px; padding:10px 12px;
background:rgba(255,255,255,0.05); border:1px solid rgba(255,255,255,0.1);
border-radius:10px; cursor:pointer; color:#fff; width:100%;">
<span style="font-size:24px;">${item.icon}</span>
<div style="flex:1;">
<div style="font-size:13px; font-weight:600;">${item.name}</div>
<div style="font-size:11px; color:#888;">${item.desc}</div>
</div>
<div style="padding:4px 10px; border-radius:8px;
background:rgba(255,215,0,0.15); color:#ffd700;
font-size:12px; font-weight:700;">
${item.price} 💰
</div>
</button>
`).join('');
}

결제 테스트 게임

실제 동작하는 인앱 결제 예시를 직접 플레이해볼 수 있습니다:

결제 테스트 게임 플레이하기

핵심 포인트

  1. 서버 검증: 클라이언트에서 잔액을 체크하는 건 UX 편의용입니다. 실제 차감은 Kplay 서버에서 수행됩니다.
  2. 비동기 처리: purchase-itempurchase-result 는 비동기입니다. 구매 요청 중 UI에 로딩 상태를 표시하세요.
  3. 중복 방지: pendingPurchase가 있는 동안 추가 구매 요청을 막으세요.
  4. 미로그인 처리: userInfo.userId가 null이면 login-required를 보내세요.