@billkit-eu/react 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,7 +9,15 @@ This package is a thin binding layer over `@billkit-eu/js`, which it takes as a
9
9
  peer dependency. Behaviour changes usually land there and are recorded in that
10
10
  package's changelog.
11
11
 
12
- ## [Unreleased]
12
+ ## [0.2.1]
13
+
14
+ ### Changed
15
+ - Documentation only. API keys are now `bk_live_…` / `bk_test_…` and webhook
16
+ signing secrets `bkwhsec_…`; every example here used the previous
17
+ Stripe-shaped `sk_`/`whsec_` spelling. No code in this package changed: it
18
+ never parsed the prefix, it forwards the key as a bearer token.
19
+
20
+ ## [0.2.0]
13
21
 
14
22
  ## [0.1.0]
15
23
 
package/README.md CHANGED
@@ -144,7 +144,7 @@ Never logged: the `clientSecret`, message payloads, or full redirect URLs. Only
144
144
 
145
145
  The shapes line up, with one difference worth calling out.
146
146
 
147
- There is **no `publishableKey`**. BillKit has no publishable-key concept; the API only mints secret keys (`sk_live_...` / `sk_test_...`), which must never reach a browser. Elements authenticate with the ephemeral `client_secret` your server gets from `POST /v1/checkout/sessions` with `ui_mode: "embedded"` — `<BillKitProvider>` takes no credential at all.
147
+ There is **no `publishableKey`**. BillKit has no publishable-key concept; the API only mints secret keys (`bk_live_...` / `bk_test_...`), which must never reach a browser. Elements authenticate with the ephemeral `client_secret` your server gets from `POST /v1/checkout/sessions` with `ui_mode: "embedded"` — `<BillKitProvider>` takes no credential at all.
148
148
 
