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

Added ability to purchase multiple quantity of one product #5711

Merged
merged 10 commits into from
Jun 27, 2022
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.3.1

* Adds ability to purchase more than one of a product.

## 0.3.0+10

* Ignores deprecation warnings for upcoming styleFrom button API changes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform {
Future<bool> buyNonConsumable({required PurchaseParam purchaseParam}) async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to change the function signature of the parent class too?
And it looks like quantity should be part of PurchaseParam as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your comment. According to your suggestions I will move the quantity param to PurchaseParam and change the function signature of the parent class. Then I will create issue describing the feature.

await _skPaymentQueueWrapper.addPayment(SKPaymentWrapper(
productIdentifier: purchaseParam.productDetails.id,
quantity: 1,
quantity:
purchaseParam is AppStorePurchaseParam ? purchaseParam.quantity : 1,
applicationUsername: purchaseParam.applicationUserName,
simulatesAskToBuyInSandbox: purchaseParam is AppStorePurchaseParam &&
purchaseParam.simulatesAskToBuyInSandbox,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class AppStorePurchaseParam extends PurchaseParam {
AppStorePurchaseParam({
required ProductDetails productDetails,
String? applicationUserName,
this.quantity = 1,
this.simulatesAskToBuyInSandbox = false,
}) : super(
productDetails: productDetails,
Expand All @@ -28,4 +29,7 @@ class AppStorePurchaseParam extends PurchaseParam {
///
/// See also [SKPaymentWrapper.simulatesAskToBuyInSandbox].
final bool simulatesAskToBuyInSandbox;

/// Quantity of the product user requested to buy.
final int quantity;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: in_app_purchase_storekit
description: An implementation for the iOS platform of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework.
repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_storekit
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
version: 0.3.0+10
version: 0.3.1

environment:
sdk: ">=2.14.0 <3.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,31 +56,37 @@ class FakeStoreKitPlatform {
queueIsActive = false;
}

SKPaymentTransactionWrapper createPendingTransaction(String id) {
SKPaymentTransactionWrapper createPendingTransaction(String id,
{int quantity = 1}) {
return SKPaymentTransactionWrapper(
transactionIdentifier: '',
payment: SKPaymentWrapper(productIdentifier: id),
transactionState: SKPaymentTransactionStateWrapper.purchasing,
transactionTimeStamp: 123123.121,
error: null,
originalTransaction: null);
transactionIdentifier: '',
payment: SKPaymentWrapper(productIdentifier: id, quantity: quantity),
transactionState: SKPaymentTransactionStateWrapper.purchasing,
transactionTimeStamp: 123123.121,
error: null,
originalTransaction: null,
);
}

SKPaymentTransactionWrapper createPurchasedTransaction(
String productId, String transactionId) {
String productId, String transactionId,
{int quantity = 1}) {
return SKPaymentTransactionWrapper(
payment: SKPaymentWrapper(productIdentifier: productId),
payment:
SKPaymentWrapper(productIdentifier: productId, quantity: quantity),
transactionState: SKPaymentTransactionStateWrapper.purchased,
transactionTimeStamp: 123123.121,
transactionIdentifier: transactionId,
error: null,
originalTransaction: null);
}

SKPaymentTransactionWrapper createFailedTransaction(String productId) {
SKPaymentTransactionWrapper createFailedTransaction(String productId,
{int quantity = 1}) {
return SKPaymentTransactionWrapper(
transactionIdentifier: '',
payment: SKPaymentWrapper(productIdentifier: productId),
payment:
SKPaymentWrapper(productIdentifier: productId, quantity: quantity),
transactionState: SKPaymentTransactionStateWrapper.failed,
transactionTimeStamp: 123123.121,
error: const SKError(
Expand All @@ -91,10 +97,12 @@ class FakeStoreKitPlatform {
}

SKPaymentTransactionWrapper createCanceledTransaction(
String productId, int errorCode) {
String productId, int errorCode,
{int quantity = 1}) {
return SKPaymentTransactionWrapper(
transactionIdentifier: '',
payment: SKPaymentWrapper(productIdentifier: productId),
payment:
SKPaymentWrapper(productIdentifier: productId, quantity: quantity),
transactionState: SKPaymentTransactionStateWrapper.failed,
transactionTimeStamp: 123123.121,
error: SKError(
Expand All @@ -105,9 +113,11 @@ class FakeStoreKitPlatform {
}

SKPaymentTransactionWrapper createRestoredTransaction(
String productId, String transactionId) {
String productId, String transactionId,
{int quantity = 1}) {
return SKPaymentTransactionWrapper(
payment: SKPaymentWrapper(productIdentifier: productId),
payment:
SKPaymentWrapper(productIdentifier: productId, quantity: quantity),
transactionState: SKPaymentTransactionStateWrapper.restored,
transactionTimeStamp: 123123.121,
transactionIdentifier: transactionId,
Expand Down Expand Up @@ -166,33 +176,38 @@ class FakeStoreKitPlatform {
return Future<void>.sync(() {});
case '-[InAppPurchasePlugin addPayment:result:]':
final String id = call.arguments['productIdentifier'] as String;
final int quantity = call.arguments['quantity'] as int;
final SKPaymentTransactionWrapper transaction =
createPendingTransaction(id);
createPendingTransaction(id, quantity: quantity);
transactions.add(transaction);
InAppPurchaseStoreKitPlatform.observer.updatedTransactions(
transactions: <SKPaymentTransactionWrapper>[transaction]);
sleep(const Duration(milliseconds: 30));
if (testTransactionFail) {
final SKPaymentTransactionWrapper transactionFailed =
createFailedTransaction(id);
createFailedTransaction(id, quantity: quantity);
InAppPurchaseStoreKitPlatform.observer.updatedTransactions(
transactions: <SKPaymentTransactionWrapper>[transactionFailed]);
} else if (testTransactionCancel > 0) {
final SKPaymentTransactionWrapper transactionCanceled =
createCanceledTransaction(id, testTransactionCancel);
createCanceledTransaction(id, testTransactionCancel,
quantity: quantity);
InAppPurchaseStoreKitPlatform.observer.updatedTransactions(
transactions: <SKPaymentTransactionWrapper>[transactionCanceled]);
} else {
final SKPaymentTransactionWrapper transactionFinished =
createPurchasedTransaction(
id, transaction.transactionIdentifier ?? '');
id, transaction.transactionIdentifier ?? '',
quantity: quantity);
InAppPurchaseStoreKitPlatform.observer.updatedTransactions(
transactions: <SKPaymentTransactionWrapper>[transactionFinished]);
}
break;
case '-[InAppPurchasePlugin finishTransaction:result:]':
finishedTransactions.add(createPurchasedTransaction(
call.arguments['productIdentifier'] as String,
call.arguments['transactionIdentifier'] as String));
call.arguments['transactionIdentifier'] as String,
quantity: transactions.first.payment.quantity));
break;
case '-[SKPaymentQueue startObservingTransactionQueue]':
queueIsActive = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,68 @@ void main() {
final PurchaseStatus purchaseStatus = await completer.future;
expect(purchaseStatus, PurchaseStatus.canceled);
});

test(
'buying non consumable, should be able to purchase multiple quantity of one product',
() async {
final List<PurchaseDetails> details = <PurchaseDetails>[];
final Completer<List<PurchaseDetails>> completer =
Completer<List<PurchaseDetails>>();
final Stream<List<PurchaseDetails>> stream =
iapStoreKitPlatform.purchaseStream;
late StreamSubscription<List<PurchaseDetails>> subscription;
subscription = stream.listen((List<PurchaseDetails> purchaseDetailsList) {
details.addAll(purchaseDetailsList);
for (final PurchaseDetails purchaseDetails in purchaseDetailsList) {
if (purchaseDetails.pendingCompletePurchase) {
iapStoreKitPlatform.completePurchase(purchaseDetails);
completer.complete(details);
subscription.cancel();
}
}
});
final AppStoreProductDetails productDetails =
AppStoreProductDetails.fromSKProduct(dummyProductWrapper);
final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam(
productDetails: productDetails,
quantity: 5,
applicationUserName: 'appName');
await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam);
await completer.future;
expect(
fakeStoreKitPlatform.finishedTransactions.first.payment.quantity, 5);
});

test(
'buying consumable, should be able to purchase multiple quantity of one product',
() async {
final List<PurchaseDetails> details = <PurchaseDetails>[];
final Completer<List<PurchaseDetails>> completer =
Completer<List<PurchaseDetails>>();
final Stream<List<PurchaseDetails>> stream =
iapStoreKitPlatform.purchaseStream;
late StreamSubscription<List<PurchaseDetails>> subscription;
subscription = stream.listen((List<PurchaseDetails> purchaseDetailsList) {
details.addAll(purchaseDetailsList);
for (final PurchaseDetails purchaseDetails in purchaseDetailsList) {
if (purchaseDetails.pendingCompletePurchase) {
iapStoreKitPlatform.completePurchase(purchaseDetails);
completer.complete(details);
subscription.cancel();
}
}
});
final AppStoreProductDetails productDetails =
AppStoreProductDetails.fromSKProduct(dummyProductWrapper);
final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam(
productDetails: productDetails,
quantity: 5,
applicationUserName: 'appName');
await iapStoreKitPlatform.buyConsumable(purchaseParam: purchaseParam);
await completer.future;
expect(
fakeStoreKitPlatform.finishedTransactions.first.payment.quantity, 5);
});
});

group('complete purchase', () {
Expand Down