@billkit-eu/react 0.2.2 → 0.2.3

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,6 +9,36 @@ 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
+ ## [0.2.3] - 2026-09-23
13
+
14
+ ### Fixed
15
+ - **The package type-checks against React 19 again.**
16
+ - `useRef<T>(null)` returns `RefObject<T | null>` in React 19 and
17
+ `RefObject<T>` in React 18. The internal element hook declared the
18
+ narrower spelling, so building this package's source against React 19
19
+ failed with:
20
+
21
+ ```
22
+ error TS2322: Type 'RefObject<HTMLDivElement | null>' is not assignable
23
+ to type 'RefObject<HTMLDivElement>'.
24
+ ```
25
+
26
+ - The wider spelling is correct under **both**: React 18's `RefObject<T>`
27
+ already declared `current` as `T | null`, so the two only ever differed in
28
+ what they admitted.
29
+ - Nothing in the published `.d.ts` changes — the hook is internal — but
30
+ `files` ships `src/`, and the peer range has said `react: >=18` since
31
+ 0.1.0.
32
+
33
+ ### Changed
34
+ - **The test matrix installs React 19**, not 18.
35
+ - 0.2.2 fixed a React 19 type break while the dev dependency pinned
36
+ `@types/react` to `^18`, so the gate could not have caught that break or
37
+ this one: a type **removal** is invisible to the older types, and code
38
+ written against them keeps compiling.
39
+ - React 19 is the edge the peer range has to keep admitting, and code that
40
+ passes under its types passes under 18's.
41
+
12
42
  ## [0.2.2] - 2026-09-22
13
43
 
14
44
  ### Fixed
@@ -55,5 +85,9 @@ First public release.
55
85
  - React `>=18` (the peer range; the suite runs against 18), ESM + CJS dual
56
86
  package via `tsup`.
57
87
 
58
- [Unreleased]: https://github.com/billkit-eu/billkit-react/compare/v0.1.0...HEAD
88
+ [Unreleased]: https://github.com/billkit-eu/billkit-react/compare/v0.2.3...HEAD
89
+ [0.2.3]: https://github.com/billkit-eu/billkit-react/compare/v0.2.2...v0.2.3
90
+ [0.2.2]: https://github.com/billkit-eu/billkit-react/compare/v0.2.1...v0.2.2
91
+ [0.2.1]: https://github.com/billkit-eu/billkit-react/compare/v0.2.0...v0.2.1
92
+ [0.2.0]: https://github.com/billkit-eu/billkit-react/compare/v0.1.0...v0.2.0
59
93
  [0.1.0]: https://github.com/billkit-eu/billkit-react/releases/tag/v0.1.0
