Overview

The Kongregate SDK includes a receipt verification API, which is intended to be used by games that do not implement their own server based solution. Game specific server-side Receipt Verification is recommended, if possible. For iOS the SDK’s Receipt Verification API relies on the client to verify the purchase with iTunes Connect. This is less reliable than server based authentication. The Android Receipt Verification API will use Kongregate servers and simply validates the given receipt was signed with appropriate key.

Purchase Flow

This Purchase Flow Diagram outlines the basic steps of a purchase. Below is a detailed walk through, followed by some sample code for iOS, Android, and Unity. These steps should be followed whether using the Kongregate Receipt Validation API or your own solution.

  1. Game initiates a purchase
  2. Invoke startPurchase (API Docs: iOS, Android, Unity) to fire the necessary analytics events.
  3. Play or App Store completes the purchase
    • If failed or cancelled, invoke finishPurchase(FAIL, transationDetails, gameFields, null). On iOS transactionDetails is the transactionId, and Android it will be the fail reason (e.g. IabResult.getMessage()). API Docs: iOS, Android, Unity.
  4. If successful, verify the receipt using your server. If you do not have a receipt verification server, you may use the API included in our SDK (see below).
    • If the verification result is successful give the goods and invoke finishPurchase(SUCCESS, transactionDetails, gameFields, dataSignature), to fire the appropriate analytics events. transactionDetails should be the unfinished transactionId on iOS and the full receipt JSON on Android. dataSignature is not use on iOS. On Android it should be the signature included with the receipt.
    • If the verification result is failed, invoke finishPurchase(RECEIPT_FAIL, transactinDetails, gameFields, null) to fire the appropriate analytics events. Again, on iOS, simply pass the transactionId.
  5. On either success or fail, invokeSKPaymentQueue finishTransaction: (iOS) or IInAppBillingService.consumePurchase() (Android)) to complete the purchase. On Unity, invoke the method on the plugin that triggers finishTransaction or consumePurchase.

IMPORTANT: if using a Unity plugin on iOS be sure to use one that allows you to delay invokation of finishTransaction until after the receipt has been verified. (e.g. Prime31)

Receipt Validation API

The SDK provides two ways of handling the verification. The first and simplest is by using a callback, this is the prefered way. The second option is by polling for the status. Use this second option if you are on a platform where callbacks are not possible to perform.

Android Setup

The producer will need to enter the Google Public Key into game configuration form on Kongregate.com.

iOS Setup

No special setup is required to use the iOS API.


iOS Sample Code

Option 1) Using a callback:

[myPurchaseManger purchase:product withPurchaseHandler:^(SKPaymentTransaction* transaction){
  [KongregateAPI.instance.mtx
    verifyTransaction:transaction
    completionHandler:^(BOOL valid) {
      if (valid) {
        //Since it is valid, track the purchase and give the goods
        [KongregateAPI.instance.analytics
          trackPurchase:transaction.payment.productIdentifier
          withQuantity:transaction.payment.quantity
          withGameFields:gameFields];
        //give the user the goods
        [myPurchaseManager productPurchased:product];
      } else {
        NSLog(@"Invalid transaction");
      }
      //Always finish the transaction
      [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
  }];
}];

Option 2) Polling the status:

NSString *newTransactionId = transaction.transactionId;
[KongregateAPI.instance.mtx verifyTransactionId:newTransactionId];
...

