5단계. 상품 지급과 거래 완료 처리
영수증 검증에 성공하면 앱 서버가 사용자에게 상품을 지급하고, 앱 클라이언트가 지급 결과를 기록한 뒤 거래를 완료 처리합니다. 앱 클라이언트가 이 단계에서 사용하는 hiveAxylTransactionId와 구독 상품 ID는 앱 서버가 영수증 검증 응답에서 받아 전달한 값입니다. 결제 확정 요청과 지급 결과 기록의 역할은 결제 확정과 지급 결과 기록을 참조하세요.
아래 과정을 따라 상품 지급과 거래 완료 처리를 수행합니다.
1. 상품 지급
앱 서버가 영수증 검증 결과를 확인한 뒤 결제한 사용자에게 상품을 지급합니다. 상품 지급은 앱에서 구현하며, 지급 방식은 앱별로 다를 수 있습니다.
결제 확정 요청을 보내는 RequestPurchaseAsync()는 소모성 상품에만 사용합니다. 구독 상품은 3. 구독 완료 처리로 구독을 확정합니다. Google 결제에서 상품 지급과 RequestPurchaseAsync()의 호출 순서는 앱이 결정하지만, 상품을 먼저 지급한 뒤 결제 확정 요청을 전송하세요. 2. 상품 지급 결과 저장은 4. 거래 완료 처리의 선행 조건이 아니므로, 지급 결과 저장이 실패해도 거래 완료 처리를 미루지 마세요.
2. 상품 지급 결과 저장
ItemResultAsync
소모성 상품 지급 결과 저장을 구현하려면 Hive Axyl SDK가 제공하는 ItemResultAsync()를 호출하세요. 상품 지급이 완료된 뒤 그 결과를 Hive Axyl 서버에 기록합니다. 이 메서드는 결제를 확정하거나 상품 지급 여부를 결정하지 않습니다.
호출 파라미터
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
| request | ItemResultBody | Required | 소모성 상품 지급 결과 저장 요청 데이터 객체입니다. |
| context | ApiCallContext | Optional | 호출 단위 설정 객체입니다. 멱등키·취소 토큰·요청 정책을 지정하며, 생략하면 기본값이 사용됩니다. |
ItemResultBody
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
AxylTransactionId | string | Required | Hive Axyl 결제 트랜잭션 ID (접두사로 마켓 구분). 앱 서버가 영수증 검증 응답에서 받아 전달한 hiveAxylTransactionId 값을 사용합니다. |
Status | int | Required | 지급 결과 상태입니다. 1: 지급 성공, 2: 취소 후 상품 회수 없음, 3: 취소 후 상품 회수이며, 그 외 값은 유효하지 않습니다. |
Assets | IReadOnlyList<ItemResultAsset> | Optional | 실제 지급된 상품 목록입니다. 기록할 항목이 없으면 이 필드를 생략하세요. |
ProjectPayloadInfo | string | Optional | 앱별 자유 형식 JSON 데이터 (JSON 문자열) |
ItemResultAsset
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
AssetId | string | Optional | 앱 내 상품 고유 ID |
AssetName | string | Optional | 앱 내 상품 이름 |
Quantity | int | Optional | 지급 수량입니다. 지정하는 경우 1 이상이어야 합니다. |
호출 예시
PaymentsItemResultResult의 성공 결과와 이 메서드에서 정의한 도메인별 결과(Outcome)는 아래 예시와 응답 상태에서 확인합니다. 요청 수행이 불가능한 공통 실패(Failure)의 결과 모델과 처리 원칙은 공통 오류 처리를 참조하세요.
using Hive.Axyl.Payments;
using Hive.Axyl.Core;
// payments: 초기화 시 등록된 IPaymentsService (자세한 획득은 [모듈 설치 및 초기화](../init.md) 참고)
IPaymentsService payments = HiveCore.Resolve<IPaymentsService>();
// 상품 지급 완료 후, 지급 결과로 요청 객체 생성
var request = new ItemResultBody
{
AxylTransactionId = "GO_1234567890", // 앱 서버가 전달한 영수증 검증 응답의 hiveAxylTransactionId (GO_ 접두사 = Google)
Status = 1, // 1: 지급 성공
Assets = new[]
{
new ItemResultAsset { AssetId = "item_gold_100", AssetName = "골드 100개", Quantity = 1 }
},
ProjectPayloadInfo = "{\"serverId\":\"server01\"}" // (선택) 앱별 자유 형식 JSON 데이터
};
PaymentsItemResultResult result = await payments.ItemResultAsync(request);
switch (result)
{
case PaymentsItemResultResult.Success success:
// 지급 결과 기록 성공. success.Data.Meta를 확인할 수 있습니다.
break;
// 요청 값 문제 — 필수값·수량·상태값 확인
case PaymentsItemResultResult.PaymentInvalidParameter:
case PaymentsItemResultResult.PaymentBadRequest:
Debug.LogError("요청 파라미터를 확인하세요.");
break;
// 공통 Failure 처리
case PaymentsItemResultResult.Failure failure:
HiveError err = failure.Problem;
Debug.LogError($"[{err.Code}] {err.Message} (trace: {err.TraceId})");
break;
// 안전망: 처리하지 않은 결과 및 알 수 없는 신규 결과(UnknownOutcome)
default:
Debug.LogWarning($"처리되지 않은 결과: {result.GetType().Name}");
break;
}
응답 데이터
성공 시 PaymentsItemResultResult.Success의 Data(SuccessResponseData)에 응답 메타 정보가 담깁니다.
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
Data.Meta | string? | Optional | 응답 메타 정보입니다. |
응답 예시
응답 상태
아래 표에는 PaymentsItemResultResult의 성공 결과와 이 메서드에서 정의한 도메인별 결과(Outcome)를 정리했습니다. 요청 수행이 불가능한 공통 실패(Failure)의 결과 모델과 처리 원칙은 공통 오류 처리를 참조하세요.
| 응답 케이스 | 설명 | 앱 클라이언트 대응 |
|---|---|---|
Success | 지급 결과 기록 성공 | 다음 결제 처리 진행 |
InvalidQuantity | 지급 수량이 유효하지 않습니다. | 지급 수량을 확인 |
InvalidStatus | 지급 결과 상태 값이 유효하지 않습니다. | Status 값을 확인 |
PaymentBadRequest | 요청을 처리할 수 없습니다. | 요청 값과 호출 조건을 점검 |
PaymentInvalidParameter | 유효하지 않은 파라미터인 경우 (예: AxylTransactionId 누락) | 필수 파라미터 설정 확인 |
PaymentResourceNotFound | 결제 정보를 찾을 수 없습니다. | 영수증·주문 번호·결제 상태를 확인 |
UnknownOutcome | SDK가 알 수 없는 도메인별 결과입니다. | 실패로 처리하고 결과 코드를 기록 |
Failure | 공통 Failure입니다. 공통 오류 처리를 참조하세요. | 공통 오류 처리 기준에 따라 처리 |
3. 구독 완료 처리
구독 상품은 앱 서버의 구독 영수증 검증과 구독 혜택 지급이 끝난 뒤 PostSubscriptionAsync()를 호출해 Hive Axyl 서버에서 구독을 확정합니다. 구독이 확정되면 4.2. 비소모성 · 구독 상품 구매 확인을 진행하세요.
PostSubscriptionAsync
PostSubscriptionAsync()는 구독 구매 정보 저장에서 저장한 구독 기록을 찾아 구독을 확정하고 거래를 종료합니다.
호출 파라미터
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
| request | SubscriptionPurchasePostRequest | Required | 구독 확정 요청 데이터 객체입니다. |
| context | ApiCallContext | Optional | 호출 단위 설정 객체입니다. 멱등키·취소 토큰·요청 정책을 지정하며, 생략하면 기본값이 사용됩니다. |
SubscriptionPurchasePostRequest
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
AppVersion | string | Optional | 앱 버전입니다. |
AxylReceipt | string | Required | 구독 구매 정보 저장의 PurchaseSubscriptionAsync() 요청에 전달한 구매 토큰(purchaseToken)을 그대로 전달합니다. 서버는 이 값으로 저장된 구독 기록을 찾으므로, 값이 다르면 구독이 확정되지 않습니다. |
Country | string | Required | 국가 코드(ISO 3166-1 두 자리)입니다. 예: KR |
Currency | string | Required | 통화(ISO 4217 세 자리)입니다. 예: KRW |
Language | string | Required | 언어 코드(ISO 639-1 두 자리)입니다. 예: ko |
ProductId | string | Required | 구독 상품 ID입니다. 앱 서버가 구독 상품 영수증 검증 응답에서 받아 전달한 hiveAxylProductId를 사용합니다. |
ProviderId | SubscriptionPurchasePostRequestProviderId | Required | Google 구독이므로 Google을 지정합니다. |
RequestTimeMs | long | Optional | 요청 시각(밀리초)입니다. |
RequestType | int | Optional | 요청 유형입니다. 1: 신규 구매, 2: 구매 복원 |
ServerId | string | Optional | Hive 콘솔 앱 서버를 따라 등록한 앱 서버 ID입니다. |
호출 예시
PaymentsPostSubscriptionResult의 성공 결과와 이 메서드에서 정의한 도메인별 결과(Outcome)는 아래 예시와 응답 상태에서 확인합니다. 요청 수행이 불가능한 공통 실패(Failure)의 결과 모델과 처리 원칙은 공통 오류 처리를 참조하세요.
using Hive.Axyl.Payments;
using Hive.Axyl.Core;
// payments: 초기화 시 등록된 IPaymentsService (자세한 획득은 [모듈 설치 및 초기화](../init.md) 참고)
IPaymentsService payments = HiveCore.Resolve<IPaymentsService>();
var request = new SubscriptionPurchasePostRequest
{
AxylReceipt = purchaseToken, // PurchaseSubscriptionAsync()에 보낸 값과 같은 구매 토큰
ProductId = verifiedProductId, // 앱 서버가 전달한 구독 영수증 검증 응답의 hiveAxylProductId
Country = "KR",
Currency = "KRW",
Language = "ko",
ProviderId = SubscriptionPurchasePostRequestProviderId.Google
};
PaymentsPostSubscriptionResult result = await payments.PostSubscriptionAsync(request);
switch (result)
{
case PaymentsPostSubscriptionResult.Success success:
if (string.IsNullOrEmpty(success.Data.HiveAxylTransactionId))
{
// 확정할 구독 기록을 찾지 못했습니다. 구독 구매 정보 저장에 보낸 AxylReceipt와 같은 값인지 확인하세요.
Debug.LogError("확정된 구독이 없습니다.");
break;
}
// 구독 확정 성공. 구매 확인(AcknowledgePurchaseAsync)을 진행하세요.
break;
case PaymentsPostSubscriptionResult.VerifyDuplicated:
// 이미 확정된 구독입니다. 구매 확인(AcknowledgePurchaseAsync)을 진행하세요.
break;
// 요청 값 또는 결제 상태 문제
case PaymentsPostSubscriptionResult.PaymentBadRequest:
case PaymentsPostSubscriptionResult.PaymentInvalidParameter:
case PaymentsPostSubscriptionResult.PaymentResourceNotFound:
case PaymentsPostSubscriptionResult.PaymentUnauthorized:
case PaymentsPostSubscriptionResult.VerifyError:
Debug.LogError("요청 값과 결제 상태를 확인하세요.");
break;
case PaymentsPostSubscriptionResult.UnknownOutcome unknownOutcome:
Debug.LogWarning($"알 수 없는 결과: {unknownOutcome.Code}");
break;
// 공통 Failure 처리
case PaymentsPostSubscriptionResult.Failure failure:
HiveError err = failure.Problem;
Debug.LogError($"[{err.Code}] {err.Message} (trace: {err.TraceId})");
break;
// 안전망: 처리하지 않은 결과
default:
Debug.LogWarning($"처리되지 않은 결과: {result.GetType().Name}");
break;
}
응답 데이터
성공 시 PaymentsPostSubscriptionResult.Success의 Data(SubscriptionPurchasePostResponseData)에 확정 결과가 담깁니다. Success가 반환되어도 확정할 구독 기록을 찾지 못하면 Data.HiveAxylTransactionId가 비어 있으므로, 이 값으로 구독이 실제로 확정됐는지 확인하세요.
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
Data.HiveAxylProductId | string | Required | 확정 요청의 구독 상품 ID입니다. |
Data.HiveAxylStoreTransactionId | string? | Optional | 구독 기록의 조회 키입니다. |
Data.HiveAxylTransactionId | string? | Optional | 확정된 구독 기록의 거래 ID입니다. 확정할 구독 기록이 없으면 비어 있습니다. |
Data.Meta | string? | Optional | 응답 메타 정보입니다. |
응답 상태
아래 표에는 PaymentsPostSubscriptionResult의 성공 결과와 이 메서드에서 정의한 도메인별 결과(Outcome)를 정리했습니다. 요청 수행이 불가능한 공통 실패(Failure)의 결과 모델과 처리 원칙은 공통 오류 처리를 참조하세요.
| 응답 케이스 | 설명 | 앱 클라이언트 대응 |
|---|---|---|
Success | 요청을 처리했습니다. 실제 확정 여부는 Data.HiveAxylTransactionId로 확인합니다. | 값이 있으면 거래 완료 처리 진행, 비어 있으면 AxylReceipt와 구독 구매 정보 저장 여부를 확인 |
PaymentBadRequest | 결제 요청을 처리할 수 없습니다. | 요청 값과 호출 조건을 점검 |
PaymentInvalidParameter | 결제 요청 파라미터가 유효하지 않습니다. | 요청 필드를 점검 |
PaymentResourceNotFound | 결제 정보를 찾을 수 없습니다. | 영수증과 거래 정보를 점검 |
PaymentUnauthorized | 결제 요청 권한이 없습니다. | 인증 정보와 요청 권한을 확인 |
VerifyDuplicated | 이미 확정된 구독입니다. | 중복 확정 없이 거래 완료 처리 진행 |
VerifyError | 결제 검증 중 오류가 발생했습니다. | 영수증과 마켓 결제 상태를 점검 |
UnknownOutcome | SDK가 알 수 없는 도메인별 결과입니다. | 실패로 처리하고 결과 코드를 기록 |
Failure | 공통 Failure입니다. 공통 오류 처리를 참조하세요. | 공통 오류 처리 기준에 따라 처리 |
4. 거래 완료 처리
Google 결제 플러그인을 통해 스토어에 구매가 완료되었음을 전달합니다. 상품 유형에 따라 호출하는 메서드가 다릅니다.
- 소모성 상품: 상품 지급과 결제 확정 요청을 마친 뒤
ConsumeAsync()호출. 구매 확인(acknowledge)도 함께 처리되며, 소모 처리 후 같은 상품 재구매 가능 - 비소모성 상품 · 구독 상품: 소모 처리 없이 구매 확인만 수행하는
AcknowledgePurchaseAsync()호출. 구독 상품은 구독 완료 처리를 마친 뒤 호출
ConsumeAsync() 또는 AcknowledgePurchaseAsync()를 호출하기 전에 반드시 StartConnectionAsync()로 BillingClient를 연결하세요. 연결 방법은 1단계. 연동 환경 구성을 참조하세요.
3일 이내 구매 확인 필수
Google Play 정책상 구매 후 3일 이내에 ConsumeAsync() 또는 AcknowledgePurchaseAsync()를 호출하지 않으면 Google이 자동으로 결제를 환불합니다.
4.1. 소모성 상품 거래 완료 처리
ConsumeAsync
소모성 상품의 거래 완료 처리를 구현하려면 Google 결제 플러그인이 제공하는 ConsumeAsync()를 호출하세요. 결제 시 받은 구매 영수증(purchaseToken)으로 소모 대상 거래를 식별합니다. 소모 처리 후 사용자는 같은 상품을 다시 구매할 수 있습니다.
호출 파라미터
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
| purchaseToken | string | Required | 소모할 구매 건의 영수증(purchaseToken)입니다. PurchasesUpdated 이벤트 또는 QueryPurchasesAsync()에서 획득한 GooglePurchase.PurchaseToken 값을 전달합니다. |
| ct | CancellationToken | Optional | 취소 토큰입니다. |
호출 예시
GooglePlayBillingServiceConsumeResult의 성공 결과와 이 메서드에서 정의한 도메인별 결과(Outcome)는 아래 예시와 응답 상태에서 확인합니다. 요청 수행이 불가능한 공통 실패(Failure)의 결과 모델과 처리 원칙은 공통 오류 처리를 참조하세요.
using Hive.Axyl.Core;
using Hive.Axyl.Payments.Addon.Google;
using System.Threading;
// Google 결제 플러그인 획득 (초기화 시 builder.AddPlayBilling()으로 등록 필요)
if (!HiveCore.TryResolve<IGooglePlayBillingPlugin>(out var googlePlugin))
{
Debug.LogError("Google Play Billing 플러그인이 등록되지 않았습니다.");
return;
}
// 소모성 상품 거래 완료 처리 (purchaseToken은 결제 시 받은 값)
GooglePlayBillingServiceConsumeResult consumeResult =
await googlePlugin.ConsumeAsync(purchaseToken, CancellationToken.None);
switch (consumeResult)
{
case GooglePlayBillingServiceConsumeResult.Success:
// 소모 성공. 사용자가 같은 상품을 다시 구매할 수 있습니다.
Debug.Log("소모성 상품 거래 완료 처리 성공");
break;
case GooglePlayBillingServiceConsumeResult.PurchaseNotFound:
// 해당 purchaseToken에 대응하는 구매 건을 찾을 수 없음
Debug.LogError("구매 건을 찾을 수 없습니다. purchaseToken을 확인하세요.");
break;
case GooglePlayBillingServiceConsumeResult.UnknownOutcome:
Debug.LogWarning("알 수 없는 소모 결과입니다.");
break;
case GooglePlayBillingServiceConsumeResult.Failure failure:
Debug.LogError($"소모 실패: {failure}");
break;
default:
Debug.LogWarning($"처리되지 않은 결과: {consumeResult.GetType().Name}");
break;
}
응답 상태
반환 객체 GooglePlayBillingServiceConsumeResult는 아래 케이스 중 하나로 분기됩니다. switch 구문으로 처리하세요.
| 응답 케이스 | 설명 | 앱 클라이언트 대응 |
|---|---|---|
Success | 소모 성공. 구매 확인(acknowledge)도 함께 처리됩니다. | 결제 플로우 종료 |
PurchaseNotFound | 해당 purchaseToken에 대응하는 구매 건을 찾을 수 없음 | purchaseToken 값 확인 |
UnknownOutcome | 알 수 없는 결과 | 재시도 또는 오류 안내 |
Failure | 공통 Failure입니다. 공통 오류 처리를 참조하세요. | 공통 오류 처리 기준에 따라 처리 |
4.2. 비소모성 · 구독 상품 구매 확인
AcknowledgePurchaseAsync
비소모성 상품 또는 구독 상품의 구매 확인을 구현하려면 Google 결제 플러그인이 제공하는 AcknowledgePurchaseAsync()를 호출하세요. 소모 처리 없이 구매 확인만 수행합니다.
호출 파라미터
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
| purchaseToken | string | Required | 구매 확인할 구매 건의 영수증(purchaseToken)입니다. |
| ct | CancellationToken | Optional | 취소 토큰입니다. |
호출 예시
using Hive.Axyl.Core;
using Hive.Axyl.Payments.Addon.Google;
using System.Threading;
// Google 결제 플러그인 획득
if (!HiveCore.TryResolve<IGooglePlayBillingPlugin>(out var googlePlugin))
{
Debug.LogError("Google Play Billing 플러그인이 등록되지 않았습니다.");
return;
}
// 비소모성 · 구독 상품 구매 확인 (purchaseToken은 결제 시 받은 값)
GooglePlayBillingServiceAcknowledgePurchaseResult ackResult =
await googlePlugin.AcknowledgePurchaseAsync(purchaseToken, CancellationToken.None);
switch (ackResult)
{
case GooglePlayBillingServiceAcknowledgePurchaseResult.Success:
Debug.Log("구매 확인 성공");
break;
case GooglePlayBillingServiceAcknowledgePurchaseResult.PurchaseNotFound:
Debug.LogError("구매 건을 찾을 수 없습니다. purchaseToken을 확인하세요.");
break;
case GooglePlayBillingServiceAcknowledgePurchaseResult.AlreadyAcknowledged:
// 이미 구매 확인이 완료된 건. 추가 처리 불필요
Debug.Log("이미 구매 확인이 완료되었습니다.");
break;
case GooglePlayBillingServiceAcknowledgePurchaseResult.UnknownOutcome:
Debug.LogWarning("알 수 없는 구매 확인 결과입니다.");
break;
case GooglePlayBillingServiceAcknowledgePurchaseResult.Failure failure:
Debug.LogError($"구매 확인 실패: {failure}");
break;
default:
Debug.LogWarning($"처리되지 않은 결과: {ackResult.GetType().Name}");
break;
}
응답 상태
반환 객체 GooglePlayBillingServiceAcknowledgePurchaseResult는 아래 케이스 중 하나로 분기됩니다. switch 구문으로 처리하세요.
| 응답 케이스 | 설명 | 앱 클라이언트 대응 |
|---|---|---|
Success | 구매 확인 성공 | 결제 플로우 종료 |
PurchaseNotFound | 해당 purchaseToken에 대응하는 구매 건을 찾을 수 없음 | purchaseToken 값 확인 |
AlreadyAcknowledged | 이미 구매 확인이 완료된 건 | 추가 처리 불필요 |
UnknownOutcome | 알 수 없는 결과 | 재시도 또는 오류 안내 |
Failure | 공통 Failure입니다. 공통 오류 처리를 참조하세요. | 공통 오류 처리 기준에 따라 처리 |
더 알아보기
결제 과정에서 네트워크 오류 등으로 상품을 지급하지 못한 경우 구매 복구를 구현하세요.