Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 74 additions & 69 deletions packages/apple/Sources/Helpers/StoreKitTypesBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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+."
)
}
}
Comment thread
hyochan marked this conversation as resolved.
} 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
}
Comment thread
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

@coderabbitai coderabbitai Bot Jan 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

StoreKit "introductoryOfferEligibility" Swift 6.1 Xcode 16.4 PurchaseOption

💡 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):

  • Server: create a JWS (signed with your App Store signing key) containing the required claims (product id, bundle id, transaction/appTransaction info and the eligibility boolean).
  • Client: pass that compact JWS to Product.PurchaseOption.introductoryOfferEligibility(compactJWS:) when calling product.purchase(options:).

Example (concise):

let jws = ""
let options: Set<Product.PurchaseOption> = [
.appAccountToken(appAccountToken),
.introductoryOfferEligibility(compactJWS: jws)
]
let result = try await subscriptionProduct.purchase(options: options)

Notes:

  • The JWS must be created server-side using the App Store signing key / key ID / issuer ID; Apple verifies it.
  • This API is intended to allow re-enabling or controlling intro-offer eligibility (not to bypass App Store rules); follow Apple’s required claims/format and testing in Sandbox.

References:

  • Blog walkthrough / example (includes code and server-side details). [1]
  • Dev summary of StoreKit/JWS changes (context for iOS 18.4 / StoreKit updates). [2]
  • Apple Developer docs on implementing introductory offers / PurchaseOption (official spec & required claims). [3]

If you want, I can:

  • show a fuller client-side Swift 6.1 example with error handling, or
  • outline the exact JWS claims and a minimal server-side Swift/Node example to generate the compact JWS. Which would you prefer?

The introductoryOfferEligibility API signature is incorrect.

The actual StoreKit API (Swift 6.1 / Xcode 16.4+) is Product.PurchaseOption.introductoryOfferEligibility(compactJWS:), which requires a compact JWS string (created server-side using your App Store signing key), not a Bool. The code currently passes eligibility (which appears to be Bool) directly to the API, causing a type mismatch. You must:

  1. Receive the compact JWS from your server (not a Bool)
  2. Update the API call to .introductoryOfferEligibility(compactJWS: jws)
  3. Verify the server-side JWS generation includes required claims (product id, bundle id, transaction/appTransaction info, and eligibility boolean)
🤖 Prompt for AI Agents
In `@packages/apple/Sources/Helpers/StoreKitTypesBridge.swift` around lines 449 -
466, The code is using subscriptionProps.introductoryOfferEligibility as a Bool
and passing it to options.insert(.introductoryOfferEligibility(eligibility)),
but StoreKit's API is
Product.PurchaseOption.introductoryOfferEligibility(compactJWS:) and requires a
server-generated compact JWS string; change
subscriptionProps.introductoryOfferEligibility to carry the compact JWS (String)
or add a new field (e.g., introductoryOfferCompactJWS), update the call to
options.insert(.introductoryOfferEligibility(compactJWS: jws)), and remove the
current Bool branch (or map Bool -> server call) so you retrieve the JWS from
your backend before inserting; also ensure server JWS includes the required
claims (product id, bundle id, transaction/appTransaction info, and eligibility)
and keep the existing error path (PurchaseError.make with props.sku) to fail
fast when JWS is missing or invalid.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You're right that the official StoreKit API introductoryOfferEligibility(compactJWS:) requires a JWS string, not a Bool.

Current situation:

  • The GraphQL schema defines introductoryOfferEligibility: Boolean based on an earlier understanding of the API
  • The actual WWDC 2025 API requires compactJWS: String for server-signed JWS verification

This is out of scope for this PR because it requires:

  1. Schema change (BooleanString or new IntroductoryOfferJWSInputIOS type)
  2. Type regeneration for all platforms (TypeScript, Swift, Kotlin, Dart, GDScript)
  3. Breaking change for developers already using the Boolean version
  4. Documentation updates

