@dekin_dev/react-checkout 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/LICENSE +21 -0
- package/README.md +332 -0
- package/dist/index.d.ts +253 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +447 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Veqta Space
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# @dekin_dev/react-checkout
|
|
2
|
+
|
|
3
|
+
A lightweight, provider-agnostic React modal for embedding a **gateway-hosted checkout page** in an iframe â built for Hubtel, Moolre, PaySwitch, and any other payment gateway with a hosted checkout URL.
|
|
4
|
+
|
|
5
|
+
> **This is a community/third-party tool. It is not an official Hubtel, Moolre, or PaySwitch SDK**, and it is not affiliated with or endorsed by any of them.
|
|
6
|
+
|
|
7
|
+
This package does **not** implement any payment gateway's API. You obtain a checkout URL from your gateway's own server-side integration (using your account credentials on your backend), and this package gives you a polished, accessible, responsive modal to display it in â so you stop rebuilding the same iframe modal for every project.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Checkout } from "@dekin_dev/react-checkout";
|
|
11
|
+
import "@dekin_dev/react-checkout/styles.css";
|
|
12
|
+
|
|
13
|
+
<Checkout
|
|
14
|
+
provider="hubtel"
|
|
15
|
+
checkoutUrl={checkoutUrl}
|
|
16
|
+
open={open}
|
|
17
|
+
onClose={() => setOpen(false)}
|
|
18
|
+
/>;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- ðŠķ Lightweight â ~4KB gzipped JS, ~1.3KB gzipped CSS, React as the only peer dependency
|
|
22
|
+
- ð§Đ Provider-agnostic core â built-in presets for Hubtel/Moolre/PaySwitch, plus a fully custom provider shape for anything else
|
|
23
|
+
- ðĻ Works with plain CSS out of the box, and just as well with Tailwind utility classes
|
|
24
|
+
- âŋ Accessible â `role="dialog"`, focus trapping/restoration, Escape to close, keyboard support
|
|
25
|
+
- ðą Mobile-first â goes full-screen under 640px so mobile money/OTP/card flows have room to breathe
|
|
26
|
+
- ð Secure by default â URL validation, strict `postMessage` origin checks, no gateway credentials anywhere near the frontend
|
|
27
|
+
- ðĨïļ SSR-safe â no `window`/`document` access outside effects; works in Next.js, Remix, CRA, plain Vite
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Table of contents
|
|
32
|
+
|
|
33
|
+
- [Installation](#installation)
|
|
34
|
+
- [Quick start](#quick-start)
|
|
35
|
+
- [Provider guides](#provider-guides)
|
|
36
|
+
- [Custom providers](#custom-providers)
|
|
37
|
+
- [Payment success handling â read this](#payment-success-handling--read-this)
|
|
38
|
+
- [Styling â plain CSS or Tailwind](#styling--plain-css-or-tailwind)
|
|
39
|
+
- [`useCheckout()` hook](#usecheckout-hook)
|
|
40
|
+
- [`CheckoutButton`](#checkoutbutton)
|
|
41
|
+
- [Props reference](#props-reference)
|
|
42
|
+
- [Accessibility](#accessibility)
|
|
43
|
+
- [SSR](#ssr)
|
|
44
|
+
- [Security notes](#security-notes)
|
|
45
|
+
- [Gateway research notes](#gateway-research-notes)
|
|
46
|
+
- [Roadmap](#roadmap)
|
|
47
|
+
- [Contributing](#contributing)
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install @dekin_dev/react-checkout
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
React 18+ (and React 19) are peer dependencies â install them yourself if you haven't already. Nothing else is required; you do **not** need Tailwind.
|
|
56
|
+
|
|
57
|
+
## Quick start
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { useState } from "react";
|
|
61
|
+
import { Checkout } from "@dekin_dev/react-checkout";
|
|
62
|
+
import "@dekin_dev/react-checkout/styles.css";
|
|
63
|
+
|
|
64
|
+
function PaymentPage() {
|
|
65
|
+
const [open, setOpen] = useState(false);
|
|
66
|
+
const [checkoutUrl, setCheckoutUrl] = useState<string | null>(null);
|
|
67
|
+
|
|
68
|
+
async function handlePay() {
|
|
69
|
+
// Your backend calls the gateway's API and returns a checkout URL.
|
|
70
|
+
const res = await fetch("/api/create-checkout", { method: "POST" });
|
|
71
|
+
const { checkoutUrl } = await res.json();
|
|
72
|
+
setCheckoutUrl(checkoutUrl);
|
|
73
|
+
setOpen(true);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
<button onClick={handlePay}>Pay now</button>
|
|
79
|
+
{checkoutUrl && (
|
|
80
|
+
<Checkout
|
|
81
|
+
provider="hubtel"
|
|
82
|
+
checkoutUrl={checkoutUrl}
|
|
83
|
+
open={open}
|
|
84
|
+
onClose={() => setOpen(false)}
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
87
|
+
</>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Never call a gateway's API directly from the browser with a secret key â `checkoutUrl` must come from your own backend.
|
|
93
|
+
|
|
94
|
+
## Provider guides
|
|
95
|
+
|
|
96
|
+
For all three built-in providers, the pattern is the same: **your backend creates the checkout URL using that gateway's own API and your account credentials; this component only displays it.**
|
|
97
|
+
|
|
98
|
+
### Hubtel
|
|
99
|
+
|
|
100
|
+
Your backend integrates with Hubtel's Online Checkout API (their `payproxyapi.hubtel.com` initiate endpoint) to create a checkout URL, configuring:
|
|
101
|
+
|
|
102
|
+
- a `callbackUrl` â a server-to-server webhook your backend exposes to receive the final payment status
|
|
103
|
+
- a `returnUrl` / `cancellationUrl` â where the customer's browser is redirected after paying/cancelling
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<Checkout
|
|
107
|
+
provider="hubtel"
|
|
108
|
+
checkoutUrl={checkoutUrl}
|
|
109
|
+
open={open}
|
|
110
|
+
onClose={() => setOpen(false)}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Hubtel's documented flow is redirect + webhook based, not a `postMessage` protocol for a plain embedded URL â see [Gateway research notes](#gateway-research-notes).
|
|
115
|
+
|
|
116
|
+
### Moolre
|
|
117
|
+
|
|
118
|
+
Your backend uses Moolre's Collections/Checkout API to generate a hosted checkout URL, then passes it straight through:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
<Checkout
|
|
122
|
+
provider="moolre"
|
|
123
|
+
checkoutUrl={checkoutUrl}
|
|
124
|
+
open={open}
|
|
125
|
+
onClose={() => setOpen(false)}
|
|
126
|
+
/>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### PaySwitch
|
|
130
|
+
|
|
131
|
+
Your backend uses PaySwitch's API (PayLink or TheTeller) to obtain a checkout/payment-link URL:
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
<Checkout
|
|
135
|
+
provider="payswitch"
|
|
136
|
+
checkoutUrl={checkoutUrl}
|
|
137
|
+
open={open}
|
|
138
|
+
onClose={() => setOpen(false)}
|
|
139
|
+
/>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Custom providers
|
|
143
|
+
|
|
144
|
+
Not using Hubtel/Moolre/PaySwitch, or want different defaults? Pass an inline provider config instead of a string:
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<Checkout
|
|
148
|
+
provider={{
|
|
149
|
+
name: "my-gateway",
|
|
150
|
+
allowedOrigins: ["https://checkout.mygateway.com"],
|
|
151
|
+
defaultTitle: "Complete your payment",
|
|
152
|
+
}}
|
|
153
|
+
checkoutUrl={checkoutUrl}
|
|
154
|
+
open={open}
|
|
155
|
+
onClose={() => setOpen(false)}
|
|
156
|
+
/>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
If you control the checkout page yourself and want it to talk back to the parent window, add a `messageHandler` to normalize its `postMessage` events:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
provider={{
|
|
163
|
+
name: "my-gateway",
|
|
164
|
+
allowedOrigins: ["https://checkout.mygateway.com"],
|
|
165
|
+
messageHandler: (event) => {
|
|
166
|
+
if (event.data?.kind !== "payment-event") return null;
|
|
167
|
+
return { type: event.data.status, provider: "my-gateway", origin: event.origin };
|
|
168
|
+
},
|
|
169
|
+
}}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Messages are only ever accepted from origins you list in `allowedOrigins` â everything else is dropped before your `messageHandler` ever sees it.
|
|
173
|
+
|
|
174
|
+
## Payment success handling â read this
|
|
175
|
+
|
|
176
|
+
**This component's frontend events (including `onSuccess`) must never be treated as final proof of payment.** A modal closing, or a message arriving, only means something happened in the browser â it says nothing about whether the payment actually settled.
|
|
177
|
+
|
|
178
|
+
The right architecture:
|
|
179
|
+
|
|
180
|
+
```text
|
|
181
|
+
Checkout UI (this package)
|
|
182
|
+
â
|
|
183
|
+
âž
|
|
184
|
+
Gateway
|
|
185
|
+
â
|
|
186
|
+
âž
|
|
187
|
+
Gateway webhook ââââââââââââââķ Merchant backend
|
|
188
|
+
â
|
|
189
|
+
âž
|
|
190
|
+
Verify with the gateway's API
|
|
191
|
+
â
|
|
192
|
+
âž
|
|
193
|
+
Update the order, notify the frontend
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Concretely:
|
|
197
|
+
|
|
198
|
+
- None of the built-in providers (Hubtel, Moolre, PaySwitch) have a **documented** `postMessage` protocol for a plain embedded checkout URL, so `onSuccess`/`onFailure`/`onCancel` never fire for them today â this package does not invent gateway behavior that isn't verified. Use `onClose` to prompt the user, then poll or subscribe for your backend's own confirmation (which it gets from the gateway's webhook).
|
|
199
|
+
- For a `custom` provider where you control the checkout page and its `postMessage` contract, `onSuccess`/`onFailure`/`onCancel` fire based on your own `messageHandler` â but still verify server-side before fulfilling an order. A compromised or buggy checkout page could otherwise "confirm" a payment that never happened.
|
|
200
|
+
|
|
201
|
+
## Styling â plain CSS or Tailwind
|
|
202
|
+
|
|
203
|
+
Import the stylesheet once:
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
import "@dekin_dev/react-checkout/styles.css";
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Every class is namespaced (`.vq-checkout-*`), so it won't collide with your app's CSS or a Tailwind `preflight` reset, and colors/spacing are exposed as CSS variables on `.vq-checkout-root` for easy re-theming:
|
|
210
|
+
|
|
211
|
+
```css
|
|
212
|
+
.vq-checkout-root {
|
|
213
|
+
--vq-radius: 8px;
|
|
214
|
+
--vq-focus-ring: #16a34a;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Every visual piece also accepts a `className` so you can layer on **Tailwind utilities** exactly like any other class list â no extra configuration needed:
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
<Checkout
|
|
222
|
+
checkoutUrl={checkoutUrl}
|
|
223
|
+
open={open}
|
|
224
|
+
onClose={() => setOpen(false)}
|
|
225
|
+
className="rounded-3xl ring-4 ring-indigo-200"
|
|
226
|
+
overlayClassName="backdrop-blur-sm"
|
|
227
|
+
/>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`theme="light" | "dark" | "auto"` switches the built-in palette; `"auto"` follows `prefers-color-scheme`.
|
|
231
|
+
|
|
232
|
+
## `useCheckout()` hook
|
|
233
|
+
|
|
234
|
+
An imperative alternative for when you don't want to manage `open` state yourself:
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
import { useCheckout } from "@dekin_dev/react-checkout";
|
|
238
|
+
|
|
239
|
+
function Payment() {
|
|
240
|
+
const { openCheckout, CheckoutModal } = useCheckout();
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<>
|
|
244
|
+
<button onClick={() => openCheckout({ provider: "hubtel", checkoutUrl })}>Pay now</button>
|
|
245
|
+
<CheckoutModal />
|
|
246
|
+
</>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## `CheckoutButton`
|
|
252
|
+
|
|
253
|
+
For the simplest possible integration â a trigger button and the modal in one component:
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { CheckoutButton } from "@dekin_dev/react-checkout";
|
|
257
|
+
|
|
258
|
+
<CheckoutButton provider="hubtel" checkoutUrl={checkoutUrl}>
|
|
259
|
+
Pay with Hubtel
|
|
260
|
+
</CheckoutButton>;
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Props reference
|
|
264
|
+
|
|
265
|
+
The primary component is `<Checkout />`:
|
|
266
|
+
|
|
267
|
+
| Prop | Type | Default | Notes |
|
|
268
|
+
| --- | --- | --- | --- |
|
|
269
|
+
| `open` | `boolean` | â | required |
|
|
270
|
+
| `checkoutUrl` | `string` | â | required; validated before use |
|
|
271
|
+
| `provider` | `"hubtel" \| "moolre" \| "payswitch" \| "custom" \| CheckoutProviderConfig` | `"custom"` | |
|
|
272
|
+
| `providerConfig` | `Partial<CheckoutProviderConfig>` | â | overrides merged over the preset |
|
|
273
|
+
| `onOpen` / `onClose` / `onLoad` / `onError` | functions | â | lifecycle callbacks |
|
|
274
|
+
| `onMessage` / `onSuccess` / `onFailure` / `onCancel` | functions | â | only fire for origin-validated, provider-normalized messages |
|
|
275
|
+
| `title` / `description` | `string` | provider default / â | |
|
|
276
|
+
| `showCloseButton` | `boolean` | `true` | |
|
|
277
|
+
| `closeOnOverlayClick` | `boolean` | `true` | |
|
|
278
|
+
| `closeOnEscape` | `boolean` | `true` | |
|
|
279
|
+
| `width` / `height` | `string \| number` | `min(95vw,500px)` / `min(90vh,750px)` | any valid CSS size |
|
|
280
|
+
| `maxWidth` / `maxHeight` | `string \| number` | `500px` / `750px` | |
|
|
281
|
+
| `className` / `overlayClassName` / `iframeClassName` | `string` | â | |
|
|
282
|
+
| `theme` | `"light" \| "dark" \| "auto"` | `"light"` | |
|
|
283
|
+
| `zIndex` | `number` | â | |
|
|
284
|
+
| `allow` | `string` | `"payment *"` | iframe `allow` attribute |
|
|
285
|
+
| `sandbox` | `string` | unset | iframe `sandbox` attribute â see note below |
|
|
286
|
+
| `loadingComponent` / `errorComponent` | `ReactNode` \| function | built-in views | |
|
|
287
|
+
| `allowInsecureHttp` | `boolean` | `false` | dev-only override for non-HTTPS URLs |
|
|
288
|
+
|
|
289
|
+
`sandbox` is left unset by default because an overly restrictive sandbox (e.g. missing `allow-forms` or `allow-popups` for a bank redirect) can silently break a gateway's checkout flow. Only set it if you've confirmed your gateway's checkout page works within the restrictions you choose.
|
|
290
|
+
|
|
291
|
+
All exported types (`CheckoutProps`, `CheckoutProvider`, `CheckoutProviderConfig`, `CheckoutError`, `CheckoutMessage`, `CheckoutTheme`) and the `validateCheckoutUrl` utility are available from the package root.
|
|
292
|
+
|
|
293
|
+
## Accessibility
|
|
294
|
+
|
|
295
|
+
- `role="dialog"` + `aria-modal="true"`, labeled by the title (or an `aria-label` fallback)
|
|
296
|
+
- Focus moves into the modal on open, is trapped there with Tab/Shift+Tab, and returns to the previously focused element on close
|
|
297
|
+
- Escape closes the modal (configurable)
|
|
298
|
+
- Background scroll is locked while open and restored on close
|
|
299
|
+
|
|
300
|
+
## SSR
|
|
301
|
+
|
|
302
|
+
The package never touches `window`/`document` at module scope â only inside effects and event handlers â so importing it is safe under Next.js, Remix, or any other SSR framework. The modal itself renders via a portal and only mounts client-side.
|
|
303
|
+
|
|
304
|
+
## Security notes
|
|
305
|
+
|
|
306
|
+
- `checkoutUrl` is validated before it's ever placed in an iframe: it must parse as a URL, must not use `javascript:`/`data:`/`vbscript:`/`file:`/`blob:`, and must be HTTPS unless you explicitly pass `allowInsecureHttp` (intended for local development only).
|
|
307
|
+
- Incoming `postMessage` events are checked against a provider's `allowedOrigins` before anything else happens. If a provider declares no allow-list and doesn't explicitly set `supportsPostMessage: true`, every message is dropped â an unconfigured provider is trusted with nothing by default.
|
|
308
|
+
- No built-in provider ships a `messageHandler` that fabricates a "success" message â see [Payment success handling](#payment-success-handling--read-this).
|
|
309
|
+
- Never put a payment gateway's secret/API key in frontend code. This package never asks for one.
|
|
310
|
+
|
|
311
|
+
## Gateway research notes
|
|
312
|
+
|
|
313
|
+
Verified from Hubtel's public API reference: their checkout-URL flow is redirect + server-webhook based (`callbackUrl`, `returnUrl`, `cancellationUrl` parameters on the checkout initiation endpoint). No documented `postMessage` protocol exists for a checkout URL embedded via a plain iframe (their separate `@hubteljs/checkout` SDK manages its own iframe and bridge internally â a different integration path from "embed a URL you already have").
|
|
314
|
+
|
|
315
|
+
**Empirically checked (2026-09-11)** against a live `pay.hubtel.com` checkout link: the response carries no `X-Frame-Options` header and its `Content-Security-Policy` has no `frame-ancestors` directive, so nothing at the HTTP level blocks embedding it in an iframe. The initial HTML also shows no obvious frame-busting script. This is a live-server observation, not a documented commitment from Hubtel â the checkout app is a client-rendered SPA (Nuxt) and its bundled JS wasn't fully audited for frame-busting, and Hubtel could add blocking headers at any time without notice. Treat iframe embedding of Hubtel's checkout as "currently unblocked," not "officially supported."
|
|
316
|
+
|
|
317
|
+
Moolre's and PaySwitch's public documentation did not yield a confirmable `postMessage` protocol, `X-Frame-Options`/CSP frame policy, or iframe embedding guidance at the time this package was written. Rather than guess, `supportsPostMessage` is left `false` for all three built-in providers, and no provider-specific success/failure detection is implemented. If you have first-hand, documented knowledge of any of these gateways' embedding or messaging behavior, contributions with citations are very welcome.
|
|
318
|
+
|
|
319
|
+
## Roadmap
|
|
320
|
+
|
|
321
|
+
- `0.1.x` â core iframe modal (this release)
|
|
322
|
+
- `0.2.x` â provider adapters and improved events, as gateway behavior gets verified
|
|
323
|
+
- `0.3.x` â hook and `CheckoutButton` refinements
|
|
324
|
+
- `1.0.0` â stable public API
|
|
325
|
+
|
|
326
|
+
## Contributing
|
|
327
|
+
|
|
328
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
329
|
+
|
|
330
|
+
## License
|
|
331
|
+
|
|
332
|
+
MIT â see [LICENSE](./LICENSE).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { ButtonHTMLAttributes } from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A modal/iframe checkout for embedding a gateway-hosted checkout page.
|
|
6
|
+
*
|
|
7
|
+
* `checkoutUrl` must come from your backend's own integration with the
|
|
8
|
+
* payment provider's API â this component never talks to a gateway
|
|
9
|
+
* directly and never needs API keys. See the README for why frontend
|
|
10
|
+
* events here (including `onSuccess`) must not be treated as final proof
|
|
11
|
+
* of payment.
|
|
12
|
+
*/
|
|
13
|
+
export declare function Checkout({ open, checkoutUrl, provider, providerConfig: providerConfigOverrides, onClose, onOpen, onLoad, onError, onMessage, onSuccess, onFailure, onCancel, title, description, showCloseButton, closeOnOverlayClick, closeOnEscape, width, height, maxWidth, maxHeight, className, iframeClassName, overlayClassName, theme, zIndex, allow, sandbox, loadingComponent, errorComponent, allowInsecureHttp, id, children, }: CheckoutProps): JSX.Element | null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convenience wrapper that owns its own `open` state: renders a trigger
|
|
17
|
+
* button and the {@link Checkout} modal together, for when you don't need
|
|
18
|
+
* to control the open state yourself.
|
|
19
|
+
*/
|
|
20
|
+
export declare function CheckoutButton({ children, buttonClassName, buttonProps, onClose, ...checkoutProps }: CheckoutButtonProps): JSX.Element;
|
|
21
|
+
|
|
22
|
+
export declare interface CheckoutButtonProps extends Omit<CheckoutProps, "open"> {
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
buttonClassName?: string;
|
|
25
|
+
buttonProps?: Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onClick" | "type" | "className">;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export declare interface CheckoutError {
|
|
29
|
+
code: CheckoutErrorCode;
|
|
30
|
+
message: string;
|
|
31
|
+
provider?: string;
|
|
32
|
+
originalError?: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export declare type CheckoutErrorCode = "INVALID_URL" | "IFRAME_LOAD_ERROR" | "PROVIDER_ERROR" | "MESSAGE_ERROR" | "UNKNOWN";
|
|
36
|
+
|
|
37
|
+
/** A normalized, origin-validated message received from the checkout iframe. */
|
|
38
|
+
export declare interface CheckoutMessage<TPayload = unknown> {
|
|
39
|
+
type: string;
|
|
40
|
+
provider?: string;
|
|
41
|
+
payload?: TPayload;
|
|
42
|
+
origin: string;
|
|
43
|
+
raw?: MessageEvent;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export declare interface CheckoutProps {
|
|
47
|
+
/** Controls whether the modal is rendered/visible. */
|
|
48
|
+
open: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The gateway-hosted checkout URL, obtained from your backend's
|
|
51
|
+
* integration with the payment provider's API. Never put secret API keys
|
|
52
|
+
* in the frontend to construct this URL yourself.
|
|
53
|
+
*/
|
|
54
|
+
checkoutUrl: string;
|
|
55
|
+
/** Provider preset name, or an inline {@link CheckoutProviderConfig}. */
|
|
56
|
+
provider?: CheckoutProvider;
|
|
57
|
+
/** Overrides merged on top of the resolved provider preset. */
|
|
58
|
+
providerConfig?: Partial<CheckoutProviderConfig>;
|
|
59
|
+
onClose?: () => void;
|
|
60
|
+
onOpen?: () => void;
|
|
61
|
+
onLoad?: () => void;
|
|
62
|
+
onError?: (error: CheckoutError) => void;
|
|
63
|
+
/** Fires for every origin-validated, normalized message from the iframe. */
|
|
64
|
+
onMessage?: (message: CheckoutMessage) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Fires only when a provider's `messageHandler` normalizes a message to
|
|
67
|
+
* type `"success"`. No built-in provider does this today â see
|
|
68
|
+
* `src/providers/*.ts`. Never treat this as final proof of payment;
|
|
69
|
+
* verify with your backend.
|
|
70
|
+
*/
|
|
71
|
+
onSuccess?: (message: CheckoutMessage) => void;
|
|
72
|
+
/** Fires only when a provider normalizes a message to type `"failure"`. */
|
|
73
|
+
onFailure?: (message: CheckoutMessage) => void;
|
|
74
|
+
/** Fires only when a provider normalizes a message to type `"cancel"`. */
|
|
75
|
+
onCancel?: (message: CheckoutMessage) => void;
|
|
76
|
+
title?: string;
|
|
77
|
+
description?: string;
|
|
78
|
+
showCloseButton?: boolean;
|
|
79
|
+
closeOnOverlayClick?: boolean;
|
|
80
|
+
closeOnEscape?: boolean;
|
|
81
|
+
width?: string | number;
|
|
82
|
+
height?: string | number;
|
|
83
|
+
maxWidth?: string | number;
|
|
84
|
+
maxHeight?: string | number;
|
|
85
|
+
className?: string;
|
|
86
|
+
iframeClassName?: string;
|
|
87
|
+
overlayClassName?: string;
|
|
88
|
+
theme?: CheckoutTheme;
|
|
89
|
+
zIndex?: number;
|
|
90
|
+
/** Value for the iframe's `allow` attribute. Defaults to `"payment *"`. */
|
|
91
|
+
allow?: string;
|
|
92
|
+
/** Value for the iframe's `sandbox` attribute. Unset by default. */
|
|
93
|
+
sandbox?: string;
|
|
94
|
+
loadingComponent?: ReactNode;
|
|
95
|
+
errorComponent?: ReactNode | ((error: CheckoutError) => ReactNode);
|
|
96
|
+
/**
|
|
97
|
+
* Allow non-HTTPS checkout URLs. Intended for local development only â
|
|
98
|
+
* never enable this in production.
|
|
99
|
+
*/
|
|
100
|
+
allowInsecureHttp?: boolean;
|
|
101
|
+
/** Accessible id used to associate the dialog with its title. */
|
|
102
|
+
id?: string;
|
|
103
|
+
children?: ReactNode;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** A provider preset name, or a full inline provider configuration. */
|
|
107
|
+
export declare type CheckoutProvider = CheckoutProviderName | CheckoutProviderConfig;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Gateway-specific behavior. Built-in presets (hubtel/moolre/payswitch) fill
|
|
111
|
+
* this in with only what is actually documented by that gateway â see
|
|
112
|
+
* `src/providers/*.ts` for citations and known-unknowns.
|
|
113
|
+
*/
|
|
114
|
+
export declare interface CheckoutProviderConfig {
|
|
115
|
+
/** Identifier used in error/message payloads and default titles. */
|
|
116
|
+
name: string;
|
|
117
|
+
/**
|
|
118
|
+
* Origins allowed to send this provider's iframe `postMessage` events.
|
|
119
|
+
* When empty/undefined, incoming messages are ignored unless
|
|
120
|
+
* `supportsPostMessage` is explicitly true.
|
|
121
|
+
*/
|
|
122
|
+
allowedOrigins?: string[];
|
|
123
|
+
defaultTitle?: string;
|
|
124
|
+
defaultWidth?: string | number;
|
|
125
|
+
defaultHeight?: string | number;
|
|
126
|
+
/**
|
|
127
|
+
* Whether this gateway has a documented postMessage protocol for a
|
|
128
|
+
* generically embedded checkout URL. Defaults to `false` for all built-in
|
|
129
|
+
* providers because none currently publish one â see provider source
|
|
130
|
+
* files. Do not flip this to `true` without a verified source.
|
|
131
|
+
*/
|
|
132
|
+
supportsPostMessage?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Normalizes a raw `MessageEvent` into a {@link CheckoutMessage}, or
|
|
135
|
+
* returns `null` to ignore it. Only called after origin validation.
|
|
136
|
+
*/
|
|
137
|
+
messageHandler?: (event: MessageEvent) => CheckoutMessage | null;
|
|
138
|
+
/** Value for the iframe's `allow` attribute. */
|
|
139
|
+
iframeAllow?: string;
|
|
140
|
+
/** Value for the iframe's `sandbox` attribute, if any. */
|
|
141
|
+
iframeSandbox?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Built-in provider identifiers. These are convenience presets only â
|
|
146
|
+
* `provider` also accepts a full {@link CheckoutProviderConfig} object for
|
|
147
|
+
* gateways that aren't built in.
|
|
148
|
+
*/
|
|
149
|
+
export declare type CheckoutProviderName = "hubtel" | "moolre" | "payswitch" | "custom";
|
|
150
|
+
|
|
151
|
+
export declare type CheckoutTheme = "light" | "dark" | "auto";
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Fallback used for `provider="custom"` with no inline config, and as the
|
|
155
|
+
* base merged under any user-supplied {@link CheckoutProviderConfig}.
|
|
156
|
+
* Intentionally trusts nothing by default: no allowed origins and
|
|
157
|
+
* `supportsPostMessage: false`, so postMessage events are ignored unless the
|
|
158
|
+
* developer explicitly configures otherwise.
|
|
159
|
+
*/
|
|
160
|
+
export declare const customProviderDefaults: CheckoutProviderConfig;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Hubtel â Online Checkout.
|
|
164
|
+
*
|
|
165
|
+
* Verified (from Hubtel's public API reference for the checkout-URL
|
|
166
|
+
* initiation endpoint): the checkout URL flow is redirect + server webhook
|
|
167
|
+
* based. The merchant backend calls Hubtel's initiate endpoint with a
|
|
168
|
+
* `callbackUrl` (server-to-server webhook) and a `returnUrl` /
|
|
169
|
+
* `cancellationUrl` (browser redirect targets), then sends the customer to
|
|
170
|
+
* the resulting checkout URL.
|
|
171
|
+
*
|
|
172
|
+
* UNVERIFIED: Hubtel's public docs do not document a `window.postMessage`
|
|
173
|
+
* protocol for a checkout URL embedded via a plain iframe (as opposed to
|
|
174
|
+
* their separate `@hubteljs/checkout` SDK, which manages its own iframe and
|
|
175
|
+
* bridge internally and is a different integration path from "embed a URL
|
|
176
|
+
* you already have"). `supportsPostMessage` is therefore left `false` here
|
|
177
|
+
* â do not flip it on without a citation. Rely on `returnUrl`/`callbackUrl`
|
|
178
|
+
* plus backend verification instead of any frontend "success" signal.
|
|
179
|
+
*/
|
|
180
|
+
export declare const hubtelProvider: CheckoutProviderConfig;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Moolre â Checkout / Collections.
|
|
184
|
+
*
|
|
185
|
+
* UNVERIFIED: Moolre's public API docs (docs.moolre.com) were not
|
|
186
|
+
* accessible as static, readable content at the time this adapter was
|
|
187
|
+
* written, so no `postMessage` protocol, allowed origins, or iframe
|
|
188
|
+
* embedding policy could be confirmed. `supportsPostMessage` is left
|
|
189
|
+
* `false` and no `messageHandler` is provided â do not assume behavior that
|
|
190
|
+
* hasn't been verified against Moolre's own documentation. Rely on the
|
|
191
|
+
* redirect/callback URLs you configure server-side plus backend
|
|
192
|
+
* verification instead of any frontend "success" signal.
|
|
193
|
+
*/
|
|
194
|
+
export declare const moolreProvider: CheckoutProviderConfig;
|
|
195
|
+
|
|
196
|
+
export declare type OpenCheckoutOptions = Omit<CheckoutProps, "open">;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* PaySwitch â PayLink / hosted checkout.
|
|
200
|
+
*
|
|
201
|
+
* UNVERIFIED: PaySwitch's public docs describe PayLink (payment links/QR)
|
|
202
|
+
* and the TheTeller processing API, but no `postMessage` protocol, allowed
|
|
203
|
+
* origins, or iframe embedding policy for a generically embedded checkout
|
|
204
|
+
* URL could be confirmed from accessible documentation. `supportsPostMessage`
|
|
205
|
+
* is left `false` â do not assume behavior that hasn't been verified. Rely
|
|
206
|
+
* on the redirect/callback URLs you configure server-side plus backend
|
|
207
|
+
* verification instead of any frontend "success" signal.
|
|
208
|
+
*/
|
|
209
|
+
export declare const payswitchProvider: CheckoutProviderConfig;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Resolves the `provider` + `providerConfig` props into one concrete
|
|
213
|
+
* {@link CheckoutProviderConfig}. Accepts either a built-in preset name or
|
|
214
|
+
* an inline config object (for gateways that aren't built in), and merges
|
|
215
|
+
* any `providerConfig` overrides on top.
|
|
216
|
+
*/
|
|
217
|
+
export declare function resolveProviderConfig(provider: CheckoutProvider | undefined, overrides?: Partial<CheckoutProviderConfig>): CheckoutProviderConfig;
|
|
218
|
+
|
|
219
|
+
export declare interface UrlValidationOptions {
|
|
220
|
+
/** Allow `http:` URLs. Intended for local development only. */
|
|
221
|
+
allowInsecureHttp?: boolean;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export declare interface UrlValidationResult {
|
|
225
|
+
valid: boolean;
|
|
226
|
+
reason?: string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Imperative alternative to rendering `<Checkout open={...} />` yourself.
|
|
231
|
+
* Keeps the simple `<Checkout />` component as the primary API â this is an
|
|
232
|
+
* optional convenience for call-site-driven flows (e.g. "Pay Now" inside a
|
|
233
|
+
* list where mounting a modal per row would be wasteful).
|
|
234
|
+
*/
|
|
235
|
+
export declare function useCheckout(defaultOptions?: Partial<OpenCheckoutOptions>): UseCheckoutResult;
|
|
236
|
+
|
|
237
|
+
export declare interface UseCheckoutResult {
|
|
238
|
+
/** Opens the checkout modal with the given options. */
|
|
239
|
+
openCheckout: (options: OpenCheckoutOptions) => void;
|
|
240
|
+
closeCheckout: () => void;
|
|
241
|
+
isOpen: boolean;
|
|
242
|
+
/** Render this once, wherever the modal should live in the tree. */
|
|
243
|
+
CheckoutModal: () => JSX.Element | null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Validates a gateway checkout URL before it is ever placed in an iframe's
|
|
248
|
+
* `src`. Rejects non-URLs, script-injection protocols, and (by default)
|
|
249
|
+
* plain `http:` URLs.
|
|
250
|
+
*/
|
|
251
|
+
export declare function validateCheckoutUrl(url: string, options?: UrlValidationOptions): UrlValidationResult;
|
|
252
|
+
|
|
253
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("react/jsx-runtime"),n=require("react"),me=require("react-dom"),C={name:"hubtel",defaultTitle:"Complete Payment",supportsPostMessage:!1,iframeAllow:"payment *"},F={name:"moolre",defaultTitle:"Complete Payment",supportsPostMessage:!1,iframeAllow:"payment *"},K={name:"payswitch",defaultTitle:"Complete Payment",supportsPostMessage:!1,iframeAllow:"payment *"},x={name:"custom",defaultTitle:"Complete Payment",supportsPostMessage:!1},ye={hubtel:C,moolre:F,payswitch:K,custom:x};function V(e,o){let t;return e?typeof e=="string"?t=ye[e]??{...x,name:e}:t={...x,...e}:t=x,o?{...t,...o}:t}const pe=new Set(["javascript:","data:","vbscript:","file:","blob:"]);function G(e,o={}){if(!e||typeof e!="string")return{valid:!1,reason:"A checkoutUrl is required."};let t;try{t=new URL(e)}catch{return{valid:!1,reason:`"${e}" is not a valid URL.`}}return pe.has(t.protocol)?{valid:!1,reason:`Unsafe URL protocol "${t.protocol}" is not allowed.`}:t.protocol!=="https:"&&t.protocol!=="http:"?{valid:!1,reason:`Unsupported URL protocol "${t.protocol}".`}:t.protocol==="http:"&&!o.allowInsecureHttp?{valid:!1,reason:"checkoutUrl must use HTTPS. Pass allowInsecureHttp to override for local development only."}:{valid:!0}}function ve(e,o){return!o||o.length===0?!1:o.some(t=>{try{return new URL(t).origin===e}catch{return t===e}})}function ke(e,o){return function(r){if(!!e.allowedOrigins&&e.allowedOrigins.length>0){if(!ve(r.origin,e.allowedOrigins))return}else if(!e.supportsPostMessage)return;let c=null;if(e.messageHandler)try{c=e.messageHandler(r)}catch{return}else if(r.data&&typeof r.data=="object"){const s=r.data;c={type:typeof s.type=="string"?s.type:"message",provider:e.name,payload:r.data,origin:r.origin}}c&&o({...c,origin:r.origin,raw:r})}}const H=typeof window<"u"&&typeof document<"u";function N(...e){return e.filter(Boolean).join(" ")}let L=0,X="",J="";function be(){if(H){if(L===0){const e=window.innerWidth-document.documentElement.clientWidth;X=document.body.style.overflow,J=document.body.style.paddingRight,document.body.style.overflow="hidden",e>0&&(document.body.style.paddingRight=`${e}px`)}L+=1}}function ge(){H&&(L=Math.max(0,L-1),L===0&&(document.body.style.overflow=X,document.body.style.paddingRight=J))}const we=["a[href]","button:not([disabled])","textarea:not([disabled])","input:not([disabled])","select:not([disabled])","iframe",'[tabindex]:not([tabindex="-1"])'].join(",");function z(e){return Array.from(e.querySelectorAll(we))}function xe(e){return function(t){if(t.key!=="Tab")return;const r=z(e);if(r.length===0){t.preventDefault();return}const i=r[0],c=r[r.length-1],s=document.activeElement;t.shiftKey?(s===i||!e.contains(s))&&(t.preventDefault(),c.focus()):(s===c||!e.contains(s))&&(t.preventDefault(),i.focus())}}function q(e){return typeof e=="number"?`${e}px`:e}function Le({id:e,title:o,description:t,showCloseButton:r,closeOnOverlayClick:i,closeOnEscape:c,onClose:s,width:l,height:f,maxWidth:u,maxHeight:p,className:h,overlayClassName:b,theme:g,zIndex:R,children:I}){const w=n.useRef(null),j=n.useRef(null);n.useEffect(()=>{be(),j.current=document.activeElement;const d=w.current;return d&&(z(d)[0]??d).focus(),()=>{var m,T;ge(),(T=(m=j.current)==null?void 0:m.focus)==null||T.call(m)}},[]),n.useEffect(()=>{if(!c)return;function d(m){m.key==="Escape"&&(m.stopPropagation(),s==null||s())}return document.addEventListener("keydown",d),()=>document.removeEventListener("keydown",d)},[c,s]);function U(d){d.key==="Tab"&&w.current&&xe(w.current)(d.nativeEvent)}function M(d){i&&d.target===d.currentTarget&&(s==null||s())}const E=`${e}-title`,P=t?`${e}-description`:void 0;return a.jsx("div",{className:"vq-checkout-root","data-theme":g,children:a.jsx("div",{className:N("vq-checkout-overlay",b),onMouseDown:M,style:R!==void 0?{zIndex:R}:void 0,children:a.jsxs("div",{ref:w,id:e,role:"dialog","aria-modal":"true","aria-labelledby":o?E:void 0,"aria-label":o?void 0:"Checkout","aria-describedby":P,className:N("vq-checkout-modal",h),style:{width:q(l),height:q(f),maxWidth:q(u),maxHeight:q(p)},tabIndex:-1,onKeyDown:U,children:[(o||t||r)&&a.jsxs("div",{className:"vq-checkout-header",children:[a.jsxs("div",{className:"vq-checkout-heading",children:[o&&a.jsx("h2",{id:E,className:"vq-checkout-title",children:o}),t&&a.jsx("p",{id:P,className:"vq-checkout-description",children:t})]}),r&&a.jsx("button",{type:"button",className:"vq-checkout-close","aria-label":"Close checkout",onClick:()=>s==null?void 0:s(),children:a.jsx("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:a.jsx("path",{d:"M2 2L14 14M14 2L2 14",stroke:"currentColor",strokeWidth:"1.6",strokeLinecap:"round"})})})]}),a.jsx("div",{className:"vq-checkout-body",children:I})]})})})}function Re({src:e,title:o,className:t,allow:r,sandbox:i,hidden:c,onLoad:s,onError:l}){const f=n.useRef(null),u=n.useRef(s),p=n.useRef(l);return u.current=s,p.current=l,n.useEffect(()=>{const h=f.current;if(!h)return;const b=()=>u.current(),g=()=>p.current();return h.addEventListener("load",b),h.addEventListener("error",g),()=>{h.removeEventListener("load",b),h.removeEventListener("error",g)}},[e]),a.jsx("iframe",{ref:f,src:e,title:o,className:N("vq-checkout-iframe",t),allow:r,sandbox:i,style:c?{position:"absolute",width:0,height:0,opacity:0}:void 0})}function je(){return a.jsxs("div",{className:"vq-checkout-state",role:"status","aria-live":"polite",children:[a.jsx("div",{className:"vq-checkout-spinner","aria-hidden":"true"}),a.jsx("span",{children:"Loading secure checkout..."})]})}function Ee({error:e}){return a.jsxs("div",{className:"vq-checkout-state",role:"alert",children:[a.jsx("div",{className:"vq-checkout-error-icon","aria-hidden":"true",children:"!"}),a.jsx("p",{className:"vq-checkout-error-message",children:e.message})]})}const Pe="min(95vw, 500px)",Te="min(90vh, 750px)",Ae="500px",De="750px",qe="payment *";function B({open:e,checkoutUrl:o,provider:t,providerConfig:r,onClose:i,onOpen:c,onLoad:s,onError:l,onMessage:f,onSuccess:u,onFailure:p,onCancel:h,title:b,description:g,showCloseButton:R=!0,closeOnOverlayClick:I=!0,closeOnEscape:w=!0,width:j=Pe,height:U=Te,maxWidth:M=Ae,maxHeight:E=De,className:P,iframeClassName:d,overlayClassName:m,theme:T="light",zIndex:Q,allow:Y,sandbox:Z,loadingComponent:ee,errorComponent:S,allowInsecureHttp:W,id:te,children:re}){const se=n.useId(),oe=te??`vq-checkout-${se}`,[ne,ae]=n.useState(!1),[A,D]=n.useState("loading"),[_,O]=n.useState(null),$=n.useRef(!1),v=n.useMemo(()=>V(t,r),[t,JSON.stringify(r??{})]);n.useEffect(()=>{ae(!0)},[]),n.useEffect(()=>{e&&!$.current&&(c==null||c()),$.current=e},[e]),n.useEffect(()=>{if(!e)return;const k=G(o,{allowInsecureHttp:W});if(!k.valid){const y={code:"INVALID_URL",message:k.reason??"The provided checkoutUrl is invalid.",provider:v.name};D("error"),O(y),l==null||l(y);return}D("loading"),O(null)},[e,o,W]),n.useEffect(()=>{if(!e||!H)return;const k=ke(v,y=>{f==null||f(y),y.type==="success"?u==null||u(y):y.type==="failure"?p==null||p(y):y.type==="cancel"&&(h==null||h(y))});return window.addEventListener("message",k),()=>window.removeEventListener("message",k)},[e,v,f,u,p,h]);const ce=n.useCallback(()=>{D("loaded"),s==null||s()},[s]),ie=n.useCallback(()=>{const k={code:"IFRAME_LOAD_ERROR",message:"The checkout page could not be loaded.",provider:v.name};D("error"),O(k),l==null||l(k)},[l,v.name]),le=n.useCallback(()=>{i==null||i()},[i]);if(!e||!ne)return null;const ue=b??v.defaultTitle??"Checkout",de=Y??v.iframeAllow??qe,fe=Z??v.iframeSandbox,he=a.jsxs(Le,{id:oe,title:b??v.defaultTitle,description:g,showCloseButton:R,closeOnOverlayClick:I,closeOnEscape:w,onClose:le,width:j,height:U,maxWidth:M,maxHeight:E,className:P,overlayClassName:m,theme:T,zIndex:Q,children:[A==="loading"&&(ee??a.jsx(je,{})),A==="error"&&_&&(typeof S=="function"?S(_):S??a.jsx(Ee,{error:_})),A!=="error"&&a.jsx(Re,{src:o,title:ue,className:d,allow:de,sandbox:fe,hidden:A==="loading",onLoad:ce,onError:ie}),re]});return me.createPortal(he,document.body)}function Ne({children:e,buttonClassName:o,buttonProps:t,onClose:r,...i}){const[c,s]=n.useState(!1);return a.jsxs(a.Fragment,{children:[a.jsx("button",{type:"button",className:N("vq-checkout-trigger",o),onClick:()=>s(!0),...t,children:e}),a.jsx(B,{...i,open:c,onClose:()=>{s(!1),r==null||r()}})]})}function Ie(e){const[o,t]=n.useState(!1),[r,i]=n.useState(null),c=n.useRef(e);c.current=e;const s=n.useCallback(u=>{i({...c.current,...u}),t(!0)},[]),l=n.useCallback(()=>t(!1),[]),f=n.useCallback(()=>r?a.jsx(B,{...r,open:o,onClose:()=>{var u;(u=r.onClose)==null||u.call(r),t(!1)}}):null,[r,o]);return n.useMemo(()=>({openCheckout:s,closeCheckout:l,isOpen:o,CheckoutModal:f}),[s,l,o,f])}exports.Checkout=B;exports.CheckoutButton=Ne;exports.customProviderDefaults=x;exports.hubtelProvider=C;exports.moolreProvider=F;exports.payswitchProvider=K;exports.resolveProviderConfig=V;exports.useCheckout=Ie;exports.validateCheckoutUrl=G;
|
|
2
|
+
//# sourceMappingURL=index.js.map
|