From cda11d9ec80c0d5848a50dfacdef55d3bc52b869 Mon Sep 17 00:00:00 2001 From: Hyo Date: Sat, 21 Mar 2026 01:33:15 +0900 Subject: [PATCH 1/6] refactor: codebase improvements across docs, google, and gql packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refactor SearchModal (915→167 lines): extract apiData to searchData.ts, keyboard logic to useSearchKeyboard hook, add useCallback memoization - Deduplicate Google Android flavors: move OpenIapViewModel to main source set, extract CommonHelpers.kt with shared suspend functions - Add GQL schema convention linter: validates iOS/Android suffixes, union/future markers before code generation - Add prettier format check to CI/CD for docs package - Align signal pattern to object-based format per docs conventions - Mark unused template-plugin.ts as @deprecated Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 4 + packages/docs/src/App.tsx | 4 +- packages/docs/src/components/SearchModal.tsx | 804 +----------------- packages/docs/src/hooks/useSearchKeyboard.ts | 58 ++ packages/docs/src/lib/searchData.ts | 713 ++++++++++++++++ packages/docs/src/lib/signals.ts | 10 +- .../dev/hyo/openiap/helpers/SharedHelpers.kt | 131 +-- .../java/dev/hyo/openiap/OpenIapViewModel.kt | 0 .../dev/hyo/openiap/helpers/CommonHelpers.kt | 132 +++ .../java/dev/hyo/openiap/OpenIapViewModel.kt | 84 -- .../java/dev/hyo/openiap/helpers/Helpers.kt | 111 +-- packages/gql/codegen/core/schema-linter.ts | 214 +++++ packages/gql/codegen/index.ts | 26 +- .../gql/codegen/plugins/template-plugin.ts | 6 + 14 files changed, 1203 insertions(+), 1094 deletions(-) create mode 100644 packages/docs/src/hooks/useSearchKeyboard.ts create mode 100644 packages/docs/src/lib/searchData.ts rename packages/google/openiap/src/{horizon => main}/java/dev/hyo/openiap/OpenIapViewModel.kt (100%) create mode 100644 packages/google/openiap/src/main/java/dev/hyo/openiap/helpers/CommonHelpers.kt delete mode 100644 packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapViewModel.kt create mode 100644 packages/gql/codegen/core/schema-linter.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95211008..88fb7e7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -206,6 +206,10 @@ jobs: working-directory: packages/docs run: bun run lint + - name: Format check + working-directory: packages/docs + run: bunx prettier --check "src/**/*.{ts,tsx,css}" + - name: Build working-directory: packages/docs run: bun run build diff --git a/packages/docs/src/App.tsx b/packages/docs/src/App.tsx index 437de453..8b817639 100644 --- a/packages/docs/src/App.tsx +++ b/packages/docs/src/App.tsx @@ -15,12 +15,12 @@ import { searchModalSignal, closeSearchModal } from './lib/signals'; import { effect } from '@preact/signals-react'; function App() { - const [isSearchOpen, setIsSearchOpen] = useState(searchModalSignal.value); + const [isSearchOpen, setIsSearchOpen] = useState(searchModalSignal.value.isOpen); useEffect(() => { // Subscribe to signal changes const unsubscribe = effect(() => { - setIsSearchOpen(searchModalSignal.value); + setIsSearchOpen(searchModalSignal.value.isOpen); }); return () => unsubscribe(); diff --git a/packages/docs/src/components/SearchModal.tsx b/packages/docs/src/components/SearchModal.tsx index 0e0325c9..8ab2e252 100644 --- a/packages/docs/src/components/SearchModal.tsx +++ b/packages/docs/src/components/SearchModal.tsx @@ -1,732 +1,32 @@ -import { useEffect, useRef, useState, useMemo } from 'react'; +import { useEffect, useRef, useState, useMemo, useCallback } from 'react'; import { createPortal } from 'react-dom'; import { Search, X } from 'lucide-react'; -import { useNavigate } from 'react-router-dom'; - -interface ApiItem { - id: string; - title: string; - category: string; - description?: string; - parameters?: string; - returns?: string; - path: string; -} +import { apiData } from '../lib/searchData'; +import { useSearchKeyboard } from '../hooks/useSearchKeyboard'; interface SearchModalProps { isOpen: boolean; onClose: () => void; } -const apiData: ApiItem[] = [ - // Connection Management - { - id: 'init-connection', - title: 'initConnection', - category: 'Connection', - description: 'Initialize connection to the store service', - parameters: '', - returns: 'Boolean!', - path: '/docs/apis/connection#init-connection', - }, - { - id: 'end-connection', - title: 'endConnection', - category: 'Connection', - description: 'End connection to the store service', - parameters: '', - returns: 'Boolean!', - path: '/docs/apis/connection#end-connection', - }, - - // Product Management - { - id: 'fetch-products', - title: 'fetchProducts', - category: 'Products', - description: 'Retrieve products or subscriptions from the store', - parameters: 'ProductRequest', - returns: '[Product!]!', - path: '/docs/apis/products#fetch-products', - }, - { - id: 'get-available-purchases', - title: 'getAvailablePurchases', - category: 'Products', - description: 'Get all available purchases for the current user', - parameters: 'PurchaseOptions?', - returns: '[Purchase!]!', - path: '/docs/apis/products#get-available-purchases', - }, - - // Purchase Operations - { - id: 'request-purchase', - title: 'requestPurchase', - category: 'Purchase', - description: 'Request a purchase (one-time or subscription)', - parameters: 'RequestPurchaseProps', - returns: 'Purchase!', - path: '/docs/apis/purchase#request-purchase', - }, - { - id: 'finish-transaction', - title: 'finishTransaction', - category: 'Purchase', - description: - 'Complete a purchase transaction. Must be called after successful verification', - parameters: 'Purchase!, isConsumable: Boolean?', - returns: 'Void', - path: '/docs/apis/purchase#finish-transaction', - }, - { - id: 'restore-purchases', - title: 'restorePurchases', - category: 'Purchase', - description: 'Restore completed transactions (cross-platform)', - parameters: '', - returns: 'Void', - path: '/docs/apis/purchase#restore-purchases', - }, - { - id: 'get-storefront', - title: 'getStorefront', - category: 'Purchase', - description: 'Get storefront country code for the active user', - parameters: '', - returns: 'String!', - path: '/docs/apis/purchase#get-storefront', - }, - - // Subscription Management - { - id: 'get-active-subscriptions', - title: 'getActiveSubscriptions', - category: 'Subscription', - description: 'Get all active subscriptions with detailed information', - parameters: 'subscriptionIds: [String]?', - returns: '[ActiveSubscription!]!', - path: '/docs/apis/subscription#get-active-subscriptions', - }, - { - id: 'has-active-subscriptions', - title: 'hasActiveSubscriptions', - category: 'Subscription', - description: 'Check if the user has any active subscriptions', - parameters: 'subscriptionIds: [String]?', - returns: 'Boolean!', - path: '/docs/apis/subscription#has-active-subscriptions', - }, - { - id: 'deep-link-to-subscriptions', - title: 'deepLinkToSubscriptions', - category: 'Subscription', - description: 'Open native subscription management interface', - parameters: 'DeepLinkOptions', - returns: 'Void', - path: '/docs/apis/subscription#deep-link-to-subscriptions', - }, - - // Verification - { - id: 'verify-purchase', - title: 'verifyPurchase', - category: 'Validation', - description: 'Verify purchases with your server or platform providers', - parameters: 'PurchaseVerificationProps!', - returns: 'PurchaseVerificationResult!', - path: '/docs/apis/validation#verify-purchase', - }, - { - id: 'verify-purchase-with-provider', - title: 'verifyPurchaseWithProvider', - category: 'Validation', - description: 'Verify purchases using IAPKit or other providers', - parameters: 'VerifyPurchaseWithProviderProps!', - returns: 'VerifyPurchaseWithProviderResult!', - path: '/docs/apis/validation#verify-purchase-with-provider', - }, - - // iOS APIs - { - id: 'clear-transaction-ios', - title: 'clearTransactionIOS', - category: 'iOS APIs', - description: 'Clear pending transactions', - parameters: '', - returns: 'Boolean!', - path: '/docs/apis/ios#clear-transaction-ios', - }, - { - id: 'sync-ios', - title: 'syncIOS', - category: 'iOS APIs', - description: 'Force StoreKit transaction sync (iOS 15+)', - parameters: '', - returns: 'Boolean!', - path: '/docs/apis/ios#sync-ios', - }, - { - id: 'get-promoted-product-ios', - title: 'getPromotedProductIOS', - category: 'iOS APIs', - description: 'Get the currently promoted product (iOS 11+)', - parameters: '', - returns: 'ProductIOS', - path: '/docs/apis/ios#get-promoted-product-ios', - }, - { - id: 'request-purchase-on-promoted-product-ios', - title: 'requestPurchaseOnPromotedProductIOS', - category: 'iOS APIs', - description: 'Purchase a promoted product (iOS 11+)', - parameters: '', - returns: 'Boolean!', - path: '/docs/apis/ios#request-purchase-on-promoted-product-ios', - }, - { - id: 'get-pending-transactions-ios', - title: 'getPendingTransactionsIOS', - category: 'iOS APIs', - description: 'Retrieve pending StoreKit transactions', - parameters: '', - returns: '[PurchaseIOS!]!', - path: '/docs/apis/ios#get-pending-transactions-ios', - }, - { - id: 'is-eligible-for-intro-offer-ios', - title: 'isEligibleForIntroOfferIOS', - category: 'iOS APIs', - description: 'Check introductory offer eligibility', - parameters: 'groupID: String!', - returns: 'Boolean!', - path: '/docs/apis/ios#is-eligible-for-intro-offer-ios', - }, - { - id: 'subscription-status-ios', - title: 'subscriptionStatusIOS', - category: 'iOS APIs', - description: 'Get StoreKit 2 subscription status (iOS 15+)', - parameters: 'sku: String!', - returns: '[SubscriptionStatusIOS!]!', - path: '/docs/apis/ios#subscription-status-ios', - }, - { - id: 'current-entitlement-ios', - title: 'currentEntitlementIOS', - category: 'iOS APIs', - description: 'Get current StoreKit 2 entitlement (iOS 15+)', - parameters: 'sku: String!', - returns: 'PurchaseIOS', - path: '/docs/apis/ios#current-entitlement-ios', - }, - { - id: 'latest-transaction-ios', - title: 'latestTransactionIOS', - category: 'iOS APIs', - description: 'Get latest StoreKit 2 transaction (iOS 15+)', - parameters: 'sku: String!', - returns: 'PurchaseIOS', - path: '/docs/apis/ios#latest-transaction-ios', - }, - { - id: 'show-manage-subscriptions-ios', - title: 'showManageSubscriptionsIOS', - category: 'iOS APIs', - description: 'Open subscription management UI and return changes (iOS 15+)', - parameters: '', - returns: '[PurchaseIOS!]!', - path: '/docs/apis/ios#show-manage-subscriptions-ios', - }, - { - id: 'begin-refund-request-ios', - title: 'beginRefundRequestIOS', - category: 'iOS APIs', - description: 'Initiate refund request (iOS 15+)', - parameters: 'sku: String!', - returns: 'String', - path: '/docs/apis/ios#begin-refund-request-ios', - }, - { - id: 'is-transaction-verified-ios', - title: 'isTransactionVerifiedIOS', - category: 'iOS APIs', - description: 'Verify StoreKit 2 transaction signature', - parameters: 'sku: String!', - returns: 'Boolean!', - path: '/docs/apis/ios#is-transaction-verified-ios', - }, - { - id: 'get-transaction-jws-ios', - title: 'getTransactionJwsIOS', - category: 'iOS APIs', - description: 'Get the transaction JWS (StoreKit 2)', - parameters: 'sku: String!', - returns: 'String', - path: '/docs/apis/ios#get-transaction-jws-ios', - }, - { - id: 'get-receipt-data-ios', - title: 'getReceiptDataIOS', - category: 'iOS APIs', - description: 'Get base64-encoded receipt data for validation', - parameters: '', - returns: 'String', - path: '/docs/apis/ios#get-receipt-data-ios', - }, - { - id: 'present-code-redemption-sheet-ios', - title: 'presentCodeRedemptionSheetIOS', - category: 'iOS APIs', - description: 'Present the App Store code redemption sheet', - parameters: '', - returns: 'Boolean!', - path: '/docs/apis/ios#present-code-redemption-sheet-ios', - }, - { - id: 'get-app-transaction-ios', - title: 'getAppTransactionIOS', - category: 'iOS APIs', - description: 'Fetch the current app transaction (iOS 16+)', - parameters: '', - returns: 'AppTransaction', - path: '/docs/apis/ios#get-app-transaction-ios', - }, - { - id: 'external-purchase-ios', - title: 'iOS External Purchase', - category: 'iOS APIs', - description: 'External purchase flow for iOS 17.4+', - parameters: '', - returns: '', - path: '/docs/apis/ios#external-purchase', - }, - - // Android APIs - { - id: 'acknowledge-purchase-android', - title: 'acknowledgePurchaseAndroid', - category: 'Android APIs', - description: 'Acknowledge a non-consumable purchase or subscription', - parameters: 'purchaseToken: String!', - returns: 'Boolean!', - path: '/docs/apis/android#acknowledge-purchase-android', - }, - { - id: 'consume-purchase-android', - title: 'consumePurchaseAndroid', - category: 'Android APIs', - description: 'Consume a purchase (for consumable products only)', - parameters: 'purchaseToken: String!', - returns: 'Boolean!', - path: '/docs/apis/android#consume-purchase-android', - }, - { - id: 'check-alternative-billing-availability-android', - title: 'checkAlternativeBillingAvailabilityAndroid', - category: 'Android APIs', - description: - 'Check if alternative billing is available (Step 1 of alternative billing)', - parameters: '', - returns: 'Boolean!', - path: '/docs/apis/android#check-alternative-billing-availability-android', - }, - { - id: 'show-alternative-billing-dialog-android', - title: 'showAlternativeBillingDialogAndroid', - category: 'Android APIs', - description: - 'Show alternative billing dialog to user (Step 2 of alternative billing)', - parameters: '', - returns: 'Boolean!', - path: '/docs/apis/android#show-alternative-billing-dialog-android', - }, - { - id: 'create-alternative-billing-token-android', - title: 'createAlternativeBillingTokenAndroid', - category: 'Android APIs', - description: - 'Create external transaction token for Google Play (Step 3 of alternative billing)', - parameters: '', - returns: 'String', - path: '/docs/apis/android#create-alternative-billing-token-android', - }, - - // Debugging & Logging - { - id: 'debugging-logging', - title: 'Debugging & Logging', - category: 'Debugging', - description: 'Enable verbose logging for development', - parameters: '', - returns: '', - path: '/docs/apis/debugging', - }, - { - id: 'enable-logging', - title: 'Enable Logging', - category: 'Debugging', - description: 'Enable or disable debug logs', - parameters: 'Boolean', - returns: '', - path: '/docs/apis/debugging#enable-logging', - }, - { - id: 'baseplanid-limitation', - title: 'Android basePlanId Limitation', - category: 'Debugging', - description: 'Understanding basePlanId limitations with multiple offers', - parameters: '', - returns: '', - path: '/docs/apis/debugging#android-baseplanid-limitation', - }, - - // Documentation Pages - { - id: 'external-purchase-page', - title: 'External Purchase', - category: 'Documentation', - description: - 'External purchase links for iOS - redirect users to external payment websites (iOS 16.0+)', - path: '/docs/features/external-purchase', - }, - { - id: 'types-page', - title: 'Types', - category: 'Documentation', - description: 'Type definitions and data structures', - path: '/docs/types', - }, - { - id: 'types-product', - title: 'Product Types', - category: 'Types', - description: 'Product, SubscriptionProduct, Unified Platform Types, Storefront', - path: '/docs/types/product', - }, - { - id: 'product', - title: 'Product', - category: 'Types', - description: 'Base product type: id, title, description, price, currency, type', - path: '/docs/types/product#product', - }, - { - id: 'subscription-product', - title: 'SubscriptionProduct', - category: 'Types', - description: 'Subscription product with pricing phases, intro offers, billing periods', - path: '/docs/types/product#product-subscription', - }, - { - id: 'storefront', - title: 'Storefront', - category: 'Types', - description: 'Store region info: countryCode returned by getStorefront()', - path: '/docs/types/product#storefront', - }, - { - id: 'types-purchase', - title: 'Purchase Types', - category: 'Types', - description: 'Purchase, PurchaseState, ActiveSubscription, RenewalInfoIOS', - path: '/docs/types/purchase', - }, - { - id: 'purchase', - title: 'Purchase', - category: 'Types', - description: 'Purchase transaction: id, productId, transactionDate, transactionReceipt', - path: '/docs/types/purchase#purchase', - }, - { - id: 'purchase-state', - title: 'PurchaseState', - category: 'Types', - description: 'Purchase state enum: purchased, pending, failed, restored, deferred', - path: '/docs/types/purchase#purchase-state', - }, - { - id: 'active-subscription', - title: 'ActiveSubscription', - category: 'Types', - description: 'Active subscription: id, productId, isActive from getActiveSubscriptions()', - path: '/docs/types/purchase#active-subscription', - }, - { - id: 'types-request', - title: 'Request Types', - category: 'Types', - description: 'ProductRequest, RequestPurchaseProps, platform-specific request types', - path: '/docs/types/request', - }, - { - id: 'types-verification', - title: 'Verification Types', - category: 'Types', - description: 'VerifyPurchaseProps, IAPKit integration, purchase verification', - path: '/docs/types/verification', - }, - { - id: 'types-ios', - title: 'iOS Types', - category: 'Types', - description: 'DiscountOffer, SubscriptionStatusIOS, PaymentMode, AppTransaction', - path: '/docs/types/ios', - }, - { - id: 'types-android', - title: 'Android Types', - category: 'Types', - description: 'SubscriptionOffer, PricingPhase, PricingPhasesAndroid', - path: '/docs/types/android', - }, - { - id: 'types-alternative', - title: 'Alternative Billing Types', - category: 'Types', - description: 'AlternativeBillingModeAndroid, InitConnectionConfig, External Purchase Link', - path: '/docs/types/alternative', - }, - - // iOS-Specific Types (from types/ios.tsx) - { - id: 'discount-offer', - title: 'DiscountOffer', - category: 'Types (iOS)', - description: 'iOS promotional offer for purchase: identifier, keyIdentifier, nonce, signature, timestamp', - path: '/docs/types/ios#discount-offer', - }, - { - id: 'discount', - title: 'Discount', - category: 'Types (iOS)', - description: 'iOS discount info: identifier, type, numberOfPeriods, price, paymentMode, subscriptionPeriod', - path: '/docs/types/ios#discount', - }, - { - id: 'subscription-period-ios', - title: 'SubscriptionPeriodIOS', - category: 'Types (iOS)', - description: 'iOS subscription period units: Day, Week, Month, Year', - path: '/docs/types/ios#subscription-period-ios', - }, - { - id: 'payment-mode', - title: 'PaymentMode', - category: 'Types (iOS)', - description: 'iOS payment mode for offers: FreeTrial, PayAsYouGo, PayUpFront', - path: '/docs/types/ios#payment-mode', - }, - { - id: 'subscription-status-ios', - title: 'SubscriptionStatusIOS', - category: 'Types (iOS)', - description: 'iOS subscription status from StoreKit 2: state, renewalInfo', - path: '/docs/types/ios#subscription-status-ios', - }, - { - id: 'app-transaction', - title: 'AppTransaction', - category: 'Types (iOS)', - description: 'iOS app transaction info: bundleId, appVersion, originalAppVersion, environment', - path: '/docs/types/ios#app-transaction', - }, - - // Android-Specific Types (from types/android.tsx) - { - id: 'subscription-offer', - title: 'SubscriptionOffer', - category: 'Types (Android)', - description: 'Android subscription offer: sku, offerToken for Play Billing purchases', - path: '/docs/types/android#subscription-offer', - }, - { - id: 'pricing-phase', - title: 'PricingPhase', - category: 'Types (Android)', - description: 'Android pricing phase: billingPeriod, formattedPrice, priceAmountMicros, recurrenceMode', - path: '/docs/types/android#pricing-phase', - }, - { - id: 'pricing-phases-android', - title: 'PricingPhasesAndroid', - category: 'Types (Android)', - description: 'Android pricing phases container: pricingPhaseList array', - path: '/docs/types/android#pricing-phases-android', - }, - - // Alternative Billing Types (from types/alternative.tsx) - { - id: 'alternative-billing-mode-android', - title: 'AlternativeBillingModeAndroid', - category: 'Types (Android)', - description: 'Android billing mode: NONE, USER_CHOICE, ALTERNATIVE_ONLY', - path: '/docs/types/alternative#alternative-billing-mode-android', - }, - { - id: 'init-connection-config', - title: 'InitConnectionConfig', - category: 'Types', - description: 'Configuration for initConnection: alternativeBillingModeAndroid', - path: '/docs/types/alternative#init-connection-config', - }, - { - id: 'external-purchase-link-ios', - title: 'External Purchase Link (iOS)', - category: 'Types (iOS)', - description: 'iOS external purchase APIs: canPresent, presentNoticeSheet, presentLink (iOS 17.4+)', - path: '/docs/types/alternative#external-purchase-link', - }, - - // Platform-Specific Request Types - { - id: 'request-purchase-ios-props', - title: 'RequestPurchaseIosProps', - category: 'Types (iOS)', - description: 'iOS purchase request parameters: sku, appAccountToken, quantity, withOffer', - path: '/docs/types/request#request-purchase-ios-props', - }, - { - id: 'request-purchase-android-props', - title: 'RequestPurchaseAndroidProps', - category: 'Types (Android)', - description: 'Android purchase request parameters: skus, obfuscatedAccountId, isOfferPersonalized', - path: '/docs/types/request#request-purchase-android-props', - }, - { - id: 'request-subscription-ios-props', - title: 'RequestSubscriptionIosProps', - category: 'Types (iOS)', - description: 'iOS subscription request parameters (same as RequestPurchaseIosProps)', - path: '/docs/types/request#request-subscription-ios-props', - }, - { - id: 'request-subscription-android-props', - title: 'RequestSubscriptionAndroidProps', - category: 'Types (Android)', - description: 'Android subscription request: purchaseToken, replacementMode, subscriptionOffers', - path: '/docs/types/request#request-subscription-android-props', - }, - - // Platform-Specific Product Types - { - id: 'product-ios', - title: 'ProductIOS', - category: 'Types (iOS)', - description: 'iOS product fields: typeIOS, isFamilyShareableIOS, subscriptionOffers', - path: '/docs/types/product#product-ios', - }, - { - id: 'product-android', - title: 'ProductAndroid', - category: 'Types (Android)', - description: 'Android product fields: nameAndroid, discountOffers, subscriptionOffers', - path: '/docs/types/product#product-android', - }, - { - id: 'subscription-product-ios', - title: 'SubscriptionProductIOS', - category: 'Types (iOS)', - description: 'iOS subscription fields: subscriptionOffers, introductoryPriceIOS, subscriptionPeriodUnitIOS', - path: '/docs/types/product#subscription-product-ios', - }, - { - id: 'subscription-product-android', - title: 'SubscriptionProductAndroid', - category: 'Types (Android)', - description: 'Android subscription fields: subscriptionOffers', - path: '/docs/types/product#subscription-product-android', - }, - - // Platform-Specific Purchase Types - { - id: 'purchase-ios', - title: 'PurchaseIOS', - category: 'Types (iOS)', - description: 'iOS purchase fields: originalTransactionDateIOS, expirationDateIOS, renewalInfoIOS', - path: '/docs/types/purchase#purchase-ios', - }, - { - id: 'purchase-android', - title: 'PurchaseAndroid', - category: 'Types (Android)', - description: 'Android purchase fields: dataAndroid, signatureAndroid, isAcknowledgedAndroid', - path: '/docs/types/purchase#purchase-android', - }, - { - id: 'renewal-info-ios', - title: 'RenewalInfoIOS', - category: 'Types (iOS)', - description: 'iOS subscription renewal info: willAutoRenew, expirationReason, gracePeriodExpirationDate', - path: '/docs/types/purchase#renewal-info-ios', - }, - { - id: 'active-subscription-ios', - title: 'ActiveSubscriptionIOS', - category: 'Types (iOS)', - description: 'iOS active subscription: expirationDateIOS, environmentIOS, daysUntilExpirationIOS', - path: '/docs/types/purchase#active-subscription-ios', - }, - { - id: 'active-subscription-android', - title: 'ActiveSubscriptionAndroid', - category: 'Types (Android)', - description: 'Android active subscription: autoRenewingAndroid, basePlanIdAndroid, purchaseTokenAndroid', - path: '/docs/types/purchase#active-subscription-android', - }, - - // Platform-Specific Verification Types - { - id: 'verify-purchase-result-ios', - title: 'VerifyPurchaseResultIOS', - category: 'Types (iOS)', - description: 'iOS verification result: isValid, receiptData, jwsRepresentation, latestTransaction', - path: '/docs/types/verification#verify-purchase-result-ios', - }, - { - id: 'verify-purchase-result-android', - title: 'VerifyPurchaseResultAndroid', - category: 'Types (Android)', - description: 'Android verification result: autoRenewing, cancelDate, renewalDate, transactionId', - path: '/docs/types/verification#verify-purchase-result-android', - }, - { - id: 'verify-purchase-result-horizon', - title: 'VerifyPurchaseResultHorizon', - category: 'Types (Horizon)', - description: 'Meta Quest verification result: success, grantTime', - path: '/docs/types/verification#verify-purchase-result-horizon', - }, - - { - id: 'apis-page', - title: 'APIs', - category: 'Documentation', - description: 'API reference and function signatures', - path: '/docs/apis', - }, - { - id: 'events-page', - title: 'Events', - category: 'Documentation', - description: 'Event listeners and callbacks', - path: '/docs/events', - }, - { - id: 'errors-page', - title: 'Errors', - category: 'Documentation', - description: 'Error codes and error handling', - path: '/docs/errors', - }, -]; +function highlightMatch(text: string, query: string) { + if (!query) return text; + const parts = text.split(new RegExp(`(${query})`, 'gi')); + return parts.map((part, index) => + part.toLowerCase() === query.toLowerCase() ? ( + + {part} + + ) : ( + part + ), + ); +} function SearchModal({ isOpen, onClose }: SearchModalProps) { const [searchQuery, setSearchQuery] = useState(''); const [selectedIndex, setSelectedIndex] = useState(0); const searchInputRef = useRef(null); - const navigate = useNavigate(); const filteredApis = useMemo(() => { if (!searchQuery) return []; @@ -738,10 +38,18 @@ function SearchModal({ isOpen, onClose }: SearchModalProps) { api.description?.toLowerCase().includes(query) || api.parameters?.toLowerCase().includes(query) || api.returns?.toLowerCase().includes(query) || - api.category.toLowerCase().includes(query) + api.category.toLowerCase().includes(query), ); }, [searchQuery]); + const { handleApiSelect } = useSearchKeyboard({ + isOpen, + filteredApis, + selectedIndex, + setSelectedIndex, + onClose, + }); + useEffect(() => { if (isOpen) { setSearchQuery(''); @@ -759,52 +67,13 @@ function SearchModal({ isOpen, onClose }: SearchModalProps) { }; }, [isOpen]); - useEffect(() => { - if (!isOpen) return; - - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - onClose(); - } else if (e.key === 'ArrowDown') { - e.preventDefault(); - setSelectedIndex((prev) => - prev < filteredApis.length - 1 ? prev + 1 : prev - ); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - setSelectedIndex((prev) => (prev > 0 ? prev - 1 : 0)); - } else if (e.key === 'Enter' && filteredApis.length > 0) { - e.preventDefault(); - const selectedApi = filteredApis[selectedIndex]; - if (selectedApi) { - navigate(selectedApi.path); - onClose(); - } - } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [isOpen, filteredApis, selectedIndex, navigate, onClose]); - - const handleApiClick = (api: ApiItem) => { - navigate(api.path); - onClose(); - }; - - const highlightMatch = (text: string, query: string) => { - if (!query) return text; - const parts = text.split(new RegExp(`(${query})`, 'gi')); - return parts.map((part, index) => - part.toLowerCase() === query.toLowerCase() ? ( - - {part} - - ) : ( - part - ) - ); - }; + const handleSearchChange = useCallback( + (e: React.ChangeEvent) => { + setSearchQuery(e.target.value); + setSelectedIndex(0); + }, + [], + ); if (!isOpen) return null; @@ -824,10 +93,7 @@ function SearchModal({ isOpen, onClose }: SearchModalProps) { type="text" placeholder="Search APIs..." value={searchQuery} - onChange={(e) => { - setSearchQuery(e.target.value); - setSelectedIndex(0); - }} + onChange={handleSearchChange} className="search-modal-input" /> ))} -
- {children[activeTab]} -
+
{children[activeTab]}
); } diff --git a/packages/docs/src/components/Pagination.tsx b/packages/docs/src/components/Pagination.tsx index 15f657fe..c3fc4308 100644 --- a/packages/docs/src/components/Pagination.tsx +++ b/packages/docs/src/components/Pagination.tsx @@ -114,7 +114,7 @@ function Pagination({ {page} - ), + ) )} )} diff --git a/packages/docs/src/components/SearchModal.tsx b/packages/docs/src/components/SearchModal.tsx index 89f77fc7..0be29c8c 100644 --- a/packages/docs/src/components/SearchModal.tsx +++ b/packages/docs/src/components/SearchModal.tsx @@ -19,7 +19,7 @@ function highlightMatch(text: string, query: string) { ) : ( part - ), + ) ); } @@ -39,7 +39,7 @@ function SearchModal({ isOpen, onClose }: SearchModalProps) { api.description?.toLowerCase().includes(query) || api.parameters?.toLowerCase().includes(query) || api.returns?.toLowerCase().includes(query) || - api.category.toLowerCase().includes(query), + api.category.toLowerCase().includes(query) ); }, [searchQuery]); @@ -48,7 +48,7 @@ function SearchModal({ isOpen, onClose }: SearchModalProps) { navigate(api.path); onClose(); }, - [navigate, onClose], + [navigate, onClose] ); useEffect(() => { @@ -77,7 +77,7 @@ function SearchModal({ isOpen, onClose }: SearchModalProps) { } else if (e.key === 'ArrowDown') { e.preventDefault(); setSelectedIndex((prev) => - prev < filteredApis.length - 1 ? prev + 1 : prev, + prev < filteredApis.length - 1 ? prev + 1 : prev ); } else if (e.key === 'ArrowUp') { e.preventDefault(); @@ -100,7 +100,7 @@ function SearchModal({ isOpen, onClose }: SearchModalProps) { setSearchQuery(e.target.value); setSelectedIndex(0); }, - [], + [] ); if (!isOpen) return null; @@ -202,7 +202,7 @@ function SearchModal({ isOpen, onClose }: SearchModalProps) { , - document.body, + document.body ); } diff --git a/packages/docs/src/lib/searchData.ts b/packages/docs/src/lib/searchData.ts index 306410ae..40e63ae0 100644 --- a/packages/docs/src/lib/searchData.ts +++ b/packages/docs/src/lib/searchData.ts @@ -391,21 +391,24 @@ export const apiData: ApiItem[] = [ id: 'types-product', title: 'Product Types', category: 'Types', - description: 'Product, SubscriptionProduct, Unified Platform Types, Storefront', + description: + 'Product, SubscriptionProduct, Unified Platform Types, Storefront', path: '/docs/types/product', }, { id: 'product', title: 'Product', category: 'Types', - description: 'Base product type: id, title, description, price, currency, type', + description: + 'Base product type: id, title, description, price, currency, type', path: '/docs/types/product#product', }, { id: 'subscription-product', title: 'SubscriptionProduct', category: 'Types', - description: 'Subscription product with pricing phases, intro offers, billing periods', + description: + 'Subscription product with pricing phases, intro offers, billing periods', path: '/docs/types/product#product-subscription', }, { @@ -426,42 +429,48 @@ export const apiData: ApiItem[] = [ id: 'purchase', title: 'Purchase', category: 'Types', - description: 'Purchase transaction: id, productId, transactionDate, transactionReceipt', + description: + 'Purchase transaction: id, productId, transactionDate, transactionReceipt', path: '/docs/types/purchase#purchase', }, { id: 'purchase-state', title: 'PurchaseState', category: 'Types', - description: 'Purchase state enum: purchased, pending, failed, restored, deferred', + description: + 'Purchase state enum: purchased, pending, failed, restored, deferred', path: '/docs/types/purchase#purchase-state', }, { id: 'active-subscription', title: 'ActiveSubscription', category: 'Types', - description: 'Active subscription: id, productId, isActive from getActiveSubscriptions()', + description: + 'Active subscription: id, productId, isActive from getActiveSubscriptions()', path: '/docs/types/purchase#active-subscription', }, { id: 'types-request', title: 'Request Types', category: 'Types', - description: 'ProductRequest, RequestPurchaseProps, platform-specific request types', + description: + 'ProductRequest, RequestPurchaseProps, platform-specific request types', path: '/docs/types/request', }, { id: 'types-verification', title: 'Verification Types', category: 'Types', - description: 'VerifyPurchaseProps, IAPKit integration, purchase verification', + description: + 'VerifyPurchaseProps, IAPKit integration, purchase verification', path: '/docs/types/verification', }, { id: 'types-ios', title: 'iOS Types', category: 'Types', - description: 'DiscountOffer, SubscriptionStatusIOS, PaymentMode, AppTransaction', + description: + 'DiscountOffer, SubscriptionStatusIOS, PaymentMode, AppTransaction', path: '/docs/types/ios', }, { @@ -475,7 +484,8 @@ export const apiData: ApiItem[] = [ id: 'types-alternative', title: 'Alternative Billing Types', category: 'Types', - description: 'AlternativeBillingModeAndroid, InitConnectionConfig, External Purchase Link', + description: + 'AlternativeBillingModeAndroid, InitConnectionConfig, External Purchase Link', path: '/docs/types/alternative', }, @@ -484,14 +494,16 @@ export const apiData: ApiItem[] = [ id: 'discount-offer', title: 'DiscountOffer', category: 'Types (iOS)', - description: 'iOS promotional offer for purchase: identifier, keyIdentifier, nonce, signature, timestamp', + description: + 'iOS promotional offer for purchase: identifier, keyIdentifier, nonce, signature, timestamp', path: '/docs/types/ios#discount-offer', }, { id: 'discount', title: 'Discount', category: 'Types (iOS)', - description: 'iOS discount info: identifier, type, numberOfPeriods, price, paymentMode, subscriptionPeriod', + description: + 'iOS discount info: identifier, type, numberOfPeriods, price, paymentMode, subscriptionPeriod', path: '/docs/types/ios#discount', }, { @@ -505,7 +517,8 @@ export const apiData: ApiItem[] = [ id: 'payment-mode', title: 'PaymentMode', category: 'Types (iOS)', - description: 'iOS payment mode for offers: FreeTrial, PayAsYouGo, PayUpFront', + description: + 'iOS payment mode for offers: FreeTrial, PayAsYouGo, PayUpFront', path: '/docs/types/ios#payment-mode', }, { @@ -519,7 +532,8 @@ export const apiData: ApiItem[] = [ id: 'app-transaction', title: 'AppTransaction', category: 'Types (iOS)', - description: 'iOS app transaction info: bundleId, appVersion, originalAppVersion, environment', + description: + 'iOS app transaction info: bundleId, appVersion, originalAppVersion, environment', path: '/docs/types/ios#app-transaction', }, @@ -528,14 +542,16 @@ export const apiData: ApiItem[] = [ id: 'subscription-offer', title: 'SubscriptionOffer', category: 'Types (Android)', - description: 'Android subscription offer: sku, offerToken for Play Billing purchases', + description: + 'Android subscription offer: sku, offerToken for Play Billing purchases', path: '/docs/types/android#subscription-offer', }, { id: 'pricing-phase', title: 'PricingPhase', category: 'Types (Android)', - description: 'Android pricing phase: billingPeriod, formattedPrice, priceAmountMicros, recurrenceMode', + description: + 'Android pricing phase: billingPeriod, formattedPrice, priceAmountMicros, recurrenceMode', path: '/docs/types/android#pricing-phase', }, { @@ -558,14 +574,16 @@ export const apiData: ApiItem[] = [ id: 'init-connection-config', title: 'InitConnectionConfig', category: 'Types', - description: 'Configuration for initConnection: alternativeBillingModeAndroid', + description: + 'Configuration for initConnection: alternativeBillingModeAndroid', path: '/docs/types/alternative#init-connection-config', }, { id: 'external-purchase-link-ios', title: 'External Purchase Link (iOS)', category: 'Types (iOS)', - description: 'iOS external purchase APIs: canPresent, presentNoticeSheet, presentLink (iOS 17.4+)', + description: + 'iOS external purchase APIs: canPresent, presentNoticeSheet, presentLink (iOS 17.4+)', path: '/docs/types/alternative#external-purchase-link', }, @@ -574,28 +592,32 @@ export const apiData: ApiItem[] = [ id: 'request-purchase-ios-props', title: 'RequestPurchaseIosProps', category: 'Types (iOS)', - description: 'iOS purchase request parameters: sku, appAccountToken, quantity, withOffer', + description: + 'iOS purchase request parameters: sku, appAccountToken, quantity, withOffer', path: '/docs/types/request#request-purchase-ios-props', }, { id: 'request-purchase-android-props', title: 'RequestPurchaseAndroidProps', category: 'Types (Android)', - description: 'Android purchase request parameters: skus, obfuscatedAccountId, isOfferPersonalized', + description: + 'Android purchase request parameters: skus, obfuscatedAccountId, isOfferPersonalized', path: '/docs/types/request#request-purchase-android-props', }, { id: 'request-subscription-ios-props', title: 'RequestSubscriptionIosProps', category: 'Types (iOS)', - description: 'iOS subscription request parameters (same as RequestPurchaseIosProps)', + description: + 'iOS subscription request parameters (same as RequestPurchaseIosProps)', path: '/docs/types/request#request-subscription-ios-props', }, { id: 'request-subscription-android-props', title: 'RequestSubscriptionAndroidProps', category: 'Types (Android)', - description: 'Android subscription request: purchaseToken, replacementMode, subscriptionOffers', + description: + 'Android subscription request: purchaseToken, replacementMode, subscriptionOffers', path: '/docs/types/request#request-subscription-android-props', }, @@ -604,21 +626,24 @@ export const apiData: ApiItem[] = [ id: 'product-ios', title: 'ProductIOS', category: 'Types (iOS)', - description: 'iOS product fields: typeIOS, isFamilyShareableIOS, subscriptionOffers', + description: + 'iOS product fields: typeIOS, isFamilyShareableIOS, subscriptionOffers', path: '/docs/types/product#product-ios', }, { id: 'product-android', title: 'ProductAndroid', category: 'Types (Android)', - description: 'Android product fields: nameAndroid, discountOffers, subscriptionOffers', + description: + 'Android product fields: nameAndroid, discountOffers, subscriptionOffers', path: '/docs/types/product#product-android', }, { id: 'subscription-product-ios', title: 'SubscriptionProductIOS', category: 'Types (iOS)', - description: 'iOS subscription fields: subscriptionOffers, introductoryPriceIOS, subscriptionPeriodUnitIOS', + description: + 'iOS subscription fields: subscriptionOffers, introductoryPriceIOS, subscriptionPeriodUnitIOS', path: '/docs/types/product#subscription-product-ios', }, { @@ -634,35 +659,40 @@ export const apiData: ApiItem[] = [ id: 'purchase-ios', title: 'PurchaseIOS', category: 'Types (iOS)', - description: 'iOS purchase fields: originalTransactionDateIOS, expirationDateIOS, renewalInfoIOS', + description: + 'iOS purchase fields: originalTransactionDateIOS, expirationDateIOS, renewalInfoIOS', path: '/docs/types/purchase#purchase-ios', }, { id: 'purchase-android', title: 'PurchaseAndroid', category: 'Types (Android)', - description: 'Android purchase fields: dataAndroid, signatureAndroid, isAcknowledgedAndroid', + description: + 'Android purchase fields: dataAndroid, signatureAndroid, isAcknowledgedAndroid', path: '/docs/types/purchase#purchase-android', }, { id: 'renewal-info-ios', title: 'RenewalInfoIOS', category: 'Types (iOS)', - description: 'iOS subscription renewal info: willAutoRenew, expirationReason, gracePeriodExpirationDate', + description: + 'iOS subscription renewal info: willAutoRenew, expirationReason, gracePeriodExpirationDate', path: '/docs/types/purchase#renewal-info-ios', }, { id: 'active-subscription-ios', title: 'ActiveSubscriptionIOS', category: 'Types (iOS)', - description: 'iOS active subscription: expirationDateIOS, environmentIOS, daysUntilExpirationIOS', + description: + 'iOS active subscription: expirationDateIOS, environmentIOS, daysUntilExpirationIOS', path: '/docs/types/purchase#active-subscription-ios', }, { id: 'active-subscription-android', title: 'ActiveSubscriptionAndroid', category: 'Types (Android)', - description: 'Android active subscription: autoRenewingAndroid, basePlanIdAndroid, purchaseTokenAndroid', + description: + 'Android active subscription: autoRenewingAndroid, basePlanIdAndroid, purchaseTokenAndroid', path: '/docs/types/purchase#active-subscription-android', }, @@ -671,14 +701,16 @@ export const apiData: ApiItem[] = [ id: 'verify-purchase-result-ios', title: 'VerifyPurchaseResultIOS', category: 'Types (iOS)', - description: 'iOS verification result: isValid, receiptData, jwsRepresentation, latestTransaction', + description: + 'iOS verification result: isValid, receiptData, jwsRepresentation, latestTransaction', path: '/docs/types/verification#verify-purchase-result-ios', }, { id: 'verify-purchase-result-android', title: 'VerifyPurchaseResultAndroid', category: 'Types (Android)', - description: 'Android verification result: autoRenewing, cancelDate, renewalDate, transactionId', + description: + 'Android verification result: autoRenewing, cancelDate, renewalDate, transactionId', path: '/docs/types/verification#verify-purchase-result-android', }, { diff --git a/packages/docs/src/pages/404.tsx b/packages/docs/src/pages/404.tsx index 80929e22..071e3ee6 100644 --- a/packages/docs/src/pages/404.tsx +++ b/packages/docs/src/pages/404.tsx @@ -38,118 +38,118 @@ export default function NotFound() { padding: '2rem', }} > -
- {/* 404 Number */} -

- 404 -

- - {/* Description */} -

- Page not found -

- - {/* Action Buttons */}
- + 404 + - { - e.currentTarget.style.transform = 'translateY(-2px)'; - e.currentTarget.style.boxShadow = - '0 8px 20px rgba(164, 116, 101, 0.35)'; + fontSize: '1.25rem', + color: 'var(--text-secondary, #6b7280)', + marginTop: '1.5rem', + marginBottom: '3rem', }} - onMouseLeave={(e) => { - e.currentTarget.style.transform = 'translateY(0)'; - e.currentTarget.style.boxShadow = 'none'; + > + Page not found +

+ + {/* Action Buttons */} +
- Home - - + + + { + e.currentTarget.style.transform = 'translateY(-2px)'; + e.currentTarget.style.boxShadow = + '0 8px 20px rgba(164, 116, 101, 0.35)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.transform = 'translateY(0)'; + e.currentTarget.style.boxShadow = 'none'; + }} + > + Home + + +
- ); } diff --git a/packages/docs/src/pages/docs/android-setup.tsx b/packages/docs/src/pages/docs/android-setup.tsx index fd5dbce7..31dd2352 100644 --- a/packages/docs/src/pages/docs/android-setup.tsx +++ b/packages/docs/src/pages/docs/android-setup.tsx @@ -221,14 +221,20 @@ function AndroidSetup() {

Add the OpenIAP Android dependency:

{`// build.gradle.kts
 dependencies {
-    implementation("io.github.hyochan.openiap:openiap-google:${"$"}version")
+    implementation("io.github.hyochan.openiap:openiap-google:${'$'}version")
 }
 
 // Or build.gradle (Groovy)
 dependencies {
-    implementation 'io.github.hyochan.openiap:openiap-google:${"$"}version'
+    implementation 'io.github.hyochan.openiap:openiap-google:${'$'}version'
 }`}
-

+

Check the latest version at{' '}

@@ -97,7 +104,10 @@ function AndroidAPIs() {

- + checkAlternativeBillingAvailabilityAndroid

@@ -346,7 +356,10 @@ suspend fun launchExternalLink( // - linkType: ExternalLinkTypeAndroid // - linkUri: String (your external URL)`} - + createBillingProgramReportingDetailsAndroid

diff --git a/packages/docs/src/pages/docs/apis/connection.tsx b/packages/docs/src/pages/docs/apis/connection.tsx index 4953f422..f9c924ac 100644 --- a/packages/docs/src/pages/docs/apis/connection.tsx +++ b/packages/docs/src/pages/docs/apis/connection.tsx @@ -26,10 +26,18 @@ function ConnectionAPIs() {

diff --git a/packages/docs/src/pages/docs/apis/debugging.tsx b/packages/docs/src/pages/docs/apis/debugging.tsx index 8acc1a63..266da691 100644 --- a/packages/docs/src/pages/docs/apis/debugging.tsx +++ b/packages/docs/src/pages/docs/apis/debugging.tsx @@ -28,11 +28,13 @@ function DebuggingAPIs() { @@ -180,10 +182,9 @@ onPurchaseSuccess: async (purchase) => {

2. IAPKit Backend Validation (Recommended)

- Use{' '} - verifyPurchaseWithProvider with IAPKit to get accurate{' '} - basePlanId from Google Play Developer API. The response - includes offerDetails.basePlanId: + Use verifyPurchaseWithProvider with IAPKit to get + accurate basePlanId from Google Play Developer API. The + response includes offerDetails.basePlanId:

{`import { verifyPurchaseWithProvider } from 'expo-iap'; diff --git a/packages/docs/src/pages/docs/apis/index.tsx b/packages/docs/src/pages/docs/apis/index.tsx index ad26903e..122a6b54 100644 --- a/packages/docs/src/pages/docs/apis/index.tsx +++ b/packages/docs/src/pages/docs/apis/index.tsx @@ -20,8 +20,10 @@ const legacyAnchorRedirects: Record = { 'restore-purchases': '/docs/apis/purchase#restore-purchases', 'get-storefront': '/docs/apis/purchase#get-storefront', // Subscription - 'get-active-subscriptions': '/docs/apis/subscription#get-active-subscriptions', - 'has-active-subscriptions': '/docs/apis/subscription#has-active-subscriptions', + 'get-active-subscriptions': + '/docs/apis/subscription#get-active-subscriptions', + 'has-active-subscriptions': + '/docs/apis/subscription#has-active-subscriptions', 'deep-link-to-subscriptions': '/docs/apis/subscription#deep-link-to-subscriptions', // Validation @@ -41,7 +43,8 @@ const legacyAnchorRedirects: Record = { 'subscription-status-ios': '/docs/apis/ios#subscription-status-ios', 'current-entitlement-ios': '/docs/apis/ios#current-entitlement-ios', 'latest-transaction-ios': '/docs/apis/ios#latest-transaction-ios', - 'show-manage-subscriptions-ios': '/docs/apis/ios#show-manage-subscriptions-ios', + 'show-manage-subscriptions-ios': + '/docs/apis/ios#show-manage-subscriptions-ios', 'begin-refund-request-ios': '/docs/apis/ios#begin-refund-request-ios', 'is-transaction-verified-ios': '/docs/apis/ios#is-transaction-verified-ios', 'get-transaction-jws-ios': '/docs/apis/ios#get-transaction-jws-ios', @@ -111,25 +114,49 @@ function APIsIndex() {
  • - Connection: Initialize and manage store connection + + Connection + + : Initialize and manage store connection
  • - Products: Fetch product information + + Products + + : Fetch product information
  • - Purchase: Request and complete purchases + + Purchase + + : Request and complete purchases
  • - Subscription: Manage subscriptions + + Subscription + + : Manage subscriptions
  • - Validation: Verify purchases server-side + + Validation + + : Verify purchases server-side
  • - iOS APIs | Android APIs + + iOS APIs + {' '} + |{' '} + + Android APIs +
  • - Debugging: Error handling and troubleshooting + + Debugging + + : Error handling and troubleshooting
diff --git a/packages/docs/src/pages/docs/apis/ios.tsx b/packages/docs/src/pages/docs/apis/ios.tsx index c8f279e4..9eeea9cf 100644 --- a/packages/docs/src/pages/docs/apis/ios.tsx +++ b/packages/docs/src/pages/docs/apis/ios.tsx @@ -25,19 +25,28 @@ function IOSAPIs() { @@ -153,9 +162,7 @@ func getStorefrontIOS() async throws -> String`}
getTransactionJwsIOS -

- Get the transaction JWS for server-side validation (iOS 15+). -

+

Get the transaction JWS for server-side validation (iOS 15+).

{`func getTransactionJwsIOS(sku: String) async throws -> String?`} @@ -290,8 +297,9 @@ func requestPurchaseOnPromotedProductIOS() async throws -> Bool`}

In StoreKit 2, promoted products can be purchased directly via the standard purchase flow. When a user taps a promoted product in the App - Store, the promotedProductListenerIOS event fires with the - product ID. Use this ID to call requestPurchase() directly. + Store, the promotedProductListenerIOS event fires with + the product ID. Use this ID to call requestPurchase(){' '} + directly.

{`// Recommended approach let subscription = promotedProductListenerIOS { productId in diff --git a/packages/docs/src/pages/docs/apis/products.tsx b/packages/docs/src/pages/docs/apis/products.tsx index 242fe313..8d1085c2 100644 --- a/packages/docs/src/pages/docs/apis/products.tsx +++ b/packages/docs/src/pages/docs/apis/products.tsx @@ -26,11 +26,16 @@ function ProductsAPIs() {
  • - fetchProducts: Get product details (price, title, - description) + + fetchProducts + + : Get product details (price, title, description)
  • - getAvailablePurchases: Get user's unfinished purchases + + getAvailablePurchases + + : Get user's unfinished purchases
  • Use type: 'subs' for subscriptions,{' '} diff --git a/packages/docs/src/pages/docs/apis/purchase.tsx b/packages/docs/src/pages/docs/apis/purchase.tsx index d7fc834f..2d0ee943 100644 --- a/packages/docs/src/pages/docs/apis/purchase.tsx +++ b/packages/docs/src/pages/docs/apis/purchase.tsx @@ -26,18 +26,28 @@ function PurchaseAPIs() { diff --git a/packages/docs/src/pages/docs/apis/subscription.tsx b/packages/docs/src/pages/docs/apis/subscription.tsx index bcbabf11..89d3557b 100644 --- a/packages/docs/src/pages/docs/apis/subscription.tsx +++ b/packages/docs/src/pages/docs/apis/subscription.tsx @@ -26,16 +26,22 @@ function SubscriptionAPIs() { diff --git a/packages/docs/src/pages/docs/apis/validation.tsx b/packages/docs/src/pages/docs/apis/validation.tsx index 82d64090..c8166b44 100644 --- a/packages/docs/src/pages/docs/apis/validation.tsx +++ b/packages/docs/src/pages/docs/apis/validation.tsx @@ -32,11 +32,16 @@ function ValidationAPIs() { entitlements
  • - verifyPurchase: Send to your own validation server + + verifyPurchase + + : Send to your own validation server
  • - verifyPurchaseWithProvider: Use IAPKit for managed - validation + + verifyPurchaseWithProvider + + : Use IAPKit for managed validation
  • Error ≠ Invalid: Network errors don't mean the diff --git a/packages/docs/src/pages/docs/errors.tsx b/packages/docs/src/pages/docs/errors.tsx index 567fdea7..d8549b94 100644 --- a/packages/docs/src/pages/docs/errors.tsx +++ b/packages/docs/src/pages/docs/errors.tsx @@ -311,7 +311,9 @@ var product_id: String # Related product SKU (if applicable)`} - ReceiptFailed + + ReceiptFailed + PurchaseVerificationFailed @@ -319,7 +321,9 @@ var product_id: String # Related product SKU (if applicable)`} - ReceiptFinished + + ReceiptFinished + PurchaseVerificationFinished @@ -327,7 +331,9 @@ var product_id: String # Related product SKU (if applicable)`} - ReceiptFinishedFailed + + ReceiptFinishedFailed + PurchaseVerificationFinishFailed diff --git a/packages/docs/src/pages/docs/events.tsx b/packages/docs/src/pages/docs/events.tsx index c7f7cf6c..1aa5d59c 100644 --- a/packages/docs/src/pages/docs/events.tsx +++ b/packages/docs/src/pages/docs/events.tsx @@ -990,13 +990,22 @@ subscription.cancel();`} Event - UserChoiceBillingAndroid - DeveloperProvidedBillingAndroid + + UserChoiceBillingAndroid + + + DeveloperProvidedBillingAndroid + Setup - AlternativeBillingModeAndroid.UserChoice - enableBillingProgram(EXTERNAL_PAYMENTS) + developerBillingOption in requestPurchase + + AlternativeBillingModeAndroid.UserChoice + + + enableBillingProgram(EXTERNAL_PAYMENTS) +{' '} + developerBillingOption in requestPurchase + diff --git a/packages/docs/src/pages/docs/features/external-purchase.tsx b/packages/docs/src/pages/docs/features/external-purchase.tsx index b0f7530e..d7217db2 100644 --- a/packages/docs/src/pages/docs/features/external-purchase.tsx +++ b/packages/docs/src/pages/docs/features/external-purchase.tsx @@ -59,7 +59,10 @@ function ExternalPurchase() { Android Alternative Billing / Billing Programs Android 6.0+ (API 23) - Google Play Billing 6.2+ (legacy), 8.2.0+ (recommended), 8.3.0+ (External Payments) + + Google Play Billing 6.2+ (legacy), 8.2.0+ (recommended), 8.3.0+ + (External Payments) + @@ -1112,17 +1115,17 @@ func handle_user_choice_purchase(product_id: String) -> void: showAlternativeBillingInformationDialog,{' '} createAlternativeBillingReportingToken) are deprecated in Google Play Billing Library 8.2.0+. For new - implementations, use the Billing Programs API{' '} - described below. + implementations, use the{' '} + Billing Programs API described below.

    Billing Programs API (8.2.0+)

    Google Play Billing Library 8.2.0 introduces the new{' '} - Billing Programs API which replaces the legacy - alternative billing APIs. This provides better support for - External Content Links and External Offers. + Billing Programs API which replaces the + legacy alternative billing APIs. This provides better support + for External Content Links and External Offers.

    Program Types
    @@ -1132,8 +1135,8 @@ func handle_user_choice_purchase(product_id: String) -> void: external content (e.g., reader apps, music streaming)
  • - ExternalOffer - For apps offering alternative - payment options + ExternalOffer - For apps offering + alternative payment options
@@ -1416,20 +1419,38 @@ func handle_external_purchase_with_billing_programs(product_id: String) -> void: - checkAlternativeBillingAvailability() - isBillingProgramAvailable(program) + + checkAlternativeBillingAvailability() + + + isBillingProgramAvailable(program) + - showAlternativeBillingInformationDialog() - launchExternalLink(activity, params) + + showAlternativeBillingInformationDialog() + + + launchExternalLink(activity, params) + - createAlternativeBillingReportingToken() - createBillingProgramReportingDetails(program) + + createAlternativeBillingReportingToken() + + + + createBillingProgramReportingDetails(program) + + - enableAlternativeBillingOnly() - enableBillingProgram(program) + + enableAlternativeBillingOnly() + + + enableBillingProgram(program) + @@ -1437,17 +1458,17 @@ func handle_external_purchase_with_billing_programs(product_id: String) -> void:

External Payments (8.3.0+ - Japan Only)

Google Play Billing Library 8.3.0 introduces the{' '} - External Payments program, currently available - only in Japan. This presents a side-by-side choice between Google - Play Billing and the developer's external payment option directly - in the purchase flow. + External Payments program, currently + available only in Japan. This presents a side-by-side choice + between Google Play Billing and the developer's external + payment option directly in the purchase flow.

- ℹ️ Availability: The External Payments program - is currently only available in Japan. Users in other regions - will not see the developer billing option. Check{' '} + ℹ️ Availability: The External Payments + program is currently only available in Japan. Users in other + regions will not see the developer billing option. Check{' '} isBillingProgramAvailable(EXTERNAL_PAYMENTS) to verify availability.

@@ -1456,20 +1477,23 @@ func handle_external_purchase_with_billing_programs(product_id: String) -> void:
Key Differences from Other Programs
  • - Side-by-side choice: User sees both Google Play - and developer payment options in the same purchase dialog + Side-by-side choice: User sees both Google + Play and developer payment options in the same purchase + dialog
  • - DeveloperProvidedBillingListener: New callback - when user selects developer billing (different from UserChoiceBillingListener) + DeveloperProvidedBillingListener: New + callback when user selects developer billing (different from + UserChoiceBillingListener)
  • DeveloperBillingOptionParams: Configure the developer billing option in BillingFlowParams
  • - EnableBillingProgramParams: Required to enable - EXTERNAL_PAYMENTS with DeveloperProvidedBillingListener + EnableBillingProgramParams: Required to + enable EXTERNAL_PAYMENTS with + DeveloperProvidedBillingListener
@@ -1789,37 +1813,64 @@ func handle_purchase_with_external_payments(product_id: String) -> void: 0 - enableBillingProgram(EXTERNAL_PAYMENTS) - Enable External Payments program BEFORE initConnection + + enableBillingProgram(EXTERNAL_PAYMENTS) + + + Enable External Payments program BEFORE initConnection + 1 - addDeveloperProvidedBillingListener() - Register callback for when user selects developer billing + + addDeveloperProvidedBillingListener() + + + Register callback for when user selects developer + billing + 2 - isBillingProgramAvailable(EXTERNAL_PAYMENTS) + + + isBillingProgramAvailable(EXTERNAL_PAYMENTS) + + Check if available (Japan only) 3 - requestPurchase(developerBillingOption: ...) - Launch purchase with developer billing option configured + + + requestPurchase(developerBillingOption: ...) + + + + Launch purchase with developer billing option configured + 4 User Choice Dialog - User sees side-by-side choice: Google Play or Developer Billing + + User sees side-by-side choice: Google Play or Developer + Billing + 5a - If Google Play: onPurchaseSuccess + + If Google Play: onPurchaseSuccess + Normal Google Play purchase flow 5b - If Developer: DeveloperProvidedBillingListener + + If Developer:{' '} + DeveloperProvidedBillingListener + Callback receives externalTransactionToken @@ -1830,7 +1881,10 @@ func handle_purchase_with_external_payments(product_id: String) -> void: 7 Report Token - Report externalTransactionToken to Google within 24 hours + + Report externalTransactionToken to Google within 24 + hours + @@ -1857,18 +1911,23 @@ func handle_purchase_with_external_payments(product_id: String) -> void: - LAUNCH_IN_EXTERNAL_BROWSER_OR_APP - Google Play will launch the linkUri in an external browser - or eligible app. Use this for web-based payment flows. + LAUNCH_IN_EXTERNAL_BROWSER_OR_APP + + + Google Play will launch the linkUri in an external + browser or eligible app. Use this for web-based payment + flows. - CALLER_WILL_LAUNCH_LINK - Google Play returns control to your app without launching. - Your app handles launching the payment flow. Use this for - in-app payment experiences. + CALLER_WILL_LAUNCH_LINK + + + Google Play returns control to your app without + launching. Your app handles launching the payment flow. + Use this for in-app payment experiences. @@ -2104,9 +2163,7 @@ func _ready_user_choice() -> void: enableBillingProgram(program) - - Enable billing program BEFORE initConnection - + Enable billing program BEFORE initConnection 1 @@ -2123,7 +2180,8 @@ func _ready_user_choice() -> void: launchExternalLink(activity, params) - Launch external link (browser or app) with configured params + Launch external link (browser or app) with configured + params @@ -2137,15 +2195,21 @@ func _ready_user_choice() -> void: 4 - createBillingProgramReportingDetails(program) + + createBillingProgramReportingDetails(program) + + + + After successful payment, create reporting details with + token - After successful payment, create reporting details with token 5 Token Reporting - Send externalTransactionToken to Google Play backend within 24 hours + Send externalTransactionToken to Google Play backend + within 24 hours diff --git a/packages/docs/src/pages/docs/features/purchase.tsx b/packages/docs/src/pages/docs/features/purchase.tsx index 0b7b4d7d..643aff2e 100644 --- a/packages/docs/src/pages/docs/features/purchase.tsx +++ b/packages/docs/src/pages/docs/features/purchase.tsx @@ -57,7 +57,9 @@ function Purchase() { issues:

    -
  • Android: Purchases refunded after 3 days if not acknowledged
  • +
  • + Android: Purchases refunded after 3 days if not acknowledged +
  • iOS: Transaction replays on every app launch
  • Both: Users cannot repurchase consumables
@@ -405,7 +407,6 @@ await purchase_product("com.app.coins_100")`} ), }} -
@@ -596,9 +597,7 @@ Future verifyOnServer(ProductPurchase purchase) async { true - - Product can be purchased again (coins, gems, etc.) - + Product can be purchased again (coins, gems, etc.) Non-Consumable @@ -793,9 +792,7 @@ func handle_purchase(purchase: Purchase) -> void: Complete Example -

- Here's a complete implementation combining all steps: -

+

Here's a complete implementation combining all steps:

{{ @@ -1262,9 +1259,7 @@ func _exit_tree() -> void: Android purchase refunded Not acknowledged within 3 days - - Finish transaction immediately after verification - + Finish transaction immediately after verification Cannot repurchase consumable diff --git a/packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx b/packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx index e8b8bd91..0b18d2f9 100644 --- a/packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx +++ b/packages/docs/src/pages/docs/features/subscription-upgrade-downgrade.tsx @@ -1093,58 +1093,129 @@ func format_date(timestamp: int) -> String: }} > - - Mode - Legacy API - 8.1.0+ API - Description + + + Mode + + + Legacy API + + + 8.1.0+ API + + + Description + - - WITH_TIME_PRORATION + + + WITH_TIME_PRORATION + 1 1 - Immediate change with prorated credit + + Immediate change with prorated credit + - - CHARGE_PRORATED_PRICE + + + CHARGE_PRORATED_PRICE + 2 2 - Immediate change, charge difference (upgrade only) + + Immediate change, charge difference (upgrade only) + - - WITHOUT_PRORATION + + + WITHOUT_PRORATION + 3 3 - Immediate change, no proration + + Immediate change, no proration + - - CHARGE_FULL_PRICE + + + CHARGE_FULL_PRICE + 5 4 - Immediate change, charge full price + + Immediate change, charge full price + - - DEFERRED + + + DEFERRED + 6 5 - Change at next billing cycle + + Change at next billing cycle + - KEEP_EXISTING + + KEEP_EXISTING + — 6 - Keep existing payment schedule (8.1.0+ only) + + Keep existing payment schedule (8.1.0+ only) +
-

- Note: Legacy API refers to SubscriptionUpdateParams.ReplacementMode, - 8.1.0+ API refers to SubscriptionProductReplacementParams.ReplacementMode. - The integer values differ for CHARGE_FULL_PRICE and DEFERRED between APIs. +

+ Note: Legacy API refers to{' '} + SubscriptionUpdateParams.ReplacementMode, + 8.1.0+ API refers to{' '} + + SubscriptionProductReplacementParams.ReplacementMode + + . The integer values differ for CHARGE_FULL_PRICE and + DEFERRED between APIs.

@@ -1155,13 +1226,18 @@ func format_date(timestamp: int) -> String: 🆕 Billing Library 8.1.0+: Per-Product Replacement Params} + title={ + <> + 🆕 Billing Library 8.1.0+: Per-Product Replacement Params + + } variant="tip" >

Starting with Google Play Billing Library 8.1.0, you can use{' '} - subscriptionProductReplacementParams for more granular - control over subscription replacements at the product level: + subscriptionProductReplacementParams for more + granular control over subscription replacements at the + product level:

    @@ -1180,10 +1256,10 @@ func format_date(timestamp: int) -> String:

- This API is useful when you need different replacement behaviors - for different products in a multi-product purchase scenario. - The new KEEP_EXISTING mode is only available through - this API. + This API is useful when you need different replacement + behaviors for different products in a multi-product purchase + scenario. The new KEEP_EXISTING mode is only + available through this API.

@@ -1347,7 +1423,8 @@ if current_sub:
  1. - Use DEFERRED replacement mode (Legacy API: 6, 8.1.0+ API: 5) + Use DEFERRED replacement mode (Legacy API: + 6, 8.1.0+ API: 5)
  2. No immediate charge to the user
  3. User keeps premium access until current period ends
  4. @@ -1503,10 +1580,16 @@ if premium_purchase: 📝 Code Example: Using subscriptionProductReplacementParams (8.1.0+)} + title={ + <> + 📝 Code Example: Using + subscriptionProductReplacementParams (8.1.0+) + + } >

    - For more granular control, use the new per-product replacement params API: + For more granular control, use the new per-product + replacement params API:

    {{ @@ -1743,16 +1826,16 @@ for purchase in purchases:
    1. Specify replacement mode when needed: Pass{' '} - replacementMode when you want to - override the default configured in Google Play Console + replacementMode when you want to override the + default configured in Google Play Console
    2. Use WITH_TIME_PRORATION for upgrades to give users credit for unused time
    3. - Use DEFERRED for downgrades to let - users keep premium features until period ends + Use DEFERRED for downgrades to let users + keep premium features until period ends
    4. Handle DEFERRED mode correctly: When using diff --git a/packages/docs/src/pages/docs/features/subscription.tsx b/packages/docs/src/pages/docs/features/subscription.tsx index e31fbf24..8a6f0c97 100644 --- a/packages/docs/src/pages/docs/features/subscription.tsx +++ b/packages/docs/src/pages/docs/features/subscription.tsx @@ -1064,8 +1064,8 @@ suspend fun purchaseSubscription(subscriptionId: String) {

      When a subscription group has multiple base plans (weekly, monthly, yearly), there is no way to determine which specific - plan was purchased from the client-side{' '} - Purchase object alone. + plan was purchased from the client-side Purchase{' '} + object alone.

      What This Means

      diff --git a/packages/docs/src/pages/docs/guides/ai-assistants.tsx b/packages/docs/src/pages/docs/guides/ai-assistants.tsx index cb78ab88..134fa83b 100644 --- a/packages/docs/src/pages/docs/guides/ai-assistants.tsx +++ b/packages/docs/src/pages/docs/guides/ai-assistants.tsx @@ -82,7 +82,9 @@ function AIAssistants() {

      Concise API overview (~300 lines)

      -
        +
        • Installation basics
        • API signatures
        • Core types
        • @@ -109,7 +111,9 @@ function AIAssistants() {

          Complete API documentation (~1000 lines)

          -
            +
            • Full configuration options
            • Complete API documentation
            • All type definitions
            • @@ -138,8 +142,7 @@ function AIAssistants() { Click Add new doc
            • - Enter URL:{' '} - https://openiap.dev/llms.txt + Enter URL: https://openiap.dev/llms.txt
            • Name it OpenIAP diff --git a/packages/docs/src/pages/docs/horizon-setup.tsx b/packages/docs/src/pages/docs/horizon-setup.tsx index 96c8fcde..7392daa0 100644 --- a/packages/docs/src/pages/docs/horizon-setup.tsx +++ b/packages/docs/src/pages/docs/horizon-setup.tsx @@ -50,7 +50,8 @@ function HorizonSetup() {
            • - openiap-google@1.3.2 or later installed (Horizon flavor support added in 1.3.2) + openiap-google@1.3.2 or later installed (Horizon flavor + support added in 1.3.2)
            • A Meta Quest device for testing (Quest 2, Quest 3, Quest Pro)
            @@ -129,9 +130,7 @@ function HorizonSetup() {
          -
        • - Set the Product ID (SKU) - This must match your code exactly -
        • +
        • Set the Product ID (SKU) - This must match your code exactly
        • Configure pricing and availability
        • Save and activate the product
        @@ -162,7 +161,7 @@ function HorizonSetup() { Add your Horizon App ID to AndroidManifest.xml:

        -{` + {`

        - The simplest approach - the build flavor you select determines which billing SDK is compiled into your APK: + The simplest approach - the build flavor you select determines which + billing SDK is compiled into your APK:

        -{`// Kotlin - Default constructor + {`// Kotlin - Default constructor val store = OpenIapStore(context) // Build with horizonDebug/horizonRelease: @@ -228,7 +228,7 @@ val store = OpenIapStore(context)

        Force Horizon billing even on non-Quest devices (for testing):

        -{`// Kotlin + {`// Kotlin val store = OpenIapStore( context, store = "horizon", @@ -245,7 +245,7 @@ val store = OpenIapStore( -{`// Initialize store (uses build flavor) + {`// Initialize store (uses build flavor) val store = OpenIapStore(context) // Connect to billing @@ -308,7 +308,7 @@ lifecycleScope.launch { -{`# Connect Quest via USB + {`# Connect Quest via USB adb devices # Install your APK @@ -347,10 +347,11 @@ adb logcat | grep OpenIap`}

        - Create or update local.properties in your project root with your Horizon App ID: + Create or update local.properties in your project root + with your Horizon App ID:

        -{`# local.properties + {`# local.properties sdk.dir=/path/to/Android/sdk # Add your Horizon OS App ID @@ -364,10 +365,11 @@ horizon.app.id=YOUR_HORIZON_APP_ID`}

        - Configure Android build flavors to support both Google Play and Horizon OS: + Configure Android build flavors to support both Google Play and + Horizon OS:

        -{`// build.gradle.kts + {`// build.gradle.kts val localProperties = Properties().apply { val localPropertiesFile = rootProject.file("local.properties") if (localPropertiesFile.exists()) { @@ -403,19 +405,22 @@ android {

        - Android Studio provides build variants for each flavor. Select the variant from the Build Variants panel: + Android Studio provides build variants for each flavor. Select the + variant from the Build Variants panel:

        • - horizonDebug - Horizon OS billing (for Quest devices) + horizonDebug - Horizon OS billing (for Quest + devices)
        • - playDebug - Google Play billing (for phones/tablets) + playDebug - Google Play billing (for + phones/tablets)

        - Build separate APKs for each platform: horizonDebug/horizonRelease for Quest devices, - playDebug/playRelease for Android phones/tablets. + Build separate APKs for each platform: horizonDebug/horizonRelease for + Quest devices, playDebug/playRelease for Android phones/tablets.

        💡 Tip: To change build variant in Android Studio:
          -
        1. Open "Build Variants" panel (View → Tool Windows → Build Variants)
        2. -
        3. Select your desired variant (e.g., "horizonDebug" or "playDebug")
        4. +
        5. + Open "Build Variants" panel (View → Tool Windows → Build Variants) +
        6. +
        7. + Select your desired variant (e.g., "horizonDebug" or "playDebug") +
        8. Click the "Run" button to build and install
        @@ -443,7 +452,7 @@ android {

        Alternatively, build from command line:

        -{`# Build for Horizon OS (Meta Quest devices) + {`# Build for Horizon OS (Meta Quest devices) ./gradlew assembleHorizonDebug # Build for Google Play (Android phones/tablets) @@ -470,7 +479,9 @@ android {
      • Check that your app is registered in Horizon Developer Hub
      • Ensure the device is connected to internet
      • -
      • Check logs: adb logcat | grep OpenIap
      • +
      • + Check logs: adb logcat | grep OpenIap +

      Products Not Found

      @@ -478,14 +489,19 @@ android {
    5. Verify products are created and activated in Developer Hub
    6. Check that SKU/Product IDs match exactly (case-sensitive)
    7. Ensure your test account has access to the products
    8. -
    9. Wait a few minutes after creating products for them to propagate
    10. +
    11. + Wait a few minutes after creating products for them to propagate +
    12. Purchase Flow Not Starting

      • Ensure you're logged into a Meta account on the device
      • Verify the test user has permission to test purchases
      • -
      • Check that the activity context is valid when calling requestPurchase
      • +
      • + Check that the activity context is valid when calling + requestPurchase +
      diff --git a/packages/docs/src/pages/docs/lifecycle/subscription.tsx b/packages/docs/src/pages/docs/lifecycle/subscription.tsx index 41b2e292..3d345cd7 100644 --- a/packages/docs/src/pages/docs/lifecycle/subscription.tsx +++ b/packages/docs/src/pages/docs/lifecycle/subscription.tsx @@ -88,7 +88,9 @@ function Subscription() { ✅ - Pending upgrade/downgrade + + Pending upgrade/downgrade + ✅ pendingUpgradeProductId @@ -145,8 +147,7 @@ function Subscription() { RenewalInfoIOS - , but server validation is still - recommended for production apps. + , but server validation is still recommended for production apps.

      Android: Only isAutoRenewing available @@ -229,8 +230,8 @@ function Subscription() {

      • - iOS: App Store Server API +{' '} - App Store Server Notifications V2 + iOS: App Store Server API + App Store Server + Notifications V2
      • Android: Google Play Developer API + RTDN @@ -238,9 +239,14 @@ function Subscription() {
      - 💡 Easy Server Verification with IAPKit} variant="tip" defaultOpen> + 💡 Easy Server Verification with IAPKit} + variant="tip" + defaultOpen + >

      - Setting up server-side verification can be complex. OpenIAP's partner{' '} + Setting up server-side verification can be complex. OpenIAP's + partner{' '} IAPKit {' '} - provides a simple, unified API for server-side receipt validation across - both iOS and Android platforms. + provides a simple, unified API for server-side receipt validation + across both iOS and Android platforms.

      - With IAPKit, you can verify purchases, manage subscriptions, and handle - webhooks without building complex server infrastructure from scratch. + With IAPKit, you can verify purchases, manage subscriptions, and + handle webhooks without building complex server infrastructure from + scratch.

      Learn more about IAPKit integration in our{' '} @@ -264,11 +271,15 @@ function Subscription() { rel="noopener noreferrer" > announcement - . + + .

      - ⚠️ Why server-side validation?} variant="warning"> + ⚠️ Why server-side validation?} + variant="warning" + >
      • Authoritative source: Client data can be @@ -287,8 +298,8 @@ function Subscription() { manipulation
      • - Analytics: Track subscription metrics and - revenue server-side + Analytics: Track subscription metrics and revenue + server-side
      @@ -357,8 +368,12 @@ function Subscription() {
      1. initConnection()
      2. getAvailablePurchases() → [PurchaseAndroid]
      3. For each purchase:
      -
      → check purchaseState
      -
      → check isAcknowledged
      +
      + → check purchaseState +
      +
      + → check isAcknowledged +
      → validate with server
      @@ -368,7 +383,9 @@ function Subscription() {
      → finishTransaction() if not acknowledged
      -
      +
      ⚠️ Unacknowledged purchases auto-refund after 3 days
      @@ -436,14 +453,16 @@ function Subscription() { finishTransaction()
      - • PENDING (2) → awaiting payment (slow payment - methods) + • PENDING (2) → awaiting payment (slow + payment methods)
      UNSPECIFIED (0) → unknown state, handle as error
      -
      +
      ⚠️ Must acknowledge within 3 days or auto-refund
      @@ -773,7 +792,8 @@ function Subscription() { Without server validation:
      - • getAvailablePurchases() may still return the purchase temporarily + • getAvailablePurchases() may still return the purchase + temporarily
      • ❌ App grants access (incorrect - refunded!) @@ -868,10 +888,9 @@ function Subscription() { RenewalInfoIOS {' '} - type contains - detailed renewal information that lets you build subscription - management UI without server calls. However, server validation - is still recommended for production apps. + type contains detailed renewal information that lets you build + subscription management UI without server calls. However, + server validation is still recommended for production apps.

      @@ -947,10 +966,10 @@ function Subscription() {
      1. Immediately after upgrade: The{' '} - productId may still show the old tier (monthly), - but autoRenewPreference shows the new tier - (yearly). The pendingUpgradeProductId is set to - the new tier. + productId may still show the old tier + (monthly), but autoRenewPreference shows the + new tier (yearly). The pendingUpgradeProductId{' '} + is set to the new tier.
      2. After processing (few minutes): The{' '} @@ -983,7 +1002,8 @@ function Subscription() {

        This logic is already calculated in{' '} pendingUpgradeProductId by comparing{' '} - productId with autoRenewPreference. + productId with autoRenewPreference + .

        @@ -1084,7 +1104,9 @@ function Subscription() { - Get detailed subscription status
      3. - RenewalInfoIOS{' '} + + RenewalInfoIOS + {' '} - Type reference
      4. diff --git a/packages/docs/src/pages/docs/types/alternative.tsx b/packages/docs/src/pages/docs/types/alternative.tsx index c77a09f8..edb429d1 100644 --- a/packages/docs/src/pages/docs/types/alternative.tsx +++ b/packages/docs/src/pages/docs/types/alternative.tsx @@ -26,21 +26,36 @@ function TypesAlternative() {
        • - BillingProgramAndroid - USER_CHOICE_BILLING (7.0+), - EXTERNAL_CONTENT_LINK, EXTERNAL_OFFER, EXTERNAL_PAYMENTS (8.2.0+, 8.3.0+) + + BillingProgramAndroid + {' '} + - USER_CHOICE_BILLING (7.0+), EXTERNAL_CONTENT_LINK, EXTERNAL_OFFER, + EXTERNAL_PAYMENTS (8.2.0+, 8.3.0+)
        • - InitConnectionConfig.enableBillingProgramAndroid - Recommended way to enable billing programs + + InitConnectionConfig.enableBillingProgramAndroid + {' '} + - Recommended way to enable billing programs
        • - DeveloperBillingOptionParamsAndroid - Configure - external payments in purchase flow (8.3.0+) + + DeveloperBillingOptionParamsAndroid + {' '} + - Configure external payments in purchase flow (8.3.0+)
        • - AlternativeBillingModeAndroid - Deprecated: Use BillingProgramAndroid instead + + AlternativeBillingModeAndroid + {' '} + - Deprecated: Use{' '} + BillingProgramAndroid instead
        • - External Purchase Link APIs for iOS 17.4+ and 18.2+ + + External Purchase Link APIs + {' '} + for iOS 17.4+ and 18.2+
        • For Android alternative billing, use the{' '} @@ -61,13 +76,24 @@ function TypesAlternative() {

          - AlternativeBillingModeAndroid (Deprecated) + AlternativeBillingModeAndroid{' '} + + (Deprecated) +
          - Deprecated: Use enableBillingProgramAndroid with BillingProgramAndroid instead. + Deprecated: Use{' '} + enableBillingProgramAndroid with{' '} + BillingProgramAndroid instead.
            -
          • USER_CHOICEBillingProgramAndroid.USER_CHOICE_BILLING
          • -
          • ALTERNATIVE_ONLYBillingProgramAndroid.EXTERNAL_OFFER
          • +
          • + USER_CHOICE →{' '} + BillingProgramAndroid.USER_CHOICE_BILLING +
          • +
          • + ALTERNATIVE_ONLY →{' '} + BillingProgramAndroid.EXTERNAL_OFFER +

          @@ -128,9 +154,11 @@ function TypesAlternative() { enableBillingProgramAndroid - (Recommended) Enable a specific billing program during connection. - Use USER_CHOICE_BILLING for user choice, EXTERNAL_OFFER for alternative only, - or EXTERNAL_PAYMENTS for Japan external payments (8.3.0+). + (Recommended) Enable a specific billing program + during connection. Use USER_CHOICE_BILLING for user + choice, EXTERNAL_OFFER for alternative only, or{' '} + EXTERNAL_PAYMENTS for Japan external payments + (8.3.0+). @@ -138,7 +166,9 @@ function TypesAlternative() { alternativeBillingModeAndroid - (Deprecated){' '} + + (Deprecated) + {' '} Use enableBillingProgramAndroid instead. @@ -453,8 +483,8 @@ await iap.request_purchase(props) Alternative Billing Only Complete Example

          - With External Offer mode (replaces Alternative Only), all purchases go through your - alternative payment system. Google Play is not shown: + With External Offer mode (replaces Alternative Only), all purchases go + through your alternative payment system. Google Play is not shown:

          {{ @@ -670,8 +700,8 @@ if payment_result.success: Billing Programs (Android 8.2.0+)

          - Google Play Billing Library 8.2.0+ introduces the Billing Programs API, - which provides a more structured approach to external offers and + Google Play Billing Library 8.2.0+ introduces the Billing Programs + API, which provides a more structured approach to external offers and content links. Version 8.3.0 adds External Payments for Japan.

          @@ -679,7 +709,9 @@ if payment_result.success: BillingProgramAndroid

          - Enum for different billing program types. Use with enableBillingProgramAndroid in InitConnectionConfig: + Enum for different billing program types. Use with{' '} + enableBillingProgramAndroid in{' '} + InitConnectionConfig:

          @@ -704,7 +736,8 @@ if payment_result.success: EXTERNAL_CONTENT_LINK @@ -713,7 +746,8 @@ if payment_result.success: EXTERNAL_OFFER @@ -722,7 +756,8 @@ if payment_result.success: EXTERNAL_PAYMENTS @@ -733,7 +768,8 @@ if payment_result.success: DeveloperBillingOptionParamsAndroid

          - Parameters for configuring developer billing option in purchase flow (8.3.0+): + Parameters for configuring developer billing option in purchase flow + (8.3.0+):

          - For apps that link to external content (reader apps, music streaming) + For apps that link to external content (reader apps, music + streaming) 8.2.0+
          - For apps offering alternative payment options (replaces ALTERNATIVE_ONLY) + For apps offering alternative payment options (replaces + ALTERNATIVE_ONLY) 8.2.0+
          - Side-by-side choice between Google Play and developer billing (Japan only) + Side-by-side choice between Google Play and developer billing + (Japan only) 8.3.0+
          @@ -762,9 +798,7 @@ if payment_result.success: - + - +
          String - URL where the external payment will be processed - URL where the external payment will be processed
          @@ -773,9 +807,7 @@ if payment_result.success: DeveloperBillingLaunchModeAndroid - How to launch the external payment link - How to launch the external payment link
          @@ -783,9 +815,7 @@ if payment_result.success: DeveloperBillingLaunchModeAndroid -

          - How the external payment URL is launched: -

          +

          How the external payment URL is launched:

          @@ -816,9 +846,7 @@ if payment_result.success: DeveloperProvidedBillingDetailsAndroid -

          - Details received when user selects developer billing (8.3.0+): -

          +

          Details received when user selects developer billing (8.3.0+):

          @@ -836,7 +864,8 @@ if payment_result.success: String @@ -991,8 +1020,8 @@ if result.is_available:

          Token Reporting: When a user completes a purchase through developer billing, you must report the{' '} - externalTransactionToken to Google Play within 24 hours. - See{' '} + externalTransactionToken to Google Play within 24 + hours. See{' '} External Payments documentation {' '} diff --git a/packages/docs/src/pages/docs/types/android.tsx b/packages/docs/src/pages/docs/types/android.tsx index 7c23ccb6..cc3c8b2c 100644 --- a/packages/docs/src/pages/docs/types/android.tsx +++ b/packages/docs/src/pages/docs/types/android.tsx @@ -195,7 +195,9 @@ function TypesAndroid() {

          - +
          - Token to report external transaction to Google (must report within 24 hours) + Token to report external transaction to Google (must report + within 24 hours)
          string | null Purchase option ID to identify which option was selected (7.0+) + Purchase option ID to identify which option was selected (7.0+) +
          diff --git a/packages/docs/src/pages/docs/types/offer.tsx b/packages/docs/src/pages/docs/types/offer.tsx index 7f378eb0..ed51e222 100644 --- a/packages/docs/src/pages/docs/types/offer.tsx +++ b/packages/docs/src/pages/docs/types/offer.tsx @@ -125,7 +125,11 @@ function TypesOffer() { DiscountOfferType! - Type of offer: Introductory, Promotional, WinBack (iOS 18+), or OneTime + + Type of offer: Introductory,{' '} + Promotional, WinBack (iOS 18+), or{' '} + OneTime + @@ -242,7 +246,10 @@ function TypesOffer() { String - Purchase option ID for identifying which purchase option was selected (7.0+) + + Purchase option ID for identifying which purchase option was + selected (7.0+) + @@ -490,7 +497,10 @@ enum DiscountOfferType { DiscountOfferType! - Introductory, Promotional, or WinBack (iOS 18+) + + Introductory, Promotional, or{' '} + WinBack (iOS 18+) + @@ -649,7 +659,9 @@ enum DiscountOfferType { InstallmentPlanDetailsAndroid - Installment plan details for subscription commitments (7.0+) + + Installment plan details for subscription commitments (7.0+) + diff --git a/packages/docs/src/pages/docs/types/product.tsx b/packages/docs/src/pages/docs/types/product.tsx index 5bba51a9..e1e6a535 100644 --- a/packages/docs/src/pages/docs/types/product.tsx +++ b/packages/docs/src/pages/docs/types/product.tsx @@ -24,17 +24,28 @@ function TypesProduct() { @@ -117,14 +128,16 @@ function TypesProduct() { store - Store discriminator: "apple",{' '} - "google", or "horizon" + Store discriminator: "apple", "google" + , or "horizon" platform{' '} - + (deprecated) @@ -221,15 +234,14 @@ function TypesProduct() { Array of one-time purchase offers. Each offer contains:{' '} formattedPrice,{' '} priceAmountMicros,{' '} - priceCurrencyCode,{' '} - offerToken,{' '} + priceCurrencyCode, offerToken,{' '} discountDisplayInfo (discount info),{' '} fullPriceMicros (original price),{' '} validTimeWindow,{' '} limitedQuantityInfo,{' '} preorderDetailsAndroid,{' '} - rentalDetailsAndroid. - See Discounts. + rentalDetailsAndroid. See{' '} + Discounts. Requires{' '} productStatusAndroid - Product fetch status code. Values: OK (success),{' '} - NOT_FOUND (SKU doesn't exist),{' '} - NO_OFFERS_AVAILABLE (user not eligible for any offers),{' '} - UNKNOWN. - Requires{' '} + Product fetch status code. Values: OK{' '} + (success), NOT_FOUND (SKU doesn't exist),{' '} + NO_OFFERS_AVAILABLE (user not eligible for + any offers), UNKNOWN. Requires{' '}

          - These types combine platform-specific types with a{' '} - store discriminator for type-safe handling across Apple, - Google, and Horizon stores. + These types combine platform-specific types with a store{' '} + discriminator for type-safe handling across Apple, Google, and Horizon + stores.

          @@ -481,8 +492,8 @@ function TypesProduct() {

          - Note: The platform field is deprecated. - Use store instead. + Note: The platform field is + deprecated. Use store instead.

          diff --git a/packages/docs/src/pages/docs/types/purchase.tsx b/packages/docs/src/pages/docs/types/purchase.tsx index f4f05c91..3cb7ac81 100644 --- a/packages/docs/src/pages/docs/types/purchase.tsx +++ b/packages/docs/src/pages/docs/types/purchase.tsx @@ -25,17 +25,28 @@ function TypesPurchase() {
          @@ -86,10 +97,20 @@ function TypesPurchase() { -

          - Note: iOS StoreKit 2 only returns Transaction objects on successful purchases, - so iOS purchases always have Purchased state. See{' '} - release notes for details. +

          + Note: iOS StoreKit 2 only returns Transaction objects on + successful purchases, so iOS purchases always have{' '} + Purchased state. See{' '} + + release notes + {' '} + for details.

          @@ -145,14 +166,16 @@ function TypesPurchase() { store - Store discriminator: "apple",{' '} - "google", or "horizon" + Store discriminator: "apple", "google" + , or "horizon" platform{' '} - + (deprecated) @@ -552,10 +575,11 @@ function TypesPurchase() { pendingPurchaseUpdateAndroid - Pending subscription upgrade/downgrade details. When a user - initiates a plan change, this contains the new product IDs - and purchase token for the pending transaction. Returns null - if no pending update exists. See{' '} + Pending subscription upgrade/downgrade details. When a + user initiates a plan change, this contains the new + product IDs and purchase token for the pending + transaction. Returns null if no pending update exists. + See{' '} PendingPurchaseUpdateAndroid {' '} @@ -589,9 +613,10 @@ function TypesPurchase() {

          - Contains details about a pending subscription upgrade or downgrade. - When a user changes their subscription plan, the new plan may be - pending until the current billing period ends. + Contains details about a pending subscription upgrade or + downgrade. When a user changes their subscription plan, the + new plan may be pending until the current billing period + ends.

          @@ -615,8 +640,8 @@ function TypesPurchase() { purchaseToken @@ -663,7 +688,9 @@ function TypesPurchase() { - + - +
          - Unique token identifying the pending transaction. - Use this to track or manage the pending update. + Unique token identifying the pending transaction. Use + this to track or manage the pending update.
          - willExpireSoon{' '} + + willExpireSoon + {' '} deprecated diff --git a/packages/docs/src/pages/docs/types/request.tsx b/packages/docs/src/pages/docs/types/request.tsx index e87b0655..60b0beeb 100644 --- a/packages/docs/src/pages/docs/types/request.tsx +++ b/packages/docs/src/pages/docs/types/request.tsx @@ -18,24 +18,33 @@ function TypesRequest() { keywords="IAP types, ProductRequest, RequestPurchaseProps, TypeScript, Swift, Kotlin" />

          Request Types

          -

          - Type definitions for requesting products and initiating purchases. -

          +

          Type definitions for requesting products and initiating purchases.

          @@ -354,20 +363,28 @@ await iap.request_purchase(subs_props)`}
          ios{' '} - + (deprecated) Use apple instead + Use apple instead +
          android{' '} - + (deprecated) Use google instead + Use google instead +
          @@ -388,34 +405,43 @@ await iap.request_purchase(subs_props)`} apple - Apple subscription parameters (RequestSubscriptionIosProps) + + Apple subscription parameters (RequestSubscriptionIosProps) + google - Google subscription parameters - (RequestSubscriptionAndroidProps) + Google subscription parameters (RequestSubscriptionAndroidProps) ios{' '} - + (deprecated) - Use apple instead + + Use apple instead + android{' '} - + (deprecated) - Use google instead + + Use google instead + @@ -535,8 +561,8 @@ await iap.request_purchase(subs_props)`} offerToken - Offer token for one-time purchase discounts (7.0+). - Pass the offerToken from{' '} + Offer token for one-time purchase discounts (7.0+). Pass + the offerToken from{' '} oneTimePurchaseOfferDetailsAndroid or{' '} discountOffers to apply a discount. diff --git a/packages/docs/src/pages/docs/types/verification.tsx b/packages/docs/src/pages/docs/types/verification.tsx index f4d00356..36fb2032 100644 --- a/packages/docs/src/pages/docs/types/verification.tsx +++ b/packages/docs/src/pages/docs/types/verification.tsx @@ -21,26 +21,35 @@ function TypesVerification() {

          Verification Types

          Type definitions for purchase verification with{' '} - verifyPurchase() and verifyPurchaseWithProvider(). + verifyPurchase() and{' '} + verifyPurchaseWithProvider().

          @@ -70,8 +79,7 @@ function TypesVerification() { apple - Apple App Store verification options. Contains:{' '} - sku + Apple App Store verification options. Contains: sku @@ -79,10 +87,9 @@ function TypesVerification() { google - Google Play verification options. Contains:{' '} - sku, packageName,{' '} - purchaseToken, accessToken,{' '} - isSub + Google Play verification options. Contains: sku,{' '} + packageName, purchaseToken,{' '} + accessToken, isSub @@ -586,7 +593,10 @@ function TypesVerification() { 'inauthentic' - Purchase failed authenticity validation (potentially fraudulent). + + Purchase failed authenticity validation (potentially + fraudulent). + diff --git a/packages/docs/src/pages/docs/updates/announcements.tsx b/packages/docs/src/pages/docs/updates/announcements.tsx index ae0e7ed1..64f9d20b 100644 --- a/packages/docs/src/pages/docs/updates/announcements.tsx +++ b/packages/docs/src/pages/docs/updates/announcements.tsx @@ -573,7 +573,7 @@ function Announcements() { // Sort by date (newest first) const sortedAnnouncements = [...announcements].sort( - (a, b) => b.date.getTime() - a.date.getTime(), + (a, b) => b.date.getTime() - a.date.getTime() ); return ( diff --git a/packages/docs/src/pages/docs/updates/notes.tsx b/packages/docs/src/pages/docs/updates/notes.tsx index 1072e216..6a5b331c 100644 --- a/packages/docs/src/pages/docs/updates/notes.tsx +++ b/packages/docs/src/pages/docs/updates/notes.tsx @@ -1,9 +1,6 @@ import { useMemo } from 'react'; import SEO from '../../../components/SEO'; -import { - useScrollToHash, - getHashId, -} from '../../../hooks/useScrollToHash'; +import { useScrollToHash, getHashId } from '../../../hooks/useScrollToHash'; import CodeBlock from '../../../components/CodeBlock'; import Pagination from '../../../components/Pagination'; import AnchorLink from '../../../components/AnchorLink'; @@ -36,8 +33,15 @@ function Notes() { 📅 openiap-apple v1.3.15 - iOS 15 Compatibility & watchOS Support -

          - Fixed iOS 15 compatibility for currency code retrieval, unified platform availability annotations, and added watchOS support. +

          + Fixed iOS 15 compatibility for currency code retrieval, unified + platform availability annotations, and added watchOS support.

          {/* Section 1: iOS 15 Compatibility */} @@ -45,9 +49,17 @@ function Notes() {
          1. iOS 15 Compatibility Fix
          -

          - Fixed potential crash when using priceFormatStyle.currencyCode on iOS 15 devices. - Now uses product.priceFormatStyle.locale.currencyCode as fallback to get the correct App Store currency. +

          + Fixed potential crash when using{' '} + priceFormatStyle.currencyCode on iOS 15 devices. Now + uses product.priceFormatStyle.locale.currencyCode as + fallback to get the correct App Store currency.

          {`// iOS 16+: Direct API product.priceFormatStyle.currencyCode @@ -61,8 +73,15 @@ product.priceFormatStyle.locale.currencyCode`}
          2. watchOS Support Added
          -

          - Added watchOS 8.0+ deployment target to podspec and unified all @available annotations. +

          + Added watchOS 8.0+ deployment target to podspec and + unified all @available annotations.

          {`@available(iOS 15.0, macOS 14.0, tvOS 16.0, watchOS 8.0, *)`}
      @@ -72,18 +91,63 @@ product.priceFormatStyle.locale.currencyCode`}
      3. Apple Documentation Links
      -

      - Added SeeAlso documentation links to all main types for easier navigation to Apple's official StoreKit documentation. +

      + Added SeeAlso documentation links to all main types + for easier navigation to Apple's official StoreKit documentation.

      {/* References */}
      - References -
      @@ -96,11 +160,19 @@ product.priceFormatStyle.locale.currencyCode`} element: (
      - 📅 openiap-gql v1.3.17 / openiap-google v1.3.28 - Android BillingClient Enhancement + 📅 openiap-gql v1.3.17 / openiap-google v1.3.28 - Android + BillingClient Enhancement -

      - Added new fields from Google Play Billing Library 5.0+ and 7.0+ for offer details, installment plans, and pending subscription updates. +

      + Added new fields from Google Play Billing Library 5.0+ and 7.0+ for + offer details, installment plans, and pending subscription updates.

      {/* Section 1: purchaseOptionId */} @@ -108,14 +180,36 @@ product.priceFormatStyle.locale.currencyCode`}
      1. purchaseOptionId for One-Time Purchase Offers
      -

      - Identifies which purchase option the user selected for one-time products with multiple offers. +

      + Identifies which purchase option the user selected for one-time + products with multiple offers.

      -
      - - ProductAndroidOneTimePurchaseOfferDetail.purchaseOptionId + @@ -126,18 +220,40 @@ product.priceFormatStyle.locale.currencyCode`}
      2. InstallmentPlanDetailsAndroid for Subscriptions
      -

      - Subscription installment plans - users pay over a commitment period (e.g., 12 monthly payments). +

      + Subscription installment plans - users pay over a commitment + period (e.g., 12 monthly payments).

      {`type InstallmentPlanDetailsAndroid { commitmentPaymentsCount: Int! # Initial commitment payments subsequentCommitmentPaymentsCount: Int! # Renewal commitment (0 = reverts to normal) }`} -
      - - ProductSubscriptionAndroidOfferDetails.installmentPlanDetails + @@ -146,17 +262,35 @@ product.priceFormatStyle.locale.currencyCode`} {/* Section 3: PendingPurchaseUpdateAndroid */}
      - 3. PendingPurchaseUpdateAndroid for Upgrades/Downgrades + 3. PendingPurchaseUpdateAndroid for + Upgrades/Downgrades
      -

      - Track pending subscription plan changes that take effect at the end of the current billing period. +

      + Track pending subscription plan changes that take effect at the + end of the current billing period.

      {`type PendingPurchaseUpdateAndroid { products: [String!]! # New product IDs user is switching to purchaseToken: String! # Token for the pending transaction }`} - @@ -182,36 +362,88 @@ product.priceFormatStyle.locale.currencyCode`} element: (
      - 📅 openiap-gql v1.3.16 / openiap-apple v1.3.14 - ExternalPurchaseCustomLink Support (iOS 18.1+) + 📅 openiap-gql v1.3.16 / openiap-apple v1.3.14 - + ExternalPurchaseCustomLink Support (iOS 18.1+) -

      - Added full support for Apple's ExternalPurchaseCustomLink API (iOS 18.1+) for apps using custom external purchase links with token-based reporting. +

      + Added full support for Apple's{' '} + ExternalPurchaseCustomLink API (iOS 18.1+) for apps + using custom external purchase links with token-based reporting.

      1. New APIs
      -
        -
      • isEligibleForExternalPurchaseCustomLinkIOS() - Check if app can use ExternalPurchaseCustomLink API
      • -
      • getExternalPurchaseCustomLinkTokenIOS(tokenType) - Get token for reporting to Apple's External Purchase Server API
      • -
      • showExternalPurchaseCustomLinkNoticeIOS(noticeType) - Show CustomLink-specific disclosure notice sheet
      • +
          +
        • + isEligibleForExternalPurchaseCustomLinkIOS() - + Check if app can use ExternalPurchaseCustomLink API +
        • +
        • + getExternalPurchaseCustomLinkTokenIOS(tokenType) - + Get token for reporting to Apple's External Purchase Server API +
        • +
        • + showExternalPurchaseCustomLinkNoticeIOS(noticeType){' '} + - Show CustomLink-specific disclosure notice sheet +
      2. New Types
      -
        -
      • ExternalPurchaseCustomLinkTokenTypeIOS - Token types: acquisition, services
      • -
      • ExternalPurchaseCustomLinkNoticeTypeIOS - Notice types: browser
      • -
      • ExternalPurchaseCustomLinkTokenResultIOS - Token result with token and error
      • -
      • ExternalPurchaseCustomLinkNoticeResultIOS - Notice result with continued and error
      • +
          +
        • + ExternalPurchaseCustomLinkTokenTypeIOS - Token + types: acquisition, services +
        • +
        • + ExternalPurchaseCustomLinkNoticeTypeIOS - Notice + types: browser +
        • +
        • + ExternalPurchaseCustomLinkTokenResultIOS - Token + result with token and error +
        • +
        • + ExternalPurchaseCustomLinkNoticeResultIOS - Notice + result with continued and error +
      -
      3. Improved presentExternalPurchaseNoticeSheetIOS()
      -

      - Now returns externalPurchaseToken field when user continues. This token is required for reporting transactions to Apple's External Purchase Server API. +

      + 3. Improved presentExternalPurchaseNoticeSheetIOS() +
      +

      + Now returns externalPurchaseToken field when user + continues. This token is required for reporting transactions to + Apple's External Purchase Server API.

      {`// Before result.result // "continue" or "dismissed" @@ -225,18 +457,72 @@ result.error // optional error`}
      4. API Comparison
      -

      - ExternalPurchase (17.4+): Basic external purchase notice | ExternalPurchaseCustomLink (18.1+): Custom links with token-based reporting +

      + ExternalPurchase (17.4+): Basic external purchase + notice | ExternalPurchaseCustomLink (18.1+): Custom + links with token-based reporting

      - References -
      @@ -249,27 +535,90 @@ result.error // optional error`} element: (
      - 📅 openiap-gql v1.3.15 / openiap-google v1.3.27 / openiap-apple v1.3.13 - Bug Fix + 📅 openiap-gql v1.3.15 / openiap-google v1.3.27 / openiap-apple + v1.3.13 - Bug Fix -

      - Fixed incorrect replacementModeConstant mapping in applySubscriptionProductReplacementParams. The function was using values from the legacy SubscriptionUpdateParams.ReplacementMode API instead of the new SubscriptionProductReplacementParams.ReplacementMode API (Billing Library 8.1.0+). Issue: #71 +

      + Fixed incorrect replacementModeConstant mapping in{' '} + applySubscriptionProductReplacementParams. The function + was using values from the legacy{' '} + SubscriptionUpdateParams.ReplacementMode API instead of + the new{' '} + SubscriptionProductReplacementParams.ReplacementMode{' '} + API (Billing Library 8.1.0+). Issue:{' '} + + #71 +

      Mode Value Changes
      -
        -
      • CHARGE_FULL_PRICE: 5 → 4
      • -
      • DEFERRED: 6 → 5
      • -
      • KEEP_EXISTING: 7 → 6
      • +
          +
        • + CHARGE_FULL_PRICE: 5 → 4 +
        • +
        • + DEFERRED: 6 → 5 +
        • +
        • + KEEP_EXISTING: 7 → 6 +
      - References -
      @@ -282,37 +631,103 @@ result.error // optional error`} element: (
      - 📅 openiap-gql v1.3.14 / openiap-google v1.3.25 / openiap-apple v1.3.13 - Breaking Changes & Bug Fixes + 📅 openiap-gql v1.3.14 / openiap-google v1.3.25 / openiap-apple + v1.3.13 - Breaking Changes & Bug Fixes -

      - Breaking changes for iOS subscription props, bug fixes for Android displayPrice, and Objective-C bridge updates. +

      + Breaking changes for iOS subscription props, bug fixes for Android + displayPrice, and Objective-C bridge updates.

      -
      1. iOS - Subscription-Only Props Cleanup (Breaking Change)
      -

      - Removed subscription-specific fields from RequestPurchaseIosProps. These fields now only exist in RequestSubscriptionIosProps. +

      + 1. iOS - Subscription-Only Props Cleanup (Breaking Change) +
      +

      + Removed subscription-specific fields from{' '} + RequestPurchaseIosProps. These fields now only exist + in RequestSubscriptionIosProps.

      -
        -
      • introductoryOfferEligibility - Removed
      • -
      • promotionalOfferJWS - Removed
      • -
      • winBackOffer - Removed
      • +
          +
        • + introductoryOfferEligibility - Removed +
        • +
        • + promotionalOfferJWS - Removed +
        • +
        • + winBackOffer - Removed +
        -

        Migration: Use requestSubscription() API.

        +

        + Migration: Use requestSubscription() API. +

      -
      2. Known Issue - introductoryOfferEligibility API (#68)
      -

      - Current field uses Boolean type, but Apple's introductoryOfferEligibility(compactJWS:) API requires a JWS string. Will be corrected in future release. +

      + 2. Known Issue - introductoryOfferEligibility API ( + + #68 + + ) +
      +

      + Current field uses Boolean type, but Apple's{' '} + + introductoryOfferEligibility(compactJWS:) + {' '} + API requires a JWS string. Will be corrected in future release.

      -
      3. Android - Fix displayPrice for Subscriptions with Free Trials
      -

      - Fixed displayPrice returning "Free" or "$0.00" instead of actual base/recurring price. +

      + 3. Android - Fix displayPrice for Subscriptions with + Free Trials +
      +

      + Fixed displayPrice returning "Free" or "$0.00" + instead of actual base/recurring price.

      {`// Before (bug): displayPrice = "Free", price = 0.0 // After (fixed): displayPrice = "$9.99", price = 9.99 @@ -320,17 +735,58 @@ result.error // optional error`}
      -
      4. Apple v1.3.13 - Objective-C Bridge Updates
      -

      - Updated OpenIapModule+ObjC.swift to expose new Swift async functions to Objective-C. Critical for kmp-iap. See Objective-C Bridge Documentation. +

      + 4. Apple v1.3.13 - Objective-C Bridge Updates +
      +

      + Updated OpenIapModule+ObjC.swift to expose new Swift + async functions to Objective-C. Critical for kmp-iap. See{' '} + + Objective-C Bridge Documentation + + .

      - References -
      @@ -343,67 +799,198 @@ result.error // optional error`} element: (
      - 📅 openiap-gql v1.3.13 / openiap-google v1.3.24 / openiap-apple v1.3.11 - Platform API Gap Analysis + 📅 openiap-gql v1.3.13 / openiap-google v1.3.24 / openiap-apple + v1.3.11 - Platform API Gap Analysis -

      - New iOS win-back offers, JWS promotional offers, and Android product status codes. +

      + New iOS win-back offers, JWS promotional offers, and Android product + status codes.

      -
      1. iOS - Win-Back Offers (iOS 18+)
      -

      - Added support for win-back offers to re-engage churned subscribers. +

      + 1. iOS - Win-Back Offers (iOS 18+) +
      +

      + Added support for{' '} + + win-back offers + {' '} + to re-engage churned subscribers.

      -
        -
      • winBackOffer - New field in purchase props
      • -
      • WinBackOfferInputIOS - Input type with offerId field
      • -
      • SubscriptionOfferTypeIOS.WinBack - New enum value
      • +
          +
        • + winBackOffer - New field in purchase props +
        • +
        • + WinBackOfferInputIOS - Input type with{' '} + offerId field +
        • +
        • + SubscriptionOfferTypeIOS.WinBack - New enum value +
      -
      2. iOS - JWS Promotional Offers (iOS 15+, WWDC 2025)
      -

      - New signature format using compact JWS string for promotional offers. Back-deployed to iOS 15. Requires Xcode 16.4+. +

      + 2. iOS - JWS Promotional Offers (iOS 15+, WWDC 2025) +
      +

      + New signature format using compact JWS string for promotional + offers. Back-deployed to iOS 15. Requires Xcode 16.4+.

      -
        -
      • promotionalOfferJWS - New field in purchase props
      • -
      • PromotionalOfferJWSInputIOS - Input type with offerId and jws fields
      • +
          +
        • + promotionalOfferJWS - New field in purchase props +
        • +
        • + PromotionalOfferJWSInputIOS - Input type with{' '} + offerId and jws fields +
      -
      3. iOS - Introductory Offer Eligibility Override (iOS 15+, WWDC 2025)
      -

      - introductoryOfferEligibility - Override system eligibility check. Set true/false/nil for system default. Requires Xcode 16.4+. +

      + 3. iOS - Introductory Offer Eligibility Override (iOS 15+, WWDC + 2025) +
      +

      + introductoryOfferEligibility - Override system + eligibility check. Set true/false/ + nil for system default. Requires Xcode 16.4+.

      -
      4. Android - Product Status Codes (Billing 8.0+)
      -

      - Product-level status codes indicating why products couldn't be fetched. +

      + 4. Android - Product Status Codes (Billing 8.0+) +
      +

      + Product-level status codes indicating why products couldn't be + fetched.

      -
        -
      • ProductStatusAndroid - Enum: Ok, NotFound, NoOffersAvailable, Unknown
      • -
      • productStatusAndroid - New field on ProductAndroid
      • +
          +
        • + ProductStatusAndroid - Enum: Ok,{' '} + NotFound, NoOffersAvailable,{' '} + Unknown +
        • +
        • + productStatusAndroid - New field on{' '} + ProductAndroid +
      -
      5. Android - Auto Service Reconnection
      -

      - enableAutoServiceReconnection() is now always enabled internally since OpenIAP uses Billing Library 8.3.0+. +

      + 5. Android - Auto Service Reconnection +
      +

      + enableAutoServiceReconnection() is now always enabled + internally since OpenIAP uses Billing Library 8.3.0+.

      - References -
      @@ -416,63 +1003,174 @@ result.error // optional error`} element: (
      - 📅 openiap-gql v1.3.12 / openiap-google v1.3.22 / openiap-apple v1.3.10 - Standardized Offer Types + 📅 openiap-gql v1.3.12 / openiap-google v1.3.22 / openiap-apple + v1.3.10 - Standardized Offer Types -

      - Introduced standardized DiscountOffer and SubscriptionOffer types for unified handling across iOS and Android. +

      + Introduced standardized DiscountOffer and{' '} + SubscriptionOffer types for unified handling across iOS + and Android.

      -
      1. DiscountOffer (One-time products)
      -
        +
        + 1. DiscountOffer (One-time products) +
        +
        • Cross-platform type for one-time purchase discounts
        • -
        • Android fields: offerTokenAndroid, fullPriceMicrosAndroid, percentageDiscountAndroid
        • -
        • Replaces deprecated ProductAndroidOneTimePurchaseOfferDetail
        • +
        • + Android fields: offerTokenAndroid,{' '} + fullPriceMicrosAndroid,{' '} + percentageDiscountAndroid +
        • +
        • + Replaces deprecated{' '} + ProductAndroidOneTimePurchaseOfferDetail +
      -
      2. SubscriptionOffer
      -
        -
      • Cross-platform type for subscription offers (introductory, promotional)
      • -
      • Includes paymentMode: FreeTrial, PayAsYouGo, PayUpFront
      • -
      • Replaces deprecated ProductSubscriptionAndroidOfferDetails, DiscountOfferIOS, DiscountIOS
      • +
        + 2. SubscriptionOffer +
        +
          +
        • + Cross-platform type for subscription offers (introductory, + promotional) +
        • +
        • + Includes paymentMode: FreeTrial, PayAsYouGo, + PayUpFront +
        • +
        • + Replaces deprecated{' '} + ProductSubscriptionAndroidOfferDetails,{' '} + DiscountOfferIOS, DiscountIOS +
      -
      3. New Fields on Product Types
      -
        -
      • discountOffers: [DiscountOffer!] - One-time product discounts
      • -
      • subscriptionOffers: [SubscriptionOffer!] - Subscription offers
      • +
        + 3. New Fields on Product Types +
        +
          +
        • + discountOffers: [DiscountOffer!] - One-time product + discounts +
        • +
        • + subscriptionOffers: [SubscriptionOffer!] - + Subscription offers +
      -
      4. PaymentMode Logic Fix (Android)
      -
        +
        + 4. PaymentMode Logic Fix (Android) +
        +
        • Zero price → FreeTrial (regardless of recurrenceMode)
        • NON_RECURRING (3) with paid → PayUpFront
        • -
        • FINITE_RECURRING (2) / INFINITE_RECURRING (1) with paid → PayAsYouGo
        • +
        • + FINITE_RECURRING (2) / INFINITE_RECURRING (1) with paid → + PayAsYouGo +
      5. Deprecated Types
      -
        -
      • ProductAndroidOneTimePurchaseOfferDetailDiscountOffer
      • -
      • ProductSubscriptionAndroidOfferDetailsSubscriptionOffer
      • -
      • oneTimePurchaseOfferDetailsAndroiddiscountOffers
      • -
      • subscriptionOfferDetailsAndroidsubscriptionOffers
      • +
          +
        • + + ProductAndroidOneTimePurchaseOfferDetail + {' '} + → DiscountOffer +
        • +
        • + + ProductSubscriptionAndroidOfferDetails + {' '} + → SubscriptionOffer +
        • +
        • + + oneTimePurchaseOfferDetailsAndroid + {' '} + → discountOffers +
        • +
        • + + subscriptionOfferDetailsAndroid + {' '} + → subscriptionOffers +
      - References -
      @@ -485,50 +1183,153 @@ result.error // optional error`} element: (
      - 📅 openiap-gql v1.3.11 / openiap-google v1.3.21 / openiap-apple v1.3.9 - PurchaseState Cleanup + 📅 openiap-gql v1.3.11 / openiap-google v1.3.21 / openiap-apple + v1.3.9 - PurchaseState Cleanup -

      - Simplified PurchaseState enum and deprecated AlternativeBillingModeAndroid in favor of BillingProgramAndroid. +

      + Simplified PurchaseState enum and deprecated + AlternativeBillingModeAndroid in favor of BillingProgramAndroid.

      -
      1. PurchaseState Simplified
      -

      - Removed unused Failed, Restored, Deferred states. Now: Pending, Purchased, Unknown +

      + 1. PurchaseState Simplified +
      +

      + Removed unused Failed, Restored,{' '} + Deferred states. Now: Pending,{' '} + Purchased, Unknown

      -
        -
      • Failed - Platforms return errors instead
      • -
      • Restored - Returns as Purchased state
      • -
      • Deferred - StoreKit 2 has no transaction state; Android uses Pending
      • +
          +
        • + Failed - Platforms return errors instead +
        • +
        • + Restored - Returns as Purchased state +
        • +
        • + Deferred - StoreKit 2 has no transaction state; + Android uses Pending +
      -
      2. API Consolidation - BillingProgramAndroid
      -

      - Deprecated AlternativeBillingModeAndroid in favor of unified BillingProgramAndroid enum. +

      + 2. API Consolidation - BillingProgramAndroid +
      +

      + Deprecated AlternativeBillingModeAndroid in favor of + unified BillingProgramAndroid enum.

      -
        -
      • BillingProgramAndroid.USER_CHOICE_BILLING - New enum value (7.0+)
      • -
      • AlternativeBillingModeAndroid - Deprecated
      • -
      • InitConnectionConfig.alternativeBillingModeAndroid - Deprecated
      • +
          +
        • + BillingProgramAndroid.USER_CHOICE_BILLING - New + enum value (7.0+) +
        • +
        • + + AlternativeBillingModeAndroid + {' '} + - Deprecated +
        • +
        • + + + InitConnectionConfig.alternativeBillingModeAndroid + + {' '} + - Deprecated +
      3. Migration
      -
        -
      • alternativeBillingModeAndroid: USER_CHOICEenableBillingProgramAndroid: USER_CHOICE_BILLING
      • -
      • alternativeBillingModeAndroid: ALTERNATIVE_ONLYenableBillingProgramAndroid: EXTERNAL_OFFER
      • +
          +
        • + alternativeBillingModeAndroid: USER_CHOICE →{' '} + enableBillingProgramAndroid: USER_CHOICE_BILLING +
        • +
        • + alternativeBillingModeAndroid: ALTERNATIVE_ONLY →{' '} + enableBillingProgramAndroid: EXTERNAL_OFFER +
      - References -
      @@ -541,45 +1342,134 @@ result.error // optional error`} element: (
      - 📅 openiap-gql v1.3.10 / openiap-google v1.3.19 / openiap-apple v1.3.8 - Google Play Billing 8.3.0 External Payments + 📅 openiap-gql v1.3.10 / openiap-google v1.3.19 / openiap-apple + v1.3.8 -{' '} + + Google Play Billing 8.3.0 External Payments + -

      - InitConnectionConfig enhancement, auto connection management for iOS, and External Payments program support. +

      + InitConnectionConfig enhancement, auto connection management for + iOS, and External Payments program support.

      -
      1. GQL v1.3.10 - InitConnectionConfig Enhancement
      -

      - Added enableBillingProgramAndroid: BillingProgramAndroid field for easier billing program setup during initConnection(). +

      + 1. GQL v1.3.10 - InitConnectionConfig Enhancement +
      +

      + Added{' '} + enableBillingProgramAndroid: BillingProgramAndroid{' '} + field for easier billing program setup during{' '} + initConnection().

      -
      2. Apple v1.3.8 - Auto Connection Management
      -

      - All API methods now automatically call initConnection() internally. No need to manually call it before using any API. Backward compatible. +

      + 2. Apple v1.3.8 - Auto Connection Management +
      +

      + All API methods now automatically call{' '} + initConnection() internally. No need to manually call + it before using any API. Backward compatible.

      -
      3. Google v1.3.19 - External Payments Program (Japan Only)
      -

      - Billing Library 8.3.0 introduces side-by-side choice between Google Play Billing and developer's external payment. +

      + 3. Google v1.3.19 - External Payments Program (Japan Only) +
      +

      + Billing Library 8.3.0 introduces side-by-side choice between + Google Play Billing and developer's external payment.

      -
        -
      • BillingProgramAndroid.EXTERNAL_PAYMENTS - New billing program type
      • -
      • DeveloperBillingOptionParamsAndroid - Configure external payment option
      • -
      • DeveloperProvidedBillingDetailsAndroid - Contains externalTransactionToken
      • -
      • IapEvent.DeveloperProvidedBillingAndroid - New event
      • +
          +
        • + BillingProgramAndroid.EXTERNAL_PAYMENTS - New + billing program type +
        • +
        • + DeveloperBillingOptionParamsAndroid - Configure + external payment option +
        • +
        • + DeveloperProvidedBillingDetailsAndroid - Contains + externalTransactionToken +
        • +
        • + IapEvent.DeveloperProvidedBillingAndroid - New + event +
      - References -
      @@ -593,38 +1483,125 @@ result.error // optional error`} element: (
      - 📅 openiap-google v1.3.16 - Google Play Billing 8.2.1 + 📅 openiap-google v1.3.16 -{' '} + + Google Play Billing 8.2.1 + -

      - Upgraded from 8.1.0 to 8.2.1 with new Billing Programs API. Skipped 8.2.0 due to bugs in isBillingProgramAvailableAsync and createBillingProgramReportingDetailsAsync. +

      + Upgraded from 8.1.0 to 8.2.1 with new Billing Programs API. Skipped + 8.2.0 due to bugs in isBillingProgramAvailableAsync and{' '} + createBillingProgramReportingDetailsAsync.

      1. New APIs
      -
        -
      • enableBillingProgram() - Setup BillingClient for billing programs
      • -
      • isBillingProgramAvailableAsync() - Determine user eligibility
      • -
      • createBillingProgramReportingDetailsAsync() - Create external transaction token
      • -
      • launchExternalLink() - Initiate external link
      • +
          +
        • + enableBillingProgram() - Setup BillingClient for + billing programs +
        • +
        • + isBillingProgramAvailableAsync() - Determine user + eligibility +
        • +
        • + createBillingProgramReportingDetailsAsync() - + Create external transaction token +
        • +
        • + launchExternalLink() - Initiate external link +
      2. Deprecated APIs
      -
        -
      • enableExternalOffer()enableBillingProgram(BillingProgramAndroid.ExternalOffer)
      • -
      • isExternalOfferAvailableAsync()isBillingProgramAvailable()
      • -
      • createExternalOfferReportingDetailsAsync()createBillingProgramReportingDetails()
      • -
      • showExternalOfferInformationDialog()launchExternalLink()
      • +
          +
        • + + enableExternalOffer() + {' '} + →{' '} + + enableBillingProgram(BillingProgramAndroid.ExternalOffer) + +
        • +
        • + + isExternalOfferAvailableAsync() + {' '} + → isBillingProgramAvailable() +
        • +
        • + + createExternalOfferReportingDetailsAsync() + {' '} + → createBillingProgramReportingDetails() +
        • +
        • + + showExternalOfferInformationDialog() + {' '} + → launchExternalLink() +
      - References -
      @@ -641,14 +1618,36 @@ result.error // optional error`} 📅 openiap-gql v1.3.8 - Kotlin Null-Safe Casting -

      - Fixed potential TypeCastException in generated Kotlin types by using safe casts (as?) instead of unsafe casts (as). +

      + Fixed potential TypeCastException in generated Kotlin + types by using safe casts (as?) instead of unsafe casts + (as).

      -
        -
      • Lists now use mapNotNull with safe element casting
      • -
      • Non-nullable fields provide sensible defaults (empty string, false, 0, emptyList)
      • -
      • Prevents crashes when JSON keys are missing or contain unexpected null values
      • +
          +
        • + Lists now use mapNotNull with safe element casting +
        • +
        • + Non-nullable fields provide sensible defaults (empty string, + false, 0, emptyList) +
        • +
        • + Prevents crashes when JSON keys are missing or contain unexpected + null values +
      ), @@ -661,32 +1660,79 @@ result.error // optional error`} element: (
      - 📅 openiap-gql v1.3.7 / openiap-apple v1.3.7 / openiap-google v1.3.15 - Advanced Commerce Data + 📅 openiap-gql v1.3.7 / openiap-apple v1.3.7 / openiap-google + v1.3.15 - Advanced Commerce Data -

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

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

      -
      1. advancedCommerceData Field
      -
        -
      • New optional field in RequestPurchaseIosProps and RequestSubscriptionIosProps
      • -
      • Use cases: Campaign attribution, affiliate marketing, promotional code tracking
      • +
        + 1. advancedCommerceData Field +
        +
          +
        • + New optional field in RequestPurchaseIosProps and{' '} + RequestSubscriptionIosProps +
        • +
        • + Use cases: Campaign attribution, affiliate marketing, + promotional code tracking +
      -
      2. Deprecated requestPurchaseOnPromotedProductIOS()
      -

      - In StoreKit 2, use promotedProductListenerIOS + requestPurchase() directly. +

      + 2. Deprecated requestPurchaseOnPromotedProductIOS() +
      +

      + In StoreKit 2, use promotedProductListenerIOS +{' '} + requestPurchase() directly.

      -
      3. Android: google Field Support
      -

      - Now supports google field with fallback to deprecated android field. +

      + 3. Android: google Field Support +
      +

      + Now supports google field with fallback to deprecated{' '} + android field.

      @@ -700,17 +1746,40 @@ result.error // optional error`} element: (
      - 📅 openiap-gql v1.3.5 / openiap-apple v1.3.5 - GitHub Release Tag Management Update + 📅 openiap-gql v1.3.5 / openiap-apple v1.3.5 - GitHub Release Tag + Management Update -

      - No API changes. Updated GitHub release tag management for Swift Package Manager (SPM) compatibility. +

      + No API changes. Updated GitHub release tag management for Swift + Package Manager (SPM) compatibility.

      -
        -
      • Apple: Uses semver tags directly (e.g., 1.3.5) - Required for SPM
      • -
      • GQL: Uses gql- prefix (e.g., gql-1.3.5)
      • -
      • Google: Uses google- prefix (e.g., google-1.3.5)
      • +
          +
        • + Apple: Uses semver tags directly (e.g.,{' '} + 1.3.5) - Required for SPM +
        • +
        • + GQL: Uses gql- prefix (e.g.,{' '} + gql-1.3.5) +
        • +
        • + Google: Uses google- prefix (e.g.,{' '} + google-1.3.5) +
      ), @@ -723,21 +1792,52 @@ result.error // optional error`} element: (
      - 📅 openiap-gql v1.3.4 / openiap-google v1.3.14 / openiap-apple v1.3.2 - Platform-Specific Verification + 📅 openiap-gql v1.3.4 / openiap-google v1.3.14 / openiap-apple + v1.3.2 - Platform-Specific Verification -

      - verifyPurchase API refactored (Breaking Change). Now requires platform-specific options. sku moved inside each platform options. +

      + verifyPurchase API refactored (Breaking Change). Now + requires platform-specific options. sku moved inside + each platform options.

      -
        -
      • VerifyPurchaseAppleOptions - Apple App Store verification
      • -
      • VerifyPurchaseGoogleOptions - Google Play with packageName, purchaseToken, accessToken
      • -
      • VerifyPurchaseHorizonOptions - Meta Horizon (Quest) via S2S API
      • -
      • androidOptions → Use google instead
      • +
          +
        • + VerifyPurchaseAppleOptions - Apple App Store + verification +
        • +
        • + VerifyPurchaseGoogleOptions - Google Play with + packageName, purchaseToken, accessToken +
        • +
        • + VerifyPurchaseHorizonOptions - Meta Horizon (Quest) + via S2S API +
        • +
        • + + androidOptions + {' '} + → Use google instead +
        -

        See: verifyPurchase API

        +

        + See: verifyPurchase API +

      ), }, @@ -749,20 +1849,61 @@ result.error // optional error`} element: (
      - 📅 openiap-google v1.3.12 / openiap-gql v1.3.2 - Google Play Billing 8.2.0 Billing Programs API + 📅 openiap-google v1.3.12 / openiap-gql v1.3.2 -{' '} + + Google Play Billing 8.2.0 + {' '} + Billing Programs API -

      - New Billing Programs API (8.2.0+) and deprecated alternative billing APIs. +

      + New Billing Programs API (8.2.0+) and deprecated alternative billing + APIs.

      -
        -
      • enableBillingProgram(), isBillingProgramAvailable(), createBillingProgramReportingDetails(), launchExternalLink()
      • -
      • checkAlternativeBillingAvailability()isBillingProgramAvailable()
      • -
      • showAlternativeBillingInformationDialog()launchExternalLink()
      • +
          +
        • + enableBillingProgram(),{' '} + isBillingProgramAvailable(),{' '} + createBillingProgramReportingDetails(),{' '} + launchExternalLink() +
        • +
        • + + checkAlternativeBillingAvailability() + {' '} + → isBillingProgramAvailable() +
        • +
        • + + showAlternativeBillingInformationDialog() + {' '} + → launchExternalLink() +
        -

        See: External Purchase Guide

        +

        + See:{' '} + + External Purchase Guide + +

      ), }, @@ -774,17 +1915,46 @@ result.error // optional error`} element: (
      - 📅 openiap-google v1.3.11 / openiap-gql v1.3.1 - Google Play Billing 8.1.0 + 📅 openiap-google v1.3.11 / openiap-gql v1.3.1 -{' '} + + Google Play Billing 8.1.0 + -

      - Billing Library 8.0.0 → 8.1.0, minSdk 21 → 23, Kotlin 2.0.21 → 2.2.0. +

      + Billing Library 8.0.0 → 8.1.0, minSdk 21 → 23, Kotlin 2.0.21 → + 2.2.0.

      -
        -
      • isSuspendedAndroid - Detect suspended subscriptions due to payment failures
      • -
      • PreorderDetailsAndroid - New type for pre-order products
      • -
      • oneTimePurchaseOfferDetailsAndroid - Changed to array type
      • +
          +
        • + isSuspendedAndroid - Detect suspended subscriptions + due to payment failures +
        • +
        • + PreorderDetailsAndroid - New type for pre-order + products +
        • +
        • + oneTimePurchaseOfferDetailsAndroid - Changed to array + type +
      ), @@ -800,12 +1970,35 @@ result.error // optional error`} 📅 openiap v1.3.0 - Platform Props & Store Field Updates -

      - Breaking Changes: Purchase.platformstore, ios/android props → apple/google. +

      + Breaking Changes:{' '} + + Purchase.platform + {' '} + → store,{' '} + + ios/android + {' '} + props → apple/google.

      -
        -
      • New: verifyPurchaseWithProvider - Verification with external providers like IAPKit
      • +
          +
        • + New: verifyPurchaseWithProvider - Verification with + external providers like IAPKit +
      ), @@ -821,8 +2014,15 @@ result.error // optional error`} 📅 openiap v1.2.6 - validateReceipt → verifyPurchase -

      - Terminology alignment with modern StoreKit 2. "Receipt Validation" was Apple's legacy term. Unified interface across iOS and Android. +

      + Terminology alignment with modern StoreKit 2. "Receipt Validation" + was Apple's legacy term. Unified interface across iOS and Android.

      ), @@ -838,8 +2038,15 @@ result.error // optional error`} 📅 openiap v1.2.0 - Version Alignment & Alternative Billing -

      - Version jumped from 1.0.12 to 1.2.0 to align with native libraries. iOS External Purchase & Android Alternative Billing support. +

      + Version jumped from 1.0.12 to 1.2.0 to align with native libraries. + iOS External Purchase & Android Alternative Billing support.

      ), @@ -855,12 +2062,29 @@ result.error // optional error`} 📅 openiap-gql 1.0.12 - External Purchase Support -

      - iOS External Purchase (iOS 17.4+, 18.2+) and Android Alternative Billing (Billing Library 6.2+/7.0+). +

      + iOS External Purchase (iOS 17.4+, 18.2+) and Android Alternative + Billing (Billing Library 6.2+/7.0+).

      -
        -
      • canPresentExternalPurchaseNoticeIOS(), presentExternalPurchaseNoticeSheetIOS(), presentExternalPurchaseLinkIOS()
      • +
          +
        • + canPresentExternalPurchaseNoticeIOS(),{' '} + presentExternalPurchaseNoticeSheetIOS(),{' '} + presentExternalPurchaseLinkIOS() +
      ), @@ -876,8 +2100,16 @@ result.error // optional error`} 📅 August 2025 - Subscription Status APIs -

      - New standardized APIs: getActiveSubscriptions(), hasActiveSubscriptions() - automatic detection without requiring product IDs. +

      + New standardized APIs: getActiveSubscriptions(),{' '} + hasActiveSubscriptions() - automatic detection without + requiring product IDs.

      ), @@ -893,8 +2125,15 @@ result.error // optional error`} 📅 August 31, 2024 - Billing Library v5 Deprecated -

      - All apps must use Google Play Billing Library v6.0.1 or later. Deadline extended to November 1, 2024. +

      + All apps must use Google Play Billing Library v6.0.1 or later. + Deadline extended to November 1, 2024.

      ), diff --git a/packages/docs/src/pages/introduction.tsx b/packages/docs/src/pages/introduction.tsx index fabb56a0..6c2c66d4 100644 --- a/packages/docs/src/pages/introduction.tsx +++ b/packages/docs/src/pages/introduction.tsx @@ -550,9 +550,7 @@ await endConnection();`} {/* Getting Started */}

      Getting Started

      -

      - Choose your framework to get started: -

      +

      Choose your framework to get started:

      API Reference diff --git a/packages/docs/src/pages/tutorials.tsx b/packages/docs/src/pages/tutorials.tsx index 249c224a..3c5c9536 100644 --- a/packages/docs/src/pages/tutorials.tsx +++ b/packages/docs/src/pages/tutorials.tsx @@ -75,11 +75,20 @@ function Tutorials() { e.currentTarget.style.borderColor = 'var(--border-color)'; }} > - + Android Billing Library 8.0.0 Release 2025-06-30 @@ -108,11 +117,20 @@ function Tutorials() { e.currentTarget.style.borderColor = 'var(--border-color)'; }} > - + What's new in StoreKit and In-App Purchase 2025-06-10 From 7941323372f55e793d1963252f8113e9bd40dc5b Mon Sep 17 00:00:00 2001 From: Hyo Date: Sat, 21 Mar 2026 01:54:01 +0900 Subject: [PATCH 5/6] style(docs): format CSS files to pass CI prettier check CI checks src/**/*.{ts,tsx,css} but previous commit only formatted ts/tsx files, missing the CSS glob. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/docs/src/styles/documentation.css | 4 ++-- packages/docs/src/styles/pages.css | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/docs/src/styles/documentation.css b/packages/docs/src/styles/documentation.css index 9a4b96bd..78650dd6 100644 --- a/packages/docs/src/styles/documentation.css +++ b/packages/docs/src/styles/documentation.css @@ -197,7 +197,8 @@ display: flex; align-items: center; gap: var(--spacing-sm); - padding: var(--spacing-xs) var(--spacing-lg) var(--spacing-xs) calc(var(--spacing-lg) + 0.5rem); + padding: var(--spacing-xs) var(--spacing-lg) var(--spacing-xs) + calc(var(--spacing-lg) + 0.5rem); color: var(--text-secondary); font-size: var(--font-size-sm); text-decoration: none; @@ -399,7 +400,6 @@ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } - /* Anchor Links */ .anchor-heading { position: relative; diff --git a/packages/docs/src/styles/pages.css b/packages/docs/src/styles/pages.css index 56bcc509..cdafdb15 100644 --- a/packages/docs/src/styles/pages.css +++ b/packages/docs/src/styles/pages.css @@ -507,7 +507,9 @@ border: 1px solid var(--border-color); text-decoration: none; color: var(--text-primary); - transition: transform 0.2s, box-shadow 0.2s; + transition: + transform 0.2s, + box-shadow 0.2s; } .getting-started-card:hover { From ac659773b772b34bb5eb45b4740db9261a330cd2 Mon Sep 17 00:00:00 2001 From: Hyo Date: Sat, 21 Mar 2026 02:01:57 +0900 Subject: [PATCH 6/6] fix(docs): escape regex in search and fix duplicate API item id - Add escapeRegExp helper to prevent crash when typing regex metacharacters (e.g., (, [, +) in the search modal - Fix duplicate 'subscription-status-ios' id in searchData.ts (renamed to 'subscription-status-ios-type' for the Types entry) Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/docs/src/components/SearchModal.tsx | 6 +++++- packages/docs/src/lib/searchData.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/docs/src/components/SearchModal.tsx b/packages/docs/src/components/SearchModal.tsx index 0be29c8c..909ac9f4 100644 --- a/packages/docs/src/components/SearchModal.tsx +++ b/packages/docs/src/components/SearchModal.tsx @@ -9,9 +9,13 @@ interface SearchModalProps { onClose: () => void; } +function escapeRegExp(value: string) { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + function highlightMatch(text: string, query: string) { if (!query) return text; - const parts = text.split(new RegExp(`(${query})`, 'gi')); + const parts = text.split(new RegExp(`(${escapeRegExp(query)})`, 'gi')); return parts.map((part, index) => part.toLowerCase() === query.toLowerCase() ? ( diff --git a/packages/docs/src/lib/searchData.ts b/packages/docs/src/lib/searchData.ts index 40e63ae0..a6e97089 100644 --- a/packages/docs/src/lib/searchData.ts +++ b/packages/docs/src/lib/searchData.ts @@ -522,7 +522,7 @@ export const apiData: ApiItem[] = [ path: '/docs/types/ios#payment-mode', }, { - id: 'subscription-status-ios', + id: 'subscription-status-ios-type', title: 'SubscriptionStatusIOS', category: 'Types (iOS)', description: 'iOS subscription status from StoreKit 2: state, renewalInfo',