diff --git a/src/api-ios.graphql b/src/api-ios.graphql index 49114ad..447d543 100644 --- a/src/api-ios.graphql +++ b/src/api-ios.graphql @@ -12,6 +12,11 @@ extend type Query { # Future getPromotedProductIOS: ProductIOS """ + Check if external purchase notice sheet can be presented (iOS 18.2+) + """ + # Future + canPresentExternalPurchaseNoticeIOS: Boolean! + """ Retrieve all pending transactions in the StoreKit queue """ # Future @@ -94,4 +99,14 @@ extend type Mutation { """ # Future presentCodeRedemptionSheetIOS: Boolean! + """ + Present external purchase notice sheet (iOS 18.2+) + """ + # Future + presentExternalPurchaseNoticeSheetIOS: ExternalPurchaseNoticeResultIOS! + """ + Present external purchase custom link with StoreKit UI (iOS 18.2+) + """ + # Future + presentExternalPurchaseLinkIOS(url: String!): ExternalPurchaseLinkResultIOS! } diff --git a/src/event.graphql b/src/event.graphql index 7f4a637..d0de8ee 100644 --- a/src/event.graphql +++ b/src/event.graphql @@ -13,4 +13,9 @@ extend type Subscription { Fires when the App Store surfaces a promoted product (iOS only) """ promotedProductIOS: String! + """ + Fires when a user selects alternative billing in the User Choice Billing dialog (Android only) + Only triggered when the user selects alternative billing instead of Google Play billing + """ + userChoiceBillingAndroid: UserChoiceBillingDetails! } diff --git a/src/generated/Types.kt b/src/generated/Types.kt index 78629e5..050eeb3 100644 --- a/src/generated/Types.kt +++ b/src/generated/Types.kt @@ -189,6 +189,34 @@ public enum class ErrorCode(val rawValue: String) { fun toJson(): String = rawValue } +/** + * User actions on external purchase notice sheet (iOS 18.2+) + */ +public enum class ExternalPurchaseNoticeAction(val rawValue: String) { + /** + * User chose to continue to external purchase + */ + Continue("continue"), + /** + * User dismissed the notice sheet + */ + Dismissed("dismissed") + + companion object { + fun fromJson(value: String): ExternalPurchaseNoticeAction = when (value) { + "continue" -> ExternalPurchaseNoticeAction.Continue + "CONTINUE" -> ExternalPurchaseNoticeAction.Continue + "Continue" -> ExternalPurchaseNoticeAction.Continue + "dismissed" -> ExternalPurchaseNoticeAction.Dismissed + "DISMISSED" -> ExternalPurchaseNoticeAction.Dismissed + "Dismissed" -> ExternalPurchaseNoticeAction.Dismissed + else -> throw IllegalArgumentException("Unknown ExternalPurchaseNoticeAction value: $value") + } + } + + fun toJson(): String = rawValue +} + public enum class IapEvent(val rawValue: String) { PurchaseUpdated("purchase-updated"), PurchaseError("purchase-error"), @@ -675,6 +703,66 @@ public data class EntitlementIOS( ) } +/** + * Result of presenting an external purchase link (iOS 18.2+) + */ +public data class ExternalPurchaseLinkResultIOS( + /** + * Optional error message if the presentation failed + */ + val error: String? = null, + /** + * Whether the user completed the external purchase flow + */ + val success: Boolean +) { + + companion object { + fun fromJson(json: Map): ExternalPurchaseLinkResultIOS { + return ExternalPurchaseLinkResultIOS( + error = json["error"] as String?, + success = json["success"] as Boolean, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "ExternalPurchaseLinkResultIOS", + "error" to error, + "success" to success, + ) +} + +/** + * Result of presenting external purchase notice sheet (iOS 18.2+) + */ +public data class ExternalPurchaseNoticeResultIOS( + /** + * Optional error message if the presentation failed + */ + val error: String? = null, + /** + * Notice result indicating user action + */ + val result: ExternalPurchaseNoticeAction +) { + + companion object { + fun fromJson(json: Map): ExternalPurchaseNoticeResultIOS { + return ExternalPurchaseNoticeResultIOS( + error = json["error"] as String?, + result = ExternalPurchaseNoticeAction.fromJson(json["result"] as String), + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "ExternalPurchaseNoticeResultIOS", + "error" to error, + "result" to result.toJson(), + ) +} + public sealed interface FetchProductsResult public data class FetchProductsResultProducts(val value: List?) : FetchProductsResult @@ -1533,6 +1621,37 @@ public data class SubscriptionStatusIOS( ) } +/** + * User Choice Billing event details (Android) + * Fired when a user selects alternative billing in the User Choice Billing dialog + */ +public data class UserChoiceBillingDetails( + /** + * Token that must be reported to Google Play within 24 hours + */ + val externalTransactionToken: String, + /** + * List of product IDs selected by the user + */ + val products: List +) { + + companion object { + fun fromJson(json: Map): UserChoiceBillingDetails { + return UserChoiceBillingDetails( + externalTransactionToken = json["externalTransactionToken"] as String, + products = (json["products"] as List<*>).map { it as String }, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "UserChoiceBillingDetails", + "externalTransactionToken" to externalTransactionToken, + "products" to products.map { it }, + ) +} + public typealias VoidResult = Unit // MARK: - Input Objects @@ -1795,10 +1914,6 @@ public data class RequestPurchaseIosProps( * App account token for user tracking */ val appAccountToken: String? = null, - /** - * External purchase URL for alternative billing (iOS) - */ - val externalPurchaseUrl: String? = null, /** * Purchase quantity */ @@ -1817,7 +1932,6 @@ public data class RequestPurchaseIosProps( return RequestPurchaseIosProps( andDangerouslyFinishTransactionAutomatically = json["andDangerouslyFinishTransactionAutomatically"] as Boolean?, appAccountToken = json["appAccountToken"] as String?, - externalPurchaseUrl = json["externalPurchaseUrl"] as String?, quantity = (json["quantity"] as Number?)?.toInt(), sku = json["sku"] as String, withOffer = (json["withOffer"] as Map?)?.let { DiscountOfferInputIOS.fromJson(it) }, @@ -1828,7 +1942,6 @@ public data class RequestPurchaseIosProps( fun toJson(): Map = mapOf( "andDangerouslyFinishTransactionAutomatically" to andDangerouslyFinishTransactionAutomatically, "appAccountToken" to appAccountToken, - "externalPurchaseUrl" to externalPurchaseUrl, "quantity" to quantity, "sku" to sku, "withOffer" to withOffer?.toJson(), @@ -1971,10 +2084,6 @@ public data class RequestSubscriptionAndroidProps( public data class RequestSubscriptionIosProps( val andDangerouslyFinishTransactionAutomatically: Boolean? = null, val appAccountToken: String? = null, - /** - * External purchase URL for alternative billing (iOS) - */ - val externalPurchaseUrl: String? = null, val quantity: Int? = null, val sku: String, val withOffer: DiscountOfferInputIOS? = null @@ -1984,7 +2093,6 @@ public data class RequestSubscriptionIosProps( return RequestSubscriptionIosProps( andDangerouslyFinishTransactionAutomatically = json["andDangerouslyFinishTransactionAutomatically"] as Boolean?, appAccountToken = json["appAccountToken"] as String?, - externalPurchaseUrl = json["externalPurchaseUrl"] as String?, quantity = (json["quantity"] as Number?)?.toInt(), sku = json["sku"] as String, withOffer = (json["withOffer"] as Map?)?.let { DiscountOfferInputIOS.fromJson(it) }, @@ -1995,7 +2103,6 @@ public data class RequestSubscriptionIosProps( fun toJson(): Map = mapOf( "andDangerouslyFinishTransactionAutomatically" to andDangerouslyFinishTransactionAutomatically, "appAccountToken" to appAccountToken, - "externalPurchaseUrl" to externalPurchaseUrl, "quantity" to quantity, "sku" to sku, "withOffer" to withOffer?.toJson(), @@ -2145,6 +2252,14 @@ public interface MutationResolver { * Present the App Store code redemption sheet */ suspend fun presentCodeRedemptionSheetIOS(): Boolean + /** + * Present external purchase custom link with StoreKit UI (iOS 18.2+) + */ + suspend fun presentExternalPurchaseLinkIOS(url: String): ExternalPurchaseLinkResultIOS + /** + * Present external purchase notice sheet (iOS 18.2+) + */ + suspend fun presentExternalPurchaseNoticeSheetIOS(): ExternalPurchaseNoticeResultIOS /** * Initiate a purchase flow; rely on events for final state */ @@ -2184,6 +2299,10 @@ public interface MutationResolver { * GraphQL root query operations. */ public interface QueryResolver { + /** + * Check if external purchase notice sheet can be presented (iOS 18.2+) + */ + suspend fun canPresentExternalPurchaseNoticeIOS(): Boolean /** * Get current StoreKit 2 entitlements (iOS 15+) */ @@ -2270,6 +2389,11 @@ public interface SubscriptionResolver { * Fires when a purchase completes successfully or a pending purchase resolves */ suspend fun purchaseUpdated(): Purchase + /** + * Fires when a user selects alternative billing in the User Choice Billing dialog (Android only) + * Only triggered when the user selects alternative billing instead of Google Play billing + */ + suspend fun userChoiceBillingAndroid(): UserChoiceBillingDetails } // MARK: - Root Operation Helpers @@ -2287,6 +2411,8 @@ public typealias MutationEndConnectionHandler = suspend () -> Boolean public typealias MutationFinishTransactionHandler = suspend (purchase: PurchaseInput, isConsumable: Boolean?) -> Unit public typealias MutationInitConnectionHandler = suspend (config: InitConnectionConfig?) -> Boolean public typealias MutationPresentCodeRedemptionSheetIOSHandler = suspend () -> Boolean +public typealias MutationPresentExternalPurchaseLinkIOSHandler = suspend (url: String) -> ExternalPurchaseLinkResultIOS +public typealias MutationPresentExternalPurchaseNoticeSheetIOSHandler = suspend () -> ExternalPurchaseNoticeResultIOS public typealias MutationRequestPurchaseHandler = suspend (params: RequestPurchaseProps) -> RequestPurchaseResult? public typealias MutationRequestPurchaseOnPromotedProductIOSHandler = suspend () -> Boolean public typealias MutationRestorePurchasesHandler = suspend () -> Unit @@ -2307,6 +2433,8 @@ public data class MutationHandlers( val finishTransaction: MutationFinishTransactionHandler? = null, val initConnection: MutationInitConnectionHandler? = null, val presentCodeRedemptionSheetIOS: MutationPresentCodeRedemptionSheetIOSHandler? = null, + val presentExternalPurchaseLinkIOS: MutationPresentExternalPurchaseLinkIOSHandler? = null, + val presentExternalPurchaseNoticeSheetIOS: MutationPresentExternalPurchaseNoticeSheetIOSHandler? = null, val requestPurchase: MutationRequestPurchaseHandler? = null, val requestPurchaseOnPromotedProductIOS: MutationRequestPurchaseOnPromotedProductIOSHandler? = null, val restorePurchases: MutationRestorePurchasesHandler? = null, @@ -2318,6 +2446,7 @@ public data class MutationHandlers( // MARK: - Query Helpers +public typealias QueryCanPresentExternalPurchaseNoticeIOSHandler = suspend () -> Boolean public typealias QueryCurrentEntitlementIOSHandler = suspend (sku: String) -> PurchaseIOS? public typealias QueryFetchProductsHandler = suspend (params: ProductRequest) -> FetchProductsResult public typealias QueryGetActiveSubscriptionsHandler = suspend (subscriptionIds: List?) -> List @@ -2337,6 +2466,7 @@ public typealias QuerySubscriptionStatusIOSHandler = suspend (sku: String) -> Li public typealias QueryValidateReceiptIOSHandler = suspend (options: ReceiptValidationProps) -> ReceiptValidationResultIOS public data class QueryHandlers( + val canPresentExternalPurchaseNoticeIOS: QueryCanPresentExternalPurchaseNoticeIOSHandler? = null, val currentEntitlementIOS: QueryCurrentEntitlementIOSHandler? = null, val fetchProducts: QueryFetchProductsHandler? = null, val getActiveSubscriptions: QueryGetActiveSubscriptionsHandler? = null, @@ -2361,9 +2491,11 @@ public data class QueryHandlers( public typealias SubscriptionPromotedProductIOSHandler = suspend () -> String public typealias SubscriptionPurchaseErrorHandler = suspend () -> PurchaseError public typealias SubscriptionPurchaseUpdatedHandler = suspend () -> Purchase +public typealias SubscriptionUserChoiceBillingAndroidHandler = suspend () -> UserChoiceBillingDetails public data class SubscriptionHandlers( val promotedProductIOS: SubscriptionPromotedProductIOSHandler? = null, val purchaseError: SubscriptionPurchaseErrorHandler? = null, - val purchaseUpdated: SubscriptionPurchaseUpdatedHandler? = null + val purchaseUpdated: SubscriptionPurchaseUpdatedHandler? = null, + val userChoiceBillingAndroid: SubscriptionUserChoiceBillingAndroidHandler? = null ) diff --git a/src/generated/Types.swift b/src/generated/Types.swift index 2f70e0a..4217407 100644 --- a/src/generated/Types.swift +++ b/src/generated/Types.swift @@ -57,6 +57,14 @@ public enum ErrorCode: String, Codable, CaseIterable { case emptySkuList = "empty-sku-list" } +/// User actions on external purchase notice sheet (iOS 18.2+) +public enum ExternalPurchaseNoticeAction: String, Codable, CaseIterable { + /// User chose to continue to external purchase + case `continue` = "continue" + /// User dismissed the notice sheet + case dismissed = "dismissed" +} + public enum IapEvent: String, Codable, CaseIterable { case purchaseUpdated = "purchase-updated" case purchaseError = "purchase-error" @@ -217,6 +225,22 @@ public struct EntitlementIOS: Codable { public var transactionId: String } +/// Result of presenting an external purchase link (iOS 18.2+) +public struct ExternalPurchaseLinkResultIOS: Codable { + /// Optional error message if the presentation failed + public var error: String? + /// Whether the user completed the external purchase flow + public var success: Bool +} + +/// Result of presenting external purchase notice sheet (iOS 18.2+) +public struct ExternalPurchaseNoticeResultIOS: Codable { + /// Optional error message if the presentation failed + public var error: String? + /// Notice result indicating user action + public var result: ExternalPurchaseNoticeAction +} + public enum FetchProductsResult { case products([Product]?) case subscriptions([ProductSubscription]?) @@ -469,6 +493,15 @@ public struct SubscriptionStatusIOS: Codable { public var state: String } +/// User Choice Billing event details (Android) +/// Fired when a user selects alternative billing in the User Choice Billing dialog +public struct UserChoiceBillingDetails: Codable { + /// Token that must be reported to Google Play within 24 hours + public var externalTransactionToken: String + /// List of product IDs selected by the user + public var products: [String] +} + public typealias VoidResult = Void // MARK: - Input Objects @@ -635,8 +668,6 @@ public struct RequestPurchaseIosProps: Codable { public var andDangerouslyFinishTransactionAutomatically: Bool? /// App account token for user tracking public var appAccountToken: String? - /// External purchase URL for alternative billing (iOS) - public var externalPurchaseUrl: String? /// Purchase quantity public var quantity: Int? /// Product SKU @@ -647,14 +678,12 @@ public struct RequestPurchaseIosProps: Codable { public init( andDangerouslyFinishTransactionAutomatically: Bool? = nil, appAccountToken: String? = nil, - externalPurchaseUrl: String? = nil, quantity: Int? = nil, sku: String, withOffer: DiscountOfferInputIOS? = nil ) { self.andDangerouslyFinishTransactionAutomatically = andDangerouslyFinishTransactionAutomatically self.appAccountToken = appAccountToken - self.externalPurchaseUrl = externalPurchaseUrl self.quantity = quantity self.sku = sku self.withOffer = withOffer @@ -784,8 +813,6 @@ public struct RequestSubscriptionAndroidProps: Codable { public struct RequestSubscriptionIosProps: Codable { public var andDangerouslyFinishTransactionAutomatically: Bool? public var appAccountToken: String? - /// External purchase URL for alternative billing (iOS) - public var externalPurchaseUrl: String? public var quantity: Int? public var sku: String public var withOffer: DiscountOfferInputIOS? @@ -793,14 +820,12 @@ public struct RequestSubscriptionIosProps: Codable { public init( andDangerouslyFinishTransactionAutomatically: Bool? = nil, appAccountToken: String? = nil, - externalPurchaseUrl: String? = nil, quantity: Int? = nil, sku: String, withOffer: DiscountOfferInputIOS? = nil ) { self.andDangerouslyFinishTransactionAutomatically = andDangerouslyFinishTransactionAutomatically self.appAccountToken = appAccountToken - self.externalPurchaseUrl = externalPurchaseUrl self.quantity = quantity self.sku = sku self.withOffer = withOffer @@ -1155,6 +1180,10 @@ public protocol MutationResolver { func initConnection(_ config: InitConnectionConfig?) async throws -> Bool /// Present the App Store code redemption sheet func presentCodeRedemptionSheetIOS() async throws -> Bool + /// Present external purchase custom link with StoreKit UI (iOS 18.2+) + func presentExternalPurchaseLinkIOS(_ url: String) async throws -> ExternalPurchaseLinkResultIOS + /// Present external purchase notice sheet (iOS 18.2+) + func presentExternalPurchaseNoticeSheetIOS() async throws -> ExternalPurchaseNoticeResultIOS /// Initiate a purchase flow; rely on events for final state func requestPurchase(_ params: RequestPurchaseProps) async throws -> RequestPurchaseResult? /// Purchase the promoted product surfaced by the App Store @@ -1178,6 +1207,8 @@ public protocol MutationResolver { /// GraphQL root query operations. public protocol QueryResolver { + /// Check if external purchase notice sheet can be presented (iOS 18.2+) + func canPresentExternalPurchaseNoticeIOS() async throws -> Bool /// Get current StoreKit 2 entitlements (iOS 15+) func currentEntitlementIOS(_ sku: String) async throws -> PurchaseIOS? /// Retrieve products or subscriptions from the store @@ -1222,6 +1253,9 @@ public protocol SubscriptionResolver { func purchaseError() async throws -> PurchaseError /// Fires when a purchase completes successfully or a pending purchase resolves func purchaseUpdated() async throws -> Purchase + /// Fires when a user selects alternative billing in the User Choice Billing dialog (Android only) + /// Only triggered when the user selects alternative billing instead of Google Play billing + func userChoiceBillingAndroid() async throws -> UserChoiceBillingDetails } // MARK: - Root Operation Helpers @@ -1239,6 +1273,8 @@ public typealias MutationEndConnectionHandler = () async throws -> Bool public typealias MutationFinishTransactionHandler = (_ purchase: PurchaseInput, _ isConsumable: Bool?) async throws -> Void public typealias MutationInitConnectionHandler = (_ config: InitConnectionConfig?) async throws -> Bool public typealias MutationPresentCodeRedemptionSheetIOSHandler = () async throws -> Bool +public typealias MutationPresentExternalPurchaseLinkIOSHandler = (_ url: String) async throws -> ExternalPurchaseLinkResultIOS +public typealias MutationPresentExternalPurchaseNoticeSheetIOSHandler = () async throws -> ExternalPurchaseNoticeResultIOS public typealias MutationRequestPurchaseHandler = (_ params: RequestPurchaseProps) async throws -> RequestPurchaseResult? public typealias MutationRequestPurchaseOnPromotedProductIOSHandler = () async throws -> Bool public typealias MutationRestorePurchasesHandler = () async throws -> Void @@ -1259,6 +1295,8 @@ public struct MutationHandlers { public var finishTransaction: MutationFinishTransactionHandler? public var initConnection: MutationInitConnectionHandler? public var presentCodeRedemptionSheetIOS: MutationPresentCodeRedemptionSheetIOSHandler? + public var presentExternalPurchaseLinkIOS: MutationPresentExternalPurchaseLinkIOSHandler? + public var presentExternalPurchaseNoticeSheetIOS: MutationPresentExternalPurchaseNoticeSheetIOSHandler? public var requestPurchase: MutationRequestPurchaseHandler? public var requestPurchaseOnPromotedProductIOS: MutationRequestPurchaseOnPromotedProductIOSHandler? public var restorePurchases: MutationRestorePurchasesHandler? @@ -1279,6 +1317,8 @@ public struct MutationHandlers { finishTransaction: MutationFinishTransactionHandler? = nil, initConnection: MutationInitConnectionHandler? = nil, presentCodeRedemptionSheetIOS: MutationPresentCodeRedemptionSheetIOSHandler? = nil, + presentExternalPurchaseLinkIOS: MutationPresentExternalPurchaseLinkIOSHandler? = nil, + presentExternalPurchaseNoticeSheetIOS: MutationPresentExternalPurchaseNoticeSheetIOSHandler? = nil, requestPurchase: MutationRequestPurchaseHandler? = nil, requestPurchaseOnPromotedProductIOS: MutationRequestPurchaseOnPromotedProductIOSHandler? = nil, restorePurchases: MutationRestorePurchasesHandler? = nil, @@ -1298,6 +1338,8 @@ public struct MutationHandlers { self.finishTransaction = finishTransaction self.initConnection = initConnection self.presentCodeRedemptionSheetIOS = presentCodeRedemptionSheetIOS + self.presentExternalPurchaseLinkIOS = presentExternalPurchaseLinkIOS + self.presentExternalPurchaseNoticeSheetIOS = presentExternalPurchaseNoticeSheetIOS self.requestPurchase = requestPurchase self.requestPurchaseOnPromotedProductIOS = requestPurchaseOnPromotedProductIOS self.restorePurchases = restorePurchases @@ -1310,6 +1352,7 @@ public struct MutationHandlers { // MARK: - Query Helpers +public typealias QueryCanPresentExternalPurchaseNoticeIOSHandler = () async throws -> Bool public typealias QueryCurrentEntitlementIOSHandler = (_ sku: String) async throws -> PurchaseIOS? public typealias QueryFetchProductsHandler = (_ params: ProductRequest) async throws -> FetchProductsResult public typealias QueryGetActiveSubscriptionsHandler = (_ subscriptionIds: [String]?) async throws -> [ActiveSubscription] @@ -1329,6 +1372,7 @@ public typealias QuerySubscriptionStatusIOSHandler = (_ sku: String) async throw public typealias QueryValidateReceiptIOSHandler = (_ options: ReceiptValidationProps) async throws -> ReceiptValidationResultIOS public struct QueryHandlers { + public var canPresentExternalPurchaseNoticeIOS: QueryCanPresentExternalPurchaseNoticeIOSHandler? public var currentEntitlementIOS: QueryCurrentEntitlementIOSHandler? public var fetchProducts: QueryFetchProductsHandler? public var getActiveSubscriptions: QueryGetActiveSubscriptionsHandler? @@ -1348,6 +1392,7 @@ public struct QueryHandlers { public var validateReceiptIOS: QueryValidateReceiptIOSHandler? public init( + canPresentExternalPurchaseNoticeIOS: QueryCanPresentExternalPurchaseNoticeIOSHandler? = nil, currentEntitlementIOS: QueryCurrentEntitlementIOSHandler? = nil, fetchProducts: QueryFetchProductsHandler? = nil, getActiveSubscriptions: QueryGetActiveSubscriptionsHandler? = nil, @@ -1366,6 +1411,7 @@ public struct QueryHandlers { subscriptionStatusIOS: QuerySubscriptionStatusIOSHandler? = nil, validateReceiptIOS: QueryValidateReceiptIOSHandler? = nil ) { + self.canPresentExternalPurchaseNoticeIOS = canPresentExternalPurchaseNoticeIOS self.currentEntitlementIOS = currentEntitlementIOS self.fetchProducts = fetchProducts self.getActiveSubscriptions = getActiveSubscriptions @@ -1391,19 +1437,23 @@ public struct QueryHandlers { public typealias SubscriptionPromotedProductIOSHandler = () async throws -> String public typealias SubscriptionPurchaseErrorHandler = () async throws -> PurchaseError public typealias SubscriptionPurchaseUpdatedHandler = () async throws -> Purchase +public typealias SubscriptionUserChoiceBillingAndroidHandler = () async throws -> UserChoiceBillingDetails public struct SubscriptionHandlers { public var promotedProductIOS: SubscriptionPromotedProductIOSHandler? public var purchaseError: SubscriptionPurchaseErrorHandler? public var purchaseUpdated: SubscriptionPurchaseUpdatedHandler? + public var userChoiceBillingAndroid: SubscriptionUserChoiceBillingAndroidHandler? public init( promotedProductIOS: SubscriptionPromotedProductIOSHandler? = nil, purchaseError: SubscriptionPurchaseErrorHandler? = nil, - purchaseUpdated: SubscriptionPurchaseUpdatedHandler? = nil + purchaseUpdated: SubscriptionPurchaseUpdatedHandler? = nil, + userChoiceBillingAndroid: SubscriptionUserChoiceBillingAndroidHandler? = nil ) { self.promotedProductIOS = promotedProductIOS self.purchaseError = purchaseError self.purchaseUpdated = purchaseUpdated + self.userChoiceBillingAndroid = userChoiceBillingAndroid } } diff --git a/src/generated/types.dart b/src/generated/types.dart index 7cf45d4..b6c7bde 100644 --- a/src/generated/types.dart +++ b/src/generated/types.dart @@ -226,6 +226,33 @@ enum ErrorCode { String toJson() => value; } +/// User actions on external purchase notice sheet (iOS 18.2+) +enum ExternalPurchaseNoticeAction { + /// User chose to continue to external purchase + Continue('continue'), + /// User dismissed the notice sheet + Dismissed('dismissed'); + + const ExternalPurchaseNoticeAction(this.value); + final String value; + + factory ExternalPurchaseNoticeAction.fromJson(String value) { + switch (value) { + case 'continue': + case 'CONTINUE': + case 'Continue': + return ExternalPurchaseNoticeAction.Continue; + case 'dismissed': + case 'DISMISSED': + case 'Dismissed': + return ExternalPurchaseNoticeAction.Dismissed; + } + throw ArgumentError('Unknown ExternalPurchaseNoticeAction value: $value'); + } + + String toJson() => value; +} + enum IapEvent { PurchaseUpdated('purchase-updated'), PurchaseError('purchase-error'), @@ -814,6 +841,66 @@ class EntitlementIOS { } } +/// Result of presenting an external purchase link (iOS 18.2+) +class ExternalPurchaseLinkResultIOS { + const ExternalPurchaseLinkResultIOS({ + /// Optional error message if the presentation failed + this.error, + /// Whether the user completed the external purchase flow + required this.success, + }); + + /// Optional error message if the presentation failed + final String? error; + /// Whether the user completed the external purchase flow + final bool success; + + factory ExternalPurchaseLinkResultIOS.fromJson(Map json) { + return ExternalPurchaseLinkResultIOS( + error: json['error'] as String?, + success: json['success'] as bool, + ); + } + + Map toJson() { + return { + '__typename': 'ExternalPurchaseLinkResultIOS', + 'error': error, + 'success': success, + }; + } +} + +/// Result of presenting external purchase notice sheet (iOS 18.2+) +class ExternalPurchaseNoticeResultIOS { + const ExternalPurchaseNoticeResultIOS({ + /// Optional error message if the presentation failed + this.error, + /// Notice result indicating user action + required this.result, + }); + + /// Optional error message if the presentation failed + final String? error; + /// Notice result indicating user action + final ExternalPurchaseNoticeAction result; + + factory ExternalPurchaseNoticeResultIOS.fromJson(Map json) { + return ExternalPurchaseNoticeResultIOS( + error: json['error'] as String?, + result: ExternalPurchaseNoticeAction.fromJson(json['result'] as String), + ); + } + + Map toJson() { + return { + '__typename': 'ExternalPurchaseNoticeResultIOS', + 'error': error, + 'result': result.toJson(), + }; + } +} + abstract class FetchProductsResult { const FetchProductsResult(); } @@ -1918,6 +2005,37 @@ class SubscriptionStatusIOS { } } +/// User Choice Billing event details (Android) +/// Fired when a user selects alternative billing in the User Choice Billing dialog +class UserChoiceBillingDetails { + const UserChoiceBillingDetails({ + /// Token that must be reported to Google Play within 24 hours + required this.externalTransactionToken, + /// List of product IDs selected by the user + required this.products, + }); + + /// Token that must be reported to Google Play within 24 hours + final String externalTransactionToken; + /// List of product IDs selected by the user + final List products; + + factory UserChoiceBillingDetails.fromJson(Map json) { + return UserChoiceBillingDetails( + externalTransactionToken: json['externalTransactionToken'] as String, + products: (json['products'] as List).map((e) => e as String).toList(), + ); + } + + Map toJson() { + return { + '__typename': 'UserChoiceBillingDetails', + 'externalTransactionToken': externalTransactionToken, + 'products': products.map((e) => e).toList(), + }; + } +} + typedef VoidResult = void; // MARK: - Input Objects @@ -2209,8 +2327,6 @@ class RequestPurchaseIosProps { this.andDangerouslyFinishTransactionAutomatically, /// App account token for user tracking this.appAccountToken, - /// External purchase URL for alternative billing (iOS) - this.externalPurchaseUrl, /// Purchase quantity this.quantity, /// Product SKU @@ -2223,8 +2339,6 @@ class RequestPurchaseIosProps { final bool? andDangerouslyFinishTransactionAutomatically; /// App account token for user tracking final String? appAccountToken; - /// External purchase URL for alternative billing (iOS) - final String? externalPurchaseUrl; /// Purchase quantity final int? quantity; /// Product SKU @@ -2236,7 +2350,6 @@ class RequestPurchaseIosProps { return RequestPurchaseIosProps( andDangerouslyFinishTransactionAutomatically: json['andDangerouslyFinishTransactionAutomatically'] as bool?, appAccountToken: json['appAccountToken'] as String?, - externalPurchaseUrl: json['externalPurchaseUrl'] as String?, quantity: json['quantity'] as int?, sku: json['sku'] as String, withOffer: json['withOffer'] != null ? DiscountOfferInputIOS.fromJson(json['withOffer'] as Map) : null, @@ -2247,7 +2360,6 @@ class RequestPurchaseIosProps { return { 'andDangerouslyFinishTransactionAutomatically': andDangerouslyFinishTransactionAutomatically, 'appAccountToken': appAccountToken, - 'externalPurchaseUrl': externalPurchaseUrl, 'quantity': quantity, 'sku': sku, 'withOffer': withOffer?.toJson(), @@ -2405,8 +2517,6 @@ class RequestSubscriptionIosProps { const RequestSubscriptionIosProps({ this.andDangerouslyFinishTransactionAutomatically, this.appAccountToken, - /// External purchase URL for alternative billing (iOS) - this.externalPurchaseUrl, this.quantity, required this.sku, this.withOffer, @@ -2414,8 +2524,6 @@ class RequestSubscriptionIosProps { final bool? andDangerouslyFinishTransactionAutomatically; final String? appAccountToken; - /// External purchase URL for alternative billing (iOS) - final String? externalPurchaseUrl; final int? quantity; final String sku; final DiscountOfferInputIOS? withOffer; @@ -2424,7 +2532,6 @@ class RequestSubscriptionIosProps { return RequestSubscriptionIosProps( andDangerouslyFinishTransactionAutomatically: json['andDangerouslyFinishTransactionAutomatically'] as bool?, appAccountToken: json['appAccountToken'] as String?, - externalPurchaseUrl: json['externalPurchaseUrl'] as String?, quantity: json['quantity'] as int?, sku: json['sku'] as String, withOffer: json['withOffer'] != null ? DiscountOfferInputIOS.fromJson(json['withOffer'] as Map) : null, @@ -2435,7 +2542,6 @@ class RequestSubscriptionIosProps { return { 'andDangerouslyFinishTransactionAutomatically': andDangerouslyFinishTransactionAutomatically, 'appAccountToken': appAccountToken, - 'externalPurchaseUrl': externalPurchaseUrl, 'quantity': quantity, 'sku': sku, 'withOffer': withOffer?.toJson(), @@ -2653,6 +2759,10 @@ abstract class MutationResolver { }); /// Present the App Store code redemption sheet Future presentCodeRedemptionSheetIOS(); + /// Present external purchase custom link with StoreKit UI (iOS 18.2+) + Future presentExternalPurchaseLinkIOS(String url); + /// Present external purchase notice sheet (iOS 18.2+) + Future presentExternalPurchaseNoticeSheetIOS(); /// Initiate a purchase flow; rely on events for final state Future requestPurchase(RequestPurchaseProps params); /// Purchase the promoted product surfaced by the App Store @@ -2679,6 +2789,8 @@ abstract class MutationResolver { /// GraphQL root query operations. abstract class QueryResolver { + /// Check if external purchase notice sheet can be presented (iOS 18.2+) + Future canPresentExternalPurchaseNoticeIOS(); /// Get current StoreKit 2 entitlements (iOS 15+) Future currentEntitlementIOS(String sku); /// Retrieve products or subscriptions from the store @@ -2732,6 +2844,9 @@ abstract class SubscriptionResolver { Future purchaseError(); /// Fires when a purchase completes successfully or a pending purchase resolves Future purchaseUpdated(); + /// Fires when a user selects alternative billing in the User Choice Billing dialog (Android only) + /// Only triggered when the user selects alternative billing instead of Google Play billing + Future userChoiceBillingAndroid(); } // MARK: - Root Operation Helpers @@ -2757,6 +2872,8 @@ typedef MutationInitConnectionHandler = Future Function({ AlternativeBillingModeAndroid? alternativeBillingModeAndroid, }); typedef MutationPresentCodeRedemptionSheetIOSHandler = Future Function(); +typedef MutationPresentExternalPurchaseLinkIOSHandler = Future Function(String url); +typedef MutationPresentExternalPurchaseNoticeSheetIOSHandler = Future Function(); typedef MutationRequestPurchaseHandler = Future Function(RequestPurchaseProps params); typedef MutationRequestPurchaseOnPromotedProductIOSHandler = Future Function(); typedef MutationRestorePurchasesHandler = Future Function(); @@ -2781,6 +2898,8 @@ class MutationHandlers { this.finishTransaction, this.initConnection, this.presentCodeRedemptionSheetIOS, + this.presentExternalPurchaseLinkIOS, + this.presentExternalPurchaseNoticeSheetIOS, this.requestPurchase, this.requestPurchaseOnPromotedProductIOS, this.restorePurchases, @@ -2801,6 +2920,8 @@ class MutationHandlers { final MutationFinishTransactionHandler? finishTransaction; final MutationInitConnectionHandler? initConnection; final MutationPresentCodeRedemptionSheetIOSHandler? presentCodeRedemptionSheetIOS; + final MutationPresentExternalPurchaseLinkIOSHandler? presentExternalPurchaseLinkIOS; + final MutationPresentExternalPurchaseNoticeSheetIOSHandler? presentExternalPurchaseNoticeSheetIOS; final MutationRequestPurchaseHandler? requestPurchase; final MutationRequestPurchaseOnPromotedProductIOSHandler? requestPurchaseOnPromotedProductIOS; final MutationRestorePurchasesHandler? restorePurchases; @@ -2812,6 +2933,7 @@ class MutationHandlers { // MARK: - Query Helpers +typedef QueryCanPresentExternalPurchaseNoticeIOSHandler = Future Function(); typedef QueryCurrentEntitlementIOSHandler = Future Function(String sku); typedef QueryFetchProductsHandler = Future Function({ required List skus, @@ -2841,6 +2963,7 @@ typedef QueryValidateReceiptIOSHandler = Future Func class QueryHandlers { const QueryHandlers({ + this.canPresentExternalPurchaseNoticeIOS, this.currentEntitlementIOS, this.fetchProducts, this.getActiveSubscriptions, @@ -2860,6 +2983,7 @@ class QueryHandlers { this.validateReceiptIOS, }); + final QueryCanPresentExternalPurchaseNoticeIOSHandler? canPresentExternalPurchaseNoticeIOS; final QueryCurrentEntitlementIOSHandler? currentEntitlementIOS; final QueryFetchProductsHandler? fetchProducts; final QueryGetActiveSubscriptionsHandler? getActiveSubscriptions; @@ -2884,15 +3008,18 @@ class QueryHandlers { typedef SubscriptionPromotedProductIOSHandler = Future Function(); typedef SubscriptionPurchaseErrorHandler = Future Function(); typedef SubscriptionPurchaseUpdatedHandler = Future Function(); +typedef SubscriptionUserChoiceBillingAndroidHandler = Future Function(); class SubscriptionHandlers { const SubscriptionHandlers({ this.promotedProductIOS, this.purchaseError, this.purchaseUpdated, + this.userChoiceBillingAndroid, }); final SubscriptionPromotedProductIOSHandler? promotedProductIOS; final SubscriptionPurchaseErrorHandler? purchaseError; final SubscriptionPurchaseUpdatedHandler? purchaseUpdated; + final SubscriptionUserChoiceBillingAndroidHandler? userChoiceBillingAndroid; } diff --git a/src/generated/types.ts b/src/generated/types.ts index 8a38324..e86fdb5 100644 --- a/src/generated/types.ts +++ b/src/generated/types.ts @@ -142,6 +142,25 @@ export enum ErrorCode { UserError = 'user-error' } +/** Result of presenting an external purchase link (iOS 18.2+) */ +export interface ExternalPurchaseLinkResultIOS { + /** Optional error message if the presentation failed */ + error?: (string | null); + /** Whether the user completed the external purchase flow */ + success: boolean; +} + +/** User actions on external purchase notice sheet (iOS 18.2+) */ +export type ExternalPurchaseNoticeAction = 'continue' | 'dismissed'; + +/** Result of presenting external purchase notice sheet (iOS 18.2+) */ +export interface ExternalPurchaseNoticeResultIOS { + /** Optional error message if the presentation failed */ + error?: (string | null); + /** Notice result indicating user action */ + result: ExternalPurchaseNoticeAction; +} + export type FetchProductsResult = Product[] | ProductSubscription[] | null; export type IapEvent = 'purchase-updated' | 'purchase-error' | 'promoted-product-ios'; @@ -194,6 +213,10 @@ export interface Mutation { initConnection: Promise; /** Present the App Store code redemption sheet */ presentCodeRedemptionSheetIOS: Promise; + /** Present external purchase custom link with StoreKit UI (iOS 18.2+) */ + presentExternalPurchaseLinkIOS: Promise; + /** Present external purchase notice sheet (iOS 18.2+) */ + presentExternalPurchaseNoticeSheetIOS: Promise; /** Initiate a purchase flow; rely on events for final state */ requestPurchase?: Promise<(Purchase | Purchase[] | null)>; /** Purchase the promoted product surfaced by the App Store */ @@ -235,6 +258,8 @@ export interface MutationFinishTransactionArgs { export type MutationInitConnectionArgs = (InitConnectionConfig | null) | undefined; +export type MutationPresentExternalPurchaseLinkIosArgs = string; + export type MutationRequestPurchaseArgs = | { /** Per-platform purchase request props */ @@ -490,6 +515,8 @@ export interface PurchaseOptions { export type PurchaseState = 'pending' | 'purchased' | 'failed' | 'restored' | 'deferred' | 'unknown'; export interface Query { + /** Check if external purchase notice sheet can be presented (iOS 18.2+) */ + canPresentExternalPurchaseNoticeIOS: Promise; /** Get current StoreKit 2 entitlements (iOS 15+) */ currentEntitlementIOS?: Promise<(PurchaseIOS | null)>; /** Retrieve products or subscriptions from the store */ @@ -628,8 +655,6 @@ export interface RequestPurchaseIosProps { andDangerouslyFinishTransactionAutomatically?: (boolean | null); /** App account token for user tracking */ appAccountToken?: (string | null); - /** External purchase URL for alternative billing (iOS) */ - externalPurchaseUrl?: (string | null); /** Purchase quantity */ quantity?: (number | null); /** Product SKU */ @@ -683,8 +708,6 @@ export interface RequestSubscriptionAndroidProps { export interface RequestSubscriptionIosProps { andDangerouslyFinishTransactionAutomatically?: (boolean | null); appAccountToken?: (string | null); - /** External purchase URL for alternative billing (iOS) */ - externalPurchaseUrl?: (string | null); quantity?: (number | null); sku: string; withOffer?: (DiscountOfferInputIOS | null); @@ -704,6 +727,11 @@ export interface Subscription { purchaseError: PurchaseError; /** Fires when a purchase completes successfully or a pending purchase resolves */ purchaseUpdated: Purchase; + /** + * Fires when a user selects alternative billing in the User Choice Billing dialog (Android only) + * Only triggered when the user selects alternative billing instead of Google Play billing + */ + userChoiceBillingAndroid: UserChoiceBillingDetails; } @@ -738,10 +766,22 @@ export interface SubscriptionStatusIOS { state: string; } +/** + * User Choice Billing event details (Android) + * Fired when a user selects alternative billing in the User Choice Billing dialog + */ +export interface UserChoiceBillingDetails { + /** Token that must be reported to Google Play within 24 hours */ + externalTransactionToken: string; + /** List of product IDs selected by the user */ + products: string[]; +} + export type VoidResult = void; // -- Query helper types (auto-generated) export type QueryArgsMap = { + canPresentExternalPurchaseNoticeIOS: never; currentEntitlementIOS: QueryCurrentEntitlementIosArgs; fetchProducts: QueryFetchProductsArgs; getActiveSubscriptions: QueryGetActiveSubscriptionsArgs; @@ -786,6 +826,8 @@ export type MutationArgsMap = { finishTransaction: MutationFinishTransactionArgs; initConnection: MutationInitConnectionArgs; presentCodeRedemptionSheetIOS: never; + presentExternalPurchaseLinkIOS: MutationPresentExternalPurchaseLinkIosArgs; + presentExternalPurchaseNoticeSheetIOS: never; requestPurchase: MutationRequestPurchaseArgs; requestPurchaseOnPromotedProductIOS: never; restorePurchases: never; @@ -812,6 +854,7 @@ export type SubscriptionArgsMap = { promotedProductIOS: never; purchaseError: never; purchaseUpdated: never; + userChoiceBillingAndroid: never; }; export type SubscriptionField = diff --git a/src/type-android.graphql b/src/type-android.graphql index 21c3685..4ea4282 100644 --- a/src/type-android.graphql +++ b/src/type-android.graphql @@ -205,3 +205,19 @@ enum AlternativeBillingModeAndroid { ALTERNATIVE_ONLY } +# User Choice Billing +""" +User Choice Billing event details (Android) +Fired when a user selects alternative billing in the User Choice Billing dialog +""" +type UserChoiceBillingDetails { + """ + Token that must be reported to Google Play within 24 hours + """ + externalTransactionToken: String! + """ + List of product IDs selected by the user + """ + products: [String!]! +} + diff --git a/src/type-ios.graphql b/src/type-ios.graphql index fda4527..0d37fb7 100644 --- a/src/type-ios.graphql +++ b/src/type-ios.graphql @@ -187,10 +187,6 @@ input RequestPurchaseIosProps { Discount offer to apply """ withOffer: DiscountOfferInputIOS - """ - External purchase URL for alternative billing (iOS) - """ - externalPurchaseUrl: String } # iOS uses the same props for subscriptions @@ -200,10 +196,6 @@ input RequestSubscriptionIosProps { appAccountToken: String quantity: Int withOffer: DiscountOfferInputIOS - """ - External purchase URL for alternative billing (iOS) - """ - externalPurchaseUrl: String } # iOS Discount Offer (input) @@ -312,3 +304,45 @@ type AppTransaction { appTransactionId: String originalPlatform: String } + +""" +Result of presenting external purchase notice sheet (iOS 18.2+) +""" +type ExternalPurchaseNoticeResultIOS { + """ + Notice result indicating user action + """ + result: ExternalPurchaseNoticeAction! + """ + Optional error message if the presentation failed + """ + error: String +} + +""" +User actions on external purchase notice sheet (iOS 18.2+) +""" +enum ExternalPurchaseNoticeAction { + """ + User chose to continue to external purchase + """ + Continue + """ + User dismissed the notice sheet + """ + Dismissed +} + +""" +Result of presenting an external purchase link (iOS 18.2+) +""" +type ExternalPurchaseLinkResultIOS { + """ + Whether the user completed the external purchase flow + """ + success: Boolean! + """ + Optional error message if the presentation failed + """ + error: String +}