Skip to content

Commit 5446559

Browse files
Upgrade to AppsFlyer SDK v6.17.3 and add validateAndLogInAppPurchaseV2 API (Beta) (#411)
* Purchase connector for flutter * runOnUi * Nullable * handle callbacks * ui thread callback * swift bridge file * register callback * docs * fixed the issue with MediationNetwork enums on Android - added a usage example of the api for testing. * This should fix the NullPointerException * Delivery 76214/update purchase connector version (#363) * bumped up PC versions * set back ios version * adding a note on the supported StoreKit to docs * typo fix * Squashed commit of the following: commit fa71c23 Merge: f420460 4f4ee27 Author: Dani-Koza-AF <dani.ko@appsflyer.com> Date: Thu Oct 31 15:16:50 2024 +0200 Merge remote-tracking branch 'origin/development' into development commit f420460 Author: Dani Koza <103039399+Dani-Koza-AF@users.noreply.github.com> Date: Wed Oct 30 15:50:41 2024 +0200 Releases/6.x.x/6.15.x/6.15.2 rc1 (#358) * fixed the issue with MediationNetwork enums on Android * Added a usage example of the logAdRevenue api for testing. * Fix to the NullPointerException some clients face. * Versioning and change log commit 4f4ee27 Merge: a58a49b 25fb530 Author: Dani Koza <103039399+Dani-Koza-AF@users.noreply.github.com> Date: Wed Oct 30 14:47:17 2024 +0200 Merge pull request #357 from AppsFlyerSDK/DELIVERY-63011/fix-android-null-pointer-exception This should fix the NullPointerException commit 25fb530 Author: Dani-Koza-AF <dani.ko@appsflyer.com> Date: Wed Oct 30 14:26:26 2024 +0200 This should fix the NullPointerException commit a58a49b Merge: 6213341 b85b1a4 Author: Dani-Koza-AF <dani.ko@appsflyer.com> Date: Mon Oct 28 12:30:10 2024 +0200 Merge remote-tracking branch 'origin/development' into development commit b85b1a4 Merge: 82764a4 6529458 Author: Dani Koza <103039399+Dani-Koza-AF@users.noreply.github.com> Date: Mon Oct 28 12:15:32 2024 +0200 Merge pull request #353 from AppsFlyerSDK/dev/DELIVERY-71973/mediation-network-value-fix Fixed the issue with MediationNetwork enums on Android commit 6529458 Author: Dani-Koza-AF <dani.ko@appsflyer.com> Date: Sun Oct 27 17:47:00 2024 +0200 fixed the issue with MediationNetwork enums on Android - added a usage example of the api for testing. commit 6213341 Merge: 3272d7e 82764a4 Author: Dani-Koza-AF <103039399+Dani-Koza-AF@users.noreply.github.com> Date: Wed Sep 4 15:51:28 2024 +0300 Merge pull request #338 from AppsFlyerSDK/releases/6.x.x/6.15.x/6.15.1-rc1 Releases/6.x.x/6.15.x/6.15.1 rc1 commit 82764a4 Merge: 3272d7e 6b76d63 Author: Dani-Koza-AF <103039399+Dani-Koza-AF@users.noreply.github.com> Date: Wed Sep 4 15:15:03 2024 +0300 Merge pull request #337 from AppsFlyerSDK/dev/DELIVERY-67805/Update-Plugin-to-v6.15.1 Update plugin to v6.15.1 commit 6b76d63 Author: Dani-Koza-AF <dani.ko@appsflyer.com> Date: Wed Sep 4 15:13:04 2024 +0300 Added missing info in docs commit e7d4dc6 Author: Dani-Koza-AF <dani.ko@appsflyer.com> Date: Wed Sep 4 14:17:28 2024 +0300 Added documentation commit 9f90c8e Author: Dani-Koza-AF <dani.ko@appsflyer.com> Date: Tue Sep 3 17:48:33 2024 +0300 Improvement of Android side impl commit 079ccad Author: Dani-Koza-AF <dani.ko@appsflyer.com> Date: Tue Sep 3 17:22:48 2024 +0300 iOS side impl - Helper func to get the correct enum properly. - requireNonNullArgumentWithCall to make sure we actually get the arguments. - Lots of null safety checks due to testing failures encountered. commit 4a3a0d6 Author: Dani-Koza-AF <dani.ko@appsflyer.com> Date: Mon Sep 2 14:48:30 2024 +0300 Android side impl - Flutter didn't like the fact that we pass enums, had to change mediation network to String, handled later on native side. - Added an helper method to ensure null safety, hopefully will be embraced by other method in the future. commit d74054e Author: Dani-Koza-AF <dani.ko@appsflyer.com> Date: Sun Sep 1 16:43:24 2024 +0300 flutter side impl - New Enum introduced. - New API logAdRevenue. - New AdRevenueData class. - Upgraded Dart SDK versions limits a bit to start from 2.17.0 . commit 3272d7e Merge: 95a4348 248dcf5 Author: liaz-af <61788924+liaz-af@users.noreply.github.com> Date: Mon Aug 19 22:35:24 2024 +0300 Merge pull request #336 from dori-af/dori/udl-note Deep link UDL - added a note commit 248dcf5 Author: Dori Frost <dori.frost@appsflyer.com> Date: Sun Aug 18 16:35:07 2024 +0300 Deep link UDL - added a note Per Slack: https://appsflyer.slack.com/archives/C5RDRS58X/p1723186908673099 * closing potential memory leaks * removed duplicated declarations = * updated example project dependencies Aligned Flutter's Android compileOptions to AppsFlyer's Android SDK. * Push notification data collection documentation updates (#381) * updated BasicIntegration.md * docs: updated push notification API's * reverting addition of PC * post revert fixes * lint * Fixes of tests and typos * Dev/update manual consent api (#383) New consent api and more - setConsentData is now deprecated. - setConsentDataV2 is the new and recommended way to set manual user consent. - Add getVersionNumber api, returns the plugin version. - version bumps. - typos fix. - doc updates. - bug fix. * fixed Locale issue by forcing toUpperCase(Locale.ENGLISH) (#395) - Expanded the unit–tests to verify not only that the right native method is invoked, but also that the correct arguments are passed. * Doc fix (#400) doc fix - broken link * Add purchase connector to development branch (#402) * removed duplicated declaration * Update .gitignore for purchase connector feature * Add complete Purchase Connector implementation - Add Purchase Connector support for Android and iOS platforms - Implement conditional compilation with include/exclude source sets - Add comprehensive Dart API with type-safe models - Include platform-specific error handling and validation - Add Purchase Connector documentation - Support for both in-app purchases and subscriptions - Zero impact when disabled via gradle/podfile flags New files: - Complete lib/src/purchase_connector/ Dart implementation - Android: include/exclude-connector source sets with ConnectorWrapper - iOS: PurchaseConnectorPlugin.swift with conditional compilation - Documentation: PurchaseConnector.md Modified integration points: - Android: build.gradle, AppsflyerSdkPlugin.java - iOS: appsflyer_sdk.podspec with subspecs architecture - Flutter: appsflyer_sdk.dart main export file * Complete Purchase Connector integration with code generation - Add missing Purchase Connector constants and AFMediationNetwork enum - Update pubspec.yaml with required dependencies (json_annotation, build_runner, json_serializable) - Generate JSON serialization code for all Purchase Connector models - Fix all compilation errors and undefined references - All 39 tests passing ✅ - Purchase Connector fully functional with type-safe models Generated files: - lib/appsflyer_sdk.g.dart - JSON serialization support - All Purchase Connector model serialization methods Dependencies added: - json_annotation: ^4.9.0 (already present) - build_runner: ^2.3.0 - json_serializable: ^6.5.4 * setting the proper SDK versions * documentation small fix * restore and solve code conflicts and ghost code Aligned with development * Immediate actions to prevent NullPointerExceptions (#403) 1. Fixed startSDKwithHandler() method: Null checks moved inside lambda execution 2. Fixed runOnUIThread() method: Added null check for mCallbackChannel. 3. Immediately return initSdk method when dev key is missing. * Latest release updates - Docs + new API for Android (#406) * add disableAppSetId() method for AppSet ID opt-out * added a simple test for the new api * docs * another doc update * documents fix lint * doc lint * doc * doc fix * last time doc fix * docs * Updated to AppsFlyer SDK v6.17.3 for both Android and iOS (#410) * iOS >> Added support for Setting StoreKit2 properly * versions bump * Dart + Android implementation * iOS implementation * docs update * purchase connector doc update for StoreKitV2 support --------- Co-authored-by: Paz Lavi <paz.lavi@appsflyer.com>
1 parent 5c40c00 commit 5446559

23 files changed

Lines changed: 848 additions & 73 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Versions
22

3+
## 6.17.3
4+
5+
- Updated to AppsFlyer SDK v6.17.3 for both Android and iOS
6+
- Added validateAndLogInAppPurchaseV2 API (Beta) for improved cross-platform purchase validation
7+
- Unified AFPurchaseDetails data structure for type-safe purchase validation
8+
- Enhanced error handling and consistent API across Android and iOS
9+
- Maintains backward compatibility with existing purchase validation methods
10+
311
## 6.17.1
412

513
- Android: Bug fix for users who expirienced `NullPointerExceptions`.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
1212
## SDK Versions
1313

14-
- Android AppsFlyer SDK **v6.17.0**
15-
- iOS AppsFlyer SDK **v6.17.1**
14+
- Android AppsFlyer SDK **v6.17.3**
15+
- iOS AppsFlyer SDK **v6.17.3**
1616

1717
### Purchase Connector versions
1818

1919
- Android 2.1.1
20-
- iOS 6.17.1
20+
- iOS 6.17.3
2121

2222
## ❗❗ Breaking changes when updating to v6.x.x❗❗
2323

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ android {
5353
dependencies {
5454
implementation fileTree(dir: 'libs', include: ['*.jar'])
5555
implementation 'androidx.appcompat:appcompat:1.0.0'
56-
implementation 'com.appsflyer:af-android-sdk:6.17.0'
56+
implementation 'com.appsflyer:af-android-sdk:6.17.3'
5757
implementation 'com.android.installreferrer:installreferrer:2.2'
5858
// implementation 'androidx.core:core-ktx:1.13.1'
5959
if (includeConnector) {

android/src/main/java/com/appsflyer/appsflyersdk/AppsFlyerConstants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.appsflyer.appsflyersdk;
22

33
public final class AppsFlyerConstants {
4-
final static String PLUGIN_VERSION = "6.17.1";
4+
final static String PLUGIN_VERSION = "6.17.3";
55
final static String AF_APP_INVITE_ONE_LINK = "appInviteOneLink";
66
final static String AF_HOST_PREFIX = "hostPrefix";
77
final static String AF_HOST_NAME = "hostName";

android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerSdkPlugin.java

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111

1212
import com.appsflyer.AFAdRevenueData;
1313
import com.appsflyer.AFLogger;
14+
import com.appsflyer.AFPurchaseDetails;
15+
import com.appsflyer.AFPurchaseType;
1416
import com.appsflyer.AppsFlyerConsent;
1517
import com.appsflyer.AppsFlyerConversionListener;
1618
import com.appsflyer.AppsFlyerInAppPurchaseValidatorListener;
19+
import com.appsflyer.AppsFlyerInAppPurchaseValidationCallback;
1720
import com.appsflyer.AppsFlyerLib;
1821
import com.appsflyer.AppsFlyerProperties;
1922
import com.appsflyer.MediationNetwork;
@@ -286,6 +289,9 @@ public void onMethodCall(MethodCall call, Result result) {
286289
case "validateAndLogInAppAndroidPurchase":
287290
validateAndLogInAppPurchase(call, result);
288291
break;
292+
case "validateAndLogInAppPurchaseV2":
293+
validateAndLogInAppPurchaseV2(call, result);
294+
break;
289295
case "getAppsFlyerUID":
290296
getAppsFlyerUID(result);
291297
break;
@@ -796,6 +802,107 @@ private void validateAndLogInAppPurchase(MethodCall call, Result result) {
796802
result.success(null);
797803
}
798804

805+
private void validateAndLogInAppPurchaseV2(MethodCall call, Result result) {
806+
try {
807+
// Get the complete purchase details map
808+
Map<String, Object> purchaseDetailsMap = (Map<String, Object>) call.argument("purchaseDetails");
809+
Map<String, String> additionalParameters = (Map<String, String>) call.argument("additionalParameters");
810+
811+
if (purchaseDetailsMap == null) {
812+
result.error("INVALID_ARGUMENTS", "Purchase details cannot be null", null);
813+
return;
814+
}
815+
816+
if (additionalParameters == null) {
817+
additionalParameters = new HashMap<>();
818+
}
819+
820+
// Extract fields from purchase details map
821+
String purchaseTypeString = (String) purchaseDetailsMap.get("purchaseType");
822+
String purchaseToken = (String) purchaseDetailsMap.get("purchaseToken");
823+
String productId = (String) purchaseDetailsMap.get("productId");
824+
825+
// Validate required fields
826+
if (purchaseTypeString == null || purchaseToken == null || productId == null) {
827+
result.error("INVALID_ARGUMENTS", "Purchase details must contain purchaseType, purchaseToken, and productId", null);
828+
return;
829+
}
830+
831+
// Map Dart enum values to Android AFPurchaseType enum
832+
AFPurchaseType purchaseType = mapPurchaseType(purchaseTypeString);
833+
if (purchaseType == null) {
834+
result.error("INVALID_PURCHASE_TYPE", "Invalid purchase type: " + purchaseTypeString + ". Expected: 'subscription' or 'one_time_purchase'", null);
835+
return;
836+
}
837+
838+
// Create AFPurchaseDetails object
839+
AFPurchaseDetails purchaseDetails = new AFPurchaseDetails(
840+
purchaseType,
841+
purchaseToken,
842+
productId
843+
);
844+
845+
Log.d(AF_PLUGIN_TAG, "validateAndLogInAppPurchaseV2 called with " + purchaseDetailsMap);
846+
847+
AppsFlyerLib.getInstance().validateAndLogInAppPurchase(
848+
purchaseDetails,
849+
additionalParameters,
850+
new AppsFlyerInAppPurchaseValidationCallback() {
851+
@Override
852+
public void onInAppPurchaseValidationFinished(@NonNull Map<String, ?> validationFinishedResult) {
853+
Log.d(AF_PLUGIN_TAG, "Purchase validation V2 response arrived");
854+
855+
// Convert the result to a format Flutter can understand
856+
Map<String, Object> flutterResult = new HashMap<>();
857+
for (Map.Entry<String, ?> entry : validationFinishedResult.entrySet()) {
858+
flutterResult.put(entry.getKey(), entry.getValue());
859+
}
860+
861+
result.success(flutterResult);
862+
}
863+
864+
@Override
865+
public void onInAppPurchaseValidationError(@NonNull Map<String, ?> validationErrorResult) {
866+
Log.d(AF_PLUGIN_TAG, "Purchase validation V2 returned error");
867+
868+
String errorMessage = "Purchase validation failed";
869+
if (validationErrorResult.containsKey("error_message")) {
870+
errorMessage = (String) validationErrorResult.get("error_message");
871+
}
872+
873+
// Convert error result to Flutter format
874+
Map<String, Object> flutterErrorResult = new HashMap<>();
875+
for (Map.Entry<String, ?> entry : validationErrorResult.entrySet()) {
876+
flutterErrorResult.put(entry.getKey(), entry.getValue());
877+
}
878+
879+
result.error("VALIDATION_ERROR", errorMessage, flutterErrorResult);
880+
}
881+
}
882+
);
883+
884+
} catch (Exception e) {
885+
Log.e(AF_PLUGIN_TAG, "Error in validateAndLogInAppPurchaseV2: " + e.getMessage(), e);
886+
result.error("VALIDATION_ERROR", "Purchase validation failed: " + e.getMessage(), null);
887+
}
888+
}
889+
890+
/**
891+
* Maps Dart enum string to Android AFPurchaseType enum.
892+
* @param purchaseTypeString The string representation from Dart
893+
* @return AFPurchaseType enum or null if invalid
894+
*/
895+
private AFPurchaseType mapPurchaseType(String purchaseTypeString) {
896+
switch (purchaseTypeString) {
897+
case "subscription":
898+
return AFPurchaseType.SUBSCRIPTION;
899+
case "one_time_purchase":
900+
return AFPurchaseType.ONE_TIME_PURCHASE;
901+
default:
902+
return null;
903+
}
904+
}
905+
799906
private void registerValidatorListener() {
800907
AppsFlyerInAppPurchaseValidatorListener validatorListener = new AppsFlyerInAppPurchaseValidatorListener() {
801908
@Override

doc/API.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
- [AppsFlyerOptions](#appsflyer-options)
77
- [AdRevenueData](#AdRevenueData)
88
- [AFMediationNetwork](#AFMediationNetwork)
9+
- [AFPurchaseDetails](#AFPurchaseDetails)
10+
- [AFPurchaseType](#AFPurchaseType)
911

1012
## Methods
1113
- [initSdk](#initSdk)
@@ -35,6 +37,7 @@
3537
- [getHostPrefix](#getHostPrefix)
3638
- [updateServerUninstallToken](#updateServerUninstallToken)
3739
- [Validate Purchase](#validatePurchase)
40+
- [validateAndLogInAppPurchaseV2](#validatePurchaseV2)
3841
- [sendPushNotificationData](#sendPushNotificationData)
3942
- [addPushNotificationDeepLinkPath](#addPushNotificationDeepLinkPath)
4043
- [User Invite](#userInvite)
@@ -511,6 +514,54 @@ appsFlyerSdk.updateServerUninstallToken("token");
511514
---
512515
**<a id="validatePurchase"> Validate Purchase**
513516

517+
***Cross-Platform V2 API (Recommended - BETA):***
518+
519+
> ⚠️ **BETA Feature**: This API is currently in beta. While it's stable and recommended for new implementations, please test thoroughly in your environment before production use.
520+
521+
**`Future<Map<String, dynamic>> validateAndLogInAppPurchaseV2(AFPurchaseDetails purchaseDetails, {Map<String, String>? additionalParameters})`**
522+
523+
The new unified purchase validation API that works across both Android and iOS platforms. This is the recommended approach for validating in-app purchases.
524+
525+
| Parameter | Type | Description |
526+
|-----------|------|-------------|
527+
| `purchaseDetails` | `AFPurchaseDetails` | Purchase details containing type, token, and product ID |
528+
| `additionalParameters` | `Map<String, String>?` | Optional additional parameters |
529+
530+
**AFPurchaseDetails:**
531+
| Property | Type | Description |
532+
|----------|------|-------------|
533+
| `purchaseType` | `AFPurchaseType` | Type of purchase (oneTimePurchase or subscription) |
534+
| `purchaseToken` | `String` | Purchase token from the app store |
535+
| `productId` | `String` | Product identifier |
536+
537+
**AFPurchaseType:**
538+
- `AFPurchaseType.oneTimePurchase` - For one-time in-app purchases
539+
- `AFPurchaseType.subscription` - For subscription purchases
540+
541+
_Example:_
542+
```dart
543+
// Create purchase details
544+
AFPurchaseDetails purchaseDetails = AFPurchaseDetails(
545+
purchaseType: AFPurchaseType.oneTimePurchase,
546+
purchaseToken: "your_purchase_token",
547+
productId: "your_product_id",
548+
);
549+
550+
// Validate purchase
551+
try {
552+
Map<String, dynamic> result = await appsFlyerSdk.validateAndLogInAppPurchaseV2(
553+
purchaseDetails,
554+
additionalParameters: {"custom_param": "value"}
555+
);
556+
print("Validation successful: $result");
557+
} catch (e) {
558+
print("Validation failed: $e");
559+
}
560+
```
561+
562+
---
563+
564+
***Legacy APIs:***
514565

515566
***Android:***
516567

@@ -572,6 +623,67 @@ appsflyerSdk.onPurchaseValidation((res){
572623
});
573624
```
574625

626+
---
627+
628+
##### <a id="validatePurchaseV2"> **validateAndLogInAppPurchaseV2 (Recommended - BETA)**
629+
630+
> ⚠️ **BETA Feature**: This API is currently in beta. While it's stable and recommended for new implementations, please test thoroughly in your environment before production use.
631+
632+
**`Future<Map<String, dynamic>> validateAndLogInAppPurchaseV2(AFPurchaseDetails purchaseDetails, {Map<String, String>? additionalParameters})`**
633+
634+
The unified cross-platform purchase validation API introduced in SDK v6.17.3. This is the recommended approach for validating in-app purchases as it provides a consistent interface across Android and iOS.
635+
636+
|| parameter | type | description |
637+
|| --------- | ----- | ----------- |
638+
|| `purchaseDetails` | `AFPurchaseDetails` | Purchase details object containing purchase type, token, and product ID |
639+
|| `additionalParameters` | `Map<String, String>?` | Optional additional parameters to send with the validation request |
640+
641+
**Returns:** `Future<Map<String, dynamic>>` - Validation result with detailed response information
642+
643+
**AFPurchaseDetails Properties:**
644+
645+
|| property | type | description |
646+
|| -------- | ----- | ----------- |
647+
|| `purchaseType` | `AFPurchaseType` | Type of purchase (`AFPurchaseType.oneTimePurchase` or `AFPurchaseType.subscription`) |
648+
|| `purchaseToken` | `String` | Purchase token obtained from the app store |
649+
|| `productId` | `String` | Product identifier of the purchased item |
650+
651+
_Example:_
652+
653+
```dart
654+
// Create purchase details
655+
AFPurchaseDetails purchaseDetails = AFPurchaseDetails(
656+
purchaseType: AFPurchaseType.subscription,
657+
purchaseToken: "your_purchase_token_from_store",
658+
productId: "premium_subscription_monthly",
659+
);
660+
661+
// Validate the purchase
662+
try {
663+
Map<String, dynamic> validationResult = await appsflyerSdk.validateAndLogInAppPurchaseV2(
664+
purchaseDetails,
665+
additionalParameters: {
666+
"app_version": "1.2.0",
667+
"validation_source": "flutter_example"
668+
}
669+
);
670+
671+
print("✅ Purchase validation successful!");
672+
print("Validation result: $validationResult");
673+
674+
} catch (e) {
675+
print("❌ Purchase validation failed: $e");
676+
// Handle validation error
677+
}
678+
```
679+
680+
**Key Benefits:**
681+
- **Cross-platform compatibility**: Works on both Android and iOS with the same API
682+
- **Type safety**: Uses structured data classes instead of platform-specific parameters
683+
- **Enhanced error handling**: Provides detailed error information in structured format
684+
- **Future-proof**: Built on AppsFlyer's latest V2 validation infrastructure
685+
- **Automatic routing**: Automatically routes to correct validation endpoints based on purchase type
686+
575687
---
576688
## **<a id="sendPushNotificationData"> `void sendPushNotificationData(Map? userInfo)`**
577689

doc/AdvancedAPI.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,58 @@ appsFlyerSdk.generateInviteLink(inviteLinkParams,
114114
Receipt validation is a secure mechanism whereby the payment platform (e.g. Apple or Google) validates that an in-app purchase indeed occurred as reported.<br>
115115
Learn more - https://support.appsflyer.com/hc/en-us/articles/207032106-Receipt-validation-for-in-app-purchases<br>
116116

117-
There are two different functions, one for iOS and one for Android:
117+
**Cross-Platform V2 API (Recommended - SDK v6.17.3+) - BETA:**
118+
119+
> ⚠️ **BETA Feature**: This API is currently in beta. While it's stable and recommended for new implementations, please test thoroughly in your environment before production use.
120+
121+
The new unified purchase validation API that works across both Android and iOS platforms:
122+
123+
```dart
124+
Future<Map<String, dynamic>> validateAndLogInAppPurchaseV2(
125+
AFPurchaseDetails purchaseDetails,
126+
{Map<String, String>? additionalParameters})
127+
```
128+
129+
**AFPurchaseDetails class:**
130+
```dart
131+
AFPurchaseDetails(
132+
purchaseType: AFPurchaseType, // oneTimePurchase or subscription
133+
purchaseToken: String, // Purchase token from app store
134+
productId: String, // Product identifier
135+
)
136+
```
137+
138+
Example:
139+
```dart
140+
// Create purchase details
141+
AFPurchaseDetails purchaseDetails = AFPurchaseDetails(
142+
purchaseType: AFPurchaseType.oneTimePurchase,
143+
purchaseToken: "sample_purchase_token_12345",
144+
productId: "com.example.product",
145+
);
146+
147+
// Validate purchase (works on both Android and iOS)
148+
try {
149+
Map<String, dynamic> result = await appsFlyerSdk.validateAndLogInAppPurchaseV2(
150+
purchaseDetails,
151+
additionalParameters: {"custom_param": "value"}
152+
);
153+
print("Validation successful: $result");
154+
} catch (e) {
155+
print("Validation failed: $e");
156+
}
157+
```
158+
159+
**Benefits of V2 API:**
160+
-**Cross-platform**: Single API works on both Android and iOS
161+
-**Type-safe**: Uses structured data classes instead of raw strings
162+
-**Better error handling**: Returns structured error information
163+
-**Enhanced validation**: Uses AppsFlyer's latest validation infrastructure
164+
-**Future-proof**: Built for AppsFlyer's V2 validation endpoints
165+
166+
---
167+
168+
**Legacy Platform-Specific APIs:**
118169

119170
**Android:**
120171
```dart

0 commit comments

Comments
 (0)