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

…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 https://github.com/flutter/flutter/issues/175072

## Pre-Review Checklist

**Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). 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.

[^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.
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md
index 3521731..ac77019 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md
+++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.4.11
+
+* Fixes StoreKit 2 date format does not match in_app_purchase_platform_interface PurchaseDetails.transactionDate format.
+  Fixes both `SK2Transaction.purchaseDate` and `SK2Transaction.expirationDate`.
+
 ## 0.4.10+1
 
 * Fixes SK2Transaction to expose the real purchased quantity instead of defaulting to 1.
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Messages.g.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Messages.g.swift
index dc75c7d..faeae55 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Messages.g.swift
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Messages.g.swift
@@ -507,8 +507,8 @@
   var id: Int64
   var originalId: Int64
   var productId: String
-  var purchaseDate: String? = nil
-  var expirationDate: String? = nil
+  var purchaseDate: Double? = nil
+  var expirationDate: Double? = nil
   var purchasedQuantity: Int64
   var appAccountToken: String? = nil
   var receiptData: String? = nil
@@ -523,8 +523,8 @@
     let id = pigeonVar_list[0] as! Int64
     let originalId = pigeonVar_list[1] as! Int64
     let productId = pigeonVar_list[2] as! String
-    let purchaseDate: String? = nilOrValue(pigeonVar_list[3])
-    let expirationDate: String? = nilOrValue(pigeonVar_list[4])
+    let purchaseDate: Double? = nilOrValue(pigeonVar_list[3])
+    let expirationDate: Double? = nilOrValue(pigeonVar_list[4])
     let purchasedQuantity = pigeonVar_list[5] as! Int64
     let appAccountToken: String? = nilOrValue(pigeonVar_list[6])
     let receiptData: String? = nilOrValue(pigeonVar_list[7])
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Translators.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Translators.swift
index ae2cf55..70bdad7 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Translators.swift
+++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Translators.swift
@@ -194,16 +194,12 @@
 extension Transaction {
   func convertToPigeon(receipt: String?, status: SK2PurchaseStatusMessage) -> SK2TransactionMessage
   {
-
-    let dateFormatter = DateFormatter()
-    dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
-
     return SK2TransactionMessage(
       id: Int64(id),
       originalId: Int64(originalID),
       productId: productID,
-      purchaseDate: dateFormatter.string(from: purchaseDate),
-      expirationDate: expirationDate.map { dateFormatter.string(from: $0) },
+      purchaseDate: purchaseDate.timeIntervalSince1970,
+      expirationDate: expirationDate.map { $0.timeIntervalSince1970 },
       purchasedQuantity: Int64(purchasedQuantity),
       appAccountToken: appAccountToken?.uuidString,
       receiptData: receipt,
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart
index cbbdbb5..8d14234 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart
+++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart
@@ -545,9 +545,9 @@
 
   String productId;
 
-  String? purchaseDate;
+  double? purchaseDate;
 
-  String? expirationDate;
+  double? expirationDate;
 
   int purchasedQuantity;
 
@@ -589,8 +589,8 @@
       id: result[0]! as int,
       originalId: result[1]! as int,
       productId: result[2]! as String,
-      purchaseDate: result[3] as String?,
-      expirationDate: result[4] as String?,
+      purchaseDate: result[3] as double?,
+      expirationDate: result[4] as double?,
       purchasedQuantity: result[5]! as int,
       appAccountToken: result[6] as String?,
       receiptData: result[7] as String?,
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 50b569f..1632068 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
@@ -44,9 +44,13 @@
 
   /// The date that the App Store charged the user's account for a purchased or
   /// restored product, or for a subscription purchase or renewal after a lapse.
+  ///
+  /// Milliseconds since epoch.
   final String purchaseDate;
 
   /// The date the subscription expires or renews.
+  ///
+  /// Milliseconds since epoch.
   final String? expirationDate;
 
   /// The number of consumable products purchased.
@@ -124,8 +128,8 @@
       id: id.toString(),
       originalId: originalId.toString(),
       productId: productId,
-      purchaseDate: purchaseDate ?? '',
-      expirationDate: expirationDate,
+      purchaseDate: _secondsToMillisecondsSinceEpochString(purchaseDate) ?? '',
+      expirationDate: _secondsToMillisecondsSinceEpochString(expirationDate),
       quantity: purchasedQuantity,
       appAccountToken: appAccountToken,
       receiptData: receiptData,
@@ -154,12 +158,15 @@
         serverVerificationData: receiptData ?? '',
         source: kIAPSource,
       ),
-      transactionDate: purchaseDate,
+      transactionDate: _secondsToMillisecondsSinceEpochString(purchaseDate),
       status: purchaseStatus,
       purchaseID: id > 0 ? id.toString() : null,
       appAccountToken: appAccountToken,
     );
   }
+
+  String? _secondsToMillisecondsSinceEpochString(double? date) =>
+      date != null ? (date * 1000).round().toString() : null;
 }
 
 /// An observer that listens to all transactions created
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart
index fca52d2..1d04424 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart
+++ b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart
@@ -179,8 +179,8 @@
   final int id;
   final int originalId;
   final String productId;
-  final String? purchaseDate;
-  final String? expirationDate;
+  final double? purchaseDate;
+  final double? expirationDate;
   final int purchasedQuantity;
   final String? appAccountToken;
   final String? receiptData;
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml
index ceba639..441ec10 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml
+++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml
@@ -2,7 +2,7 @@
 description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework.
 repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
-version: 0.4.10+1
+version: 0.4.11
 
 environment:
   sdk: ^3.10.0
diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart
index 2953616..7092552 100644
--- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart
+++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart
@@ -355,7 +355,7 @@
         id: 123,
         originalId: 123,
         productId: 'product_id',
-        purchaseDate: '12-12',
+        purchaseDate: 123123.121,
         purchasedQuantity: 2,
         status: SK2PurchaseStatusMessage.purchased,
       ),
@@ -365,7 +365,8 @@
         id: 123,
         originalId: 123,
         productId: 'product_id',
-        purchaseDate: '12-12',
+        purchaseDate: 123123.121,
+        expirationDate: 321321.32,
         receiptData: 'fake_jws_representation',
         appAccountToken: 'fake_app_account_token',
         purchasedQuantity: 3,
@@ -383,7 +384,6 @@
       id: 123,
       originalId: 321,
       productId: '',
-      purchaseDate: '',
       appAccountToken: '',
       status: SK2PurchaseStatusMessage.restored,
     );
@@ -430,7 +430,8 @@
           id: 1,
           originalId: 2,
           productId: id,
-          purchaseDate: 'purchaseDate',
+          purchaseDate: 123123.121,
+          expirationDate: 321321.32,
           appAccountToken: 'appAccountToken',
           receiptData: 'receiptData',
           jsonRepresentation: 'jsonRepresentation',
@@ -563,7 +564,7 @@
     id: 1,
     originalId: 2,
     productId: id,
-    purchaseDate: 'purchaseDate',
+    purchaseDate: 123123.121,
     appAccountToken: 'appAccountToken',
     receiptData: 'receiptData',
     jsonRepresentation: 'jsonRepresentation',
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 6376b73..386c465 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,6 +114,7 @@
         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());
       },
     );
 
@@ -141,6 +142,7 @@
         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());
       },
     );
 
@@ -638,6 +640,7 @@
       expect(transactions, isNotEmpty);
       expect(transactions.first.id, '123');
       expect(transactions.first.productId, 'product_id');
+      expect(transactions.first.expirationDate, (321321.32 * 1000).round().toString());
     });
 
     test('should expose receiptData (JWS) in unfinished transactions', () async {