Skip to content

Receipt verification

Ungiven item receipts

Sending items is occasionally failed or cancelled after purchase. To prevent this situation, request receipt information of items not given by calling restore() method of IAPV4 class. Request the receipt information for ungiven items by calling the restore() method of the IAPV4 class.

  • If SUCCESS is returned and ReceiptList is sent after calling restore(), items needed restore exist. Send the ungiven items and complete the restore process.
  • If SUCCESS is not returned after calling restore(), no more process is required for game company. If an error occurs after calling restore(), implement it so users can keep playing without a separate notification unless it is an error users must know about.

Followings are sample codes to request receipt information of items not given.

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: Request receipt verification using the received receiptList
                } else if (Result.ErrorCode == FHiveResultAPI::EErrorCode::RESTORE_NOT_OWNED) {
                        //  No unpaid items
                }
        }
}));

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

may differ by operating system/market. For more details, see the status of supported features.

List of purchase-restoring error codes

Error Code Description
NEED_INITIALIZE Unable to initialize
NETWORK Network error
NOT_SUPPORTED Unable to restore (In-app purchase denied on device, etc.). User sets unavailable store
INVALID_SESSION Invalid session to restore
IN_PROGRESS Restore API in progress
RESTORE_NOT_OWNED No item to restore
NOT_OWNED No item to restore
RESPONSE_FAIL IAP server error

Verifying purchase receipt

Before sending items from game server, verification API from IAP should confirm whether the receipt is valid. hiveiap_transaction_id in verification API is a unique ID issued by receipt. Thus, save the ID on game server to check duplicated receipts. If you use the verification API and send all data from the request parameters, the IAP server forwards the sales and game data to to analyze sales status. Just utilize APIs to search payment on Hive One > Search All as well. For more details, refer to IAP Receipt Verification API.

Completion to give purchased or ungiven items

If you have finished granting items after a purchase, make sure to call the transactionFinish() method of the IAPV4 class to complete the purchase.

Warning

If you do not call transactionFinish() after granting the item, the item may remain ungiven, and the receipt information can be returned again when you call restore().

Note

When applying Hive SDK 4.12.0 or later on the Android Google Play Store, if calling the transactionFinish() method of the IAPV4 class does not succeed within three days (five minutes in the test environment), the payment is automatically refunded.

Followings are sample codes which complete a request for item sending.

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()) {
                // call successful 
        }
}));

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    
         }    
}];

Followings are sample codes to request to send several items.

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 call successful
                }
        }
}));

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    
             }    
         }    
}];

Get user purchase activity information: using AccountUuid

There may be users who abuse the payment system, such as a single game account using multiple app store accounts to make payments. On the game company side, you can obtain and analyze specific user behavior by comparing payment user information (AccountUuid).

The method below returns AccountUuid, which is the hash value of PlayerId in UUID v3 format that the Hive SDK passes to the app store.

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];

The game company can use this hash value with the purchase receipt information to analyze a user's purchases.

The example below shows analyzing the purchase activity of a game account with a specific PlayerId via AccountUuid when there are multiple in-game characters in this game account. This example is only for development reference and does not guarantee the actual operation. 

Store purchase meta info in game server in advance

Before a request to purchase occurs, save the purchase meta information (AccountUuid and character information) to the game server.

/* 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)   

Send bypassInfo

When a purchase request occurs, the app client passes the bypassInfo data in the purchase receipt to the app server. The code below is pseudo code for development reference.

Warning

bypassInfo should be used only to pass it as-is in the Request Body when calling the receipt verification server API. Do not parse the internal structure of bypassInfo directly in the game client or game server for other uses. The internal format of bypassInfo may change without prior notice depending on Hive SDK and store policies, and direct parsing can cause unexpected issues in the game.

Note

If you need additional information such as purchase analysis, use AccountUuid or iapPayload.

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

Request for receipt verification

The game server passes the bypassInfo data to the IAP v4 server and requests validating the receipt. For details of requesting receipt verification, see here. The AccountUuid included in the hiveiap_receipt, which is the original receipt of app market/store received within the response value.

# Apple
# Purchase time: purchase_date_ms
# Purchased products: product_id
# AccountUuid: app_account_token   
# Google
# Purchase time: purchaseTime
# Purchased products: productId
# AccountUuid: obfuscatedAccountId

Analyze by matching AccountUuid of the purchase meta information with AccountUuid of the hiveiap_receipt

Find the user information necessary for analysis, such as the game character information, by matching the AccountUuid previously stored in the purchase meta information with the AccountUuid obtained from the result of the receipt verification. You can analyze the purchase activity of the corresponding game account by matching the purchase meta information with the receipt information.

If a user has purchased multiple items, each purchase can be identified by matching the purchase meta information with the purchaseTime of hiveiap_receipt. For details on hiveiap_receipt, see Verify Receipt and check the API response by market.

Get user purchase activity information: using iapPayload

When requesting a purchase with Purchase, if you include the purchase activity analysis data predefined by the game company in iapPayload, the Hive server matches the purchase receipt corresponding to iapPayload and delivers it to the game server. The game company can analyze user purchase activity by using the information in the receipt together with the information in iapPayload.

Note

With this method, because the Hive server matches the purchase metadata of each purchase with each receipt and sends it to your game server, you do not need to match the purchase metadata and the receipt with AccountUuid yourself as shown above.

Define iapPayload and execute purchase()

When executing each product purchase request, define the desired fields and values in iapPayload and use it as the argument. The process is similar to defining purchase metadata.

Request receipt verification and receive the iapPayload and the corresponding receipt

When payment is complete and receipt verification is requested, the Hive server matches iapPayload with the corresponding receipt for each purchase and delivers them to the game server. Game companies can use the information in iapPayload and the receipt to analyze user purchase activity.

Below is an example of a situation using iapPayload.

  1. Market account: A, Hive Platform PlayerId: a, app character: 1 logs in
  2. purchase(marketPid, "playerId a - character 1")
  3. Switch to Hive Platform PlayerId: b, app character: 2
  4. restore()
  5. Receive a receipt containing iapPayload "playerId a - character 1"6. After receipt verification, grant the item to app character 1 instead of app character 2