149
149
  See the [migration guide](https://docs.billkit.eu/migration/elements/) for the full comparison.
150
150
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/BillKitProvider.tsx","../src/useElement.ts","../src/CheckoutElement.tsx","../src/PaymentMethodElement.tsx"],"names":["createContext","useMemo","jsx","useContext","useState","useRef","useEffect","forwardRef","CheckoutElement","mountCheckoutElement","useImperativeHandle","PaymentMethodElement","mountPaymentMethodElement"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { BillKitElementLogger } from \"@billkit-eu/js\";\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\n/**\n * Config shared by every BillKit element on the page. Mirrors\n * `@stripe/react-stripe-js`'s `<Elements>` provider — minus the key:\n * BillKit has no publishable-key concept. The API only mints secret keys\n * (`sk_live_...` / `sk_test_...`), which must never reach a browser;\n * elements authenticate with the ephemeral `client_secret` your server\n * gets from `POST /v1/checkout/sessions` with `ui_mode: \"embedded\"`, and\n * that secret already names the tenant, the mode, and the session. So the\n * provider carries origin overrides and a logger, nothing credential-shaped.\n */\nexport interface BillKitContextValue {\n /** Origin the element iframe is served from. Defaults to js.billkit.eu. */\n iframeOrigin?: string;\n /** API origin the iframe calls. Defaults to api.billkit.eu. */\n apiBase?: string;\n /**\n * Where every element beneath this provider sends its lifecycle\n * diagnostics: iframe boot, dropped `postMessage`s, refused\n * redirects, load timeouts. Omitted (the default) means silence: the\n * elements never write to `console` on their own.\n *\n * `console` works as-is. An individual element can override this with\n * its own `logger` prop. The `clientSecret` is never passed to it.\n */\n logger?: BillKitElementLogger;\n}\n\nconst BillKitContext = createContext<BillKitContextValue | null>(null);\n\nexport interface BillKitProviderProps extends BillKitContextValue {\n children: ReactNode;\n}\n\n/**\n * Wrap the part of your tree that renders BillKit elements.\n *\n * No credential is needed here; the element authenticates with the\n * ephemeral `client_secret` your server minted for the session:\n *\n * ```tsx\n * <BillKitProvider>\n * <CheckoutElement clientSecret={clientSecret} onSuccess={done} />\n * </BillKitProvider>\n * ```\n */\nexport function BillKitProvider({\n iframeOrigin,\n apiBase,\n logger,\n children,\n}: BillKitProviderProps): JSX.Element {\n const value = useMemo<BillKitContextValue>(\n () => ({ iframeOrigin, apiBase, logger }),\n [iframeOrigin, apiBase, logger],\n );\n return <BillKitContext.Provider value={value}>{children}</BillKitContext.Provider>;\n}\n\n/**\n * Read the nearest {@link BillKitProvider}. Throws a clear error when an\n * element is rendered outside a provider, the most common integration\n * mistake.\n */\nexport function useBillKit(): BillKitContextValue {\n const ctx = useContext(BillKitContext);\n if (ctx === null) {\n throw new Error(\n \"BillKit: a <CheckoutElement/> must be rendered inside a <BillKitProvider>.\",\n );\n }\n return ctx;\n}\n","import type {\n BaseElementOptions,\n BillKitElementError,\n BillKitElementHandle,\n BillKitElementLogger,\n BillKitThemeTokens,\n ChangeEvent,\n SuccessEvent,\n} from \"@billkit-eu/js\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useBillKit } from \"./BillKitProvider\";\n\n/** Callback + presentational props common to the React element components. */\nexport interface ReactElementProps {\n /** Ephemeral checkout `client_secret` (`<sessionId>_secret_...`). */\n clientSecret: string;\n theme?: BillKitThemeTokens;\n locale?: string;\n /** Load-watchdog timeout (ms) before `onError({code:\"load_timeout\"})`. */\n loadTimeoutMs?: number;\n /**\n * Overrides the provider's `logger` for this element only. Omitted,\n * the provider's is used; with neither, the element is silent.\n */\n logger?: BillKitElementLogger;\n /** Applied to the container `<div>` the iframe mounts into. */\n className?: string;\n style?: React.CSSProperties;\n onReady?: () => void;\n onChange?: (event: ChangeEvent) => void;\n onSuccess?: (event: SuccessEvent) => void;\n onError?: (error: BillKitElementError) => void;\n onRedirect?: (url: string) => boolean | void;\n}\n\ntype MountFn = (\n target: HTMLElement,\n options: BaseElementOptions,\n) => BillKitElementHandle;\n\n/**\n * SSR-safe mount hook shared by `<CheckoutElement/>` and\n * `<PaymentMethodElement/>`.\n *\n * - Returns `isClient = false` on the server and on the first client\n * render, so the component renders `null` and hydration matches. The\n * `useEffect` then flips it true and the real mount happens, with no\n * hydration mismatch, no `window` access during render.\n * - Callbacks are read through a ref, so a parent passing fresh closures\n * every render never forces a costly iframe remount. Only the identity\n * inputs (secret, origins) remount; theme changes hot-update in place.\n */\nexport function useElement(\n mount: MountFn,\n // `customerId` is not part of the public `ReactElementProps` (only the\n // payment-method element has one), but the hook still has to *see* it:\n // it is a remount input, and leaving it out of the dependency list\n // meant switching customers kept the previous customer's wallet — and\n // its \"set default\" / \"remove\" actions — on screen.\n props: ReactElementProps & { customerId?: string },\n): {\n isClient: boolean;\n containerRef: React.RefObject<HTMLDivElement>;\n handleRef: React.RefObject<BillKitElementHandle | null>;\n} {\n const { iframeOrigin, apiBase, logger: providerLogger } = useBillKit();\n const [isClient, setIsClient] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<BillKitElementHandle | null>(null);\n\n // Latest callbacks, read at event time and decoupled from remount inputs.\n const callbacks = useRef(props);\n callbacks.current = props;\n\n // Same trick for the logger: resolve it at call time through a ref so\n // an inline `logger={{...}}` (or switching one on mid-session) never\n // tears down and rebuilds the payment iframe. The element always\n // receives this one stable object; it forwards to whatever is current,\n // or drops the line when neither prop nor provider supplies one.\n const activeLogger = useRef<BillKitElementLogger | undefined>(undefined);\n activeLogger.current = props.logger ?? providerLogger;\n const stableLogger = useRef<BillKitElementLogger>({\n debug: (message, context) => activeLogger.current?.debug(message, context),\n warn: (message, context) => activeLogger.current?.warn(message, context),\n }).current;\n\n useEffect(() => setIsClient(true), []);\n\n const themeKey = props.theme ? JSON.stringify(props.theme) : \"\";\n\n useEffect(() => {\n if (!isClient || containerRef.current === null) return;\n const options: BaseElementOptions = {\n clientSecret: props.clientSecret,\n ...(props.theme ? { theme: props.theme } : {}),\n ...(props.locale ? { locale: props.locale } : {}),\n ...(iframeOrigin ? { iframeOrigin } : {}),\n ...(apiBase ? { apiBase } : {}),\n ...(props.loadTimeoutMs !== undefined ? { loadTimeoutMs: props.loadTimeoutMs } : {}),\n logger: stableLogger,\n onReady: () => callbacks.current.onReady?.(),\n onChange: (e) => callbacks.current.onChange?.(e),\n onSuccess: (e) => callbacks.current.onSuccess?.(e),\n onError: (e) => callbacks.current.onError?.(e),\n onRedirect: (url) => callbacks.current.onRedirect?.(url),\n };\n const handle = mount(containerRef.current, options);\n handleRef.current = handle;\n return () => {\n handle.destroy();\n handleRef.current = null;\n };\n // Remount only on identity inputs (secret / customer / origins);\n // callbacks are read through a ref and theme is hot-applied in the\n // effect below, so neither belongs in this dependency list.\n }, [isClient, props.clientSecret, props.customerId, props.locale, iframeOrigin, apiBase]);\n\n // Hot-apply theme changes without tearing down the iframe.\n useEffect(() => {\n if (themeKey && handleRef.current && props.theme) {\n handleRef.current.updateTheme(props.theme);\n }\n }, [themeKey]);\n\n return { isClient, containerRef, handleRef };\n}\n\n/**\n * The imperative handle `<CheckoutElement/>` and\n * `<PaymentMethodElement/>` expose through `ref`.\n *\n * Everything else about these components is declarative, but submitting\n * is genuinely an *event*, not a state: a merchant's own pay button\n * lives outside the iframe (that is the point of `onChange.complete`),\n * and it has to be able to say \"go\" exactly once. A `submit` prop would\n * have to be a toggling boolean, which is the classic React smell for an\n * action modelled as state, so this follows the\n * `useImperativeHandle` path that `<input>`'s `focus()` set.\n *\n * Calls made before the iframe has mounted are no-ops rather than\n * throwing — on the server, and on the first client render, there is no\n * element yet.\n */\nexport interface ThemeableElementRef {\n /** Push new theme tokens in without remounting. */\n updateTheme(theme: BillKitThemeTokens): void;\n}\n\nexport interface BillKitElementRef extends ThemeableElementRef {\n /** Submit the form from your own pay button. */\n submit(): void;\n}\n\n/** Build the ref `<CheckoutElement/>` exposes. */\nexport function checkoutElementRef(\n handleRef: React.RefObject<BillKitElementHandle | null>,\n): BillKitElementRef {\n return {\n submit: () => handleRef.current?.submit(),\n updateTheme: (theme) => handleRef.current?.updateTheme(theme),\n };\n}\n\n/**\n * Build the ref `<PaymentMethodElement/>` exposes.\n *\n * Deliberately no `submit`: the wallet element drops `billkit:submit` on\n * the floor (there is no form to submit — its actions are per-row \"set\n * default\" / \"remove\" buttons inside the iframe). Exposing a method that\n * silently does nothing would be worse than not having one.\n */\nexport function themeableElementRef(\n handleRef: React.RefObject<BillKitElementHandle | null>,\n): ThemeableElementRef {\n return {\n updateTheme: (theme) => handleRef.current?.updateTheme(theme),\n };\n}\n","import { mountCheckoutElement } from \"@billkit-eu/js\";\nimport { forwardRef, useImperativeHandle } from \"react\";\nimport {\n type BillKitElementRef,\n checkoutElementRef,\n type ReactElementProps,\n useElement,\n} from \"./useElement\";\n\nexport type CheckoutElementProps = ReactElementProps;\n\n/**\n * Embedded checkout, as a React component. SSR-safe: renders `null` on the\n * server and the first client render, then mounts the js.billkit.eu iframe\n * after hydration.\n *\n * ```tsx\n * <BillKitProvider>\n * <CheckoutElement\n * clientSecret={clientSecret}\n * theme={{ colorPrimary: \"#6d28d9\", borderRadius: \"10px\" }}\n * onSuccess={({ sessionId }) => router.push(`/thanks?cs=${sessionId}`)}\n * />\n * </BillKitProvider>\n * ```\n *\n * Driving your own pay button: take a `ref` and call `submit()`. Gate the\n * button on `onChange`'s `complete`, and re-enable it from `onError` —\n * a declined card fires `onError({ code: \"payment_declined\" })` and the\n * element shows its own retry panel.\n *\n * ```tsx\n * const element = useRef<BillKitElementRef>(null);\n * const [ready, setReady] = useState(false);\n * <>\n * <CheckoutElement\n * ref={element}\n * clientSecret={clientSecret}\n * onChange={({ complete }) => setReady(complete)}\n * onError={() => setSubmitting(false)}\n * />\n * <button disabled={!ready} onClick={() => element.current?.submit()}>Pay</button>\n * </>\n * ```\n */\nexport const CheckoutElement = forwardRef<BillKitElementRef, CheckoutElementProps>(\n function CheckoutElement(props, ref) {\n const { isClient, containerRef, handleRef } = useElement(mountCheckoutElement, props);\n // No dependency list: `handleRef` is a stable ref object, and the\n // closures read `.current` at call time, so the imperative handle\n // never goes stale across remounts.\n useImperativeHandle(ref, () => checkoutElementRef(handleRef), []);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n },\n);\n","import { type BaseElementOptions, type BillKitElementHandle, mountPaymentMethodElement } from \"@billkit-eu/js\";\nimport { forwardRef, useImperativeHandle } from \"react\";\nimport {\n type ReactElementProps,\n type ThemeableElementRef,\n themeableElementRef,\n useElement,\n} from \"./useElement\";\n\nexport interface PaymentMethodElementProps extends ReactElementProps {\n /** The customer whose saved payment methods to render + manage. */\n customerId: string;\n}\n\n/**\n * The customer's saved payment methods (\"Visa •••• 4242 · Update\"), as a\n * React component. Same SSR-safe mounting as {@link CheckoutElement}.\n *\n * Changing `customerId` remounts the element, so the wallet on screen\n * always belongs to the customer named in the props.\n *\n * The `ref` exposes `updateTheme()` only — this element has no form to\n * submit; its actions are per-row buttons inside the iframe.\n */\nexport const PaymentMethodElement = forwardRef<ThemeableElementRef, PaymentMethodElementProps>(\n function PaymentMethodElement(props, ref) {\n const { customerId } = props;\n const mount = (target: HTMLElement, options: BaseElementOptions): BillKitElementHandle =>\n mountPaymentMethodElement(target, { ...options, customerId });\n const { isClient, containerRef, handleRef } = useElement(mount, props);\n useImperativeHandle(ref, () => themeableElementRef(handleRef), []);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n },\n);\n"]}
1
+ {"version":3,"sources":["../src/BillKitProvider.tsx","../src/useElement.ts","../src/CheckoutElement.tsx","../src/PaymentMethodElement.tsx"],"names":["createContext","useMemo","jsx","useContext","useState","useRef","useEffect","forwardRef","CheckoutElement","mountCheckoutElement","useImperativeHandle","PaymentMethodElement","mountPaymentMethodElement"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { BillKitElementLogger } from \"@billkit-eu/js\";\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\n/**\n * Config shared by every BillKit element on the page. Mirrors\n * `@stripe/react-stripe-js`'s `<Elements>` provider — minus the key:\n * BillKit has no publishable-key concept. The API only mints secret keys\n * (`bk_live_...` / `bk_test_...`), which must never reach a browser;\n * elements authenticate with the ephemeral `client_secret` your server\n * gets from `POST /v1/checkout/sessions` with `ui_mode: \"embedded\"`, and\n * that secret already names the tenant, the mode, and the session. So the\n * provider carries origin overrides and a logger, nothing credential-shaped.\n */\nexport interface BillKitContextValue {\n /** Origin the element iframe is served from. Defaults to js.billkit.eu. */\n iframeOrigin?: string;\n /** API origin the iframe calls. Defaults to api.billkit.eu. */\n apiBase?: string;\n /**\n * Where every element beneath this provider sends its lifecycle\n * diagnostics: iframe boot, dropped `postMessage`s, refused\n * redirects, load timeouts. Omitted (the default) means silence: the\n * elements never write to `console` on their own.\n *\n * `console` works as-is. An individual element can override this with\n * its own `logger` prop. The `clientSecret` is never passed to it.\n */\n logger?: BillKitElementLogger;\n}\n\nconst BillKitContext = createContext<BillKitContextValue | null>(null);\n\nexport interface BillKitProviderProps extends BillKitContextValue {\n children: ReactNode;\n}\n\n/**\n * Wrap the part of your tree that renders BillKit elements.\n *\n * No credential is needed here; the element authenticates with the\n * ephemeral `client_secret` your server minted for the session:\n *\n * ```tsx\n * <BillKitProvider>\n * <CheckoutElement clientSecret={clientSecret} onSuccess={done} />\n * </BillKitProvider>\n * ```\n */\nexport function BillKitProvider({\n iframeOrigin,\n apiBase,\n logger,\n children,\n}: BillKitProviderProps): JSX.Element {\n const value = useMemo<BillKitContextValue>(\n () => ({ iframeOrigin, apiBase, logger }),\n [iframeOrigin, apiBase, logger],\n );\n return <BillKitContext.Provider value={value}>{children}</BillKitContext.Provider>;\n}\n\n/**\n * Read the nearest {@link BillKitProvider}. Throws a clear error when an\n * element is rendered outside a provider, the most common integration\n * mistake.\n */\nexport function useBillKit(): BillKitContextValue {\n const ctx = useContext(BillKitContext);\n if (ctx === null) {\n throw new Error(\n \"BillKit: a <CheckoutElement/> must be rendered inside a <BillKitProvider>.\",\n );\n }\n return ctx;\n}\n","import type {\n BaseElementOptions,\n BillKitElementError,\n BillKitElementHandle,\n BillKitElementLogger,\n BillKitThemeTokens,\n ChangeEvent,\n SuccessEvent,\n} from \"@billkit-eu/js\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useBillKit } from \"./BillKitProvider\";\n\n/** Callback + presentational props common to the React element components. */\nexport interface ReactElementProps {\n /** Ephemeral checkout `client_secret` (`<sessionId>_secret_...`). */\n clientSecret: string;\n theme?: BillKitThemeTokens;\n locale?: string;\n /** Load-watchdog timeout (ms) before `onError({code:\"load_timeout\"})`. */\n loadTimeoutMs?: number;\n /**\n * Overrides the provider's `logger` for this element only. Omitted,\n * the provider's is used; with neither, the element is silent.\n */\n logger?: BillKitElementLogger;\n /** Applied to the container `<div>` the iframe mounts into. */\n className?: string;\n style?: React.CSSProperties;\n onReady?: () => void;\n onChange?: (event: ChangeEvent) => void;\n onSuccess?: (event: SuccessEvent) => void;\n onError?: (error: BillKitElementError) => void;\n onRedirect?: (url: string) => boolean | void;\n}\n\ntype MountFn = (\n target: HTMLElement,\n options: BaseElementOptions,\n) => BillKitElementHandle;\n\n/**\n * SSR-safe mount hook shared by `<CheckoutElement/>` and\n * `<PaymentMethodElement/>`.\n *\n * - Returns `isClient = false` on the server and on the first client\n * render, so the component renders `null` and hydration matches. The\n * `useEffect` then flips it true and the real mount happens, with no\n * hydration mismatch, no `window` access during render.\n * - Callbacks are read through a ref, so a parent passing fresh closures\n * every render never forces a costly iframe remount. Only the identity\n * inputs (secret, origins) remount; theme changes hot-update in place.\n */\nexport function useElement(\n mount: MountFn,\n // `customerId` is not part of the public `ReactElementProps` (only the\n // payment-method element has one), but the hook still has to *see* it:\n // it is a remount input, and leaving it out of the dependency list\n // meant switching customers kept the previous customer's wallet — and\n // its \"set default\" / \"remove\" actions — on screen.\n props: ReactElementProps & { customerId?: string },\n): {\n isClient: boolean;\n containerRef: React.RefObject<HTMLDivElement>;\n handleRef: React.RefObject<BillKitElementHandle | null>;\n} {\n const { iframeOrigin, apiBase, logger: providerLogger } = useBillKit();\n const [isClient, setIsClient] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<BillKitElementHandle | null>(null);\n\n // Latest callbacks, read at event time and decoupled from remount inputs.\n const callbacks = useRef(props);\n callbacks.current = props;\n\n // Same trick for the logger: resolve it at call time through a ref so\n // an inline `logger={{...}}` (or switching one on mid-session) never\n // tears down and rebuilds the payment iframe. The element always\n // receives this one stable object; it forwards to whatever is current,\n // or drops the line when neither prop nor provider supplies one.\n const activeLogger = useRef<BillKitElementLogger | undefined>(undefined);\n activeLogger.current = props.logger ?? providerLogger;\n const stableLogger = useRef<BillKitElementLogger>({\n debug: (message, context) => activeLogger.current?.debug(message, context),\n warn: (message, context) => activeLogger.current?.warn(message, context),\n }).current;\n\n useEffect(() => setIsClient(true), []);\n\n const themeKey = props.theme ? JSON.stringify(props.theme) : \"\";\n\n useEffect(() => {\n if (!isClient || containerRef.current === null) return;\n const options: BaseElementOptions = {\n clientSecret: props.clientSecret,\n ...(props.theme ? { theme: props.theme } : {}),\n ...(props.locale ? { locale: props.locale } : {}),\n ...(iframeOrigin ? { iframeOrigin } : {}),\n ...(apiBase ? { apiBase } : {}),\n ...(props.loadTimeoutMs !== undefined ? { loadTimeoutMs: props.loadTimeoutMs } : {}),\n logger: stableLogger,\n onReady: () => callbacks.current.onReady?.(),\n onChange: (e) => callbacks.current.onChange?.(e),\n onSuccess: (e) => callbacks.current.onSuccess?.(e),\n onError: (e) => callbacks.current.onError?.(e),\n onRedirect: (url) => callbacks.current.onRedirect?.(url),\n };\n const handle = mount(containerRef.current, options);\n handleRef.current = handle;\n return () => {\n handle.destroy();\n handleRef.current = null;\n };\n // Remount only on identity inputs (secret / customer / origins);\n // callbacks are read through a ref and theme is hot-applied in the\n // effect below, so neither belongs in this dependency list.\n }, [isClient, props.clientSecret, props.customerId, props.locale, iframeOrigin, apiBase]);\n\n // Hot-apply theme changes without tearing down the iframe.\n useEffect(() => {\n if (themeKey && handleRef.current && props.theme) {\n handleRef.current.updateTheme(props.theme);\n }\n }, [themeKey]);\n\n return { isClient, containerRef, handleRef };\n}\n\n/**\n * The imperative handle `<CheckoutElement/>` and\n * `<PaymentMethodElement/>` expose through `ref`.\n *\n * Everything else about these components is declarative, but submitting\n * is genuinely an *event*, not a state: a merchant's own pay button\n * lives outside the iframe (that is the point of `onChange.complete`),\n * and it has to be able to say \"go\" exactly once. A `submit` prop would\n * have to be a toggling boolean, which is the classic React smell for an\n * action modelled as state, so this follows the\n * `useImperativeHandle` path that `<input>`'s `focus()` set.\n *\n * Calls made before the iframe has mounted are no-ops rather than\n * throwing — on the server, and on the first client render, there is no\n * element yet.\n */\nexport interface ThemeableElementRef {\n /** Push new theme tokens in without remounting. */\n updateTheme(theme: BillKitThemeTokens): void;\n}\n\nexport interface BillKitElementRef extends ThemeableElementRef {\n /** Submit the form from your own pay button. */\n submit(): void;\n}\n\n/** Build the ref `<CheckoutElement/>` exposes. */\nexport function checkoutElementRef(\n handleRef: React.RefObject<BillKitElementHandle | null>,\n): BillKitElementRef {\n return {\n submit: () => handleRef.current?.submit(),\n updateTheme: (theme) => handleRef.current?.updateTheme(theme),\n };\n}\n\n/**\n * Build the ref `<PaymentMethodElement/>` exposes.\n *\n * Deliberately no `submit`: the wallet element drops `billkit:submit` on\n * the floor (there is no form to submit — its actions are per-row \"set\n * default\" / \"remove\" buttons inside the iframe). Exposing a method that\n * silently does nothing would be worse than not having one.\n */\nexport function themeableElementRef(\n handleRef: React.RefObject<BillKitElementHandle | null>,\n): ThemeableElementRef {\n return {\n updateTheme: (theme) => handleRef.current?.updateTheme(theme),\n };\n}\n","import { mountCheckoutElement } from \"@billkit-eu/js\";\nimport { forwardRef, useImperativeHandle } from \"react\";\nimport {\n type BillKitElementRef,\n checkoutElementRef,\n type ReactElementProps,\n useElement,\n} from \"./useElement\";\n\nexport type CheckoutElementProps = ReactElementProps;\n\n/**\n * Embedded checkout, as a React component. SSR-safe: renders `null` on the\n * server and the first client render, then mounts the js.billkit.eu iframe\n * after hydration.\n *\n * ```tsx\n * <BillKitProvider>\n * <CheckoutElement\n * clientSecret={clientSecret}\n * theme={{ colorPrimary: \"#6d28d9\", borderRadius: \"10px\" }}\n * onSuccess={({ sessionId }) => router.push(`/thanks?cs=${sessionId}`)}\n * />\n * </BillKitProvider>\n * ```\n *\n * Driving your own pay button: take a `ref` and call `submit()`. Gate the\n * button on `onChange`'s `complete`, and re-enable it from `onError` —\n * a declined card fires `onError({ code: \"payment_declined\" })` and the\n * element shows its own retry panel.\n *\n * ```tsx\n * const element = useRef<BillKitElementRef>(null);\n * const [ready, setReady] = useState(false);\n * <>\n * <CheckoutElement\n * ref={element}\n * clientSecret={clientSecret}\n * onChange={({ complete }) => setReady(complete)}\n * onError={() => setSubmitting(false)}\n * />\n * <button disabled={!ready} onClick={() => element.current?.submit()}>Pay</button>\n * </>\n * ```\n */\nexport const CheckoutElement = forwardRef<BillKitElementRef, CheckoutElementProps>(\n function CheckoutElement(props, ref) {\n const { isClient, containerRef, handleRef } = useElement(mountCheckoutElement, props);\n // No dependency list: `handleRef` is a stable ref object, and the\n // closures read `.current` at call time, so the imperative handle\n // never goes stale across remounts.\n useImperativeHandle(ref, () => checkoutElementRef(handleRef), []);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n },\n);\n","import { type BaseElementOptions, type BillKitElementHandle, mountPaymentMethodElement } from \"@billkit-eu/js\";\nimport { forwardRef, useImperativeHandle } from \"react\";\nimport {\n type ReactElementProps,\n type ThemeableElementRef,\n themeableElementRef,\n useElement,\n} from \"./useElement\";\n\nexport interface PaymentMethodElementProps extends ReactElementProps {\n /** The customer whose saved payment methods to render + manage. */\n customerId: string;\n}\n\n/**\n * The customer's saved payment methods (\"Visa •••• 4242 · Update\"), as a\n * React component. Same SSR-safe mounting as {@link CheckoutElement}.\n *\n * Changing `customerId` remounts the element, so the wallet on screen\n * always belongs to the customer named in the props.\n *\n * The `ref` exposes `updateTheme()` only — this element has no form to\n * submit; its actions are per-row buttons inside the iframe.\n */\nexport const PaymentMethodElement = forwardRef<ThemeableElementRef, PaymentMethodElementProps>(\n function PaymentMethodElement(props, ref) {\n const { customerId } = props;\n const mount = (target: HTMLElement, options: BaseElementOptions): BillKitElementHandle =>\n mountPaymentMethodElement(target, { ...options, customerId });\n const { isClient, containerRef, handleRef } = useElement(mount, props);\n useImperativeHandle(ref, () => themeableElementRef(handleRef), []);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n },\n);\n"]}
package/dist/index.d.cts CHANGED
@@ -7,7 +7,7 @@ import { ReactNode } from 'react';
7
7
  * Config shared by every BillKit element on the page. Mirrors
