미지급된 아이템의 영수증 정보 요청
상품 구매 과정에서 여러 가지 상황으로 인하여 아이템 지급을 실패하거나 취소되는 경우가 발생할 수 있습니다. 이런 상황을 대비하기 위해서는 미지급된 아이템의 영수증 정보를 요청해야 합니다.
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
1 2 3 4 5 6 7 8 9 10 11 |
using hive; IAPV4.restore(ResultAPI result, List receiptList) => { if (result.isSuccess()) { if (result.errorCode = ResultAPI.ErrorCode.SUCCESS) { // TODO: 전달받은 receiptList로 영수증 검증 요청 } else if (result.errorCode = ResultAPI.ErrorCode.NOT_OWNED) { // 미지급된 아이템 없음 } } } |
API Reference: IAPV4::restore
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#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: 전달받은 receiptList로 영수증 검증 요청 } else if (result.errorCode == ResultAPI::ErrorCode::RESTORE_NOT_OWNED) { // 미지급된 아이템 없음 } } }); |
API Reference: IAPV4.restore
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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: 전달받은 iapv4ReceiptList 로 영수증 검증 요청 } else if (result.errorCode == ResultAPI.RESTORE_NOT_OWNED) { // 미지급된 아이템 없음 } } } }) |
API Reference: com.hive.IAPV4.restore
1 2 3 4 5 6 7 8 9 10 11 12 |
import com.hive.IAPV4; import com.hive.ResultAPI; IAPV4.INSTANCE.restore((result, iapv4ReceiptList) -> { if (result.isSuccess()) { if (result.getErrorCode() == ResultAPI.Companion.getSUCCESS()) { // TODO: 전달받은 iapv4ReceiptList 로 영수증 검증 요청 } else if (result.getErrorCode() == ResultAPI.Companion.getRESTORE_NOT_OWNED()) { // 미지급된 아이템 없음 } } }); |
API Reference: IAPV4Interface.restore
1 2 3 4 5 6 7 8 9 10 11 |
import HIVEService IAPV4Interface.restore() { result, receiptList in if result.isSuccess() { if result.getErrorCode() == .success { // TODO: 전달받은 receiptList로 영수증 검증 요청 } else if result.getErrorCode() == .notOwned { // 미지급된 아이템 없음 } } } |
API Reference: HIVEIAPV4::restore
1 2 3 4 5 6 7 8 9 10 11 |
#import <HIVEService/HIVEService-Swift.h> [HIVEIAPV4 restore: ^(HIVEResultAPI *result, NSArray<HIVEIAPV4Receipt *> *receiptList) { if ([result isSuccess]) { if ([result getErrorCode] == HIVEResultAPITypeSuccess) { // TODO: 전달받은 receiptList로 영수증 검증 요청 } else if ([result getErrorCode] == HIVEResultAPITypeNotOwned){ // 미지급된 아이템 없음 } } }]; |
구매 복구 에러코드
에러코드 | 설명 |
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 Once 통합 조회의 결제 조회를 위한 API를 게임에서 따로 개발할 필요가 없습니다. 자세한 내용은 IAP v4 영수증 검증 가이드를 확인하세요.
구매/복구한 아이템의 지급 완료
상품 구매 후 아이템 지급을 완료했다면 IAPV4 클래스의 transactionFinish()
를 반드시 호출하여 구매를 완료하세요.
다음은 하나의 지급 요청을 완료하는 예제 코드입니다.
API Reference: hive.IAPV4.transactionFinish
1 2 3 4 5 6 7 8 9 |
using hive; String marketPid = "{YOUR_PRODUCT_MARKET_PID}"; IAPV4.transactionFinish(marketPid, (ResultAPI result, String marketPid) => { if (result.isSuccess()) { // 호출 성공 } }); |
API Reference: IAPV4::transactionFinish
1 2 3 4 5 6 7 8 9 10 11 |
#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()) { // 호출 성공 } }); |
API Reference: IAPV4.transactionFinish
1 2 3 4 5 6 7 8 9 10 11 12 |
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) { // 호출 성공 } } }) |
API Reference: com.hive.IAPV4.transactionFinish
1 2 3 4 5 6 7 8 9 10 |
import com.hive.IAPV4; import com.hive.ResultAPI; String pid = "{YOUR_PRODUCT_MARKET_PID}"; IAPV4.INSTANCE.transactionFinish(pid, (result, marketPid) -> { if (result.isSuccess()) { // 호출 성공 } }); |
API Reference: IAPV4Interface.transactionFinish
1 2 3 4 5 6 7 8 9 |
import HIVEService let marketPid = "{YOUR_PRODUCT_MARKET_PID}" IAPV4Interface.transactionFinish() { result, marketPid in if result.isSuccess() { // 호출 성공 } } |
API Reference: HIVEIAPV4::transactionFinish:handler:
1 2 3 4 5 6 7 8 9 |
#import <HIVEService/HIVEService-Swift.h> NSString *marketPid = @"{YOUR_PRODUCT_MARKET_PID}"; [HIVEIAPV4 transactionFinish: marketPid handler: ^(HIVEResultAPI *result, NSString *marketPid) { if ([result isSuccess]) { // 호출 성공 } }]; |
다음은 여러 개의 지급 요청을 완료하는 예제 코드입니다.
API Reference: hive.IAPV4.transactionMultiFinish
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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()) { // 호출 성공 } } }); |
API Reference: IAPV4::transactionMultiFinish
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#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()) { // 호출 성공 } } }); |
API Reference: IAPV4.transactionMultiFinish
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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) { // 호출 성공 } } } }) |
API Reference: com.hive.IAPV4.transactionMultiFinish
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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()) { // 호출 성공 } } }); |
API Reference: IAPV4Interface.transactionMultiFinish
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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() { // 호출 성공 } } } |
API Reference: HIVEIAPV4::transactionMultiFinish
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#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]) { // 호출 성공 } } }]; |
유저 구매 활동 정보 얻기: AccountUuid
를 활용
하나의 게임 계정이 여러 앱 스토어 계정을 동원해 결제하는 등 결제 시스템을 악용하는 사용자가 존재할 수 있습니다. 게임 스튜디오 측에서는 결제 사용자 정보(AccountUuid)를 대조하는 방법으로 특정 사용자 행태를 얻어 분석할 수 있습니다.
아래 메서드는 Hive SDK가 앱 스토어에 전달하는 UUID v3 형태의 PlayerId 해시값인 AccountUuid
를 반환합니다.
API Reference: IAPV4.getAccountUuid
1 2 3 |
using hive; String accountUuid = IAPV4.getAccountUuid(); |
API Reference: IAPV4::getAccountUuid
1 2 3 4 5 |
#include <HIVE_SDK_Plugin/HIVE_CPP.h> using namespace std; using namespace hive; string accountUuid = IAPV4::getAccountUuid(); |
API Reference: IAPV4.getAccountUuid
1 2 3 |
import com.hive.IAPV4 val accountUuid = IAPV4.getAccountUuid() |
API Reference: IAPV4.INSTANCE.getAccountUuid
1 2 3 |
import com.hive.IAPV4; String accountUuid = IAPV4.INSTANCE.getAccountUuid(); |
API Reference: IAPV4Interface.getAccountUuid
1 2 3 |
import HIVEService let accountUuid = IAPV4Interface.getAccountUuid() |
API Reference: [HIVEIAPV4 getAccountUuid]
1 2 3 |
#import <HIVEService/HIVEService-Swift.h> NSString *accountUuid = [HIVEIAPV4 getAccountUuid]; |
게임 스튜디오가 이 해시값과 구매 영수증 정보를 함께 사용하면 어떤 사용자의 상품 구매가 정상적이었는지 분석할 수 있습니다. 아래는 AccountUuid
를 활용해 특정 PlayerId를 가진 게임 계정 내에 여러 게임 캐릭터가 있을 때, 이 게임 계정의 구매 활동을 분석하는 예시입니다. 이 예시는 개발 참고용이며 실제 동작을 보장하지 않습니다.
구매 메타 정보를 게임 서버에 미리 저장
구매 요청이 일어나기 전에 구매 메타 정보(AccountUuid
와 케릭터 정보 등)을 게임 서버에 저장합니다.
1 2 3 4 5 6 7 8 9 10 |
/* 구매 전 게임 클라이언트가 게임 서버에 전달하는 로직 * * @param purchaseTime: 구매 시간. 영수증들은 보통 UTC 기준입니다. since the epoch (Jan 1, 1970). * @param productId: 구매 상품 ID. * @param accountUuId: PlayerId 해시값. 영수증에 포함되며 IAPV4.getAccountUuid()로 얻을 수 있습니다. * @param characterId: PlayerId 내의 케릭터 식별자 입니다. */ game.sendPurchaseGameData(purchaseTime, productId, accountUuid, characterId) |
bypassInfo
전달
구매 요청이 일어나면 bypassInfo
데이터를 게임 서버에 전달합니다. 아래 코드는 개발 참고용 의사 코드입니다.
1 2 3 4 5 |
IAPV4.purchase(productId) { iapv4Receipt -> game.sendReceipt(iapv4Receipt.purchase_bypass_info) } |
영수증 검증 요청
게임 서버는 bypassInfo
데이터를 Hive IAP v4 서버에 전달하고 영수증 검증을 요청합니다. 영수증 검증 요청은 다음을 확인하세요.
AccountUuid
는 응답 값에 있는 앱 마켓/스토어 영수증 원문인 hiveiap_receipt
안에 다음과 같이 포함되어 있습니다.
1 2 3 4 5 6 |
# Apple # 구매 시간: purchase_date_ms # 구매 상품: product_id # AccountUuid: app_account_token |
1 2 3 4 5 6 |
# Google # 구매 시간: purchaseTime # 구매 상품: productId # AccountUuid: obfuscatedAccountId |
구매 메타 정보의 AccountUuid
와 hiveiap_receipt
의 AccountUuid
를 대조해 분석
앞서 구매 메타 정보에 저장해두었던 AccountUuid
와 영수증 검증 응답 값에서 얻은 AccountUuid
를 대조해 게임 캐릭터 정보 등 분석에 필요한 유저 정보를 찾습니다. 구매 메타 정보와 영수증 정보를 매치시켜 해당 게임 계정의 구매 활동을 분석할 수 있습니다.
한 유저가 여러 아이템을 구매한 경우, 구매 메타 정보와 hiveiap_receipt
의 purchaseTime
을 대조하여 각 구매 건을 특정할 수 있습니다. hiveiap_receipt
에 관한 자세한 내용은 영수증 검증에서 마켓별 API 응답값을 확인하세요.
유저 구매 활동 정보 얻기: iapPayload
를 활용
Purchase()로 구매를 요청할 때 iapPayload
에 게임사에서 사전에 정의한 구매 활동 분석용 데이터를 넣으면, Hive 서버가 iapPayload
에 해당하는 구매 영수증을 매치시켜 게임 서버에 전달합니다. 게임 스튜디오는, 영수증에 있는 정보와 iadPayload
에 있는 정보를 함께 사용하여 사용자 구매 활동을 분석할 수 있습니다.
iapPayload
를 정의해 Purchase()
실행
각 상품 구매 요청을 실행할 때 iapPayload
에 원하는 필드와 값을 정의하여 인자로 사용합니다. 이 과정은 구매 메타 데이터 정의와 유사합니다.
영수증 검증 요청하여 iapPayload
와 이에 해당하는 영수증을 수령
결제가 완료되고 영수증 검증 요청을 할 때, Hive 서버는 게임 서버에 구매 건별로 iapPayload
와 이에 해당하는 영수증을 매치시켜 전달합니다. 게임 스튜디오는 iapPayload
영수증에 있는 정보를 활용해 유저의 구매 활동을 분석할 수 있습니다.