Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 7c834f6

Browse files
committed
Implement transaction caching for store kit
1 parent 2bf962e commit 7c834f6

File tree

9 files changed

+426
-19
lines changed

9 files changed

+426
-19
lines changed

packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 46;
6+
objectVersion = 50;
77
objects = {
88

99
/* Begin PBXBuildFile section */
@@ -22,6 +22,7 @@
2222
A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5279297219369C600FF69E6 /* StoreKit.framework */; };
2323
A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */; };
2424
F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */; };
25+
F6995BDD27CF73000050EA78 /* FIATransactionCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */; };
2526
F78AF3142342BC89008449C7 /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F78AF3132342BC89008449C7 /* PaymentQueueTests.m */; };
2627
/* End PBXBuildFile section */
2728

@@ -78,6 +79,7 @@
7879
A59001A821E69658004A3E5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7980
E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
8081
F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIAPPaymentQueueDeleteTests.m; sourceTree = "<group>"; };
82+
F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIATransactionCacheTests.m; sourceTree = "<group>"; };
8183
F6E5D5F926131C4800C68BED /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = "<group>"; };
8284
F78AF3132342BC89008449C7 /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PaymentQueueTests.m; sourceTree = "<group>"; };
8385
/* End PBXFileReference section */
@@ -190,6 +192,7 @@
190192
F78AF3132342BC89008449C7 /* PaymentQueueTests.m */,
191193
688DE35021F2A5A100EA2684 /* TranslatorTests.m */,
192194
F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */,
195+
F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */,
193196
);
194197
path = RunnerTests;
195198
sourceTree = "<group>";
@@ -254,7 +257,7 @@
254257
isa = PBXProject;
255258
attributes = {
256259
DefaultBuildSystemTypeForWorkspace = Original;
257-
LastUpgradeCheck = 1100;
260+
LastUpgradeCheck = 1300;
258261
ORGANIZATIONNAME = "The Flutter Authors";
259262
TargetAttributes = {
260263
97C146ED1CF9000F007C117D = {
@@ -406,6 +409,7 @@
406409
F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */,
407410
6896B34621E9363700D37AEF /* ProductRequestHandlerTests.m in Sources */,
408411
688DE35121F2A5A100EA2684 /* TranslatorTests.m in Sources */,
412+
F6995BDD27CF73000050EA78 /* FIATransactionCacheTests.m in Sources */,
409413
A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */,
410414
6896B34C21EEB4B800D37AEF /* Stubs.m in Sources */,
411415
);

packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1100"
3+
LastUpgradeVersion = "1300"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import <XCTest/XCTest.h>
6+
7+
@import in_app_purchase_storekit;
8+
9+
@interface FIATransactionCacheTests : XCTestCase
10+
11+
@end
12+
13+
@implementation FIATransactionCacheTests
14+
15+
- (void)testAddObjectsForNewKey {
16+
NSArray *dummyArray = @[ @1, @2, @3 ];
17+
FIATransactionCache *cache = [[FIATransactionCache alloc] init];
18+
[cache addObjects:dummyArray forKey:TransactionCacheKeyUpdatedTransactions];
19+
20+
XCTAssertEqual(dummyArray, [cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
21+
}
22+
23+
- (void)testAddObjectsForExistingKey {
24+
NSArray *dummyArray = @[ @1, @2, @3 ];
25+
FIATransactionCache *cache = [[FIATransactionCache alloc] init];
26+
[cache addObjects:dummyArray forKey:TransactionCacheKeyUpdatedTransactions];
27+
28+
XCTAssertEqual(dummyArray, [cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
29+
30+
[cache addObjects:@[ @4, @5, @6 ] forKey:TransactionCacheKeyUpdatedTransactions];
31+
32+
NSArray *expected = @[ @1, @2, @3, @4, @5, @6 ];
33+
XCTAssertEqualObjects(expected, [cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
34+
}
35+
36+
- (void)testGetObjectsForNonExistingKey {
37+
FIATransactionCache *cache = [[FIATransactionCache alloc] init];
38+
XCTAssertNil([cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
39+
}
40+
41+
- (void)testRemoveObjectsForNonExistingKey {
42+
FIATransactionCache *cache = [[FIATransactionCache alloc] init];
43+
[cache removeObjectsForKey:TransactionCacheKeyUpdatedTransactions];
44+
}
45+
46+
- (void)testRemoveObjectsForExistingKey {
47+
NSArray *dummyArray = @[ @1, @2, @3 ];
48+
FIATransactionCache *cache = [[FIATransactionCache alloc] init];
49+
[cache addObjects:dummyArray forKey:TransactionCacheKeyUpdatedTransactions];
50+
51+
XCTAssertEqual(dummyArray, [cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
52+
53+
[cache removeObjectsForKey:TransactionCacheKeyUpdatedTransactions];
54+
XCTAssertNil([cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
55+
}
56+
@end

packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/PaymentQueueTests.m

Lines changed: 187 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#import <OCMock/OCMock.h>
56
#import <XCTest/XCTest.h>
67
#import "Stubs.h"
78

@@ -59,10 +60,11 @@ - (void)testTransactionPurchased {
5960
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
6061
return YES;
6162
}
62-
updatedDownloads:nil];
63-
[queue addTransactionObserver:handler];
63+
updatedDownloads:nil
64+
transactionCache:OCMClassMock(FIATransactionCache.class)];
6465
SKPayment *payment =
6566
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
67+
[handler startObservingPaymentQueue];
6668
[handler addPayment:payment];
6769
[self waitForExpectations:@[ expectation ] timeout:5];
6870
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchased);
@@ -87,10 +89,12 @@ - (void)testTransactionFailed {
8789
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
8890
return YES;
8991
}
90-
updatedDownloads:nil];
91-
[queue addTransactionObserver:handler];
92+
updatedDownloads:nil
93+
transactionCache:OCMClassMock(FIATransactionCache.class)];
94+
9295
SKPayment *payment =
9396
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
97+
[handler startObservingPaymentQueue];
9498
[handler addPayment:payment];
9599
[self waitForExpectations:@[ expectation ] timeout:5];
96100
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateFailed);
@@ -115,10 +119,12 @@ - (void)testTransactionRestored {
115119
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
116120
return YES;
117121
}
118-
updatedDownloads:nil];
119-
[queue addTransactionObserver:handler];
122+
updatedDownloads:nil
123+
transactionCache:OCMClassMock(FIATransactionCache.class)];
124+
120125
SKPayment *payment =
121126
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
127+
[handler startObservingPaymentQueue];
122128
[handler addPayment:payment];
123129
[self waitForExpectations:@[ expectation ] timeout:5];
124130
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateRestored);
@@ -143,10 +149,12 @@ - (void)testTransactionPurchasing {
143149
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
144150
return YES;
145151
}
146-
updatedDownloads:nil];
147-
[queue addTransactionObserver:handler];
152+
updatedDownloads:nil
153+
transactionCache:OCMClassMock(FIATransactionCache.class)];
154+
148155
SKPayment *payment =
149156
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
157+
[handler startObservingPaymentQueue];
150158
[handler addPayment:payment];
151159
[self waitForExpectations:@[ expectation ] timeout:5];
152160
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchasing);
@@ -171,10 +179,11 @@ - (void)testTransactionDeferred {
171179
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
172180
return YES;
173181
}
174-
updatedDownloads:nil];
175-
[queue addTransactionObserver:handler];
182+
updatedDownloads:nil
183+
transactionCache:OCMClassMock(FIATransactionCache.class)];
176184
SKPayment *payment =
177185
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
186+
[handler startObservingPaymentQueue];
178187
[handler addPayment:payment];
179188
[self waitForExpectations:@[ expectation ] timeout:5];
180189
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateDeferred);
@@ -201,12 +210,178 @@ - (void)testFinishTransaction {
201210
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
202211
return YES;
203212
}
204-
updatedDownloads:nil];
205-
[queue addTransactionObserver:handler];
213+
updatedDownloads:nil
214+
transactionCache:OCMClassMock(FIATransactionCache.class)];
206215
SKPayment *payment =
207216
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
217+
[handler startObservingPaymentQueue];
208218
[handler addPayment:payment];
209219
[self waitForExpectations:@[ expectation ] timeout:5];
210220
}
211221