8
8
  * `@stripe/react-stripe-js`'s `<Elements>` provider — minus the key:
9
9
  * BillKit has no publishable-key concept. The API only mints secret keys
10
- * (`sk_live_...` / `sk_test_...`), which must never reach a browser;
10
+ * (`bk_live_...` / `bk_test_...`), which must never reach a browser;
11
11
  * elements authenticate with the ephemeral `client_secret` your server
12
12
  * gets from `POST /v1/checkout/sessions` with `ui_mode: "embedded"`, and
13
13
  * that secret already names the tenant, the mode, and the session. So the
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ import { ReactNode } from 'react';
7
7
  * Config shared by every BillKit element on the page. Mirrors
8
8
  * `@stripe/react-stripe-js`'s `<Elements>` provider — minus the key:
9
9
  * BillKit has no publishable-key concept. The API only mints secret keys
10
- * (`sk_live_...` / `sk_test_...`), which must never reach a browser;
10
+ * (`bk_live_...` / `bk_test_...`), which must never reach a browser;
11
11
  * elements authenticate with the ephemeral `client_secret` your server
12
12
  * gets from `POST /v1/checkout/sessions` with `ui_mode: "embedded"`, and
13
13
  * that secret already names the tenant, the mode, and the session. So the
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/BillKitProvider.tsx","../src/useElement.ts","../src/CheckoutElement.tsx","../src/PaymentMethodElement.tsx"],"names":["CheckoutElement","jsx","forwardRef","PaymentMethodElement","useImperativeHandle"],"mappings":"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","file":"index.js","sourcesContent":["import type { BillKitElementLogger } from \"@billkit-eu/js\";\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\n/**\n * Config shared by every BillKit element on the page. Mirrors\n * `@stripe/react-stripe-js`'s `<Elements>` provider — minus the key:\n * BillKit has no publishable-key concept. The API only mints secret keys\n * (`sk_live_...` / `sk_test_...`), which must never reach a browser;\n * elements authenticate with the ephemeral `client_secret` your server\n * gets from `POST /v1/checkout/sessions` with `ui_mode: \"embedded\"`, and\n * that secret already names the tenant, the mode, and the session. So the\n * provider carries origin overrides and a logger, nothing credential-shaped.\n */\nexport interface BillKitContextValue {\n /** Origin the element iframe is served from. Defaults to js.billkit.eu. */\n iframeOrigin?: string;\n /** API origin the iframe calls. Defaults to api.billkit.eu. */\n apiBase?: string;\n /**\n * Where every element beneath this provider sends its lifecycle\n * diagnostics: iframe boot, dropped `postMessage`s, refused\n * redirects, load timeouts. Omitted (the default) means silence: the\n * elements never write to `console` on their own.\n *\n * `console` works as-is. An individual element can override this with\n * its own `logger` prop. The `clientSecret` is never passed to it.\n */\n logger?: BillKitElementLogger;\n}\n\nconst BillKitContext = createContext<BillKitContextValue | null>(null);\n\nexport interface BillKitProviderProps extends BillKitContextValue {\n children: ReactNode;\n}\n\n/**\n * Wrap the part of your tree that renders BillKit elements.\n *\n * No credential is needed here; the element authenticates with the\n * ephemeral `client_secret` your server minted for the session:\n *\n * ```tsx\n * <BillKitProvider>\n * <CheckoutElement clientSecret={clientSecret} onSuccess={done} />\n * </BillKitProvider>\n * ```\n */\nexport function BillKitProvider({\n iframeOrigin,\n apiBase,\n logger,\n children,\n}: BillKitProviderProps): JSX.Element {\n const value = useMemo<BillKitContextValue>(\n () => ({ iframeOrigin, apiBase, logger }),\n [iframeOrigin, apiBase, logger],\n );\n return <BillKitContext.Provider value={value}>{children}</BillKitContext.Provider>;\n}\n\n/**\n * Read the nearest {@link BillKitProvider}. Throws a clear error when an\n * element is rendered outside a provider, the most common integration\n * mistake.\n */\nexport function useBillKit(): BillKitContextValue {\n const ctx = useContext(BillKitContext);\n if (ctx === null) {\n throw new Error(\n \"BillKit: a <CheckoutElement/> must be rendered inside a <BillKitProvider>.\",\n );\n }\n return ctx;\n}\n","import type {\n BaseElementOptions,\n BillKitElementError,\n BillKitElementHandle,\n BillKitElementLogger,\n BillKitThemeTokens,\n ChangeEvent,\n SuccessEvent,\n} from \"@billkit-eu/js\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useBillKit } from \"./BillKitProvider\";\n\n/** Callback + presentational props common to the React element components. */\nexport interface ReactElementProps {\n /** Ephemeral checkout `client_secret` (`<sessionId>_secret_...`). */\n clientSecret: string;\n theme?: BillKitThemeTokens;\n locale?: string;\n /** Load-watchdog timeout (ms) before `onError({code:\"load_timeout\"})`. */\n loadTimeoutMs?: number;\n /**\n * Overrides the provider's `logger` for this element only. Omitted,\n * the provider's is used; with neither, the element is silent.\n */\n logger?: BillKitElementLogger;\n /** Applied to the container `<div>` the iframe mounts into. */\n className?: string;\n style?: React.CSSProperties;\n onReady?: () => void;\n onChange?: (event: ChangeEvent) => void;\n onSuccess?: (event: SuccessEvent) => void;\n onError?: (error: BillKitElementError) => void;\n onRedirect?: (url: string) => boolean | void;\n}\n\ntype MountFn = (\n target: HTMLElement,\n options: BaseElementOptions,\n) => BillKitElementHandle;\n\n/**\n * SSR-safe mount hook shared by `<CheckoutElement/>` and\n * `<PaymentMethodElement/>`.\n *\n * - Returns `isClient = false` on the server and on the first client\n * render, so the component renders `null` and hydration matches. The\n * `useEffect` then flips it true and the real mount happens, with no\n * hydration mismatch, no `window` access during render.\n * - Callbacks are read through a ref, so a parent passing fresh closures\n * every render never forces a costly iframe remount. Only the identity\n * inputs (secret, origins) remount; theme changes hot-update in place.\n */\nexport function useElement(\n mount: MountFn,\n // `customerId` is not part of the public `ReactElementProps` (only the\n // payment-method element has one), but the hook still has to *see* it:\n // it is a remount input, and leaving it out of the dependency list\n // meant switching customers kept the previous customer's wallet — and\n // its \"set default\" / \"remove\" actions — on screen.\n props: ReactElementProps & { customerId?: string },\n): {\n isClient: boolean;\n containerRef: React.RefObject<HTMLDivElement>;\n handleRef: React.RefObject<BillKitElementHandle | null>;\n} {\n const { iframeOrigin, apiBase, logger: providerLogger } = useBillKit();\n const [isClient, setIsClient] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<BillKitElementHandle | null>(null);\n\n // Latest callbacks, read at event time and decoupled from remount inputs.\n const callbacks = useRef(props);\n callbacks.current = props;\n\n // Same trick for the logger: resolve it at call time through a ref so\n // an inline `logger={{...}}` (or switching one on mid-session) never\n // tears down and rebuilds the payment iframe. The element always\n // receives this one stable object; it forwards to whatever is current,\n // or drops the line when neither prop nor provider supplies one.\n const activeLogger = useRef<BillKitElementLogger | undefined>(undefined);\n activeLogger.current = props.logger ?? providerLogger;\n const stableLogger = useRef<BillKitElementLogger>({\n debug: (message, context) => activeLogger.current?.debug(message, context),\n warn: (message, context) => activeLogger.current?.warn(message, context),\n }).current;\n\n useEffect(() => setIsClient(true), []);\n\n const themeKey = props.theme ? JSON.stringify(props.theme) : \"\";\n\n useEffect(() => {\n if (!isClient || containerRef.current === null) return;\n const options: BaseElementOptions = {\n clientSecret: props.clientSecret,\n ...(props.theme ? { theme: props.theme } : {}),\n ...(props.locale ? { locale: props.locale } : {}),\n ...(iframeOrigin ? { iframeOrigin } : {}),\n ...(apiBase ? { apiBase } : {}),\n ...(props.loadTimeoutMs !== undefined ? { loadTimeoutMs: props.loadTimeoutMs } : {}),\n logger: stableLogger,\n onReady: () => callbacks.current.onReady?.(),\n onChange: (e) => callbacks.current.onChange?.(e),\n onSuccess: (e) => callbacks.current.onSuccess?.(e),\n onError: (e) => callbacks.current.onError?.(e),\n onRedirect: (url) => callbacks.current.onRedirect?.(url),\n };\n const handle = mount(containerRef.current, options);\n handleRef.current = handle;\n return () => {\n handle.destroy();\n handleRef.current = null;\n };\n // Remount only on identity inputs (secret / customer / origins);\n // callbacks are read through a ref and theme is hot-applied in the\n // effect below, so neither belongs in this dependency list.\n }, [isClient, props.clientSecret, props.customerId, props.locale, iframeOrigin, apiBase]);\n\n // Hot-apply theme changes without tearing down the iframe.\n useEffect(() => {\n if (themeKey && handleRef.current && props.theme) {\n handleRef.current.updateTheme(props.theme);\n }\n }, [themeKey]);\n\n return { isClient, containerRef, handleRef };\n}\n\n/**\n * The imperative handle `<CheckoutElement/>` and\n * `<PaymentMethodElement/>` expose through `ref`.\n *\n * Everything else about these components is declarative, but submitting\n * is genuinely an *event*, not a state: a merchant's own pay button\n * lives outside the iframe (that is the point of `onChange.complete`),\n * and it has to be able to say \"go\" exactly once. A `submit` prop would\n * have to be a toggling boolean, which is the classic React smell for an\n * action modelled as state, so this follows the\n * `useImperativeHandle` path that `<input>`'s `focus()` set.\n *\n * Calls made before the iframe has mounted are no-ops rather than\n * throwing — on the server, and on the first client render, there is no\n * element yet.\n */\nexport interface ThemeableElementRef {\n /** Push new theme tokens in without remounting. */\n updateTheme(theme: BillKitThemeTokens): void;\n}\n\nexport interface BillKitElementRef extends ThemeableElementRef {\n /** Submit the form from your own pay button. */\n submit(): void;\n}\n\n/** Build the ref `<CheckoutElement/>` exposes. */\nexport function checkoutElementRef(\n handleRef: React.RefObject<BillKitElementHandle | null>,\n): BillKitElementRef {\n return {\n submit: () => handleRef.current?.submit(),\n updateTheme: (theme) => handleRef.current?.updateTheme(theme),\n };\n}\n\n/**\n * Build the ref `<PaymentMethodElement/>` exposes.\n *\n * Deliberately no `submit`: the wallet element drops `billkit:submit` on\n * the floor (there is no form to submit — its actions are per-row \"set\n * default\" / \"remove\" buttons inside the iframe). Exposing a method that\n * silently does nothing would be worse than not having one.\n */\nexport function themeableElementRef(\n handleRef: React.RefObject<BillKitElementHandle | null>,\n): ThemeableElementRef {\n return {\n updateTheme: (theme) => handleRef.current?.updateTheme(theme),\n };\n}\n","import { mountCheckoutElement } from \"@billkit-eu/js\";\nimport { forwardRef, useImperativeHandle } from \"react\";\nimport {\n type BillKitElementRef,\n checkoutElementRef,\n type ReactElementProps,\n useElement,\n} from \"./useElement\";\n\nexport type CheckoutElementProps = ReactElementProps;\n\n/**\n * Embedded checkout, as a React component. SSR-safe: renders `null` on the\n * server and the first client render, then mounts the js.billkit.eu iframe\n * after hydration.\n *\n * ```tsx\n * <BillKitProvider>\n * <CheckoutElement\n * clientSecret={clientSecret}\n * theme={{ colorPrimary: \"#6d28d9\", borderRadius: \"10px\" }}\n * onSuccess={({ sessionId }) => router.push(`/thanks?cs=${sessionId}`)}\n * />\n * </BillKitProvider>\n * ```\n *\n * Driving your own pay button: take a `ref` and call `submit()`. Gate the\n * button on `onChange`'s `complete`, and re-enable it from `onError` —\n * a declined card fires `onError({ code: \"payment_declined\" })` and the\n * element shows its own retry panel.\n *\n * ```tsx\n * const element = useRef<BillKitElementRef>(null);\n * const [ready, setReady] = useState(false);\n * <>\n * <CheckoutElement\n * ref={element}\n * clientSecret={clientSecret}\n * onChange={({ complete }) => setReady(complete)}\n * onError={() => setSubmitting(false)}\n * />\n * <button disabled={!ready} onClick={() => element.current?.submit()}>Pay</button>\n * </>\n * ```\n */\nexport const CheckoutElement = forwardRef<BillKitElementRef, CheckoutElementProps>(\n function CheckoutElement(props, ref) {\n const { isClient, containerRef, handleRef } = useElement(mountCheckoutElement, props);\n // No dependency list: `handleRef` is a stable ref object, and the\n // closures read `.current` at call time, so the imperative handle\n // never goes stale across remounts.\n useImperativeHandle(ref, () => checkoutElementRef(handleRef), []);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n },\n);\n","import { type BaseElementOptions, type BillKitElementHandle, mountPaymentMethodElement } from \"@billkit-eu/js\";\nimport { forwardRef, useImperativeHandle } from \"react\";\nimport {\n type ReactElementProps,\n type ThemeableElementRef,\n themeableElementRef,\n useElement,\n} from \"./useElement\";\n\nexport interface PaymentMethodElementProps extends ReactElementProps {\n /** The customer whose saved payment methods to render + manage. */\n customerId: string;\n}\n\n/**\n * The customer's saved payment methods (\"Visa •••• 4242 · Update\"), as a\n * React component. Same SSR-safe mounting as {@link CheckoutElement}.\n *\n * Changing `customerId` remounts the element, so the wallet on screen\n * always belongs to the customer named in the props.\n *\n * The `ref` exposes `updateTheme()` only — this element has no form to\n * submit; its actions are per-row buttons inside the iframe.\n */\nexport const PaymentMethodElement = forwardRef<ThemeableElementRef, PaymentMethodElementProps>(\n function PaymentMethodElement(props, ref) {\n const { customerId } = props;\n const mount = (target: HTMLElement, options: BaseElementOptions): BillKitElementHandle =>\n mountPaymentMethodElement(target, { ...options, customerId });\n const { isClient, containerRef, handleRef } = useElement(mount, props);\n useImperativeHandle(ref, () => themeableElementRef(handleRef), []);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n },\n);\n"]}
1
+ {"version":3,"sources":["../src/BillKitProvider.tsx","../src/useElement.ts","../src/CheckoutElement.tsx","../src/PaymentMethodElement.tsx"],"names":["CheckoutElement","jsx","forwardRef","PaymentMethodElement","useImperativeHandle"],"mappings":"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","file":"index.js","sourcesContent":["import type { BillKitElementLogger } from \"@billkit-eu/js\";\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\n\n/**\n * Config shared by every BillKit element on the page. Mirrors\n * `@stripe/react-stripe-js`'s `<Elements>` provider — minus the key:\n * BillKit has no publishable-key concept. The API only mints secret keys\n * (`bk_live_...` / `bk_test_...`), which must never reach a browser;\n * elements authenticate with the ephemeral `client_secret` your server\n * gets from `POST /v1/checkout/sessions` with `ui_mode: \"embedded\"`, and\n * that secret already names the tenant, the mode, and the session. So the\n * provider carries origin overrides and a logger, nothing credential-shaped.\n */\nexport interface BillKitContextValue {\n /** Origin the element iframe is served from. Defaults to js.billkit.eu. */\n iframeOrigin?: string;\n /** API origin the iframe calls. Defaults to api.billkit.eu. */\n apiBase?: string;\n /**\n * Where every element beneath this provider sends its lifecycle\n * diagnostics: iframe boot, dropped `postMessage`s, refused\n * redirects, load timeouts. Omitted (the default) means silence: the\n * elements never write to `console` on their own.\n *\n * `console` works as-is. An individual element can override this with\n * its own `logger` prop. The `clientSecret` is never passed to it.\n */\n logger?: BillKitElementLogger;\n}\n\nconst BillKitContext = createContext<BillKitContextValue | null>(null);\n\nexport interface BillKitProviderProps extends BillKitContextValue {\n children: ReactNode;\n}\n\n/**\n * Wrap the part of your tree that renders BillKit elements.\n *\n * No credential is needed here; the element authenticates with the\n * ephemeral `client_secret` your server minted for the session:\n *\n * ```tsx\n * <BillKitProvider>\n * <CheckoutElement clientSecret={clientSecret} onSuccess={done} />\n * </BillKitProvider>\n * ```\n */\nexport function BillKitProvider({\n iframeOrigin,\n apiBase,\n logger,\n children,\n}: BillKitProviderProps): JSX.Element {\n const value = useMemo<BillKitContextValue>(\n () => ({ iframeOrigin, apiBase, logger }),\n [iframeOrigin, apiBase, logger],\n );\n return <BillKitContext.Provider value={value}>{children}</BillKitContext.Provider>;\n}\n\n/**\n * Read the nearest {@link BillKitProvider}. Throws a clear error when an\n * element is rendered outside a provider, the most common integration\n * mistake.\n */\nexport function useBillKit(): BillKitContextValue {\n const ctx = useContext(BillKitContext);\n if (ctx === null) {\n throw new Error(\n \"BillKit: a <CheckoutElement/> must be rendered inside a <BillKitProvider>.\",\n );\n }\n return ctx;\n}\n","import type {\n BaseElementOptions,\n BillKitElementError,\n BillKitElementHandle,\n BillKitElementLogger,\n BillKitThemeTokens,\n ChangeEvent,\n SuccessEvent,\n} from \"@billkit-eu/js\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { useBillKit } from \"./BillKitProvider\";\n\n/** Callback + presentational props common to the React element components. */\nexport interface ReactElementProps {\n /** Ephemeral checkout `client_secret` (`<sessionId>_secret_...`). */\n clientSecret: string;\n theme?: BillKitThemeTokens;\n locale?: string;\n /** Load-watchdog timeout (ms) before `onError({code:\"load_timeout\"})`. */\n loadTimeoutMs?: number;\n /**\n * Overrides the provider's `logger` for this element only. Omitted,\n * the provider's is used; with neither, the element is silent.\n */\n logger?: BillKitElementLogger;\n /** Applied to the container `<div>` the iframe mounts into. */\n className?: string;\n style?: React.CSSProperties;\n onReady?: () => void;\n onChange?: (event: ChangeEvent) => void;\n onSuccess?: (event: SuccessEvent) => void;\n onError?: (error: BillKitElementError) => void;\n onRedirect?: (url: string) => boolean | void;\n}\n\ntype MountFn = (\n target: HTMLElement,\n options: BaseElementOptions,\n) => BillKitElementHandle;\n\n/**\n * SSR-safe mount hook shared by `<CheckoutElement/>` and\n * `<PaymentMethodElement/>`.\n *\n * - Returns `isClient = false` on the server and on the first client\n * render, so the component renders `null` and hydration matches. The\n * `useEffect` then flips it true and the real mount happens, with no\n * hydration mismatch, no `window` access during render.\n * - Callbacks are read through a ref, so a parent passing fresh closures\n * every render never forces a costly iframe remount. Only the identity\n * inputs (secret, origins) remount; theme changes hot-update in place.\n */\nexport function useElement(\n mount: MountFn,\n // `customerId` is not part of the public `ReactElementProps` (only the\n // payment-method element has one), but the hook still has to *see* it:\n // it is a remount input, and leaving it out of the dependency list\n // meant switching customers kept the previous customer's wallet — and\n // its \"set default\" / \"remove\" actions — on screen.\n props: ReactElementProps & { customerId?: string },\n): {\n isClient: boolean;\n containerRef: React.RefObject<HTMLDivElement>;\n handleRef: React.RefObject<BillKitElementHandle | null>;\n} {\n const { iframeOrigin, apiBase, logger: providerLogger } = useBillKit();\n const [isClient, setIsClient] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<BillKitElementHandle | null>(null);\n\n // Latest callbacks, read at event time and decoupled from remount inputs.\n const callbacks = useRef(props);\n callbacks.current = props;\n\n // Same trick for the logger: resolve it at call time through a ref so\n // an inline `logger={{...}}` (or switching one on mid-session) never\n // tears down and rebuilds the payment iframe. The element always\n // receives this one stable object; it forwards to whatever is current,\n // or drops the line when neither prop nor provider supplies one.\n const activeLogger = useRef<BillKitElementLogger | undefined>(undefined);\n activeLogger.current = props.logger ?? providerLogger;\n const stableLogger = useRef<BillKitElementLogger>({\n debug: (message, context) => activeLogger.current?.debug(message, context),\n warn: (message, context) => activeLogger.current?.warn(message, context),\n }).current;\n\n useEffect(() => setIsClient(true), []);\n\n const themeKey = props.theme ? JSON.stringify(props.theme) : \"\";\n\n useEffect(() => {\n if (!isClient || containerRef.current === null) return;\n const options: BaseElementOptions = {\n clientSecret: props.clientSecret,\n ...(props.theme ? { theme: props.theme } : {}),\n ...(props.locale ? { locale: props.locale } : {}),\n ...(iframeOrigin ? { iframeOrigin } : {}),\n ...(apiBase ? { apiBase } : {}),\n ...(props.loadTimeoutMs !== undefined ? { loadTimeoutMs: props.loadTimeoutMs } : {}),\n logger: stableLogger,\n onReady: () => callbacks.current.onReady?.(),\n onChange: (e) => callbacks.current.onChange?.(e),\n onSuccess: (e) => callbacks.current.onSuccess?.(e),\n onError: (e) => callbacks.current.onError?.(e),\n onRedirect: (url) => callbacks.current.onRedirect?.(url),\n };\n const handle = mount(containerRef.current, options);\n handleRef.current = handle;\n return () => {\n handle.destroy();\n handleRef.current = null;\n };\n // Remount only on identity inputs (secret / customer / origins);\n // callbacks are read through a ref and theme is hot-applied in the\n // effect below, so neither belongs in this dependency list.\n }, [isClient, props.clientSecret, props.customerId, props.locale, iframeOrigin, apiBase]);\n\n // Hot-apply theme changes without tearing down the iframe.\n useEffect(() => {\n if (themeKey && handleRef.current && props.theme) {\n handleRef.current.updateTheme(props.theme);\n }\n }, [themeKey]);\n\n return { isClient, containerRef, handleRef };\n}\n\n/**\n * The imperative handle `<CheckoutElement/>` and\n * `<PaymentMethodElement/>` expose through `ref`.\n *\n * Everything else about these components is declarative, but submitting\n * is genuinely an *event*, not a state: a merchant's own pay button\n * lives outside the iframe (that is the point of `onChange.complete`),\n * and it has to be able to say \"go\" exactly once. A `submit` prop would\n * have to be a toggling boolean, which is the classic React smell for an\n * action modelled as state, so this follows the\n * `useImperativeHandle` path that `<input>`'s `focus()` set.\n *\n * Calls made before the iframe has mounted are no-ops rather than\n * throwing — on the server, and on the first client render, there is no\n * element yet.\n */\nexport interface ThemeableElementRef {\n /** Push new theme tokens in without remounting. */\n updateTheme(theme: BillKitThemeTokens): void;\n}\n\nexport interface BillKitElementRef extends ThemeableElementRef {\n /** Submit the form from your own pay button. */\n submit(): void;\n}\n\n/** Build the ref `<CheckoutElement/>` exposes. */\nexport function checkoutElementRef(\n handleRef: React.RefObject<BillKitElementHandle | null>,\n): BillKitElementRef {\n return {\n submit: () => handleRef.current?.submit(),\n updateTheme: (theme) => handleRef.current?.updateTheme(theme),\n };\n}\n\n/**\n * Build the ref `<PaymentMethodElement/>` exposes.\n *\n * Deliberately no `submit`: the wallet element drops `billkit:submit` on\n * the floor (there is no form to submit — its actions are per-row \"set\n * default\" / \"remove\" buttons inside the iframe). Exposing a method that\n * silently does nothing would be worse than not having one.\n */\nexport function themeableElementRef(\n handleRef: React.RefObject<BillKitElementHandle | null>,\n): ThemeableElementRef {\n return {\n updateTheme: (theme) => handleRef.current?.updateTheme(theme),\n };\n}\n","import { mountCheckoutElement } from \"@billkit-eu/js\";\nimport { forwardRef, useImperativeHandle } from \"react\";\nimport {\n type BillKitElementRef,\n checkoutElementRef,\n type ReactElementProps,\n useElement,\n} from \"./useElement\";\n\nexport type CheckoutElementProps = ReactElementProps;\n\n/**\n * Embedded checkout, as a React component. SSR-safe: renders `null` on the\n * server and the first client render, then mounts the js.billkit.eu iframe\n * after hydration.\n *\n * ```tsx\n * <BillKitProvider>\n * <CheckoutElement\n * clientSecret={clientSecret}\n * theme={{ colorPrimary: \"#6d28d9\", borderRadius: \"10px\" }}\n * onSuccess={({ sessionId }) => router.push(`/thanks?cs=${sessionId}`)}\n * />\n * </BillKitProvider>\n * ```\n *\n * Driving your own pay button: take a `ref` and call `submit()`. Gate the\n * button on `onChange`'s `complete`, and re-enable it from `onError` —\n * a declined card fires `onError({ code: \"payment_declined\" })` and the\n * element shows its own retry panel.\n *\n * ```tsx\n * const element = useRef<BillKitElementRef>(null);\n * const [ready, setReady] = useState(false);\n * <>\n * <CheckoutElement\n * ref={element}\n * clientSecret={clientSecret}\n * onChange={({ complete }) => setReady(complete)}\n * onError={() => setSubmitting(false)}\n * />\n * <button disabled={!ready} onClick={() => element.current?.submit()}>Pay</button>\n * </>\n * ```\n */\nexport const CheckoutElement = forwardRef<BillKitElementRef, CheckoutElementProps>(\n function CheckoutElement(props, ref) {\n const { isClient, containerRef, handleRef } = useElement(mountCheckoutElement, props);\n // No dependency list: `handleRef` is a stable ref object, and the\n // closures read `.current` at call time, so the imperative handle\n // never goes stale across remounts.\n useImperativeHandle(ref, () => checkoutElementRef(handleRef), []);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n },\n);\n","import { type BaseElementOptions, type BillKitElementHandle, mountPaymentMethodElement } from \"@billkit-eu/js\";\nimport { forwardRef, useImperativeHandle } from \"react\";\nimport {\n type ReactElementProps,\n type ThemeableElementRef,\n themeableElementRef,\n useElement,\n} from \"./useElement\";\n\nexport interface PaymentMethodElementProps extends ReactElementProps {\n /** The customer whose saved payment methods to render + manage. */\n customerId: string;\n}\n\n/**\n * The customer's saved payment methods (\"Visa •••• 4242 · Update\"), as a\n * React component. Same SSR-safe mounting as {@link CheckoutElement}.\n *\n * Changing `customerId` remounts the element, so the wallet on screen\n * always belongs to the customer named in the props.\n *\n * The `ref` exposes `updateTheme()` only — this element has no form to\n * submit; its actions are per-row buttons inside the iframe.\n */\nexport const PaymentMethodElement = forwardRef<ThemeableElementRef, PaymentMethodElementProps>(\n function PaymentMethodElement(props, ref) {\n const { customerId } = props;\n const mount = (target: HTMLElement, options: BaseElementOptions): BillKitElementHandle =>\n mountPaymentMethodElement(target, { ...options, customerId });\n const { isClient, containerRef, handleRef } = useElement(mount, props);\n useImperativeHandle(ref, () => themeableElementRef(handleRef), []);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n },\n);\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@billkit-eu/react",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "React bindings for BillKit's embedded checkout: <BillKitProvider> + <CheckoutElement/>, @stripe/react-stripe-js-shaped and SSR-safe.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -55,12 +55,12 @@
55
55
  "provenance": true
56
56
  },
57
57
  "peerDependencies": {
58
- "@billkit-eu/js": ">=0.2.0 <1",
58
+ "@billkit-eu/js": ">=0.2.1 <1",
59
59
  "react": ">=18",
60
60
  "react-dom": ">=18"
61
61
  },
62
62
  "devDependencies": {
63
- "@billkit-eu/js": ">=0.2.0 <1",
63
+ "@billkit-eu/js": ">=0.2.1 <1",
64
64
  "@testing-library/react": "^16.1.0",
65
65
  "@types/node": "^26.1.1",
66
66
  "@types/react": "^18.3.12",
@@ -5,7 +5,7 @@ import { createContext, type ReactNode, useContext, useMemo } from "react";
5
5
  * Config shared by every BillKit element on the page. Mirrors
6
6
  * `@stripe/react-stripe-js`'s `<Elements>` provider — minus the key:
7
7
  * BillKit has no publishable-key concept. The API only mints secret keys
8
- * (`sk_live_...` / `sk_test_...`), which must never reach a browser;
8
+ * (`bk_live_...` / `bk_test_...`), which must never reach a browser;
9
9
  * elements authenticate with the ephemeral `client_secret` your server
10
10
  * gets from `POST /v1/checkout/sessions` with `ui_mode: "embedded"`, and
11
11
  * that secret already names the tenant, the mode, and the session. So the