Skip to content

Commit 76ddc6e

Browse files
committed
[in_app_purchase] Fixes StoreKit 2 date format does not match in_app_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
1 parent 66cfb4a commit 76ddc6e

9 files changed

Lines changed: 52 additions & 25 deletions

File tree

packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.4.10
2+
3+
* Fixes StoreKit 2 date format does not match in_app_purchase_platform_interface PurchaseDetails.transactionDate format.
4+
Fixes both `SK2Transaction.purchaseDate` and `SK2Transaction.expirationDate`.
5+
16
## 0.4.9
27

38
* Add support for offer codes in StoreKit 2.

packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Messages.g.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -507,8 +507,8 @@ struct SK2TransactionMessage: Hashable {
507507
var id: Int64
508508
var originalId: Int64
509509
var productId: String
510-
var purchaseDate: String? = nil
511-
var expirationDate: String? = nil
510+
var purchaseDate: Double? = nil
511+
var expirationDate: Double? = nil
512512
var purchasedQuantity: Int64
513513
var appAccountToken: String? = nil
514514
var receiptData: String? = nil
@@ -523,8 +523,8 @@ struct SK2TransactionMessage: Hashable {
523523
let id = pigeonVar_list[0] as! Int64
524524
let originalId = pigeonVar_list[1] as! Int64
525525
let productId = pigeonVar_list[2] as! String
526-
let purchaseDate: String? = nilOrValue(pigeonVar_list[3])
527-
let expirationDate: String? = nilOrValue(pigeonVar_list[4])
526+
let purchaseDate: Double? = nilOrValue(pigeonVar_list[3])
527+
let expirationDate: Double? = nilOrValue(pigeonVar_list[4])
528528
let purchasedQuantity = pigeonVar_list[5] as! Int64
529529
let appAccountToken: String? = nilOrValue(pigeonVar_list[6])
530530
let receiptData: String? = nilOrValue(pigeonVar_list[7])

packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Translators.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,16 +194,12 @@ extension Product.PurchaseResult {
194194
extension Transaction {
195195
func convertToPigeon(receipt: String?, status: SK2PurchaseStatusMessage) -> SK2TransactionMessage
196196
{
197-
198-
let dateFormatter = DateFormatter()
199-
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
200-
201197
return SK2TransactionMessage(
202198
id: Int64(id),
203199
originalId: Int64(originalID),
204200
productId: productID,
205-
purchaseDate: dateFormatter.string(from: purchaseDate),
206-
expirationDate: expirationDate.map { dateFormatter.string(from: $0) },
201+
purchaseDate: purchaseDate.timeIntervalSince1970,
202+
expirationDate: expirationDate.map { $0.timeIntervalSince1970 },
207203
purchasedQuantity: Int64(purchasedQuantity),
208204
appAccountToken: appAccountToken?.uuidString,
209205
receiptData: receipt,

packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -580,9 +580,9 @@ class SK2TransactionMessage {
580580

581581
String productId;
582582

583-
String? purchaseDate;
583+
double? purchaseDate;
584584

585-
String? expirationDate;
585+
double? expirationDate;
586586

587587
int purchasedQuantity;
588588

@@ -624,8 +624,8 @@ class SK2TransactionMessage {
624624
id: result[0]! as int,
625625
originalId: result[1]! as int,
626626
productId: result[2]! as String,
627-
purchaseDate: result[3] as String?,
628-
expirationDate: result[4] as String?,
627+
purchaseDate: result[3] as double?,
628+
expirationDate: result[4] as double?,
629629
purchasedQuantity: result[5]! as int,
630630
appAccountToken: result[6] as String?,
631631
receiptData: result[7] as String?,

packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,13 @@ class SK2Transaction {
4444

4545
/// The date that the App Store charged the user's account for a purchased or
4646
/// restored product, or for a subscription purchase or renewal after a lapse.
47+
///
48+
/// Milliseconds since epoch.
4749
final String purchaseDate;
4850

4951
/// The date the subscription expires or renews.
52+
///
53+
/// Milliseconds since epoch.
5054
final String? expirationDate;
5155

5256
/// The number of consumable products purchased.
@@ -125,8 +129,12 @@ extension on SK2TransactionMessage {
125129
id: id.toString(),
126130
originalId: originalId.toString(),
127131
productId: productId,
128-
purchaseDate: purchaseDate ?? '',
129-
expirationDate: expirationDate,
132+
purchaseDate: purchaseDate != null
133+
? _secondsToMillisecondsSinceEpochString(purchaseDate!)
134+
: '',
135+
expirationDate: expirationDate != null
136+
? _secondsToMillisecondsSinceEpochString(expirationDate!)
137+
: null,
130138
appAccountToken: appAccountToken,
131139
receiptData: receiptData,
132140
jsonRepresentation: jsonRepresentation,
@@ -154,12 +162,17 @@ extension on SK2TransactionMessage {
154162
serverVerificationData: receiptData ?? '',
155163
source: kIAPSource,
156164
),
157-
transactionDate: purchaseDate,
165+
transactionDate: purchaseDate != null
166+
? _secondsToMillisecondsSinceEpochString(purchaseDate!)
167+
: null,
158168
status: purchaseStatus,
159169
purchaseID: id > 0 ? id.toString() : null,
160170
appAccountToken: appAccountToken,
161171
);
162172
}
173+
174+
String _secondsToMillisecondsSinceEpochString(double date) =>
175+
(date * 1000).round().toString();
163176
}
164177

165178
/// An observer that listens to all transactions created

packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@ class SK2TransactionMessage {
186186
final int id;
187187
final int originalId;
188188
final String productId;
189-
final String? purchaseDate;
190-
final String? expirationDate;
189+
final double? purchaseDate;
190+
final double? expirationDate;
191191
final int purchasedQuantity;
192192
final String? appAccountToken;
193193
final String? receiptData;

packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: in_app_purchase_storekit
22
description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework.
33
repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
5-
version: 0.4.9
5+
version: 0.4.10
66

77
environment:
88
sdk: ^3.10.0

packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,6 @@ class FakeStoreKit2Platform implements InAppPurchase2API {
392392
id: 123,
393393
originalId: 321,
394394
productId: '',
395-
purchaseDate: '',
396395
appAccountToken: '',
397396
status: SK2PurchaseStatusMessage.restored,
398397
);
@@ -439,7 +438,8 @@ class FakeStoreKit2Platform implements InAppPurchase2API {
439438
id: 1,
440439
originalId: 2,
441440
productId: id,
442-
purchaseDate: 'purchaseDate',
441+
purchaseDate: 123123.121,
442+
expirationDate: 321321.32,
443443
appAccountToken: 'appAccountToken',
444444
receiptData: 'receiptData',
445445
jsonRepresentation: 'jsonRepresentation',
@@ -488,7 +488,7 @@ class FakeStoreKit2Platform implements InAppPurchase2API {
488488
id: 123,
489489
originalId: 123,
490490
productId: 'product_id',
491-
purchaseDate: '12-12',
491+
purchaseDate: 123123.121,
492492
status: SK2PurchaseStatusMessage.purchased,
493493
),
494494
]);
@@ -501,7 +501,8 @@ class FakeStoreKit2Platform implements InAppPurchase2API {
501501
id: 123,
502502
originalId: 123,
503503
productId: 'product_id',
504-
purchaseDate: '12-12',
504+
purchaseDate: 123123.121,
505+
expirationDate: 321321.32,
505506
receiptData: 'fake_jws_representation',
506507
appAccountToken: 'fake_app_account_token',
507508
status: SK2PurchaseStatusMessage.purchased,
@@ -593,7 +594,7 @@ SK2TransactionMessage createPendingTransaction(String id, {int quantity = 1}) {
593594
id: 1,
594595
originalId: 2,
595596
productId: id,
596-
purchaseDate: 'purchaseDate',
597+
purchaseDate: 123123.121,
597598
appAccountToken: 'appAccountToken',
598599
receiptData: 'receiptData',
599600
jsonRepresentation: 'jsonRepresentation',

packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ void main() {
122122
expect(result.first.productID, dummyProductWrapper.id);
123123
expect(result.first.status, PurchaseStatus.purchased);
124124
expect(result.first.pendingCompletePurchase, true);
125+
expect(
126+
result.first.transactionDate,
127+
(123123.121 * 1000).round().toString(),
128+
);
125129
},
126130
);
127131

@@ -154,6 +158,10 @@ void main() {
154158
final List<PurchaseDetails> result = await completer.future;
155159
expect(result.length, 1);
156160
expect(result.first.productID, dummyProductWrapper.id);
161+
expect(
162+
result.first.transactionDate,
163+
(123123.121 * 1000).round().toString(),
164+
);
157165
},
158166
);
159167

@@ -783,6 +791,10 @@ void main() {
783791
expect(transactions, isNotEmpty);
784792
expect(transactions.first.id, '123');
785793
expect(transactions.first.productId, 'product_id');
794+
expect(
795+
transactions.first.expirationDate,
796+
(321321.32 * 1000).round().toString(),
797+
);
786798
});
787799

788800
test(

0 commit comments

Comments
 (0)