5단계. 상품 지급과 거래 완료 처리
영수증 검증에 성공하면 앱 서버가 사용자에게 상품을 지급하고, 앱 클라이언트가 지급 결과를 기록한 뒤 거래를 완료 처리합니다. 앱 클라이언트가 이 단계에서 사용하는 hiveAxylTransactionId와 구독 상품 ID는 앱 서버가 영수증 검증 응답에서 받아 전달한 값입니다. 결제 확정 요청과 지급 결과 기록의 역할은 결제 확정과 지급 결과 기록을 참조하세요.
아래 과정을 따라 상품 지급과 거래 완료 처리를 수행합니다.
1. 상품 지급
앱 서버가 영수증 검증 결과를 확인한 뒤 결제한 사용자에게 상품을 지급합니다. 상품 지급은 앱에서 구현하며, 지급 방식은 앱별로 다를 수 있습니다.
결제 확정 요청을 보내는 RequestPurchaseAsync()는 소모성 상품에만 사용합니다. 구독 상품은 3. 구독 완료 처리로 구독을 확정합니다. Apple 결제에서 상품 지급과 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 = axylTransactionId, // 앱 서버가 전달한 영수증 검증 응답의 hiveAxylTransactionId
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. 거래 완료 처리를 진행하세요.
PostSubscriptionAsync
PostSubscriptionAsync()는 구독 구매 정보 저장에서 저장한 구독 기록을 찾아 구독을 확정하고 거래를 종료합니다.
호출 파라미터
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
| request | SubscriptionPurchasePostRequest | Required | 구독 확정 요청 데이터 객체입니다. |
| context | ApiCallContext | Optional | 호출 단위 설정 객체입니다. 멱등키·취소 토큰·요청 정책을 지정하며, 생략하면 기본값이 사용됩니다. |
SubscriptionPurchasePostRequest
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
AppVersion | string | Optional | 앱 버전입니다. |
AxylReceipt | string | Required | 구독 구매 정보 저장의 PurchaseSubscriptionAsync() 요청에 전달한 StoreKit 2 트랜잭션 JWS를 그대로 전달합니다. 서버는 이 값으로 저장된 구독 기록을 찾으므로, 값이 다르면 구독이 확정되지 않습니다. |
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 | Apple 구독이므로 Apple을 지정합니다. |
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 = transactionJws, // PurchaseSubscriptionAsync()에 보낸 값과 같은 JWS
ProductId = verifiedProductId, // 앱 서버가 전달한 구독 영수증 검증 응답의 hiveAxylProductId
Country = "KR",
Currency = "KRW",
Language = "ko",
ProviderId = SubscriptionPurchasePostRequestProviderId.Apple
};
PaymentsPostSubscriptionResult result = await payments.PostSubscriptionAsync(request);
switch (result)
{
case PaymentsPostSubscriptionResult.Success success:
if (string.IsNullOrEmpty(success.Data.HiveAxylTransactionId))
{
// 확정할 구독 기록을 찾지 못했습니다. 구독 구매 정보 저장에 보낸 AxylReceipt와 같은 값인지 확인하세요.
Debug.LogError("확정된 구독이 없습니다.");
break;
}
// 구독 확정 성공. 거래 완료 처리(FinishTransactionAsync)를 진행하세요.
break;
case PaymentsPostSubscriptionResult.VerifyDuplicated:
// 이미 확정된 구독입니다. 거래 완료 처리(FinishTransactionAsync)를 진행하세요.
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. 거래 완료 처리
Apple 결제 플러그인(IAppleStoreKitPlugin)의 FinishTransactionAsync()를 호출하여 Apple App Store에 거래가 완료되었음을 전달합니다. 소모성 상품은 상품 지급과 결제 확정 요청을 마친 뒤, 구독 상품은 구독 완료 처리를 마친 뒤 호출하세요. 소모성 상품은 거래 완료 처리 후 사용자가 같은 상품을 다시 구매할 수 있습니다.
FinishTransactionAsync
호출 파라미터
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
| request | FinishTransactionRequest | Required | 거래 완료 처리 요청 데이터 객체입니다. |
| ct | CancellationToken | Optional | 취소 토큰입니다. |
FinishTransactionRequest
| 필드명 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
TransactionId | uint64 | Required | 완료 처리할 Apple 트랜잭션 ID입니다. PurchaseAsync() 성공 응답의 AppleTransaction.Id 값을 사용합니다. |
호출 예시
AppleStoreKitServiceFinishTransactionResult의 성공 결과와 이 메서드에서 정의한 도메인별 결과(Outcome)는 아래 예시와 응답 상태에서 확인합니다. 요청 수행이 불가능한 공통 실패(Failure)의 결과 모델과 처리 원칙은 공통 오류 처리를 참조하세요.
using Hive.Axyl.Core;
using Hive.Axyl.Payments.Addon.Apple;
using System.Threading;
// Apple StoreKit 플러그인 획득
if (!HiveCore.TryResolve<IAppleStoreKitPlugin>(out var applePlugin))
{
Debug.LogError("Apple StoreKit 플러그인이 등록되지 않았습니다.");
return;
}
// PurchaseAsync() 성공 응답에서 받은 트랜잭션 ID로 거래 완료 처리
var request = new FinishTransactionRequest
{
TransactionId = transactionId // AppleTransaction.Id 값
};
AppleStoreKitServiceFinishTransactionResult result =
await applePlugin.FinishTransactionAsync(request, CancellationToken.None);
switch (result)
{
case AppleStoreKitServiceFinishTransactionResult.Success:
// 거래 완료 처리 성공. 사용자는 같은 상품을 다시 구매할 수 있습니다.
Debug.Log("거래 완료 처리 성공");
break;
case AppleStoreKitServiceFinishTransactionResult.TransactionNotFound:
// 해당 트랜잭션 ID를 찾을 수 없는 경우
Debug.LogError("트랜잭션을 찾을 수 없습니다. 트랜잭션 ID를 확인하세요.");
break;
case AppleStoreKitServiceFinishTransactionResult.UnknownOutcome:
Debug.LogWarning("알 수 없는 결과입니다.");
break;
case AppleStoreKitServiceFinishTransactionResult.Failure failure:
Debug.LogError($"거래 완료 처리 실패: {failure}");
break;
default:
Debug.LogWarning($"처리되지 않은 결과: {result.GetType().Name}");
break;
}
응답 데이터
성공 시 별도 반환 데이터는 없습니다.
응답 상태
반환 객체 AppleStoreKitServiceFinishTransactionResult는 아래 케이스 중 하나로 분기됩니다. switch 구문으로 처리하세요.
| 응답 케이스 | 설명 | 앱 클라이언트 대응 |
|---|---|---|
Success | 거래 완료 처리 성공 | 결제 플로우 종료 처리 |
TransactionNotFound | 해당 트랜잭션 ID를 찾을 수 없는 경우 | TransactionId 값 확인 |
UnknownOutcome | 알 수 없는 결과 | 재시도 또는 오류 안내 |
Failure | 공통 Failure입니다. 공통 오류 처리를 참조하세요. | 공통 오류 처리 기준에 따라 처리 |
더 알아보기
결제 과정에서 네트워크 오류 등으로 상품을 지급하지 못한 경우 구매 복구를 구현하세요.