diff --git a/bun.lock b/bun.lock index 9e78d072..ee54ca7c 100644 --- a/bun.lock +++ b/bun.lock @@ -16,7 +16,7 @@ }, "packages/docs": { "name": "@hyodotdev/openiap-docs", - "version": "1.0.0", + "version": "2.0.0", "dependencies": { "@preact/signals-react": "^3.2.1", "@types/prismjs": "^1.26.5", @@ -61,7 +61,7 @@ }, "packages/gql": { "name": "@hyodotdev/openiap-gql", - "version": "1.0.0", + "version": "2.0.0", "devDependencies": { "@graphql-codegen/add": "^6.0.0", "@graphql-codegen/cli": "^6.0.0", diff --git a/libraries/expo-iap/example/.env.example b/libraries/expo-iap/example/.env.example index 79be8836..619c5fa5 100644 --- a/libraries/expo-iap/example/.env.example +++ b/libraries/expo-iap/example/.env.example @@ -1,3 +1,3 @@ # IAPKit Configuration -# Get your API key from https://iapkit.com +# Get your API key from https://kit.openiap.dev EXPO_PUBLIC_IAPKIT_API_KEY=your_iapkit_api_key_here diff --git a/libraries/expo-iap/example/app.config.ts b/libraries/expo-iap/example/app.config.ts index e75c0954..4cada57a 100644 --- a/libraries/expo-iap/example/app.config.ts +++ b/libraries/expo-iap/example/app.config.ts @@ -30,7 +30,7 @@ export default ({config}: ConfigContext): ExpoConfig => { '../app.plugin.js', { // IAPKit API key for server-side receipt verification - // Get your API key from https://iapkit.com + // Get your API key from https://kit.openiap.dev iapkitApiKey: process.env.EXPO_PUBLIC_IAPKIT_API_KEY, enableLocalDev: useLocalDev, localPath: { diff --git a/libraries/expo-iap/plugin/src/withIAP.ts b/libraries/expo-iap/plugin/src/withIAP.ts index 3a7d17a1..af7364f2 100644 --- a/libraries/expo-iap/plugin/src/withIAP.ts +++ b/libraries/expo-iap/plugin/src/withIAP.ts @@ -489,7 +489,7 @@ const withIapIOS: ConfigPlugin = ( export interface ExpoIapPluginOptions { /** * IAPKit API key for server-side receipt verification. - * Get your API key from https://iapkit.com + * Get your API key from https://kit.openiap.dev * This will be available via `Constants.expoConfig?.extra?.iapkitApiKey` */ iapkitApiKey?: string; diff --git a/libraries/flutter_inapp_purchase/example/env.example b/libraries/flutter_inapp_purchase/example/env.example index ec6aa10e..a52704fc 100644 --- a/libraries/flutter_inapp_purchase/example/env.example +++ b/libraries/flutter_inapp_purchase/example/env.example @@ -1,3 +1,3 @@ # IAPKit API Key for purchase verification -# Get your API key from https://iapkit.com +# Get your API key from https://kit.openiap.dev IAPKIT_API_KEY=your_iapkit_api_key_here diff --git a/libraries/flutter_inapp_purchase/example/lib/src/constants.dart b/libraries/flutter_inapp_purchase/example/lib/src/constants.dart index 16e75fdd..a0002793 100644 --- a/libraries/flutter_inapp_purchase/example/lib/src/constants.dart +++ b/libraries/flutter_inapp_purchase/example/lib/src/constants.dart @@ -3,7 +3,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; // Product IDs for testing in the example app class IapConstants { // IAPKit API Key for purchase verification - // Get your API key from https://iapkit.com + // Get your API key from https://kit.openiap.dev static String get iapkitApiKey => dotenv.env['IAPKIT_API_KEY'] ?? ''; // Consumable Product IDs diff --git a/libraries/kmp-iap/.env.example b/libraries/kmp-iap/.env.example index ec6aa10e..a52704fc 100644 --- a/libraries/kmp-iap/.env.example +++ b/libraries/kmp-iap/.env.example @@ -1,3 +1,3 @@ # IAPKit API Key for purchase verification -# Get your API key from https://iapkit.com +# Get your API key from https://kit.openiap.dev IAPKIT_API_KEY=your_iapkit_api_key_here diff --git a/libraries/kmp-iap/example/.env.example b/libraries/kmp-iap/example/.env.example index 2a220323..be95248c 100644 --- a/libraries/kmp-iap/example/.env.example +++ b/libraries/kmp-iap/example/.env.example @@ -1,3 +1,3 @@ # IAPKit Configuration -# Get your API key from https://iapkit.com +# Get your API key from https://kit.openiap.dev IAPKIT_API_KEY=your_iapkit_api_key_here diff --git a/libraries/kmp-iap/example/composeApp/src/commonMain/kotlin/dev/hyo/martie/config/AppConfig.kt b/libraries/kmp-iap/example/composeApp/src/commonMain/kotlin/dev/hyo/martie/config/AppConfig.kt index a8a03a2d..6c99154d 100644 --- a/libraries/kmp-iap/example/composeApp/src/commonMain/kotlin/dev/hyo/martie/config/AppConfig.kt +++ b/libraries/kmp-iap/example/composeApp/src/commonMain/kotlin/dev/hyo/martie/config/AppConfig.kt @@ -9,7 +9,7 @@ package dev.hyo.martie.config expect object AppConfig { /** * IAPKit API key for purchase verification. - * Get your API key from https://iapkit.com + * Get your API key from https://kit.openiap.dev */ val iapkitApiKey: String } diff --git a/libraries/kmp-iap/example/iosApp/Configuration/Secrets.xcconfig.example b/libraries/kmp-iap/example/iosApp/Configuration/Secrets.xcconfig.example index 8beb5532..7bfbf77b 100644 --- a/libraries/kmp-iap/example/iosApp/Configuration/Secrets.xcconfig.example +++ b/libraries/kmp-iap/example/iosApp/Configuration/Secrets.xcconfig.example @@ -2,5 +2,5 @@ // Copy this file to Secrets.xcconfig and add your API keys // IAPKit API Key for purchase verification -// Get your API key from https://iapkit.com +// Get your API key from https://kit.openiap.dev IAPKIT_API_KEY=your_iapkit_api_key_here diff --git a/libraries/react-native-iap/example-expo/.env.example b/libraries/react-native-iap/example-expo/.env.example index 79be8836..619c5fa5 100644 --- a/libraries/react-native-iap/example-expo/.env.example +++ b/libraries/react-native-iap/example-expo/.env.example @@ -1,3 +1,3 @@ # IAPKit Configuration -# Get your API key from https://iapkit.com +# Get your API key from https://kit.openiap.dev EXPO_PUBLIC_IAPKIT_API_KEY=your_iapkit_api_key_here diff --git a/libraries/react-native-iap/example-expo/app.config.ts b/libraries/react-native-iap/example-expo/app.config.ts index ada2f4f8..385c2066 100644 --- a/libraries/react-native-iap/example-expo/app.config.ts +++ b/libraries/react-native-iap/example-expo/app.config.ts @@ -6,7 +6,7 @@ export default ({config}: ConfigContext): ExpoConfig => { 'react-native-iap', { // IAPKit API key for purchase verification (optional) - // Get your API key from https://iapkit.com + // Get your API key from https://kit.openiap.dev iapkitApiKey: process.env.EXPO_PUBLIC_IAPKIT_API_KEY, // iOS Alternative Billing configuration (optional) diff --git a/libraries/react-native-iap/example/.env.example b/libraries/react-native-iap/example/.env.example index 2a220323..be95248c 100644 --- a/libraries/react-native-iap/example/.env.example +++ b/libraries/react-native-iap/example/.env.example @@ -1,3 +1,3 @@ # IAPKit Configuration -# Get your API key from https://iapkit.com +# Get your API key from https://kit.openiap.dev IAPKIT_API_KEY=your_iapkit_api_key_here diff --git a/libraries/react-native-iap/plugin/src/withIAP.ts b/libraries/react-native-iap/plugin/src/withIAP.ts index fc0d9b8c..8075bdb4 100644 --- a/libraries/react-native-iap/plugin/src/withIAP.ts +++ b/libraries/react-native-iap/plugin/src/withIAP.ts @@ -363,7 +363,7 @@ type IapPluginProps = { /** * IAPKit API key for purchase verification. * This key will be added to AndroidManifest.xml (as meta-data) and Info.plist. - * Get your API key from https://iapkit.com + * Get your API key from https://kit.openiap.dev */ iapkitApiKey?: string; }; diff --git a/packages/apple/Sources/OpenIapModule.swift b/packages/apple/Sources/OpenIapModule.swift index 6c60a193..7cd95b39 100644 --- a/packages/apple/Sources/OpenIapModule.swift +++ b/packages/apple/Sources/OpenIapModule.swift @@ -706,7 +706,7 @@ public final class OpenIapModule: NSObject, OpenIapModuleProtocol { // but it is never invoked from this module. private func verifyPurchaseWithIapkit(props: RequestVerifyPurchaseWithIapkitProps) async throws -> RequestVerifyPurchaseWithIapkitResult { // URL is a constant and cannot fail, so force unwrap is safe - let url = URL(string: "https://api.iapkit.com/v1/purchase/verify")! + let url = URL(string: "https://kit.openiap.dev/v1/purchase/verify")! // On Apple, only Apple verification is supported guard props.apple != nil else { diff --git a/packages/docs/src/components/Navigation.tsx b/packages/docs/src/components/Navigation.tsx index cd78234a..fd05f7e5 100644 --- a/packages/docs/src/components/Navigation.tsx +++ b/packages/docs/src/components/Navigation.tsx @@ -4,7 +4,7 @@ import { DarkModeToggle } from './DarkModeToggle'; import { Menu, X } from 'lucide-react'; import { FaGithub, FaSearch } from 'react-icons/fa'; import { openSearchModal } from '../lib/signals'; -import { LOGO_PATH } from '../lib/config'; +import { IAPKIT_URL, LOGO_PATH, trackIapKitClick } from '../lib/config'; function Navigation() { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); @@ -96,6 +96,17 @@ function Navigation() { + {/* IAPKit Link */} + + IAPKit + + {/* GitHub Link */} + +
  • + { + trackIapKitClick(); + closeMobileMenu(); + }} + > + IAPKit + +
  • diff --git a/packages/docs/src/lib/config.ts b/packages/docs/src/lib/config.ts index fccd93b0..a83ce9a0 100644 --- a/packages/docs/src/lib/config.ts +++ b/packages/docs/src/lib/config.ts @@ -15,7 +15,7 @@ export const LOGO_PATH = '/logo.webp'; // IAPKit Configuration // ============================================================================= -export const IAPKIT_URL = 'https://iapkit.com'; +export const IAPKIT_URL = 'https://kit.openiap.dev'; export const IAPKIT_AD_BANNER_URL = 'https://www.hyo.dev/api/ad-banner/cmjf0l27p0004249h2blztbct'; diff --git a/packages/docs/src/pages/docs/example.tsx b/packages/docs/src/pages/docs/example.tsx index 02cb8832..a0e301ad 100644 --- a/packages/docs/src/pages/docs/example.tsx +++ b/packages/docs/src/pages/docs/example.tsx @@ -221,7 +221,7 @@ xcodebuild -project Martie.xcodeproj \\ className="external-link" onClick={trackIapKitClick} > - iapkit.com + kit.openiap.dev {' '} and configure it in Info.plist:
                         className="external-link"
                         onClick={trackIapKitClick}
                       >
    -                    iapkit.com
    +                    kit.openiap.dev
                       {' '}
                       and configure it in local.properties:
                       
     verifyOnServer(ProductPurchase purchase) async {
             
           
     
    +      
    + + Verify Purchase with IAPKit + +

    + Don't want to implement App Store / Google Play verification + yourself?{' '} + + IAPKit + {' '} + is a hosted purchase verification service that validates App Store and + Google Play purchases for you. Use{' '} + verifyPurchaseWithProvider with the{' '} + 'iapkit' provider and pass the + platform-specific token (iOS JWS or Android purchase token) — no + store-verification code required. If your app has server-side accounts + or entitlements, keep the final grant decision on your backend and + call IAPKit from that trusted path; client-only use is convenient, but + not tamper-proof. +

    + +
    +

    + ℹ️ Get an API key: Sign up at{' '} + + kit.openiap.dev + {' '} + to obtain an IAPKIT_API_KEY. You can pass it directly, + or configure it once in your app (Expo extra, Info.plist, + AndroidManifest, etc.) so the SDK picks it up automatically. +

    +
    + + + {{ + typescript: ( + {`import { Platform } from 'react-native'; +import { verifyPurchaseWithProvider, type Purchase } from 'expo-iap'; + +const verifyWithIapkit = async (purchase: Purchase) => { + const result = await verifyPurchaseWithProvider({ + provider: 'iapkit', + iapkit: { + // apiKey is optional when configured via app config / Info.plist / AndroidManifest + apiKey: process.env.EXPO_PUBLIC_IAPKIT_API_KEY, + ...(Platform.OS === 'ios' + ? { apple: { jws: purchase.purchaseToken ?? '' } } + : { google: { purchaseToken: purchase.purchaseToken ?? '' } }), + }, + }); + + if (result.iapkit?.isValid) { + console.log('IAPKit verified:', result.iapkit.state); + return true; + } + + console.error('IAPKit verification failed'); + return false; +};`} + ), + swift: ( + {`import OpenIap + +func verifyWithIapkit(_ purchase: PurchaseIOS) async -> Bool { + let iapStore = OpenIapStore.shared + + do { + let result = try await iapStore.verifyPurchaseWithProvider( + VerifyPurchaseWithProviderProps( + provider: .iapkit, + iapkit: RequestVerifyPurchaseWithIapkitProps( + apiKey: Bundle.main.object(forInfoDictionaryKey: "IAPKitAPIKey") as? String, + apple: RequestVerifyPurchaseWithIapkitAppleProps(jws: purchase.purchaseToken ?? "") + ) + ) + ) + + if result.iapkit?.isValid == true { + print("IAPKit verified: \\(result.iapkit?.state.rawValue ?? "")") + return true + } + + print("IAPKit verification failed") + return false + } catch { + print("IAPKit verification error: \\(error.localizedDescription)") + return false + } +}`} + ), + kotlin: ( + {`import dev.hyo.openiap.OpenIapStore +import dev.hyo.openiap.models.* + +suspend fun verifyWithIapkit(purchase: PurchaseAndroid): Boolean { + return try { + val result = iapStore.verifyPurchaseWithProvider( + VerifyPurchaseWithProviderProps( + provider = PurchaseVerificationProvider.Iapkit, + iapkit = RequestVerifyPurchaseWithIapkitProps( + // apiKey is optional when configured via AndroidManifest meta-data + apiKey = BuildConfig.IAPKIT_API_KEY, + google = RequestVerifyPurchaseWithIapkitGoogleProps( + purchaseToken = purchase.purchaseToken.orEmpty() + ) + ) + ) + ) + + if (result.iapkit?.isValid == true) { + println("IAPKit verified: \${result.iapkit?.state}") + true + } else { + println("IAPKit verification failed") + false + } + } catch (e: Exception) { + println("IAPKit verification error: \${e.message}") + false + } +}`} + ), + kmp: ( + {`import io.github.hyochan.kmpiap.KmpIAP +import io.github.hyochan.kmpiap.* + +suspend fun verifyWithIapkit(purchase: PurchaseAndroid): Boolean { + return try { + val result = kmpIAP.verifyPurchaseWithProvider( + VerifyPurchaseWithProviderProps( + provider = PurchaseVerificationProvider.Iapkit, + iapkit = RequestVerifyPurchaseWithIapkitProps( + apiKey = AppConfig.iapkitApiKey, + google = RequestVerifyPurchaseWithIapkitGoogleProps( + purchaseToken = purchase.purchaseToken.orEmpty() + ) + ) + ) + ) + + if (result.iapkit?.isValid == true) { + println("IAPKit verified: \${result.iapkit?.state}") + true + } else { + println("IAPKit verification failed") + false + } + } catch (e: Exception) { + println("IAPKit verification error: \${e.message}") + false + } +}`} + ), + dart: ( + {`import 'dart:io'; +import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart'; + +Future verifyWithIapkit(ProductPurchase purchase) async { + final iap = FlutterInappPurchase.instance; + + try { + final result = await iap.verifyPurchaseWithProvider( + VerifyPurchaseWithProviderProps( + provider: PurchaseVerificationProvider.iapkit, + iapkit: RequestVerifyPurchaseWithIapkitProps( + apiKey: IapConstants.iapkitApiKey, + apple: Platform.isIOS + ? RequestVerifyPurchaseWithIapkitAppleProps( + jws: purchase.purchaseToken ?? '', + ) + : null, + google: Platform.isAndroid + ? RequestVerifyPurchaseWithIapkitGoogleProps( + purchaseToken: purchase.purchaseToken ?? '', + ) + : null, + ), + ), + ); + + if (result.iapkit?.isValid == true) { + print('IAPKit verified: \${result.iapkit?.state}'); + return true; + } + + print('IAPKit verification failed'); + return false; + } catch (e) { + print('IAPKit verification error: $e'); + return false; + } +}`} + ), + gdscript: ( + {`func verify_with_iapkit(purchase: Purchase) -> bool: + var props = VerifyPurchaseWithProviderProps.new() + props.provider = PurchaseVerificationProvider.IAPKIT + props.iapkit = RequestVerifyPurchaseWithIapkitProps.new() + props.iapkit.api_key = AppConfig.iapkit_api_key + + if OS.get_name() == "iOS": + props.iapkit.apple = RequestVerifyPurchaseWithIapkitAppleProps.new() + props.iapkit.apple.jws = purchase.purchase_token + else: + props.iapkit.google = RequestVerifyPurchaseWithIapkitGoogleProps.new() + props.iapkit.google.purchase_token = purchase.purchase_token + + var result = await iap.verify_purchase_with_provider(props) + + if result.iapkit and result.iapkit.is_valid: + print("IAPKit verified: %s" % result.iapkit.state) + return true + + print("IAPKit verification failed") + return false`} + ), + }} + + +
    +

    + ℹ️ Endpoint: Requests are sent to{' '} + https://kit.openiap.dev/v1/purchase/verify with{' '} + Authorization: Bearer <apiKey>. See the{' '} + + PurchaseVerificationProvider + {' '} + type reference for the full response shape. +

    +
    +
    +
    Finish Transaction diff --git a/packages/docs/src/pages/docs/lifecycle/subscription.tsx b/packages/docs/src/pages/docs/lifecycle/subscription.tsx index 745d8aef..95f2840b 100644 --- a/packages/docs/src/pages/docs/lifecycle/subscription.tsx +++ b/packages/docs/src/pages/docs/lifecycle/subscription.tsx @@ -141,7 +141,7 @@ function Subscription() {

    Recommendation: Use{' '} diff --git a/packages/docs/src/pages/docs/types/verification.tsx b/packages/docs/src/pages/docs/types/verification.tsx index 36fb2032..c49a96cb 100644 --- a/packages/docs/src/pages/docs/types/verification.tsx +++ b/packages/docs/src/pages/docs/types/verification.tsx @@ -646,7 +646,7 @@ function TypesVerification() { diff --git a/packages/docs/src/pages/docs/updates/releases.tsx b/packages/docs/src/pages/docs/updates/releases.tsx index 3d4327b8..58d82fb9 100644 --- a/packages/docs/src/pages/docs/updates/releases.tsx +++ b/packages/docs/src/pages/docs/updates/releases.tsx @@ -25,6 +25,67 @@ function Releases() { useScrollToHash(); const allNotes: Note[] = [ + // April 19, 2026 — IAPKit verification host migration + { + id: 'releases-2026-04-19', + date: new Date('2026-04-19'), + element: ( +

    + ), + }, + // April 17, 2026 — Advanced Commerce & Transaction History { id: 'releases-2026-04-17', diff --git a/packages/docs/src/styles/navigation.css b/packages/docs/src/styles/navigation.css index 5a2fed55..f9cad7cb 100644 --- a/packages/docs/src/styles/navigation.css +++ b/packages/docs/src/styles/navigation.css @@ -87,6 +87,36 @@ background: var(--bg-secondary); } +/* IAPKit Link */ +.iapkit-link { + display: inline-flex; + align-items: center; + justify-content: center; + height: 32px; + padding: 0 0.75rem; + border-radius: 6px; + font-size: 0.85rem; + font-weight: 600; + color: #000000; + background: linear-gradient( + 135deg, + var(--primary-color), + var(--accent-color) + ); + text-decoration: none; + transition: + transform 0.15s, + box-shadow 0.15s, + opacity 0.15s; + white-space: nowrap; +} + +.iapkit-link:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + opacity: 0.95; +} + /* Mobile menu button */ .mobile-menu-button { display: none; diff --git a/packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt b/packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt index 118b798c..cf829a81 100644 --- a/packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt +++ b/packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt @@ -18,7 +18,7 @@ import java.net.HttpURLConnection import java.net.URL import java.net.URLEncoder -private const val DEFAULT_IAPKIT_ENDPOINT = "https://api.iapkit.com/v1/purchase/verify" +private const val DEFAULT_IAPKIT_ENDPOINT = "https://kit.openiap.dev/v1/purchase/verify" private val gson = Gson() private fun openConnection(url: String): HttpURLConnection {