Skip to content

Commit 1d327c4

Browse files
authored
Added ability to purchase multiple quantity of one product (#5711)
1 parent abeb29f commit 1d327c4

File tree

6 files changed

+108
-22
lines changed

6 files changed

+108
-22
lines changed

packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.3.1
2+
3+
* Adds ability to purchase more than one of a product.
4+
15
## 0.3.0+10
26

37
* Ignores deprecation warnings for upcoming styleFrom button API changes.

packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform {
7171
Future<bool> buyNonConsumable({required PurchaseParam purchaseParam}) async {
7272
await _skPaymentQueueWrapper.addPayment(SKPaymentWrapper(
7373
productIdentifier: purchaseParam.productDetails.id,
74-
quantity: 1,
74+
quantity:
75+
purchaseParam is AppStorePurchaseParam ? purchaseParam.quantity : 1,
7576
applicationUsername: purchaseParam.applicationUserName,
7677
simulatesAskToBuyInSandbox: purchaseParam is AppStorePurchaseParam &&
7778
purchaseParam.simulatesAskToBuyInSandbox,

packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class AppStorePurchaseParam extends PurchaseParam {
1212
AppStorePurchaseParam({
1313
required ProductDetails productDetails,
1414
String? applicationUserName,
15+
this.quantity = 1,
1516
this.simulatesAskToBuyInSandbox = false,
1617
}) : super(
1718
productDetails: productDetails,
@@ -28,4 +29,7 @@ class AppStorePurchaseParam extends PurchaseParam {
2829
///
2930
/// See also [SKPaymentWrapper.simulatesAskToBuyInSandbox].
3031
final bool simulatesAskToBuyInSandbox;
32+
33+
/// Quantity of the product user requested to buy.
34+
final int quantity;
3135
}

packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: in_app_purchase_storekit
22
description: An implementation for the iOS platform of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework.
33
repository: https://github.com/flutter/plugins/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.3.0+10
5+
version: 0.3.1
66

77
environment:
88
sdk: ">=2.14.0 <3.0.0"

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

+35-20
Original file line numberDiff line numberDiff line change
@@ -56,31 +56,37 @@ class FakeStoreKitPlatform {
5656
queueIsActive = false;
5757
}
5858

59-
SKPaymentTransactionWrapper createPendingTransaction(String id) {
59+
SKPaymentTransactionWrapper createPendingTransaction(String id,
60+
{int quantity = 1}) {
6061
return SKPaymentTransactionWrapper(
61-
transactionIdentifier: '',
62-
payment: SKPaymentWrapper(productIdentifier: id),
63-
transactionState: SKPaymentTransactionStateWrapper.purchasing,
64-
transactionTimeStamp: 123123.121,
65-
error: null,
66-
originalTransaction: null);
62+
transactionIdentifier: '',
63+
payment: SKPaymentWrapper(productIdentifier: id, quantity: quantity),
64+
transactionState: SKPaymentTransactionStateWrapper.purchasing,
65+
transactionTimeStamp: 123123.121,
66+
error: null,
67+
originalTransaction: null,
68+
);
6769
}
6870

6971
SKPaymentTransactionWrapper createPurchasedTransaction(
70-
String productId, String transactionId) {
72+
String productId, String transactionId,
73+
{int quantity = 1}) {
7174
return SKPaymentTransactionWrapper(
72-
payment: SKPaymentWrapper(productIdentifier: productId),
75+
payment:
76+
SKPaymentWrapper(productIdentifier: productId, quantity: quantity),
7377
transactionState: SKPaymentTransactionStateWrapper.purchased,
7478
transactionTimeStamp: 123123.121,
7579
transactionIdentifier: transactionId,
7680
error: null,
7781
originalTransaction: null);
7882
}
7983

80-
SKPaymentTransactionWrapper createFailedTransaction(String productId) {
84+
SKPaymentTransactionWrapper createFailedTransaction(String productId,
85+
{int quantity = 1}) {
8186
return SKPaymentTransactionWrapper(
8287
transactionIdentifier: '',
83-
payment: SKPaymentWrapper(productIdentifier: productId),
88+
payment:
89+
SKPaymentWrapper(productIdentifier: productId, quantity: quantity),
8490
transactionState: SKPaymentTransactionStateWrapper.failed,
8591
transactionTimeStamp: 123123.121,
8692
error: const SKError(
@@ -91,10 +97,12 @@ class FakeStoreKitPlatform {
9197
}
9298

9399
SKPaymentTransactionWrapper createCanceledTransaction(
94-
String productId, int errorCode) {
100+
String productId, int errorCode,
101+
{int quantity = 1}) {
95102
return SKPaymentTransactionWrapper(
96103
transactionIdentifier: '',
97-
payment: SKPaymentWrapper(productIdentifier: productId),
104+
payment:
105+
SKPaymentWrapper(productIdentifier: productId, quantity: quantity),
98106
transactionState: SKPaymentTransactionStateWrapper.failed,
99107
transactionTimeStamp: 123123.121,
100108
error: SKError(
@@ -105,9 +113,11 @@ class FakeStoreKitPlatform {
105113
}
106114

107115
SKPaymentTransactionWrapper createRestoredTransaction(
108-
String productId, String transactionId) {
116+
String productId, String transactionId,
117+
{int quantity = 1}) {
109118
return SKPaymentTransactionWrapper(
110-
payment: SKPaymentWrapper(productIdentifier: productId),
119+
payment:
120+
SKPaymentWrapper(productIdentifier: productId, quantity: quantity),
111121
transactionState: SKPaymentTransactionStateWrapper.restored,
112122
transactionTimeStamp: 123123.121,
113123
transactionIdentifier: transactionId,
@@ -166,33 +176,38 @@ class FakeStoreKitPlatform {
166176
return Future<void>.sync(() {});
167177
case '-[InAppPurchasePlugin addPayment:result:]':
168178
final String id = call.arguments['productIdentifier'] as String;
179+
final int quantity = call.arguments['quantity'] as int;
169180
final SKPaymentTransactionWrapper transaction =
170-
createPendingTransaction(id);
181+
createPendingTransaction(id, quantity: quantity);
182+
transactions.add(transaction);
171183
InAppPurchaseStoreKitPlatform.observer.updatedTransactions(
172184
transactions: <SKPaymentTransactionWrapper>[transaction]);
173185
sleep(const Duration(milliseconds: 30));
174186
if (testTransactionFail) {
175187
final SKPaymentTransactionWrapper transactionFailed =
176-
createFailedTransaction(id);
188+
createFailedTransaction(id, quantity: quantity);
177189
InAppPurchaseStoreKitPlatform.observer.updatedTransactions(
178190
transactions: <SKPaymentTransactionWrapper>[transactionFailed]);
179191
} else if (testTransactionCancel > 0) {
180192
final SKPaymentTransactionWrapper transactionCanceled =
181-
createCanceledTransaction(id, testTransactionCancel);
193+
createCanceledTransaction(id, testTransactionCancel,
194+
quantity: quantity);
182195
InAppPurchaseStoreKitPlatform.observer.updatedTransactions(
183196
transactions: <SKPaymentTransactionWrapper>[transactionCanceled]);
184197
} else {
185198
final SKPaymentTransactionWrapper transactionFinished =
186199
createPurchasedTransaction(
187-
id, transaction.transactionIdentifier ?? '');
200+
id, transaction.transactionIdentifier ?? '',
201+
quantity: quantity);
188202
InAppPurchaseStoreKitPlatform.observer.updatedTransactions(
189203
transactions: <SKPaymentTransactionWrapper>[transactionFinished]);
190204
}
191205
break;
192206
case '-[InAppPurchasePlugin finishTransaction:result:]':
193207
finishedTransactions.add(createPurchasedTransaction(
194208
call.arguments['productIdentifier'] as String,
195-
call.arguments['transactionIdentifier'] as String));
209+
call.arguments['transactionIdentifier'] as String,
210+
quantity: transactions.first.payment.quantity));
196211
break;
197212
case '-[SKPaymentQueue startObservingTransactionQueue]':
198213
queueIsActive = true;

packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart

+62
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,68 @@ void main() {
427427
final PurchaseStatus purchaseStatus = await completer.future;
428428
expect(purchaseStatus, PurchaseStatus.canceled);
429429
});
430+
431+
test(
432+
'buying non consumable, should be able to purchase multiple quantity of one product',
433+
() async {
434+
final List<PurchaseDetails> details = <PurchaseDetails>[];
435+
final Completer<List<PurchaseDetails>> completer =
436+
Completer<List<PurchaseDetails>>();
437+
final Stream<List<PurchaseDetails>> stream =
438+
iapStoreKitPlatform.purchaseStream;
439+
late StreamSubscription<List<PurchaseDetails>> subscription;
440+
subscription = stream.listen((List<PurchaseDetails> purchaseDetailsList) {
441+
details.addAll(purchaseDetailsList);
442+
for (final PurchaseDetails purchaseDetails in purchaseDetailsList) {
443+
if (purchaseDetails.pendingCompletePurchase) {
444+
iapStoreKitPlatform.completePurchase(purchaseDetails);
445+
completer.complete(details);
446+
subscription.cancel();
447+
}
448+
}
449+
});
450+
final AppStoreProductDetails productDetails =
451+
AppStoreProductDetails.fromSKProduct(dummyProductWrapper);
452+
final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam(
453+
productDetails: productDetails,
454+
quantity: 5,
455+
applicationUserName: 'appName');
456+
await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam);
457+
await completer.future;
458+
expect(
459+
fakeStoreKitPlatform.finishedTransactions.first.payment.quantity, 5);
460+
});
461+
462+
test(
463+
'buying consumable, should be able to purchase multiple quantity of one product',
464+
() async {
465+
final List<PurchaseDetails> details = <PurchaseDetails>[];
466+
final Completer<List<PurchaseDetails>> completer =
467+
Completer<List<PurchaseDetails>>();
468+
final Stream<List<PurchaseDetails>> stream =
469+
iapStoreKitPlatform.purchaseStream;
470+
late StreamSubscription<List<PurchaseDetails>> subscription;
471+
subscription = stream.listen((List<PurchaseDetails> purchaseDetailsList) {
472+
details.addAll(purchaseDetailsList);
473+
for (final PurchaseDetails purchaseDetails in purchaseDetailsList) {
474+
if (purchaseDetails.pendingCompletePurchase) {
475+
iapStoreKitPlatform.completePurchase(purchaseDetails);
476+
completer.complete(details);
477+
subscription.cancel();
478+
}
479+
}
480+
});
481+
final AppStoreProductDetails productDetails =
482+
AppStoreProductDetails.fromSKProduct(dummyProductWrapper);
483+
final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam(
484+
productDetails: productDetails,
485+
quantity: 5,
486+
applicationUserName: 'appName');
487+
await iapStoreKitPlatform.buyConsumable(purchaseParam: purchaseParam);
488+
await completer.future;
489+
expect(
490+
fakeStoreKitPlatform.finishedTransactions.first.payment.quantity, 5);
491+
});
430492
});
431493

432494
group('complete purchase', () {

0 commit comments

Comments
 (0)