From fc8df97435337886f7cec70c6437131e32d6ccc9 Mon Sep 17 00:00:00 2001 From: lianshumin Date: Wed, 1 Jul 2026 16:35:30 +0800 Subject: [PATCH 1/2] [in_app_purchase_android] Add obfuscated profile ID purchase param --- .../in_app_purchase_android/CHANGELOG.md | 4 +++ .../src/in_app_purchase_android_platform.dart | 3 ++ .../src/types/google_play_purchase_param.dart | 7 +++++ .../in_app_purchase_android/pubspec.yaml | 2 +- ...in_app_purchase_android_platform_test.dart | 28 +++++++++++++++++++ 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 5c39773bae6d..3ee089b54b4d 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.2 + +* Adds support for passing an obfuscated profile ID in `GooglePlayPurchaseParam`. + ## 0.5.1 * Adds support to overlay billing related messages. See `InAppPurchaseAndroidPlatformAddition.showInAppMessages`. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart index d865e328edbe..7a6e73048159 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart @@ -152,10 +152,12 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { Future buyNonConsumable({required PurchaseParam purchaseParam}) async { ChangeSubscriptionParam? changeSubscriptionParam; String? offerToken; + String? obfuscatedProfileId; if (purchaseParam is GooglePlayPurchaseParam) { changeSubscriptionParam = purchaseParam.changeSubscriptionParam; offerToken = purchaseParam.offerToken; + obfuscatedProfileId = purchaseParam.obfuscatedProfileId; } if (offerToken == null && purchaseParam.productDetails is GooglePlayProductDetails) { @@ -167,6 +169,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { product: purchaseParam.productDetails.id, offerToken: offerToken, accountId: purchaseParam.applicationUserName, + obfuscatedProfileId: obfuscatedProfileId, oldProduct: changeSubscriptionParam?.oldPurchaseDetails.productID, purchaseToken: changeSubscriptionParam?.oldPurchaseDetails.verificationData.serverVerificationData, diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_param.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_param.dart index 0e5f91701153..5a02e4833f83 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_param.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_param.dart @@ -14,6 +14,7 @@ class GooglePlayPurchaseParam extends PurchaseParam { super.applicationUserName, this.changeSubscriptionParam, this.offerToken, + this.obfuscatedProfileId, }); /// The 'changeSubscriptionParam' containing information for upgrading or @@ -25,4 +26,10 @@ class GooglePlayPurchaseParam extends PurchaseParam { /// For subscriptions, to get the offer token corresponding to the selected /// offer call productDetails.subscriptionOfferDetails?.get(selectedOfferIndex)?.offerToken final String? offerToken; + + /// An optional obfuscated profile ID associated with the user's profile. + /// + /// Setting this field requires [applicationUserName], which maps to the + /// obfuscated account ID, to be specified as well. + final String? obfuscatedProfileId; } diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml index 110702ff74f1..6309b423db2e 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -3,7 +3,7 @@ description: An implementation for the Android platform of the Flutter `in_app_p repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.5.1 +version: 0.5.2 environment: sdk: ^3.12.0 diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart index c0b6260167d4..6ed7349164a5 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart @@ -415,6 +415,34 @@ void main() { expect(result.productID, productDetails.productId); }); + test('buyNonConsumable passes obfuscatedProfileId', () async { + const profileId = 'hashedProfileId'; + const accountId = 'hashedAccountId'; + const expectedBillingResult = BillingResultWrapper( + responseCode: BillingResponse.ok, + debugMessage: 'dummy message', + ); + when( + mockApi.launchBillingFlow(any), + ).thenAnswer((_) async => convertToPigeonResult(expectedBillingResult)); + + final bool launchResult = await iapAndroidPlatform.buyNonConsumable( + purchaseParam: GooglePlayPurchaseParam( + productDetails: GooglePlayProductDetails.fromProductDetails( + dummyOneTimeProductDetails, + ).first, + applicationUserName: accountId, + obfuscatedProfileId: profileId, + ), + ); + + final VerificationResult result = verify(mockApi.launchBillingFlow(captureAny)); + final params = result.captured.single as PlatformBillingFlowParams; + expect(launchResult, isTrue); + expect(params.accountId, accountId); + expect(params.obfuscatedProfileId, profileId); + }); + test('handles an error with an empty purchases list', () async { const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; const accountId = 'hashedAccountId'; From 8a908da8586dc8fd487a27e9b5b00394f151f86e Mon Sep 17 00:00:00 2001 From: lianshumin Date: Wed, 8 Jul 2026 11:41:31 +0800 Subject: [PATCH 2/2] Clarify obfuscated profile ID documentation --- .../lib/src/types/google_play_purchase_param.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_param.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_param.dart index 5a02e4833f83..4e75e6b32b84 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_param.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_param.dart @@ -29,7 +29,9 @@ class GooglePlayPurchaseParam extends PurchaseParam { /// An optional obfuscated profile ID associated with the user's profile. /// - /// Setting this field requires [applicationUserName], which maps to the - /// obfuscated account ID, to be specified as well. + /// This is useful when an app allows multiple profiles under a single account. + /// Google Play can use this value to detect irregular activity and to associate + /// the purchase with the profile that initiated it. If specified, consider also + /// setting [applicationUserName], which maps to the obfuscated account ID. final String? obfuscatedProfileId; }