I'll create a separate issue to track this API correction. Thanks for the thorough analysis!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Created issue #68 to track this API correction: #68

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@hyochan, thank you for the clarification! I completely understand—fixing this API mismatch would indeed require a coordinated change across the entire stack (schema, type generation, all platform bindings), which is well beyond this PR's scope.

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
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 67
File: packages/apple/Sources/Helpers/StoreKitTypesBridge.swift:449-466
Timestamp: 2026-01-18T15:32:05.480Z
Learning: In `packages/apple/Sources/Helpers/StoreKitTypesBridge.swift` and the GraphQL schema, `introductoryOfferEligibility` is currently defined as `Boolean` but the actual StoreKit API `Product.PurchaseOption.introductoryOfferEligibility(compactJWS:)` requires a server-signed JWS string. This is a known limitation that requires a breaking schema change across all platforms (TypeScript, Swift, Kotlin, Dart, GDScript) to fix properly.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T16:22:04.517Z
Learning: Applies to packages/apple/Sources/Models/Types.swift : DO NOT edit packages/apple/Sources/Models/Types.swift - this is auto-generated from OpenIAP GraphQL schema and must be regenerated via ./scripts/generate-types.sh

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@hyochan, thank you for creating issue #68 to track this! Your reasoning makes perfect sense—changing from Boolean to a JWS string is indeed a breaking change that requires schema updates and regeneration across all platforms. Deferring it to a separate PR is the right approach to keep this PR focused.

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
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 67
File: packages/apple/Sources/Helpers/StoreKitTypesBridge.swift:449-466
Timestamp: 2026-01-18T15:32:21.560Z
Learning: In packages/apple/Sources/Helpers/StoreKitTypesBridge.swift: The StoreKit API `Product.PurchaseOption.introductoryOfferEligibility(compactJWS:)` (Swift 6.1 / Xcode 16.4+) requires a server-signed compact JWS string, not a Boolean value. The JWS must be created server-side using the App Store signing key and include required claims (product id, bundle id, transaction/appTransaction info, and eligibility boolean).

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T16:22:04.517Z
Learning: Applies to packages/apple/Sources/Models/Types.swift : DO NOT edit packages/apple/Sources/Models/Types.swift - this is auto-generated from OpenIAP GraphQL schema and must be regenerated via ./scripts/generate-types.sh

Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-27T16:22:04.517Z
Learning: Applies to packages/apple/Sources/Models/** : Apple Models directory (Sources/Models/) contains official OpenIAP types matching openiap.dev/docs/types (Product.swift, Purchase.swift, ActiveSubscription.swift, etc.)

}

// Advanced Commerce Data (iOS 15+)
Expand Down
24 changes: 4 additions & 20 deletions packages/apple/Sources/Models/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1417,45 +1417,27 @@ public struct RequestPurchaseIosProps: Codable {
public var andDangerouslyFinishTransactionAutomatically: Bool?
/// App account token for user tracking
public var appAccountToken: String?
/// Override introductory offer eligibility (iOS 15+, WWDC 2025).
/// Set to true to indicate the user is eligible for introductory offer,
/// or false to indicate they are not. When nil, the system determines eligibility.
/// Back-deployed to iOS 15.
public var introductoryOfferEligibility: Bool?
/// JWS promotional offer (iOS 15+, WWDC 2025).
/// New signature format using compact JWS string for promotional offers.
/// Back-deployed to iOS 15.
public var promotionalOfferJWS: PromotionalOfferJWSInputIOS?
/// Purchase quantity
public var quantity: Int?
/// Product SKU
public var sku: String
/// Win-back offer to apply (iOS 18+)
/// Used to re-engage churned subscribers with a discount or free trial.
/// Note: Win-back offers only apply to subscription products.
public var winBackOffer: WinBackOfferInputIOS?
/// Discount offer to apply
/// Promotional offer to apply (subscriptions only, ignored for one-time purchases).
/// iOS only supports promotional offers for auto-renewable subscriptions.
public var withOffer: DiscountOfferInputIOS?

public init(
advancedCommerceData: String? = nil,
andDangerouslyFinishTransactionAutomatically: Bool? = nil,
appAccountToken: String? = nil,
introductoryOfferEligibility: Bool? = nil,
promotionalOfferJWS: PromotionalOfferJWSInputIOS? = nil,
quantity: Int? = nil,
sku: String,
winBackOffer: WinBackOfferInputIOS? = nil,
withOffer: DiscountOfferInputIOS? = nil
) {
self.advancedCommerceData = advancedCommerceData
self.andDangerouslyFinishTransactionAutomatically = andDangerouslyFinishTransactionAutomatically
self.appAccountToken = appAccountToken
self.introductoryOfferEligibility = introductoryOfferEligibility
self.promotionalOfferJWS = promotionalOfferJWS
self.quantity = quantity
self.sku = sku
self.winBackOffer = winBackOffer
self.withOffer = withOffer
}
}
Expand Down Expand Up @@ -1630,6 +1612,8 @@ public struct RequestSubscriptionIosProps: Codable {
/// The offer is available when the customer is eligible and can be discovered
/// via StoreKit Message (automatic) or subscription offer APIs.
public var winBackOffer: WinBackOfferInputIOS?
/// Promotional offer to apply for subscription purchases.
/// Requires server-signed offer with nonce, timestamp, keyId, and signature.
public var withOffer: DiscountOfferInputIOS?

public init(
Expand Down
16 changes: 4 additions & 12 deletions packages/apple/Sources/OpenIapModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1263,25 +1263,17 @@ public final class OpenIapModule: NSObject, OpenIapModuleProtocol {
return first
}

private func resolveIosPurchaseProps(from params: RequestPurchaseProps) throws -> RequestPurchaseIosProps {
/// Resolves iOS purchase props from request params.
/// Returns either RequestPurchaseIosProps or RequestSubscriptionIosProps based on request type.
private func resolveIosPurchaseProps(from params: RequestPurchaseProps) throws -> any IosPropsProtocol {
switch params.request {
case let .purchase(platforms):
if let ios = platforms.ios {
return ios
}
case let .subscription(platforms):
if let ios = platforms.ios {
return RequestPurchaseIosProps(
advancedCommerceData: ios.advancedCommerceData,
andDangerouslyFinishTransactionAutomatically: ios.andDangerouslyFinishTransactionAutomatically,
appAccountToken: ios.appAccountToken,
introductoryOfferEligibility: ios.introductoryOfferEligibility,
promotionalOfferJWS: ios.promotionalOfferJWS,
quantity: ios.quantity,
sku: ios.sku,
winBackOffer: ios.winBackOffer,
withOffer: ios.withOffer
)
return ios
}
}
throw makePurchaseError(code: .purchaseError, message: "Missing iOS purchase parameters")
Expand Down
16 changes: 16 additions & 0 deletions packages/apple/Sources/OpenIapProtocol.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import Foundation
import StoreKit

// MARK: - iOS Props Protocol

/// Protocol for iOS purchase/subscription props to enable polymorphic handling.
/// Both RequestPurchaseIosProps and RequestSubscriptionIosProps conform to this protocol.
public protocol IosPropsProtocol {
var sku: String { get }
var quantity: Int? { get }
var appAccountToken: String? { get }
var withOffer: DiscountOfferInputIOS? { get }
var advancedCommerceData: String? { get }
var andDangerouslyFinishTransactionAutomatically: Bool? { get }
}

extension RequestPurchaseIosProps: IosPropsProtocol {}
extension RequestSubscriptionIosProps: IosPropsProtocol {}

// MARK: - Event Listeners

@available(iOS 15.0, macOS 14.0, *)
Expand Down
32 changes: 6 additions & 26 deletions packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3527,19 +3527,6 @@ public data class RequestPurchaseIosProps(
* App account token for user tracking
*/
val appAccountToken: String? = null,
/**
* Override introductory offer eligibility (iOS 15+, WWDC 2025).
* Set to true to indicate the user is eligible for introductory offer,
* or false to indicate they are not. When nil, the system determines eligibility.
* Back-deployed to iOS 15.
*/
val introductoryOfferEligibility: Boolean? = null,
/**
* JWS promotional offer (iOS 15+, WWDC 2025).
* New signature format using compact JWS string for promotional offers.
* Back-deployed to iOS 15.
*/
val promotionalOfferJWS: PromotionalOfferJWSInputIOS? = null,
/**
* Purchase quantity
*/
Expand All @@ -3549,13 +3536,8 @@ public data class RequestPurchaseIosProps(
*/
val sku: String,
/**
* Win-back offer to apply (iOS 18+)
* Used to re-engage churned subscribers with a discount or free trial.
* Note: Win-back offers only apply to subscription products.
*/
val winBackOffer: WinBackOfferInputIOS? = null,
/**
* Discount offer to apply
* Promotional offer to apply (subscriptions only, ignored for one-time purchases).
* iOS only supports promotional offers for auto-renewable subscriptions.
*/
val withOffer: DiscountOfferInputIOS? = null
) {
Expand All @@ -3565,11 +3547,8 @@ public data class RequestPurchaseIosProps(
advancedCommerceData = json["advancedCommerceData"] as? String,
andDangerouslyFinishTransactionAutomatically = json["andDangerouslyFinishTransactionAutomatically"] as? Boolean,
appAccountToken = json["appAccountToken"] as? String,
introductoryOfferEligibility = json["introductoryOfferEligibility"] as? Boolean,
promotionalOfferJWS = (json["promotionalOfferJWS"] as? Map<String, Any?>)?.let { PromotionalOfferJWSInputIOS.fromJson(it) },
quantity = (json["quantity"] as? Number)?.toInt(),
sku = json["sku"] as? String ?: "",
winBackOffer = (json["winBackOffer"] as? Map<String, Any?>)?.let { WinBackOfferInputIOS.fromJson(it) },
withOffer = (json["withOffer"] as? Map<String, Any?>)?.let { DiscountOfferInputIOS.fromJson(it) },
)
}
Expand All @@ -3579,11 +3558,8 @@ public data class RequestPurchaseIosProps(
"advancedCommerceData" to advancedCommerceData,
"andDangerouslyFinishTransactionAutomatically" to andDangerouslyFinishTransactionAutomatically,
"appAccountToken" to appAccountToken,
"introductoryOfferEligibility" to introductoryOfferEligibility,
"promotionalOfferJWS" to promotionalOfferJWS?.toJson(),
"quantity" to quantity,
"sku" to sku,
"winBackOffer" to winBackOffer?.toJson(),
"withOffer" to withOffer?.toJson(),
)
}
Expand Down Expand Up @@ -3789,6 +3765,10 @@ public data class RequestSubscriptionIosProps(
* via StoreKit Message (automatic) or subscription offer APIs.
*/
val winBackOffer: WinBackOfferInputIOS? = null,
/**
* Promotional offer to apply for subscription purchases.
* Requires server-signed offer with nonce, timestamp, keyId, and signature.
*/
val withOffer: DiscountOfferInputIOS? = null
) {
companion object {
Expand Down
Loading
Loading