@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 +9 -1
- package/README.md +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/BillKitProvider.tsx +1 -1
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
|
-
## [
|
|
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 (`
|
|
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
|
|
package/dist/index.cjs.map
CHANGED
|
@@ -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":";;;;;;;AA8BA,IAAM,cAAA,GAAiBA,oBAA0C,IAAI,CAAA;AAkB9D,SAAS,eAAA,CAAgB;AAAA,EAC9B,YAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAsC;AACpC,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,EAKA;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;ACpIO,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 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
|
-
* (`
|
|
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
|
-
* (`
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
package/src/BillKitProvider.tsx
CHANGED
|
@@ -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
|
-
* (`
|
|
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
|