diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 606fd82a618..6dd64fd7e1d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.4.4 + +* Adds a new case `.unverified` to enum `SK2ProductPurchaseResult` +* Fixes the StoreKit2 implementation throwing `PlatformException`s instead of returning the corresponding +`SK2ProductPurchaseResult` when a purchase is cancelled / unverified / pending. + ## 0.4.3 * Adds **Introductory Offer Eligibility** support for StoreKit2 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/InAppPurchasePlugin+StoreKit2.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/InAppPurchasePlugin+StoreKit2.swift index 899f7262bda..4ed03d49f3f 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/InAppPurchasePlugin+StoreKit2.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/InAppPurchasePlugin+StoreKit2.swift @@ -86,32 +86,14 @@ extension InAppPurchasePlugin: InAppPurchase2API { switch result { case .success(let verification): - switch verification { - case .verified(let transaction): - self.sendTransactionUpdate( - transaction: transaction, receipt: verification.jwsRepresentation) - completion(.success(result.convertToPigeon())) - case .unverified(_, let error): - completion(.failure(error)) - } - case .pending: - completion( - .failure( - PigeonError( - code: "storekit2_purchase_pending", - message: - "This transaction is still pending and but may complete in the future. If it completes, it will be delivered via `purchaseStream`", - details: "Product ID : \(id)"))) - case .userCancelled: - completion( - .failure( - PigeonError( - code: "storekit2_purchase_cancelled", - message: "This transaction has been cancelled by the user.", - details: "Product ID : \(id)"))) + sendTransactionUpdate( + transaction: verification.unsafePayloadValue, receipt: verification.jwsRepresentation) + case .pending, .userCancelled: + break @unknown default: fatalError("An unknown StoreKit PurchaseResult has been encountered.") } + completion(.success(result.convertToPigeon())) } catch { completion(.failure(error)) } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Translators.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Translators.swift index 9be3bb370ca..3266aa3b964 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Translators.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/StoreKit2Translators.swift @@ -216,13 +216,11 @@ extension SK2PriceLocaleMessage: Equatable { @available(iOS 15.0, macOS 12.0, *) extension Product.PurchaseResult { func convertToPigeon() -> SK2ProductPurchaseResultMessage { - switch self { - case .success(_): - return SK2ProductPurchaseResultMessage.success - case .userCancelled: - return SK2ProductPurchaseResultMessage.userCancelled - case .pending: - return SK2ProductPurchaseResultMessage.pending + return switch self { + case .success(.verified): .success + case .success(.unverified): .unverified + case .userCancelled: .userCancelled + case .pending: .pending @unknown default: fatalError() } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/sk2_pigeon.g.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/sk2_pigeon.g.swift index bf07f4e9324..e74412a446e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/sk2_pigeon.g.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/sk2_pigeon.g.swift @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.1), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -169,8 +169,9 @@ enum SK2SubscriptionPeriodUnitMessage: Int { enum SK2ProductPurchaseResultMessage: Int { case success = 0 - case userCancelled = 1 - case pending = 2 + case unverified = 1 + case userCancelled = 2 + case pending = 3 } /// Generated class from Pigeon that represents data sent in messages. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift index 7fdbf49febc..bcf073634f9 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -194,7 +194,8 @@ final class InAppPurchase2PluginTests: XCTestCase { let expectation = self.expectation(description: "Purchase request should succeed") plugin.purchase(id: "consumable", options: nil) { result in switch result { - case .success: + case .success(let message): + XCTAssert(message == .success) expectation.fulfill() case .failure(let error): XCTFail("Purchase should NOT fail. Failed with \(error)") diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift index edf936e7347..ad52486284d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift @@ -105,4 +105,11 @@ final class StoreKit2TranslatorTests: XCTestCase { let pigeonMessage = locale.convertToPigeon XCTAssertEqual(pigeonMessage, productMessage.priceLocale) } + + func testPigeonConversionForPurchaseResult() { + // Unfortunately the .success case is not testable because the Transaction + // type has no visible initializers. + XCTAssertEqual(Product.PurchaseResult.pending.convertToPigeon(), .pending) + XCTAssertEqual(Product.PurchaseResult.userCancelled.convertToPigeon(), .userCancelled) + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart index fa19079811f..5fcf95b081f 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.1), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -79,6 +79,7 @@ enum SK2SubscriptionPeriodUnitMessage { enum SK2ProductPurchaseResultMessage { success, + unverified, userCancelled, pending, } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart index 7170c3e50b6..324e85ba9e7 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart @@ -22,7 +22,7 @@ enum SK2ProductType { nonRenewable, /// An auto-renewable subscription. - autoRenewable; + autoRenewable, } extension on SK2ProductTypeMessage { @@ -119,12 +119,13 @@ class SK2SubscriptionOffer { extension on SK2SubscriptionOfferMessage { SK2SubscriptionOffer convertFromPigeon() { return SK2SubscriptionOffer( - id: id, - price: price, - type: type.convertFromPigeon(), - period: period.convertFromPigeon(), - periodCount: periodCount, - paymentMode: paymentMode.convertFromPigeon()); + id: id, + price: price, + type: type.convertFromPigeon(), + period: period.convertFromPigeon(), + periodCount: periodCount, + paymentMode: paymentMode.convertFromPigeon(), + ); } } @@ -196,7 +197,7 @@ enum SK2SubscriptionPeriodUnit { month, /// A subscription period unit of a year. - year + year, } extension on SK2SubscriptionPeriodUnitMessage { @@ -224,7 +225,7 @@ enum SK2SubscriptionOfferPaymentMode { payUpFront, /// A payment mode of a product discount that indicates a free trial offer. - freeTrial; + freeTrial, } extension on SK2SubscriptionOfferPaymentModeMessage { @@ -255,28 +256,35 @@ class SK2PriceLocale { /// Convert this instance of [SK2PriceLocale] to [SK2PriceLocaleMessage] SK2PriceLocaleMessage convertToPigeon() { return SK2PriceLocaleMessage( - currencyCode: currencyCode, currencySymbol: currencySymbol); + currencyCode: currencyCode, + currencySymbol: currencySymbol, + ); } } extension on SK2PriceLocaleMessage { SK2PriceLocale convertFromPigeon() { return SK2PriceLocale( - currencyCode: currencyCode, currencySymbol: currencySymbol); + currencyCode: currencyCode, + currencySymbol: currencySymbol, + ); } } /// Wrapper around [PurchaseResult] /// https://developer.apple.com/documentation/storekit/product/purchaseresult enum SK2ProductPurchaseResult { - /// The purchase succeeded and results in a transaction. + /// The purchase succeeded and results in a transaction signed by the App Store. success, + /// The purchase succeeded but the transation could not be verified. + unverified, + /// The user canceled the purchase. userCancelled, /// The purchase is pending, and requires action from the customer. - pending + pending, } /// Wrapper around [PurchaseOption] @@ -315,14 +323,16 @@ class SK2ProductPurchaseOptions { extension on SK2ProductPurchaseResultMessage { SK2ProductPurchaseResult convertFromPigeon() { - switch (this) { - case SK2ProductPurchaseResultMessage.success: - return SK2ProductPurchaseResult.success; - case SK2ProductPurchaseResultMessage.userCancelled: - return SK2ProductPurchaseResult.userCancelled; - case SK2ProductPurchaseResultMessage.pending: - return SK2ProductPurchaseResult.pending; - } + return switch (this) { + SK2ProductPurchaseResultMessage.success => + SK2ProductPurchaseResult.success, + SK2ProductPurchaseResultMessage.userCancelled => + SK2ProductPurchaseResult.userCancelled, + SK2ProductPurchaseResultMessage.pending => + SK2ProductPurchaseResult.pending, + SK2ProductPurchaseResultMessage.unverified => + SK2ProductPurchaseResult.unverified, + }; } } @@ -371,8 +381,9 @@ class SK2Product { /// If any of the identifiers are invalid or can't be found, they are excluded /// from the returned list. static Future> products(List identifiers) async { - final List productsMsg = - await _hostApi.products(identifiers); + final List productsMsg = await _hostApi.products( + identifiers, + ); if (productsMsg.isEmpty && identifiers.isNotEmpty) { throw PlatformException( code: 'storekit_no_response', @@ -389,8 +400,10 @@ class SK2Product { /// Wrapper for StoreKit's [Product.purchase] /// https://developer.apple.com/documentation/storekit/product/3791971-purchase /// Initiates a purchase for the product with the App Store and displays the confirmation sheet. - static Future purchase(String id, - {SK2ProductPurchaseOptions? options}) async { + static Future purchase( + String id, { + SK2ProductPurchaseOptions? options, + }) async { SK2ProductPurchaseResultMessage result; if (options != null) { result = await _hostApi.purchase(id, options: options.convertToPigeon()); @@ -402,12 +415,8 @@ class SK2Product { /// Checks if the user is eligible for an introductory offer. /// The product must be an auto-renewable subscription. - static Future isIntroductoryOfferEligible( - String productId, - ) async { - final bool result = await _hostApi.isIntroductoryOfferEligible( - productId, - ); + static Future isIntroductoryOfferEligible(String productId) async { + final bool result = await _hostApi.isIntroductoryOfferEligible(productId); return result; } @@ -428,26 +437,28 @@ class SK2Product { /// Converts this instance of [SK2Product] to it's pigeon representation [SK2ProductMessage] SK2ProductMessage convertToPigeon() { return SK2ProductMessage( - id: id, - displayName: displayName, - description: description, - price: price, - displayPrice: displayPrice, - type: type.convertToPigeon(), - priceLocale: priceLocale.convertToPigeon()); + id: id, + displayName: displayName, + description: description, + price: price, + displayPrice: displayPrice, + type: type.convertToPigeon(), + priceLocale: priceLocale.convertToPigeon(), + ); } } extension on SK2ProductMessage { SK2Product convertFromPigeon() { return SK2Product( - id: id, - displayName: displayName, - displayPrice: displayPrice, - price: price, - description: description, - type: type.convertFromPigeon(), - subscription: subscription?.convertFromPigeon(), - priceLocale: priceLocale.convertFromPigeon()); + id: id, + displayName: displayName, + displayPrice: displayPrice, + price: price, + description: description, + type: type.convertFromPigeon(), + subscription: subscription?.convertFromPigeon(), + priceLocale: priceLocale.convertFromPigeon(), + ); } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart index 1afb97dbdcf..044eb842747 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart @@ -206,7 +206,12 @@ class SK2ErrorMessage { final Map? userInfo; } -enum SK2ProductPurchaseResultMessage { success, userCancelled, pending } +enum SK2ProductPurchaseResultMessage { + success, + unverified, + userCancelled, + pending +} @HostApi(dartHostTestHandler: 'TestInAppPurchase2Api') abstract class InAppPurchase2API { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index b04206ec231..53c27bdebf8 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/packages/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.4.3 +version: 0.4.4 environment: sdk: ^3.6.0 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart index 8dd95cfdd3e..d37308b398c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart @@ -1,16 +1,16 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.1), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: avoid_relative_lib_imports import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; - import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; + import 'package:in_app_purchase_storekit/src/sk2_pigeon.g.dart'; class _PigeonCodec extends StandardMessageCodec {