Validate and log purchase

Learn how to validate and log purchases.

The validateAndLogInAppPurchase method is part of the receipt validation flow, which enables your app to validate in-app purchase events generated by the App Store.

📘Note

The function validateAndLogInAppPurchase can be replaced by the fully automatic purchase SDK connector (a premium service). To learn how to integrate the connector, see on Github iOS purchase SDK connector.

The method is currently implemented in two versions.

Implement validateAndLogInAppPurchase

The validateAndLogInAppPurchase method sends the purchase details to AppsFlyer for validation. After AppsFlyer validates the purchase with the App Store, the method returns the response to a completion handler block.

To implement the method, perform the following steps:

  1. Query the App Store for the object of the in-app purchase event.
  2. Initialize an AFSDKPurchaseDetails instance and set it with the purchase type, token, and product ID details retrieved from the Purchase object.
  3. If you want to add additional details to the purchase in-app event, populate a dictionary with key-value pairs.
  4. Invoke validateAndLogInAppPurchase with the following arguments:
    • The AFSDKPurchaseDetails object you created in step 2.
    • The dictionary with the additional details you created in step 3.
  5. Add your logic for handling the failure, success, or error responses to a completion handler block in the function body.

If the validation is successful, an af_purchase event is logged with the values provided to validateAndLogInAppPurchase.

📘Note

validateAndLogInAppPurchase generates an af_purchase in-app event upon successful validation. Sending this event yourself will cause duplicate event reporting.

Code example

let purchaseDetails = AFSDKPurchaseDetails(
    productId: "premium_monthly_subscription",
    transactionId: "2000000569065806",
    purchaseType: .subscription)

AppsFlyerLib.shared().validateAndLogInAppPurchase(
    purchaseDetails: purchaseDetails,
    purchaseAdditionalDetails: nil
) { result, error in
    // Case 1: Technical Error. Network/SDK error
    if let error = error, result == nil {
        print("Technical error: \(error.localizedDescription)")
        // Retry purchase validation
    }
    
    // Case 2: Backend Response. Check the validation result
    if let result = result, error == nil {
        let validationResult = result["result"] as? Bool
        
        if validationResult == true {
            // Validation Success. Backend validation succeeded
            print("Purchase validated successfully")
            // Handle successful validation
            
        } else if validationResult == false {
            // Validation Failure. Backend rejected the request (HTTP 200 with result set to false)
            print("Purchase validation failed by backend")
            // Handle validation failure
            
        } 
        return
    }
}

Subscription purchase that failed validation

{
  "result": false,
  "status": "failure",
  "product_id": "my-product-id",
  "price": "1.99",
  "currency": "USD",
  "transaction_id": "12345-transaction-id",
  "error": {
    "code": 100,
    "message": "Invalid purchase token or product id."
  },
  "additional_parameters": {
    "firstExtraEventValue": "something",
    "secondExtraEventValue": "nice"
  }
}

Testing purchase validation in Sandbox mode

To test purchase validation using a sandboxed environment, add the following code:

[AppsFlyerLib shared].useReceiptValidationSandbox = YES;
AppsFlyerLib.shared().useReceiptValidationSandbox = true

📘

Note

This code must be removed from your production builds.

Validating an in-app purchase automatically generates and sends an in-app purchase event to AppsFlyer. Its eventValues will look something like this:

{
   "some_parameter": "some_value", // from additional_event_values
   "af_currency": "USD", // from currency
   "af_content_id" :"test_id", // from purchase
   "af_revenue": "10", // from revenue
   "af_quantity": "1", // from purchase
   "af_validated": true // flag that AF verified the purchase
}

validateAndLogInAppPurchase (LEGACY)

Purchase validation using validateAndLogInAppPurchase

validateAndLoginInAppPurchase takes these arguments:

- (void) validateAndLogInAppPurchase:(NSString *) productIdentifier,
                  price:(NSString *) price
                  currency:(NSString *) currency
                  transactionId:(NSString *) tranactionId
                  additionalParameters:(NSDictionary *) params
                  success:(void (^)(NSDictionary *response)) successBlock
                  failure:(void (^)(NSError *error, id reponse)) failedBlock;
validateAndLog(inAppPurchase: String?,
               price: String?,
               currency: String?,
               transactionId: String?,
               additionalParameters: [AnyHashable : Any]?,
               success: ([AnyHashable : Any]) -> Void)?,
               failure: ((Error?, Any?) -> Void)?)

Upon successful validation, a NSDictionary is returned with the receipt validation data (provided by Apple servers).

📘

Note

Calling validateAndLogInAppPurchase generates an af_purchase in-app event upon successful validation. Sending this event yourself creates duplicate event reporting.

Example: Validate in-app purchase

[[AppsFlyerLib shared] validateAndLogInAppPurchase:@"ProductIdentifier" price:@"price"
    currency:@"USD"
    transactionId:@"transactionID"
    additionalParameters:@{@"test": @"val" , @"test1" : @"val 1"}
    success:^(NSDictionary *result){
      NSLog(@"Purchase succeeded And verified! response: %@", result[@"receipt"]);
    } failure:^(NSError *error, id response) {
      NSLog(@"response = %@", response);
      if([response isKindOfClass:[NSDictionary class]]) {
        if([response[@"status"] isEqualToString:@"in_app_arr_empty"]){
          // retry with 'SKReceiptRefreshRequest' because
          // Apple has returned an empty response
          // <YOUR CODE HERE>
        }

      } else {
        //handle other errors
        return;
      }
  }];
AppsFlyerLib.shared().validateAndLogInAppPurchase (
  inAppPurchase: "productIdentifier",
  price: "price",
  currency: "currency",
  transactionId: "transactionId",
  additionalParameters: [:],
  success: {
      guard let dictionary = $0 as? [String:Any] else { return }
      dump(dictionary)
    }, 
  failure: { error, result in
      guard let emptyInApp = result as? [String:Any],
      let status = emptyInApp["status"] as? String,
      status == "in_app_arr_empty" else {
      // Try to handle other errors
      return
    }     
    })