@@ -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 ReactElement, 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): ReactElement {\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":";;;;;;;AA8BA,IAAM,cAAA,GAAiBA,oBAA0C,IAAI,CAAA;AAkB9D,SAAS,eAAA,CAAgB;AAAA,EAC9B,YAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAuC;AACrC,EAAA,MAAM,KAAA,GAAQC,aAAA;AAAA,IACZ,OAAO,EAAE,YAAA,EAAc,OAAA,EAAS,MAAA,EAAO,CAAA;AAAA,IACvC,CAAC,YAAA,EAAc,OAAA,EAAS,MAAM;AAAA,GAChC;AACA,EAAA,uBAAOC,cAAA,CAAC,cAAA,CAAe,QAAA,EAAf,EAAwB,OAAe,QAAA,EAAS,CAAA;AAC1D;AAOO,SAAS,UAAA,GAAkC;AAChD,EAAA,MAAM,GAAA,GAAMC,iBAAW,cAAc,CAAA;AACrC,EAAA,IAAI,QAAQ,IAAA,EAAM;AAChB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;ACtBO,SAAS,UAAA,CACd,OAMA,KAAA,EAYA;AACA,EAAA,MAAM,EAAE,YAAA,EAAc,OAAA,EAAS,MAAA,EAAQ,cAAA,KAAmB,UAAA,EAAW;AACrE,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIC,eAAS,KAAK,CAAA;AAC9C,EAAA,MAAM,YAAA,GAAeC,aAAuB,IAAI,CAAA;AAChD,EAAA,MAAM,SAAA,GAAYA,aAAoC,IAAI,CAAA;AAG1D,EAAA,MAAM,SAAA,GAAYA,aAAO,KAAK,CAAA;AAC9B,EAAA,SAAA,CAAU,OAAA,GAAU,KAAA;AAOpB,EAAA,MAAM,YAAA,GAAeA,aAAyC,MAAS,CAAA;AACvE,EAAA,YAAA,CAAa,OAAA,GAAU,MAAM,MAAA,IAAU,cAAA;AACvC,EAAA,MAAM,eAAeA,YAAA,CAA6B;AAAA,IAChD,KAAA,EAAO,CAAC,OAAA,EAAS,OAAA,KAAY,aAAa,OAAA,EAAS,KAAA,CAAM,SAAS,OAAO,CAAA;AAAA,IACzE,IAAA,EAAM,CAAC,OAAA,EAAS,OAAA,KAAY,aAAa,OAAA,EAAS,IAAA,CAAK,SAAS,OAAO;AAAA,GACxE,CAAA,CAAE,OAAA;AAEH,EAAAC,eAAA,CAAU,MAAM,WAAA,CAAY,IAAI,CAAA,EAAG,EAAE,CAAA;AAErC,EAAA,MAAM,WAAW,KAAA,CAAM,KAAA,GAAQ,KAAK,SAAA,CAAU,KAAA,CAAM,KAAK,CAAA,GAAI,EAAA;AAE7D,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,QAAA,IAAY,YAAA,CAAa,OAAA,KAAY,IAAA,EAAM;AAChD,IAAA,MAAM,OAAA,GAA8B;AAAA,MAClC,cAAc,KAAA,CAAM,YAAA;AAAA,MACpB,GAAI,MAAM,KAAA,GAAQ,EAAE,OAAO,KAAA,CAAM,KAAA,KAAU,EAAC;AAAA,MAC5C,GAAI,MAAM,MAAA,GAAS,EAAE,QAAQ,KAAA,CAAM,MAAA,KAAW,EAAC;AAAA,MAC/C,GAAI,YAAA,GAAe,EAAE,YAAA,KAAiB,EAAC;AAAA,MACvC,GAAI,OAAA,GAAU,EAAE,OAAA,KAAY,EAAC;AAAA,MAC7B,GAAI,MAAM,aAAA,KAAkB,MAAA,GAAY,EAAE,aAAA,EAAe,KAAA,CAAM,aAAA,EAAc,GAAI,EAAC;AAAA,MAClF,MAAA,EAAQ,YAAA;AAAA,MACR,OAAA,EAAS,MAAM,SAAA,CAAU,OAAA,CAAQ,OAAA,IAAU;AAAA,MAC3C,UAAU,CAAC,CAAA,KAAM,SAAA,CAAU,OAAA,CAAQ,WAAW,CAAC,CAAA;AAAA,MAC/C,WAAW,CAAC,CAAA,KAAM,SAAA,CAAU,OAAA,CAAQ,YAAY,CAAC,CAAA;AAAA,MACjD,SAAS,CAAC,CAAA,KAAM,SAAA,CAAU,OAAA,CAAQ,UAAU,CAAC,CAAA;AAAA,MAC7C,YAAY,CAAC,GAAA,KAAQ,SAAA,CAAU,OAAA,CAAQ,aAAa,GAAG;AAAA,KACzD;AACA,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,YAAA,CAAa,OAAA,EAAS,OAAO,CAAA;AAClD,IAAA,SAAA,CAAU,OAAA,GAAU,MAAA;AACpB,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,CAAO,OAAA,EAAQ;AACf,MAAA,SAAA,CAAU,OAAA,GAAU,IAAA;AAAA,IACtB,CAAA;AAAA,EAIF,CAAA,EAAG,CAAC,QAAA,EAAU,KAAA,CAAM,YAAA,EAAc,KAAA,CAAM,UAAA,EAAY,KAAA,CAAM,MAAA,EAAQ,YAAA,EAAc,OAAO,CAAC,CAAA;AAGxF,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,QAAA,IAAY,SAAA,CAAU,OAAA,IAAW,KAAA,CAAM,KAAA,EAAO;AAChD,MAAA,SAAA,CAAU,OAAA,CAAQ,WAAA,CAAY,KAAA,CAAM,KAAK,CAAA;AAAA,IAC3C;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,OAAO,EAAE,QAAA,EAAU,YAAA,EAAc,SAAA,EAAU;AAC7C;AA6BO,SAAS,mBACd,SAAA,EACmB;AACnB,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,MAAM,SAAA,CAAU,OAAA,EAAS,MAAA,EAAO;AAAA,IACxC,aAAa,CAAC,KAAA,KAAU,SAAA,CAAU,OAAA,EAAS,YAAY,KAAK;AAAA,GAC9D;AACF;AAUO,SAAS,oBACd,SAAA,EACqB;AACrB,EAAA,OAAO;AAAA,IACL,aAAa,CAAC,KAAA,KAAU,SAAA,CAAU,OAAA,EAAS,YAAY,KAAK;AAAA,GAC9D;AACF;AC3IO,IAAM,eAAA,GAAkBC,gBAAA;AAAA,EAC7B,SAASC,gBAAAA,CAAgB,KAAA,EAAO,GAAA,EAAK;AACnC,IAAA,MAAM,EAAE,QAAA,EAAU,YAAA,EAAc,WAAU,GAAI,UAAA,CAAWC,yBAAsB,KAAK,CAAA;AAIpF,IAAAC,yBAAA,CAAoB,KAAK,MAAM,kBAAA,CAAmB,SAAS,CAAA,EAAG,EAAE,CAAA;AAChE,IAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AACtB,IAAA,uBAAOR,cAAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAK,YAAA,EAAc,WAAW,KAAA,CAAM,SAAA,EAAW,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,CAAA;AAAA,EACjF;AACF;AC/BO,IAAM,oBAAA,GAAuBK,gBAAAA;AAAA,EAClC,SAASI,qBAAAA,CAAqB,KAAA,EAAO,GAAA,EAAK;AACxC,IAAA,MAAM,EAAE,YAAW,GAAI,KAAA;AACvB,IAAA,MAAM,KAAA,GAAQ,CAAC,MAAA,EAAqB,OAAA,KAClCC,4BAAA,CAA0B,QAAQ,EAAE,GAAG,OAAA,EAAS,UAAA,EAAY,CAAA;AAC9D,IAAA,MAAM,EAAE,QAAA,EAAU,YAAA,EAAc,WAAU,GAAI,UAAA,CAAW,OAAO,KAAK,CAAA;AACrE,IAAAF,0BAAoB,GAAA,EAAK,MAAM,oBAAoB,SAAS,CAAA,EAAG,EAAE,CAAA;AACjE,IAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AACtB,IAAA,uBAAOR,cAAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAK,YAAA,EAAc,WAAW,KAAA,CAAM,SAAA,EAAW,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,CAAA;AAAA,EACjF;AACF","file":"index.cjs","sourcesContent":["import type { BillKitElementLogger } from \"@billkit-eu/js\";\nimport { createContext, type ReactElement, 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): ReactElement {\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 // `| null` is not decoration. React 19 changed `useRef<T>(null)` to return\n // `RefObject<T | null>` rather than `RefObject<T>`, so the narrower spelling\n // stopped type-checking against the version this package's peer range is\n // mostly installed alongside. It is also correct under React 18, whose\n // `RefObject<T>` already declared `current` as `T | null`, so the two\n // spellings only ever differed in what they admitted, never in what they\n // held. Same reason `handleRef` below has always carried it.\n containerRef: React.RefObject<HTMLDivElement | null>;\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.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 ReactElement, 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): ReactElement {\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 ReactElement, 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): ReactElement {\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 // `| null` is not decoration. React 19 changed `useRef<T>(null)` to return\n // `RefObject<T | null>` rather than `RefObject<T>`, so the narrower spelling\n // stopped type-checking against the version this package's peer range is\n // mostly installed alongside. It is also correct under React 18, whose\n // `RefObject<T>` already declared `current` as `T | null`, so the two\n // spellings only ever differed in what they admitted, never in what they\n // held. Same reason `handleRef` below has always carried it.\n containerRef: React.RefObject<HTMLDivElement | null>;\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.2",
3
+ "version": "0.2.3",
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",
@@ -63,14 +63,14 @@
63
63
  "@billkit-eu/js": ">=0.2.1 <1",
64
64
  "@testing-library/react": "^16.1.0",
65
65
  "@types/node": "^26.1.1",
66
- "@types/react": "^18.3.12",
67
- "@types/react-dom": "^18.3.1",
66
+ "@types/react": "^19.3.0",
67
+ "@types/react-dom": "^19.3.0",
68
68
  "@typescript-eslint/eslint-plugin": "^8.64.0",
69
69
  "@typescript-eslint/parser": "^8.20.0",
70
70
  "eslint": "^10.7.0",
71
71
  "jsdom": "^25.0.1",
72
- "react": "^18.3.1",
73
- "react-dom": "^18.3.1",
72
+ "react": "^19.3.0",
73
+ "react-dom": "^19.3.0",
74
74
  "tsup": "^8.3.5",
75
75
  "typescript": "^6.0.3",
76
76
  "vitest": "^4.1.10"
package/src/useElement.ts CHANGED
@@ -60,7 +60,14 @@ export function useElement(
60
60
  props: ReactElementProps & { customerId?: string },
61
61
  ): {
62
62
  isClient: boolean;
63
- containerRef: React.RefObject<HTMLDivElement>;
63
+ // `| null` is not decoration. React 19 changed `useRef<T>(null)` to return
64
+ // `RefObject<T | null>` rather than `RefObject<T>`, so the narrower spelling
65
+ // stopped type-checking against the version this package's peer range is
66
+ // mostly installed alongside. It is also correct under React 18, whose
67
+ // `RefObject<T>` already declared `current` as `T | null`, so the two
68
+ // spellings only ever differed in what they admitted, never in what they
69
+ // held. Same reason `handleRef` below has always carried it.
70
+ containerRef: React.RefObject<HTMLDivElement | null>;
64
71
  handleRef: React.RefObject<BillKitElementHandle | null>;
65
72
  } {
66
73
  const { iframeOrigin, apiBase, logger: providerLogger } = useBillKit();