Unreal Engine 빌링 튜토리얼
이 문서는 UnrealStarter 샘플 프로젝트를 기준으로, Android 환경에서 Google Play 인앱 결제(PlayStore) 를 이용해 Hybe Game Platform Service(이하 플랫폼)의 빌링(결제) 기능을 연동하는 방법을 단계별로 안내합니다.
멤버십 튜토리얼에서 Google 로그인으로 플레이어 식별 정보를 확보한 뒤, 이 문서를 이어서 진행하는 것을 권장합니다. Android 멤버십 설정은 멤버십 가이드 - 플랫폼 로그인 (Android)를 참고하세요.
결제용 유니크 ID (Player ID)
PlayStore 결제·상품 예약·PreInit에는 유저를 식별하는 유니크 ID가 필요합니다. 이 ID는 게임 서버가 빌링 백엔드 API(상품 예약·검증·완료 등)를 호출할 때 사용하는 값과 동일해야 합니다.
- SDK API 파라미터 이름:
PreInit(playerid)의playerid - UnrealStarter 샘플: 플랫폼 로그인 결과의
imid를 그대로 전달 - 반드시
imid를 써야 하는 것은 아닙니다. 게임 서버·빌링 백엔드가 약속한 유저 ID이면 됩니다. 샘플만 편의상imid를 사용합니다.
클라이언트 PreInit에 넘기는 ID와 게임 서버 예약 API에 쓰는 ID가 다르면 결제·지급·복구가 어긋날 수 있습니다. 자세한 내용은 Step 4와 빌링 가이드 - PreInit을 참고하세요.
튜토리얼을 마치면 다음을 할 수 있습니다.
- Billing Agent를 등록하고, 게임 서버·빌링 백엔드와 동일한 유저 ID로 PlayStore 결제 환경을 초기화(
PreInit) 한다. - Google Play에 등록된 상품 정보를 조회하고 상점 UI를 구성한다.
- 게임 서버 예약(
boid) 후 구매(StartPurchase) 를 수행한다. - 결제 완료 후 Verify → Complete →
OnCompletedPurchase흐름을 처리한다. Restore로 미처리 구매를 복구한다. (구매 연동과 동등하게 필수)
구매 복구(Restore)는 선택 기능이 아닙니다
PlayStore에서 결제는 성공했지만 앱이 영수증을 받지 못하거나, 게임 서버 전달·완료 처리에 실패하면 유저는 결제했으나 아이템을 받지 못하는 상황이 발생합니다. 구매(StartPurchase) 연동과 함께 반드시 복구 플로우를 구현·테스트해야 합니다.
이 문서와 빌링 가이드의 차이
빌링 가이드는 스팀·PG·iOS·원스토어 등 모든 결제 수단과 API 레퍼런스를 다루는 참고 문서입니다. 이 튜토리얼은 처음 연동할 때 손으로 따라 하는 실습 문서이며, Android + PlayStore 에 집중합니다.
튜토리얼에서 PreInit, StartPurchase, OnPurchasesUpdated 등 API 이름은 빌링 가이드 API Reference로 연결되어 있습니다.
이 튜토리얼에서 다루지 않는 것
- Windows PC 결제(스팀, PG, Xsolla 등) — Unreal Engine 빌링 해당 섹션 참고
- iOS App Store, 원스토어 — 빌링 가이드 해당 섹션 참고
- 실제 게임 서버 HTTP 연동 — 샘플의
VirtualGameServerSubsystem(가상 게임 서버) 흐름까지만 다룹니다 - Google Play Console 상품 등록·심사 전체 절차 — 기술 PM·퍼블리싱 담당자와 협의
사전 요구사항
| 항목 | 내용 |
|---|---|
| OS | Android 10 이상 실기기 권장 (에뮬레이터는 Google Play 결제 테스트 제한) |
| Unreal Engine | 5.4 이상 (5.4.4, 5.5.4에서 테스트됨) |
| 개발 환경 | Android Studio, JDK, NDK (언리얼 Android SDK 설정 완료) |
| 샘플 프로젝트 | UnrealStarter (UnrealStarter_x.x.x.zip) |
| Google 계정 | Play Console 내부 테스트 참여·라이선스 테스터 등록된 계정 |
| 멤버십 | 플랫폼 Google 로그인 완료, 결제용 유니크 ID 확보 (샘플: imid) (멤버십 튜토리얼 또는 Android 로그인 가이드) |
| 네트워크 | QA(또는 dev) 플랫폼 환경 접근 가능 |
PlayStore 결제 테스트는 Play Console에 업로드한 빌드로 진행해야 합니다.
로컬에서 빌드한 APK를 직접 설치(sideload)한 경우, Play Store 결제 모듈이 정상 동작하지 않을 수 있습니다. 내부 테스트 트랙에 AAB/APK를 업로드하고, 테스터 링크를 통해 설치하세요.
Step 0. Android 빌드 환경 준비
0-1. Android 프로젝트 설정
- 언리얼 에디터에서 Edit → Project Settings → Platforms → Android 를 엽니다.
- SDK/NDK/JDK 경로가 올바른지 확인합니다.
- Package Name이 Play Console에 등록된 앱 ID와 일치하는지 확인합니다.
0-2. 앱 사이닝 (Google 로그인·결제 공통)
Google 로그인과 PlayStore 결제 모두 배포용 사이닝이 적용된 빌드가 필요합니다.
- Project Settings → Platforms → Android → Distribution Signing 에 키스토어를 설정합니다.
- Play Console에 등록한 업로드 키·앱 서명 구성과 일치하는지 확인합니다.
사이닝을 적용하지 않으면 Google 로그인 인증이 실패합니다. (멤버십 가이드 - 앱 사이닝)
0-3. Firebase 설정 (google-services.json)
Android Google 로그인·FCM 등에 Firebase 설정 파일이 필요합니다.
- 기술 PM에게 전달받은
google-services.json을 프로젝트에 복사합니다.- 기본 경로:
Config/Firebase/google-services.json
- 기본 경로:
DefaultEngine.ini에서 경로를 변경할 수 있습니다.
[HybeGamePlatform]
AndroidGoogleServiceJson=Config/Firebase/google-services.json
자세한 내용은 멤버십 가이드 - 푸시 연동 (Android)을 참고하세요.
0-4. Play Console 테스트 준비
PlayStore 결제 실습 전에 아래를 확인하세요.
| 항목 | 확인 내용 |
|---|---|
| 내부 테스트 트랙 | 최신 빌드가 업로드되어 있고, 테스터가 참여 수락 완료 |
| 라이선스 테스터 | Play Console → 설정 → 라이선스 테스트에 테스트 계정 등록 |
| 인앱 상품 | QA 환경에 등록된 상품 ID(SKU)가 Play Console에 존재 |
| 빌드 버전 | 기기에 설치된 앱 versionCode가 Play Console 업로드 버전과 동일 |
결제 창이 나오지 않는 가장 흔한 원인
실행 중인 앱 버전이 Play Console에 등록된 버전과 다르면 결제 UI가 표시되지 않습니다. 내부 테스트 링크로 재설치한 뒤 다시 시도하세요. (빌링 가이드 - Troubleshooting)
0-5. 접속 환경 설정 (drimage_config.json)
샘플은 Content/UnrealStarter/Data/DrimagePlatform/drimage_config.json 에서 플랫폼 접속 환경을 읽습니다.
{
"QA-1202": {
"BuildEnv": "qa",
"ProjectId": "1202",
"WebClientId": "134676729186-dfbh87213kflsoqq17koq9fa1er1oviu.apps.googleusercontent.com",
"ServiceId": "12020020",
"BillingAuth": "test-auth-key"
}
}
| 필드 | Android 빌링에서의 역할 |
|---|---|
BuildEnv, ProjectId, WebClientId | Mount() — 멤버십·빌링 공통 |
ServiceId, BillingAuth | (샘플앱 전용) 가상 게임 서버 HTTP 헤더. 플랫폼 SDK Plugin은 사용하지 않음 |
실제 게임 연동 시 Mount JSON에는 SDK가 요구하는 필드만 포함하면 되며, ServiceId·BillingAuth는 샘플의 가상 게임 서버 데모용입니다. (멤버십 튜토리얼 - Configuration JSON)
0-6. Android 빌드 및 설치
- 언리얼 에디터에서 Platforms → Android → Package Project 로 AAB(또는 APK)를 빌드합니다.
- Play Console 내부 테스트 트랙에 업로드합니다.
- 테스터 링크(Play Console → 테스트 → 내부 테스트 → 테스트 참여 방법)로 기기에 설치합니다.
Step 1. 샘플 앱 빌링 흐름 이해
PlayStore 결제에 관련된 화면과 소스 파일의 대응 관계는 아래와 같습니다.
| 구분 | 샘플 파일 | 역할 |
|---|---|---|
| Billing Agent | UnrealStarter/Agent/USBillingAgent.* | 결제 이벤트·Verify/Complete 처리 |
| 상점 UI | UnrealStarter/UI/Page/StorePage.cpp | 상품 조회·구매·복구 UI |
| 게임 서버 연동 | UnrealStarter/Helper/VirtualGameServerSubsystem.* | 상품 예약·검증·완료 API (샘플 전용) |
| 초기화 | UnrealStarter/UI/Page/InitPage.cpp | Billing Agent 등록·Mount() |
| 토큰 갱신 | UnrealStarter/Agent/USPlatformAuthAgent.cpp | PreInit 호출 시점 |
구매 플로우
정상적인 상품 구매 시퀀스입니다.
미처리 구매 복구 플로우
결제는 완료됐지만 클라이언트·서버 처리가 끝나지 않은 미처리 구매를 복구하는 시퀀스입니다. OnPurchasesUpdated와 동일한 Verify → Complete → OnCompletedPurchase 를 수행해야 합니다.
| 구분 | 구매 플로우 | 복구 플로우 |
|---|---|---|
| 시작 API | StartPurchase | Restore |
| SDK 콜백 | OnPurchasesUpdated | OnRestore |
| 선행 조건 | 상품 예약(boid) | PreInit 후 OnPrepared(OK), 게임 서버 로그인 |
| 완료 처리 | Verify → Complete → OnCompletedPurchase | 동일 |
상세 다이어그램은 빌링 가이드 - PlayStore 결제 연동을 참고하세요.
PlayStore vs Windows PC 결제 차이
| 항목 | Android PlayStore | Windows (PG/스팀 등) |
|---|---|---|
SetAvailablePayment | 자동 설정 (호출 불필요) | Mount 전 명시적 지정 필요 |
| 커스텀 결제 핸들러 | 불필요 | 스팀·PG·GPC·Xsolla는 필수 |
| 결제 UI | Google Play 결제 모듈 | 스팀 오버레이·외부 브라우저 등 |
Restore | 지원 | PG·Xsolla는 미지원 |
| Init 화면 PG 선택 | 불필요 (Android는 환경·언어만 선택) | Windows는 PG 선택 필요 |
Step 2. Billing Agent 구현
PlayStore 결제 이벤트를 수신하기 위해 HybeBillingAgent를 상속한 USBillingAgent 클래스를 구현합니다.
2-1. 클래스 선언
class UNREALSTARTER_API USBillingAgent : public HybeBillingAgent
{
public:
virtual void OnPrepared(BillingResultCode result) override;
virtual void OnPurchasesUpdated(std::shared_ptr<Purchase> purchase) override;
virtual void OnPurchasesCanceled() override;
virtual void OnPurchasesError(BillingStatus code, const FString& message) override;
virtual void OnRestore(const TArray<std::shared_ptr<Purchase>>& details) override;
static void PrepareBillingPlatform(const FString& playerId);
private:
static void RegisterPurchaseHandler();
void HandleVerify(std::shared_ptr<Purchase> purchase, const CommonResponse& Response);
void HandleComplete(const FString& boid, const FString& ProductId, const CommonResponse& Response);
};
PlayStore 튜토리얼에서 반드시 이해해야 할 콜백은 다음과 같습니다.
| 콜백 | 호출 시점 | 게임에서 할 일 |
|---|---|---|
OnPrepared | PreInit 완료 후 | 결제 가능 상태 확인, Restore 호출 (Step 8) |
OnPurchasesUpdated | Google Play 결제 완료 | 게임 서버 Verify → Complete → OnCompletedPurchase |
OnPurchasesCanceled | 사용자 결제 취소 | UI 안내, 예약(boid) 정리 |
OnRestore | Restore() 후 미처리 구매 발견 | OnPurchasesUpdated와 동일한 Verify → Complete → OnCompletedPurchase (필수 구현) |
2-2. OnPrepared — 빌링 초기화 완료
void USBillingAgent::OnPrepared(BillingResultCode result)
{
if (result == BillingResultCode::OK)
{
// 빌링 플랫폼 연동 완료
// 권장: 게임 서버 로그인이 끝난 뒤 Restore() 호출 (Step 8 참고)
HybeBillingPlatform::Instance().Restore();
}
else
{
// UNREGISTERED_AGENT, UNSUPPORTED_STORE, FAIL 등 — 로그 확인
}
}
OnPrepared 직후 Restore() 호출
PreInit 성공 직후 미처리 구매를 조회하는 것이 가장 일반적인 패턴입니다. 게임 서버 로그인이 OnPrepared보다 늦게 끝나는 구조라면, 서버 로그인 완료 콜백에서 Restore()를 호출하세요.
2-3. OnPurchasesUpdated — 결제 완료 (핵심)
PlayStore에서는 Google Play 결제 UI가 닫힌 뒤 구매 정보(Purchase)가 전달됩니다. 샘플은 게임 서버에 Verify → Complete 를 요청합니다.
void USBillingAgent::OnPurchasesUpdated(std::shared_ptr<Purchase> purchase)
{
#if PLATFORM_WINDOWS
// PG/Xsolla는 이 튜토리얼 범위 밖 — Android PlayStore는 아래 분기로 진행
PaymentType paymentType = HybeBillingPlatform::Instance().GetPayment();
if (paymentType == PaymentType::PG || paymentType == PaymentType::XSOLLA)
{
return;
}
#endif
UVirtualGameServerSubsystem* Server =
USHelper::GetVirtualGameServerSubsystemGlobal();
if (Server)
{
Server->VerifyPurchase(purchase, [this, purchase](CommonResponse Response)
{
HandleVerify(purchase, Response);
});
}
}
HandleVerify 성공 시 CompletePurchase를 호출하고, HandleComplete에서 SDK에 결과를 알립니다.
void USBillingAgent::HandleComplete(const FString& boid, const FString& ProductId,
const CommonResponse& Response)
{
if (Response.resultCode == TEXT("SUCCESS"))
{
GetBillingPlatform().OnCompletedPurchase(boid, ProductId, true);
}
else
{
GetBillingPlatform().OnCompletedPurchase(boid, ProductId, false);
}
}
PlayStore 소비 처리
Android에서는 영수증 소비(consume) 가 주로 서버 측 결제 프로세스에서 처리됩니다. 클라이언트는 수신한 영수증을 게임 서버에 전달하여 소비요청 완료 후 OnCompletedPurchase로 SDK에 결과를 전달해야합니다. iOS App Store와 달리 앱에서의 소비 처리 의무는 상대적으로 낮지만, 서버 완료 후 OnCompletedPurchase(true) 호출은 권장됩니다. (빌링 개발 연동 체크리스트)
2-4. OnRestore — 미처리 구매 복구 (필수)
Restore() 호출 시 SDK가 Google Play에 남아 있는 미처리 구매를 조회하고, 건별로 OnRestore에 Purchase 배열을 전달합니다. 구현 내용은 OnPurchasesUpdated와 동일해야 합니다.
void USBillingAgent::OnRestore(const TArray<std::shared_ptr<Purchase>>& details)
{
for (const auto& purchase : details)
{
// OnPurchasesUpdated와 동일: Verify → Complete → OnCompletedPurchase
OnPurchasesUpdated(purchase);
}
}
Purchase 필드 | 복구 시 활용 |
|---|---|
boid | 예약 ID — 서버 Verify/Complete에 전달 |
productId | 상품 ID — OnCompletedPurchase에 전달 |
playerId | 구매 당시 유저 ID — PreInit에 등록한 결제용 유니크 ID와 일치해야 함 |
details | 영수증·서명 등 — 서버 검증에 사용 |
OnRestore를 비우거나 OnPurchasesUpdated와 다른 로직을 쓰면 안 됩니다
미처리 구매가 반복 누적되고, 유저는 결제했지만 아이템을 받지 못하는 상태가 지속됩니다. 출시 전 반드시 복구 시나리오를 테스트하세요.
자세한 시나리오·호출 시점·서버 협업은 Step 8을 참고하세요.
Step 3. Mount 시 Billing Agent 등록
플랫폼 SDK Mount() 이전에 Billing Agent를 등록해야 합니다.
3-1. Init 화면 — Android 선택 사항
Android에서는 Windows와 달리 결제 플랫폼(PG) 버튼 선택이 필요 없습니다. Init 화면에서 환경(QA) 과 언어만 선택한 뒤 Apply를 누릅니다.
| Init UI 선택 | 샘플 코드 | API 반영 |
|---|---|---|
| 환경 버튼 | OnConfig_ButtonClicked → CachedConfig | Mount(configJson)의 BuildEnv, ProjectId, WebClientId |
| 언어 버튼 | OnLanguage_ButtonClicked → CachedLanguage | SetClientLanguage |
멤버십 튜토리얼의 Init UI 매핑을 참고하세요.
3-2. MountSDK() 내 빌링 등록
void UInitPage::MountSDK()
{
// ... Auth/Webview Agent 등록, SetWritableStorageDirectory, SetClientLanguage ...
HybePlatformAgent::Instance().SetPlatformBillingAgent(new USBillingAgent());
#if PLATFORM_WINDOWS
// Windows 전용: 결제 수단 명시. Android PlayStore는 자동 설정.
HybeBillingPlatform::SetAvailablePayment(CachedPaymentType);
#endif
HybePlatformAgent::Instance().Mount(JsonString);
}
| API | Android PlayStore에서의 역할 |
|---|---|
SetPlatformBillingAgent | 필수 — 결제 이벤트 수신자 등록 |
SetAvailablePayment | 불필요 — 모바일은 SDK가 GOOGLE_PLAY 자동 설정 (빌링 가이드) |
RegisterPgPurchaseHandler 등 | 불필요 — PlayStore는 커스텀 핸들러 없음 |
이 단계 완료 기준
-
SetPlatformBillingAgent가Mount()이전에 호출됨 -
OnMount(true)후 Google 로그인 성공, 결제용 유니크 ID 확보 (샘플:imid)
Step 4. 빌링 플랫폼 초기화 (PreInit)
PreInit은 결제 모듈을 초기화할 때 현재 플레이어의 유니크 ID를 SDK에 등록하는 API입니다. 이 문서에서는 SDK 파라미터명 playerid 를 결제용 유니크 ID로 부릅니다.
| 용어 | 설명 |
|---|---|
결제용 유니크 ID (playerid) | 게임 서버가 빌링 백엔드 API 호출 시 사용하는 동일한 유저 식별자 |
| 샘플에서의 값 | 플랫폼 로그인 JSON의 imid (GetClientContext().GetImid()) |
| 필수 여부 | imid 문자열 자체가 필수는 아님. 서버·백엔드와 같은 ID 체계가 필수 |
게임 서버 로그인이 끝나 위 ID를 확정한 뒤 PreInit을 호출합니다. UnrealStarter 샘플은 토큰 갱신 시점에 imid를 넘깁니다.
클라이언트 ID ≠ 서버 ID 이면 결제가 깨집니다
예: PreInit에는 플랫폼 imid를 넣었는데, 게임 서버 예약 API에는 자체 characterId만 보내는 경우 — 예약·결제·지급·복구가 서로 다른 유저로 처리될 수 있습니다. 게임 서버 담당자와 빌링 백엔드에 쓰는 유저 ID 하나로 통일하세요.
4-1. 호출 시점
void USPlatformAuthAgent::OnRefreshVerifyToken(const FString& token)
{
// ... loginToken 저장 ...
// 샘플: 게임 서버·빌링 백엔드와 동일한 ID로 imid 사용
USBillingAgent::PrepareBillingPlatform(GetClientContext().GetImid());
}
void USBillingAgent::PrepareBillingPlatform(const FString& playerId)
{
#if PLATFORM_WINDOWS
RegisterPurchaseHandler(); // Android PlayStore에서는 no-op
#endif
GetBillingPlatform().PreInit(playerId); // → OnPrepared
}
| 항목 | 내용 |
|---|---|
| 호출 시점 | 게임 서버 연동 이후, 플레이 가능 상태(로비·마을 진입 등). ID가 확정된 뒤 |
인자 playerId | 게임 서버가 빌링 백엔드 예약·검증 API에 넣는 유저 ID와 동일한 값. 샘플은 imid |
| 계정 변경 시 | 다른 계정 로그인 후 ID가 바뀌면 반드시 PreInit 재호출 |
imid는 샘플의 선택
OnLoginSuccess JSON의 imid가 곧 플랫폼·게임 서버·빌링 백엔드에서 쓰는 유저 ID인 QA 환경에서는 그대로 PreInit에 넘기면 됩니다. 자체 게임에서 서버가 다른 키(예: 내부 accountId)를 쓰면, 서버와 합의된 그 값을 PreInit에 전달하세요. (빌링 가이드 - playerid 란?)
OnLoginSuccess 직후 곧바로 PreInit을 호출하지 마세요. 샘플은 게임 서버 토큰 갱신(RefreshVerifyToken) 이후에 호출합니다. 실제 게임에서도 서버 로그인 완료 후 초기화하는 것이 안전합니다.
4-2. 샘플 앱에서 확인
- Google 로그인 후 Lobby 화면으로 진입합니다.
- Account 화면에서 토큰 갱신을 실행하거나, 샘플이 자동으로
RefreshVerifyToken을 호출하는 흐름을 따릅니다. - Output Log에서
OnPrepared/ 빌링 초기화 성공 로그를 확인합니다.
Step 5. 상품 조회 (QueryProductDetails)
상점 UI에 표시할 상품명·가격·통화 정보를 Google Play에서 조회합니다.
5-1. 상품 ID 목록
샘플 StorePage는 QA 환경에 등록된 상품 ID 배열을 사용합니다. 실제 프로젝트에서는 기술 PM·백오피스에서 전달받은 SKU를 사용하세요.
TArray<FString> ProductIds = { TEXT("item_01"), TEXT("item_02") /* ... */ };
HybeBillingPlatform::Instance().QueryProductDetails(ProductIds,
[this](bool bSuccess)
{
if (bSuccess)
{
FetchAndDisplayItems(); // HybeProductManager에서 캐시 읽기
}
});
5-2. 캐시에서 상품 정보 읽기
조회 성공 시 상품은 HybeProductManager에 캐싱됩니다.
ProductDetail detail = HybeProductManager::GetProduct(TEXT("item_01"));
if (detail.IsPurchasable())
{
// detail.GetFormattedPrice(), GetName() 등으로 UI 구성
}
GetProduct로 캐시에 없는 상품이면 QueryProductDetails를 호출해 서버 트래픽을 최소화하세요. (빌링 개발 연동 체크리스트)
5-3. 구매 가능 상태 확인
auto billingStatus = HybeBillingPlatform::Instance().CanPurchable();
if (billingStatus != HybePlatform::BillingStatus::OK)
{
// BILLING_SERVICE_DISCONNECTED, SERVICE_UNAVAILABLE 등 — 사용자 안내
}
Step 6. 상품 구매 (StartPurchase)
PlayStore 구매는 예약 → 구매 요청 → Google Play UI → 완료 처리 순서입니다.
6-1. 게임 서버 상품 예약 (boid)
구매 전 게임 서버(샘플: VirtualGameServerSubsystem)에 상품 예약을 요청하고 예약 ID(boid) 를 받습니다. 예약 API에도 PreInit과 동일한 결제용 유니크 ID가 사용됩니다. 샘플 가상 서버는 로그인·예약·검증 HTTP에 동일한 imid(또는 loginToken과 연계된 계정)를 전제로 동작합니다.
Server->StartPurchaseAsync(productId, 1, 0,
[this, productId](ReserveResponse Response)
{
if (Response.bSuccess)
{
HandleStartPurchaseResponse(productId, Response.boid, Response.paymentUrl);
}
});
| 단계 | 주체 | 설명 |
|---|---|---|
| 예약 | 게임 서버 → 빌링 백엔드 | boid 발급. 요청 시 결제용 유니크 ID 사용 (샘플: imid). PlayStore는 paymentUrl 불필요 |
| 구매 | 클라이언트 → SDK | StartPurchase(productId, boid). SDK는 PreInit에 등록된 동일 유저 컨텍스트로 결제 진행 |
6-2. 구매 요청
void UStorePage::HandleStartPurchaseResponse(const FString& productId,
const FString& boid, const FString& paymentUrl)
{
// PlayStore: paymentUrl 없이 boid만 전달
HybeBillingPlatform::Instance().StartPurchase(productId, boid);
}
Google Play 결제 UI가 표시됩니다. 사용자가 결제를 완료하면 OnPurchasesUpdated가 호출됩니다.
6-3. 결제 취소
사용자가 Google Play 결제 창에서 취소하면 OnPurchasesCanceled가 호출됩니다. 예약된 boid에 대한 서버 측 정리 정책은 게임 서버 스펙을 따르세요.
Step 7. 샘플 앱에서 따라 하기
아래 순서로 UnrealStarter Android 빌드를 실행하며 PlayStore 결제를 확인합니다.
7-1. 초기화·로그인
- 앱 실행 → Init 화면에서 QA 환경·언어 선택 → Apply
- Google 로그인 (사이닝·
WebClientId·내부 테스트 설치 확인) - Lobby 화면 진입 확인
7-2. 빌링 초기화
- Account → 토큰 갱신 (또는 샘플 자동 갱신 흐름)
PreInit→OnPrepared(OK)확인
7-3. 상점에서 구매
- Lobby → Store 이동
- 상품 목록·가격이 표시되는지 확인 (
QueryProductDetails성공) - 상품 선택 → 구매
- Google Play 결제 UI에서 테스트 결제 완료
- 게임 내 구매 완료 안내·아이템 지급(샘플 UI) 확인
Step 8. 미처리 구매 복구 (Restore) — 필수
구매(StartPurchase) 연동만으로는 부족합니다. 네트워크 끊김, 앱 강제 종료, Google·서버 일시 장애 등으로 결제는 됐지만 아이템 지급이 안 된 미처리 구매가 실제 서비스에서 자주 발생합니다. PlayStore는 Restore를 지원하므로, 출시 전 구매와 동등한 우선순위로 복구 플로우를 구현해야 합니다.
상세 배경은 빌링 가이드 - 소비처리되지 않은 구매 복구, 빌링 개발 연동 체크리스트를 참고하세요.
8-1. 미처리 구매가 생기는 대표 시나리오
| 시나리오 | 결제 | 앱 영수증 수신 | 게임 서버 전달 | 대응 |
|---|---|---|---|---|
| A. 영수증 미수신 | 완료 | 실패 (앱 종료·크래시) | 실패 | 로그인 후 Restore() → OnRestore → 서버 Verify |
| B. 서버 전달 실패 | 완료 | 성공 | 실패 (네트워크) | Restore()로 재전달 또는 서버 미완료 건 처리 |
| C. 서버 처리 성공 | 완료 | 성공 | 성공 | 서버가 로그인 시 미완료 건 점검. 클라이언트 Restore()는 빈 결과 가능 |
D. Complete 후 OnCompletedPurchase 누락 | 완료 | 성공 | 지급 완료 | Restore() 시 이미 지급 건이 올 수 있음 → 서버가 "이미 지급" 응답 시에도 OnCompletedPurchase(true) 호출 |
클라이언트 Restore() 전에 서버 확인 (권장 순서)
- 게임 서버 로그인 완료
- 서버가 해당 계정의 미완료 결제 건을 조회·처리 (빌링 개발 연동 - Android)
- 클라이언트에서
Restore()호출 — 서버로 영수증이 전달되지 않은 건을 SDK·Google Play에서 가져옴
PlayStore는 영수증이 서버에 한 번 전달된 뒤에는 서버·빌링 백엔드가 결제를 끝내야 합니다. 클라이언트 Restore()는 앱이 영수증을 받지 못한 경우의 안전망입니다.
8-2. Restore 호출 시점 (권장)
아래 최소 한 곳 이상에서 Restore()를 호출하도록 구현하세요.
| 시점 | 설명 | 샘플 참고 |
|---|---|---|
OnPrepared(OK) 직후 | PreInit 성공 후 가장 먼저 미처리 건 조회 | USBillingAgent::OnPrepared |
| 로비·상점 진입 시 | 플레이 가능 상태에서 재조회 | StorePage 진입 시 |
| 수동 복구 UI | 설정·상점에 구매 복구 버튼 제공 (체크리스트 권장) | StorePage::OnRestore_ButtonClicked |
| 재로그인 후 | 계정 변경·PreInit 재호출 이후 | OnRefreshVerifyToken → PreInit → Restore |
호출 조건
PreInit완료 후OnPrepared(OK)를 받은 뒤에만 호출- 게임 서버에 구매 완료(Verify/Complete)를 요청할 수 있는 상태여야 함
OnRestore처리 중에는 중복Restore()호출을 피하도록 플래그·큐 관리 권장
8-3. 구현 — Restore() 호출
void UStorePage::OnRestore_ButtonClicked()
{
HybeBillingPlatform::Instance().Restore();
}
자동 호출 예시 (OnPrepared):
void USBillingAgent::OnPrepared(BillingResultCode result)
{
if (result == BillingResultCode::OK)
{
HybeBillingPlatform::Instance().Restore();
}
}
8-4. 구현 — OnRestore 처리
OnRestore로 받은 각 Purchase에 대해 구매 완료와 동일한 서버 연동을 수행합니다.
void USBillingAgent::OnRestore(const TArray<std::shared_ptr<Purchase>>& details)
{
if (details.Num() == 0)
{
return; // 미처리 구매 없음
}
for (const auto& purchase : details)
{
// 구매 완료와 동일한 경로 — 반드시 공통 함수로 묶는 것을 권장
ProcessPurchaseCompletion(purchase);
}
}
void USBillingAgent::ProcessPurchaseCompletion(std::shared_ptr<Purchase> purchase)
{
Server->VerifyPurchase(purchase, [this, purchase](CommonResponse Response)
{
HandleVerify(purchase, Response);
});
}
OnPurchasesUpdated에서도 ProcessPurchaseCompletion을 호출하면 구매·복구 로직이 한곳에 모여 유지보수가 쉬워집니다.
8-5. 서버·클라이언트 역할 분담 (PlayStore)
| 단계 | Android PlayStore |
|---|---|
| 결제용 유니크 ID | PreInit·서버 예약·검증 API 동일 ID (샘플: imid) |
| 영수증 소비(consume) | 주로 서버 결제 프로세스에서 처리 |
클라이언트 OnCompletedPurchase | 서버 완료 후 권장 — SDK에 처리 결과 전달 |
Restore() 역할 | 앱이 받지 못한 미처리 영수증을 Google Play에서 다시 가져옴 |
| 서버 로그인 시 | 미완료 결제 건 서버 측 선행 처리 |
8-6. 샘플 앱에서 복구 테스트
구매 연동(Step 7) 후 반드시 아래를 수행하세요.
- 테스트 구매를 한 뒤,
OnPurchasesUpdated처리 직전에 앱을 강제 종료하거나 네트워크를 끊어 영수증 전달을 실패시킵니다. - 앱을 다시 실행 → Google 로그인 → 동일 유저 ID로 토큰 갱신(
PreInit)까지 진행합니다. - Store → 복구(Restore) 버튼을 누르거나,
OnPrepared후 자동Restore()가 동작하는지 확인합니다. OnRestore→ Verify → Complete →OnCompletedPurchase(true)로그·아이템 지급을 확인합니다.
복구 대상이 없을 때
미처리 구매가 없으면 OnRestore에 빈 배열이 전달되거나 콜백이 호출되지 않을 수 있습니다. 이는 정상 동작입니다.
8-7. 자주 하는 실수
| 실수 | 결과 |
|---|---|
Restore() 미구현 | 결제만 되고 아이템 미지급 — CS·환불 증가 |
OnRestore에서 OnPurchasesUpdated와 다른 처리 | 일부 구매만 복구 실패 |
PreInit 전 Restore() 호출 | 복구 실패 또는 예기치 않은 동작 |
서버 완료 없이 OnCompletedPurchase(true) | 상태 불일치 |
재로그인 후 PreInit만 하고 Restore 생략 | 이전 계정 미처리 건 누락 |
| 복구 UI 없음 | 유저가 스스로 복구할 수 없음 |
이 단계 완료 기준
-
OnPrepared(OK)또는 상점 진입 시Restore()호출 구현 -
OnRestore가OnPurchasesUpdated와 동일한 Verify → Complete →OnCompletedPurchase수행 - 수동 구매 복구 버튼(또는 설정 메뉴) 제공
- 강제 종료·네트워크 차단 시나리오로 복구 테스트 완료
검증 체크리스트
튜토리얼을 완료했는지 아래 항목을 확인하세요.
- Play Console 내부 테스트 빌드로 앱이 설치되어 있다.
- Init 화면에서 QA 환경·언어 선택 후 Apply·Mount 성공
- Google 로그인 성공, 결제용 유니크 ID 확보 (샘플:
imid, 서버·백엔드와 동일 ID인지 확인) - 토큰 갱신 후
PreInit(동일 유저 ID)→OnPrepared(OK)수신 - Store에서 상품명·가격이 표시된다 (
QueryProductDetails성공) - Google Play 결제 UI에서 테스트 구매 완료
-
OnPurchasesUpdated→ 서버 Verify/Complete →OnCompletedPurchase(true)흐름 확인 -
Restore()호출 시점 구현 (OnPrepared직후 또는 상점 진입) -
OnRestore가 구매 완료와 동일한 Verify → Complete →OnCompletedPurchase수행 - 구매 복구 UI(Store 복구 버튼 등) 제공
- 강제 종료·네트워크 차단 후 복구 시나리오 테스트 완료
자주 발생하는 문제 (Android + PlayStore)
결제 창이 나오지 않음
- Play Console 업로드 versionCode와 기기 설치 버전이 일치하는지 확인
- 내부 테스트 링크로 설치했는지 확인 (sideload APK 비권장)
- 빌링 가이드 - Troubleshooting 참고
테스트 카드 결제 실패
- Play Console → 설정 → 라이선스 테스트에 계정 등록 여부 확인
구입 상품 정보를 조회할 수 없음
- 내부 테스트 테스터 참여 링크에서 수락했는지 확인
- Play Console → 테스트 → 내부 테스트 → 테스트 참여 방법 링크 사용
OnPrepared 실패 (UNREGISTERED_AGENT 등)
SetPlatformBillingAgent가Mount()이전에 호출되었는지 확인
Google 로그인 실패 (Login cancel)
- Distribution Signing 적용 여부
WebClientId가 현재BuildEnv에 맞게 등록되었는지 (멤버십 가이드 - Android)
구매 후 아이템 미지급
OnPurchasesUpdated에서 게임 서버 Verify/Complete 호출 여부- 서버에 영수증 전달 후
OnCompletedPurchase(true)호출 여부 - 서버 로그·빌링 백엔드 예약(
boid) 상태 확인 Restore()구현·호출 여부 — 미처리 구매가 쌓였는지 Logcat에서OnRestore확인
복구(Restore)가 동작하지 않음
PreInit→OnPrepared(OK)이후에Restore()를 호출했는지 확인- 게임 서버 로그인이 끝나기 전에
Restore()를 호출하지 않았는지 확인 OnRestore가 비어 있거나OnPurchasesUpdated와 다른 코드 경로를 쓰지 않았는지 확인- 이미 서버에 영수증이 전달된 건은 서버 측 미완료 처리가 필요할 수 있음 (Step 8-1)
기타
더 많은 문제 해결 방법은 빌링 가이드 - Troubleshooting을 참고하세요.
다음에 읽을 문서
| 문서 | 내용 |
|---|---|
| Unreal Engine 빌링 | 스팀·PG·iOS·원스토어 등 전 결제 수단, API Reference |
| Unreal Engine 멤버십 튜토리얼 | Windows Google 로그인 실습 (멤버십 Part 1) |
| Unreal Engine 멤버십 | Android/iOS 로그인 상세, API Reference |
| 빌링 개발 연동 체크리스트 | 출시 전 빌링 연동 점검 |
| 빌링 체크리스트 | 빌링 정책·연령 확인 |
핵심 소스 파일 요약
| 파일 | 빌링 튜토리얼에서의 역할 |
|---|---|
UnrealStarter/Agent/USBillingAgent.* | OnPrepared, OnPurchasesUpdated, OnRestore, Verify/Complete |
UnrealStarter/UI/Page/StorePage.cpp | QueryProductDetails, 예약, StartPurchase, Restore |
UnrealStarter/UI/Page/InitPage.cpp | SetPlatformBillingAgent, Mount() |
UnrealStarter/Agent/USPlatformAuthAgent.cpp | PrepareBillingPlatform 호출 시점 |
UnrealStarter/Helper/VirtualGameServerSubsystem.* | 샘플 전용 상품 예약·검증·완료 HTTP |
UnrealStarter/UI/Component/USItemPurchasePopup.cpp | (Windows PG용) Android PlayStore 실습에서는 미사용 |
Config/Firebase/google-services.json | Android Firebase·Google 로그인 |
Content/.../drimage_config.json | QA/dev 환경·WebClientId |
Plugins/HybePlatform/ | 플랫폼 SDK 언리얼 플러그인 (Play Billing 포함) |