Skip to content

[in_app_purchase] Fixes StoreKit 2 date format does not match in_app_…#11717

Open
absar wants to merge 2 commits into
flutter:mainfrom
absar:date-format-issue-175072
Open

[in_app_purchase] Fixes StoreKit 2 date format does not match in_app_…#11717
absar wants to merge 2 commits into
flutter:mainfrom
absar:date-format-issue-175072

Conversation

@absar

@absar absar commented May 15, 2026

Copy link
Copy Markdown

…purchase_platform_interface

Description

  • As per PurchaseDetails.transactionDate api documentation, date should be Milliseconds since epoch but the migration to StoreKit2 probably mistakenly changed the date format to "yyyy-MM-dd HH:mm:ss". The issue originated in StoreKit2Translators.swift extension Transaction convertToPigeon which was using a magic hardcoded formatter dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
  • Fixes both SK2Transaction.purchaseDate and SK2Transaction.expirationDate and ultimately PurchaseDetails.transactionDate

Tests

Unfortunately it is not testable because the StoreKit 2 Transaction type has no visible initializers, probably thats why there are no tests for StoreKit2Translators.swift extension Transaction convertToPigeon

Related Issues

Fixes flutter/flutter#175072

Pre-Review Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. 2

@flutter-dashboard

Copy link
Copy Markdown

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates the in_app_purchase_storekit package to version 0.4.9 and modifies the date conversion logic in StoreKit2Translators.swift. The implementation replaces DateFormatter with a calculation of milliseconds since the Unix epoch for purchaseDate and expirationDate. Review feedback suggests applying .rounded() before casting to Int64 to avoid potential precision issues and recommends extracting this logic into a helper extension for improved testability.

Comment on lines +201 to +202
purchaseDate: String(Int64(purchaseDate.timeIntervalSince1970 * 1000)),
expirationDate: expirationDate.map { String(Int64($0.timeIntervalSince1970 * 1000)) },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

When converting from a floating-point TimeInterval (seconds) to integer milliseconds, it is safer to use .rounded() before casting to Int64. This prevents potential off-by-one errors due to floating-point precision (e.g., a value like 0.999999 being truncated to 0 instead of rounded to 1).

Additionally, although Transaction is difficult to instantiate for testing, you could make this logic testable by extracting it into a small helper extension on Date (e.g., var millisecondsSince1970: String).

Suggested change
purchaseDate: String(Int64(purchaseDate.timeIntervalSince1970 * 1000)),
expirationDate: expirationDate.map { String(Int64($0.timeIntervalSince1970 * 1000)) },
purchaseDate: String(Int64((purchaseDate.timeIntervalSince1970 * 1000.0).rounded())),
expirationDate: expirationDate.map { String(Int64(($0.timeIntervalSince1970 * 1000.0).rounded())) },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'll let @LouiseHsu comment on the larger design question, but if this should be passed as a timestamp it should absolutely not be a String at the Pigeon layer. If it needs to be a string at the Dart level, that should be converted in Dart. The Pigeon interface should have a structured and clearly documented value; if it's a timestamp, that should be a numeric value. Serializing a number as a string rather than converting it on the Dart side is needlessly inefficient, and also makes the Pigeon layer more confusing.

@absar absar force-pushed the date-format-issue-175072 branch 2 times, most recently from c7a872c to 9419e64 Compare May 17, 2026 22:52
@absar

absar commented May 18, 2026

Copy link
Copy Markdown
Author

Hi @LouiseHsu for your review

@LouiseHsu LouiseHsu added the CICD Run CI/CD label May 18, 2026
@LouiseHsu LouiseHsu self-requested a review May 18, 2026 19:45
@absar absar force-pushed the date-format-issue-175072 branch from 9419e64 to c20f723 Compare May 19, 2026 21:31
@github-actions github-actions Bot removed the CICD Run CI/CD label May 19, 2026
@absar absar force-pushed the date-format-issue-175072 branch 2 times, most recently from 8c77311 to a8a1036 Compare May 19, 2026 22:09

@LouiseHsu LouiseHsu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I do not think your change is wrong but @stuartmorgan-g makes a good point. I think its worth changing the pigeon schema so thats it only handles and passes ints for all dates, and have the string conversion done in the dart layer for clarity, but that can be done in a different PR. Can you address the gemini comment?

@LouiseHsu LouiseHsu added the CICD Run CI/CD label May 28, 2026
@stuartmorgan-g

Copy link
Copy Markdown
Collaborator

but that can be done in a different PR

Why would we want to do it in a different PR? This PR is changing the conceptual type of what is passed through Pigeon from a string to an integer. The whole point of Pigeon is to do things in a type-safe way, so a PR that changes the conceptual type to an int should also change the actual type to an int.

@LouiseHsu

Copy link
Copy Markdown
Contributor

but that can be done in a different PR

Why would we want to do it in a different PR? This PR is changing the conceptual type of what is passed through Pigeon from a string to an integer. The whole point of Pigeon is to do things in a type-safe way, so a PR that changes the conceptual type to an int should also change the actual type to an int.

ah, my bad, i misunderstood you. I thought you meant the original design should be changed since I was converting the date to a string in the pigeon layer already, and this conversion should be moved to dart.

@absar, if you like can you please update this PR to:

  1. Change the types of purchaseDate and expirationDate in the Pigeon definition file to int/int?
  2. Re-run Pigeon generation.
  3. Perform the conversion on the dart side.

