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

Commit 104e9bf

Browse files
authored
[In_App_Purchase]queryPastPurchases() shouldn't block transaction updates. (#2834)
* Allow all transactions except for purchasing ones to be passed back to the Flutter side. * Restore transactions shouldn't block transaction updates
1 parent 957fb75 commit 104e9bf

File tree

11 files changed

+56
-15
lines changed

11 files changed

+56
-15
lines changed

packages/in_app_purchase/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.3.4+1
2+
3+
* iOS: Fix the bug that `SKPaymentQueueWrapper.transactions` doesn't return all transactions.
4+
* iOS: Fix the app crashes if `InAppPurchaseConnection.instance` is called in the `main()`.
5+
16
## 0.3.4
27

38
* Expose SKError code to client apps.

packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.m

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ - (void)restoreTransactions:(nullable NSString *)applicationName {
7575
- (void)paymentQueue:(SKPaymentQueue *)queue
7676
updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
7777
for (SKPaymentTransaction *transaction in transactions) {
78-
if (transaction.transactionIdentifier) {
78+
if (transaction.transactionState != SKPaymentTransactionStatePurchasing) {
7979
// Use product identifier instead of transaction identifier for few reasons:
8080
// 1. Only transactions with purchased state and failed state will have a transaction id, it
8181
// will become impossible for clients to finish deferred transactions when needed.
@@ -92,9 +92,7 @@ - (void)paymentQueue:(SKPaymentQueue *)queue
9292
- (void)paymentQueue:(SKPaymentQueue *)queue
9393
removedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
9494
for (SKPaymentTransaction *transaction in transactions) {
95-
if (transaction.transactionIdentifier) {
96-
[self.transactionsSetter removeObjectForKey:transaction.payment.productIdentifier];
97-
}
95+
[self.transactionsSetter removeObjectForKey:transaction.payment.productIdentifier];
9896
}
9997
self.transactionsRemoved(transactions);
10098
}

packages/in_app_purchase/ios/Tests/InAppPurchasePluginTest.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ - (void)testAddPaymentWithSameProductIDWillFail {
121121
@"simulatesAskToBuyInSandBox" : @YES,
122122
}];
123123
SKPaymentQueueStub* queue = [SKPaymentQueueStub new];
124+
queue.testState = SKPaymentTransactionStatePurchased;
124125
self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
125126
transactionsUpdated:^(NSArray<SKPaymentTransaction*>* _Nonnull transactions) {
126127
}

packages/in_app_purchase/ios/Tests/PaymentQueueTest.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ - (void)testTransactionPurchased {
6666
[handler addPayment:payment];
6767
[self waitForExpectations:@[ expectation ] timeout:5];
6868
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchased);
69+
XCTAssertEqual(tran.transactionIdentifier, @"fakeID");
6970
}
7071

7172
- (void)testDuplicateTransactionsWillTriggerAnError {
@@ -113,6 +114,7 @@ - (void)testTransactionFailed {
113114
[handler addPayment:payment];
114115
[self waitForExpectations:@[ expectation ] timeout:5];
115116
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateFailed);
117+
XCTAssertEqual(tran.transactionIdentifier, nil);
116118
}
117119

118120
- (void)testTransactionRestored {
@@ -140,6 +142,7 @@ - (void)testTransactionRestored {
140142
[handler addPayment:payment];
141143
[self waitForExpectations:@[ expectation ] timeout:5];
142144
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateRestored);
145+
XCTAssertEqual(tran.transactionIdentifier, @"fakeID");
143146
}
144147

145148
- (void)testTransactionPurchasing {
@@ -167,6 +170,7 @@ - (void)testTransactionPurchasing {
167170
[handler addPayment:payment];
168171
[self waitForExpectations:@[ expectation ] timeout:5];
169172
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchasing);
173+
XCTAssertEqual(tran.transactionIdentifier, nil);
170174
}
171175

172176
- (void)testTransactionDeferred {
@@ -194,6 +198,7 @@ - (void)testTransactionDeferred {
194198
[handler addPayment:payment];
195199
[self waitForExpectations:@[ expectation ] timeout:5];
196200
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateDeferred);
201+
XCTAssertEqual(tran.transactionIdentifier, nil);
197202
}
198203

199204
- (void)testFinishTransaction {

packages/in_app_purchase/ios/Tests/Stubs.m

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,11 @@ - (instancetype)initWithMap:(NSDictionary *)map {
215215
- (instancetype)initWithState:(SKPaymentTransactionState)state {
216216
self = [super init];
217217
if (self) {
218-
[self setValue:@"fakeID" forKey:@"transactionIdentifier"];
218+
// Only purchased and restored transactions have transactionIdentifier:
219+
// https://developer.apple.com/documentation/storekit/skpaymenttransaction/1411288-transactionidentifier?language=objc
220+
if (state == SKPaymentTransactionStatePurchased || state == SKPaymentTransactionStateRestored) {
221+
[self setValue:@"fakeID" forKey:@"transactionIdentifier"];
222+
}
219223
[self setValue:@(state) forKey:@"transactionState"];
220224
}
221225
return self;
@@ -224,7 +228,11 @@ - (instancetype)initWithState:(SKPaymentTransactionState)state {
224228
- (instancetype)initWithState:(SKPaymentTransactionState)state payment:(SKPayment *)payment {
225229
self = [super init];
226230
if (self) {
227-
[self setValue:@"fakeID" forKey:@"transactionIdentifier"];
231+
// Only purchased and restored transactions have transactionIdentifier:
232+
// https://developer.apple.com/documentation/storekit/skpaymenttransaction/1411288-transactionidentifier?language=objc
233+
if (state == SKPaymentTransactionStatePurchased || state == SKPaymentTransactionStateRestored) {
234+
[self setValue:@"fakeID" forKey:@"transactionIdentifier"];
235+
}
228236
[self setValue:@(state) forKey:@"transactionState"];
229237
_payment = payment;
230238
}

packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,14 @@ class _TransactionObserver implements SKTransactionObserverWrapper {
208208
return wrapper.transactionState ==
209209
SKPaymentTransactionStateWrapper.restored;
210210
}).map((SKPaymentTransactionWrapper wrapper) => wrapper));
211-
return;
212211
}
213212

214213
String receiptData = await getReceiptData();
215214
purchaseUpdatedController
216-
.add(transactions.map((SKPaymentTransactionWrapper transaction) {
215+
.add(transactions.where((SKPaymentTransactionWrapper wrapper) {
216+
return wrapper.transactionState !=
217+
SKPaymentTransactionStateWrapper.restored;
218+
}).map((SKPaymentTransactionWrapper transaction) {
217219
PurchaseDetails purchaseDetails =
218220
PurchaseDetails.fromSKTransaction(transaction, receiptData);
219221
return purchaseDetails;

packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ abstract class InAppPurchaseConnection {
7171

7272
/// Enable the [InAppPurchaseConnection] to handle pending purchases.
7373
///
74-
/// Android Only: This method is required to be called when initialize the application.
74+
/// This method is required to be called when initialize the application.
7575
/// It is to acknowledge your application has been updated to support pending purchases.
7676
/// See [Support pending transactions](https://developer.android.com/google/play/billing/billing_library_overview#pending)
7777
/// for more details.

packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ class SKPaymentQueueWrapper {
3737

3838
static final SKPaymentQueueWrapper _singleton = SKPaymentQueueWrapper._();
3939

40-
SKPaymentQueueWrapper._() {
41-
callbackChannel.setMethodCallHandler(_handleObserverCallbacks);
42-
}
40+
SKPaymentQueueWrapper._();
4341

4442
/// Calls [`-[SKPaymentQueue transactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions?language=objc)
4543
Future<List<SKPaymentTransactionWrapper>> transactions() async {
@@ -59,6 +57,7 @@ class SKPaymentQueueWrapper {
5957
/// addTransactionObserver:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506042-addtransactionobserver?language=objc).
6058
void setTransactionObserver(SKTransactionObserverWrapper observer) {
6159
_observer = observer;
60+
callbackChannel.setMethodCallHandler(_handleObserverCallbacks);
6261
}
6362

6463
/// Posts a payment to the queue.

packages/in_app_purchase/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: in_app_purchase
22
description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play.
33
homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase
4-
version: 0.3.4
4+
version: 0.3.4+1
55

66
dependencies:
77
async: ^2.0.8

packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,28 @@ void main() {
9090
expect(response.error, isNull);
9191
});
9292

93+
test('queryPastPurchases should not block transaction updates', () async {
94+
fakeIOSPlatform.transactions
95+
.add(fakeIOSPlatform.createPurchasedTransactionWithProductID('foo'));
96+
Completer completer = Completer();
97+
Stream<List<PurchaseDetails>> stream =
98+
AppStoreConnection.instance.purchaseUpdatedStream;
99+
100+
StreamSubscription subscription;
101+
subscription = stream.listen((purchaseDetailsList) {
102+
if (purchaseDetailsList.first.status == PurchaseStatus.purchased) {
103+
completer.complete(purchaseDetailsList);
104+
subscription.cancel();
105+
}
106+
});
107+
QueryPurchaseDetailsResponse response =
108+
await AppStoreConnection.instance.queryPastPurchases();
109+
List<PurchaseDetails> result = await completer.future;
110+
expect(result.length, 1);
111+
expect(result.first.productID, 'foo');
112+
expect(response.error, isNull);
113+
});
114+
93115
test('should get empty result if there is no restored transactions',
94116
() async {
95117
fakeIOSPlatform.testRestoredTransactionsNull = true;
@@ -328,10 +350,10 @@ class FakeIOSPlatform {
328350

329351
SKPaymentTransactionWrapper createPendingTransactionWithProductID(String id) {
330352
return SKPaymentTransactionWrapper(
353+
transactionIdentifier: null,
331354
payment: SKPaymentWrapper(productIdentifier: id),
332355
transactionState: SKPaymentTransactionStateWrapper.purchasing,
333356
transactionTimeStamp: 123123.121,
334-
transactionIdentifier: id,
335357
error: null,
336358
originalTransaction: null);
337359
}
@@ -349,10 +371,10 @@ class FakeIOSPlatform {
349371

350372
SKPaymentTransactionWrapper createFailedTransactionWithProductID(String id) {
351373
return SKPaymentTransactionWrapper(
374+
transactionIdentifier: null,
352375
payment: SKPaymentWrapper(productIdentifier: id),
353376
transactionState: SKPaymentTransactionStateWrapper.failed,
354377
transactionTimeStamp: 123123.121,
355-
transactionIdentifier: id,
356378
error: SKError(
357379
code: 0,
358380
domain: 'ios_domain',

0 commit comments

Comments
 (0)