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 (BETA)

The validateAndLogInAppPurchase method (currently in BETA) 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, product ID, price, and currency 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

func sampleCodeValidateAndLog(){
    let productId = "my-product-id"
    let price = "1.99"
    let currency = "USD"
    let transactionId = "12345-transaction-id"

    // Create a new instance of AFSDKPurchaseDetails with the required information
    let purchaseDetails = AFSDKPurchaseDetails(productId: productId, price: price, currency: currency, transactionId: transactionId)
    
    let extraEventValues: [String: Any]? = [
        "firstExtraEventValue": "something",
        "secondExtraEventValue": "nice",
    ]
    
    AppsFlyerLib.shared().validateAndLog(inAppPurchase: purchaseDetails, extraEventValues: extraEventValues) { result in
        // This block is executed when the logging and validation operation is complete.
        // Process the 'result' as needed
        switch result!.status {
        case .success:
            print("Purchase validation and logging succeeded.")
            if let resultData = result?.result {
                // Process successful result data
                print("Validation successful data: \(resultData)")
            }
        case .failure:
            print("Purchase was not validated.")
            if let errorData = result?.errorData {
                // Process failure error data
                print("Validation failed data: \(errorData)")
            }
        case .error:
            print("An error occurred during the validation and logging operation.")
            if let error = result?.error {
                // Process NSError
                print("Error: \(error)")
            }
        }
    }
}

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
    }     
    })