앱이 Google Play를 통해 배포되고 디지털 상품을 판매하거나 정기 결제를 제공하려는 경우 Google Play 결제를 사용해야 합니다. Google Play 결제는 카탈로그, 가격, 정기 결제를 관리하는 도구, 유용한 보고서, 사용자가 이미 익숙한 Play 스토어 기반의 결제 절차를 제공합니다.
신뢰할 수 있는 웹 활동을 사용하여 빌드되고 Google Play 스토어를 통해 제공되는 앱의 경우 이제 결제 요청 API 및 디지털 상품 API를 사용하여 Google Play 결제와 통합할 수 있습니다. Android 및 ChromeOS의 Chrome 101 이상에서 사용할 수 있습니다.
이 가이드에서는 PWA에 Google Play 결제 지원을 추가하고 ChromeOS 및 Play 스토어용 Google Play 스토어에 배포할 수 있도록 패키징하는 방법을 알아봅니다.
두 가지 웹 플랫폼 API를 사용하여 PWA에 Play 결제 지원을 추가합니다. Digital Goods API는 SKU 정보를 수집하고 Play 스토어에서 구매 및 사용 권한을 확인하는 데 사용됩니다. Payment Request API는 Google Play 스토어를 결제 수단으로 구성하고 구매 흐름을 완료하는 데 사용됩니다.
Play 스토어에서 애플리케이션으로 수익을 창출하는 방법
애플리케이션이 Play 스토어에서 Google Play 결제를 사용해 수익을 창출할 수 있는 방법에는 두 가지가 있습니다.
- 인앱 구매를 사용하면 추가 기능 또는 광고 삭제와 같은 내구재 및 소비성 가상 상품을 모두 판매할 수 있습니다.
- 정기 결제: 뉴스 구독이나 멤버십과 같은 반복적인 요금을 지불하고 사용자가 콘텐츠나 서비스에 지속적으로 액세스할 수 있습니다.
요구사항
Google Play 결제를 설정하려면 다음이 필요합니다.
- 서로 연결된 Google Play 개발자 계정과 Google Payments 판매자 계정
- 공개, 비공개 테스트 또는 내부 테스트 트랙에 출시된 Play 스토어 등록정보
- Play 스토어에서 앱의 제품 및 정기 결제를 만들고 구성합니다.
- 작동하는 디지털 애셋 링크 구성이 있는 Bubblewrap 생성 프로젝트
Bubblewrap 프로젝트 업데이트
Bubblewrap이 설치되어 있지 않으면 설치해야 합니다. 시작 방법에 관한 자세한 내용은 빠른 시작 가이드를 참고하세요. Bubblewrap이 이미 있는 경우 버전 1.8.2 이상으로 업데이트해야 합니다.
Bubblewrap에도 플래그 뒤에 있는 기능이 있습니다. 이 기능을 사용 설정하려면 프로젝트 루트에 있는 twa-manifest.json
에서 프로젝트 구성을 수정하고 alphaDependencies
및 playBilling
기능을 모두 사용 설정해야 합니다.
...,
"enableNotifications": true,
"features": {
"playBilling": {
"enabled": true
}
},
"alphaDependencies": {
"enabled": true
},
...
구성 파일을 업데이트하고 bubblewrap update
를 실행하여 프로젝트에 구성을 적용한 후 bubblewrap build
를 실행하여 새 Android 패키지를 생성하고 이 패키지를 Play 스토어에 업로드합니다.
Digital Goods API 및 Google Play 결제 사용 가능 여부를 감지하는 기능
Digital Goods API는 현재 신뢰할 수 있는 웹 활동 내에서 PWA가 실행되는 경우에만 Chrome에서 지원되며 window
객체의 getDigitalGoodsService
를 확인하여 사용 가능 여부를 감지할 수 있습니다.
if ('getDigitalGoodsService' in window) {
// Digital Goods API is supported!
}
Digital Goods API는 모든 브라우저에서 사용할 수 있으며 다양한 스토어를 지원합니다. 특정 매장 백엔드가 지원되는지 확인하려면 매장 ID를 매개변수로 전달하는 getDigitalGoodsService()
를 호출해야 합니다. Google Play 스토어는 https://play.google.com/billing
문자열로 식별됩니다.
if ('getDigitalGoodsService' in window) {
// Digital Goods API is supported!
try {
const service =
await window.getDigitalGoodsService('https://play.google.com/billing');
// Google Play Billing is supported!
} catch (error) {
// Google Play Billing is not available. Use another payment flow.
return;
}
}
SKU 세부정보 검색
Digital Goods API는 결제 백엔드에서 제품명, 설명, 가장 중요한 가격과 같은 정보를 가져올 수 있는 getDetails()
를 제공합니다.
그런 다음 이 정보를 사용자 인터페이스에서 사용하고 사용자에게 더 많은 세부정보를 제공할 수 있습니다.
const skuDetails = await service.getDetails(['shiny_sword', 'gem']);
for (item of skuDetails) {
// Format the price according to the user locale.
const localizedPrice = new Intl.NumberFormat(
navigator.language,
{style: 'currency', currency: item.price.currency}
).format(item.price.value);
// Render the price to the UI.
renderProductDetails(
item.itemId, item.title, localizedPrice, item.description);
}
구매 흐름 구축
PaymentRequest의 생성자는 결제 수단 목록과 결제 세부정보 목록이라는 두 가지 매개변수를 사용합니다.
신뢰할 수 있는 웹 활동 내에서는 https://play.google.com/billing
를 식별자로 설정하고 제품 SKU를 데이터 멤버로 추가하여 Google Play 결제 결제 수단을 사용해야 합니다.
async function makePurchase(service, sku) {
// Define the preferred payment method and item ID
const paymentMethods = [{
supportedMethods: "https://play.google.com/billing",
data: {
sku: sku,
}
}];
...
}
결제 세부정보는 필수사항이지만 Play 결제는 이러한 값을 무시하고 Play Console에서 SKU를 만들 때 설정된 값을 사용하므로 가짜 값으로 채울 수 있습니다.
const paymentDetails = {
total: {
label: `Total`,
amount: {currency: `USD`, value: `0`}
}
};
const request = new PaymentRequest(paymentMethods, paymentDetails);
결제 요청 객체에서 show()
를 호출하여 결제 흐름을 시작합니다. Promise가 성공하면 결제가 완료된 것입니다. 실패하면 사용자가 결제를 중단했을 가능성이 높습니다.
약속이 성공하면 구매를 확인하고 확인해야 합니다. 사기를 방지하려면 이 단계를 백엔드를 사용하여 구현해야 합니다. Play 결제 문서에서 백엔드에 인증을 구현하는 방법을 알아보세요. 구매를 확인하지 않으면 3일 후 사용자에게 환불되고 Google Play에서 구매가 취소됩니다.
...
const request = new PaymentRequest(paymentMethods, paymentDetails);
try {
const paymentResponse = await request.show();
const {purchaseToken} = paymentResponse.details;
// Call backend to validate and acknowledge the purchase.
if (await acknowledgePurchaseOnBackend(purchaseToken, sku)) {
// Optional: tell the PaymentRequest API the validation was
// successful. The user-agent may show a "payment successful"
// message to the user.
const paymentComplete = await paymentResponse.complete('success');
} else {
// Optional: tell the PaymentRequest API the validation failed. The
// user agent may show a message to the user.
const paymentComplete = await paymentResponse.complete('fail');
}
} catch(e) {
// The purchase failed, and we can handle the failure here. AbortError
// usually means a user cancellation
}
...
원하는 경우 purchaseToken에서 consume()
를 호출하여 구매를 사용한 것으로 표시하고 다시 구매할 수 있도록 허용할 수 있습니다.
종합하면 구매 방법은 다음과 같습니다.
async function makePurchase(service, sku) {
// Define the preferred payment method and item ID
const paymentMethods = [{
supportedMethods: "https://play.google.com/billing",
data: {
sku: sku,
}
}];
// The "total" member of the paymentDetails is required by the Payment
// Request API, but is not used when using Google Play Billing. We can
// set it up with bogus details.
const paymentDetails = {
total: {
label: `Total`,
amount: {currency: `USD`, value: `0`}
}
};
const request = new PaymentRequest(paymentMethods, paymentDetails);
try {
const paymentResponse = await request.show();
const {purchaseToken} = paymentResponse.details;
// Call backend to validate and acknowledge the purchase.
if (await acknowledgePurchaseOnBackend(purchaseToken, sku)) {
// Optional: consume the purchase, allowing the user to purchase
// the same item again.
service.consume(purchaseToken);
// Optional: tell the PaymentRequest API the validation was
// successful. The user-agent may show a "payment successful"
// message to the user.
const paymentComplete =
await paymentResponse.complete('success');
} else {
// Optional: tell the PaymentRequest API the validation failed.
// The user agent may show a message to the user.
const paymentComplete = await paymentResponse.complete('fail');
}
} catch(e) {
// The purchase failed, and we can handle the failure here.
// AbortError usually means a user cancellation
}
}
기존 구매 상태 확인
Digital Goods API를 사용하면 사용자가 이전에 다른 기기에서, 이전 설치에서, 프로모션 코드에서 사용했거나, 앱을 마지막으로 연 시점에 구매한 기존 사용 권한 (아직 사용하지 않은 인앱 구매 또는 진행 중인 정기 결제)이 있는지 확인할 수 있습니다.
const service =
await window.getDigitalGoodsService('https://play.google.com/billing');
...
const existingPurchases = await service.listPurchases();
for (const p of existingPurchases) {
// Update the UI with items the user is already entitled to.
console.log(`Users has entitlement for ${p.itemId}`);
}
이전에 이뤄졌지만 확인되지 않은 구매도 확인할 수 있는 좋은 기회입니다. 사용자의 사용 권한이 앱에 올바르게 반영되도록 하려면 최대한 빨리 구매를 확인하는 것이 좋습니다.
const service =
await window.getDigitalGoodsService("https://play.google.com/billing");
...
const existingPurchases = await service.listPurchases();
for (const p of existingPurchases) {
await verifyOrAcknowledgePurchaseOnBackend(p.purchaseToken, p.itemId);
// Update the UI with items the user is already entitled to.
console.log(`Users has entitlement for ${p.itemId}`);
}
통합 테스트
개발용 Android 기기에서
테스트를 위해 개발용 Android 기기에서 Digital Goods API를 사용 설정할 수 있습니다.
- Android 9 이상을 사용하고 개발자 모드를 사용 설정해야 합니다.
- Chrome 101 이상을 설치합니다.
- Chrome에서
chrome://flags
로 이동하여 이름으로 플래그를 검색하여 다음 플래그를 사용 설정합니다.#enable-debug-for-store-billing
- 사이트가 https 프로토콜을 사용하여 호스팅되고 있는지 확인합니다. http를 사용하면 API가
undefined
ChromeOS 기기에서
Digital Goods API는 버전 89부터 ChromeOS 안정화 버전에서 사용할 수 있습니다. 그동안 Digital Goods API를 테스트할 수 있습니다.
- Play 스토어에서 기기에 앱을 설치합니다.
- 사이트가 https 프로토콜을 사용하여 호스팅되는지 확인합니다. http를 사용하면 API가
undefined
이 됩니다.
테스트 사용자 및 QA팀
Play 스토어는 사용자 테스트 계정 및 테스트 SKU를 비롯한 테스트 기능을 제공합니다. 자세한 내용은 Google Play 결제 테스트 문서를 참고하세요.
다음 단계는 무엇일까요?
이 문서에 설명된 것처럼 Play Billing API에는 Digital Goods API에서 관리하는 클라이언트 측 구성요소와 서버 측 구성요소가 있습니다.
- https://github.com/PEConn/beer에서 피터 코닝의 샘플을 살펴보세요.
- 구매 인증에 관한 Play 문서를 확인하세요.
- 다양한 언어로 제공되는 Google Play Developer API 클라이언트 라이브러리 중 하나를 사용하는 것이 좋습니다.
- 애플리케이션에서 정기 결제 모델을 구현하는 경우 Play 결제 정기 결제 문서를 확인하세요.
- 실시간 개발자 알림 (RTDN)을 구현하고 알림을 구독하여 Play에서 상태를 폴링하는 대신 정기 결제 상태가 변경될 때 백엔드에 알림을 전송합니다.
linkedPurchaseToken
를 구현하여 중복 정기 결제를 방지합니다. 올바르게 구현하는 방법은 이 블로그 게시물을 참고하세요.