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..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 @@ -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,12 @@ 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. + /// + /// 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; } 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';