Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[in_app_purchase_storekit] Add storekit 2 support for canMakePayments and products #7473

Merged
merged 19 commits into from
Aug 29, 2024
Merged
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.3.18+1

* Adds support for StoreKit2's `canMakePayments` and `products`

## 0.3.18

* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI {
registrar.addMethodCallDelegate(instance, channel: channel)
registrar.addApplicationDelegate(instance)
SetUpInAppPurchaseAPI(messenger, instance)
if #available(iOS 15.0, macOS 12.0, *) {
InAppPurchase2APISetup.setUp(binaryMessenger: messenger, api: instance)
Copy link
Contributor

Choose a reason for hiding this comment

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

InAppPurchaseStoreKit2APISetup

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

}
}

// This init is used for tests
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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.

@available(iOS 15.0, macOS 12.0, *)
extension InAppPurchasePlugin: InAppPurchase2API {
// MARK: - Pigeon Functions

// Wrapper method around StoreKit2's canMakePayments() method
// https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments
func canMakePayments() throws -> Bool {
return AppStore.canMakePayments
}

// Wrapper method around StoreKit2's products() method
// https://developer.apple.com/documentation/storekit/product/3851116-products
func products(
identifiers: [String], completion: @escaping (Result<[SK2ProductMessage], Error>) -> Void
) {
Task {
do {
let products = try await Product.products(for: identifiers)
let productMessages = products.map {
$0.convertToPigeon
}
completion(.success(productMessages))
} catch {
completion(
.failure(
PigeonError(
code: "storekit2_products_error",
message: error.localizedDescription,
details: error.localizedDescription)))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// 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.

import Foundation
import StoreKit

@available(iOS 15.0, macOS 12.0, *)
extension Product {
var convertToPigeon: SK2ProductMessage {

return SK2ProductMessage(
id: id,
displayName: displayName,
description: description,
price: NSDecimalNumber(decimal: price).doubleValue,
displayPrice: displayPrice,
type: type.convertToPigeon,
subscription: subscription?.convertToPigeon,
priceLocale: priceFormatStyle.locale.convertToPigeon
)
}
}

extension SK2ProductMessage: Equatable {
LouiseHsu marked this conversation as resolved.
Show resolved Hide resolved
static func == (lhs: SK2ProductMessage, rhs: SK2ProductMessage) -> Bool {
return lhs.id == rhs.id && lhs.displayName == rhs.displayName
&& lhs.description == rhs.description && lhs.price == rhs.price
&& lhs.displayPrice == rhs.displayPrice && lhs.type == rhs.type
&& lhs.subscription == rhs.subscription && lhs.priceLocale == rhs.priceLocale
}
}

@available(iOS 15.0, macOS 12.0, *)
extension Product.ProductType {
var convertToPigeon: SK2ProductTypeMessage {
switch self {
case Product.ProductType.autoRenewable:
return SK2ProductTypeMessage.autoRenewable
case Product.ProductType.consumable:
return SK2ProductTypeMessage.consumable
case Product.ProductType.nonConsumable:
return SK2ProductTypeMessage.nonConsumable
case Product.ProductType.nonRenewable:
return SK2ProductTypeMessage.nonRenewable
default:
fatalError("An unknown ProductType was passed in")
}
}
}

@available(iOS 15.0, macOS 12.0, *)
extension Product.SubscriptionInfo {
var convertToPigeon: SK2SubscriptionInfoMessage {
return SK2SubscriptionInfoMessage(
promotionalOffers: promotionalOffers.map({ $0.convertToPigeon }),
subscriptionGroupID: subscriptionGroupID,
subscriptionPeriod: subscriptionPeriod.convertToPigeon)
}
}

extension SK2SubscriptionInfoMessage: Equatable {
static func == (lhs: SK2SubscriptionInfoMessage, rhs: SK2SubscriptionInfoMessage) -> Bool {
return lhs.promotionalOffers == rhs.promotionalOffers
&& lhs.subscriptionGroupID == rhs.subscriptionGroupID
&& lhs.subscriptionPeriod == rhs.subscriptionPeriod
}
}

@available(iOS 15.0, macOS 12.0, *)
extension Product.SubscriptionOffer {
var convertToPigeon: SK2SubscriptionOfferMessage {
return SK2SubscriptionOfferMessage(
/// ID is always `nil` for introductory offers and never `nil` for other offer types.
id: id,
price: NSDecimalNumber(decimal: price).doubleValue,
type: type.convertToPigeon,
period: period.convertToPigeon,
periodCount: Int64(periodCount),
paymentMode: paymentMode.convertToPigeon
)
}
}

extension SK2SubscriptionOfferMessage: Equatable {
static func == (lhs: SK2SubscriptionOfferMessage, rhs: SK2SubscriptionOfferMessage) -> Bool {
return lhs.id == rhs.id && lhs.price == rhs.price && lhs.type == rhs.type
&& lhs.period == rhs.period && lhs.periodCount == rhs.periodCount
&& lhs.paymentMode == rhs.paymentMode
}
}

@available(iOS 15.0, macOS 12.0, *)
extension Product.SubscriptionOffer.OfferType {
var convertToPigeon: SK2SubscriptionOfferTypeMessage {
switch self {
case .introductory:
return SK2SubscriptionOfferTypeMessage.introductory
case .promotional:
return SK2SubscriptionOfferTypeMessage.promotional
default:
fatalError("An unknown OfferType was passed in")
}
}
}

@available(iOS 15.0, macOS 12.0, *)
extension Product.SubscriptionPeriod {
var convertToPigeon: SK2SubscriptionPeriodMessage {
return SK2SubscriptionPeriodMessage(
value: Int64(value),
unit: unit.convertToPigeon)
}
}

extension SK2SubscriptionPeriodMessage: Equatable {
static func == (lhs: SK2SubscriptionPeriodMessage, rhs: SK2SubscriptionPeriodMessage) -> Bool {
return lhs.value == rhs.value && lhs.unit == rhs.unit
}
}

@available(iOS 15.0, macOS 12.0, *)
extension Product.SubscriptionPeriod.Unit {
var convertToPigeon: SK2SubscriptionPeriodUnitMessage {
switch self {
case .day:
return SK2SubscriptionPeriodUnitMessage.day
case .week:
return SK2SubscriptionPeriodUnitMessage.week
case .month:
return SK2SubscriptionPeriodUnitMessage.month
case .year:
return SK2SubscriptionPeriodUnitMessage.year
@unknown default:
fatalError("unknown SubscriptionPeriodUnit encountered")
}
}
}

@available(iOS 15.0, macOS 12.0, *)
extension Product.SubscriptionOffer.PaymentMode {
var convertToPigeon: SK2SubscriptionOfferPaymentModeMessage {
switch self {
case .freeTrial:
return SK2SubscriptionOfferPaymentModeMessage.freeTrial
case .payUpFront:
return SK2SubscriptionOfferPaymentModeMessage.payUpFront
case .payAsYouGo:
return SK2SubscriptionOfferPaymentModeMessage.payAsYouGo
default:
fatalError("Encountered an unknown PaymentMode")
}
}
}

extension Locale {
var convertToPigeon: SK2PriceLocaleMessage {
return SK2PriceLocaleMessage(
currencyCode: currencyCode ?? "",
currencySymbol: currencySymbol ?? ""
)
}
}

extension SK2PriceLocaleMessage: Equatable {
static func == (lhs: SK2PriceLocaleMessage, rhs: SK2PriceLocaleMessage) -> Bool {
return lhs.currencyCode == rhs.currencyCode && lhs.currencySymbol == rhs.currencySymbol
}
}
Loading