Skip to content

Change result of purchase methods to PurchaseResult #1408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions api_tester/lib/api_tests/models/purchase_result_api_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:purchases_flutter/object_wrappers.dart';

// ignore_for_file: unused_element
// ignore_for_file: unused_local_variable
// ignore_for_file: deprecated_member_use
class _PurchaseResultApiTest {
void _checkFromJsonFactory(Map<String, dynamic> json) {
PurchaseResult transaction = PurchaseResult.fromJson(json);
}

void _checkConstructor(
CustomerInfo customerInfo,
StoreTransaction storeTransaction) {
PurchaseResult purchaseResult =
PurchaseResult(customerInfo, storeTransaction);
}
Comment on lines +11 to +16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand why we need to test the constructor's API.
Is the constructor public? If so, if we ever need to add an extra property to PurchaseResult, it would be a breaking change since the constructor would also change. Or am I missing something? (note that I know null about Dart 😅)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question! So the constructors for all our classes are currently public in dart (they were public before removing freezed as well). If we need to add new fields in the future, we would need to add multiple constructors, falling back to some default when no value is passed. This can indeed be problematic if no good default can be set though...

We could try to make the constructors package private... we haven't really been doing that until now, but I guess that now that we have removed freezed, we have more control. I might just want to leave it like this for now so we dedicate more time to study our options moving forward. Wdyt @RevenueCat/coresdk?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation! Exactly, it might be problematic if we need to add new fields in the future.

We could try to make the constructors package private...

Yes, I think this should be our end goal. But I agree that, for now, we can leave it like this and think about this in the future 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I like non-public constructors, but doing that now would be a breaking change right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup... though having said that, it would be "ok" since we're releasing this on a major anyway. My main concern is over delaying the release for this, since I'm not sure of the best way to do this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah I'm not sure either. It's not just adding visibility modifiers? 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for package private you need to define each file as part of a library, and prepend any method with an _: https://medium.com/@simplemanderson/dart-libraries-and-visibility-and-how-they-caught-me-by-surprise-d98ba7868fde.

I just need to test that it works 😅 (I'm focusing on returning the StoreTransaction in purchases-unity after a purchase but I can test this in a bit)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just testing this for a bit and it's not straightforward... We would need to define a library and make all models a part of that library... I wouldn't make that change right now without more testing, so I would prefer to keep it as is for this major. Wdyt @JayShortway @ajpallares ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for explaining and testing! 🙏 In that case I'd be okay keeping it as is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the investigation Toni! Sure, let's go with what we have right now, as it involves way deeper changes 👍


void _checkProperties(PurchaseResult purchaseResult) {
CustomerInfo customerInfo = purchaseResult.customerInfo;
StoreTransaction storeTransaction = purchaseResult.storeTransaction;
}
}
38 changes: 19 additions & 19 deletions api_tester/lib/api_tests/purchases_flutter_api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,67 +93,67 @@ class _PurchasesFlutterApiTest {
GoogleProductChangeInfo? googleProductChangeInfo;
PurchaseType purchaseType = PurchaseType.subs;
ProductCategory productType = ProductCategory.subscription;
CustomerInfo customerInfo = await Purchases.purchaseProduct(
PurchaseResult purchaseResult = await Purchases.purchaseProduct(
productIdentifier,
type: purchaseType,
upgradeInfo: upgradeInfo);
customerInfo = await Purchases.purchaseProduct(productIdentifier,
purchaseResult = await Purchases.purchaseProduct(productIdentifier,
upgradeInfo: upgradeInfo);
customerInfo = await Purchases.purchaseProduct(productIdentifier);
purchaseResult = await Purchases.purchaseProduct(productIdentifier);
}

void _checkPurchaseStoreProduct(StoreProduct storeProduct) async {
GoogleProductChangeInfo? googleProductChangeInfo;
CustomerInfo customerInfo = await Purchases.purchaseStoreProduct(
PurchaseResult purchaseResult = await Purchases.purchaseStoreProduct(
storeProduct,
googleProductChangeInfo: googleProductChangeInfo,
googleIsPersonalizedPrice: true);
customerInfo = await Purchases.purchaseStoreProduct(storeProduct,
purchaseResult = await Purchases.purchaseStoreProduct(storeProduct,
googleIsPersonalizedPrice: true);
customerInfo = await Purchases.purchaseStoreProduct(storeProduct,
purchaseResult = await Purchases.purchaseStoreProduct(storeProduct,
googleProductChangeInfo: googleProductChangeInfo);
customerInfo = await Purchases.purchaseStoreProduct(storeProduct);
purchaseResult = await Purchases.purchaseStoreProduct(storeProduct);
}

void _checkPurchasePackage(Package package) async {
UpgradeInfo? upgradeInfo;
GoogleProductChangeInfo? googleProductChangeInfo;
CustomerInfo customerInfo =
PurchaseResult purchaseResult =
await Purchases.purchasePackage(package, upgradeInfo: upgradeInfo);
customerInfo = await Purchases.purchasePackage(package,
purchaseResult = await Purchases.purchasePackage(package,
googleProductChangeInfo: googleProductChangeInfo,
googleIsPersonalizedPrice: true);
customerInfo = await Purchases.purchasePackage(package,
purchaseResult = await Purchases.purchasePackage(package,
upgradeInfo: upgradeInfo, googleIsPersonalizedPrice: true);
customerInfo = await Purchases.purchasePackage(package,
purchaseResult = await Purchases.purchasePackage(package,
googleIsPersonalizedPrice: true);
}

void _checkPurchaseSubscriptionOption(SubscriptionOption subscriptionOption,
GoogleProductChangeInfo? googleProductChangeInfo) async {
CustomerInfo customerInfo = await Purchases.purchaseSubscriptionOption(
PurchaseResult purchaseResult = await Purchases.purchaseSubscriptionOption(
subscriptionOption,
googleProductChangeInfo: googleProductChangeInfo);
customerInfo = await Purchases.purchaseSubscriptionOption(
purchaseResult = await Purchases.purchaseSubscriptionOption(
subscriptionOption,
googleProductChangeInfo: googleProductChangeInfo,
googleIsPersonalizedPrice: true);
customerInfo = await Purchases.purchaseSubscriptionOption(
purchaseResult = await Purchases.purchaseSubscriptionOption(
subscriptionOption,
googleIsPersonalizedPrice: true);
customerInfo =
purchaseResult =
await Purchases.purchaseSubscriptionOption(subscriptionOption);
}

void _checkPurchaseDiscountedProduct(
StoreProduct product, PromotionalOffer offer) async {
CustomerInfo customerInfo =
PurchaseResult purchaseResult =
await Purchases.purchaseDiscountedProduct(product, offer);
}

void _checkPurchaseDiscountedPackage(
Package package, PromotionalOffer offer) async {
CustomerInfo customerInfo =
PurchaseResult purchaseResult =
await Purchases.purchaseDiscountedPackage(package, offer);
}

Expand Down Expand Up @@ -568,15 +568,15 @@ class _PurchasesFlutterApiTest {
}
}

Future<CustomerInfo> _checkFetchAndPurchaseWinBackOffersForProduct(
Future<PurchaseResult> _checkFetchAndPurchaseWinBackOffersForProduct(
StoreProduct product) async {
List<WinBackOffer>? offers =
await Purchases.getEligibleWinBackOffersForProduct(product);

return await Purchases.purchaseProductWithWinBackOffer(product, offers[0]);
}

Future<CustomerInfo> _checkFetchAndPurchaseWinBackOffersForPackage(
Future<PurchaseResult> _checkFetchAndPurchaseWinBackOffersForPackage(
Package package) async {
List<WinBackOffer>? offers =
await Purchases.getEligibleWinBackOffersForPackage(package);
Expand Down
1 change: 1 addition & 0 deletions api_tester/lib/api_tests_import.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'package:api_tester/api_tests/models/price_wrapper_api_test.dart';
import 'package:api_tester/api_tests/models/pricing_phase_wrapper_api_test.dart';
import 'package:api_tester/api_tests/models/promotional_offer_api_test.dart';
import 'package:api_tester/api_tests/models/purchase_configuration_api_test.dart';
import 'package:api_tester/api_tests/models/purchase_result_api_test.dart';
import 'package:api_tester/api_tests/models/purchases_error_api_test.dart';
import 'package:api_tester/api_tests/models/store_api_test.dart';
import 'package:api_tester/api_tests/models/store_product_discount_api_test.dart';
Expand Down
29 changes: 29 additions & 0 deletions lib/models/purchase_result.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:equatable/equatable.dart';

import 'customer_info_wrapper.dart';
import 'store_transaction.dart';

/// Represents the successful result of a purchase operation.
class PurchaseResult extends Equatable {
/// The updated [CustomerInfo] after the purchase has been synced with
/// RevenueCat's servers.
final CustomerInfo customerInfo;
/// The [StoreTransaction] for this purchase.
final StoreTransaction storeTransaction;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that in the web platforms (RN and capacitor) we also have the productIdentifier here... but seemed redundant since it's already part of the PurchaseResult.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing you mean that it's part of the StoreTransaction, right? So yeah, totally not needed to add the redundancy 👍


const PurchaseResult(
this.customerInfo,
this.storeTransaction,
);

factory PurchaseResult.fromJson(Map<String, dynamic> json) => PurchaseResult(
CustomerInfo.fromJson(Map<String, dynamic>.from(json['customerInfo'])),
StoreTransaction.fromJson(Map<String, dynamic>.from(json['transaction'])),
);

@override
List<Object> get props => [
customerInfo,
storeTransaction,
];
}
1 change: 1 addition & 0 deletions lib/object_wrappers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export 'models/price_wrapper.dart';
export 'models/pricing_phase_wrapper.dart';
export 'models/product_category.dart';
export 'models/promotional_offer.dart';
export 'models/purchase_result.dart';
export 'models/purchases_completed_by.dart';
export 'models/purchases_configuration.dart';
export 'models/purchases_error.dart';
Expand Down
Loading