@absar absar force-pushed the date-format-issue-175072 branch from a8a1036 to d151b54 Compare June 1, 2026 09:57
@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 1, 2026
@absar absar force-pushed the date-format-issue-175072 branch 2 times, most recently from 3fd0010 to 76ddc6e Compare June 1, 2026 20:09
@absar

absar commented Jun 1, 2026

Copy link
Copy Markdown
Author

@LouiseHsu

  1. Changed the types of purchaseDate and expirationDate in the Pigeon definition to double, since timeIntervalSince1970 is a double and i double checked the same is being done in SK1
  2. Did the conversion at the dart side
  3. Added tests, all tests are passing locally

Could you please review it and add CICD again

@absar absar force-pushed the date-format-issue-175072 branch from 76ddc6e to 20a666f Compare June 1, 2026 20:31
@absar absar requested review from LouiseHsu and stuartmorgan-g June 1, 2026 20:37
@absar absar force-pushed the date-format-issue-175072 branch 3 times, most recently from 6d9121f to 3d53cf9 Compare June 7, 2026 21:34
@stuartmorgan-g stuartmorgan-g added the triage-ios Should be looked at in iOS triage label Jun 11, 2026
@absar

absar commented Jun 15, 2026

Copy link
Copy Markdown
Author

@LouiseHsu a friendly ping, is there anything i can do to move this forward, it's under review for more than 2 weeks now

@LouiseHsu LouiseHsu added the CICD Run CI/CD label Jun 15, 2026
@LouiseHsu

Copy link
Copy Markdown
Contributor

@absar you have a tiny formatting issue, should be good after that fix

These files are not formatted correctly (see diff below):
  packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart
  packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart

To fix run the repository tooling `format` command: https://github.com/flutter/packages/blob/main/script/tool/README.md#format-code
or copy-paste this command into your terminal:
patch -p1 <<DONE
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart
index aa3ba8a691..e4625aae9f 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart
+++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart
@@ -170,8 +170,7 @@ extension on SK2TransactionMessage {
     );
   }
 
-  String _secondsToMillisecondsSinceEpochString(double date) =>
-      (date * 1000).round().toString();
+  String _secondsToMillisecondsSinceEpochString(double date) => (date * 1000).round().toString();
 }
 
 /// An observer that listens to all transactions created
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart
index b3b89d6b3b..77bd204a7a 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart
+++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart
@@ -114,10 +114,7 @@ void main() {
         expect(result.first.productID, dummyProductWrapper.id);
         expect(result.first.status, PurchaseStatus.purchased);
         expect(result.first.pendingCompletePurchase, true);
-        expect(
-          result.first.transactionDate,
-          (123123.121 * 1000).round().toString(),
-        );
+        expect(result.first.transactionDate, (123123.121 * 1000).round().toString());
       },
     );
 
@@ -145,10 +142,7 @@ void main() {
         final List<PurchaseDetails> result = await completer.future;
         expect(result.length, 1);
         expect(result.first.productID, dummyProductWrapper.id);
-        expect(
-          result.first.transactionDate,
-          (123123.121 * 1000).round().toString(),
-        );
+        expect(result.first.transactionDate, (123123.121 * 1000).round().toString());
       },
     );
 
@@ -646,10 +640,7 @@ void main() {
       expect(transactions, isNotEmpty);
       expect(transactions.first.id, '123');
       expect(transactions.first.productId, 'product_id');
-      expect(
-        transactions.first.expirationDate,
-        (321321.32 * 1000).round().toString(),
-      );
+      expect(transactions.first.expirationDate, (321321.32 * 1000).round().toString());
     });
 
     test('should expose receiptData (JWS) in unfinished transactions', () async {

DONE

…purchase_platform_interface

- As per `PurchaseDetails.transactionDate` api documentation, date should be `Milliseconds since epoch` but the migration to StoreKit2 probably mistakenly changed the date format to "yyyy-MM-dd HH:mm:ss". The issue originated in StoreKit2Translators.swift `extension Transaction convertToPigeon` which was using a magic hardcoded formatter dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
- Fixes both `SK2Transaction.purchaseDate` and `SK2Transaction.expirationDate` and ultimately `PurchaseDetails.transactionDate`

Fixes flutter/flutter#175072
@absar absar force-pushed the date-format-issue-175072 branch from 3d53cf9 to d0a18ee Compare June 17, 2026 10:06
@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 17, 2026
@absar

absar commented Jun 17, 2026

Copy link
Copy Markdown
Author

@LouiseHsu reformatted the code. Please review

@LouiseHsu LouiseHsu added the CICD Run CI/CD label Jun 18, 2026
@LouiseHsu LouiseHsu added the autosubmit Merge PR when tree becomes green via auto submit App label Jun 18, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jun 18, 2026
@auto-submit

auto-submit Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/packages/11717, because This PR has not met approval requirements for merging. The PR author is not a member of flutter-hackers and needs 1 more review(s) in order to merge this PR.

  • Merge guidelines: A PR needs at least one approved review if the author is already part of flutter-hackers or two member reviews if the author is not a member of flutter-hackers before re-applying the autosubmit label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

@LongCatIsLooong LongCatIsLooong self-requested a review June 18, 2026 21:46
@stuartmorgan-g stuartmorgan-g removed their request for review June 22, 2026 17:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

in_app_purchase_storekit date format does not match in_app_purchase_platform_interface PurchaseDetails.transactionDate format

3 participants