콘텐츠로 이동

영수증 확인

미지급된 아이템의 영수증 정보 요청

상품 구매 과정에서 여러 가지 상황으로 인하여 아이템 지급을 실패하거나 취소되는 경우가 발생할 수 있습니다. 이런 상황을 대비하기 위해서는 미지급된 아이템의 영수증 정보를 요청해야 합니다.

IAPV4 클래스의 restore() 메서드를 호출하여 미지급된 아이템의 정보를 요청하세요.

  • restore() 메서드를 호출한 결과값이 SUCCESS이고 ReceiptList가 전달되었다면, 복구할 아이템이 존재하는 상황입니다. 미지급된 아이템을 지급하고 완료로 처리하세요.
  • restore() 메서드를 호출한 결과 값이 SUCCESS가 아닌 경우에는 게임에서 처리하지 않아도 됩니다.
  • restore() 메서드 호출 후 에러가 발생하더라도 유저가 반드시 알아야 하는 에러가 아니라면 별도 알림 없이 유저가 게임을 계속 플레이할 수 있도록 구현하세요.
  • Google Play용 Windows 앱(Hive SDK v4 Unity Windows 23.0.0 이상)에서 `GOOGLE_PLAYSTORE` 마켓을 사용할 때, restore() 메서드를 호출하는 시점에 결제용 Google 계정의 인증 정보가 만료되면, Hive SDK는 자동으로 재인증 과정을 실행합니다.

다음은 미지급된 아이템의 영수증 정보를 요청하는 예제 코드입니다.

API Reference: hive .IAPV4.restore