//check the status on our transaction id
NSString *status =[KongregateAPI.instance.mtx receiptVerificationStatus:newTransactionId];
if ([KONG_RECEIPT_VERIFICATION_STATUS_PROCESSING isEqualToString:status]) {
  //the transaction status is still processing, check again later
} else {
  //now that the processing is complete, we need to make sure we finish the transaction
  [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
  if ([KONG_RECEIPT_VERIFICATION_STATUS_UNKNOWN isEqualToString:status]) {
      //the transaction is not found, this is handled the same as invalid
  }
  if ([KONG_RECEIPT_VERIFICATION_STATUS_INVALID isEqualToString:status]) {
      //the transaction is not valid
     [KongregateAPI.instance.analytics finishPurchase:KONG_PURCHASE_RECEIPT_FAIL
                                     withTransactionId:transaction.transactionIdentifier
                                        withGameFields:@{ }];
  }
  if ([KONG_RECEIPT_VERIFICATION_STATUS_VALID isEqualToString:status]) {
      //the transaction is valid
      [KongregateAPI.instance.analytics finishPurchase:KONG_PURCHASE_SUCCESS
                                     withTransactionId:transaction.transactionIdentifier
                                        withGameFields:@{ @"hard_currency_change": @5 }];

      //give the user the goods
      [myPurchaseManager productPurchased:product];
  }
}

Android Sample Code

Option 1) Using a callback:

// purchase is a Purchase object from the Google IABHelper sample code. mAPI
// is a reference to the KongregateAPI.
mAPI.mtx().verifyTransaction(purchase.getOriginalJson(), purchase.getSignature(),
    new MicrotransactionServices.ReceiptVerificationListener() {
        @Override
        public void receiptVerificationComplete(boolean success) {
            if (success) {
                // The receipt is good award the goods
                ...
                // notify the kong SDK to track the analytics
                mAPI.analytics().finishPurchase(AnalyticsServices.IabResult.SUCCESS,
                  purchase.getOriginalJson(), purchaseEvent, purchase.getSignature());

                // and consume. mHelper and mConsumeFinishedListener are also
                // from the Google IABHelper sample code.
                mHelper.consumeAsync(purchase, mConsumeFinishedListener)
            } else {

                // notify the Kong SDK the receipt validation failed.
                mAPI.analytics().finishPurchase(AnalyticsServices.IabResult.RECEIPT_FAIL,
                        purchase.getOriginalJson(), purchase.getSignature());

                // consume the purchase
                mHelper.consumeAsync(purchase, mConsumeFinishedListener);
            }
        }
      });

Option 2) Polling the status:

When a purchase is made, schedule the receipt validation.

// maintain a map of purchases pending receipt validation
private Map<String,Purchase> mPurchasesPendingRV = new HashMap<>();

// purchase is a Purchase object from the Google IABHelper sample code.
// mAPI is a reference to the KongregateAPI.
mPurchasesPendingRV.put(purchase.getOrderId(), purchase);
mAPI.mtx().verifyTransaction(purchase.getOriginalJson(), purchase.getSignature());

Listen and handle the receipt validation completion event.

// your method for handling Kongregate Events
private void handleEvent(String event)
  if (KongregateEvent.RECEIPT_VERIFICATION_COMPLETE.equals(event)) {
    handleReceiptVerificationComplete();
  } else ... // handle other kongregaet events
}

// Method checks the status of pending purchases and handle the completed ones.
void handleReceiptVerificationComplete() {
  List<String> orderIdsCompleted  = new LinkedList<>();
  for (String key : mPurchasesPendingRV.keySet()) {
      MicrotransactionServices.ReceiptVerificationStatus rvStatus = mAPI.mtx().receiptVerificationStatus(key);
      if (!MicrotransactionServices.ReceiptVerificationStatus.PROCESSING.equals(rvStatus)) {
          orderIdsCompleted.add(key);
          Purchase purchase = mPurchasesPendingRV.get(key);
          if (!MicrotransactionServices.ReceiptVerificationStatus.INVALID.equals(rvStatus)) {
              // receipt is not invalid, go ahead and award the currency or items
              awardCurrency(purchase.getSku());
          }
          // always consume purchases once RV is complete, so uses can attempt to purchase
          // the items again.
          mHelper.consumeAsync(purchase, mConsumeFinishedListener);
      }
  }

  // clear out pending list
  for (String orderId : orderIdsCompleted) {
      mPurchasesPendingRV.remove(orderId);
  }
}

Unity Sample Code

Unity only supports the polling method. See KongregateGameObject-Example.cs included with the Kongregate unity package for sample code.