Skip to content

Commit d0a18ee

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 6ce00a8 commit d0a18ee

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.11
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.10
27

38
* Clarifies `completePurchase` usage and the consequences of unfinished transactions in the README and API docstrings.

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
@@ -545,9 +545,9 @@ class SK2TransactionMessage {
545545

546546
String productId;
547547

548-
String? purchaseDate;
548+
double? purchaseDate;
549549

550-
String? expirationDate;
550+
double? expirationDate;
551551

552552
int purchasedQuantity;
553553

@@ -589,8 +589,8 @@ class SK2TransactionMessage {
589589
id: result[0]! as int,
590590
originalId: result[1]! as int,
591591
productId: result[2]! as String,
592-
purchaseDate: result[3] as String?,
593-
expirationDate: result[4] as String?,
592+
purchaseDate: result[3] as double?,
593+
expirationDate: result[4] as double?,
594594
purchasedQuantity: result[5]! as int,
595595
appAccountToken: result[6] as String?,
596596
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.
@@ -124,8 +128,12 @@ extension on SK2TransactionMessage {
124128
id: id.toString(),
125129
originalId: originalId.toString(),
126130
productId: productId,
127-
purchaseDate: purchaseDate ?? '',
128-
expirationDate: expirationDate,
131+
purchaseDate: purchaseDate != null
132+
? _secondsToMillisecondsSinceEpochString(purchaseDate!)
133+
: '',
134+
expirationDate: expirationDate != null
135+
? _secondsToMillisecondsSinceEpochString(expirationDate!)
136+
: null,
129137
appAccountToken: appAccountToken,
130138
receiptData: receiptData,
131139
jsonRepresentation: jsonRepresentation,
@@ -153,12 +161,17 @@ extension on SK2TransactionMessage {
153161
serverVerificationData: receiptData ?? '',
154162
source: kIAPSource,
155163
),
156-
transactionDate: purchaseDate,
164+
transactionDate: purchaseDate != null
165+
? _secondsToMillisecondsSinceEpochString(purchaseDate!)
166+
: null,
157167
status: purchaseStatus,
158168
purchaseID: id > 0 ? id.toString() : null,
159169
appAccountToken: appAccountToken,
160170
);
161171
}
172+
173+
String _secondsToMillisecondsSinceEpochString(double date) =>
174+
(date * 1000).round().toString();
162175
}
163176

164177
/// 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
@@ -179,8 +179,8 @@ class SK2TransactionMessage {
179179
final int id;
180180
final int originalId;
181181
final String productId;
182-
final String? purchaseDate;
183-
final String? expirationDate;
182+
final double? purchaseDate;
183+
final double? expirationDate;
184184
final int purchasedQuantity;
185185
final String? appAccountToken;
186186
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.10
5+
version: 0.4.11
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
@@ -359,7 +359,6 @@ class FakeStoreKit2Platform implements InAppPurchase2API {
359359
id: 123,
360360
originalId: 321,
361361
productId: '',
362-
purchaseDate: '',
363362
appAccountToken: '',
364363
status: SK2PurchaseStatusMessage.restored,
365364
);
@@ -406,7 +405,8 @@ class FakeStoreKit2Platform implements InAppPurchase2API {
406405
id: 1,
407406
originalId: 2,
408407
productId: id,
409-
purchaseDate: 'purchaseDate',
408+
purchaseDate: 123123.121,
409+
expirationDate: 321321.32,
410410
appAccountToken: 'appAccountToken',
411411
receiptData: 'receiptData',
412412
jsonRepresentation: 'jsonRepresentation',
@@ -454,7 +454,7 @@ class FakeStoreKit2Platform implements InAppPurchase2API {
454454
id: 123,
455455
originalId: 123,
456456
productId: 'product_id',
457-
purchaseDate: '12-12',
457+
purchaseDate: 123123.121,
458458
status: SK2PurchaseStatusMessage.purchased,
459459
),
460460
]);
@@ -467,7 +467,8 @@ class FakeStoreKit2Platform implements InAppPurchase2API {
467467
id: 123,
468468
originalId: 123,
469469
productId: 'product_id',
470-
purchaseDate: '12-12',
470+
purchaseDate: 123123.121,
471+
expirationDate: 321321.32,
471472
receiptData: 'fake_jws_representation',
472473
appAccountToken: 'fake_app_account_token',
473474
status: SK2PurchaseStatusMessage.purchased,
@@ -557,7 +558,7 @@ SK2TransactionMessage createPendingTransaction(String id, {int quantity = 1}) {
557558
id: 1,
558559
originalId: 2,
559560
productId: id,
560-
purchaseDate: 'purchaseDate',
561+
purchaseDate: 123123.121,
561562
appAccountToken: 'appAccountToken',
562563
receiptData: 'receiptData',
563564
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
@@ -114,6 +114,10 @@ void main() {
114114
expect(result.first.productID, dummyProductWrapper.id);
115115
expect(result.first.status, PurchaseStatus.purchased);
116116
expect(result.first.pendingCompletePurchase, true);
117+
expect(
118+
result.first.transactionDate,
119+
(123123.121 * 1000).round().toString(),
120+
);
117121
},
118122
);
119123

@@ -141,6 +145,10 @@ void main() {
141145
final List<PurchaseDetails> result = await completer.future;
142146
expect(result.length, 1);
143147
expect(result.first.productID, dummyProductWrapper.id);
148+
expect(
149+
result.first.transactionDate,
150+
(123123.121 * 1000).round().toString(),
151+
);
144152
},
145153
);
146154

@@ -638,6 +646,10 @@ void main() {
638646
expect(transactions, isNotEmpty);
639647
expect(transactions.first.id, '123');
640648
expect(transactions.first.productId, 'product_id');
649+
expect(
650+
transactions.first.expirationDate,
651+
(321321.32 * 1000).round().toString(),
652+
);
641653
});
642654

643655
test('should expose receiptData (JWS) in unfinished transactions', () async {

0 commit comments

Comments
 (0)