From 309aa7303530ae2f19ef701bde0ed2b6f89d6fc9 Mon Sep 17 00:00:00 2001 From: Hyo Date: Tue, 23 Dec 2025 23:40:00 +0900 Subject: [PATCH 1/4] feat: add advancedCommerceDataIOS support for StoreKit 2 attribution --- .gitignore | 1 + CHANGELOG.md | 301 +----------------- .../Sources/Helpers/StoreKitTypesBridge.swift | 18 ++ packages/apple/Sources/Models/Types.swift | 14 + packages/apple/Sources/OpenIapModule.swift | 1 + packages/apple/Sources/OpenIapStore.swift | 5 +- packages/apple/Tests/OpenIapTests.swift | 80 +++++ .../docs/src/pages/docs/types/request.tsx | 17 + .../docs/src/pages/docs/updates/notes.tsx | 77 +++++ .../src/main/java/dev/hyo/openiap/Types.kt | 18 ++ packages/gql/src/generated/Types.kt | 18 ++ packages/gql/src/generated/Types.swift | 14 + packages/gql/src/generated/types.dart | 24 ++ packages/gql/src/generated/types.ts | 14 + packages/gql/src/type-ios.graphql | 14 + 15 files changed, 315 insertions(+), 301 deletions(-) diff --git a/.gitignore b/.gitignore index 8eb3aa2e..9d5a7f19 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ coverage/ # Temp tmp/ temp/ +.build \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 53b7bb2f..1a6f852f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,302 +1,3 @@ # Changelog -All notable changes to the OpenIAP monorepo will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [openiap-gql 1.3.2] - 2025-12-11 - -### Added - -#### Google Play Billing Library 8.1.0 Support - -- **`SubscriptionProductReplacementParamsAndroid`**: New type for per-product subscription replacement configuration - - `oldProductId`: The product ID being replaced - - `replacementMode`: The replacement mode enum value -- **`SubscriptionReplacementModeAndroid.KeepExisting`**: New replacement mode (8.1.0+) to keep the existing payment schedule unchanged - -#### Google Play Billing Library 8.2.0 Support (Billing Programs API) - -- **`BillingProgramAndroid`**: Enum for billing program types - - `ExternalContentLink`: For apps linking to external content (reader apps, music streaming) - - `ExternalOffer`: For apps offering alternative payment options - - `Unspecified`: Default/unspecified value -- **`BillingProgramAvailabilityResultAndroid`**: Result type for billing program availability checks - - `billingProgram`: The program that was checked - - `isAvailable`: Whether the program is available -- **`BillingProgramReportingDetailsAndroid`**: Reporting details for external transactions - - `billingProgram`: The billing program used - - `externalTransactionToken`: Token for reporting to Google Play -- **`LaunchExternalLinkParamsAndroid`**: Parameters for launching external links - - `billingProgram`: Which billing program to use - - `launchMode`: How to launch the link - - `linkType`: Type of external link - - `linkUri`: The URI to launch -- **`ExternalLinkLaunchModeAndroid`**: Enum for external link launch modes - - `LaunchInExternalBrowserOrApp`: Launch in external browser or app - - `CallerWillLaunchLink`: Caller handles the link launch - - `Unspecified`: Default value -- **`ExternalLinkTypeAndroid`**: Enum for external link types - - `LinkToDigitalContentOffer`: Link to digital content offer - - `LinkToAppDownload`: Link to app download - - `Unspecified`: Default value - -### Changed - -- Updated `RequestSubscriptionAndroidProps` to include optional `subscriptionProductReplacementParams` field - ---- - -## [openiap-google 1.3.12] - 2025-12-11 - -### Added - -#### Google Play Billing Library 8.1.0 APIs - -- **`applySubscriptionProductReplacementParams()`**: Apply per-product replacement params to subscription upgrades/downgrades - - Enables `KeepExisting` replacement mode (only available via this API) - - More granular control over subscription replacements at the product level - -#### Google Play Billing Library 8.2.0 APIs (Billing Programs) - -- **`enableBillingProgram(program: BillingProgramAndroid)`**: Enable a billing program before `initConnection()` - - Must be called before connecting to configure the BillingClient - - Available via both `OpenIapModule` and `OpenIapStore` -- **`isBillingProgramAvailable(program: BillingProgramAndroid)`**: Check if a billing program is available - - Replaces deprecated `checkAlternativeBillingAvailability()` for external offers - - Returns `BillingProgramAvailabilityResultAndroid` with availability status -- **`createBillingProgramReportingDetails(program: BillingProgramAndroid)`**: Create reporting details for external transactions - - Replaces deprecated `createAlternativeBillingReportingToken()` - - Returns `BillingProgramReportingDetailsAndroid` with `externalTransactionToken` -- **`launchExternalLink(activity: Activity, params: LaunchExternalLinkParamsAndroid)`**: Launch external link for external offers - - Replaces deprecated `showAlternativeBillingInformationDialog()` - - Supports configurable launch modes and link types - -### Deprecated - -The following APIs are deprecated in favor of the new Billing Programs API (8.2.0+): - -- `checkAlternativeBillingAvailability()` → Use `isBillingProgramAvailable(BillingProgramAndroid.ExternalOffer)` -- `showAlternativeBillingInformationDialog()` → Use `launchExternalLink(activity, params)` -- `createAlternativeBillingReportingToken()` → Use `createBillingProgramReportingDetails(BillingProgramAndroid.ExternalOffer)` - -### Changed - -- **Example App (`AlternativeBillingScreen`)**: Updated to demonstrate all three billing modes: - - Billing Programs (8.2.0+) - Recommended - - Alternative Billing Only (Legacy 6.2+) - - User Choice Billing (Legacy 7.0+) -- **Error Handling**: Improved exception propagation in Proxy handlers using `resumeWithException()` -- **Null Safety**: Added null-safe activity handling to prevent potential NPE - -### Fixed - -- Empty catch blocks now properly log errors and display status messages to users -- Exception handling in coroutine Proxy handlers now correctly propagates exceptions - -### Documentation - -- Updated `external-purchase.tsx` with Billing Programs API (8.2.0+) documentation -- Added API Migration Guide table for legacy to new API mapping -- Updated Implementation Flow section with new step-by-step guide -- Added code examples for TypeScript, Kotlin, and Dart - ---- - -## [openiap-gql 1.3.1 / openiap-google 1.3.11] - 2025-12-10 - -### Added - -#### One-Time Purchase Discount Offers (Google Play Billing 7.0+) - -- **`oneTimePurchaseOfferDetailsAndroid`**: Changed from single object to array to support multiple discount offers -- **`DiscountDisplayInfoAndroid`**: Discount display information - - `discountPercent`: Percentage off - - `discountAmount`: Discount amount details -- **`DiscountAmountAndroid`**: Discount amount with currency -- **`ValidTimeWindowAndroid`**: Start and end time for limited-time offers -- **`LimitedQuantityInfoAndroid`**: Limited quantity information -- **`RentalDetailsAndroid`**: Rental product metadata - -#### Google Play Billing 8.1.0 Support - -- **`PreorderDetailsAndroid`**: Pre-order product details - - `preorderPresaleEndTimeMillis`: Presale end time - - `preorderReleaseTimeMillis`: Release time -- **`isSuspendedAndroid`**: Detect suspended subscriptions due to payment failures - -### Changed - -- **Upgraded Google Play Billing Library**: 8.0.0 → 8.1.0 -- **Increased minSdk**: 21 → 23 (Android 6.0) -- **Upgraded Kotlin**: 2.0.21 → 2.2.0 - -### Breaking Changes - -- **`oneTimePurchaseOfferDetailsAndroid`** type changed from single object to array - - Before: `product.oneTimePurchaseOfferDetailsAndroid?.formattedPrice` - - After: `product.oneTimePurchaseOfferDetailsAndroid?.firstOrNull()?.formattedPrice` - -### Documentation - -- Added comprehensive "Discounts (Android)" documentation -- Updated types page with new Android fields - ---- - -## [openiap-gql 1.3.0 / openiap-google 1.3.0 / openiap-apple 1.3.0] - 2025-12-08 - -### Added - -- **`IapStore` enum**: Unified store identification - - `Unknown`: Unknown store - - `Apple`: Apple App Store - - `Google`: Google Play Store - - `Horizon`: Meta Horizon Store -- **`store` field**: Added to `PurchaseCommon` interface for consistent store identification -- **`verifyPurchaseWithProvider()`**: Server-side purchase verification API - -### Changed - -- **Renamed request props**: `ios`/`android` → `apple`/`google` in request payloads - - `RequestPurchasePropsByPlatforms`: `ios` → `apple`, `android` → `google` - - `RequestSubscriptionPropsByPlatforms`: `ios` → `apple`, `android` → `google` - -### Deprecated - -- **`platform` field**: Use `store` field instead -- **`ios`/`android` props**: Use `apple`/`google` props in request payloads - -### Migration Guide - -**Request props migration:** -```typescript -// Before (deprecated) -requestPurchase({ - request: { - ios: { sku: 'product_id' }, - android: { skus: ['product_id'] } - } -}); - -// After (recommended) -requestPurchase({ - request: { - apple: { sku: 'product_id' }, - google: { skus: ['product_id'] } - } -}); -``` - -**Platform field migration:** -```typescript -// Before (deprecated) -if (purchase.platform === 'ios') { ... } - -// After (recommended) -if (purchase.store === 'Apple') { ... } -``` - ---- - -## [1.2.2] - 2025-10-16 - -### Added - -#### Monorepo Integration - -- **Unified monorepo structure**: Consolidated all OpenIAP packages into a single monorepo for better maintainability and version synchronization -- **Centralized version management**: Single `openiap-versions.json` at root manages versions across all packages -- **Unified agent guidelines**: Consolidated `CLAUDE.md` with symlinks for `AGENTS.md` and `GEMINI.md` to provide consistent AI agent guidelines - -#### Documentation - -- **Enhanced documentation site** (`packages/docs`): - - Improved API reference with consistent naming conventions - - Platform-specific function documentation (iOS/Android suffixes) - - Updated component architecture and code examples - -#### Type Generation - -- **Improved type generation workflow** (`packages/gql`): - - Multi-platform type generation (TypeScript, Swift, Kotlin, Dart) - - Automatic synchronization to platform packages - - Streamlined generation scripts - -#### Deployment - -- **Automated deployment workflow**: - - `npm run deploy ` command for production deployments - - Local Vercel deployment for documentation site - - GitHub Actions workflow for release artifacts and tagging - - Automated artifact generation for all platforms (TypeScript, Dart, Kotlin, Swift) - -#### Platform Libraries - -- **Google (Android)** (`packages/google`): - - Updated to version 1.2.12 - - Improved Kotlin type generation - - Better Gradle build configuration - -- **Apple (iOS/macOS)** (`packages/apple`): - - Updated to version 1.2.23 - - Enhanced Swift type generation - - Improved Package.swift configuration - -### Changed - -#### Repository Structure - -- Migrated from separate repositories to unified monorepo -- All packages now share common tooling and CI/CD workflows -- Centralized dependency management - -#### Version Management - -- Changed from separate version files to unified `openiap-versions.json` -- All packages reference the root version file via symlinks -- Simplified version bumping and synchronization - -#### CI/CD - -- Updated GitHub Actions workflows for monorepo structure -- Separate workflows for platform-specific releases -- Unified release workflow for type artifacts - -### Migration Notes - -**For maintainers:** - -- The monorepo is now the source of truth for all OpenIAP packages -- Version updates should be made in the root `openiap-versions.json` -- All agent guidelines are centralized in root `CLAUDE.md` - -**For contributors:** - -- Clone the monorepo instead of individual package repositories -- Run `bun install` at the root to install all dependencies -- Use workspace commands to work with specific packages (e.g., `bun run --filter docs build`) - -**For consumers:** - -- No breaking changes to public APIs -- Continue using platform-specific packages as before -- Artifacts are now available from the monorepo releases - ---- - -## Pre-1.2.2 (Historical) - -Prior to version 1.2.2, OpenIAP packages were maintained in separate repositories: - -- `openiap-gql`: GraphQL schema and type generation -- `openiap-google`: Android library -- `openiap-apple`: iOS/macOS library -- `openiap.dev`: Documentation site - -These have been consolidated into this monorepo as of version 1.2.2. - ---- - -[1.2.2]: https://github.com/hyodotdev/openiap/releases/tag/v1.2.2 +For detailed release notes, see: https://www.openiap.dev/docs/updates/notes diff --git a/packages/apple/Sources/Helpers/StoreKitTypesBridge.swift b/packages/apple/Sources/Helpers/StoreKitTypesBridge.swift index dd4f9d7b..f5eabaf5 100644 --- a/packages/apple/Sources/Helpers/StoreKitTypesBridge.swift +++ b/packages/apple/Sources/Helpers/StoreKitTypesBridge.swift @@ -373,6 +373,24 @@ enum StoreKitTypesBridge { } options.insert(option) } + // Advanced Commerce Data (iOS 15+) + // Used with StoreKit 2's Product.PurchaseOption.custom API for passing + // campaign tokens, affiliate IDs, or other attribution data + if let advancedCommerceData = props.advancedCommerceDataIOS, !advancedCommerceData.isEmpty { + let payload: [String: Any] = ["signatureInfo": ["token": advancedCommerceData]] + do { + let jsonData = try JSONSerialization.data(withJSONObject: payload) + options.insert(.custom(key: "advancedCommerceData", value: jsonData)) + OpenIapLog.debug("✅ Added advancedCommerceData purchase option") + } catch { + OpenIapLog.error("❌ Failed to serialize advancedCommerceData: \(error.localizedDescription)") + throw PurchaseError.make( + code: .developerError, + productId: props.sku, + message: "Failed to serialize advancedCommerceData: \(error.localizedDescription)" + ) + } + } return options } } diff --git a/packages/apple/Sources/Models/Types.swift b/packages/apple/Sources/Models/Types.swift index a56f0602..11f35a6a 100644 --- a/packages/apple/Sources/Models/Types.swift +++ b/packages/apple/Sources/Models/Types.swift @@ -1055,6 +1055,11 @@ public struct RequestPurchaseAndroidProps: Codable { } public struct RequestPurchaseIosProps: Codable { + /// Advanced commerce data token (iOS 15+). + /// Used with StoreKit 2's Product.PurchaseOption.custom API for passing + /// campaign tokens, affiliate IDs, or other attribution data. + /// The data is formatted as JSON: {"signatureInfo": {"token": ""}} + public var advancedCommerceDataIOS: String? /// Auto-finish transaction (dangerous) public var andDangerouslyFinishTransactionAutomatically: Bool? /// App account token for user tracking @@ -1067,12 +1072,14 @@ public struct RequestPurchaseIosProps: Codable { public var withOffer: DiscountOfferInputIOS? public init( + advancedCommerceDataIOS: String? = nil, andDangerouslyFinishTransactionAutomatically: Bool? = nil, appAccountToken: String? = nil, quantity: Int? = nil, sku: String, withOffer: DiscountOfferInputIOS? = nil ) { + self.advancedCommerceDataIOS = advancedCommerceDataIOS self.andDangerouslyFinishTransactionAutomatically = andDangerouslyFinishTransactionAutomatically self.appAccountToken = appAccountToken self.quantity = quantity @@ -1222,6 +1229,11 @@ public struct RequestSubscriptionAndroidProps: Codable { } public struct RequestSubscriptionIosProps: Codable { + /// Advanced commerce data token (iOS 15+). + /// Used with StoreKit 2's Product.PurchaseOption.custom API for passing + /// campaign tokens, affiliate IDs, or other attribution data. + /// The data is formatted as JSON: {"signatureInfo": {"token": ""}} + public var advancedCommerceDataIOS: String? public var andDangerouslyFinishTransactionAutomatically: Bool? public var appAccountToken: String? public var quantity: Int? @@ -1229,12 +1241,14 @@ public struct RequestSubscriptionIosProps: Codable { public var withOffer: DiscountOfferInputIOS? public init( + advancedCommerceDataIOS: String? = nil, andDangerouslyFinishTransactionAutomatically: Bool? = nil, appAccountToken: String? = nil, quantity: Int? = nil, sku: String, withOffer: DiscountOfferInputIOS? = nil ) { + self.advancedCommerceDataIOS = advancedCommerceDataIOS self.andDangerouslyFinishTransactionAutomatically = andDangerouslyFinishTransactionAutomatically self.appAccountToken = appAccountToken self.quantity = quantity diff --git a/packages/apple/Sources/OpenIapModule.swift b/packages/apple/Sources/OpenIapModule.swift index 7d12c463..daed729f 100644 --- a/packages/apple/Sources/OpenIapModule.swift +++ b/packages/apple/Sources/OpenIapModule.swift @@ -1250,6 +1250,7 @@ public final class OpenIapModule: NSObject, OpenIapModuleProtocol { case let .subscription(platforms): if let ios = platforms.ios { return RequestPurchaseIosProps( + advancedCommerceDataIOS: ios.advancedCommerceDataIOS, andDangerouslyFinishTransactionAutomatically: ios.andDangerouslyFinishTransactionAutomatically, appAccountToken: ios.appAccountToken, quantity: ios.quantity, diff --git a/packages/apple/Sources/OpenIapStore.swift b/packages/apple/Sources/OpenIapStore.swift index 5a767187..a1064157 100644 --- a/packages/apple/Sources/OpenIapStore.swift +++ b/packages/apple/Sources/OpenIapStore.swift @@ -251,11 +251,13 @@ public final class OpenIapStore: ObservableObject { autoFinish: Bool? = nil, quantity: Int? = nil, appAccountToken: String? = nil, - withOffer: DiscountOfferInputIOS? = nil + withOffer: DiscountOfferInputIOS? = nil, + advancedCommerceDataIOS: String? = nil ) async throws -> OpenIAP.Purchase? { switch type { case .subs: let iosProps = RequestSubscriptionIosProps( + advancedCommerceDataIOS: advancedCommerceDataIOS, andDangerouslyFinishTransactionAutomatically: autoFinish, appAccountToken: appAccountToken, quantity: quantity, @@ -269,6 +271,7 @@ public final class OpenIapStore: ObservableObject { return try await requestPurchase(request) default: let iosProps = RequestPurchaseIosProps( + advancedCommerceDataIOS: advancedCommerceDataIOS, andDangerouslyFinishTransactionAutomatically: autoFinish, appAccountToken: appAccountToken, quantity: quantity, diff --git a/packages/apple/Tests/OpenIapTests.swift b/packages/apple/Tests/OpenIapTests.swift index d3878288..b83ca40b 100644 --- a/packages/apple/Tests/OpenIapTests.swift +++ b/packages/apple/Tests/OpenIapTests.swift @@ -295,6 +295,86 @@ final class OpenIapTests: XCTestCase { XCTAssertNil(ErrorCode(rawValue: "nonexistent")) } + // MARK: - Advanced Commerce Data Tests + + func testRequestPurchaseIosPropsWithAdvancedCommerceData() throws { + let props = RequestPurchaseIosProps( + advancedCommerceDataIOS: "campaign_summer_2025", + andDangerouslyFinishTransactionAutomatically: false, + appAccountToken: "user-uuid", + quantity: 1, + sku: "dev.hyo.premium", + withOffer: nil + ) + + XCTAssertEqual(props.sku, "dev.hyo.premium") + XCTAssertEqual(props.advancedCommerceDataIOS, "campaign_summer_2025") + XCTAssertEqual(props.appAccountToken, "user-uuid") + XCTAssertEqual(props.quantity, 1) + XCTAssertEqual(props.andDangerouslyFinishTransactionAutomatically, false) + + // Test encoding/decoding + let data = try JSONEncoder().encode(props) + let decoded = try JSONDecoder().decode(RequestPurchaseIosProps.self, from: data) + XCTAssertEqual(decoded.advancedCommerceDataIOS, "campaign_summer_2025") + XCTAssertEqual(decoded.sku, "dev.hyo.premium") + } + + func testRequestSubscriptionIosPropsWithAdvancedCommerceData() throws { + let props = RequestSubscriptionIosProps( + advancedCommerceDataIOS: "affiliate_partner_123", + andDangerouslyFinishTransactionAutomatically: nil, + appAccountToken: nil, + quantity: nil, + sku: "dev.hyo.subscription.monthly", + withOffer: nil + ) + + XCTAssertEqual(props.sku, "dev.hyo.subscription.monthly") + XCTAssertEqual(props.advancedCommerceDataIOS, "affiliate_partner_123") + + // Test encoding/decoding + let data = try JSONEncoder().encode(props) + let decoded = try JSONDecoder().decode(RequestSubscriptionIosProps.self, from: data) + XCTAssertEqual(decoded.advancedCommerceDataIOS, "affiliate_partner_123") + } + + func testRequestPurchaseIosPropsWithoutAdvancedCommerceData() throws { + let props = RequestPurchaseIosProps( + advancedCommerceDataIOS: nil, + andDangerouslyFinishTransactionAutomatically: nil, + appAccountToken: nil, + quantity: nil, + sku: "dev.hyo.consumable", + withOffer: nil + ) + + XCTAssertEqual(props.sku, "dev.hyo.consumable") + XCTAssertNil(props.advancedCommerceDataIOS) + + // Test encoding/decoding + let data = try JSONEncoder().encode(props) + let decoded = try JSONDecoder().decode(RequestPurchaseIosProps.self, from: data) + XCTAssertNil(decoded.advancedCommerceDataIOS) + } + + func testAdvancedCommerceDataJSONSerialization() throws { + let props = RequestPurchaseIosProps( + advancedCommerceDataIOS: "promo_code_abc", + andDangerouslyFinishTransactionAutomatically: nil, + appAccountToken: nil, + quantity: nil, + sku: "dev.hyo.premium", + withOffer: nil + ) + + let data = try JSONEncoder().encode(props) + let jsonString = String(data: data, encoding: .utf8)! + + XCTAssertTrue(jsonString.contains("advancedCommerceDataIOS")) + XCTAssertTrue(jsonString.contains("promo_code_abc")) + } + func testErrorCodeJSONDecoding() throws { // Test decoding from JSON with camelCase (react-native-iap format) let jsonCamel = """ diff --git a/packages/docs/src/pages/docs/types/request.tsx b/packages/docs/src/pages/docs/types/request.tsx index bf5a85ca..3d09aff7 100644 --- a/packages/docs/src/pages/docs/types/request.tsx +++ b/packages/docs/src/pages/docs/types/request.tsx @@ -434,6 +434,23 @@ await FlutterInappPurchase.instance.requestPurchase( Promotional/discount offer to apply (see DiscountOffer) + + + advancedCommerceDataIOS + + + Attribution data token for StoreKit 2's{' '} + + Product.PurchaseOption.custom + {' '} + API. Used for campaign tokens, affiliate IDs, or other + attribution data. (iOS 15+) + + externalPurchaseUrlOnIOS diff --git a/packages/docs/src/pages/docs/updates/notes.tsx b/packages/docs/src/pages/docs/updates/notes.tsx index 86f1fae1..f90a9b86 100644 --- a/packages/docs/src/pages/docs/updates/notes.tsx +++ b/packages/docs/src/pages/docs/updates/notes.tsx @@ -23,6 +23,83 @@ function Notes() { useScrollToHash(); const allNotes: Note[] = [ + // v1.3.6 Advanced Commerce Data - Dec 23, 2025 + { + id: 'v1.3.6-advanced-commerce', + date: new Date('2025-12-23'), + element: ( +
+

