-
-
Notifications
You must be signed in to change notification settings - Fork 11
fix: remove subscription-only props from RequestPurchaseIosProps #67
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
Changes from all commits
ba3dac2
4440111
ee891b3
a327b35
80456f1
8f1d1dd
5b15f10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -359,7 +359,7 @@ enum StoreKitTypesBridge { | |
| } | ||
| } | ||
|
|
||
| static func purchaseOptions(from props: RequestPurchaseIosProps, product: StoreKit.Product? = nil) throws -> Set<StoreKit.Product.PurchaseOption> { | ||
| static func purchaseOptions(from props: some IosPropsProtocol, product: StoreKit.Product? = nil) throws -> Set<StoreKit.Product.PurchaseOption> { | ||
| var options: Set<StoreKit.Product.PurchaseOption> = [] | ||
| if let quantity = props.quantity, quantity > 1 { | ||
| options.insert(.quantity(quantity)) | ||
|
|
@@ -377,88 +377,93 @@ enum StoreKitTypesBridge { | |
| } | ||
| options.insert(option) | ||
| } | ||
| // Win-back offers (iOS 18+) | ||
| // Used to re-engage churned subscribers | ||
| if #available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) { | ||
| if let winBackInput = props.winBackOffer { | ||
| guard let product = product else { | ||
| OpenIapLog.error("❌ Win-back offer requires product context") | ||
| throw PurchaseError.make( | ||
| code: .developerError, | ||
| productId: props.sku, | ||
| message: "Win-back offer requires product context. Fetch the product before calling requestPurchase." | ||
| ) | ||
| } | ||
| // Find the win-back offer from the product's promotional offers | ||
| if let subscription = product.subscription { | ||
| let winBackOffer = subscription.promotionalOffers.first { offer in | ||
| offer.id == winBackInput.offerId && offer.type == .winBack | ||
|
|
||
| // Subscription-only options (only available on RequestSubscriptionIosProps) | ||
| if let subscriptionProps = props as? RequestSubscriptionIosProps { | ||
| // Win-back offers (iOS 18+) | ||
| // Used to re-engage churned subscribers | ||
| if let winBackInput = subscriptionProps.winBackOffer { | ||
| if #available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) { | ||
| guard let product = product else { | ||
| OpenIapLog.error("❌ Win-back offer requires product context") | ||
| throw PurchaseError.make( | ||
| code: .developerError, | ||
| productId: props.sku, | ||
| message: "Win-back offer requires product context. Fetch the product before calling requestPurchase." | ||
| ) | ||
| } | ||
| if let offer = winBackOffer { | ||
| options.insert(.winBackOffer(offer)) | ||
| OpenIapLog.debug("✅ Added win-back offer: \(winBackInput.offerId)") | ||
| // Find the win-back offer from the product's promotional offers | ||
| if let subscription = product.subscription { | ||
| let winBackOffer = subscription.promotionalOffers.first { offer in | ||
| offer.id == winBackInput.offerId && offer.type == .winBack | ||
| } | ||
| if let offer = winBackOffer { | ||
| options.insert(.winBackOffer(offer)) | ||
| OpenIapLog.debug("✅ Added win-back offer: \(winBackInput.offerId)") | ||
| } else { | ||
| OpenIapLog.error("❌ Win-back offer not found: \(winBackInput.offerId)") | ||
| throw PurchaseError.make( | ||
| code: .developerError, | ||
| productId: props.sku, | ||
| message: "Win-back offer not found: \(winBackInput.offerId). Ensure the user is eligible and the offer ID is correct." | ||
| ) | ||
| } | ||
| } else { | ||
| OpenIapLog.error("❌ Win-back offer not found: \(winBackInput.offerId)") | ||
| OpenIapLog.error("❌ Win-back offer requires a subscription product") | ||
| throw PurchaseError.make( | ||
| code: .developerError, | ||
| productId: props.sku, | ||
| message: "Win-back offer not found: \(winBackInput.offerId). Ensure the user is eligible and the offer ID is correct." | ||
| message: "Win-back offers can only be applied to subscription products" | ||
| ) | ||
| } | ||
| } else { | ||
| OpenIapLog.error("❌ Win-back offer requires a subscription product") | ||
| // Fail fast when win-back offers are used on unsupported OS versions | ||
| OpenIapLog.error("❌ Win-back offers require iOS 18+ / macOS 15+ / tvOS 18+ / watchOS 11+ / visionOS 2+") | ||
| throw PurchaseError.make( | ||
| code: .developerError, | ||
| productId: props.sku, | ||
| message: "Win-back offers can only be applied to subscription products" | ||
| message: "Win-back offers are only supported on iOS 18+ / macOS 15+ / tvOS 18+ / watchOS 11+ / visionOS 2+." | ||
| ) | ||
| } | ||
| } | ||
| } else if props.winBackOffer != nil { | ||
| // Fail fast when win-back offers are used on unsupported OS versions | ||
| OpenIapLog.error("❌ Win-back offers require iOS 18+ / macOS 15+ / tvOS 18+ / watchOS 11+ / visionOS 2+") | ||
| throw PurchaseError.make( | ||
| code: .developerError, | ||
| productId: props.sku, | ||
| message: "Win-back offers are only supported on iOS 18+ / macOS 15+ / tvOS 18+ / watchOS 11+ / visionOS 2+." | ||
| ) | ||
| } | ||
| // JWS Promotional Offer (iOS 15+, WWDC 2025) | ||
| // New signature format using compact JWS string for promotional offers | ||
| // Back-deployed to iOS 15, but requires Xcode 16.4+ / Swift 6.1+ to compile | ||
| if let jwsOffer = props.promotionalOfferJWS { | ||
| #if swift(>=6.1) | ||
| // Swift 6.1+ implementation | ||
| options.insert(.promotionalOffer(jwsOffer.jws)) | ||
| OpenIapLog.debug("✅ Added JWS promotional offer: \(jwsOffer.offerId)") | ||
| #else | ||
| // Swift < 6.1: API not available, throw error to fail fast | ||
| OpenIapLog.error("❌ JWS promotional offers require Xcode 16.4+ / Swift 6.1+") | ||
| throw PurchaseError.make( | ||
| code: .developerError, | ||
| productId: props.sku, | ||
| message: "JWS promotional offers require Xcode 16.4+ / Swift 6.1+. Use withOffer with signature-based promotional offers instead." | ||
| ) | ||
| #endif | ||
| } | ||
|
|
||
| // Introductory Offer Eligibility Override (iOS 15+, WWDC 2025) | ||
| // Allows overriding the system's eligibility check for introductory offers | ||
| // Back-deployed to iOS 15, but requires Xcode 16.4+ / Swift 6.1+ to compile | ||
| if let eligibility = props.introductoryOfferEligibility { | ||
| #if swift(>=6.1) | ||
| // Swift 6.1+ implementation | ||
| options.insert(.introductoryOfferEligibility(eligibility)) | ||
| OpenIapLog.debug("✅ Added introductory offer eligibility override: \(eligibility)") | ||
| #else | ||
| // Swift < 6.1: API not available, throw error to fail fast | ||
| OpenIapLog.error("❌ Introductory offer eligibility override requires Xcode 16.4+ / Swift 6.1+") | ||
| throw PurchaseError.make( | ||
| code: .developerError, | ||
| productId: props.sku, | ||
| message: "Introductory offer eligibility override requires Xcode 16.4+ / Swift 6.1+. The system will determine eligibility automatically." | ||
| ) | ||
| #endif | ||
|
|
||
| // JWS Promotional Offer (iOS 15+, WWDC 2025) | ||
| // New signature format using compact JWS string for promotional offers | ||
| // Back-deployed to iOS 15, but requires Xcode 16.4+ / Swift 6.1+ to compile | ||
| if let jwsOffer = subscriptionProps.promotionalOfferJWS { | ||
| #if swift(>=6.1) | ||
| // Swift 6.1+ implementation | ||
| options.insert(.promotionalOffer(compactJWS: jwsOffer.jws)) | ||
| OpenIapLog.debug("✅ Added JWS promotional offer: \(jwsOffer.offerId)") | ||
| #else | ||
| // Swift < 6.1: API not available, throw error to fail fast | ||
| OpenIapLog.error("❌ JWS promotional offers require Xcode 16.4+ / Swift 6.1+") | ||
| throw PurchaseError.make( | ||
| code: .developerError, | ||
| productId: props.sku, | ||
| message: "JWS promotional offers require Xcode 16.4+ / Swift 6.1+. Use withOffer with signature-based promotional offers instead." | ||
| ) | ||
| #endif | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| // Introductory Offer Eligibility Override (iOS 15+, WWDC 2025) | ||
| // Allows overriding the system's eligibility check for introductory offers | ||
| // Back-deployed to iOS 15, but requires Xcode 16.4+ / Swift 6.1+ to compile | ||
| if let eligibility = subscriptionProps.introductoryOfferEligibility { | ||
| #if swift(>=6.1) | ||
| // Swift 6.1+ implementation | ||
| options.insert(.introductoryOfferEligibility(eligibility)) | ||
| OpenIapLog.debug("✅ Added introductory offer eligibility override: \(eligibility)") | ||
| #else | ||
| // Swift < 6.1: API not available, throw error to fail fast | ||
| OpenIapLog.error("❌ Introductory offer eligibility override requires Xcode 16.4+ / Swift 6.1+") | ||
| throw PurchaseError.make( | ||
| code: .developerError, | ||
| productId: props.sku, | ||
| message: "Introductory offer eligibility override requires Xcode 16.4+ / Swift 6.1+. The system will determine eligibility automatically." | ||
| ) | ||
| #endif | ||
| } | ||
|
Comment on lines
+449
to
466
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Short answer — you can pass a signed compact JWS to Product.PurchaseOption to set introductory-offer eligibility when calling purchase. The API looks like this (Swift 6.1 / Xcode 16.4):
Example (concise): let jws = "" Notes:
References:
If you want, I can:
The The actual StoreKit API (Swift 6.1 / Xcode 16.4+) is
🤖 Prompt for AI Agents
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right that the official StoreKit API Current situation:
This is out of scope for this PR because it requires:
I'll create a separate issue to track this API correction. Thanks for the thorough analysis!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If you'd like, I can help create the follow-up issue using the GitHub CLI with all the technical details (current Boolean type, required JWS string format, affected platforms, etc.). Just let me know! Otherwise, I'll note this for future reference. Thanks again for the thorough explanation of the current state and planned approach! 🐰 ✏️ Learnings added
🧠 Learnings used
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I appreciate you taking the time to document this for future work. Good luck with the API correction when you get to it! 🚀 ✏️ Learnings added
🧠 Learnings used |
||
| } | ||
|
|
||
| // Advanced Commerce Data (iOS 15+) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.