diff --git a/README.md b/README.md index c1038168d..b294498c9 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,9 @@ Flare is a framework written in Swift that makes it easy for you to work with in ## Features - [x] Support Consumable & Non-Consumable Purchases - [x] Support Subscription Purchase -- [x] Refresh Receipt -- [x] Complete Unit Test Coverage +- [x] Support Promotional & Introductory Offers +- [x] iOS, tvOS, watchOS, macOS, and visionOS compatible +- [x] Complete Unit Test & Integration Coverage ## Documentation Check out [flare documentation](https://space-code.github.io/flare/documentation/flare/). diff --git a/Sources/Flare/Flare.docc/Articles/perform-purchase.md b/Sources/Flare/Flare.docc/Articles/perform-purchase.md index 55d439082..65f127781 100644 --- a/Sources/Flare/Flare.docc/Articles/perform-purchase.md +++ b/Sources/Flare/Flare.docc/Articles/perform-purchase.md @@ -10,7 +10,7 @@ The transactions array will only be synchronized with the server while the queue ```swift // Adds transaction observer to the payment queue and handles payment transactions. -Flare.default.addTransactionObserver { result in +Flare.shared.addTransactionObserver { result in switch result { case let .success(transaction): debugPrint("A transaction was received: \(transaction)") @@ -22,7 +22,7 @@ Flare.default.addTransactionObserver { result in ```swift // Removes transaction observer from the payment queue. -Flare.default.removeTransactionObserver() +Flare.shared.removeTransactionObserver() ``` ## Getting Products @@ -32,7 +32,7 @@ The fetch method sends a request to the App Store, which retrieves the products > important: Before attempting to add a payment always check if the user can actually make payments. The Flare does it under the hood, if a user cannot make payments, you will get an ``IAPError`` with the value ``IAPError/paymentNotAllowed``. ```swift -Flare.default.fetch(productIDs: ["product_id"]) { result in +Flare.shared.fetch(productIDs: ["product_id"]) { result in switch result { case let .success(products): debugPrint("Fetched products: \(products)") @@ -46,7 +46,7 @@ Additionally, there are versions of both fetch that provide an `async` method, a ```swift do { - let products = try await Flare.default.fetch(productIDs: Set(arrayLiteral: ["product_id"])) + let products = try await Flare.shared.fetch(productIDs: Set(arrayLiteral: ["product_id"])) } catch { debugPrint("An error occurred while fetching products: \(error.localizedDescription)") } @@ -64,7 +64,7 @@ Flare provides a few methods to perform a purchase: The method accepts a product parameter which represents a product: ```swift -Flare.default.purchase(product: product) { result in +Flare.shared.purchase(product: product) { result in switch result { case let .success(transaction): debugPrint("A transaction was received: \(transaction)") @@ -77,7 +77,7 @@ Flare.default.purchase(product: product) { result in If your app has a deployment target higher than iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, you can pass a set of [`options`](https://developer.apple.com/documentation/storekit/product/purchaseoption) along with a purchase request. ```swift -let transaction = try await Flare.default.purchase(product: product, options: [.appAccountToken(UUID())]) +let transaction = try await Flare.shared.purchase(product: product, options: [.appAccountToken(UUID())]) ``` ## Finishing Transaction @@ -87,7 +87,7 @@ Finishing a transaction tells StoreKit that your app completed its workflow to m To finish the transaction, call the ``IFlare/finish(transaction:completion:)`` method. ```swift -Flare.default.finish(transaction: transaction, completion: nil) +Flare.shared.finish(transaction: transaction, completion: nil) ``` > important: Don’t call the ``IFlare/finish(transaction:completion:)`` method before the transaction is actually complete and attempt to use some other mechanism in your app to track the transaction as unfinished. StoreKit doesn’t function that way, and doing that prevents your app from downloading Apple-hosted content and can lead to other issues. diff --git a/Sources/Flare/Flare.docc/Articles/promotional-offers.md b/Sources/Flare/Flare.docc/Articles/promotional-offers.md new file mode 100644 index 000000000..bdb9015c1 --- /dev/null +++ b/Sources/Flare/Flare.docc/Articles/promotional-offers.md @@ -0,0 +1,86 @@ +# Promotional Offers + +Learn how to use promotional offers. + +## Overview + +[Promotional offers](https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/subscriptions_and_offers/implementing_promotional_offers_in_your_app) can be effective in winning back lapsed subscribers or retaining current subscribers. You can provide lapsed or current subscribers a limited-time offer of a discounted or free period of service for auto-renewable subscriptions on macOS, iOS, and tvOS. + +[Introductory offers](https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/subscriptions_and_offers/implementing_introductory_offers_in_your_app#2940726) can offer a discounted introductory price, including a free trial, to eligible users. You can make introductory offers to customers who haven’t previously received an introductory offer for the given product, or for any products in the same subscription group. + +> note: To implement the offers, first complete the setup on App Store Connect, including generating a private key. See [Setting up promotional offers](https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/subscriptions_and_offers/setting_up_promotional_offers) for more details. + +## Introductory Offers + +> important: Do not show a subscription offer to users if they are not eligible for it. It’s very important to check this beforehand. + +First, check if the user is eligible for an introductory offer. + +> tip For this purpose can be used ``IFlare/checkEligibility(productIDs:)`` method. This method requires iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0. Otherwise, see [Determine Eligibility](https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/subscriptions_and_offers/implementing_introductory_offers_in_your_app#2940726). + +```swift +func isEligibleForIntroductoryOffer(productID: String) async -> Bool { + let dict = await Flare.shared.checkEligibility(productIDs: [productID]) + return dict[productID] == .eligible +} +``` + +Second, proceed with the purchase as usual. See [Perform Purchase]() + +## Promotional Offers + +First, you need to fetch the signature from your server. See [Generation a signature for promotional offers](https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/subscriptions_and_offers/generating_a_signature_for_promotional_offers) for more information. + +Second, configure ``IFlare`` with a ``Configuration``. + +```swift +Flare.configure(configuration: Configuration(applicationUsername: "username")) +``` + +Third, request a signature from your server and prepare the discount offer. + +```swift +func prepareOffer(username: String, productID: String, offerID: String, completion: @escaping (PromotionalOffer.SignedData) -> Void) { + YourServer.fetchOfferDetails( + username: username, + productIdentifier: productID, + offerIdentifier: offerID, + completion: { (nonce: UUID, timestamp: NSNumber, keyIdentifier: String, signature: String) in + let signedData = PromotionalOffer.SignedData( + identifier: offerID, + keyIdentifier: keyIdentifier, + nonce: nonce, + signature: signature, + timestamp: timestamp + ) + + completion(signedData) + } +} +``` + +Fourth, complete the purchase with the promotional offer. + +```swift +func purchase(product: StoreProduct, discount: StoreProductDiscount, signedData: SignedData) { + let promotionalOffer = PromotionalOffer(discount: discount, signedData: signedData) + + Flare.default.purchase(product: product, promotionalOffer: promotionalOffer) { result in + switch result { + case let .success(transaction): + break + case let .failure(error): + break + } + } + + // Or using async/await + let transaction = Flare.shared.purchase(product: product, promotionalOffer: promotionalOffer) +} +``` + +Fifth, complete the transaction. + +```swift +Flare.default.finish(transaction: transaction) +``` diff --git a/Sources/Flare/Flare.docc/Articles/refund-purchase.md b/Sources/Flare/Flare.docc/Articles/refund-purchase.md index f4f551738..46ab24f6d 100644 --- a/Sources/Flare/Flare.docc/Articles/refund-purchase.md +++ b/Sources/Flare/Flare.docc/Articles/refund-purchase.md @@ -9,7 +9,7 @@ Starting with iOS 15, Flare now includes support for refunding purchases as part Flare suggest to use ``IFlare/beginRefundRequest(productID:)`` for refunding purchase. ```swift -let status = try await Flare.default.beginRefundRequest(productID: "product_id") +let status = try await Flare.shared.beginRefundRequest(productID: "product_id") ``` > important: If an issue occurs during the refund process, this method throws an ``IAPError/refund(error:)`` error. diff --git a/Sources/Flare/Flare.docc/Articles/restore-purchase.md b/Sources/Flare/Flare.docc/Articles/restore-purchase.md index 18dae9f76..1164954b4 100644 --- a/Sources/Flare/Flare.docc/Articles/restore-purchase.md +++ b/Sources/Flare/Flare.docc/Articles/restore-purchase.md @@ -17,7 +17,7 @@ Use this API to request a new app receipt from the App Store if the receipt is i > important: The receipt refresh request displays a system prompt that asks users to authenticate with their App Store credentials. For a better user experience, initiate the request after an explicit user action, like tapping or clicking a button. ```swift -Flare.default.receipt { result in +Flare.shared.receipt { result in switch result { case let .success(receipt): // Handle a receipt @@ -32,5 +32,5 @@ Flare.default.receipt { result in There is an ``IFlare/receipt()`` method for obtaining a receipt using async/await. ```swift -let receipt = try await Flare.default.receipt() +let receipt = try await Flare.shared.receipt() ``` diff --git a/Sources/Flare/Flare.docc/Flare.md b/Sources/Flare/Flare.docc/Flare.md index 0682d736d..847fb53fa 100644 --- a/Sources/Flare/Flare.docc/Flare.md +++ b/Sources/Flare/Flare.docc/Flare.md @@ -10,11 +10,11 @@ Flare provides a clear and convenient API for making in-app purchases. import Flare /// Fetch a product with the given id -guard let product = try await Flare.default.products(productIDs: ["product_identifier"]) else { return } +guard let product = try await Flare.shared.products(productIDs: ["product_identifier"]) else { return } /// Purchase a product -let transaction = try await Flare.default.purchase(product: product) +let transaction = try await Flare.shared.purchase(product: product) /// Finish a transaction -Flare.default.finish(transaction: transaction, completion: nil) +Flare.shared.finish(transaction: transaction, completion: nil) ``` Flare supports both StoreKit and StoreKit2; it decides which one to use under the hood based on the operating system version. Flare provides two ways to work with in-app purchases (IAP): it supports the traditional closure-based syntax and the modern async/await approach. @@ -23,7 +23,7 @@ Flare supports both StoreKit and StoreKit2; it decides which one to use under th import Flare /// Fetch a product with the given id -Flare.default.products(productIDs: ["product_identifier"]) { result in +Flare.shared.products(productIDs: ["product_identifier"]) { result in switch result { case let .success(products): // Purchase a product @@ -64,3 +64,4 @@ flare is available under the MIT license. See the LICENSE file for more info. - - - +-