+ 📅 openiap-gql v1.3.6 / openiap-apple v1.3.6 - Advanced Commerce Data + Support (iOS 15+) +

+

+ New Feature: Advanced Commerce Data +

+

+ Added support for{' '} + + StoreKit 2's Product.PurchaseOption.custom API + {' '} + to pass attribution data during purchases. +

+
    +
  • + + advancedCommerceDataIOS + {' '} + - New optional field in RequestPurchaseIosProps and{' '} + RequestSubscriptionIosProps +
  • +
  • + Enables passing campaign tokens, affiliate IDs, and other + attribution data to StoreKit during purchase +
  • +
  • + Data is formatted as JSON:{' '} + {`{"signatureInfo": {"token": ""}}`} +
  • +
+

+ Usage: +

+ + {`requestPurchase({ + request: { + apple: { + sku: 'com.example.premium', + advancedCommerceDataIOS: 'campaign_summer_2025', + } + }, + type: 'in-app' +});`} + +

+ Use Cases: +

+
    +
  • Campaign attribution tracking
  • +
  • Affiliate marketing integration
  • +
  • Promotional code tracking
  • +
+

+ Reference:{' '} + + react-native-iap PR #3106 + +

+
+ ), + }, + // v1.3.5 Tag Management - Dec 16, 2025 { id: 'v1.3.5-tag', 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 4aefb97f..ef7bfe46 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 @@ -2703,6 +2703,13 @@ public data class RequestPurchaseAndroidProps( } public data class RequestPurchaseIosProps( + /** + * Advanced commerce data token (iOS 15+). + * Used with StoreKit 2's Product.PurchaseOption.custom API for passing + * campaign tokens, affiliate IDs, or other attribution data. + * The data is formatted as JSON: {"signatureInfo": {"token": ""}} + */ + val advancedCommerceDataIOS: String? = null, /** * Auto-finish transaction (dangerous) */ @@ -2727,6 +2734,7 @@ public data class RequestPurchaseIosProps( companion object { fun fromJson(json: Map): RequestPurchaseIosProps { return RequestPurchaseIosProps( + advancedCommerceDataIOS = json["advancedCommerceDataIOS"] as String?, andDangerouslyFinishTransactionAutomatically = json["andDangerouslyFinishTransactionAutomatically"] as Boolean?, appAccountToken = json["appAccountToken"] as String?, quantity = (json["quantity"] as Number?)?.toInt(), @@ -2737,6 +2745,7 @@ public data class RequestPurchaseIosProps( } fun toJson(): Map = mapOf( + "advancedCommerceDataIOS" to advancedCommerceDataIOS, "andDangerouslyFinishTransactionAutomatically" to andDangerouslyFinishTransactionAutomatically, "appAccountToken" to appAccountToken, "quantity" to quantity, @@ -2907,6 +2916,13 @@ public data class RequestSubscriptionAndroidProps( } public data class RequestSubscriptionIosProps( + /** + * Advanced commerce data token (iOS 15+). + * Used with StoreKit 2's Product.PurchaseOption.custom API for passing + * campaign tokens, affiliate IDs, or other attribution data. + * The data is formatted as JSON: {"signatureInfo": {"token": ""}} + */ + val advancedCommerceDataIOS: String? = null, val andDangerouslyFinishTransactionAutomatically: Boolean? = null, val appAccountToken: String? = null, val quantity: Int? = null, @@ -2916,6 +2932,7 @@ public data class RequestSubscriptionIosProps( companion object { fun fromJson(json: Map): RequestSubscriptionIosProps { return RequestSubscriptionIosProps( + advancedCommerceDataIOS = json["advancedCommerceDataIOS"] as String?, andDangerouslyFinishTransactionAutomatically = json["andDangerouslyFinishTransactionAutomatically"] as Boolean?, appAccountToken = json["appAccountToken"] as String?, quantity = (json["quantity"] as Number?)?.toInt(), @@ -2926,6 +2943,7 @@ public data class RequestSubscriptionIosProps( } fun toJson(): Map = mapOf( + "advancedCommerceDataIOS" to advancedCommerceDataIOS, "andDangerouslyFinishTransactionAutomatically" to andDangerouslyFinishTransactionAutomatically, "appAccountToken" to appAccountToken, "quantity" to quantity, diff --git a/packages/gql/src/generated/Types.kt b/packages/gql/src/generated/Types.kt index 304fe050..4bad8c03 100644 --- a/packages/gql/src/generated/Types.kt +++ b/packages/gql/src/generated/Types.kt @@ -2775,6 +2775,13 @@ public data class RequestPurchaseAndroidProps( } public data class RequestPurchaseIosProps( + /** + * Advanced commerce data token (iOS 15+). + * Used with StoreKit 2's Product.PurchaseOption.custom API for passing + * campaign tokens, affiliate IDs, or other attribution data. + * The data is formatted as JSON: {"signatureInfo": {"token": ""}} + */ + val advancedCommerceDataIOS: String? = null, /** * Auto-finish transaction (dangerous) */ @@ -2799,6 +2806,7 @@ public data class RequestPurchaseIosProps( companion object { fun fromJson(json: Map): RequestPurchaseIosProps { return RequestPurchaseIosProps( + advancedCommerceDataIOS = json["advancedCommerceDataIOS"] as String?, andDangerouslyFinishTransactionAutomatically = json["andDangerouslyFinishTransactionAutomatically"] as Boolean?, appAccountToken = json["appAccountToken"] as String?, quantity = (json["quantity"] as Number?)?.toInt(), @@ -2809,6 +2817,7 @@ public data class RequestPurchaseIosProps( } fun toJson(): Map = mapOf( + "advancedCommerceDataIOS" to advancedCommerceDataIOS, "andDangerouslyFinishTransactionAutomatically" to andDangerouslyFinishTransactionAutomatically, "appAccountToken" to appAccountToken, "quantity" to quantity, @@ -2979,6 +2988,13 @@ public data class RequestSubscriptionAndroidProps( } public data class RequestSubscriptionIosProps( + /** + * Advanced commerce data token (iOS 15+). + * Used with StoreKit 2's Product.PurchaseOption.custom API for passing + * campaign tokens, affiliate IDs, or other attribution data. + * The data is formatted as JSON: {"signatureInfo": {"token": ""}} + */ + val advancedCommerceDataIOS: String? = null, val andDangerouslyFinishTransactionAutomatically: Boolean? = null, val appAccountToken: String? = null, val quantity: Int? = null, @@ -2988,6 +3004,7 @@ public data class RequestSubscriptionIosProps( companion object { fun fromJson(json: Map): RequestSubscriptionIosProps { return RequestSubscriptionIosProps( + advancedCommerceDataIOS = json["advancedCommerceDataIOS"] as String?, andDangerouslyFinishTransactionAutomatically = json["andDangerouslyFinishTransactionAutomatically"] as Boolean?, appAccountToken = json["appAccountToken"] as String?, quantity = (json["quantity"] as Number?)?.toInt(), @@ -2998,6 +3015,7 @@ public data class RequestSubscriptionIosProps( } fun toJson(): Map = mapOf( + "advancedCommerceDataIOS" to advancedCommerceDataIOS, "andDangerouslyFinishTransactionAutomatically" to andDangerouslyFinishTransactionAutomatically, "appAccountToken" to appAccountToken, "quantity" to quantity, diff --git a/packages/gql/src/generated/Types.swift b/packages/gql/src/generated/Types.swift index a56f0602..11f35a6a 100644 --- a/packages/gql/src/generated/Types.swift +++ b/packages/gql/src/generated/Types.swift @@ -1055,6 +1055,11 @@ public struct RequestPurchaseAndroidProps: Codable { } public struct RequestPurchaseIosProps: Codable { + /// Advanced commerce data token (iOS 15+). + /// Used with StoreKit 2's Product.PurchaseOption.custom API for passing + /// campaign tokens, affiliate IDs, or other attribution data. + /// The data is formatted as JSON: {"signatureInfo": {"token": ""}} + public var advancedCommerceDataIOS: String? /// Auto-finish transaction (dangerous) public var andDangerouslyFinishTransactionAutomatically: Bool? /// App account token for user tracking @@ -1067,12 +1072,14 @@ public struct RequestPurchaseIosProps: Codable { public var withOffer: DiscountOfferInputIOS? public init( + advancedCommerceDataIOS: String? = nil, andDangerouslyFinishTransactionAutomatically: Bool? = nil, appAccountToken: String? = nil, quantity: Int? = nil, sku: String, withOffer: DiscountOfferInputIOS? = nil ) { + self.advancedCommerceDataIOS = advancedCommerceDataIOS self.andDangerouslyFinishTransactionAutomatically = andDangerouslyFinishTransactionAutomatically self.appAccountToken = appAccountToken self.quantity = quantity @@ -1222,6 +1229,11 @@ public struct RequestSubscriptionAndroidProps: Codable { } public struct RequestSubscriptionIosProps: Codable { + /// Advanced commerce data token (iOS 15+). + /// Used with StoreKit 2's Product.PurchaseOption.custom API for passing + /// campaign tokens, affiliate IDs, or other attribution data. + /// The data is formatted as JSON: {"signatureInfo": {"token": ""}} + public var advancedCommerceDataIOS: String? public var andDangerouslyFinishTransactionAutomatically: Bool? public var appAccountToken: String? public var quantity: Int? @@ -1229,12 +1241,14 @@ public struct RequestSubscriptionIosProps: Codable { public var withOffer: DiscountOfferInputIOS? public init( + advancedCommerceDataIOS: String? = nil, andDangerouslyFinishTransactionAutomatically: Bool? = nil, appAccountToken: String? = nil, quantity: Int? = nil, sku: String, withOffer: DiscountOfferInputIOS? = nil ) { + self.advancedCommerceDataIOS = advancedCommerceDataIOS self.andDangerouslyFinishTransactionAutomatically = andDangerouslyFinishTransactionAutomatically self.appAccountToken = appAccountToken self.quantity = quantity diff --git a/packages/gql/src/generated/types.dart b/packages/gql/src/generated/types.dart index 6e9c8c2f..f15eb441 100644 --- a/packages/gql/src/generated/types.dart +++ b/packages/gql/src/generated/types.dart @@ -3209,6 +3209,11 @@ class RequestPurchaseAndroidProps { class RequestPurchaseIosProps { const RequestPurchaseIosProps({ + /// Advanced commerce data token (iOS 15+). + /// Used with StoreKit 2's Product.PurchaseOption.custom API for passing + /// campaign tokens, affiliate IDs, or other attribution data. + /// The data is formatted as JSON: {"signatureInfo": {"token": ""}} + this.advancedCommerceDataIOS, /// Auto-finish transaction (dangerous) this.andDangerouslyFinishTransactionAutomatically, /// App account token for user tracking @@ -3221,6 +3226,11 @@ class RequestPurchaseIosProps { this.withOffer, }); + /// Advanced commerce data token (iOS 15+). + /// Used with StoreKit 2's Product.PurchaseOption.custom API for passing + /// campaign tokens, affiliate IDs, or other attribution data. + /// The data is formatted as JSON: {"signatureInfo": {"token": ""}} + final String? advancedCommerceDataIOS; /// Auto-finish transaction (dangerous) final bool? andDangerouslyFinishTransactionAutomatically; /// App account token for user tracking @@ -3234,6 +3244,7 @@ class RequestPurchaseIosProps { factory RequestPurchaseIosProps.fromJson(Map json) { return RequestPurchaseIosProps( + advancedCommerceDataIOS: json['advancedCommerceDataIOS'] as String?, andDangerouslyFinishTransactionAutomatically: json['andDangerouslyFinishTransactionAutomatically'] as bool?, appAccountToken: json['appAccountToken'] as String?, quantity: json['quantity'] as int?, @@ -3244,6 +3255,7 @@ class RequestPurchaseIosProps { Map toJson() { return { + 'advancedCommerceDataIOS': advancedCommerceDataIOS, 'andDangerouslyFinishTransactionAutomatically': andDangerouslyFinishTransactionAutomatically, 'appAccountToken': appAccountToken, 'quantity': quantity, @@ -3429,6 +3441,11 @@ class RequestSubscriptionAndroidProps { class RequestSubscriptionIosProps { const RequestSubscriptionIosProps({ + /// Advanced commerce data token (iOS 15+). + /// Used with StoreKit 2's Product.PurchaseOption.custom API for passing + /// campaign tokens, affiliate IDs, or other attribution data. + /// The data is formatted as JSON: {"signatureInfo": {"token": ""}} + this.advancedCommerceDataIOS, this.andDangerouslyFinishTransactionAutomatically, this.appAccountToken, this.quantity, @@ -3436,6 +3453,11 @@ class RequestSubscriptionIosProps { this.withOffer, }); + /// Advanced commerce data token (iOS 15+). + /// Used with StoreKit 2's Product.PurchaseOption.custom API for passing + /// campaign tokens, affiliate IDs, or other attribution data. + /// The data is formatted as JSON: {"signatureInfo": {"token": ""}} + final String? advancedCommerceDataIOS; final bool? andDangerouslyFinishTransactionAutomatically; final String? appAccountToken; final int? quantity; @@ -3444,6 +3466,7 @@ class RequestSubscriptionIosProps { factory RequestSubscriptionIosProps.fromJson(Map json) { return RequestSubscriptionIosProps( + advancedCommerceDataIOS: json['advancedCommerceDataIOS'] as String?, andDangerouslyFinishTransactionAutomatically: json['andDangerouslyFinishTransactionAutomatically'] as bool?, appAccountToken: json['appAccountToken'] as String?, quantity: json['quantity'] as int?, @@ -3454,6 +3477,7 @@ class RequestSubscriptionIosProps { Map toJson() { return { + 'advancedCommerceDataIOS': advancedCommerceDataIOS, 'andDangerouslyFinishTransactionAutomatically': andDangerouslyFinishTransactionAutomatically, 'appAccountToken': appAccountToken, 'quantity': quantity, diff --git a/packages/gql/src/generated/types.ts b/packages/gql/src/generated/types.ts index 65eb2f93..ee458a26 100644 --- a/packages/gql/src/generated/types.ts +++ b/packages/gql/src/generated/types.ts @@ -889,6 +889,13 @@ export interface RequestPurchaseAndroidProps { } export interface RequestPurchaseIosProps { + /** + * Advanced commerce data token (iOS 15+). + * Used with StoreKit 2's Product.PurchaseOption.custom API for passing + * campaign tokens, affiliate IDs, or other attribution data. + * The data is formatted as JSON: {"signatureInfo": {"token": ""}} + */ + advancedCommerceDataIOS?: (string | null); /** Auto-finish transaction (dangerous) */ andDangerouslyFinishTransactionAutomatically?: (boolean | null); /** App account token for user tracking */ @@ -964,6 +971,13 @@ export interface RequestSubscriptionAndroidProps { } export interface RequestSubscriptionIosProps { + /** + * Advanced commerce data token (iOS 15+). + * Used with StoreKit 2's Product.PurchaseOption.custom API for passing + * campaign tokens, affiliate IDs, or other attribution data. + * The data is formatted as JSON: {"signatureInfo": {"token": ""}} + */ + advancedCommerceDataIOS?: (string | null); andDangerouslyFinishTransactionAutomatically?: (boolean | null); appAccountToken?: (string | null); quantity?: (number | null); diff --git a/packages/gql/src/type-ios.graphql b/packages/gql/src/type-ios.graphql index a67bfefd..b7ff2f46 100644 --- a/packages/gql/src/type-ios.graphql +++ b/packages/gql/src/type-ios.graphql @@ -192,6 +192,13 @@ input RequestPurchaseIosProps { Discount offer to apply """ withOffer: DiscountOfferInputIOS + """ + Advanced commerce data token (iOS 15+). + Used with StoreKit 2's Product.PurchaseOption.custom API for passing + campaign tokens, affiliate IDs, or other attribution data. + The data is formatted as JSON: {"signatureInfo": {"token": ""}} + """ + advancedCommerceDataIOS: String } # iOS uses the same props for subscriptions @@ -201,6 +208,13 @@ input RequestSubscriptionIosProps { appAccountToken: String quantity: Int withOffer: DiscountOfferInputIOS + """ + Advanced commerce data token (iOS 15+). + Used with StoreKit 2's Product.PurchaseOption.custom API for passing + campaign tokens, affiliate IDs, or other attribution data. + The data is formatted as JSON: {"signatureInfo": {"token": ""}} + """ + advancedCommerceDataIOS: String } # iOS Discount Offer (input) From f48c1326b687f9a400ca338e83ef131da0e2930b Mon Sep 17 00:00:00 2001 From: Hyo Date: Tue, 23 Dec 2025 23:54:20 +0900 Subject: [PATCH 2/4] Update CHANGELOG.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a6f852f..8f56f6bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,3 @@ # Changelog -For detailed release notes, see: https://www.openiap.dev/docs/updates/notes +For detailed release notes, see: [openiap.dev/docs/updates/notes](https://www.openiap.dev/docs/updates/notes) From bb53e3a92bea46f13890d9800ecc7c91429a3b53 Mon Sep 17 00:00:00 2001 From: Hyo Date: Tue, 23 Dec 2025 23:56:39 +0900 Subject: [PATCH 3/4] ci: add path filters to skip unrelated builds --- .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0708992d..e7a33cb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,9 +9,43 @@ on: - main jobs: + # Detect which packages have changes + changes: + name: Detect Changes + runs-on: ubuntu-latest + outputs: + gql: ${{ steps.filter.outputs.gql }} + android: ${{ steps.filter.outputs.android }} + ios: ${{ steps.filter.outputs.ios }} + docs: ${{ steps.filter.outputs.docs }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check for changes + uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + gql: + - 'packages/gql/**' + - 'openiap-versions.json' + android: + - 'packages/google/**' + - 'packages/gql/**' + - 'openiap-versions.json' + ios: + - 'packages/apple/**' + - 'packages/gql/**' + - 'openiap-versions.json' + docs: + - 'packages/docs/**' + test-gql: name: Test GQL Types runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.gql == 'true' steps: - name: Checkout uses: actions/checkout@v4 @@ -46,7 +80,8 @@ jobs: test-android: name: Test Android runs-on: ubuntu-latest - needs: test-gql + needs: changes + if: needs.changes.outputs.android == 'true' steps: - name: Checkout uses: actions/checkout@v4 @@ -95,7 +130,8 @@ jobs: test-ios: name: Test iOS runs-on: macos-latest - needs: test-gql + needs: changes + if: needs.changes.outputs.ios == 'true' steps: - name: Checkout uses: actions/checkout@v4 @@ -130,6 +166,8 @@ jobs: test-docs: name: Test Docs runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.docs == 'true' steps: - name: Checkout uses: actions/checkout@v4 From 72d0bfa80422456a44410fa0ae4b35e8557d9e0d Mon Sep 17 00:00:00 2001 From: Hyo Date: Wed, 24 Dec 2025 00:01:59 +0900 Subject: [PATCH 4/4] ci: include workflow and scripts in path filters --- .github/workflows/ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7a33cb2..7cd7202f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,17 +29,24 @@ jobs: filters: | gql: - 'packages/gql/**' + - 'scripts/**' - 'openiap-versions.json' + - '.github/workflows/ci.yml' android: - 'packages/google/**' - 'packages/gql/**' + - 'scripts/**' - 'openiap-versions.json' + - '.github/workflows/ci.yml' ios: - 'packages/apple/**' - 'packages/gql/**' + - 'scripts/**' - 'openiap-versions.json' + - '.github/workflows/ci.yml' docs: - 'packages/docs/**' + - '.github/workflows/ci.yml' test-gql: name: Test GQL Types