@billkit-eu/react 0.1.0

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 ADDED
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ All notable changes to the BillKit React SDK will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ This package is a thin binding layer over `@billkit-eu/js`, which it takes as a
9
+ peer dependency. Behaviour changes usually land there and are recorded in that
10
+ package's changelog.
11
+
12
+ ## [Unreleased]
13
+
14
+ ## [0.1.0]
15
+
16
+ First public release.
17
+
18
+ ### Added
19
+ - `<BillKitProvider>`: holds the publishable key and shared element
20
+ configuration for the tree below it, so individual elements take only the
21
+ props that vary. Shaped after `@stripe/react-stripe-js` `<Elements>`, which
22
+ is the API most teams are migrating from.
23
+ - `<CheckoutElement/>` and `<PaymentMethodElement/>`: React wrappers over the
24
+ corresponding `@billkit-eu/js` mounts, with the iframe's lifecycle tied to
25
+ the component's.
26
+ - `useBillKit()` for reading the provider's context, and the `ReactElementProps`
27
+ type for components that wrap an element of their own.
28
+ - Server rendering support: elements render `null` on the server and mount the
29
+ iframe after hydration, so importing this package does not break an SSR build
30
+ or produce a hydration mismatch.
31
+ - Callback props are held in refs internally, so passing a new inline arrow
32
+ function on every render does not tear down and remount the iframe. This is
33
+ the failure mode that makes naive wrappers lose payment state mid-entry.
34
+ - The `@billkit-eu/js` types are re-exported (`BillKitElementHandle`,
35
+ `BillKitThemeTokens`, `SuccessEvent`, `ChangeEvent`, `BillKitElementError`,
36
+ `BillKitElementLogger`, `ElementLogContext`), so consumers import from one
37
+ package.
38
+ - React `>=18` (the peer range; the suite runs against 18), ESM + CJS dual
39
+ package via `tsup`.
40
+
41
+ [Unreleased]: https://github.com/billkit-eu/billkit-react/compare/v0.1.0...HEAD
42
+ [0.1.0]: https://github.com/billkit-eu/billkit-react/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for describing the origin of the Work and
141
+ reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may accept and charge a
167
+ fee for the acceptance of support, warranty, indemnity, or other
168
+ liability obligations and/or rights consistent with this License.
169
+ However, in accepting such obligations, You may act only on Your
170
+ own behalf and on Your sole responsibility, not on behalf of any
171
+ other Contributor, and only if You agree to indemnify, defend,
172
+ and hold each Contributor harmless for any liability incurred by,
173
+ or claims asserted against, such Contributor by reason of your
174
+ accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 BillKit
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
200
+ implied. See the License for the specific language governing
201
+ permissions and limitations under the License.
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # BillKit React SDK
2
+
3
+ React bindings for [BillKit](https://billkit.eu)'s embedded checkout, shaped like `@stripe/react-stripe-js` and safe to render on the server.
4
+
5
+ A thin wrapper over [`@billkit-eu/js`](https://www.npmjs.com/package/@billkit-eu/js). The card fields render inside a cross-origin iframe served from `js.billkit.eu`, so card data never touches your origin and you stay on PCI SAQ A. All of the security model and `postMessage` handling lives in the loader; this package is the React surface over it.
6
+
7
+ No secret key ever reaches the browser. For server-side calls, use [`@billkit-eu/sdk`](https://www.npmjs.com/package/@billkit-eu/sdk).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @billkit-eu/react @billkit-eu/js
13
+ # or pnpm add @billkit-eu/react @billkit-eu/js
14
+ ```
15
+
16
+ `@billkit-eu/js`, `react` (18+) and `react-dom` (18+) are peer dependencies, so install `@billkit-eu/js` alongside this package.
17
+
18
+ ## Quick start
19
+
20
+ Mint an ephemeral `client_secret` on your server, then hand it to the element:
21
+
22
+ ```tsx
23
+ import { BillKitProvider, CheckoutElement } from "@billkit-eu/react";
24
+
25
+ export function Checkout({ clientSecret }: { clientSecret: string }) {
26
+ return (
27
+ <BillKitProvider>
28
+ <CheckoutElement
29
+ clientSecret={clientSecret}
30
+ theme={{ colorPrimary: "#6d28d9", borderRadius: "10px" }}
31
+ onSuccess={({ sessionId }) => router.push(`/thanks?cs=${sessionId}`)}
32
+ onError={({ message }) => toast.error(message)}
33
+ />
34
+ </BillKitProvider>
35
+ );
36
+ }
37
+ ```
38
+
39
+ No credential goes on the provider. The element authenticates with the `client_secret`, which already names the tenant, the mode and the session.
40
+
41
+ ## Saved payment methods
42
+
43
+ ```tsx
44
+ import { BillKitProvider, PaymentMethodElement } from "@billkit-eu/react";
45
+
46
+ <BillKitProvider>
47
+ <PaymentMethodElement clientSecret={clientSecret} customerId="cus_123" />
48
+ </BillKitProvider>;
49
+ ```
50
+
51
+ ## Server rendering
52
+
53
+ Both components are SSR-safe. They render `null` on the server and on the first client render, then mount the iframe after hydration, so there is no hydration mismatch and no `window` access during render. Next.js App Router, Remix and Astro islands all work without a `dynamic(..., { ssr: false })` wrapper.
54
+
55
+ ## Props
56
+
57
+ `<CheckoutElement/>` and `<PaymentMethodElement/>` share these:
58
+
59
+ | Prop | Type | Notes |
60
+ |---|---|---|
61
+ | `clientSecret` | `string` | Required. From `POST /v1/checkout/sessions` with `ui_mode: "embedded"`. |
62
+ | `theme` | `BillKitThemeTokens` | Colour, radius, font and spacing tokens. |
63
+ | `locale` | `string` | BCP-47, for example `"nl"`. Defaults to the customer's browser. |
64
+ | `loadTimeoutMs` | `number` | Before `onError({ code: "load_timeout" })`. Default `20000`; `0` disables it. |
65
+ | `logger` | `BillKitElementLogger` | Overrides the provider's logger for this element only. |
66
+ | `className` / `style` | | Applied to the container `<div>` the iframe mounts into. |
67
+ | `onReady` | `() => void` | The iframe booted and loaded the session. |
68
+ | `onChange` | `(e: ChangeEvent) => void` | `e.complete` drives an external pay button. |
69
+ | `onSuccess` | `(e: SuccessEvent) => void` | Terminal success with no redirect. |
70
+ | `onError` | `(e: BillKitElementError) => void` | Any element or payment error. |
71
+ | `onRedirect` | `(url: string) => boolean \| void` | Before the top window navigates for 3DS or iDEAL. Return `false` to navigate yourself. |
72
+
73
+ `<PaymentMethodElement/>` also requires `customerId`.
74
+
75
+ `<BillKitProvider>` accepts `iframeOrigin`, `apiBase` and `logger`, applied to every element beneath it.
76
+
77
+ ### Callbacks and re-renders
78
+
79
+ Pass inline arrow functions freely. Callbacks are read through a ref at event time, so a fresh closure on every render does not tear down a live payment iframe. The same holds for `logger`: an inline `logger={{...}}`, or switching one on mid-session, never triggers a remount.
80
+
81
+ Only `clientSecret`, `locale` and the origin overrides remount the element. That is deliberate, because a remount destroys an in-progress payment.
82
+
83
+ ## Content Security Policy
84
+
85
+ ```
86
+ frame-src https://js.billkit.eu;
87
+ ```
88
+
89
+ If the iframe never boots, `onError` fires with `load_timeout`. A missing `frame-src` and an ad blocker look identical from the page, so check both.
90
+
91
+ ## Logging
92
+
93
+ Elements are silent by default. Installing the package does not start writing to anyone's console. Opt in on the provider, or per element:
94
+
95
+ ```tsx
96
+ <BillKitProvider logger={console}>
97
+ <CheckoutElement clientSecret={clientSecret} />
98
+ </BillKitProvider>
99
+ ```
100
+
101
+ Never logged: the `clientSecret`, message payloads, or full redirect URLs. Only a redirect's origin is recorded, because the rest of the URL carries provider payment identifiers.
102
+
103
+ ## Migrating from `@stripe/react-stripe-js`
104
+
105
+ The shapes line up, with one difference worth calling out.
106
+
107
+ `publishableKey` on `<BillKitProvider>` is **deprecated, optional and ignored**. BillKit has no publishable-key concept; the API only mints secret keys, which must never reach a browser. The prop was required in 0.1.0 by analogy with Stripe, but nothing ever read it. Pass nothing. It will be removed in the next major.
108
+
109
+ See the [migration guide](https://docs.billkit.eu/migration/elements/) for the full comparison.
110
+
111
+ ## TypeScript
112
+
113
+ Types ship with the package. The loader's shared types are re-exported so you can import from one place:
114
+
115
+ ```ts
116
+ import type {
117
+ BillKitThemeTokens,
118
+ ChangeEvent,
119
+ SuccessEvent,
120
+ BillKitElementError,
121
+ } from "@billkit-eu/react";
122
+ ```
123
+
124
+ ## Links
125
+
126
+ - Documentation: [docs.billkit.eu](https://docs.billkit.eu)
127
+ - Source: [github.com/billkit-eu/billkit-react](https://github.com/billkit-eu/billkit-react)
128
+ - Issues: [github.com/billkit-eu/billkit-react/issues](https://github.com/billkit-eu/billkit-react/issues)
129
+
130
+ ## License
131
+
132
+ Apache-2.0
package/dist/index.cjs ADDED
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var js = require('@billkit-eu/js');
6
+
7
+ // src/BillKitProvider.tsx
8
+ var BillKitContext = react.createContext(null);
9
+ function BillKitProvider({
10
+ publishableKey,
11
+ iframeOrigin,
12
+ apiBase,
13
+ logger,
14
+ children
15
+ }) {
16
+ const value = react.useMemo(
17
+ () => ({ publishableKey, iframeOrigin, apiBase, logger }),
18
+ [publishableKey, iframeOrigin, apiBase, logger]
19
+ );
20
+ return /* @__PURE__ */ jsxRuntime.jsx(BillKitContext.Provider, { value, children });
21
+ }
22
+ function useBillKit() {
23
+ const ctx = react.useContext(BillKitContext);
24
+ if (ctx === null) {
25
+ throw new Error(
26
+ "BillKit: a <CheckoutElement/> must be rendered inside a <BillKitProvider>."
27
+ );
28
+ }
29
+ return ctx;
30
+ }
31
+ function useElement(mount, props) {
32
+ const { iframeOrigin, apiBase, logger: providerLogger } = useBillKit();
33
+ const [isClient, setIsClient] = react.useState(false);
34
+ const containerRef = react.useRef(null);
35
+ const handleRef = react.useRef(null);
36
+ const callbacks = react.useRef(props);
37
+ callbacks.current = props;
38
+ const activeLogger = react.useRef(void 0);
39
+ activeLogger.current = props.logger ?? providerLogger;
40
+ const stableLogger = react.useRef({
41
+ debug: (message, context) => activeLogger.current?.debug(message, context),
42
+ warn: (message, context) => activeLogger.current?.warn(message, context)
43
+ }).current;
44
+ react.useEffect(() => setIsClient(true), []);
45
+ const themeKey = props.theme ? JSON.stringify(props.theme) : "";
46
+ react.useEffect(() => {
47
+ if (!isClient || containerRef.current === null) return;
48
+ const options = {
49
+ clientSecret: props.clientSecret,
50
+ ...props.theme ? { theme: props.theme } : {},
51
+ ...props.locale ? { locale: props.locale } : {},
52
+ ...iframeOrigin ? { iframeOrigin } : {},
53
+ ...apiBase ? { apiBase } : {},
54
+ ...props.loadTimeoutMs !== void 0 ? { loadTimeoutMs: props.loadTimeoutMs } : {},
55
+ logger: stableLogger,
56
+ onReady: () => callbacks.current.onReady?.(),
57
+ onChange: (e) => callbacks.current.onChange?.(e),
58
+ onSuccess: (e) => callbacks.current.onSuccess?.(e),
59
+ onError: (e) => callbacks.current.onError?.(e),
60
+ onRedirect: (url) => callbacks.current.onRedirect?.(url)
61
+ };
62
+ const handle = mount(containerRef.current, options);
63
+ handleRef.current = handle;
64
+ return () => {
65
+ handle.destroy();
66
+ handleRef.current = null;
67
+ };
68
+ }, [isClient, props.clientSecret, props.locale, iframeOrigin, apiBase]);
69
+ react.useEffect(() => {
70
+ if (themeKey && handleRef.current && props.theme) {
71
+ handleRef.current.updateTheme(props.theme);
72
+ }
73
+ }, [themeKey]);
74
+ return { isClient, containerRef };
75
+ }
76
+ function CheckoutElement(props) {
77
+ const { isClient, containerRef } = useElement(js.mountCheckoutElement, props);
78
+ if (!isClient) return null;
79
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: props.className, style: props.style });
80
+ }
81
+ function PaymentMethodElement(props) {
82
+ const { customerId } = props;
83
+ const mount = (target, options) => js.mountPaymentMethodElement(target, { ...options, customerId });
84
+ const { isClient, containerRef } = useElement(mount, props);
85
+ if (!isClient) return null;
86
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: props.className, style: props.style });
87
+ }
88
+
89
+ exports.BillKitProvider = BillKitProvider;
90
+ exports.CheckoutElement = CheckoutElement;
91
+ exports.PaymentMethodElement = PaymentMethodElement;
92
+ exports.useBillKit = useBillKit;
93
+ //# sourceMappingURL=index.cjs.map
94
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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","mountCheckoutElement","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: put your public key\n * (and any origin overrides) here once, then render elements freely\n * beneath it.\n */\nexport interface BillKitContextValue {\n /**\n * @deprecated Not required, not used, and not something BillKit issues.\n *\n * BillKit has no publishable-key concept; the API only mints secret\n * keys (`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\"`,\n * and that secret already names the tenant, the mode, and the session.\n *\n * The prop was accepted (and required) in 0.1.0 by analogy with\n * `@stripe/react-stripe-js`, but nothing ever read it, and a tenant\n * following the docs went looking for a `pk_...` value that does not\n * exist. It is now optional and ignored; pass nothing. It will be\n * removed in the next major.\n */\n publishableKey?: string;\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 publishableKey,\n iframeOrigin,\n apiBase,\n logger,\n children,\n}: BillKitProviderProps): JSX.Element {\n const value = useMemo<BillKitContextValue>(\n () => ({ publishableKey, iframeOrigin, apiBase, logger }),\n [publishableKey, 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 props: ReactElementProps,\n): { isClient: boolean; containerRef: React.RefObject<HTMLDivElement> } {\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 / origins); callbacks are\n // read through a ref and theme is hot-applied in the effect below, so\n // neither belongs in this dependency list.\n }, [isClient, props.clientSecret, 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 };\n}\n","import { mountCheckoutElement } from \"@billkit-eu/js\";\nimport { type ReactElementProps, useElement } 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 */\nexport function CheckoutElement(props: CheckoutElementProps): JSX.Element | null {\n const { isClient, containerRef } = useElement(mountCheckoutElement, props);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n}\n","import { type BaseElementOptions, type BillKitElementHandle, mountPaymentMethodElement } from \"@billkit-eu/js\";\nimport { type ReactElementProps, useElement } 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 */\nexport function PaymentMethodElement(props: PaymentMethodElementProps): JSX.Element | null {\n const { customerId } = props;\n const mount = (target: HTMLElement, options: BaseElementOptions): BillKitElementHandle =>\n mountPaymentMethodElement(target, { ...options, customerId });\n const { isClient, containerRef } = useElement(mount, props);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n}\n"]}
@@ -0,0 +1,117 @@
1
+ import { BillKitElementLogger, BillKitThemeTokens, ChangeEvent, SuccessEvent, BillKitElementError } from '@billkit-eu/js';
2
+ export { BillKitElementError, BillKitElementHandle, BillKitElementLogger, BillKitThemeTokens, ChangeEvent, ElementLogContext, SuccessEvent } from '@billkit-eu/js';
3
+ import { ReactNode } from 'react';
4
+
5
+ /**
6
+ * Config shared by every BillKit element on the page. Mirrors
7
+ * `@stripe/react-stripe-js`'s `<Elements>` provider: put your public key
8
+ * (and any origin overrides) here once, then render elements freely
9
+ * beneath it.
10
+ */
11
+ interface BillKitContextValue {
12
+ /**
13
+ * @deprecated Not required, not used, and not something BillKit issues.
14
+ *
15
+ * BillKit has no publishable-key concept; the API only mints secret
16
+ * keys (`sk_live_...` / `sk_test_...`), which must never reach a browser.
17
+ * Elements authenticate with the ephemeral `client_secret` your server
18
+ * gets from `POST /v1/checkout/sessions` with `ui_mode: "embedded"`,
19
+ * and that secret already names the tenant, the mode, and the session.
20
+ *
21
+ * The prop was accepted (and required) in 0.1.0 by analogy with
22
+ * `@stripe/react-stripe-js`, but nothing ever read it, and a tenant
23
+ * following the docs went looking for a `pk_...` value that does not
24
+ * exist. It is now optional and ignored; pass nothing. It will be
25
+ * removed in the next major.
26
+ */
27
+ publishableKey?: string;
28
+ /** Origin the element iframe is served from. Defaults to js.billkit.eu. */
29
+ iframeOrigin?: string;
30
+ /** API origin the iframe calls. Defaults to api.billkit.eu. */
31
+ apiBase?: string;
32
+ /**
33
+ * Where every element beneath this provider sends its lifecycle
34
+ * diagnostics: iframe boot, dropped `postMessage`s, refused
35
+ * redirects, load timeouts. Omitted (the default) means silence: the
36
+ * elements never write to `console` on their own.
37
+ *
38
+ * `console` works as-is. An individual element can override this with
39
+ * its own `logger` prop. The `clientSecret` is never passed to it.
40
+ */
41
+ logger?: BillKitElementLogger;
42
+ }
43
+ interface BillKitProviderProps extends BillKitContextValue {
44
+ children: ReactNode;
45
+ }
46
+ /**
47
+ * Wrap the part of your tree that renders BillKit elements.
48
+ *
49
+ * No credential is needed here; the element authenticates with the
50
+ * ephemeral `client_secret` your server minted for the session:
51
+ *
52
+ * ```tsx
53
+ * <BillKitProvider>
54
+ * <CheckoutElement clientSecret={clientSecret} onSuccess={done} />
55
+ * </BillKitProvider>
56
+ * ```
57
+ */
58
+ declare function BillKitProvider({ publishableKey, iframeOrigin, apiBase, logger, children, }: BillKitProviderProps): JSX.Element;
59
+ /**
60
+ * Read the nearest {@link BillKitProvider}. Throws a clear error when an
61
+ * element is rendered outside a provider, the most common integration
62
+ * mistake.
63
+ */
64
+ declare function useBillKit(): BillKitContextValue;
65
+
66
+ /** Callback + presentational props common to the React element components. */
67
+ interface ReactElementProps {
68
+ /** Ephemeral checkout `client_secret` (`<sessionId>_secret_...`). */
69
+ clientSecret: string;
70
+ theme?: BillKitThemeTokens;
71
+ locale?: string;
72
+ /** Load-watchdog timeout (ms) before `onError({code:"load_timeout"})`. */
73
+ loadTimeoutMs?: number;
74
+ /**
75
+ * Overrides the provider's `logger` for this element only. Omitted,
76
+ * the provider's is used; with neither, the element is silent.
77
+ */
78
+ logger?: BillKitElementLogger;
79
+ /** Applied to the container `<div>` the iframe mounts into. */
80
+ className?: string;
81
+ style?: React.CSSProperties;
82
+ onReady?: () => void;
83
+ onChange?: (event: ChangeEvent) => void;
84
+ onSuccess?: (event: SuccessEvent) => void;
85
+ onError?: (error: BillKitElementError) => void;
86
+ onRedirect?: (url: string) => boolean | void;
87
+ }
88
+
89
+ type CheckoutElementProps = ReactElementProps;
90
+ /**
91
+ * Embedded checkout, as a React component. SSR-safe: renders `null` on the
92
+ * server and the first client render, then mounts the js.billkit.eu iframe
93
+ * after hydration.
94
+ *
95
+ * ```tsx
96
+ * <BillKitProvider>
97
+ * <CheckoutElement
98
+ * clientSecret={clientSecret}
99
+ * theme={{ colorPrimary: "#6d28d9", borderRadius: "10px" }}
100
+ * onSuccess={({ sessionId }) => router.push(`/thanks?cs=${sessionId}`)}
101
+ * />
102
+ * </BillKitProvider>
103
+ * ```
104
+ */
105
+ declare function CheckoutElement(props: CheckoutElementProps): JSX.Element | null;
106
+
107
+ interface PaymentMethodElementProps extends ReactElementProps {
108
+ /** The customer whose saved payment methods to render + manage. */
109
+ customerId: string;
110
+ }
111
+ /**
112
+ * The customer's saved payment methods ("Visa •••• 4242 · Update"), as a
113
+ * React component. Same SSR-safe mounting as {@link CheckoutElement}.
114
+ */
115
+ declare function PaymentMethodElement(props: PaymentMethodElementProps): JSX.Element | null;
116
+
117
+ export { type BillKitContextValue, BillKitProvider, type BillKitProviderProps, CheckoutElement, type CheckoutElementProps, PaymentMethodElement, type PaymentMethodElementProps, type ReactElementProps, useBillKit };
@@ -0,0 +1,117 @@
1
+ import { BillKitElementLogger, BillKitThemeTokens, ChangeEvent, SuccessEvent, BillKitElementError } from '@billkit-eu/js';
2
+ export { BillKitElementError, BillKitElementHandle, BillKitElementLogger, BillKitThemeTokens, ChangeEvent, ElementLogContext, SuccessEvent } from '@billkit-eu/js';
3
+ import { ReactNode } from 'react';
4
+
5
+ /**
6
+ * Config shared by every BillKit element on the page. Mirrors
7
+ * `@stripe/react-stripe-js`'s `<Elements>` provider: put your public key
8
+ * (and any origin overrides) here once, then render elements freely
9
+ * beneath it.
10
+ */
11
+ interface BillKitContextValue {
12
+ /**
13
+ * @deprecated Not required, not used, and not something BillKit issues.
14
+ *
15
+ * BillKit has no publishable-key concept; the API only mints secret
16
+ * keys (`sk_live_...` / `sk_test_...`), which must never reach a browser.
17
+ * Elements authenticate with the ephemeral `client_secret` your server
18
+ * gets from `POST /v1/checkout/sessions` with `ui_mode: "embedded"`,
19
+ * and that secret already names the tenant, the mode, and the session.
20
+ *
21
+ * The prop was accepted (and required) in 0.1.0 by analogy with
22
+ * `@stripe/react-stripe-js`, but nothing ever read it, and a tenant
23
+ * following the docs went looking for a `pk_...` value that does not
24
+ * exist. It is now optional and ignored; pass nothing. It will be
25
+ * removed in the next major.
26
+ */
27
+ publishableKey?: string;
28
+ /** Origin the element iframe is served from. Defaults to js.billkit.eu. */
29
+ iframeOrigin?: string;
30
+ /** API origin the iframe calls. Defaults to api.billkit.eu. */
31
+ apiBase?: string;
32
+ /**
33
+ * Where every element beneath this provider sends its lifecycle
34
+ * diagnostics: iframe boot, dropped `postMessage`s, refused
35
+ * redirects, load timeouts. Omitted (the default) means silence: the
36
+ * elements never write to `console` on their own.
37
+ *
38
+ * `console` works as-is. An individual element can override this with
39
+ * its own `logger` prop. The `clientSecret` is never passed to it.
40
+ */
41
+ logger?: BillKitElementLogger;
42
+ }
43
+ interface BillKitProviderProps extends BillKitContextValue {
44
+ children: ReactNode;
45
+ }
46
+ /**
47
+ * Wrap the part of your tree that renders BillKit elements.
48
+ *
49
+ * No credential is needed here; the element authenticates with the
50
+ * ephemeral `client_secret` your server minted for the session:
51
+ *
52
+ * ```tsx
53
+ * <BillKitProvider>
54
+ * <CheckoutElement clientSecret={clientSecret} onSuccess={done} />
55
+ * </BillKitProvider>
56
+ * ```
57
+ */
58
+ declare function BillKitProvider({ publishableKey, iframeOrigin, apiBase, logger, children, }: BillKitProviderProps): JSX.Element;
59
+ /**
60
+ * Read the nearest {@link BillKitProvider}. Throws a clear error when an
61
+ * element is rendered outside a provider, the most common integration
62
+ * mistake.
63
+ */
64
+ declare function useBillKit(): BillKitContextValue;
65
+
66
+ /** Callback + presentational props common to the React element components. */
67
+ interface ReactElementProps {
68
+ /** Ephemeral checkout `client_secret` (`<sessionId>_secret_...`). */
69
+ clientSecret: string;
70
+ theme?: BillKitThemeTokens;
71
+ locale?: string;
72
+ /** Load-watchdog timeout (ms) before `onError({code:"load_timeout"})`. */
73
+ loadTimeoutMs?: number;
74
+ /**
75
+ * Overrides the provider's `logger` for this element only. Omitted,
76
+ * the provider's is used; with neither, the element is silent.
77
+ */
78
+ logger?: BillKitElementLogger;
79
+ /** Applied to the container `<div>` the iframe mounts into. */
80
+ className?: string;
81
+ style?: React.CSSProperties;
82
+ onReady?: () => void;
83
+ onChange?: (event: ChangeEvent) => void;
84
+ onSuccess?: (event: SuccessEvent) => void;
85
+ onError?: (error: BillKitElementError) => void;
86
+ onRedirect?: (url: string) => boolean | void;
87
+ }
88
+
89
+ type CheckoutElementProps = ReactElementProps;
90
+ /**
91
+ * Embedded checkout, as a React component. SSR-safe: renders `null` on the
92
+ * server and the first client render, then mounts the js.billkit.eu iframe
93
+ * after hydration.
94
+ *
95
+ * ```tsx
96
+ * <BillKitProvider>
97
+ * <CheckoutElement
98
+ * clientSecret={clientSecret}
99
+ * theme={{ colorPrimary: "#6d28d9", borderRadius: "10px" }}
100
+ * onSuccess={({ sessionId }) => router.push(`/thanks?cs=${sessionId}`)}
101
+ * />
102
+ * </BillKitProvider>
103
+ * ```
104
+ */
105
+ declare function CheckoutElement(props: CheckoutElementProps): JSX.Element | null;
106
+
107
+ interface PaymentMethodElementProps extends ReactElementProps {
108
+ /** The customer whose saved payment methods to render + manage. */
109
+ customerId: string;
110
+ }
111
+ /**
112
+ * The customer's saved payment methods ("Visa •••• 4242 · Update"), as a
113
+ * React component. Same SSR-safe mounting as {@link CheckoutElement}.
114
+ */
115
+ declare function PaymentMethodElement(props: PaymentMethodElementProps): JSX.Element | null;
116
+
117
+ export { type BillKitContextValue, BillKitProvider, type BillKitProviderProps, CheckoutElement, type CheckoutElementProps, PaymentMethodElement, type PaymentMethodElementProps, type ReactElementProps, useBillKit };
package/dist/index.js ADDED
@@ -0,0 +1,89 @@
1
+ import { createContext, useMemo, useContext, useState, useRef, useEffect } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { mountCheckoutElement, mountPaymentMethodElement } from '@billkit-eu/js';
4
+
5
+ // src/BillKitProvider.tsx
6
+ var BillKitContext = createContext(null);
7
+ function BillKitProvider({
8
+ publishableKey,
9
+ iframeOrigin,
10
+ apiBase,
11
+ logger,
12
+ children
13
+ }) {
14
+ const value = useMemo(
15
+ () => ({ publishableKey, iframeOrigin, apiBase, logger }),
16
+ [publishableKey, iframeOrigin, apiBase, logger]
17
+ );
18
+ return /* @__PURE__ */ jsx(BillKitContext.Provider, { value, children });
19
+ }
20
+ function useBillKit() {
21
+ const ctx = useContext(BillKitContext);
22
+ if (ctx === null) {
23
+ throw new Error(
24
+ "BillKit: a <CheckoutElement/> must be rendered inside a <BillKitProvider>."
25
+ );
26
+ }
27
+ return ctx;
28
+ }
29
+ function useElement(mount, props) {
30
+ const { iframeOrigin, apiBase, logger: providerLogger } = useBillKit();
31
+ const [isClient, setIsClient] = useState(false);
32
+ const containerRef = useRef(null);
33
+ const handleRef = useRef(null);
34
+ const callbacks = useRef(props);
35
+ callbacks.current = props;
36
+ const activeLogger = useRef(void 0);
37
+ activeLogger.current = props.logger ?? providerLogger;
38
+ const stableLogger = useRef({
39
+ debug: (message, context) => activeLogger.current?.debug(message, context),
40
+ warn: (message, context) => activeLogger.current?.warn(message, context)
41
+ }).current;
42
+ useEffect(() => setIsClient(true), []);
43
+ const themeKey = props.theme ? JSON.stringify(props.theme) : "";
44
+ useEffect(() => {
45
+ if (!isClient || containerRef.current === null) return;
46
+ const options = {
47
+ clientSecret: props.clientSecret,
48
+ ...props.theme ? { theme: props.theme } : {},
49
+ ...props.locale ? { locale: props.locale } : {},
50
+ ...iframeOrigin ? { iframeOrigin } : {},
51
+ ...apiBase ? { apiBase } : {},
52
+ ...props.loadTimeoutMs !== void 0 ? { loadTimeoutMs: props.loadTimeoutMs } : {},
53
+ logger: stableLogger,
54
+ onReady: () => callbacks.current.onReady?.(),
55
+ onChange: (e) => callbacks.current.onChange?.(e),
56
+ onSuccess: (e) => callbacks.current.onSuccess?.(e),
57
+ onError: (e) => callbacks.current.onError?.(e),
58
+ onRedirect: (url) => callbacks.current.onRedirect?.(url)
59
+ };
60
+ const handle = mount(containerRef.current, options);
61
+ handleRef.current = handle;
62
+ return () => {
63
+ handle.destroy();
64
+ handleRef.current = null;
65
+ };
66
+ }, [isClient, props.clientSecret, props.locale, iframeOrigin, apiBase]);
67
+ useEffect(() => {
68
+ if (themeKey && handleRef.current && props.theme) {
69
+ handleRef.current.updateTheme(props.theme);
70
+ }
71
+ }, [themeKey]);
72
+ return { isClient, containerRef };
73
+ }
74
+ function CheckoutElement(props) {
75
+ const { isClient, containerRef } = useElement(mountCheckoutElement, props);
76
+ if (!isClient) return null;
77
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, className: props.className, style: props.style });
78
+ }
79
+ function PaymentMethodElement(props) {
80
+ const { customerId } = props;
81
+ const mount = (target, options) => mountPaymentMethodElement(target, { ...options, customerId });
82
+ const { isClient, containerRef } = useElement(mount, props);
83
+ if (!isClient) return null;
84
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, className: props.className, style: props.style });
85
+ }
86
+
87
+ export { BillKitProvider, CheckoutElement, PaymentMethodElement, useBillKit };
88
+ //# sourceMappingURL=index.js.map
89
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/BillKitProvider.tsx","../src/useElement.ts","../src/CheckoutElement.tsx","../src/PaymentMethodElement.tsx"],"names":["jsx"],"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: put your public key\n * (and any origin overrides) here once, then render elements freely\n * beneath it.\n */\nexport interface BillKitContextValue {\n /**\n * @deprecated Not required, not used, and not something BillKit issues.\n *\n * BillKit has no publishable-key concept; the API only mints secret\n * keys (`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\"`,\n * and that secret already names the tenant, the mode, and the session.\n *\n * The prop was accepted (and required) in 0.1.0 by analogy with\n * `@stripe/react-stripe-js`, but nothing ever read it, and a tenant\n * following the docs went looking for a `pk_...` value that does not\n * exist. It is now optional and ignored; pass nothing. It will be\n * removed in the next major.\n */\n publishableKey?: string;\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 publishableKey,\n iframeOrigin,\n apiBase,\n logger,\n children,\n}: BillKitProviderProps): JSX.Element {\n const value = useMemo<BillKitContextValue>(\n () => ({ publishableKey, iframeOrigin, apiBase, logger }),\n [publishableKey, 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 props: ReactElementProps,\n): { isClient: boolean; containerRef: React.RefObject<HTMLDivElement> } {\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 / origins); callbacks are\n // read through a ref and theme is hot-applied in the effect below, so\n // neither belongs in this dependency list.\n }, [isClient, props.clientSecret, 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 };\n}\n","import { mountCheckoutElement } from \"@billkit-eu/js\";\nimport { type ReactElementProps, useElement } 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 */\nexport function CheckoutElement(props: CheckoutElementProps): JSX.Element | null {\n const { isClient, containerRef } = useElement(mountCheckoutElement, props);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n}\n","import { type BaseElementOptions, type BillKitElementHandle, mountPaymentMethodElement } from \"@billkit-eu/js\";\nimport { type ReactElementProps, useElement } 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 */\nexport function PaymentMethodElement(props: PaymentMethodElementProps): JSX.Element | null {\n const { customerId } = props;\n const mount = (target: HTMLElement, options: BaseElementOptions): BillKitElementHandle =>\n mountPaymentMethodElement(target, { ...options, customerId });\n const { isClient, containerRef } = useElement(mount, props);\n if (!isClient) return null;\n return <div ref={containerRef} className={props.className} style={props.style} />;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@billkit-eu/react",
3
+ "version": "0.1.0",
4
+ "description": "React bindings for BillKit's embedded checkout: <BillKitProvider> + <CheckoutElement/>, @stripe/react-stripe-js-shaped and SSR-safe.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "src",
19
+ "README.md",
20
+ "CHANGELOG.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "typecheck": "tsc --noEmit",
28
+ "lint": "eslint src tests",
29
+ "prepublishOnly": "npm run build && npm run test && npm run typecheck"
30
+ },
31
+ "engines": {
32
+ "node": ">=20"
33
+ },
34
+ "keywords": [
35
+ "billkit",
36
+ "react",
37
+ "checkout",
38
+ "payment-element",
39
+ "embedded",
40
+ "mollie",
41
+ "stripe"
42
+ ],
43
+ "author": "BillKit",
44
+ "license": "Apache-2.0",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/billkit-eu/billkit-react.git"
48
+ },
49
+ "homepage": "https://github.com/billkit-eu/billkit-react#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/billkit-eu/billkit-react/issues"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public",
55
+ "provenance": true
56
+ },
57
+ "peerDependencies": {
58
+ "@billkit-eu/js": "^0.1.0",
59
+ "react": ">=18",
60
+ "react-dom": ">=18"
61
+ },
62
+ "devDependencies": {
63
+ "@billkit-eu/js": "^0.1.0",
64
+ "@testing-library/react": "^16.1.0",
65
+ "@types/node": "^26.1.1",
66
+ "@types/react": "^18.3.12",
67
+ "@types/react-dom": "^18.3.1",
68
+ "@typescript-eslint/eslint-plugin": "^8.64.0",
69
+ "@typescript-eslint/parser": "^8.20.0",
70
+ "eslint": "^10.7.0",
71
+ "jsdom": "^25.0.1",
72
+ "react": "^18.3.1",
73
+ "react-dom": "^18.3.1",
74
+ "tsup": "^8.3.5",
75
+ "typescript": "^6.0.3",
76
+ "vitest": "^4.1.10"
77
+ },
78
+ "sideEffects": false
79
+ }
@@ -0,0 +1,88 @@
1
+ import type { BillKitElementLogger } from "@billkit-eu/js";
2
+ import { createContext, type ReactNode, useContext, useMemo } from "react";
3
+
4
+ /**
5
+ * Config shared by every BillKit element on the page. Mirrors
6
+ * `@stripe/react-stripe-js`'s `<Elements>` provider: put your public key
7
+ * (and any origin overrides) here once, then render elements freely
8
+ * beneath it.
9
+ */
10
+ export interface BillKitContextValue {
11
+ /**
12
+ * @deprecated Not required, not used, and not something BillKit issues.
13
+ *
14
+ * BillKit has no publishable-key concept; the API only mints secret
15
+ * keys (`sk_live_...` / `sk_test_...`), which must never reach a browser.
16
+ * Elements authenticate with the ephemeral `client_secret` your server
17
+ * gets from `POST /v1/checkout/sessions` with `ui_mode: "embedded"`,
18
+ * and that secret already names the tenant, the mode, and the session.
19
+ *
20
+ * The prop was accepted (and required) in 0.1.0 by analogy with
21
+ * `@stripe/react-stripe-js`, but nothing ever read it, and a tenant
22
+ * following the docs went looking for a `pk_...` value that does not
23
+ * exist. It is now optional and ignored; pass nothing. It will be
24
+ * removed in the next major.
25
+ */
26
+ publishableKey?: string;
27
+ /** Origin the element iframe is served from. Defaults to js.billkit.eu. */
28
+ iframeOrigin?: string;
29
+ /** API origin the iframe calls. Defaults to api.billkit.eu. */
30
+ apiBase?: string;
31
+ /**
32
+ * Where every element beneath this provider sends its lifecycle
33
+ * diagnostics: iframe boot, dropped `postMessage`s, refused
34
+ * redirects, load timeouts. Omitted (the default) means silence: the
35
+ * elements never write to `console` on their own.
36
+ *
37
+ * `console` works as-is. An individual element can override this with
38
+ * its own `logger` prop. The `clientSecret` is never passed to it.
39
+ */
40
+ logger?: BillKitElementLogger;
41
+ }
42
+
43
+ const BillKitContext = createContext<BillKitContextValue | null>(null);
44
+
45
+ export interface BillKitProviderProps extends BillKitContextValue {
46
+ children: ReactNode;
47
+ }
48
+
49
+ /**
50
+ * Wrap the part of your tree that renders BillKit elements.
51
+ *
52
+ * No credential is needed here; the element authenticates with the
53
+ * ephemeral `client_secret` your server minted for the session:
54
+ *
55
+ * ```tsx
56
+ * <BillKitProvider>
57
+ * <CheckoutElement clientSecret={clientSecret} onSuccess={done} />
58
+ * </BillKitProvider>
59
+ * ```
60
+ */
61
+ export function BillKitProvider({
62
+ publishableKey,
63
+ iframeOrigin,
64
+ apiBase,
65
+ logger,
66
+ children,
67
+ }: BillKitProviderProps): JSX.Element {
68
+ const value = useMemo<BillKitContextValue>(
69
+ () => ({ publishableKey, iframeOrigin, apiBase, logger }),
70
+ [publishableKey, iframeOrigin, apiBase, logger],
71
+ );
72
+ return <BillKitContext.Provider value={value}>{children}</BillKitContext.Provider>;
73
+ }
74
+
75
+ /**
76
+ * Read the nearest {@link BillKitProvider}. Throws a clear error when an
77
+ * element is rendered outside a provider, the most common integration
78
+ * mistake.
79
+ */
80
+ export function useBillKit(): BillKitContextValue {
81
+ const ctx = useContext(BillKitContext);
82
+ if (ctx === null) {
83
+ throw new Error(
84
+ "BillKit: a <CheckoutElement/> must be rendered inside a <BillKitProvider>.",
85
+ );
86
+ }
87
+ return ctx;
88
+ }
@@ -0,0 +1,25 @@
1
+ import { mountCheckoutElement } from "@billkit-eu/js";
2
+ import { type ReactElementProps, useElement } from "./useElement";
3
+
4
+ export type CheckoutElementProps = ReactElementProps;
5
+
6
+ /**
7
+ * Embedded checkout, as a React component. SSR-safe: renders `null` on the
8
+ * server and the first client render, then mounts the js.billkit.eu iframe
9
+ * after hydration.
10
+ *
11
+ * ```tsx
12
+ * <BillKitProvider>
13
+ * <CheckoutElement
14
+ * clientSecret={clientSecret}
15
+ * theme={{ colorPrimary: "#6d28d9", borderRadius: "10px" }}
16
+ * onSuccess={({ sessionId }) => router.push(`/thanks?cs=${sessionId}`)}
17
+ * />
18
+ * </BillKitProvider>
19
+ * ```
20
+ */
21
+ export function CheckoutElement(props: CheckoutElementProps): JSX.Element | null {
22
+ const { isClient, containerRef } = useElement(mountCheckoutElement, props);
23
+ if (!isClient) return null;
24
+ return <div ref={containerRef} className={props.className} style={props.style} />;
25
+ }
@@ -0,0 +1,20 @@
1
+ import { type BaseElementOptions, type BillKitElementHandle, mountPaymentMethodElement } from "@billkit-eu/js";
2
+ import { type ReactElementProps, useElement } from "./useElement";
3
+
4
+ export interface PaymentMethodElementProps extends ReactElementProps {
5
+ /** The customer whose saved payment methods to render + manage. */
6
+ customerId: string;
7
+ }
8
+
9
+ /**
10
+ * The customer's saved payment methods ("Visa •••• 4242 · Update"), as a
11
+ * React component. Same SSR-safe mounting as {@link CheckoutElement}.
12
+ */
13
+ export function PaymentMethodElement(props: PaymentMethodElementProps): JSX.Element | null {
14
+ const { customerId } = props;
15
+ const mount = (target: HTMLElement, options: BaseElementOptions): BillKitElementHandle =>
16
+ mountPaymentMethodElement(target, { ...options, customerId });
17
+ const { isClient, containerRef } = useElement(mount, props);
18
+ if (!isClient) return null;
19
+ return <div ref={containerRef} className={props.className} style={props.style} />;
20
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `@billkit-eu/react`: React bindings for BillKit's embedded checkout.
3
+ *
4
+ * `@stripe/react-stripe-js`-shaped: wrap your tree in a
5
+ * Wrap your tree in `<BillKitProvider>` and drop in `<CheckoutElement/>`.
6
+ * SSR-safe: the elements render `null` on the server and mount the
7
+ * js.billkit.eu iframe after hydration.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+
12
+ export {
13
+ BillKitProvider,
14
+ type BillKitContextValue,
15
+ type BillKitProviderProps,
16
+ useBillKit,
17
+ } from "./BillKitProvider";
18
+ export { CheckoutElement, type CheckoutElementProps } from "./CheckoutElement";
19
+ export {
20
+ PaymentMethodElement,
21
+ type PaymentMethodElementProps,
22
+ } from "./PaymentMethodElement";
23
+ export type { ReactElementProps } from "./useElement";
24
+ // Re-export the loader's shared types so consumers import from one place.
25
+ export type {
26
+ BillKitElementError,
27
+ BillKitElementHandle,
28
+ BillKitElementLogger,
29
+ BillKitThemeTokens,
30
+ ChangeEvent,
31
+ ElementLogContext,
32
+ SuccessEvent,
33
+ } from "@billkit-eu/js";
@@ -0,0 +1,117 @@
1
+ import type {
2
+ BaseElementOptions,
3
+ BillKitElementError,
4
+ BillKitElementHandle,
5
+ BillKitElementLogger,
6
+ BillKitThemeTokens,
7
+ ChangeEvent,
8
+ SuccessEvent,
9
+ } from "@billkit-eu/js";
10
+ import { useEffect, useRef, useState } from "react";
11
+ import { useBillKit } from "./BillKitProvider";
12
+
13
+ /** Callback + presentational props common to the React element components. */
14
+ export interface ReactElementProps {
15
+ /** Ephemeral checkout `client_secret` (`<sessionId>_secret_...`). */
16
+ clientSecret: string;
17
+ theme?: BillKitThemeTokens;
18
+ locale?: string;
19
+ /** Load-watchdog timeout (ms) before `onError({code:"load_timeout"})`. */
20
+ loadTimeoutMs?: number;
21
+ /**
22
+ * Overrides the provider's `logger` for this element only. Omitted,
23
+ * the provider's is used; with neither, the element is silent.
24
+ */
25
+ logger?: BillKitElementLogger;
26
+ /** Applied to the container `<div>` the iframe mounts into. */
27
+ className?: string;
28
+ style?: React.CSSProperties;
29
+ onReady?: () => void;
30
+ onChange?: (event: ChangeEvent) => void;
31
+ onSuccess?: (event: SuccessEvent) => void;
32
+ onError?: (error: BillKitElementError) => void;
33
+ onRedirect?: (url: string) => boolean | void;
34
+ }
35
+
36
+ type MountFn = (
37
+ target: HTMLElement,
38
+ options: BaseElementOptions,
39
+ ) => BillKitElementHandle;
40
+
41
+ /**
42
+ * SSR-safe mount hook shared by `<CheckoutElement/>` and
43
+ * `<PaymentMethodElement/>`.
44
+ *
45
+ * - Returns `isClient = false` on the server and on the first client
46
+ * render, so the component renders `null` and hydration matches. The
47
+ * `useEffect` then flips it true and the real mount happens, with no
48
+ * hydration mismatch, no `window` access during render.
49
+ * - Callbacks are read through a ref, so a parent passing fresh closures
50
+ * every render never forces a costly iframe remount. Only the identity
51
+ * inputs (secret, origins) remount; theme changes hot-update in place.
52
+ */
53
+ export function useElement(
54
+ mount: MountFn,
55
+ props: ReactElementProps,
56
+ ): { isClient: boolean; containerRef: React.RefObject<HTMLDivElement> } {
57
+ const { iframeOrigin, apiBase, logger: providerLogger } = useBillKit();
58
+ const [isClient, setIsClient] = useState(false);
59
+ const containerRef = useRef<HTMLDivElement>(null);
60
+ const handleRef = useRef<BillKitElementHandle | null>(null);
61
+
62
+ // Latest callbacks, read at event time and decoupled from remount inputs.
63
+ const callbacks = useRef(props);
64
+ callbacks.current = props;
65
+
66
+ // Same trick for the logger: resolve it at call time through a ref so
67
+ // an inline `logger={{...}}` (or switching one on mid-session) never
68
+ // tears down and rebuilds the payment iframe. The element always
69
+ // receives this one stable object; it forwards to whatever is current,
70
+ // or drops the line when neither prop nor provider supplies one.
71
+ const activeLogger = useRef<BillKitElementLogger | undefined>(undefined);
72
+ activeLogger.current = props.logger ?? providerLogger;
73
+ const stableLogger = useRef<BillKitElementLogger>({
74
+ debug: (message, context) => activeLogger.current?.debug(message, context),
75
+ warn: (message, context) => activeLogger.current?.warn(message, context),
76
+ }).current;
77
+
78
+ useEffect(() => setIsClient(true), []);
79
+
80
+ const themeKey = props.theme ? JSON.stringify(props.theme) : "";
81
+
82
+ useEffect(() => {
83
+ if (!isClient || containerRef.current === null) return;
84
+ const options: BaseElementOptions = {
85
+ clientSecret: props.clientSecret,
86
+ ...(props.theme ? { theme: props.theme } : {}),
87
+ ...(props.locale ? { locale: props.locale } : {}),
88
+ ...(iframeOrigin ? { iframeOrigin } : {}),
89
+ ...(apiBase ? { apiBase } : {}),
90
+ ...(props.loadTimeoutMs !== undefined ? { loadTimeoutMs: props.loadTimeoutMs } : {}),
91
+ logger: stableLogger,
92
+ onReady: () => callbacks.current.onReady?.(),
93
+ onChange: (e) => callbacks.current.onChange?.(e),
94
+ onSuccess: (e) => callbacks.current.onSuccess?.(e),
95
+ onError: (e) => callbacks.current.onError?.(e),
96
+ onRedirect: (url) => callbacks.current.onRedirect?.(url),
97
+ };
98
+ const handle = mount(containerRef.current, options);
99
+ handleRef.current = handle;
100
+ return () => {
101
+ handle.destroy();
102
+ handleRef.current = null;
103
+ };
104
+ // Remount only on identity inputs (secret / origins); callbacks are
105
+ // read through a ref and theme is hot-applied in the effect below, so
106
+ // neither belongs in this dependency list.
107
+ }, [isClient, props.clientSecret, props.locale, iframeOrigin, apiBase]);
108
+
109
+ // Hot-apply theme changes without tearing down the iframe.
110
+ useEffect(() => {
111
+ if (themeKey && handleRef.current && props.theme) {
112
+ handleRef.current.updateTheme(props.theme);
113
+ }
114
+ }, [themeKey]);
115
+
116
+ return { isClient, containerRef };
117
+ }