콘텐츠로 이동

5단계. 상품 지급과 거래 완료 처리

영수증 검증에 성공하면 앱 서버가 사용자에게 상품을 지급하고, 앱 클라이언트가 지급 결과를 기록한 뒤 거래를 완료 처리합니다. 앱 클라이언트가 이 단계에서 사용하는 hiveAxylTransactionId와 구독 상품 ID는 앱 서버가 영수증 검증 응답에서 받아 전달한 값입니다. 결제 확정 요청과 지급 결과 기록의 역할은 결제 확정과 지급 결과 기록을 참조하세요.

아래 과정을 따라 상품 지급과 거래 완료 처리를 수행합니다.

1. 상품 지급

앱 서버가 영수증 검증 결과를 확인한 뒤 결제한 사용자에게 상품을 지급합니다. 상품 지급은 앱에서 구현하며, 지급 방식은 앱별로 다를 수 있습니다.

결제 확정 요청을 보내는 RequestPurchaseAsync()는 소모성 상품에만 사용합니다. 구독 상품은 3. 구독 완료 처리로 구독을 확정합니다. Apple 결제에서 상품 지급과 RequestPurchaseAsync()의 호출 순서는 앱이 결정하지만, 상품을 먼저 지급한 뒤 결제 확정 요청을 전송하세요. 2. 상품 지급 결과 저장은 4. 거래 완료 처리의 선행 조건이 아니므로, 지급 결과 저장이 실패해도 거래 완료 처리를 미루지 마세요.

2. 상품 지급 결과 저장

Method

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 응답 메타 정보입니다.

응답 예시

// Success 분기에서 success.Data.Meta를 확인할 수 있습니다.

응답 상태

아래 표에는 PaymentsItemResultResult의 성공 결과와 이 메서드에서 정의한 도메인별 결과(Outcome)를 정리했습니다. 요청 수행이 불가능한 공통 실패(Failure)의 결과 모델과 처리 원칙은 공통 오류 처리를 참조하세요.

응답 케이스 설명 앱 클라이언트 대응
Success 지급 결과 기록 성공 다음 결제 처리 진행
InvalidQuantity 지급 수량이 유효하지 않습니다. 지급 수량을 확인
InvalidStatus 지급 결과 상태 값이 유효하지 않습니다. Status 값을 확인
PaymentBadRequest 요청을 처리할 수 없습니다. 요청 값과 호출 조건을 점검
PaymentInvalidParameter 유효하지 않은 파라미터인 경우 (예: AxylTransactionId 누락) 필수 파라미터 설정 확인
PaymentResourceNotFound 결제 정보를 찾을 수 없습니다. 영수증·주문 번호·결제 상태를 확인
UnknownOutcome SDK가 알 수 없는 도메인별 결과입니다. 실패로 처리하고 결과 코드를 기록
Failure 공통 Failure입니다. 공통 오류 처리를 참조하세요. 공통 오류 처리 기준에 따라 처리

3. 구독 완료 처리

구독 상품은 앱 서버의 구독 영수증 검증과 구독 혜택 지급이 끝난 뒤 PostSubscriptionAsync()를 호출해 Hive Axyl 서버에서 구독을 확정합니다. 구독이 확정되면 4. 거래 완료 처리를 진행하세요.

Method

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에 거래가 완료되었음을 전달합니다. 소모성 상품은 상품 지급과 결제 확정 요청을 마친 뒤, 구독 상품은 구독 완료 처리를 마친 뒤 호출하세요. 소모성 상품은 거래 완료 처리 후 사용자가 같은 상품을 다시 구매할 수 있습니다.

Method

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입니다. 공통 오류 처리를 참조하세요. 공통 오류 처리 기준에 따라 처리

더 알아보기

결제 과정에서 네트워크 오류 등으로 상품을 지급하지 못한 경우 구매 복구를 구현하세요.