using hive;    
    IAPV4.restore((ResultAPI result, List receiptList) => {    
         if (result.isSuccess()) {    
            if (result.errorCode = ResultAPI.ErrorCode.SUCCESS) {    
            // TODO: Request receipt verification using the received receiptList    
            } else if (result.errorCode = ResultAPI.ErrorCode.NOT_OWNED) {    
            // No unpaid items    
            }    
        }    
}
#include "HiveIAPV4.h"

FHiveIAPV4::Restore(FHiveIAPV4OnRestoreDelegate::CreateLambda([this](const FHiveResultAPI& Result, const TArray<FHiveIAPV4Receipt>& IAPV4ReceiptList) {
        if (Result.IsSuccess()) {
                if (Result.ErrorCode == FHiveResultAPI::EErrorCode::SUCCESS) {
                        // TODO: 전달받은 IAPV4ReceiptList 로 영수증 검증 요청
                } else if (Result.ErrorCode == FHiveResultAPI::EErrorCode::RESTORE_NOT_OWNED) {
                        // 미지급된 아이템 없음
                }
        }
}));

API Reference: IAPV4 ::restore

#include <HIVE_SDK_Plugin/HIVE_CPP.h>    
    using namespace std;    
    using namespace hive;    
    IAPV4::restore([=](ResultAPI const & result, vector<reference_wrapper<IAPV4Receipt>> receiptList) {    
         if (result.isSuccess()) {    
            if (result.errorCode == ResultAPI::ErrorCode::SUCCESS) {    
            // TODO: Request receipt verification using the received receiptList    
            } else if (result.errorCode == ResultAPI::ErrorCode::RESTORE_NOT_OWNED) {    
            // No unpaid items    
            }    
         }    
});

API Reference: IAPV4.restore

import com.hive.IAPV4    
    import com.hive.ResultAPI    
    IAPV4.restore(object : IAPV4.IAPV4RestoreListener {    
         override fun onIAPV4Restore(result: ResultAPI, iapv4ReceiptList: ArrayList<IAPV4.IAPV4Receipt>?) {    
             if (result.isSuccess) {    
                 if (result.errorCode == ResultAPI.SUCCESS) {    
                     // TODO: Request receipt verification using the received iapv4ReceiptList    
                 } else if (result.errorCode == ResultAPI.RESTORE_NOT_OWNED) {    
                     // No unpaid items    
                 }    
             }    
         }    
})

API Reference: com.hive.IAPV4.restore

import com.hive.IAPV4;    
    import com.hive.ResultAPI;    
    IAPV4.INSTANCE.restore((result, iapv4ReceiptList) -> {    
         if (result.isSuccess()) {    
             if (result.getErrorCode() == ResultAPI.Companion.getSUCCESS()) {    
                 // TODO: Request receipt verification using the received iapv4ReceiptList    
             } else if (result.getErrorCode() == ResultAPI.Companion.getRESTORE_NOT_OWNED()) {    
                 // No unpaid items    
             }    
         }    
});

API Reference: IAPV4Interface .restore

import HIVEService    
    IAPV4Interface.restore() { result, receiptList in    
        if result.isSuccess() {    
            if result.getErrorCode() == .success {    
            // TODO: Request receipt verification using the received receiptList    
            } else if result.getErrorCode() == .notOwned {    
            // No unpaid items    
            }    
        }    
}

API Reference: HIVEIAPV4::restore

#import <HIVEService/HIVEService-Swift.h>    
    [HIVEIAPV4 restore: ^(HIVEResultAPI *result, NSArray<HIVEIAPV4Receipt *> *receiptList) {    
         if ([result isSuccess]) {    
            if ([result getErrorCode] == HIVEResultAPITypeSuccess) {    
            // TODO: Request receipt verification using the received receiptList    
            } else if ([result getErrorCode] == HIVEResultAPITypeNotOwned){    
        // No unpaid items    
            }    
         }    
}];
Note

운영체제/마켓별로 지원하는 기능에 차이가 있을 수 있습니다. 자세한 내용은 기능 지원 현황을 확인하세요.

구매 복구 에러코드

에러코드 설명
NEED_INITIALIZE 초기화가 안됨
NETWORK 네트워크 에러
NOT_SUPPORTED restore 불가 상태(기기 앱 내 구입 차단 등),지원되지 않는 마켓 설정 시
INVALID_SESSION 복구를 진행할 수 없는 사용자 세션
IN_PROGRESS restore API가 이미 호출됨
RESTORE_NOT_OWNED 복구할 아이템이 없음
NOT_OWNED 복구할 아이템이 없음
RESPONSE_FAIL 기타 오류. Hive IAP 서버 오류

구매 영수증 검증 요청

구매 성공 후 게임 서버에서 아이템을 지급하기 전에 Hive IAP의 영수증 검증 API를 이용하여 구매 영수증의 유효성 검증을 구현합니다. 영수증 검증 API 응답값의 hiveiap_transaction_id는 영수증 별로 고유하게 발급되는 ID이기 때문에 이 값을 게임 서버에 저장 후 영수증의 중복 여부를 체크할 수 있습니다. Hive IAP 영수증 검증 API를 사용하여 검증하고 요청 파라미터의 모든 값을 전송하면 매출 정보 및 매출에 대한 게임 정보의 애널리틱스 전송을 Hive IAP 서버에서 대행합니다. 또한 Hive One 통합 조회의 결제 조회를 위한 API를 게임에서 따로 개발할 필요가 없습니다. 자세한 내용은 IAP v4 영수증 검증 가이드를 확인하세요.


구매/복구한 아이템의 지급 완료

상품 구매 후 아이템 지급을 완료했다면 IAPV4 클래스의 transactionFinish()를 반드시 호출하여 구매를 완료하세요.

Warning

Unless you call transactionFinish() method after sending an item, 미지급된 아이템으로 남아있게 되고 restore() 메서드를 호출해도 영수증 정보가 반환될 수 있습니다.

Note

Android 환경의 Google Play Store에서 Hive SDK v4.12.0 이후 버전 적용 시, 결제는 성공했으나 IAPV4 클래스의 TransactionFinish() 메서드 호출 결과가 3일 (테스트 환경에서는 5분) 이내에 성공하지 못하면 자동 환불 처리됩니다.

다음은 하나의 지급 요청을 완료하는 예제 코드입니다.

API Reference: hive .IAPV4.transactionFinish

using hive;    
    String marketPid = "{YOUR_PRODUCT_MARKET_PID}";    
    IAPV4.transactionFinish(marketPid, (ResultAPI result, String marketPid) => {    
         if (result.isSuccess()) {    
             // call successful    
         }    
});
#include "HiveIAPV4.h"

FString MarketPid = TEXT("YOUR_PRODUCT_MARKET_PID");
FHiveIAPV4::TransactionFinish(MarketPid, FHiveIAPV4OnTransactionFinishDelegate::CreateLambda([this, idx](const FHiveResultAPI& Result, const FString& MarketPid) {
        if (Result.IsSuccess()) {
                // API 호출 성공
        }
}));

API Reference: IAPV4 ::transactionFinish

#include <HIVE_SDK_Plugin/HIVE_CPP.h>    
using namespace std;    
using namespace hive;    
string marketPid = "{YOUR_PRODUCT_MARKET_PID}";    

IAPV4::transactionFinish(marketPid, [=](ResultAPI const & result, string marketPid) {    
if (result.isSuccess()) {    
// call successful    
}    
});

API Reference: IAPV4.transactionFinish

import com.hive.IAPV4    
    import com.hive.ResultAPI    
    val marketPid = "{YOUR_PRODUCT_MARKET_PID}"    
    IAPV4.transactionFinish(marketPid, object : IAPV4.IAPV4TransactionFinishListener {    
         override fun onIAPV4TransactionFinish(result: ResultAPI, marketPid: String) {    
             if (result.isSuccess) {    
                 // call successful    
             }    
         }    
})

API Reference: com.hive.IAPV4.transactionFinish

import com.hive.IAPV4;    
    import com.hive.ResultAPI;    
    String pid = "{YOUR_PRODUCT_MARKET_PID}";    
    IAPV4.INSTANCE.transactionFinish(pid, (result, marketPid) -> {    
         if (result.isSuccess()) {    
             // call successful    
         }    
});

API Reference: IAPV4Interface.transactionFinish

import HIVEService    
    let marketPid = "{YOUR_PRODUCT_MARKET_PID}"    
    IAPV4Interface.transactionFinish() { result, marketPid in    
    if result.isSuccess() {    
        // call successful    
        }    
}

API Reference: HIVEIAPV4::transactionFinish:handler:

#import <HIVEService/HIVEService-Swift.h>    
    NSString *marketPid = @"{YOUR_PRODUCT_MARKET_PID}";    
    [HIVEIAPV4 transactionFinish: marketPid handler: ^(HIVEResultAPI *result, NSString *marketPid) {    
         if ([result isSuccess]) {    
             // call successful    
         }    
}];

다음은 여러 개의 지급 요청을 완료하는 예제 코드입니다.

API Reference: hive .IAPV4.transactionMultiFinish

using hive;    
    List marketPidList = new List();    
    marketPidList.Add("{YOUR_PRODUCT_MARKET_PID_01}");    
    marketPidList.Add("{YOUR_PRODUCT_MARKET_PID_02}");    
    marketPidList.Add("{YOUR_PRODUCT_MARKET_PID_03}");    
    IAPV4.transactionMultiFinish((List<ResultAPI> resultList, List<String> marketPidList) => {    
        for (ResultAPI result in resultList) {    
            if (result.isSuccess()) {    
            // call successful    
            }    
        }    
});
#include "HiveIAPV4.h"

TArray<FString> MarketPidList;
MarketPidList.Add(TEXT("YOUR_PRODUCT_MARKET_PID_01"));
MarketPidList.Add(TEXT("YOUR_PRODUCT_MARKET_PID_02"));
MarketPidList.Add(TEXT("YOUR_PRODUCT_MARKET_PID_03"));

FHiveIAPV4::TransactionMultiFinish(MarketPidList, FHiveIAPV4OnTransactionMultiFinishDelegate::CreateLambda([this](const TArray<FHiveResultAPI>& ResultList, const TArray<FString>& MarketPidList) {
        for (const auto& Result : ResultList) {
                if (Result.IsSuccess()) {
                        // API 호출 성공
                }
        }
}));

API Reference: IAPV4 ::transactionMultiFinish

#include <HIVE_SDK_Plugin/HIVE_CPP.h>    
    using namespace std;    
    using namespace hive;    
    vector<string> marketPidList;    
    marketPidList.push_back("{YOUR_PRODUCT_MARKET_PID_01}");    
    marketPidList.push_back("{YOUR_PRODUCT_MARKET_PID_02}");    
    marketPidList.push_back("{YOUR_PRODUCT_MARKET_PID_03}");    

    IAPV4::transactionMultiFinish(marketPidList, [=](vector<ResultAPI> const & resultList, vector<string> const & marketPidList) {    
        for (ResultAPI result : resultList) {    
            if (result.isSuccess()) {    
                        // call successful    
                    }    
        }    
});

API Reference: IAPV4.transactionMultiFinish

import com.hive.IAPV4    
    import com.hive.ResultAPI    
    val marketPidList = arrayListOf(    
         "{YOUR_PRODUCT_MARKET_PID_01}",    
         "{YOUR_PRODUCT_MARKET_PID_02}",    
         "{YOUR_PRODUCT_MARKET_PID_03}"    
    )    
    IAPV4.transactionMultiFinish(marketPidList, object : IAPV4.IAPV4TransactionMultiFinishListener {    
         override fun onIAPV4TransactionMultiFinish(resultList: ArrayList<ResultAPI>, marketPidList: ArrayList<String>) {    
             for (i in 0 until resultList.size) {    
                 if (resultList[i].isSuccess) {    
                     // call successful    
                 }    
             }    
         }    
})

API Reference: com.hive.IAPV4.transactionMultiFinish

import com.hive.IAPV4;    
    import com.hive.ResultAPI;    
    ArrayList<String> marketPidList = new ArrayList<>(Arrays.asList(    
             "{YOUR_PRODUCT_MARKET_PID_01}",    
             "{YOUR_PRODUCT_MARKET_PID_02}",    
             "{YOUR_PRODUCT_MARKET_PID_03}"    
    ));    
    IAPV4.INSTANCE.transactionMultiFinish(marketPidList, (resultList, marketPidList1) -> {    
         for (ResultAPI result : resultList) {    
             if (result.isSuccess()) {    
                 // call successful    
             }    
         }    
});

API Reference: IAPV4Interface.transactionMultiFinish

import HIVEService    
    var marketPidList = [String]()    
    marketPidList.append("{YOUR_PRODUCT_MARKET_PID_01}")    
    marketPidList.append("{YOUR_PRODUCT_MARKET_PID_02}")    
    marketPidList.append("{YOUR_PRODUCT_MARKET_PID_03}")    
    IAPV4Interface.transactionMultiFinish(marketPidList) { resultList, marketPidList in    
        for result in resultList {    
            if result.isSuccess() {    
            // call successful    
            }    
        }    
}

API Reference: HIVEIAPV4::transactionMultiFinish

#import <HIVEService/HIVEService-Swift.h>    
    NSMutableArray<NSString *> *maketPidList = [NSMutableArray array];    
    [maketPidList addObject:@"{YOUR_PRODUCT_MARKET_PID_01}"];    
    [maketPidList addObject:@"{YOUR_PRODUCT_MARKET_PID_02}"];    
    [maketPidList addObject:@"{YOUR_PRODUCT_MARKET_PID_03}"];    

    [HIVEIAPV4 transactionMultiFinish: maketPidList handler: ^(NSArray<HIVEResultAPI *> *resultList, NSArray<NSString *> *marketPidList) {    
         for (HIVEResultAPI *result in resultList) {    
             if ([result isSuccess]) {    
                 // call successful    
             }    
         }    
}];

유저 구매 활동 정보 얻기: AccountUuid를 활용

하나의 게임 계정이 여러 앱 스토어 계정을 동원해 결제하는 등 결제 시스템을 악용하는 사용자가 존재할 수 있습니다. 게임 스튜디오 측에서는 결제 사용자 정보(AccountUuid)를 대조하는 방법으로 특정 사용자 행태를 얻어 분석할 수 있습니다.

아래 메서드는 Hive SDK가 앱 스토어에 전달하는 UUID v3 형태의 PlayerId 해시값인 AccountUuid를 반환합니다.

API Reference: IAPV4.getAccountUuid

using hive;    
String accountUuid = IAPV4.getAccountUuid();
#include "HiveIAPV4.h"

FString AccountUuid = FHiveIAPV4::GetAccountUuid();

API Reference: IAPV4 ::getAccountUuid

#include <HIVE_SDK_Plugin/HIVE_CPP.h>    
    using namespace std;    
    using namespace hive;    
string accountUuid = IAPV4::getAccountUuid();

API Reference: IAPV4.getAccountUuid

import com.hive.IAPV4    
val accountUuid = IAPV4.getAccountUuid()

API Reference: IAPV4.INSTANCE .getAccountUuid

import com.hive.IAPV4;    
String accountUuid = IAPV4.INSTANCE.getAccountUuid();

API Reference: IAPV4Interface.getAccountUuid

import HIVEService    
let accountUuid = IAPV4Interface.getAccountUuid()

API Reference: [HIVEIAPV4 getAccountUuid ]

#import <HIVEService/HIVEService-Swift.h>    
NSString *accountUuid = [HIVEIAPV4 getAccountUuid];

게임 스튜디오가 이 해시값과 구매 영수증 정보를 함께 사용하면 어떤 사용자의 상품 구매가 정상적이었는지 분석할 수 있습니다. 아래는 AccountUuid를 활용해 특정 PlayerId를 가진 게임 계정 내에 여러 게임 캐릭터가 있을 때, 이 게임 계정의 구매 활동을 분석하는 예시입니다. 이 예시는 개발 참고용이며 실제 동작을 보장하지 않습니다.  

구매 메타 정보를 게임 서버에 미리 저장

구매 요청이 일어나기 전에 구매 메타 정보(AccountUuid와 케릭터 정보 등)을 게임 서버에 저장합니다.

/* The game client pass the info to the game server before the purchase occurs.
*
* @param purchaseTime: purchase time. The receipts are usually based on UTC. since the epoch (Jan 1, 1970).
* @param productId: purchase product ID.
* @param accountUuId: hashed PlayerId. It is included in the receipt and can be obtained with IAPV4.getAccountUuid().
* @param characterId: the character identifier within PlayerId.
*/
game.sendPurchaseGameData(purchaseTime, productId, accountUuid, characterId)   

bypassInfo 전달

구매 요청이 일어나면 bypassInfo 데이터를 게임 서버에 전달합니다. 아래 코드는 개발 참고용 의사 코드입니다.


IAPV4.purchase(productId) {
    iapv4Receipt -> game.sendReceipt(iapv4Receipt.purchase_bypass_info)
}

 

영수증 검증 요청

게임 서버는 bypassInfo 데이터를 Hive IAP v4 서버에 전달하고 영수증 검증을 요청합니다. 영수증 검증 요청은 다음을 확인하세요. AccountUuid는 응답 값에 있는 앱 마켓/스토어 영수증 원문인 hiveiap_receipt안에 다음과 같이 포함되어 있습니다.


# Apple
# 구매 시간: purchase_date_ms
# 구매 상품: product_id
# AccountUuid: app_account_token   

# Google
# 구매 시간: purchaseTime
# 구매 상품: productId
# AccountUuid: obfuscatedAccountId

 

구매 메타 정보의 AccountUuidhiveiap_receiptAccountUuid를 대조해 분석

앞서 구매 메타 정보에 저장해두었던 AccountUuid와 영수증 검증 응답 값에서 얻은 AccountUuid를 대조해 게임 캐릭터 정보 등 분석에 필요한 유저 정보를 찾습니다. 구매 메타 정보와 영수증 정보를 매치시켜 해당 게임 계정의 구매 활동을 분석할 수 있습니다.

한 유저가 여러 아이템을 구매한 경우, 구매 메타 정보와 hiveiap_receiptpurchaseTime을 대조하여 각 구매 건을 특정할 수 있습니다. hiveiap_receipt에 관한 자세한 내용은 영수증 검증에서 마켓별 API 응답값을 확인하세요.

유저 구매 활동 정보 얻기: iapPayload를 활용

Purchase()로 구매를 요청할 때 iapPayload에 게임사에서 사전에 정의한 구매 활동 분석용 데이터를 넣으면, Hive 서버가 iapPayload에 해당하는 구매 영수증을 매치시켜 게임 서버에 전달합니다. 게임 스튜디오는, 영수증에 있는 정보와 iadPayload에 있는 정보를 함께 사용하여 사용자 구매 활동을 분석할 수 있습니다.

Note

이 방법을 사용하면 Hive 서버에서 각 구매 건의 구매 메타 정보를 영수증과 매치시켜 전달해주므로, 위 방법과 같이 AccountUuid로 구매 메타 데이터와 영수증을 게임 스튜디오가 직접 매치할 필요가 없습니다.

iapPayload를 정의해 Purchase() 실행

상품 구매 요청을 실행할 때 iapPayload에 원하는 필드와 값을 정의하여 인자로 사용합니다. 이 과정은 구매 메타 데이터 정의와 유사합니다.

영수증 검증 요청하여 iapPayload와 이에 해당하는 영수증을 수령

결제가 완료되고 영수증 검증 요청을 할 때, Hive 서버는 게임 서버에 구매 건별로 iapPayload와 이에 해당하는 영수증을 매치시켜 전달합니다. 게임 스튜디오는 iapPayload 영수증에 있는 정보를 활용해 유저의 구매 활동을 분석할 수 있습니다.