222+
- (void)testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheIsEmpty {
223+
FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
224+
FIAPaymentQueueHandler *handler =
225+
[[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init]
226+
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
227+
XCTFail("transactionsUpdated callback should not be called when cache is empty.");
228+
}
229+
transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
230+
XCTFail("transactionRemoved callback should not be called when cache is empty.");
231+
}
232+
restoreTransactionFailed:nil
233+
restoreCompletedTransactionsFinished:nil
234+
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
235+
return YES;
236+
}
237+
updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
238+
XCTFail("updatedDownloads callback should not be called when cache is empty.");
239+
}
240+
transactionCache:mockCache];
241+
242+
[handler startObservingPaymentQueue];
243+
244+
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
245+
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]);
246+
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]);
247+
}
248+
249+
- (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache {
250+
XCTestExpectation *updateTransactionsExpectation =
251+
[self expectationWithDescription:
252+
@"transactionsUpdated callback should be called with one transaction."];
253+
XCTestExpectation *removeTransactionsExpectation =
254+
[self expectationWithDescription:
255+
@"transactionsRemoved callback should be called with one transaction."];
256+
XCTestExpectation *updateDownloadsExpectation =
257+
[self expectationWithDescription:
258+
@"downloadsUpdated callback should be called with one transaction."];
259+
SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class);
260+
SKDownload *mockDownload = OCMClassMock(SKDownload.class);
261+
FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
262+
FIAPaymentQueueHandler *handler =
263+
[[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init]
264+
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
265+
XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
266+
[updateTransactionsExpectation fulfill];
267+
}
268+
transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
269+
XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
270+
[removeTransactionsExpectation fulfill];
271+
}
272+
restoreTransactionFailed:nil
273+
restoreCompletedTransactionsFinished:nil
274+
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
275+
return YES;
276+
}
277+
updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
278+
XCTAssertEqualObjects(downloads, @[ mockDownload ]);
279+
[updateDownloadsExpectation fulfill];
280+
}
281+
transactionCache:mockCache];
282+
283+
OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]).andReturn(@[
284+
mockTransaction
285+
]);
286+
OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]).andReturn(@[
287+
mockDownload
288+
]);
289+
OCMStub([mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]).andReturn(@[
290+
mockTransaction
291+
]);
292+
293+
[handler startObservingPaymentQueue];
294+
295+
[self waitForExpectations:@[
296+
updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation
297+
]
298+
timeout:5];
299+
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
300+
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]);
301+
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]);
302+
}
303+
304+
- (void)testTransactionsShouldBeCachedWhenNotObserving {
305+
SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
306+
FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
307+
FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
308+
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
309+
XCTFail("transactionsUpdated callback should not be called when cache is empty.");
310+
}
311+
transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
312+
XCTFail("transactionRemoved callback should not be called when cache is empty.");
313+
}
314+
restoreTransactionFailed:nil
315+
restoreCompletedTransactionsFinished:nil
316+
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
317+
return YES;
318+
}
319+
updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
320+
XCTFail("updatedDownloads callback should not be called when cache is empty.");
321+
}
322+
transactionCache:mockCache];
323+
324+
SKPayment *payment =
325+
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
326+
[handler addPayment:payment];
327+
328+
OCMVerify(times(1), [mockCache addObjects:[OCMArg any]
329+
forKey:TransactionCacheKeyUpdatedTransactions]);
330+
OCMVerify(never(), [mockCache addObjects:[OCMArg any]
331+
forKey:TransactionCacheKeyUpdatedDownloads]);
332+
OCMVerify(never(), [mockCache addObjects:[OCMArg any]
333+
forKey:TransactionCacheKeyRemovedTransactions]);
334+
}
335+
336+
- (void)testTransactionsShouldNotBeCachedWhenNotObserving {
337+
XCTestExpectation *updateTransactionsExpectation =
338+
[self expectationWithDescription:
339+
@"transactionsUpdated callback should be called with one transaction."];
340+
XCTestExpectation *removeTransactionsExpectation =
341+
[self expectationWithDescription:
342+
@"transactionsRemoved callback should be called with one transaction."];
343+
XCTestExpectation *updateDownloadsExpectation =
344+
[self expectationWithDescription:
345+
@"downloadsUpdated callback should be called with one transaction."];
346+
SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class);
347+
SKDownload *mockDownload = OCMClassMock(SKDownload.class);
348+
SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
349+
queue.testState = SKPaymentTransactionStatePurchased;
350+
FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
351+
FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
352+
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
353+
XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
354+
[updateTransactionsExpectation fulfill];
355+
}
356+
transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
357+
XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
358+
[removeTransactionsExpectation fulfill];
359+
}
360+
restoreTransactionFailed:nil
361+
restoreCompletedTransactionsFinished:nil
362+
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
363+
return YES;
364+
}
365+
updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
366+
XCTAssertEqualObjects(downloads, @[ mockDownload ]);
367+
[updateDownloadsExpectation fulfill];
368+
}
369+
transactionCache:mockCache];
370+
371+
[handler startObservingPaymentQueue];
372+
[handler paymentQueue:queue updatedTransactions:@[ mockTransaction ]];
373+
[handler paymentQueue:queue removedTransactions:@[ mockTransaction ]];
374+
[handler paymentQueue:queue updatedDownloads:@[ mockDownload ]];
375+
376+
[self waitForExpectations:@[
377+
updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation
378+
]
379+
timeout:5];
380+
OCMVerify(never(), [mockCache addObjects:[OCMArg any]
381+
forKey:TransactionCacheKeyUpdatedTransactions]);
382+
OCMVerify(never(), [mockCache addObjects:[OCMArg any]
383+
forKey:TransactionCacheKeyUpdatedDownloads]);
384+
OCMVerify(never(), [mockCache addObjects:[OCMArg any]
385+
forKey:TransactionCacheKeyRemovedTransactions]);
386+
}
212387
@end

0 commit comments

Comments
 (0)