diff --git a/README.md b/README.md index 3c1e723d..7d4aa519 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ This monorepo contains all OpenIAP packages: - **[docs](packages/docs)** - Documentation site at [openiap.dev](https://openiap.dev) - **[spec](packages/gql)** - OpenIAP specification and type generation [![Spec Release](https://img.shields.io/github/v/tag/hyodotdev/openiap?filter=gql-*&label=version&logo=graphql&color=purple)](https://github.com/hyodotdev/openiap/releases?q=gql&expanded=true) -- **[google](packages/google)** - Android library [![Maven Central (Play)](https://img.shields.io/maven-central/v/io.github.hyochan.openiap/openiap-google?label=Play%20Store)](https://central.sonatype.com/artifact/io.github.hyochan.openiap/openiap-google) [![Maven Central (Horizon)](https://img.shields.io/maven-central/v/io.github.hyochan.openiap/openiap-google-horizon?label=Meta%20Horizon)](https://central.sonatype.com/artifact/io.github.hyochan.openiap/openiap-google-horizon) [![CI](https://github.com/hyodotdev/openiap/actions/workflows/ci.yml/badge.svg)](https://github.com/hyodotdev/openiap/actions/workflows/ci.yml) -- **[apple](packages/apple)** - iOS/macOS library [![Swift Package](https://img.shields.io/github/v/tag/hyodotdev/openiap?filter=2.*&label=version&logo=swift&color=orange)](https://github.com/hyodotdev/openiap/releases?q=Apple&expanded=true) [![CocoaPods](https://img.shields.io/cocoapods/v/openiap?color=E35A5F&logo=cocoapods)](https://cocoapods.org/pods/openiap) [![CI](https://github.com/hyodotdev/openiap/actions/workflows/ci.yml/badge.svg)](https://github.com/hyodotdev/openiap/actions/workflows/ci.yml) +- **[google](packages/google)** - Android library [![Maven Central (Play)](https://img.shields.io/maven-central/v/io.github.hyochan.openiap/openiap-google?label=Play%20Store)](https://central.sonatype.com/artifact/io.github.hyochan.openiap/openiap-google) [![Maven Central (Horizon)](https://img.shields.io/maven-central/v/io.github.hyochan.openiap/openiap-google-horizon?label=Meta%20Horizon)](https://central.sonatype.com/artifact/io.github.hyochan.openiap/openiap-google-horizon) [![CI](https://github.com/hyodotdev/openiap/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/hyodotdev/openiap/actions/workflows/ci.yml?query=branch%3Amain) +- **[apple](packages/apple)** - iOS/macOS library [![Swift Package](https://img.shields.io/github/v/tag/hyodotdev/openiap?filter=2.*&label=version&logo=swift&color=orange)](https://github.com/hyodotdev/openiap/releases?q=Apple&expanded=true) [![CocoaPods](https://img.shields.io/cocoapods/v/openiap?color=E35A5F&logo=cocoapods)](https://cocoapods.org/pods/openiap) [![CI](https://github.com/hyodotdev/openiap/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/hyodotdev/openiap/actions/workflows/ci.yml?query=branch%3Amain) ## Libraries diff --git a/knowledge/_claude-context/context.md b/knowledge/_claude-context/context.md index 22cceeea..e90c018b 100644 --- a/knowledge/_claude-context/context.md +++ b/knowledge/_claude-context/context.md @@ -1,7 +1,7 @@ # OpenIAP Project Context > **Auto-generated for Claude Code** -> Last updated: 2026-04-16T13:24:54.898Z +> Last updated: 2026-04-16T13:44:54.257Z > > Usage: `claude --context knowledge/_claude-context/context.md` diff --git a/libraries/expo-iap/src/types.ts b/libraries/expo-iap/src/types.ts index 9a4c0338..4afb30b7 100644 --- a/libraries/expo-iap/src/types.ts +++ b/libraries/expo-iap/src/types.ts @@ -36,6 +36,56 @@ export interface ActiveSubscription { willExpireSoon?: (boolean | null); } +/** + * Advanced Commerce metadata from a transaction (iOS 18.4+). + * Contains item details, tax information, and refund data for purchases + * made through the Advanced Commerce API using generic SKUs. + * Only present for transactions that use the Advanced Commerce API. + */ +export interface AdvancedCommerceInfoIOS { + /** Optional description */ + description?: (string | null); + /** Optional display name */ + displayName?: (string | null); + /** Estimated tax amount (decimal string) */ + estimatedTax?: (string | null); + /** The items purchased as part of this transaction */ + items: AdvancedCommerceItemIOS[]; + /** Request reference identifier for tracking */ + requestReferenceId?: (string | null); + /** Tax code for the transaction */ + taxCode?: (string | null); + /** Price excluding tax (decimal string) */ + taxExclusivePrice?: (string | null); + /** Tax rate applied (decimal string) */ + taxRate?: (string | null); +} + +/** Details of an Advanced Commerce item (iOS 18.4+). */ +export interface AdvancedCommerceItemDetailsIOS { + /** JSON representation of the item details */ + jsonRepresentation?: (string | null); +} + +/** + * An item purchased through the Advanced Commerce API (iOS 18.4+). + * Represents a developer-defined product within a generic SKU transaction. + */ +export interface AdvancedCommerceItemIOS { + /** The item's detail information */ + details?: (AdvancedCommerceItemDetailsIOS | null); + /** Refunds issued for this item, if any */ + refunds?: (AdvancedCommerceRefundIOS[] | null); + /** Date access to this item was revoked (milliseconds since epoch) */ + revocationDate?: (number | null); +} + +/** Refund information for an Advanced Commerce item (iOS 18.4+). */ +export interface AdvancedCommerceRefundIOS { + /** JSON representation of the refund details */ + jsonRepresentation?: (string | null); +} + /** * Alternative billing mode for Android * Controls which billing system is used @@ -1112,6 +1162,12 @@ export interface PurchaseError { } export interface PurchaseIOS extends PurchaseCommon { + /** + * Advanced Commerce API metadata (iOS 18.4+). + * Present only for transactions that use the Advanced Commerce API. + * Contains item details, tax information, and refund data for generic SKU purchases. + */ + advancedCommerceInfoIOS?: (AdvancedCommerceInfoIOS | null); appAccountToken?: (string | null); appBundleIdIOS?: (string | null); countryCodeIOS?: (string | null); @@ -1188,6 +1244,13 @@ export interface Query { fetchProducts: Promise<(ProductOrSubscription[] | Product[] | ProductSubscription[] | null)>; /** Get active subscriptions (filters by subscriptionIds when provided) */ getActiveSubscriptions: Promise; + /** + * Get the full StoreKit 2 transaction history as PurchaseIOS values. + * Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + * for finished consumables to be included (iOS 18+). + * Unlike getAvailablePurchases, always returns the iOS-specific PurchaseIOS shape. + */ + getAllTransactionsIOS: Promise; /** Fetch the current app transaction (iOS 16+) */ getAppTransactionIOS?: Promise<(AppTransaction | null)>; /** Get all available purchases for the current user */ @@ -1901,6 +1964,7 @@ export type QueryArgsMap = { currentEntitlementIOS: QueryCurrentEntitlementIosArgs; fetchProducts: QueryFetchProductsArgs; getActiveSubscriptions: QueryGetActiveSubscriptionsArgs; + getAllTransactionsIOS: never; getAppTransactionIOS: never; getAvailablePurchases: QueryGetAvailablePurchasesArgs; getExternalPurchaseCustomLinkTokenIOS: QueryGetExternalPurchaseCustomLinkTokenIosArgs; diff --git a/libraries/flutter_inapp_purchase/lib/types.dart b/libraries/flutter_inapp_purchase/lib/types.dart index 1b65274a..ea75b5b3 100644 --- a/libraries/flutter_inapp_purchase/lib/types.dart +++ b/libraries/flutter_inapp_purchase/lib/types.dart @@ -1048,6 +1048,147 @@ class ActiveSubscription { } } +/// Advanced Commerce metadata from a transaction (iOS 18.4+). +/// Contains item details, tax information, and refund data for purchases +/// made through the Advanced Commerce API using generic SKUs. +/// Only present for transactions that use the Advanced Commerce API. +class AdvancedCommerceInfoIOS { + const AdvancedCommerceInfoIOS({ + this.description, + this.displayName, + this.estimatedTax, + required this.items, + this.requestReferenceId, + this.taxCode, + this.taxExclusivePrice, + this.taxRate, + }); + + /// Optional description + final String? description; + /// Optional display name + final String? displayName; + /// Estimated tax amount (decimal string) + final String? estimatedTax; + /// The items purchased as part of this transaction + final List items; + /// Request reference identifier for tracking + final String? requestReferenceId; + /// Tax code for the transaction + final String? taxCode; + /// Price excluding tax (decimal string) + final String? taxExclusivePrice; + /// Tax rate applied (decimal string) + final String? taxRate; + + factory AdvancedCommerceInfoIOS.fromJson(Map json) { + return AdvancedCommerceInfoIOS( + description: json['description'] as String?, + displayName: json['displayName'] as String?, + estimatedTax: json['estimatedTax'] as String?, + items: (json['items'] as List).map((e) => AdvancedCommerceItemIOS.fromJson(e as Map)).toList(), + requestReferenceId: json['requestReferenceId'] as String?, + taxCode: json['taxCode'] as String?, + taxExclusivePrice: json['taxExclusivePrice'] as String?, + taxRate: json['taxRate'] as String?, + ); + } + + Map toJson() { + return { + '__typename': 'AdvancedCommerceInfoIOS', + 'description': description, + 'displayName': displayName, + 'estimatedTax': estimatedTax, + 'items': items.map((e) => e.toJson()).toList(), + 'requestReferenceId': requestReferenceId, + 'taxCode': taxCode, + 'taxExclusivePrice': taxExclusivePrice, + 'taxRate': taxRate, + }; + } +} + +/// Details of an Advanced Commerce item (iOS 18.4+). +class AdvancedCommerceItemDetailsIOS { + const AdvancedCommerceItemDetailsIOS({ + this.jsonRepresentation, + }); + + /// JSON representation of the item details + final String? jsonRepresentation; + + factory AdvancedCommerceItemDetailsIOS.fromJson(Map json) { + return AdvancedCommerceItemDetailsIOS( + jsonRepresentation: json['jsonRepresentation'] as String?, + ); + } + + Map toJson() { + return { + '__typename': 'AdvancedCommerceItemDetailsIOS', + 'jsonRepresentation': jsonRepresentation, + }; + } +} + +/// An item purchased through the Advanced Commerce API (iOS 18.4+). +/// Represents a developer-defined product within a generic SKU transaction. +class AdvancedCommerceItemIOS { + const AdvancedCommerceItemIOS({ + this.details, + this.refunds, + this.revocationDate, + }); + + /// The item's detail information + final AdvancedCommerceItemDetailsIOS? details; + /// Refunds issued for this item, if any + final List? refunds; + /// Date access to this item was revoked (milliseconds since epoch) + final double? revocationDate; + + factory AdvancedCommerceItemIOS.fromJson(Map json) { + return AdvancedCommerceItemIOS( + details: json['details'] != null ? AdvancedCommerceItemDetailsIOS.fromJson(json['details'] as Map) : null, + refunds: (json['refunds'] as List?) == null ? null : (json['refunds'] as List?)!.map((e) => AdvancedCommerceRefundIOS.fromJson(e as Map)).toList(), + revocationDate: (json['revocationDate'] as num?)?.toDouble(), + ); + } + + Map toJson() { + return { + '__typename': 'AdvancedCommerceItemIOS', + 'details': details?.toJson(), + 'refunds': refunds == null ? null : refunds!.map((e) => e.toJson()).toList(), + 'revocationDate': revocationDate, + }; + } +} + +/// Refund information for an Advanced Commerce item (iOS 18.4+). +class AdvancedCommerceRefundIOS { + const AdvancedCommerceRefundIOS({ + this.jsonRepresentation, + }); + + /// JSON representation of the refund details + final String? jsonRepresentation; + + factory AdvancedCommerceRefundIOS.fromJson(Map json) { + return AdvancedCommerceRefundIOS( + jsonRepresentation: json['jsonRepresentation'] as String?, + ); + } + + Map toJson() { + return { + '__typename': 'AdvancedCommerceRefundIOS', + 'jsonRepresentation': jsonRepresentation, + }; + } +} + class AppTransaction { const AppTransaction({ required this.appId, @@ -2612,6 +2753,7 @@ class PurchaseError { class PurchaseIOS extends Purchase implements PurchaseCommon { const PurchaseIOS({ + this.advancedCommerceInfoIOS, this.appAccountToken, this.appBundleIdIOS, this.countryCodeIOS, @@ -2649,6 +2791,10 @@ class PurchaseIOS extends Purchase implements PurchaseCommon { this.isAlternativeBilling, }); + /// Advanced Commerce API metadata (iOS 18.4+). + /// Present only for transactions that use the Advanced Commerce API. + /// Contains item details, tax information, and refund data for generic SKU purchases. + final AdvancedCommerceInfoIOS? advancedCommerceInfoIOS; final String? appAccountToken; final String? appBundleIdIOS; final String? countryCodeIOS; @@ -2688,6 +2834,7 @@ class PurchaseIOS extends Purchase implements PurchaseCommon { factory PurchaseIOS.fromJson(Map json) { return PurchaseIOS( + advancedCommerceInfoIOS: json['advancedCommerceInfoIOS'] != null ? AdvancedCommerceInfoIOS.fromJson(json['advancedCommerceInfoIOS'] as Map) : null, appAccountToken: json['appAccountToken'] as String?, appBundleIdIOS: json['appBundleIdIOS'] as String?, countryCodeIOS: json['countryCodeIOS'] as String?, @@ -2730,6 +2877,7 @@ class PurchaseIOS extends Purchase implements PurchaseCommon { Map toJson() { return { '__typename': 'PurchaseIOS', + 'advancedCommerceInfoIOS': advancedCommerceInfoIOS?.toJson(), 'appAccountToken': appAccountToken, 'appBundleIdIOS': appBundleIdIOS, 'countryCodeIOS': countryCodeIOS, @@ -4830,6 +4978,11 @@ abstract class QueryResolver { }); /// Get active subscriptions (filters by subscriptionIds when provided) Future> getActiveSubscriptions([List? subscriptionIds]); + /// Get the full StoreKit 2 transaction history as PurchaseIOS values. + /// Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + /// for finished consumables to be included (iOS 18+). + /// Unlike getAvailablePurchases, always returns the iOS-specific PurchaseIOS shape. + Future> getAllTransactionsIOS(); /// Fetch the current app transaction (iOS 16+) Future getAppTransactionIOS(); /// Get all available purchases for the current user @@ -5030,6 +5183,7 @@ typedef QueryFetchProductsHandler = Future Function({ ProductQueryType? type, }); typedef QueryGetActiveSubscriptionsHandler = Future> Function([List? subscriptionIds]); +typedef QueryGetAllTransactionsIOSHandler = Future> Function(); typedef QueryGetAppTransactionIOSHandler = Future Function(); typedef QueryGetAvailablePurchasesHandler = Future> Function({ bool? alsoPublishToEventListenerIOS, @@ -5061,6 +5215,7 @@ class QueryHandlers { this.currentEntitlementIOS, this.fetchProducts, this.getActiveSubscriptions, + this.getAllTransactionsIOS, this.getAppTransactionIOS, this.getAvailablePurchases, this.getExternalPurchaseCustomLinkTokenIOS, @@ -5083,6 +5238,7 @@ class QueryHandlers { final QueryCurrentEntitlementIOSHandler? currentEntitlementIOS; final QueryFetchProductsHandler? fetchProducts; final QueryGetActiveSubscriptionsHandler? getActiveSubscriptions; + final QueryGetAllTransactionsIOSHandler? getAllTransactionsIOS; final QueryGetAppTransactionIOSHandler? getAppTransactionIOS; final QueryGetAvailablePurchasesHandler? getAvailablePurchases; final QueryGetExternalPurchaseCustomLinkTokenIOSHandler? getExternalPurchaseCustomLinkTokenIOS; diff --git a/libraries/godot-iap/addons/godot-iap/types.gd b/libraries/godot-iap/addons/godot-iap/types.gd index 88dbdb17..f1b51f5a 100644 --- a/libraries/godot-iap/addons/godot-iap/types.gd +++ b/libraries/godot-iap/addons/godot-iap/types.gd @@ -384,6 +384,161 @@ class ActiveSubscription: dict["renewalInfoIOS"] = renewal_info_ios return dict +## Advanced Commerce metadata from a transaction (iOS 18.4+). Contains item details, tax information, and refund data for purchases made through the Advanced Commerce API using generic SKUs. Only present for transactions that use the Advanced Commerce API. +class AdvancedCommerceInfoIOS: + ## The items purchased as part of this transaction + var items: Array[AdvancedCommerceItemIOS] = [] + ## Request reference identifier for tracking + var request_reference_id: Variant = null + ## Tax code for the transaction + var tax_code: Variant = null + ## Price excluding tax (decimal string) + var tax_exclusive_price: Variant = null + ## Estimated tax amount (decimal string) + var estimated_tax: Variant = null + ## Tax rate applied (decimal string) + var tax_rate: Variant = null + ## Optional display name + var display_name: Variant = null + ## Optional description + var description: Variant = null + + static func from_dict(data: Dictionary) -> AdvancedCommerceInfoIOS: + var obj = AdvancedCommerceInfoIOS.new() + if data.has("items") and data["items"] != null: + var arr = [] + for item in data["items"]: + if item is Dictionary: + arr.append(AdvancedCommerceItemIOS.from_dict(item)) + else: + arr.append(item) + obj.items = arr + if data.has("requestReferenceId") and data["requestReferenceId"] != null: + obj.request_reference_id = data["requestReferenceId"] + if data.has("taxCode") and data["taxCode"] != null: + obj.tax_code = data["taxCode"] + if data.has("taxExclusivePrice") and data["taxExclusivePrice"] != null: + obj.tax_exclusive_price = data["taxExclusivePrice"] + if data.has("estimatedTax") and data["estimatedTax"] != null: + obj.estimated_tax = data["estimatedTax"] + if data.has("taxRate") and data["taxRate"] != null: + obj.tax_rate = data["taxRate"] + if data.has("displayName") and data["displayName"] != null: + obj.display_name = data["displayName"] + if data.has("description") and data["description"] != null: + obj.description = data["description"] + return obj + + func to_dict() -> Dictionary: + var dict = {} + if items != null: + var arr = [] + for item in items: + if item != null and item.has_method("to_dict"): + arr.append(item.to_dict()) + else: + arr.append(item) + dict["items"] = arr + else: + dict["items"] = null + if request_reference_id != null: + dict["requestReferenceId"] = request_reference_id + if tax_code != null: + dict["taxCode"] = tax_code + if tax_exclusive_price != null: + dict["taxExclusivePrice"] = tax_exclusive_price + if estimated_tax != null: + dict["estimatedTax"] = estimated_tax + if tax_rate != null: + dict["taxRate"] = tax_rate + if display_name != null: + dict["displayName"] = display_name + if description != null: + dict["description"] = description + return dict + +## Details of an Advanced Commerce item (iOS 18.4+). +class AdvancedCommerceItemDetailsIOS: + ## JSON representation of the item details + var json_representation: Variant = null + + static func from_dict(data: Dictionary) -> AdvancedCommerceItemDetailsIOS: + var obj = AdvancedCommerceItemDetailsIOS.new() + if data.has("jsonRepresentation") and data["jsonRepresentation"] != null: + obj.json_representation = data["jsonRepresentation"] + return obj + + func to_dict() -> Dictionary: + var dict = {} + if json_representation != null: + dict["jsonRepresentation"] = json_representation + return dict + +## An item purchased through the Advanced Commerce API (iOS 18.4+). Represents a developer-defined product within a generic SKU transaction. +class AdvancedCommerceItemIOS: + ## The item's detail information + var details: AdvancedCommerceItemDetailsIOS + ## Refunds issued for this item, if any + var refunds: Array[AdvancedCommerceRefundIOS] = [] + ## Date access to this item was revoked (milliseconds since epoch) + var revocation_date: Variant = null + + static func from_dict(data: Dictionary) -> AdvancedCommerceItemIOS: + var obj = AdvancedCommerceItemIOS.new() + if data.has("details") and data["details"] != null: + if data["details"] is Dictionary: + obj.details = AdvancedCommerceItemDetailsIOS.from_dict(data["details"]) + else: + obj.details = data["details"] + if data.has("refunds") and data["refunds"] != null: + var arr = [] + for item in data["refunds"]: + if item is Dictionary: + arr.append(AdvancedCommerceRefundIOS.from_dict(item)) + else: + arr.append(item) + obj.refunds = arr + if data.has("revocationDate") and data["revocationDate"] != null: + obj.revocation_date = data["revocationDate"] + return obj + + func to_dict() -> Dictionary: + var dict = {} + if details != null and details.has_method("to_dict"): + dict["details"] = details.to_dict() + else: + dict["details"] = details + if refunds != null: + var arr = [] + for item in refunds: + if item != null and item.has_method("to_dict"): + arr.append(item.to_dict()) + else: + arr.append(item) + dict["refunds"] = arr + else: + dict["refunds"] = null + if revocation_date != null: + dict["revocationDate"] = revocation_date + return dict + +## Refund information for an Advanced Commerce item (iOS 18.4+). +class AdvancedCommerceRefundIOS: + ## JSON representation of the refund details + var json_representation: Variant = null + + static func from_dict(data: Dictionary) -> AdvancedCommerceRefundIOS: + var obj = AdvancedCommerceRefundIOS.new() + if data.has("jsonRepresentation") and data["jsonRepresentation"] != null: + obj.json_representation = data["jsonRepresentation"] + return obj + + func to_dict() -> Dictionary: + var dict = {} + if json_representation != null: + dict["jsonRepresentation"] = json_representation + return dict + class AppTransaction: var bundle_id: String = "" var app_version: String = "" @@ -2130,6 +2285,8 @@ class PurchaseIOS: var currency_symbol_ios: Variant = null var country_code_ios: Variant = null var renewal_info_ios: RenewalInfoIOS + ## Advanced Commerce API metadata (iOS 18.4+). + var advanced_commerce_info_ios: AdvancedCommerceInfoIOS static func from_dict(data: Dictionary) -> PurchaseIOS: var obj = PurchaseIOS.new() @@ -2219,6 +2376,11 @@ class PurchaseIOS: obj.renewal_info_ios = RenewalInfoIOS.from_dict(data["renewalInfoIOS"]) else: obj.renewal_info_ios = data["renewalInfoIOS"] + if data.has("advancedCommerceInfoIOS") and data["advancedCommerceInfoIOS"] != null: + if data["advancedCommerceInfoIOS"] is Dictionary: + obj.advanced_commerce_info_ios = AdvancedCommerceInfoIOS.from_dict(data["advancedCommerceInfoIOS"]) + else: + obj.advanced_commerce_info_ios = data["advancedCommerceInfoIOS"] return obj func to_dict() -> Dictionary: @@ -2294,6 +2456,10 @@ class PurchaseIOS: dict["renewalInfoIOS"] = renewal_info_ios.to_dict() else: dict["renewalInfoIOS"] = renewal_info_ios + if advanced_commerce_info_ios != null and advanced_commerce_info_ios.has_method("to_dict"): + dict["advancedCommerceInfoIOS"] = advanced_commerce_info_ios.to_dict() + else: + dict["advancedCommerceInfoIOS"] = advanced_commerce_info_ios return dict class PurchaseOfferIOS: @@ -4934,6 +5100,15 @@ class Query: const return_type = "AppTransaction" const is_array = false + ## Get the full StoreKit 2 transaction history as PurchaseIOS values. + class getAllTransactionsIOSField: + const name = "getAllTransactionsIOS" + const snake_name = "get_all_transactions_ios" + class Args: + pass + const return_type = "PurchaseIOS" + const is_array = true + ## Validate a receipt for a specific product class validateReceiptIOSField: const name = "validateReceiptIOS" @@ -5507,6 +5682,10 @@ static func get_receipt_data_ios_args() -> Dictionary: static func get_app_transaction_ios_args() -> Dictionary: return {} +## Get the full StoreKit 2 transaction history as PurchaseIOS values. +static func get_all_transactions_ios_args() -> Dictionary: + return {} + ## Validate a receipt for a specific product static func validate_receipt_ios_args(options: VerifyPurchaseProps) -> Dictionary: var args = {} diff --git a/libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt b/libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt index 224ee849..23ffe1f8 100644 --- a/libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt +++ b/libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt @@ -515,6 +515,8 @@ internal class InAppPurchaseAndroid : KmpInAppPurchase, Application.ActivityLife override suspend fun getPendingTransactionsIOS(): List = emptyList() + override suspend fun getAllTransactionsIOS(): List = emptyList() + override suspend fun getReceiptDataIOS(): String? = null override suspend fun getTransactionJwsIOS(sku: String): String? = null diff --git a/libraries/kmp-iap/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/openiap/Types.kt b/libraries/kmp-iap/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/openiap/Types.kt index 5ad6c0e7..becd8523 100644 --- a/libraries/kmp-iap/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/openiap/Types.kt +++ b/libraries/kmp-iap/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/openiap/Types.kt @@ -1171,6 +1171,160 @@ public data class ActiveSubscription( ) } +/** + * Advanced Commerce metadata from a transaction (iOS 18.4+). + * Contains item details, tax information, and refund data for purchases + * made through the Advanced Commerce API using generic SKUs. + * Only present for transactions that use the Advanced Commerce API. + */ +public data class AdvancedCommerceInfoIOS( + /** + * Optional description + */ + val description: String? = null, + /** + * Optional display name + */ + val displayName: String? = null, + /** + * Estimated tax amount (decimal string) + */ + val estimatedTax: String? = null, + /** + * The items purchased as part of this transaction + */ + val items: List, + /** + * Request reference identifier for tracking + */ + val requestReferenceId: String? = null, + /** + * Tax code for the transaction + */ + val taxCode: String? = null, + /** + * Price excluding tax (decimal string) + */ + val taxExclusivePrice: String? = null, + /** + * Tax rate applied (decimal string) + */ + val taxRate: String? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceInfoIOS { + return AdvancedCommerceInfoIOS( + description = json["description"] as? String, + displayName = json["displayName"] as? String, + estimatedTax = json["estimatedTax"] as? String, + items = (json["items"] as? List<*>)?.mapNotNull { (it as? Map)?.let { AdvancedCommerceItemIOS.fromJson(it) } ?: throw IllegalArgumentException("Missing required object for AdvancedCommerceItemIOS") } ?: emptyList(), + requestReferenceId = json["requestReferenceId"] as? String, + taxCode = json["taxCode"] as? String, + taxExclusivePrice = json["taxExclusivePrice"] as? String, + taxRate = json["taxRate"] as? String, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceInfoIOS", + "description" to description, + "displayName" to displayName, + "estimatedTax" to estimatedTax, + "items" to items.map { it.toJson() }, + "requestReferenceId" to requestReferenceId, + "taxCode" to taxCode, + "taxExclusivePrice" to taxExclusivePrice, + "taxRate" to taxRate, + ) +} + +/** + * Details of an Advanced Commerce item (iOS 18.4+). + */ +public data class AdvancedCommerceItemDetailsIOS( + /** + * JSON representation of the item details + */ + val jsonRepresentation: String? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceItemDetailsIOS { + return AdvancedCommerceItemDetailsIOS( + jsonRepresentation = json["jsonRepresentation"] as? String, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceItemDetailsIOS", + "jsonRepresentation" to jsonRepresentation, + ) +} + +/** + * An item purchased through the Advanced Commerce API (iOS 18.4+). + * Represents a developer-defined product within a generic SKU transaction. + */ +public data class AdvancedCommerceItemIOS( + /** + * The item's detail information + */ + val details: AdvancedCommerceItemDetailsIOS? = null, + /** + * Refunds issued for this item, if any + */ + val refunds: List? = null, + /** + * Date access to this item was revoked (milliseconds since epoch) + */ + val revocationDate: Double? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceItemIOS { + return AdvancedCommerceItemIOS( + details = (json["details"] as? Map)?.let { AdvancedCommerceItemDetailsIOS.fromJson(it) }, + refunds = (json["refunds"] as? List<*>)?.mapNotNull { (it as? Map)?.let { AdvancedCommerceRefundIOS.fromJson(it) } ?: throw IllegalArgumentException("Missing required object for AdvancedCommerceRefundIOS") }, + revocationDate = (json["revocationDate"] as? Number)?.toDouble(), + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceItemIOS", + "details" to details?.toJson(), + "refunds" to refunds?.map { it.toJson() }, + "revocationDate" to revocationDate, + ) +} + +/** + * Refund information for an Advanced Commerce item (iOS 18.4+). + */ +public data class AdvancedCommerceRefundIOS( + /** + * JSON representation of the refund details + */ + val jsonRepresentation: String? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceRefundIOS { + return AdvancedCommerceRefundIOS( + jsonRepresentation = json["jsonRepresentation"] as? String, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceRefundIOS", + "jsonRepresentation" to jsonRepresentation, + ) +} + public data class AppTransaction( val appId: Double, val appTransactionId: String? = null, @@ -2656,6 +2810,12 @@ public data class PurchaseError( } public data class PurchaseIOS( + /** + * Advanced Commerce API metadata (iOS 18.4+). + * Present only for transactions that use the Advanced Commerce API. + * Contains item details, tax information, and refund data for generic SKU purchases. + */ + val advancedCommerceInfoIOS: AdvancedCommerceInfoIOS? = null, val appAccountToken: String? = null, val appBundleIdIOS: String? = null, val countryCodeIOS: String? = null, @@ -2698,6 +2858,7 @@ public data class PurchaseIOS( companion object { fun fromJson(json: Map): PurchaseIOS { return PurchaseIOS( + advancedCommerceInfoIOS = (json["advancedCommerceInfoIOS"] as? Map)?.let { AdvancedCommerceInfoIOS.fromJson(it) }, appAccountToken = json["appAccountToken"] as? String, appBundleIdIOS = json["appBundleIdIOS"] as? String, countryCodeIOS = json["countryCodeIOS"] as? String, @@ -2738,6 +2899,7 @@ public data class PurchaseIOS( override fun toJson(): Map = mapOf( "__typename" to "PurchaseIOS", + "advancedCommerceInfoIOS" to advancedCommerceInfoIOS?.toJson(), "appAccountToken" to appAccountToken, "appBundleIdIOS" to appBundleIdIOS, "countryCodeIOS" to countryCodeIOS, @@ -4838,6 +5000,13 @@ public interface QueryResolver { * Get active subscriptions (filters by subscriptionIds when provided) */ suspend fun getActiveSubscriptions(subscriptionIds: List? = null): List + /** + * Get the full StoreKit 2 transaction history as PurchaseIOS values. + * Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + * for finished consumables to be included (iOS 18+). + * Unlike getAvailablePurchases, always returns the iOS-specific PurchaseIOS shape. + */ + suspend fun getAllTransactionsIOS(): List /** * Fetch the current app transaction (iOS 16+) */ @@ -5019,6 +5188,7 @@ public typealias QueryCanPresentExternalPurchaseNoticeIOSHandler = suspend () -> public typealias QueryCurrentEntitlementIOSHandler = suspend (sku: String) -> PurchaseIOS? public typealias QueryFetchProductsHandler = suspend (params: ProductRequest) -> FetchProductsResult public typealias QueryGetActiveSubscriptionsHandler = suspend (subscriptionIds: List?) -> List +public typealias QueryGetAllTransactionsIOSHandler = suspend () -> List public typealias QueryGetAppTransactionIOSHandler = suspend () -> AppTransaction? public typealias QueryGetAvailablePurchasesHandler = suspend (options: PurchaseOptions?) -> List public typealias QueryGetExternalPurchaseCustomLinkTokenIOSHandler = suspend (tokenType: ExternalPurchaseCustomLinkTokenTypeIOS) -> ExternalPurchaseCustomLinkTokenResultIOS @@ -5041,6 +5211,7 @@ public data class QueryHandlers( val currentEntitlementIOS: QueryCurrentEntitlementIOSHandler? = null, val fetchProducts: QueryFetchProductsHandler? = null, val getActiveSubscriptions: QueryGetActiveSubscriptionsHandler? = null, + val getAllTransactionsIOS: QueryGetAllTransactionsIOSHandler? = null, val getAppTransactionIOS: QueryGetAppTransactionIOSHandler? = null, val getAvailablePurchases: QueryGetAvailablePurchasesHandler? = null, val getExternalPurchaseCustomLinkTokenIOS: QueryGetExternalPurchaseCustomLinkTokenIOSHandler? = null, diff --git a/libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt b/libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt index e89d4dfa..9131ce60 100644 --- a/libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt +++ b/libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt @@ -411,6 +411,13 @@ internal class InAppPurchaseIOS : KmpInAppPurchase { } } + // TODO: Wire to ObjC bridge once getAllTransactionsIOSWithCompletion is available in consumed CocoaPods artifacts. + override suspend fun getAllTransactionsIOS(): List { + throw UnsupportedOperationException( + "getAllTransactionsIOS is not available in this kmp-iap iOS build yet" + ) + } + override suspend fun getReceiptDataIOS(): String? = suspendCoroutine { continuation -> openIapModule.getReceiptDataIOSWithCompletion { result, error -> if (error != null) { diff --git a/libraries/react-native-iap/src/types.ts b/libraries/react-native-iap/src/types.ts index 9a4c0338..4afb30b7 100644 --- a/libraries/react-native-iap/src/types.ts +++ b/libraries/react-native-iap/src/types.ts @@ -36,6 +36,56 @@ export interface ActiveSubscription { willExpireSoon?: (boolean | null); } +/** + * Advanced Commerce metadata from a transaction (iOS 18.4+). + * Contains item details, tax information, and refund data for purchases + * made through the Advanced Commerce API using generic SKUs. + * Only present for transactions that use the Advanced Commerce API. + */ +export interface AdvancedCommerceInfoIOS { + /** Optional description */ + description?: (string | null); + /** Optional display name */ + displayName?: (string | null); + /** Estimated tax amount (decimal string) */ + estimatedTax?: (string | null); + /** The items purchased as part of this transaction */ + items: AdvancedCommerceItemIOS[]; + /** Request reference identifier for tracking */ + requestReferenceId?: (string | null); + /** Tax code for the transaction */ + taxCode?: (string | null); + /** Price excluding tax (decimal string) */ + taxExclusivePrice?: (string | null); + /** Tax rate applied (decimal string) */ + taxRate?: (string | null); +} + +/** Details of an Advanced Commerce item (iOS 18.4+). */ +export interface AdvancedCommerceItemDetailsIOS { + /** JSON representation of the item details */ + jsonRepresentation?: (string | null); +} + +/** + * An item purchased through the Advanced Commerce API (iOS 18.4+). + * Represents a developer-defined product within a generic SKU transaction. + */ +export interface AdvancedCommerceItemIOS { + /** The item's detail information */ + details?: (AdvancedCommerceItemDetailsIOS | null); + /** Refunds issued for this item, if any */ + refunds?: (AdvancedCommerceRefundIOS[] | null); + /** Date access to this item was revoked (milliseconds since epoch) */ + revocationDate?: (number | null); +} + +/** Refund information for an Advanced Commerce item (iOS 18.4+). */ +export interface AdvancedCommerceRefundIOS { + /** JSON representation of the refund details */ + jsonRepresentation?: (string | null); +} + /** * Alternative billing mode for Android * Controls which billing system is used @@ -1112,6 +1162,12 @@ export interface PurchaseError { } export interface PurchaseIOS extends PurchaseCommon { + /** + * Advanced Commerce API metadata (iOS 18.4+). + * Present only for transactions that use the Advanced Commerce API. + * Contains item details, tax information, and refund data for generic SKU purchases. + */ + advancedCommerceInfoIOS?: (AdvancedCommerceInfoIOS | null); appAccountToken?: (string | null); appBundleIdIOS?: (string | null); countryCodeIOS?: (string | null); @@ -1188,6 +1244,13 @@ export interface Query { fetchProducts: Promise<(ProductOrSubscription[] | Product[] | ProductSubscription[] | null)>; /** Get active subscriptions (filters by subscriptionIds when provided) */ getActiveSubscriptions: Promise; + /** + * Get the full StoreKit 2 transaction history as PurchaseIOS values. + * Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + * for finished consumables to be included (iOS 18+). + * Unlike getAvailablePurchases, always returns the iOS-specific PurchaseIOS shape. + */ + getAllTransactionsIOS: Promise; /** Fetch the current app transaction (iOS 16+) */ getAppTransactionIOS?: Promise<(AppTransaction | null)>; /** Get all available purchases for the current user */ @@ -1901,6 +1964,7 @@ export type QueryArgsMap = { currentEntitlementIOS: QueryCurrentEntitlementIosArgs; fetchProducts: QueryFetchProductsArgs; getActiveSubscriptions: QueryGetActiveSubscriptionsArgs; + getAllTransactionsIOS: never; getAppTransactionIOS: never; getAvailablePurchases: QueryGetAvailablePurchasesArgs; getExternalPurchaseCustomLinkTokenIOS: QueryGetExternalPurchaseCustomLinkTokenIosArgs; diff --git a/packages/apple/Sources/Helpers/StoreKitTypesBridge.swift b/packages/apple/Sources/Helpers/StoreKitTypesBridge.swift index 85f28487..ba08e336 100644 --- a/packages/apple/Sources/Helpers/StoreKitTypesBridge.swift +++ b/packages/apple/Sources/Helpers/StoreKitTypesBridge.swift @@ -129,8 +129,10 @@ enum StoreKitTypesBridge { let ownershipDescription = ownershipTypeDescription(from: transaction.ownershipType) let reasonDetails = transactionReasonDetails(from: transaction) + let advancedCommerceInfo: AdvancedCommerceInfoIOS? = extractAdvancedCommerceInfo(from: transaction) return PurchaseIOS( + advancedCommerceInfoIOS: advancedCommerceInfo, appAccountToken: transaction.appAccountToken?.uuidString, appBundleIdIOS: transaction.appBundleID, countryCodeIOS: { @@ -886,6 +888,42 @@ private extension StoreKitTypesBridge { return TransactionReason(lowercased: "purchase", string: "purchase", uppercased: "PURCHASE") } + + // MARK: - Advanced Commerce Info (iOS 18.4+) + + static func extractAdvancedCommerceInfo(from transaction: StoreKit.Transaction) -> AdvancedCommerceInfoIOS? { + #if swift(>=6.1) + if #available(iOS 18.4, macOS 15.4, tvOS 18.4, watchOS 11.4, visionOS 2.4, *) { + guard let info = transaction.advancedCommerceInfo else { return nil } + let items: [AdvancedCommerceItemIOS] = info.items.map { item in + let details = AdvancedCommerceItemDetailsIOS( + jsonRepresentation: String(data: item.details.jsonRepresentation, encoding: .utf8) + ) + let refunds: [AdvancedCommerceRefundIOS]? = item.refunds?.map { refund in + AdvancedCommerceRefundIOS( + jsonRepresentation: String(data: refund.jsonRepresentation, encoding: .utf8) + ) + } + return AdvancedCommerceItemIOS( + details: details, + refunds: refunds, + revocationDate: item.revocationDate?.milliseconds + ) + } + return AdvancedCommerceInfoIOS( + description: info.description, + displayName: info.displayName, + estimatedTax: info.estimatedTax.map { "\($0)" }, + items: items, + requestReferenceId: info.requestReferenceID, + taxCode: info.taxCode, + taxExclusivePrice: info.taxExclusivePrice.map { "\($0)" }, + taxRate: info.taxRate.map { "\($0)" } + ) + } + #endif + return nil + } } @available(iOS 15.0, macOS 14.0, tvOS 16.0, watchOS 8.0, *) diff --git a/packages/apple/Sources/Models/Types.swift b/packages/apple/Sources/Models/Types.swift index 39db135b..a3547e12 100644 --- a/packages/apple/Sources/Models/Types.swift +++ b/packages/apple/Sources/Models/Types.swift @@ -488,6 +488,52 @@ public struct ActiveSubscription: Codable { public var willExpireSoon: Bool? = nil } +/// Advanced Commerce metadata from a transaction (iOS 18.4+). +/// Contains item details, tax information, and refund data for purchases +/// made through the Advanced Commerce API using generic SKUs. +/// Only present for transactions that use the Advanced Commerce API. +public struct AdvancedCommerceInfoIOS: Codable { + /// Optional description + public var description: String? = nil + /// Optional display name + public var displayName: String? = nil + /// Estimated tax amount (decimal string) + public var estimatedTax: String? = nil + /// The items purchased as part of this transaction + public var items: [AdvancedCommerceItemIOS] + /// Request reference identifier for tracking + public var requestReferenceId: String? = nil + /// Tax code for the transaction + public var taxCode: String? = nil + /// Price excluding tax (decimal string) + public var taxExclusivePrice: String? = nil + /// Tax rate applied (decimal string) + public var taxRate: String? = nil +} + +/// Details of an Advanced Commerce item (iOS 18.4+). +public struct AdvancedCommerceItemDetailsIOS: Codable { + /// JSON representation of the item details + public var jsonRepresentation: String? = nil +} + +/// An item purchased through the Advanced Commerce API (iOS 18.4+). +/// Represents a developer-defined product within a generic SKU transaction. +public struct AdvancedCommerceItemIOS: Codable { + /// The item's detail information + public var details: AdvancedCommerceItemDetailsIOS? = nil + /// Refunds issued for this item, if any + public var refunds: [AdvancedCommerceRefundIOS]? = nil + /// Date access to this item was revoked (milliseconds since epoch) + public var revocationDate: Double? = nil +} + +/// Refund information for an Advanced Commerce item (iOS 18.4+). +public struct AdvancedCommerceRefundIOS: Codable { + /// JSON representation of the refund details + public var jsonRepresentation: String? = nil +} + public struct AppTransaction: Codable { public var appId: Double public var appTransactionId: String? = nil @@ -995,6 +1041,10 @@ public struct PurchaseError: Codable { } public struct PurchaseIOS: Codable, PurchaseCommon { + /// Advanced Commerce API metadata (iOS 18.4+). + /// Present only for transactions that use the Advanced Commerce API. + /// Contains item details, tax information, and refund data for generic SKU purchases. + public var advancedCommerceInfoIOS: AdvancedCommerceInfoIOS? = nil public var appAccountToken: String? = nil public var appBundleIdIOS: String? = nil public var countryCodeIOS: String? = nil @@ -2388,6 +2438,11 @@ public protocol QueryResolver { func fetchProducts(_ params: ProductRequest) async throws -> FetchProductsResult /// Get active subscriptions (filters by subscriptionIds when provided) func getActiveSubscriptions(_ subscriptionIds: [String]?) async throws -> [ActiveSubscription] + /// Get the full StoreKit 2 transaction history as PurchaseIOS values. + /// Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + /// for finished consumables to be included (iOS 18+). + /// Unlike getAvailablePurchases, always returns the iOS-specific PurchaseIOS shape. + func getAllTransactionsIOS() async throws -> [PurchaseIOS] /// Fetch the current app transaction (iOS 16+) func getAppTransactionIOS() async throws -> AppTransaction? /// Get all available purchases for the current user @@ -2579,6 +2634,7 @@ public typealias QueryCanPresentExternalPurchaseNoticeIOSHandler = () async thro public typealias QueryCurrentEntitlementIOSHandler = (_ sku: String) async throws -> PurchaseIOS? public typealias QueryFetchProductsHandler = (_ params: ProductRequest) async throws -> FetchProductsResult public typealias QueryGetActiveSubscriptionsHandler = (_ subscriptionIds: [String]?) async throws -> [ActiveSubscription] +public typealias QueryGetAllTransactionsIOSHandler = () async throws -> [PurchaseIOS] public typealias QueryGetAppTransactionIOSHandler = () async throws -> AppTransaction? public typealias QueryGetAvailablePurchasesHandler = (_ options: PurchaseOptions?) async throws -> [Purchase] public typealias QueryGetExternalPurchaseCustomLinkTokenIOSHandler = (_ tokenType: ExternalPurchaseCustomLinkTokenTypeIOS) async throws -> ExternalPurchaseCustomLinkTokenResultIOS @@ -2601,6 +2657,7 @@ public struct QueryHandlers { public var currentEntitlementIOS: QueryCurrentEntitlementIOSHandler? public var fetchProducts: QueryFetchProductsHandler? public var getActiveSubscriptions: QueryGetActiveSubscriptionsHandler? + public var getAllTransactionsIOS: QueryGetAllTransactionsIOSHandler? public var getAppTransactionIOS: QueryGetAppTransactionIOSHandler? public var getAvailablePurchases: QueryGetAvailablePurchasesHandler? public var getExternalPurchaseCustomLinkTokenIOS: QueryGetExternalPurchaseCustomLinkTokenIOSHandler? @@ -2623,6 +2680,7 @@ public struct QueryHandlers { currentEntitlementIOS: QueryCurrentEntitlementIOSHandler? = nil, fetchProducts: QueryFetchProductsHandler? = nil, getActiveSubscriptions: QueryGetActiveSubscriptionsHandler? = nil, + getAllTransactionsIOS: QueryGetAllTransactionsIOSHandler? = nil, getAppTransactionIOS: QueryGetAppTransactionIOSHandler? = nil, getAvailablePurchases: QueryGetAvailablePurchasesHandler? = nil, getExternalPurchaseCustomLinkTokenIOS: QueryGetExternalPurchaseCustomLinkTokenIOSHandler? = nil, @@ -2644,6 +2702,7 @@ public struct QueryHandlers { self.currentEntitlementIOS = currentEntitlementIOS self.fetchProducts = fetchProducts self.getActiveSubscriptions = getActiveSubscriptions + self.getAllTransactionsIOS = getAllTransactionsIOS self.getAppTransactionIOS = getAppTransactionIOS self.getAvailablePurchases = getAvailablePurchases self.getExternalPurchaseCustomLinkTokenIOS = getExternalPurchaseCustomLinkTokenIOS diff --git a/packages/apple/Sources/OpenIapModule+ObjC.swift b/packages/apple/Sources/OpenIapModule+ObjC.swift index 7d5270c1..cfa621ad 100644 --- a/packages/apple/Sources/OpenIapModule+ObjC.swift +++ b/packages/apple/Sources/OpenIapModule+ObjC.swift @@ -292,6 +292,18 @@ import StoreKit } } + @objc func getAllTransactionsIOSWithCompletion(_ completion: @escaping ([Any]?, Error?) -> Void) { + Task { + do { + let transactions = try await getAllTransactionsIOS() + let dictionaries = transactions.map { OpenIapSerialization.encode($0) } + completion(dictionaries, nil) + } catch { + completion(nil, error) + } + } + } + @objc func syncIOSWithCompletion(_ completion: @escaping (Bool, Error?) -> Void) { Task { do { diff --git a/packages/apple/Sources/OpenIapModule.swift b/packages/apple/Sources/OpenIapModule.swift index 9558a596..6c60a193 100644 --- a/packages/apple/Sources/OpenIapModule.swift +++ b/packages/apple/Sources/OpenIapModule.swift @@ -503,6 +503,34 @@ public final class OpenIapModule: NSObject, OpenIapModuleProtocol { return purchasedItems } + /// Get the full StoreKit 2 transaction history as `PurchaseIOS` values. + /// Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + /// for finished consumables to be included in `Transaction.all` (iOS 18+). + /// Unlike `getAvailablePurchases(_:)`, this method always reads from + /// `Transaction.all` and returns the iOS-specific `PurchaseIOS` shape rather than + /// the cross-platform `Purchase` type. + public func getAllTransactionsIOS() async throws -> [PurchaseIOS] { + try await ensureConnection() + var transactions: [PurchaseIOS] = [] + + for await verification in Transaction.all { + do { + let transaction = try checkVerified(verification) + let purchase = await StoreKitTypesBridge.purchaseIOS( + from: transaction, + jwsRepresentation: verification.jwsRepresentation + ) + transactions.append(purchase) + } catch { + OpenIapLog.error("getAllTransactionsIOS: failed to verify transaction: \(error)") + continue + } + } + + OpenIapLog.debug("🔍 getAllTransactionsIOS: \(transactions.count) transactions") + return transactions + } + // MARK: - Transaction Management public func finishTransaction(purchase: PurchaseInput, isConsumable: Bool?) async throws -> Void { diff --git a/packages/apple/Sources/OpenIapProtocol.swift b/packages/apple/Sources/OpenIapProtocol.swift index 7540fe35..f1d7ac11 100644 --- a/packages/apple/Sources/OpenIapProtocol.swift +++ b/packages/apple/Sources/OpenIapProtocol.swift @@ -51,6 +51,7 @@ public protocol OpenIapModuleProtocol { func requestPurchaseOnPromotedProductIOS() async throws -> Bool func restorePurchases() async throws -> Void func getAvailablePurchases(_ options: PurchaseOptions?) async throws -> [Purchase] + func getAllTransactionsIOS() async throws -> [PurchaseIOS] // Transaction Management func finishTransaction(purchase: PurchaseInput, isConsumable: Bool?) async throws -> Void diff --git a/packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift b/packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift index ad7b3f96..2018f9ef 100644 --- a/packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift +++ b/packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift @@ -87,6 +87,7 @@ private final class FakeOpenIapModule: OpenIapModuleProtocol { func requestPurchaseOnPromotedProductIOS() async throws -> Bool { false } func restorePurchases() async throws -> Void { () } func getAvailablePurchases(_ options: PurchaseOptions?) async throws -> [Purchase] { [] } + func getAllTransactionsIOS() async throws -> [PurchaseIOS] { [] } // MARK: - Transaction Management func finishTransaction(purchase: PurchaseInput, isConsumable: Bool?) async throws -> Void { () } diff --git a/packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift b/packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift index ae247d5a..46f6d3f4 100644 --- a/packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift +++ b/packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift @@ -102,6 +102,7 @@ private final class FakeVerifyPurchaseModule: OpenIapModuleProtocol { func requestPurchaseOnPromotedProductIOS() async throws -> Bool { false } func restorePurchases() async throws -> Void { () } func getAvailablePurchases(_ options: PurchaseOptions?) async throws -> [Purchase] { [] } + func getAllTransactionsIOS() async throws -> [PurchaseIOS] { [] } // MARK: - Transaction Management func finishTransaction(purchase: PurchaseInput, isConsumable: Bool?) async throws -> Void { () } diff --git a/packages/docs/public/llms-full.txt b/packages/docs/public/llms-full.txt index b3a4defa..f74aad0e 100644 --- a/packages/docs/public/llms-full.txt +++ b/packages/docs/public/llms-full.txt @@ -3,7 +3,7 @@ > OpenIAP: Unified in-app purchase specification for iOS & Android > Documentation: https://openiap.dev > Quick Reference: https://openiap.dev/llms.txt -> Generated: 2026-04-16T13:24:54.944Z +> Generated: 2026-04-16T13:44:54.268Z ## Table of Contents 1. Installation diff --git a/packages/docs/public/llms.txt b/packages/docs/public/llms.txt index c53fe836..9e68dfcc 100644 --- a/packages/docs/public/llms.txt +++ b/packages/docs/public/llms.txt @@ -3,7 +3,7 @@ > OpenIAP: Unified in-app purchase specification for iOS & Android > Documentation: https://openiap.dev > Full Reference: https://openiap.dev/llms-full.txt -> Generated: 2026-04-16T13:24:54.944Z +> Generated: 2026-04-16T13:44:54.268Z ## Installation diff --git a/packages/docs/src/pages/docs/android-setup.tsx b/packages/docs/src/pages/docs/android-setup.tsx index 6604e17c..022c2aaa 100644 --- a/packages/docs/src/pages/docs/android-setup.tsx +++ b/packages/docs/src/pages/docs/android-setup.tsx @@ -209,11 +209,11 @@ function AndroidSetup() {

- The OpenIAP Android core library (openiap-google) requires{' '} - minSdk 23 (Android 6.0) with Google Play Billing 8.x. - Each framework library may require a higher minimum — check the{' '} - framework-specific setup page for the exact - value. + The OpenIAP Android core library (openiap-google) + requires minSdk 23 (Android 6.0) with Google Play + Billing 8.x. Each framework library may require a higher minimum — + check the framework-specific setup page for + the exact value.

diff --git a/packages/docs/src/pages/docs/apis/ios.tsx b/packages/docs/src/pages/docs/apis/ios.tsx index 9eeea9cf..5e3aef23 100644 --- a/packages/docs/src/pages/docs/apis/ios.tsx +++ b/packages/docs/src/pages/docs/apis/ios.tsx @@ -28,7 +28,8 @@ function IOSAPIs() { Transaction Management - : clearTransactionIOS, getPendingTransactionsIOS + : clearTransactionIOS, getPendingTransactionsIOS, + getAllTransactionsIOS
  • @@ -68,6 +69,16 @@ function IOSAPIs() {

    Retrieve all pending transactions in the StoreKit queue.

    {`func getPendingTransactionsIOS() async throws -> [Purchase]`} + + getAllTransactionsIOS + +

    + Get the full StoreKit 2 transaction history as PurchaseIOS values. + Requires the SK2ConsumableTransactionHistory Info.plist key for + finished consumables to be included (iOS 18+). +

    + {`func getAllTransactionsIOS() async throws -> [PurchaseIOS]`} + syncIOS diff --git a/packages/docs/src/pages/docs/types/purchase.tsx b/packages/docs/src/pages/docs/types/purchase.tsx index b3aa69dd..17074155 100644 --- a/packages/docs/src/pages/docs/types/purchase.tsx +++ b/packages/docs/src/pages/docs/types/purchase.tsx @@ -377,6 +377,18 @@ function TypesPurchase() { below) + + + advancedCommerceInfoIOS + + + Advanced Commerce API metadata (iOS 18.4+, see{' '} +
    + AdvancedCommerceInfoIOS + {' '} + below) + + @@ -478,6 +490,87 @@ function TypesPurchase() { + +
    + + AdvancedCommerceInfoIOS{' '} + + (iOS 18.4+, from{' '} + + Transaction.AdvancedCommerceInfo + + ) + + +

    + Present only for transactions using the Advanced Commerce + API with generic SKU purchases. +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameSummary
    + items + Items purchased in this transaction
    + requestReferenceId + + Request reference identifier for tracking (optional) +
    + taxCode + Tax code for the transaction (optional)
    + taxExclusivePrice + Price excluding tax, decimal string (optional)
    + estimatedTax + Estimated tax amount, decimal string (optional)
    + taxRate + Tax rate applied, decimal string (optional)
    + displayName + Optional display name
    + description + Optional description
    +
    ), android: ( diff --git a/packages/docs/src/pages/docs/updates/releases.tsx b/packages/docs/src/pages/docs/updates/releases.tsx index 7e324fa3..8722a1e8 100644 --- a/packages/docs/src/pages/docs/updates/releases.tsx +++ b/packages/docs/src/pages/docs/updates/releases.tsx @@ -46,14 +46,13 @@ function Releases() { color: 'var(--text-secondary)', }} > - Until now, catching a failed payment before the platform - silently downgrades or cancels a subscription required running - your own server and polling Apple/Google Server-to-Server - Notifications. With the new{' '} - subscriptionBillingIssue event, apps can detect - billing problems purely on the client — no backend infrastructure - needed. Listen for the event, deep-link the user to the - platform's subscription management screen via{' '} + Until now, catching a failed payment before the platform silently + downgrades or cancels a subscription required running your own + server and polling Apple/Google Server-to-Server Notifications. + With the new subscriptionBillingIssue event, apps can + detect billing problems purely on the client — no backend + infrastructure needed. Listen for the event, deep-link the user to + the platform's subscription management screen via{' '} deepLinkToSubscriptions, and turn involuntary churn into a recoverable moment.

    @@ -68,8 +67,8 @@ function Releases() {
  • Schema: new{' '} IapEvent.SubscriptionBillingIssue enum value and{' '} - subscriptionBillingIssue: Purchase! subscription - in event.graphql. Payload is the affected{' '} + subscriptionBillingIssue: Purchase! subscription in{' '} + event.graphql. Payload is the affected{' '} Purchase.
  • @@ -347,9 +346,9 @@ function Releases() { Breaking: ErrorCode gains{' '} .serviceTimeout {' '} - — the shared spec schema adds ServiceTimeout, so the - Swift ErrorCode enum picks up a new case. Any Swift - consumer that does an exhaustive switch on{' '} + — the shared spec schema adds ServiceTimeout, so + the Swift ErrorCode enum picks up a new case. Any + Swift consumer that does an exhaustive switch on{' '} ErrorCode without a default: branch will need to add a .serviceTimeout case (or a fallback), hence the major bump per SemVer. The enum is not{' '} diff --git a/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt b/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt index 7326529f..bc6306db 100644 --- a/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt +++ b/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt @@ -1080,6 +1080,160 @@ public data class ActiveSubscription( ) } +/** + * Advanced Commerce metadata from a transaction (iOS 18.4+). + * Contains item details, tax information, and refund data for purchases + * made through the Advanced Commerce API using generic SKUs. + * Only present for transactions that use the Advanced Commerce API. + */ +public data class AdvancedCommerceInfoIOS( + /** + * Optional description + */ + val description: String? = null, + /** + * Optional display name + */ + val displayName: String? = null, + /** + * Estimated tax amount (decimal string) + */ + val estimatedTax: String? = null, + /** + * The items purchased as part of this transaction + */ + val items: List, + /** + * Request reference identifier for tracking + */ + val requestReferenceId: String? = null, + /** + * Tax code for the transaction + */ + val taxCode: String? = null, + /** + * Price excluding tax (decimal string) + */ + val taxExclusivePrice: String? = null, + /** + * Tax rate applied (decimal string) + */ + val taxRate: String? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceInfoIOS { + return AdvancedCommerceInfoIOS( + description = json["description"] as? String, + displayName = json["displayName"] as? String, + estimatedTax = json["estimatedTax"] as? String, + items = (json["items"] as? List<*>)?.mapNotNull { (it as? Map)?.let { AdvancedCommerceItemIOS.fromJson(it) } ?: throw IllegalArgumentException("Missing required object for AdvancedCommerceItemIOS") } ?: emptyList(), + requestReferenceId = json["requestReferenceId"] as? String, + taxCode = json["taxCode"] as? String, + taxExclusivePrice = json["taxExclusivePrice"] as? String, + taxRate = json["taxRate"] as? String, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceInfoIOS", + "description" to description, + "displayName" to displayName, + "estimatedTax" to estimatedTax, + "items" to items.map { it.toJson() }, + "requestReferenceId" to requestReferenceId, + "taxCode" to taxCode, + "taxExclusivePrice" to taxExclusivePrice, + "taxRate" to taxRate, + ) +} + +/** + * Details of an Advanced Commerce item (iOS 18.4+). + */ +public data class AdvancedCommerceItemDetailsIOS( + /** + * JSON representation of the item details + */ + val jsonRepresentation: String? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceItemDetailsIOS { + return AdvancedCommerceItemDetailsIOS( + jsonRepresentation = json["jsonRepresentation"] as? String, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceItemDetailsIOS", + "jsonRepresentation" to jsonRepresentation, + ) +} + +/** + * An item purchased through the Advanced Commerce API (iOS 18.4+). + * Represents a developer-defined product within a generic SKU transaction. + */ +public data class AdvancedCommerceItemIOS( + /** + * The item's detail information + */ + val details: AdvancedCommerceItemDetailsIOS? = null, + /** + * Refunds issued for this item, if any + */ + val refunds: List? = null, + /** + * Date access to this item was revoked (milliseconds since epoch) + */ + val revocationDate: Double? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceItemIOS { + return AdvancedCommerceItemIOS( + details = (json["details"] as? Map)?.let { AdvancedCommerceItemDetailsIOS.fromJson(it) }, + refunds = (json["refunds"] as? List<*>)?.mapNotNull { (it as? Map)?.let { AdvancedCommerceRefundIOS.fromJson(it) } ?: throw IllegalArgumentException("Missing required object for AdvancedCommerceRefundIOS") }, + revocationDate = (json["revocationDate"] as? Number)?.toDouble(), + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceItemIOS", + "details" to details?.toJson(), + "refunds" to refunds?.map { it.toJson() }, + "revocationDate" to revocationDate, + ) +} + +/** + * Refund information for an Advanced Commerce item (iOS 18.4+). + */ +public data class AdvancedCommerceRefundIOS( + /** + * JSON representation of the refund details + */ + val jsonRepresentation: String? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceRefundIOS { + return AdvancedCommerceRefundIOS( + jsonRepresentation = json["jsonRepresentation"] as? String, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceRefundIOS", + "jsonRepresentation" to jsonRepresentation, + ) +} + public data class AppTransaction( val appId: Double, val appTransactionId: String? = null, @@ -2565,6 +2719,12 @@ public data class PurchaseError( } public data class PurchaseIOS( + /** + * Advanced Commerce API metadata (iOS 18.4+). + * Present only for transactions that use the Advanced Commerce API. + * Contains item details, tax information, and refund data for generic SKU purchases. + */ + val advancedCommerceInfoIOS: AdvancedCommerceInfoIOS? = null, val appAccountToken: String? = null, val appBundleIdIOS: String? = null, val countryCodeIOS: String? = null, @@ -2607,6 +2767,7 @@ public data class PurchaseIOS( companion object { fun fromJson(json: Map): PurchaseIOS { return PurchaseIOS( + advancedCommerceInfoIOS = (json["advancedCommerceInfoIOS"] as? Map)?.let { AdvancedCommerceInfoIOS.fromJson(it) }, appAccountToken = json["appAccountToken"] as? String, appBundleIdIOS = json["appBundleIdIOS"] as? String, countryCodeIOS = json["countryCodeIOS"] as? String, @@ -2647,6 +2808,7 @@ public data class PurchaseIOS( override fun toJson(): Map = mapOf( "__typename" to "PurchaseIOS", + "advancedCommerceInfoIOS" to advancedCommerceInfoIOS?.toJson(), "appAccountToken" to appAccountToken, "appBundleIdIOS" to appBundleIdIOS, "countryCodeIOS" to countryCodeIOS, @@ -4747,6 +4909,13 @@ public interface QueryResolver { * Get active subscriptions (filters by subscriptionIds when provided) */ suspend fun getActiveSubscriptions(subscriptionIds: List? = null): List + /** + * Get the full StoreKit 2 transaction history as PurchaseIOS values. + * Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + * for finished consumables to be included (iOS 18+). + * Unlike getAvailablePurchases, always returns the iOS-specific PurchaseIOS shape. + */ + suspend fun getAllTransactionsIOS(): List /** * Fetch the current app transaction (iOS 16+) */ @@ -4928,6 +5097,7 @@ public typealias QueryCanPresentExternalPurchaseNoticeIOSHandler = suspend () -> public typealias QueryCurrentEntitlementIOSHandler = suspend (sku: String) -> PurchaseIOS? public typealias QueryFetchProductsHandler = suspend (params: ProductRequest) -> FetchProductsResult public typealias QueryGetActiveSubscriptionsHandler = suspend (subscriptionIds: List?) -> List +public typealias QueryGetAllTransactionsIOSHandler = suspend () -> List public typealias QueryGetAppTransactionIOSHandler = suspend () -> AppTransaction? public typealias QueryGetAvailablePurchasesHandler = suspend (options: PurchaseOptions?) -> List public typealias QueryGetExternalPurchaseCustomLinkTokenIOSHandler = suspend (tokenType: ExternalPurchaseCustomLinkTokenTypeIOS) -> ExternalPurchaseCustomLinkTokenResultIOS @@ -4950,6 +5120,7 @@ public data class QueryHandlers( val currentEntitlementIOS: QueryCurrentEntitlementIOSHandler? = null, val fetchProducts: QueryFetchProductsHandler? = null, val getActiveSubscriptions: QueryGetActiveSubscriptionsHandler? = null, + val getAllTransactionsIOS: QueryGetAllTransactionsIOSHandler? = null, val getAppTransactionIOS: QueryGetAppTransactionIOSHandler? = null, val getAvailablePurchases: QueryGetAvailablePurchasesHandler? = null, val getExternalPurchaseCustomLinkTokenIOS: QueryGetExternalPurchaseCustomLinkTokenIOSHandler? = null, diff --git a/packages/gql/src/api-ios.graphql b/packages/gql/src/api-ios.graphql index 128959bd..ba7802c0 100644 --- a/packages/gql/src/api-ios.graphql +++ b/packages/gql/src/api-ios.graphql @@ -81,11 +81,19 @@ extend type Query { """ # Future getAppTransactionIOS: AppTransaction -""" -Validate a receipt for a specific product -""" -# Future -validateReceiptIOS(options: VerifyPurchaseProps!): VerifyPurchaseResultIOS! @deprecated(reason: "Use verifyPurchase") + """ + Get the full StoreKit 2 transaction history as PurchaseIOS values. + Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + for finished consumables to be included (iOS 18+). + Unlike getAvailablePurchases, always returns the iOS-specific PurchaseIOS shape. + """ + # Future + getAllTransactionsIOS: [PurchaseIOS!]! + """ + Validate a receipt for a specific product + """ + # Future + validateReceiptIOS(options: VerifyPurchaseProps!): VerifyPurchaseResultIOS! @deprecated(reason: "Use verifyPurchase") } extend type Mutation { diff --git a/packages/gql/src/generated/Types.kt b/packages/gql/src/generated/Types.kt index 856e7a9d..d5151235 100644 --- a/packages/gql/src/generated/Types.kt +++ b/packages/gql/src/generated/Types.kt @@ -1169,6 +1169,160 @@ public data class ActiveSubscription( ) } +/** + * Advanced Commerce metadata from a transaction (iOS 18.4+). + * Contains item details, tax information, and refund data for purchases + * made through the Advanced Commerce API using generic SKUs. + * Only present for transactions that use the Advanced Commerce API. + */ +public data class AdvancedCommerceInfoIOS( + /** + * Optional description + */ + val description: String? = null, + /** + * Optional display name + */ + val displayName: String? = null, + /** + * Estimated tax amount (decimal string) + */ + val estimatedTax: String? = null, + /** + * The items purchased as part of this transaction + */ + val items: List, + /** + * Request reference identifier for tracking + */ + val requestReferenceId: String? = null, + /** + * Tax code for the transaction + */ + val taxCode: String? = null, + /** + * Price excluding tax (decimal string) + */ + val taxExclusivePrice: String? = null, + /** + * Tax rate applied (decimal string) + */ + val taxRate: String? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceInfoIOS { + return AdvancedCommerceInfoIOS( + description = json["description"] as? String, + displayName = json["displayName"] as? String, + estimatedTax = json["estimatedTax"] as? String, + items = (json["items"] as? List<*>)?.mapNotNull { (it as? Map)?.let { AdvancedCommerceItemIOS.fromJson(it) } ?: throw IllegalArgumentException("Missing required object for AdvancedCommerceItemIOS") } ?: emptyList(), + requestReferenceId = json["requestReferenceId"] as? String, + taxCode = json["taxCode"] as? String, + taxExclusivePrice = json["taxExclusivePrice"] as? String, + taxRate = json["taxRate"] as? String, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceInfoIOS", + "description" to description, + "displayName" to displayName, + "estimatedTax" to estimatedTax, + "items" to items.map { it.toJson() }, + "requestReferenceId" to requestReferenceId, + "taxCode" to taxCode, + "taxExclusivePrice" to taxExclusivePrice, + "taxRate" to taxRate, + ) +} + +/** + * Details of an Advanced Commerce item (iOS 18.4+). + */ +public data class AdvancedCommerceItemDetailsIOS( + /** + * JSON representation of the item details + */ + val jsonRepresentation: String? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceItemDetailsIOS { + return AdvancedCommerceItemDetailsIOS( + jsonRepresentation = json["jsonRepresentation"] as? String, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceItemDetailsIOS", + "jsonRepresentation" to jsonRepresentation, + ) +} + +/** + * An item purchased through the Advanced Commerce API (iOS 18.4+). + * Represents a developer-defined product within a generic SKU transaction. + */ +public data class AdvancedCommerceItemIOS( + /** + * The item's detail information + */ + val details: AdvancedCommerceItemDetailsIOS? = null, + /** + * Refunds issued for this item, if any + */ + val refunds: List? = null, + /** + * Date access to this item was revoked (milliseconds since epoch) + */ + val revocationDate: Double? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceItemIOS { + return AdvancedCommerceItemIOS( + details = (json["details"] as? Map)?.let { AdvancedCommerceItemDetailsIOS.fromJson(it) }, + refunds = (json["refunds"] as? List<*>)?.mapNotNull { (it as? Map)?.let { AdvancedCommerceRefundIOS.fromJson(it) } ?: throw IllegalArgumentException("Missing required object for AdvancedCommerceRefundIOS") }, + revocationDate = (json["revocationDate"] as? Number)?.toDouble(), + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceItemIOS", + "details" to details?.toJson(), + "refunds" to refunds?.map { it.toJson() }, + "revocationDate" to revocationDate, + ) +} + +/** + * Refund information for an Advanced Commerce item (iOS 18.4+). + */ +public data class AdvancedCommerceRefundIOS( + /** + * JSON representation of the refund details + */ + val jsonRepresentation: String? = null +) { + + companion object { + fun fromJson(json: Map): AdvancedCommerceRefundIOS { + return AdvancedCommerceRefundIOS( + jsonRepresentation = json["jsonRepresentation"] as? String, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "AdvancedCommerceRefundIOS", + "jsonRepresentation" to jsonRepresentation, + ) +} + public data class AppTransaction( val appId: Double, val appTransactionId: String? = null, @@ -2654,6 +2808,12 @@ public data class PurchaseError( } public data class PurchaseIOS( + /** + * Advanced Commerce API metadata (iOS 18.4+). + * Present only for transactions that use the Advanced Commerce API. + * Contains item details, tax information, and refund data for generic SKU purchases. + */ + val advancedCommerceInfoIOS: AdvancedCommerceInfoIOS? = null, val appAccountToken: String? = null, val appBundleIdIOS: String? = null, val countryCodeIOS: String? = null, @@ -2696,6 +2856,7 @@ public data class PurchaseIOS( companion object { fun fromJson(json: Map): PurchaseIOS { return PurchaseIOS( + advancedCommerceInfoIOS = (json["advancedCommerceInfoIOS"] as? Map)?.let { AdvancedCommerceInfoIOS.fromJson(it) }, appAccountToken = json["appAccountToken"] as? String, appBundleIdIOS = json["appBundleIdIOS"] as? String, countryCodeIOS = json["countryCodeIOS"] as? String, @@ -2736,6 +2897,7 @@ public data class PurchaseIOS( override fun toJson(): Map = mapOf( "__typename" to "PurchaseIOS", + "advancedCommerceInfoIOS" to advancedCommerceInfoIOS?.toJson(), "appAccountToken" to appAccountToken, "appBundleIdIOS" to appBundleIdIOS, "countryCodeIOS" to countryCodeIOS, @@ -4836,6 +4998,13 @@ public interface QueryResolver { * Get active subscriptions (filters by subscriptionIds when provided) */ suspend fun getActiveSubscriptions(subscriptionIds: List? = null): List + /** + * Get the full StoreKit 2 transaction history as PurchaseIOS values. + * Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + * for finished consumables to be included (iOS 18+). + * Unlike getAvailablePurchases, always returns the iOS-specific PurchaseIOS shape. + */ + suspend fun getAllTransactionsIOS(): List /** * Fetch the current app transaction (iOS 16+) */ @@ -5017,6 +5186,7 @@ public typealias QueryCanPresentExternalPurchaseNoticeIOSHandler = suspend () -> public typealias QueryCurrentEntitlementIOSHandler = suspend (sku: String) -> PurchaseIOS? public typealias QueryFetchProductsHandler = suspend (params: ProductRequest) -> FetchProductsResult public typealias QueryGetActiveSubscriptionsHandler = suspend (subscriptionIds: List?) -> List +public typealias QueryGetAllTransactionsIOSHandler = suspend () -> List public typealias QueryGetAppTransactionIOSHandler = suspend () -> AppTransaction? public typealias QueryGetAvailablePurchasesHandler = suspend (options: PurchaseOptions?) -> List public typealias QueryGetExternalPurchaseCustomLinkTokenIOSHandler = suspend (tokenType: ExternalPurchaseCustomLinkTokenTypeIOS) -> ExternalPurchaseCustomLinkTokenResultIOS @@ -5039,6 +5209,7 @@ public data class QueryHandlers( val currentEntitlementIOS: QueryCurrentEntitlementIOSHandler? = null, val fetchProducts: QueryFetchProductsHandler? = null, val getActiveSubscriptions: QueryGetActiveSubscriptionsHandler? = null, + val getAllTransactionsIOS: QueryGetAllTransactionsIOSHandler? = null, val getAppTransactionIOS: QueryGetAppTransactionIOSHandler? = null, val getAvailablePurchases: QueryGetAvailablePurchasesHandler? = null, val getExternalPurchaseCustomLinkTokenIOS: QueryGetExternalPurchaseCustomLinkTokenIOSHandler? = null, diff --git a/packages/gql/src/generated/Types.swift b/packages/gql/src/generated/Types.swift index 39db135b..a3547e12 100644 --- a/packages/gql/src/generated/Types.swift +++ b/packages/gql/src/generated/Types.swift @@ -488,6 +488,52 @@ public struct ActiveSubscription: Codable { public var willExpireSoon: Bool? = nil } +/// Advanced Commerce metadata from a transaction (iOS 18.4+). +/// Contains item details, tax information, and refund data for purchases +/// made through the Advanced Commerce API using generic SKUs. +/// Only present for transactions that use the Advanced Commerce API. +public struct AdvancedCommerceInfoIOS: Codable { + /// Optional description + public var description: String? = nil + /// Optional display name + public var displayName: String? = nil + /// Estimated tax amount (decimal string) + public var estimatedTax: String? = nil + /// The items purchased as part of this transaction + public var items: [AdvancedCommerceItemIOS] + /// Request reference identifier for tracking + public var requestReferenceId: String? = nil + /// Tax code for the transaction + public var taxCode: String? = nil + /// Price excluding tax (decimal string) + public var taxExclusivePrice: String? = nil + /// Tax rate applied (decimal string) + public var taxRate: String? = nil +} + +/// Details of an Advanced Commerce item (iOS 18.4+). +public struct AdvancedCommerceItemDetailsIOS: Codable { + /// JSON representation of the item details + public var jsonRepresentation: String? = nil +} + +/// An item purchased through the Advanced Commerce API (iOS 18.4+). +/// Represents a developer-defined product within a generic SKU transaction. +public struct AdvancedCommerceItemIOS: Codable { + /// The item's detail information + public var details: AdvancedCommerceItemDetailsIOS? = nil + /// Refunds issued for this item, if any + public var refunds: [AdvancedCommerceRefundIOS]? = nil + /// Date access to this item was revoked (milliseconds since epoch) + public var revocationDate: Double? = nil +} + +/// Refund information for an Advanced Commerce item (iOS 18.4+). +public struct AdvancedCommerceRefundIOS: Codable { + /// JSON representation of the refund details + public var jsonRepresentation: String? = nil +} + public struct AppTransaction: Codable { public var appId: Double public var appTransactionId: String? = nil @@ -995,6 +1041,10 @@ public struct PurchaseError: Codable { } public struct PurchaseIOS: Codable, PurchaseCommon { + /// Advanced Commerce API metadata (iOS 18.4+). + /// Present only for transactions that use the Advanced Commerce API. + /// Contains item details, tax information, and refund data for generic SKU purchases. + public var advancedCommerceInfoIOS: AdvancedCommerceInfoIOS? = nil public var appAccountToken: String? = nil public var appBundleIdIOS: String? = nil public var countryCodeIOS: String? = nil @@ -2388,6 +2438,11 @@ public protocol QueryResolver { func fetchProducts(_ params: ProductRequest) async throws -> FetchProductsResult /// Get active subscriptions (filters by subscriptionIds when provided) func getActiveSubscriptions(_ subscriptionIds: [String]?) async throws -> [ActiveSubscription] + /// Get the full StoreKit 2 transaction history as PurchaseIOS values. + /// Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + /// for finished consumables to be included (iOS 18+). + /// Unlike getAvailablePurchases, always returns the iOS-specific PurchaseIOS shape. + func getAllTransactionsIOS() async throws -> [PurchaseIOS] /// Fetch the current app transaction (iOS 16+) func getAppTransactionIOS() async throws -> AppTransaction? /// Get all available purchases for the current user @@ -2579,6 +2634,7 @@ public typealias QueryCanPresentExternalPurchaseNoticeIOSHandler = () async thro public typealias QueryCurrentEntitlementIOSHandler = (_ sku: String) async throws -> PurchaseIOS? public typealias QueryFetchProductsHandler = (_ params: ProductRequest) async throws -> FetchProductsResult public typealias QueryGetActiveSubscriptionsHandler = (_ subscriptionIds: [String]?) async throws -> [ActiveSubscription] +public typealias QueryGetAllTransactionsIOSHandler = () async throws -> [PurchaseIOS] public typealias QueryGetAppTransactionIOSHandler = () async throws -> AppTransaction? public typealias QueryGetAvailablePurchasesHandler = (_ options: PurchaseOptions?) async throws -> [Purchase] public typealias QueryGetExternalPurchaseCustomLinkTokenIOSHandler = (_ tokenType: ExternalPurchaseCustomLinkTokenTypeIOS) async throws -> ExternalPurchaseCustomLinkTokenResultIOS @@ -2601,6 +2657,7 @@ public struct QueryHandlers { public var currentEntitlementIOS: QueryCurrentEntitlementIOSHandler? public var fetchProducts: QueryFetchProductsHandler? public var getActiveSubscriptions: QueryGetActiveSubscriptionsHandler? + public var getAllTransactionsIOS: QueryGetAllTransactionsIOSHandler? public var getAppTransactionIOS: QueryGetAppTransactionIOSHandler? public var getAvailablePurchases: QueryGetAvailablePurchasesHandler? public var getExternalPurchaseCustomLinkTokenIOS: QueryGetExternalPurchaseCustomLinkTokenIOSHandler? @@ -2623,6 +2680,7 @@ public struct QueryHandlers { currentEntitlementIOS: QueryCurrentEntitlementIOSHandler? = nil, fetchProducts: QueryFetchProductsHandler? = nil, getActiveSubscriptions: QueryGetActiveSubscriptionsHandler? = nil, + getAllTransactionsIOS: QueryGetAllTransactionsIOSHandler? = nil, getAppTransactionIOS: QueryGetAppTransactionIOSHandler? = nil, getAvailablePurchases: QueryGetAvailablePurchasesHandler? = nil, getExternalPurchaseCustomLinkTokenIOS: QueryGetExternalPurchaseCustomLinkTokenIOSHandler? = nil, @@ -2644,6 +2702,7 @@ public struct QueryHandlers { self.currentEntitlementIOS = currentEntitlementIOS self.fetchProducts = fetchProducts self.getActiveSubscriptions = getActiveSubscriptions + self.getAllTransactionsIOS = getAllTransactionsIOS self.getAppTransactionIOS = getAppTransactionIOS self.getAvailablePurchases = getAvailablePurchases self.getExternalPurchaseCustomLinkTokenIOS = getExternalPurchaseCustomLinkTokenIOS diff --git a/packages/gql/src/generated/types.dart b/packages/gql/src/generated/types.dart index 1b65274a..ea75b5b3 100644 --- a/packages/gql/src/generated/types.dart +++ b/packages/gql/src/generated/types.dart @@ -1048,6 +1048,147 @@ class ActiveSubscription { } } +/// Advanced Commerce metadata from a transaction (iOS 18.4+). +/// Contains item details, tax information, and refund data for purchases +/// made through the Advanced Commerce API using generic SKUs. +/// Only present for transactions that use the Advanced Commerce API. +class AdvancedCommerceInfoIOS { + const AdvancedCommerceInfoIOS({ + this.description, + this.displayName, + this.estimatedTax, + required this.items, + this.requestReferenceId, + this.taxCode, + this.taxExclusivePrice, + this.taxRate, + }); + + /// Optional description + final String? description; + /// Optional display name + final String? displayName; + /// Estimated tax amount (decimal string) + final String? estimatedTax; + /// The items purchased as part of this transaction + final List items; + /// Request reference identifier for tracking + final String? requestReferenceId; + /// Tax code for the transaction + final String? taxCode; + /// Price excluding tax (decimal string) + final String? taxExclusivePrice; + /// Tax rate applied (decimal string) + final String? taxRate; + + factory AdvancedCommerceInfoIOS.fromJson(Map json) { + return AdvancedCommerceInfoIOS( + description: json['description'] as String?, + displayName: json['displayName'] as String?, + estimatedTax: json['estimatedTax'] as String?, + items: (json['items'] as List).map((e) => AdvancedCommerceItemIOS.fromJson(e as Map)).toList(), + requestReferenceId: json['requestReferenceId'] as String?, + taxCode: json['taxCode'] as String?, + taxExclusivePrice: json['taxExclusivePrice'] as String?, + taxRate: json['taxRate'] as String?, + ); + } + + Map toJson() { + return { + '__typename': 'AdvancedCommerceInfoIOS', + 'description': description, + 'displayName': displayName, + 'estimatedTax': estimatedTax, + 'items': items.map((e) => e.toJson()).toList(), + 'requestReferenceId': requestReferenceId, + 'taxCode': taxCode, + 'taxExclusivePrice': taxExclusivePrice, + 'taxRate': taxRate, + }; + } +} + +/// Details of an Advanced Commerce item (iOS 18.4+). +class AdvancedCommerceItemDetailsIOS { + const AdvancedCommerceItemDetailsIOS({ + this.jsonRepresentation, + }); + + /// JSON representation of the item details + final String? jsonRepresentation; + + factory AdvancedCommerceItemDetailsIOS.fromJson(Map json) { + return AdvancedCommerceItemDetailsIOS( + jsonRepresentation: json['jsonRepresentation'] as String?, + ); + } + + Map toJson() { + return { + '__typename': 'AdvancedCommerceItemDetailsIOS', + 'jsonRepresentation': jsonRepresentation, + }; + } +} + +/// An item purchased through the Advanced Commerce API (iOS 18.4+). +/// Represents a developer-defined product within a generic SKU transaction. +class AdvancedCommerceItemIOS { + const AdvancedCommerceItemIOS({ + this.details, + this.refunds, + this.revocationDate, + }); + + /// The item's detail information + final AdvancedCommerceItemDetailsIOS? details; + /// Refunds issued for this item, if any + final List? refunds; + /// Date access to this item was revoked (milliseconds since epoch) + final double? revocationDate; + + factory AdvancedCommerceItemIOS.fromJson(Map json) { + return AdvancedCommerceItemIOS( + details: json['details'] != null ? AdvancedCommerceItemDetailsIOS.fromJson(json['details'] as Map) : null, + refunds: (json['refunds'] as List?) == null ? null : (json['refunds'] as List?)!.map((e) => AdvancedCommerceRefundIOS.fromJson(e as Map)).toList(), + revocationDate: (json['revocationDate'] as num?)?.toDouble(), + ); + } + + Map toJson() { + return { + '__typename': 'AdvancedCommerceItemIOS', + 'details': details?.toJson(), + 'refunds': refunds == null ? null : refunds!.map((e) => e.toJson()).toList(), + 'revocationDate': revocationDate, + }; + } +} + +/// Refund information for an Advanced Commerce item (iOS 18.4+). +class AdvancedCommerceRefundIOS { + const AdvancedCommerceRefundIOS({ + this.jsonRepresentation, + }); + + /// JSON representation of the refund details + final String? jsonRepresentation; + + factory AdvancedCommerceRefundIOS.fromJson(Map json) { + return AdvancedCommerceRefundIOS( + jsonRepresentation: json['jsonRepresentation'] as String?, + ); + } + + Map toJson() { + return { + '__typename': 'AdvancedCommerceRefundIOS', + 'jsonRepresentation': jsonRepresentation, + }; + } +} + class AppTransaction { const AppTransaction({ required this.appId, @@ -2612,6 +2753,7 @@ class PurchaseError { class PurchaseIOS extends Purchase implements PurchaseCommon { const PurchaseIOS({ + this.advancedCommerceInfoIOS, this.appAccountToken, this.appBundleIdIOS, this.countryCodeIOS, @@ -2649,6 +2791,10 @@ class PurchaseIOS extends Purchase implements PurchaseCommon { this.isAlternativeBilling, }); + /// Advanced Commerce API metadata (iOS 18.4+). + /// Present only for transactions that use the Advanced Commerce API. + /// Contains item details, tax information, and refund data for generic SKU purchases. + final AdvancedCommerceInfoIOS? advancedCommerceInfoIOS; final String? appAccountToken; final String? appBundleIdIOS; final String? countryCodeIOS; @@ -2688,6 +2834,7 @@ class PurchaseIOS extends Purchase implements PurchaseCommon { factory PurchaseIOS.fromJson(Map json) { return PurchaseIOS( + advancedCommerceInfoIOS: json['advancedCommerceInfoIOS'] != null ? AdvancedCommerceInfoIOS.fromJson(json['advancedCommerceInfoIOS'] as Map) : null, appAccountToken: json['appAccountToken'] as String?, appBundleIdIOS: json['appBundleIdIOS'] as String?, countryCodeIOS: json['countryCodeIOS'] as String?, @@ -2730,6 +2877,7 @@ class PurchaseIOS extends Purchase implements PurchaseCommon { Map toJson() { return { '__typename': 'PurchaseIOS', + 'advancedCommerceInfoIOS': advancedCommerceInfoIOS?.toJson(), 'appAccountToken': appAccountToken, 'appBundleIdIOS': appBundleIdIOS, 'countryCodeIOS': countryCodeIOS, @@ -4830,6 +4978,11 @@ abstract class QueryResolver { }); /// Get active subscriptions (filters by subscriptionIds when provided) Future> getActiveSubscriptions([List? subscriptionIds]); + /// Get the full StoreKit 2 transaction history as PurchaseIOS values. + /// Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + /// for finished consumables to be included (iOS 18+). + /// Unlike getAvailablePurchases, always returns the iOS-specific PurchaseIOS shape. + Future> getAllTransactionsIOS(); /// Fetch the current app transaction (iOS 16+) Future getAppTransactionIOS(); /// Get all available purchases for the current user @@ -5030,6 +5183,7 @@ typedef QueryFetchProductsHandler = Future Function({ ProductQueryType? type, }); typedef QueryGetActiveSubscriptionsHandler = Future> Function([List? subscriptionIds]); +typedef QueryGetAllTransactionsIOSHandler = Future> Function(); typedef QueryGetAppTransactionIOSHandler = Future Function(); typedef QueryGetAvailablePurchasesHandler = Future> Function({ bool? alsoPublishToEventListenerIOS, @@ -5061,6 +5215,7 @@ class QueryHandlers { this.currentEntitlementIOS, this.fetchProducts, this.getActiveSubscriptions, + this.getAllTransactionsIOS, this.getAppTransactionIOS, this.getAvailablePurchases, this.getExternalPurchaseCustomLinkTokenIOS, @@ -5083,6 +5238,7 @@ class QueryHandlers { final QueryCurrentEntitlementIOSHandler? currentEntitlementIOS; final QueryFetchProductsHandler? fetchProducts; final QueryGetActiveSubscriptionsHandler? getActiveSubscriptions; + final QueryGetAllTransactionsIOSHandler? getAllTransactionsIOS; final QueryGetAppTransactionIOSHandler? getAppTransactionIOS; final QueryGetAvailablePurchasesHandler? getAvailablePurchases; final QueryGetExternalPurchaseCustomLinkTokenIOSHandler? getExternalPurchaseCustomLinkTokenIOS; diff --git a/packages/gql/src/generated/types.gd b/packages/gql/src/generated/types.gd index 88dbdb17..f1b51f5a 100644 --- a/packages/gql/src/generated/types.gd +++ b/packages/gql/src/generated/types.gd @@ -384,6 +384,161 @@ class ActiveSubscription: dict["renewalInfoIOS"] = renewal_info_ios return dict +## Advanced Commerce metadata from a transaction (iOS 18.4+). Contains item details, tax information, and refund data for purchases made through the Advanced Commerce API using generic SKUs. Only present for transactions that use the Advanced Commerce API. +class AdvancedCommerceInfoIOS: + ## The items purchased as part of this transaction + var items: Array[AdvancedCommerceItemIOS] = [] + ## Request reference identifier for tracking + var request_reference_id: Variant = null + ## Tax code for the transaction + var tax_code: Variant = null + ## Price excluding tax (decimal string) + var tax_exclusive_price: Variant = null + ## Estimated tax amount (decimal string) + var estimated_tax: Variant = null + ## Tax rate applied (decimal string) + var tax_rate: Variant = null + ## Optional display name + var display_name: Variant = null + ## Optional description + var description: Variant = null + + static func from_dict(data: Dictionary) -> AdvancedCommerceInfoIOS: + var obj = AdvancedCommerceInfoIOS.new() + if data.has("items") and data["items"] != null: + var arr = [] + for item in data["items"]: + if item is Dictionary: + arr.append(AdvancedCommerceItemIOS.from_dict(item)) + else: + arr.append(item) + obj.items = arr + if data.has("requestReferenceId") and data["requestReferenceId"] != null: + obj.request_reference_id = data["requestReferenceId"] + if data.has("taxCode") and data["taxCode"] != null: + obj.tax_code = data["taxCode"] + if data.has("taxExclusivePrice") and data["taxExclusivePrice"] != null: + obj.tax_exclusive_price = data["taxExclusivePrice"] + if data.has("estimatedTax") and data["estimatedTax"] != null: + obj.estimated_tax = data["estimatedTax"] + if data.has("taxRate") and data["taxRate"] != null: + obj.tax_rate = data["taxRate"] + if data.has("displayName") and data["displayName"] != null: + obj.display_name = data["displayName"] + if data.has("description") and data["description"] != null: + obj.description = data["description"] + return obj + + func to_dict() -> Dictionary: + var dict = {} + if items != null: + var arr = [] + for item in items: + if item != null and item.has_method("to_dict"): + arr.append(item.to_dict()) + else: + arr.append(item) + dict["items"] = arr + else: + dict["items"] = null + if request_reference_id != null: + dict["requestReferenceId"] = request_reference_id + if tax_code != null: + dict["taxCode"] = tax_code + if tax_exclusive_price != null: + dict["taxExclusivePrice"] = tax_exclusive_price + if estimated_tax != null: + dict["estimatedTax"] = estimated_tax + if tax_rate != null: + dict["taxRate"] = tax_rate + if display_name != null: + dict["displayName"] = display_name + if description != null: + dict["description"] = description + return dict + +## Details of an Advanced Commerce item (iOS 18.4+). +class AdvancedCommerceItemDetailsIOS: + ## JSON representation of the item details + var json_representation: Variant = null + + static func from_dict(data: Dictionary) -> AdvancedCommerceItemDetailsIOS: + var obj = AdvancedCommerceItemDetailsIOS.new() + if data.has("jsonRepresentation") and data["jsonRepresentation"] != null: + obj.json_representation = data["jsonRepresentation"] + return obj + + func to_dict() -> Dictionary: + var dict = {} + if json_representation != null: + dict["jsonRepresentation"] = json_representation + return dict + +## An item purchased through the Advanced Commerce API (iOS 18.4+). Represents a developer-defined product within a generic SKU transaction. +class AdvancedCommerceItemIOS: + ## The item's detail information + var details: AdvancedCommerceItemDetailsIOS + ## Refunds issued for this item, if any + var refunds: Array[AdvancedCommerceRefundIOS] = [] + ## Date access to this item was revoked (milliseconds since epoch) + var revocation_date: Variant = null + + static func from_dict(data: Dictionary) -> AdvancedCommerceItemIOS: + var obj = AdvancedCommerceItemIOS.new() + if data.has("details") and data["details"] != null: + if data["details"] is Dictionary: + obj.details = AdvancedCommerceItemDetailsIOS.from_dict(data["details"]) + else: + obj.details = data["details"] + if data.has("refunds") and data["refunds"] != null: + var arr = [] + for item in data["refunds"]: + if item is Dictionary: + arr.append(AdvancedCommerceRefundIOS.from_dict(item)) + else: + arr.append(item) + obj.refunds = arr + if data.has("revocationDate") and data["revocationDate"] != null: + obj.revocation_date = data["revocationDate"] + return obj + + func to_dict() -> Dictionary: + var dict = {} + if details != null and details.has_method("to_dict"): + dict["details"] = details.to_dict() + else: + dict["details"] = details + if refunds != null: + var arr = [] + for item in refunds: + if item != null and item.has_method("to_dict"): + arr.append(item.to_dict()) + else: + arr.append(item) + dict["refunds"] = arr + else: + dict["refunds"] = null + if revocation_date != null: + dict["revocationDate"] = revocation_date + return dict + +## Refund information for an Advanced Commerce item (iOS 18.4+). +class AdvancedCommerceRefundIOS: + ## JSON representation of the refund details + var json_representation: Variant = null + + static func from_dict(data: Dictionary) -> AdvancedCommerceRefundIOS: + var obj = AdvancedCommerceRefundIOS.new() + if data.has("jsonRepresentation") and data["jsonRepresentation"] != null: + obj.json_representation = data["jsonRepresentation"] + return obj + + func to_dict() -> Dictionary: + var dict = {} + if json_representation != null: + dict["jsonRepresentation"] = json_representation + return dict + class AppTransaction: var bundle_id: String = "" var app_version: String = "" @@ -2130,6 +2285,8 @@ class PurchaseIOS: var currency_symbol_ios: Variant = null var country_code_ios: Variant = null var renewal_info_ios: RenewalInfoIOS + ## Advanced Commerce API metadata (iOS 18.4+). + var advanced_commerce_info_ios: AdvancedCommerceInfoIOS static func from_dict(data: Dictionary) -> PurchaseIOS: var obj = PurchaseIOS.new() @@ -2219,6 +2376,11 @@ class PurchaseIOS: obj.renewal_info_ios = RenewalInfoIOS.from_dict(data["renewalInfoIOS"]) else: obj.renewal_info_ios = data["renewalInfoIOS"] + if data.has("advancedCommerceInfoIOS") and data["advancedCommerceInfoIOS"] != null: + if data["advancedCommerceInfoIOS"] is Dictionary: + obj.advanced_commerce_info_ios = AdvancedCommerceInfoIOS.from_dict(data["advancedCommerceInfoIOS"]) + else: + obj.advanced_commerce_info_ios = data["advancedCommerceInfoIOS"] return obj func to_dict() -> Dictionary: @@ -2294,6 +2456,10 @@ class PurchaseIOS: dict["renewalInfoIOS"] = renewal_info_ios.to_dict() else: dict["renewalInfoIOS"] = renewal_info_ios + if advanced_commerce_info_ios != null and advanced_commerce_info_ios.has_method("to_dict"): + dict["advancedCommerceInfoIOS"] = advanced_commerce_info_ios.to_dict() + else: + dict["advancedCommerceInfoIOS"] = advanced_commerce_info_ios return dict class PurchaseOfferIOS: @@ -4934,6 +5100,15 @@ class Query: const return_type = "AppTransaction" const is_array = false + ## Get the full StoreKit 2 transaction history as PurchaseIOS values. + class getAllTransactionsIOSField: + const name = "getAllTransactionsIOS" + const snake_name = "get_all_transactions_ios" + class Args: + pass + const return_type = "PurchaseIOS" + const is_array = true + ## Validate a receipt for a specific product class validateReceiptIOSField: const name = "validateReceiptIOS" @@ -5507,6 +5682,10 @@ static func get_receipt_data_ios_args() -> Dictionary: static func get_app_transaction_ios_args() -> Dictionary: return {} +## Get the full StoreKit 2 transaction history as PurchaseIOS values. +static func get_all_transactions_ios_args() -> Dictionary: + return {} + ## Validate a receipt for a specific product static func validate_receipt_ios_args(options: VerifyPurchaseProps) -> Dictionary: var args = {} diff --git a/packages/gql/src/generated/types.ts b/packages/gql/src/generated/types.ts index 9a4c0338..4afb30b7 100644 --- a/packages/gql/src/generated/types.ts +++ b/packages/gql/src/generated/types.ts @@ -36,6 +36,56 @@ export interface ActiveSubscription { willExpireSoon?: (boolean | null); } +/** + * Advanced Commerce metadata from a transaction (iOS 18.4+). + * Contains item details, tax information, and refund data for purchases + * made through the Advanced Commerce API using generic SKUs. + * Only present for transactions that use the Advanced Commerce API. + */ +export interface AdvancedCommerceInfoIOS { + /** Optional description */ + description?: (string | null); + /** Optional display name */ + displayName?: (string | null); + /** Estimated tax amount (decimal string) */ + estimatedTax?: (string | null); + /** The items purchased as part of this transaction */ + items: AdvancedCommerceItemIOS[]; + /** Request reference identifier for tracking */ + requestReferenceId?: (string | null); + /** Tax code for the transaction */ + taxCode?: (string | null); + /** Price excluding tax (decimal string) */ + taxExclusivePrice?: (string | null); + /** Tax rate applied (decimal string) */ + taxRate?: (string | null); +} + +/** Details of an Advanced Commerce item (iOS 18.4+). */ +export interface AdvancedCommerceItemDetailsIOS { + /** JSON representation of the item details */ + jsonRepresentation?: (string | null); +} + +/** + * An item purchased through the Advanced Commerce API (iOS 18.4+). + * Represents a developer-defined product within a generic SKU transaction. + */ +export interface AdvancedCommerceItemIOS { + /** The item's detail information */ + details?: (AdvancedCommerceItemDetailsIOS | null); + /** Refunds issued for this item, if any */ + refunds?: (AdvancedCommerceRefundIOS[] | null); + /** Date access to this item was revoked (milliseconds since epoch) */ + revocationDate?: (number | null); +} + +/** Refund information for an Advanced Commerce item (iOS 18.4+). */ +export interface AdvancedCommerceRefundIOS { + /** JSON representation of the refund details */ + jsonRepresentation?: (string | null); +} + /** * Alternative billing mode for Android * Controls which billing system is used @@ -1112,6 +1162,12 @@ export interface PurchaseError { } export interface PurchaseIOS extends PurchaseCommon { + /** + * Advanced Commerce API metadata (iOS 18.4+). + * Present only for transactions that use the Advanced Commerce API. + * Contains item details, tax information, and refund data for generic SKU purchases. + */ + advancedCommerceInfoIOS?: (AdvancedCommerceInfoIOS | null); appAccountToken?: (string | null); appBundleIdIOS?: (string | null); countryCodeIOS?: (string | null); @@ -1188,6 +1244,13 @@ export interface Query { fetchProducts: Promise<(ProductOrSubscription[] | Product[] | ProductSubscription[] | null)>; /** Get active subscriptions (filters by subscriptionIds when provided) */ getActiveSubscriptions: Promise; + /** + * Get the full StoreKit 2 transaction history as PurchaseIOS values. + * Requires the SK2ConsumableTransactionHistory Info.plist key in the host app + * for finished consumables to be included (iOS 18+). + * Unlike getAvailablePurchases, always returns the iOS-specific PurchaseIOS shape. + */ + getAllTransactionsIOS: Promise; /** Fetch the current app transaction (iOS 16+) */ getAppTransactionIOS?: Promise<(AppTransaction | null)>; /** Get all available purchases for the current user */ @@ -1901,6 +1964,7 @@ export type QueryArgsMap = { currentEntitlementIOS: QueryCurrentEntitlementIosArgs; fetchProducts: QueryFetchProductsArgs; getActiveSubscriptions: QueryGetActiveSubscriptionsArgs; + getAllTransactionsIOS: never; getAppTransactionIOS: never; getAvailablePurchases: QueryGetAvailablePurchasesArgs; getExternalPurchaseCustomLinkTokenIOS: QueryGetExternalPurchaseCustomLinkTokenIosArgs; diff --git a/packages/gql/src/type-ios.graphql b/packages/gql/src/type-ios.graphql index 796767a2..a9dc6990 100644 --- a/packages/gql/src/type-ios.graphql +++ b/packages/gql/src/type-ios.graphql @@ -210,6 +210,12 @@ type PurchaseIOS implements PurchaseCommon { currencySymbolIOS: String countryCodeIOS: String renewalInfoIOS: RenewalInfoIOS + """ + Advanced Commerce API metadata (iOS 18.4+). + Present only for transactions that use the Advanced Commerce API. + Contains item details, tax information, and refund data for generic SKU purchases. + """ + advancedCommerceInfoIOS: AdvancedCommerceInfoIOS } type PurchaseOfferIOS { @@ -610,3 +616,89 @@ type ExternalPurchaseCustomLinkTokenResultIOS { """ error: String } + +# ============================================================================ +# Advanced Commerce API Types (iOS 18.4+) +# For apps using the Advanced Commerce API with generic SKU purchases +# Reference: https://developer.apple.com/documentation/storekit/transaction/advancedcommerceinfo +# ============================================================================ + +""" +Advanced Commerce metadata from a transaction (iOS 18.4+). +Contains item details, tax information, and refund data for purchases +made through the Advanced Commerce API using generic SKUs. +Only present for transactions that use the Advanced Commerce API. +""" +type AdvancedCommerceInfoIOS { + """ + The items purchased as part of this transaction + """ + items: [AdvancedCommerceItemIOS!]! + """ + Request reference identifier for tracking + """ + requestReferenceId: String + """ + Tax code for the transaction + """ + taxCode: String + """ + Price excluding tax (decimal string) + """ + taxExclusivePrice: String + """ + Estimated tax amount (decimal string) + """ + estimatedTax: String + """ + Tax rate applied (decimal string) + """ + taxRate: String + """ + Optional display name + """ + displayName: String + """ + Optional description + """ + description: String +} + +""" +An item purchased through the Advanced Commerce API (iOS 18.4+). +Represents a developer-defined product within a generic SKU transaction. +""" +type AdvancedCommerceItemIOS { + """ + The item's detail information + """ + details: AdvancedCommerceItemDetailsIOS + """ + Refunds issued for this item, if any + """ + refunds: [AdvancedCommerceRefundIOS!] + """ + Date access to this item was revoked (milliseconds since epoch) + """ + revocationDate: Float +} + +""" +Details of an Advanced Commerce item (iOS 18.4+). +""" +type AdvancedCommerceItemDetailsIOS { + """ + JSON representation of the item details + """ + jsonRepresentation: String +} + +""" +Refund information for an Advanced Commerce item (iOS 18.4+). +""" +type AdvancedCommerceRefundIOS { + """ + JSON representation of the refund details + """ + jsonRepresentation: String +}