@swiftpayfi/checkout-sdk 0.1.0-beta.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,11 @@
1
+ # @swiftpayfi/checkout-sdk
2
+
3
+ All notable changes to `@swiftpayfi/checkout-sdk` will be documented in this file.
4
+
5
+ ## 0.1.0
6
+
7
+ - Initial release
8
+ - Browser and React checkout SDK
9
+ - Popup, iframe, and redirect modes
10
+ - Event-driven payment lifecycle
11
+ - IIFE bundle for CDN usage
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 in 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 exercising
24
+ 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 Object
36
+ form, made available under the License, as indicated by a copyright
37
+ notice that is included in or attached to the work (an example is
38
+ 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 Work and any Derivative Works thereof, that is intentionally
50
+ submitted to, or received by, Licensor for inclusion in the Work
51
+ by the copyright owner or by an individual or Legal Entity authorized
52
+ to submit on behalf of the copyright owner. For the purposes of this
53
+ definition, "submitted" means any form of electronic, verbal, or
54
+ written communication sent to the Licensor or its representatives,
55
+ including but not limited to communication on electronic mailing lists,
56
+ source code control systems, and issue tracking systems that are
57
+ managed by, or on behalf of, the Licensor for the purpose of
58
+ discussing and improving the Work, but excluding communication
59
+ that is conspicuously marked or otherwise designated in writing by
60
+ 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, then any
107
+ Derivative Works that You distribute must include a readable
108
+ 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 from the Work, provided that
120
+ 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 of 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 Contribution.
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 reasonable and customary use in describing the
141
+ origin of the Work and 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 choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your 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 type. We also recommend that a file or
185
+ class name and description of purpose be included on the same
186
+ "printed page" as the copyright notice for easier identification
187
+ within third-party archives.
188
+
189
+ Copyright 2026 SwiftPay Finance
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 implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,429 @@
1
+ # SwiftPay Checkout SDK
2
+
3
+ Browser and React SDK for accepting stablecoin payments. Supports popup, iframe, and redirect checkout flows with real-time payment status tracking.
4
+
5
+ ## Features
6
+
7
+ - 🎯 **Multiple checkout modes** — Popup, iframe, or redirect
8
+ - 🔄 **Real-time updates** — SSE + polling for payment status
9
+ - 🏪 **Marketplace-friendly** — Create multiple invoices with one instance
10
+ - 🛡️ **Type-safe** — Full TypeScript support
11
+ - 📦 **Lightweight** — ~11 KB minified (IIFE)
12
+ - 🌍 **Multi-chain** — Ethereum, Polygon, Solana, Tron support
13
+ - ⚙️ **Sandbox mode** — Built-in sandbox/production switching
14
+
15
+ ## Installation
16
+
17
+ ### NPM Package
18
+
19
+ ```bash
20
+ npm install @swiftpayfi/checkout-sdk
21
+ ```
22
+
23
+ ### Direct Script Tag (Browser)
24
+
25
+ ```html
26
+ <script src="https://cdn.swiftpay.finance/checkout@latest/index.iife.js"></script>
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### Vanilla JavaScript
32
+
33
+ ```javascript
34
+ import SwiftPayCheckout from '@swiftpayfi/checkout-sdk';
35
+
36
+ // 1. Initialize once per page
37
+ const checkout = new SwiftPayCheckout({
38
+ key: 'pk_live_xxx', // Your publishable key
39
+ token: 'USDC', // Default asset (can override per invoice)
40
+ chains: ['ethereum'], // Supported chains
41
+ mode: 'iframe', // popup | iframe | redirect
42
+ sandbox: false, // Use sandbox API (https://sandbox-api.swiftpay.finance)
43
+ });
44
+
45
+ // 2. Listen for payment events
46
+ checkout.on('payment.completed', ({ invoice }) => {
47
+ console.log('✅ Payment completed:', invoice.reference);
48
+ // Update your backend/UI
49
+ });
50
+
51
+ // 3. Create invoice for a product
52
+ async function buyProduct(productId, price) {
53
+ const session = await checkout.createInvoice({
54
+ amount: price,
55
+ reference: productId,
56
+ metadata: { productId, userId: currentUser.id },
57
+ });
58
+
59
+ if (session) {
60
+ // 4. Open checkout UI
61
+ await checkout.open();
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### React Hook
67
+
68
+ ```typescript
69
+ import { useSwiftPayCheckout } from '@swiftpayfi/checkout-sdk/react';
70
+
71
+ function CheckoutButton({ product }) {
72
+ const {
73
+ createInvoice,
74
+ open,
75
+ isLoading,
76
+ session,
77
+ error,
78
+ } = useSwiftPayCheckout({
79
+ key: 'pk_live_xxx',
80
+ token: 'USDC',
81
+ chains: ['ethereum'],
82
+ sandbox: false,
83
+ onSuccess: ({ invoice }) => {
84
+ console.log('Payment complete:', invoice);
85
+ // Fulfill order
86
+ },
87
+ });
88
+
89
+ const handleCheckout = async () => {
90
+ const session = await createInvoice({
91
+ amount: product.price,
92
+ reference: product.id,
93
+ });
94
+ if (session) {
95
+ await open();
96
+ }
97
+ };
98
+
99
+ return (
100
+ <button
101
+ onClick={handleCheckout}
102
+ disabled={isLoading}
103
+ >
104
+ {isLoading ? 'Loading...' : `Buy for $${product.price}`}
105
+ </button>
106
+ );
107
+ }
108
+ ```
109
+
110
+ ## API Reference
111
+
112
+ ### SwiftPayCheckout Constructor
113
+
114
+ ```typescript
115
+ new SwiftPayCheckout(options: SwiftPayCheckoutOptions)
116
+ ```
117
+
118
+ #### Options
119
+
120
+ | Option | Type | Default | Description |
121
+ |--------|------|---------|-------------|
122
+ | `key` | `string` | **required** | Publishable key from dashboard |
123
+ | `token` | `string` | undefined | Default asset symbol (e.g. `USDC`, `USDT`) — can override per invoice |
124
+ | `chains` | `string[]` | undefined | Default chains (e.g. `['ethereum', 'polygon']`) — can override per invoice |
125
+ | `mode` | `'popup' \| 'iframe' \| 'redirect'` | `'popup'` | Checkout UI mode |
126
+ | `sandbox` | `boolean` | `false` | Use sandbox API endpoint |
127
+ | `callbackUrl` | `string` | current page | Redirect destination after payment (for redirect mode) |
128
+ | `autoClose` | `boolean` | `true` | Auto-close on payment completion |
129
+ | `pollIntervalMs` | `number` | `4000` | Status polling interval in ms |
130
+
131
+ #### Automatic API Endpoints
132
+
133
+ ```javascript
134
+ // Production (default)
135
+ sandbox: false
136
+ // → https://api.swiftpay.finance
137
+
138
+ // Sandbox/Testing
139
+ sandbox: true
140
+ // → https://sandbox-api.swiftpay.finance
141
+ ```
142
+
143
+ ### Methods
144
+
145
+ #### `createInvoice(options: CreateInvoiceOptions): Promise<CheckoutSessionResponse>`
146
+
147
+ Create a new invoice session. Must be called before `open()`.
148
+
149
+ ```typescript
150
+ const session = await checkout.createInvoice({
151
+ amount: 99.99, // Required: amount in USD
152
+ token: 'USDT', // Optional: override default asset
153
+ reference: 'order-12345', // Optional: your order ID
154
+ chains: ['ethereum', 'solana'], // Optional: override default chains
155
+ metadata: { orderId: '12345' }, // Optional: custom data
156
+ recipient: '0xaddress', // Optional: recipient address
157
+ expiresAt: new Date(...), // Optional: expiry time
158
+ });
159
+ ```
160
+
161
+ #### `open(options?: { mode?: CheckoutMode }): Promise<CheckoutSessionResponse>`
162
+
163
+ Open the checkout UI. Requires `createInvoice()` to be called first.
164
+
165
+ ```typescript
166
+ // Use instance's default mode
167
+ await checkout.open();
168
+
169
+ // Override mode for this open
170
+ await checkout.open({ mode: 'redirect' });
171
+ ```
172
+
173
+ #### `close(reason?: CheckoutCloseReason): void`
174
+
175
+ Close the checkout UI.
176
+
177
+ ```typescript
178
+ checkout.close('manual');
179
+ ```
180
+
181
+ #### `getIframeElement(): HTMLIFrameElement | null`
182
+
183
+ Get the iframe DOM element (iframe mode only). Useful for advanced customization.
184
+
185
+ ```typescript
186
+ const iframe = checkout.getIframeElement();
187
+ if (iframe) {
188
+ iframe.style.borderRadius = '16px';
189
+ }
190
+ ```
191
+
192
+ #### `getSession(): CheckoutSessionResponse | null`
193
+
194
+ Get the current session data.
195
+
196
+ ```typescript
197
+ const session = checkout.getSession();
198
+ console.log(session.invoice.status); // 'pending' | 'partial' | 'paid' | 'completed'
199
+ ```
200
+
201
+ #### `destroy(): void`
202
+
203
+ Clean up the checkout instance.
204
+
205
+ ```typescript
206
+ checkout.destroy();
207
+ ```
208
+
209
+ ### Events
210
+
211
+ Use `.on(event, handler)` to listen for checkout events:
212
+
213
+ ```typescript
214
+ checkout.on('payment.completed', ({ invoice, session }) => {
215
+ // Handle completion
216
+ });
217
+
218
+ // Unsubscribe
219
+ const unsubscribe = checkout.on('payment.paid', handler);
220
+ unsubscribe();
221
+ ```
222
+
223
+ #### Event Types
224
+
225
+ | Event | Payload | When |
226
+ |-------|---------|------|
227
+ | `ready` | `{ session }` | Session created & ready to open |
228
+ | `open` | `{ mode, checkoutUrl, session }` | Checkout UI opened |
229
+ | `close` | `{ reason, session? }` | Checkout UI closed |
230
+ | `cancel` | `{ reason, session? }` | User cancelled (not auto-closed) |
231
+ | `error` | `{ error }` | Error during session creation or polling |
232
+ | `status` | `{ status, previousStatus?, invoice, session }` | Invoice status changed |
233
+ | `payment.pending` | `{ invoice, session }` | First payment detected |
234
+ | `payment.partial` | `{ invoice, session }` | Partial payment received |
235
+ | `payment.paid` | `{ invoice, session }` | Full payment received |
236
+ | `payment.completed` | `{ invoice, session }` | Payment settled (final status) |
237
+ | `redirect` | `{ url, session }` | About to redirect (redirect mode) |
238
+ | `expired` | `{ sessionToken, session? }` | Session expired |
239
+
240
+ ### React Hook: useSwiftPayCheckout
241
+
242
+ ```typescript
243
+ const {
244
+ instance, // SwiftPayCheckout | null
245
+ createInvoice, // (options) => Promise<session | null>
246
+ open, // (options?) => Promise<session | null>
247
+ close, // () => void
248
+ isReady, // boolean
249
+ isLoading, // boolean (during createInvoice/open)
250
+ session, // CheckoutSessionResponse | null
251
+ branding, // MerchantBranding | null
252
+ status, // InvoiceStatus | null
253
+ error, // CheckoutSDKError | null
254
+ } = useSwiftPayCheckout(options);
255
+ ```
256
+
257
+ ## Patterns
258
+
259
+ ### Marketplace (Multiple Products)
260
+
261
+ ```javascript
262
+ const checkout = new SwiftPayCheckout({ key: 'pk_live_...' });
263
+
264
+ async function buyProduct(product) {
265
+ // Create invoice for this product
266
+ const session = await checkout.createInvoice({
267
+ amount: product.price,
268
+ reference: product.id,
269
+ metadata: { category: product.category },
270
+ });
271
+
272
+ // Show checkout
273
+ if (session) {
274
+ await checkout.open();
275
+ }
276
+ }
277
+
278
+ // Later, buy a different product
279
+ await buyProduct(anotherProduct);
280
+ ```
281
+
282
+ ### Environment Switching
283
+
284
+ ```javascript
285
+ const checkout = new SwiftPayCheckout({
286
+ key: process.env.REACT_APP_SWIFTPAY_KEY,
287
+ sandbox: process.env.NODE_ENV === 'development',
288
+ });
289
+ ```
290
+
291
+ ### Iframe with Custom Styling
292
+
293
+ ```javascript
294
+ const checkout = new SwiftPayCheckout({
295
+ key: 'pk_live_...',
296
+ mode: 'iframe',
297
+ iframe: {
298
+ container: '#checkout-container',
299
+ width: '100%',
300
+ height: '600px',
301
+ title: 'SwiftPay Checkout',
302
+ },
303
+ });
304
+
305
+ // Access and style the iframe
306
+ const iframe = checkout.getIframeElement();
307
+ if (iframe) {
308
+ iframe.style.borderRadius = '12px';
309
+ iframe.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
310
+ }
311
+ ```
312
+
313
+ ### Handling All Payment States
314
+
315
+ ```javascript
316
+ checkout.on('payment.pending', ({ invoice }) => {
317
+ // Show "waiting for payment" UI
318
+ updateUI('pending');
319
+ });
320
+
321
+ checkout.on('payment.partial', ({ invoice }) => {
322
+ // Show "partial payment received" with expected amount
323
+ updateUI('partial', {
324
+ received: invoice.pendingAmount,
325
+ expected: invoice.amountExpected,
326
+ });
327
+ });
328
+
329
+ checkout.on('payment.paid', ({ invoice }) => {
330
+ // Show "payment received, confirming..."
331
+ updateUI('confirming');
332
+ });
333
+
334
+ checkout.on('payment.completed', ({ invoice }) => {
335
+ // Payment settled — fulfill order
336
+ updateUI('completed');
337
+ fulfillOrder(invoice.externalRef);
338
+ });
339
+ ```
340
+
341
+ ## Error Handling
342
+
343
+ ```javascript
344
+ try {
345
+ const session = await checkout.createInvoice({
346
+ amount: 100,
347
+ reference: 'order-123',
348
+ });
349
+
350
+ if (!session) {
351
+ console.error('Failed to create invoice');
352
+ return;
353
+ }
354
+
355
+ await checkout.open();
356
+ } catch (error) {
357
+ if (error instanceof CheckoutSDKError) {
358
+ console.error('Checkout error:', error.code, error.message);
359
+ }
360
+ }
361
+
362
+ // Listen for errors
363
+ checkout.on('error', ({ error }) => {
364
+ console.error('Checkout error:', error.message);
365
+ // Show error UI
366
+ });
367
+ ```
368
+
369
+ ## Development
370
+
371
+ ### Build
372
+
373
+ ```bash
374
+ npm run build # All formats (types + ESM + IIFE)
375
+ npm run build:types # TypeScript declarations only
376
+ npm run build:esm # ESM module (for npm)
377
+ npm run build:iife # Standalone browser script
378
+ ```
379
+
380
+ ### Watch Mode
381
+
382
+ ```bash
383
+ npm run dev # TypeScript compilation with watch
384
+ ```
385
+
386
+ ### Testing
387
+
388
+ ```bash
389
+ # No tests configured yet
390
+ npm run test
391
+ ```
392
+
393
+ ## Publishing
394
+
395
+ Uses [Changesets](/.changeset/README.md) for version management.
396
+
397
+ ```bash
398
+ # Add a changeset when making changes
399
+ npx changeset add
400
+
401
+ # On main: Creates "Version Packages" PR
402
+ # On dev: Publishes beta immediately
403
+ ```
404
+
405
+ ## Browser Support
406
+
407
+ - Chrome/Edge 90+
408
+ - Firefox 88+
409
+ - Safari 14+
410
+
411
+ ## Security
412
+
413
+ - ✅ Uses HTTPS only
414
+ - ✅ Publishable key (not secret)
415
+ - ✅ CORS configured
416
+ - ✅ No sensitive data in logs
417
+ - ✅ postMessage origin validation
418
+
419
+ ## License
420
+
421
+ Licensed under the Apache License, Version 2.0. See [LICENSE](../LICENSE) for details.
422
+
423
+ **Copyright © 2026 SwiftPay Finance**
424
+
425
+ ## Support
426
+
427
+ - Documentation: https://docs.swiftpay.finance
428
+ - Issues: https://github.com/swiftpayfi/sdk/issues
429
+ - Email: support@swiftpay.finance
@@ -0,0 +1,260 @@
1
+ type InvoiceStatus = 'pending' | 'partial' | 'paid' | 'completed';
2
+ type ISO8601DateString = string;
3
+ interface CheckoutInvoiceAddress {
4
+ networkId: string;
5
+ networkName: string;
6
+ networkSymbol: string;
7
+ address: string;
8
+ }
9
+ interface CheckoutInvoice {
10
+ id: string;
11
+ merchantId: string;
12
+ externalRef: string | null;
13
+ reference: string;
14
+ addresses: CheckoutInvoiceAddress[];
15
+ tokenSymbol: string;
16
+ amountExpected: string | null;
17
+ pendingAmount: string | null;
18
+ status: InvoiceStatus;
19
+ metadata?: Record<string, unknown>;
20
+ expiresAt: ISO8601DateString | null;
21
+ createdAt: ISO8601DateString;
22
+ }
23
+ interface MerchantBranding {
24
+ brandName: string | null;
25
+ logoUrl: string | null;
26
+ faviconUrl: string | null;
27
+ accentColor: string | null;
28
+ themeMode: 'light' | 'dark' | 'auto';
29
+ supportEmail: string | null;
30
+ termsUrl: string | null;
31
+ privacyUrl: string | null;
32
+ allowedCallbackHosts: string[];
33
+ createdAt: ISO8601DateString;
34
+ updatedAt: ISO8601DateString;
35
+ }
36
+ interface CheckoutSessionResponse {
37
+ sessionToken: string;
38
+ checkout: Record<string, unknown>;
39
+ checkoutUrl: string;
40
+ /** Origin captured at session creation. Required by the backend's session-origin guard. */
41
+ sessionOrigin?: string | null;
42
+ /** Validated callback URL the hosted page may redirect to on completion. */
43
+ callbackUrl?: string | null;
44
+ /** Token used to authenticate postMessage payloads from the hosted checkout iframe. */
45
+ postMessageToken: string;
46
+ /** Merchant branding snapshot for the hosted checkout. */
47
+ branding?: MerchantBranding | null;
48
+ expiresAt: ISO8601DateString | null;
49
+ createdAt: ISO8601DateString;
50
+ invoice: CheckoutInvoice;
51
+ }
52
+ interface CheckoutCreateBody {
53
+ token: string;
54
+ amount: number;
55
+ chains?: string[];
56
+ externalRef?: string;
57
+ expiresAt?: ISO8601DateString;
58
+ metadata?: Record<string, unknown>;
59
+ checkout?: Record<string, unknown>;
60
+ recipient?: string;
61
+ }
62
+ type CheckoutMode = 'popup' | 'iframe' | 'redirect';
63
+ interface CheckoutPopupOptions {
64
+ width?: number;
65
+ height?: number;
66
+ features?: string;
67
+ }
68
+ interface CheckoutIframeOptions {
69
+ container?: string | HTMLElement;
70
+ width?: number | string;
71
+ height?: number | string;
72
+ title?: string;
73
+ }
74
+ type CheckoutCloseReason = 'completed' | 'manual' | 'user_closed' | 'error' | 'expired' | 'destroyed' | 'redirect';
75
+ interface CheckoutClosePayload {
76
+ reason: CheckoutCloseReason;
77
+ session?: CheckoutSessionResponse;
78
+ }
79
+ interface CreateInvoiceOptions {
80
+ amount: number;
81
+ token?: string;
82
+ reference?: string;
83
+ chains?: string[];
84
+ metadata?: Record<string, unknown>;
85
+ expiresAt?: Date | string;
86
+ recipient?: string;
87
+ }
88
+ interface SwiftPayCheckoutOptions {
89
+ /** Publishable key from dashboard */
90
+ key: string;
91
+ /** Default asset and chain (can be overridden per invoice in createInvoice) */
92
+ token?: string;
93
+ chains?: string[];
94
+ checkout?: Record<string, unknown>;
95
+ /** SDK-specific behaviour */
96
+ mode?: CheckoutMode;
97
+ sandbox?: boolean;
98
+ callbackUrl?: string;
99
+ autoClose?: boolean;
100
+ /** @deprecated No longer used — the hosted checkout app now owns real-time state polling. */
101
+ pollIntervalMs?: number;
102
+ /** Popup options */
103
+ popup?: CheckoutPopupOptions;
104
+ /** Iframe options */
105
+ iframe?: CheckoutIframeOptions;
106
+ /** Event and callback handlers */
107
+ onLoad?: (payload: {
108
+ session: CheckoutSessionResponse;
109
+ }) => void;
110
+ onOpen?: (payload: {
111
+ mode: CheckoutMode;
112
+ checkoutUrl: string;
113
+ session: CheckoutSessionResponse;
114
+ }) => void;
115
+ onClose?: (payload: {
116
+ reason: CheckoutCloseReason;
117
+ session?: CheckoutSessionResponse;
118
+ }) => void;
119
+ onCancel?: (payload: CheckoutClosePayload) => void;
120
+ onSuccess?: (payload: {
121
+ invoice: CheckoutInvoice;
122
+ session: CheckoutSessionResponse;
123
+ }) => void;
124
+ onError?: (error: CheckoutSDKError) => void;
125
+ onStatusChange?: (payload: {
126
+ status: InvoiceStatus;
127
+ previousStatus?: InvoiceStatus;
128
+ invoice: CheckoutInvoice;
129
+ session: CheckoutSessionResponse;
130
+ }) => void;
131
+ }
132
+ type CheckoutEvent = 'ready' | 'open' | 'close' | 'cancel' | 'error' | 'status' | 'payment.pending' | 'payment.partial' | 'payment.paid' | 'payment.completed' | 'redirect' | 'expired';
133
+ interface CheckoutSuccessEvent {
134
+ invoice: CheckoutInvoice;
135
+ session: CheckoutSessionResponse;
136
+ }
137
+ interface CheckoutOpenPayload {
138
+ mode: CheckoutMode;
139
+ checkoutUrl: string;
140
+ session: CheckoutSessionResponse;
141
+ }
142
+ interface CheckoutStatusPayload {
143
+ status: InvoiceStatus;
144
+ previousStatus?: InvoiceStatus;
145
+ invoice: CheckoutInvoice;
146
+ session: CheckoutSessionResponse;
147
+ }
148
+ interface CheckoutErrorPayload {
149
+ error: CheckoutSDKError;
150
+ }
151
+ interface CheckoutRedirectPayload {
152
+ url: string;
153
+ session: CheckoutSessionResponse;
154
+ }
155
+ interface CheckoutExpiredPayload {
156
+ sessionToken: string;
157
+ session?: CheckoutSessionResponse;
158
+ }
159
+ type CheckoutEventPayloadMap = {
160
+ ready: {
161
+ session: CheckoutSessionResponse;
162
+ };
163
+ open: CheckoutOpenPayload;
164
+ close: CheckoutClosePayload;
165
+ cancel: CheckoutClosePayload;
166
+ error: CheckoutErrorPayload;
167
+ status: CheckoutStatusPayload;
168
+ 'payment.pending': CheckoutSuccessEvent;
169
+ 'payment.partial': CheckoutSuccessEvent;
170
+ 'payment.paid': CheckoutSuccessEvent;
171
+ 'payment.completed': CheckoutSuccessEvent;
172
+ redirect: CheckoutRedirectPayload;
173
+ expired: CheckoutExpiredPayload;
174
+ };
175
+ type CheckoutEventHandler<EventName extends CheckoutEvent> = (payload: CheckoutEventPayloadMap[EventName]) => void;
176
+ declare class CheckoutSDKError extends Error {
177
+ readonly code?: string;
178
+ constructor(message: string, code?: string);
179
+ }
180
+ declare class SwiftPayOriginError extends CheckoutSDKError {
181
+ constructor(message?: string);
182
+ }
183
+
184
+ interface CheckoutEventEnvelope {
185
+ type: string;
186
+ data?: unknown;
187
+ }
188
+ interface SubscribeSessionEventsOptions {
189
+ apiBaseUrl?: string;
190
+ onEvent?: (event: CheckoutEventEnvelope) => void;
191
+ onOpen?: () => void;
192
+ onError?: (event: Event) => void;
193
+ }
194
+ interface SessionEventSubscription {
195
+ close: () => void;
196
+ readonly readyState: number;
197
+ }
198
+ /**
199
+ * Subscribe to a checkout session's SSE event stream. Validates the session
200
+ * origin against the current document so the caller can detect cross-origin
201
+ * misuse early. The browser will already send the Origin header that the
202
+ * backend's SessionOriginGuard checks.
203
+ */
204
+ declare const subscribeSessionEvents: (sessionToken: string, options?: SubscribeSessionEventsOptions) => SessionEventSubscription;
205
+ declare class SwiftPayCheckout {
206
+ private readonly options;
207
+ private readonly apiBaseUrl;
208
+ private session;
209
+ private currentStatus;
210
+ private completed;
211
+ private popupPollTimer;
212
+ private heartbeatTimer;
213
+ private lastMessageTimestamp;
214
+ private isProcessing;
215
+ private isOpen;
216
+ private destroyed;
217
+ private iframeElement;
218
+ private iframeContainer;
219
+ private popupWindow;
220
+ private postMessageHandler;
221
+ private listeners;
222
+ constructor(options: SwiftPayCheckoutOptions);
223
+ on<EventName extends CheckoutEvent>(event: EventName, handler: CheckoutEventHandler<EventName>): () => void;
224
+ off<EventName extends CheckoutEvent>(event: EventName, handler: CheckoutEventHandler<EventName>): void;
225
+ createInvoice(invoiceOptions: CreateInvoiceOptions): Promise<CheckoutSessionResponse>;
226
+ open(openOptions?: {
227
+ mode?: CheckoutMode;
228
+ }): Promise<CheckoutSessionResponse>;
229
+ close(reason?: CheckoutCloseReason): void;
230
+ destroy(): void;
231
+ get sessionToken(): string | null;
232
+ getSession(): CheckoutSessionResponse | null;
233
+ getBranding(): MerchantBranding | null;
234
+ getIframeElement(): HTMLIFrameElement | null;
235
+ private assertValidOptions;
236
+ private emit;
237
+ private emitError;
238
+ private request;
239
+ private buildCheckoutURL;
240
+ private createPopupFeatures;
241
+ private openPopup;
242
+ private createIframe;
243
+ private resolveContainer;
244
+ private startListening;
245
+ private attachPostMessageListener;
246
+ private detachPostMessageListener;
247
+ private handleStatusUpdate;
248
+ private startHeartbeatCheck;
249
+ private isExpired;
250
+ private cleanupOpen;
251
+ private resetForOpen;
252
+ private ensureBrowserEnvironment;
253
+ }
254
+ declare global {
255
+ interface Window {
256
+ SwiftPayCheckout?: typeof SwiftPayCheckout;
257
+ }
258
+ }
259
+
260
+ export { type CheckoutClosePayload, type CheckoutCloseReason, type CheckoutCreateBody, type CheckoutEvent, type CheckoutEventEnvelope, type CheckoutIframeOptions, type CheckoutInvoice, type CheckoutInvoiceAddress, type CheckoutMode, type CheckoutPopupOptions, CheckoutSDKError, type CheckoutSessionResponse, type CreateInvoiceOptions, type InvoiceStatus, type MerchantBranding, type SessionEventSubscription, type SubscribeSessionEventsOptions, SwiftPayCheckout, type SwiftPayCheckoutOptions, SwiftPayOriginError, SwiftPayCheckout as default, subscribeSessionEvents };
@@ -0,0 +1 @@
1
+ "use strict";(()=>{var m=Object.defineProperty;var f=(u,e,t)=>e in u?m(u,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):u[e]=t;var c=(u,e,t)=>f(u,typeof e!="symbol"?e+"":e,t);var v="swiftpay-checkout";var k="https://sandbox-api.swiftpay.finance",g="https://api.swiftpay.finance",h=u=>u?k:g,y={pending:"payment.pending",partial:"payment.partial",paid:"payment.paid"},a=class extends Error{constructor(t,s){super(t);c(this,"code");this.name="CheckoutSDKError",this.code=s}},d=class extends a{constructor(e="Origin not permitted by SwiftPay"){super(e,"origin_forbidden"),this.name="SwiftPayOriginError"}};var w=(u,e={})=>{if(typeof EventSource>"u")throw new a("EventSource is not available in this environment","no_eventsource");if(!u)throw new a("sessionToken is required","invalid_options");let s=`${e.apiBaseUrl??h(!1)}/v1/checkout/sessions/${encodeURIComponent(u)}/events`,o=new EventSource(s,{withCredentials:!1});return e.onOpen&&o.addEventListener("open",e.onOpen),e.onError&&o.addEventListener("error",e.onError),e.onEvent&&o.addEventListener("message",r=>{try{let i=JSON.parse(r.data);e.onEvent?.(i)}catch{}}),{close:()=>o.close(),get readyState(){return o.readyState}}},p=class{constructor(e){c(this,"options");c(this,"apiBaseUrl");c(this,"session",null);c(this,"currentStatus",null);c(this,"completed",!1);c(this,"popupPollTimer",null);c(this,"heartbeatTimer",null);c(this,"lastMessageTimestamp",0);c(this,"isProcessing",!1);c(this,"isOpen",!1);c(this,"destroyed",!1);c(this,"iframeElement",null);c(this,"iframeContainer",null);c(this,"popupWindow",null);c(this,"postMessageHandler",null);c(this,"listeners",new Map);this.options={autoClose:!0,mode:"popup",...e},this.apiBaseUrl=h(this.options.sandbox??!1),this.assertValidOptions(this.options)}on(e,t){let s=this.listeners.get(e)??new Set;return s.add(t),this.listeners.set(e,s),()=>this.off(e,t)}off(e,t){let s=this.listeners.get(e);s&&(s.delete(t),s.size===0&&this.listeners.delete(e))}async createInvoice(e){if(this.ensureBrowserEnvironment(),this.destroyed)throw new a("Cannot create invoice on a destroyed checkout instance","instance_destroyed");if(!Number.isFinite(e.amount))throw new a("Amount must be a finite number","invalid_options");if(e.amount<=0)throw new a("Amount must be greater than zero","invalid_options");let t=e.token??this.options.token;if(!t||t.trim()==="")throw new a("Token is required (token)","invalid_options");this.resetForOpen();let s={token:t,amount:e.amount};e.reference&&(s.externalRef=e.reference),e.chains?.length?s.chains=e.chains:this.options.chains?.length&&(s.chains=this.options.chains),e.recipient&&(s.recipient=e.recipient),e.metadata&&(s.metadata=e.metadata);let o={...this.options.checkout??{},sandbox:this.options.sandbox??!1};if(this.options.callbackUrl?o.callbackUrl=this.options.callbackUrl:this.options.callbackUrl===void 0&&typeof window<"u"&&window.location?.href&&(o.callbackUrl=window.location.href),Object.keys(o).length>0&&(s.checkout=o),e.expiresAt){let i=e.expiresAt instanceof Date?e.expiresAt:new Date(e.expiresAt);if(Number.isNaN(i.getTime()))throw new a("expiresAt must be a valid date","invalid_options");s.expiresAt=i.toISOString()}try{let i=await this.request("/v1/checkout/sessions",{method:"POST",body:JSON.stringify(s)});return this.session=i,this.currentStatus=i.invoice.status,i}catch(i){throw this.emitError(i),i}}async open(e){if(this.ensureBrowserEnvironment(),this.destroyed)throw new a("Cannot open a destroyed checkout instance","instance_destroyed");if(!this.session)throw new a("Call createInvoice() before open()","no_session");if(this.isOpen||this.isProcessing)return this.session;this.isProcessing=!0;try{let t=this.session;this.currentStatus=t.invoice.status,this.emit("ready",{session:t}),this.options.onLoad?.({session:t});let s=this.buildCheckoutURL(t.checkoutUrl),o=e?.mode??this.options.mode;return this.isOpen=!0,o==="redirect"?(this.emit("redirect",{url:s,session:t}),this.options.onOpen?.({mode:o,checkoutUrl:s,session:t}),this.options.onStatusChange?.({status:t.invoice.status,invoice:t.invoice,session:t}),window.location.assign(s),this.close("redirect"),t):(o==="iframe"?this.createIframe(s):this.openPopup(s),this.emit("open",{mode:o,checkoutUrl:s,session:t}),this.options.onOpen?.({mode:o,checkoutUrl:s,session:t}),this.startListening(),t)}catch(t){let s=this.emitError(t);throw this.close("error"),s}finally{this.isProcessing=!1}}close(e="manual"){let t=this.session??void 0;if(this.cleanupOpen(),this.emit("close",{reason:e,session:t}),this.options.onClose?.({reason:e,session:t}),this.session&&(e==="user_closed"||e==="manual"||e==="error"||e==="expired")){let s={reason:e,session:this.session};this.emit("cancel",s),this.options.onCancel?.(s)}}destroy(){this.destroyed=!0,this.cleanupOpen(),this.listeners.clear()}get sessionToken(){return this.session?.sessionToken??null}getSession(){return this.session}getBranding(){return this.session?.branding??null}getIframeElement(){return this.iframeElement}assertValidOptions(e){if(!e.key||e.key.trim()==="")throw new a("Publishable key is required (key)","invalid_options");if(e.mode!=="iframe"&&e.iframe)throw new a("Iframe options are only valid in iframe mode","invalid_options")}emit(e,t){let s=this.listeners.get(e);if(!(!s||s.size===0))for(let o of s)try{o(t)}catch{}}emitError(e){let t=e instanceof a?e:new a(e instanceof Error?e.message:"Checkout failed","unexpected_error");return this.emit("error",{error:t}),this.options.onError?.(t),t}async request(e,t){let s=await fetch(`${this.apiBaseUrl}${e}`,{...t,headers:{"Content-Type":"application/json","X-Swift-Key":this.options.key},mode:"cors",credentials:"omit"});if(!s.ok){let r=`${s.status} ${s.statusText}`;throw s.status===403?new d(r):new a(r,`http_${s.status}`)}let o=await s.json();if(!o.success)throw new a("Checkout request failed","request_failed");return o.data}buildCheckoutURL(e){return e}createPopupFeatures(e,t){let s=Math.max(0,Math.floor((window.screen.width-e)/2)),o=Math.max(0,Math.floor((window.screen.height-t)/2));return`width=${e},height=${t},top=${o},left=${s},popup=1,dependent=1,toolbar=no,menubar=no,location=no,status=no,scrollbars=yes,resizable=yes`}openPopup(e){if(!window)throw new a("Browser window object is unavailable","no_window");let t=this.options.popup?.width??520,s=this.options.popup?.height??740,o=this.options.popup?.features??this.createPopupFeatures(t,s),r=window.open("about:blank","swiftpay-checkout",o);if(!r)throw new a("Popup blocked by browser","popup_blocked");r.location.href=e,this.popupWindow=r,this.popupPollTimer=window.setInterval(()=>{(!this.popupWindow||this.popupWindow.closed)&&this.close("user_closed")},500)}createIframe(e){if(!document)throw new a("Document is unavailable","no_document");let t=this.resolveContainer(this.options.iframe?.container),s=this.options.iframe?.width??520,o=this.options.iframe?.height??720,r=document.createElement("div");r.style.position="fixed",r.style.inset="0",r.style.display="flex",r.style.alignItems="center",r.style.justifyContent="center",r.style.background="rgba(0, 0, 0, 0.5)",r.style.zIndex="2147483647";let i=document.createElement("iframe");i.src=e,i.title=this.options.iframe?.title??"SwiftPay checkout",i.style.width=typeof s=="number"?`${s}px`:String(s),i.style.height=typeof o=="number"?`${o}px`:String(o),i.style.border="0",i.style.borderRadius="12px",i.style.background="white",i.allow="clipboard-write";let n=document.createElement("button");n.type="button",n.textContent="\xD7",n.style.position="absolute",n.style.top="16px",n.style.right="16px",n.style.border="none",n.style.fontSize="24px",n.style.lineHeight="24px",n.style.padding="4px 8px",n.style.cursor="pointer",n.style.borderRadius="999px",n.style.background="rgba(255, 255, 255, 0.14)",n.style.color="#fff",n.addEventListener("click",()=>this.close("manual"));let l=document.createElement("div");l.style.position="relative",l.appendChild(n),l.appendChild(i),r.appendChild(l),t.style.position=t.style.position||"relative",t.appendChild(r),this.iframeContainer=r,this.iframeElement=i}resolveContainer(e){if(!e)return document.body;if(typeof e=="string"){let t=document.querySelector(e);if(!t)throw new a(`Checkout container not found: ${e}`,"invalid_container");return t}return e}startListening(){this.attachPostMessageListener(),this.options.mode==="iframe"&&this.startHeartbeatCheck()}attachPostMessageListener(){if(this.postMessageHandler||typeof window>"u")return;let e=this.session?.postMessageToken;if(!e)return;let t=(()=>{try{return new URL(this.session.checkoutUrl).origin}catch{return null}})();this.lastMessageTimestamp=Date.now();let s=o=>{if(t&&o.origin!==t)return;let r=o.data;if(!r||typeof r!="object"||r.source!==v||r.token!==e||typeof r.type!="string"||(this.lastMessageTimestamp=Date.now(),!this.isOpen||this.completed||this.destroyed||!this.session))return;let i=r.data??{};switch(r.type){case"ready":{let n=i.session;n&&(this.session=n,this.currentStatus=n.invoice.status);break}case"status":{let n=i;n.session&&n.status&&this.handleStatusUpdate(n.session,n.status,n.previousStatus);break}case"payment.pending":case"payment.partial":case"payment.paid":{let n=i;n.session&&this.handleStatusUpdate(n.session,n.session.invoice.status,this.currentStatus??void 0);break}case"payment.completed":{let n=i;n.session&&this.handleStatusUpdate(n.session,"completed",this.currentStatus??void 0);break}case"error":{let n=i;this.emitError(new a(n.error?.message??"Checkout error",n.error?.code));break}case"close":{let n=i;this.close(n.reason??"manual");break}case"cancel":{let l={reason:i.reason??"manual",session:this.session};this.emit("cancel",l),this.options.onCancel?.(l);break}}};window.addEventListener("message",s),this.postMessageHandler=s}detachPostMessageListener(){this.postMessageHandler&&typeof window<"u"&&window.removeEventListener("message",this.postMessageHandler),this.postMessageHandler=null}handleStatusUpdate(e,t,s){if(this.isExpired(e)){let i={sessionToken:e.sessionToken,session:e};this.emit("expired",i),this.emitError(new a("Checkout session has expired","session_expired")),this.close("expired");return}let o=s??this.currentStatus;if(o!==t){this.emit("status",{status:t,previousStatus:o??void 0,invoice:e.invoice,session:e}),this.options.onStatusChange?.({status:t,previousStatus:o??void 0,invoice:e.invoice,session:e});let i=t==="completed"?void 0:y[t];i&&this.emit(i,{invoice:e.invoice,session:e})}let r=t==="paid"&&this.options.mode==="iframe";if(t==="completed"||r){this.completed=!0,this.emit(r?"payment.paid":"payment.completed",{invoice:e.invoice,session:e}),this.options.onSuccess?.({invoice:e.invoice,session:e}),this.options.autoClose&&this.close("completed");return}this.session=e,this.currentStatus=t}startHeartbeatCheck(){this.heartbeatTimer===null&&(this.heartbeatTimer=window.setInterval(()=>{if(!this.isOpen||this.completed||this.destroyed)return;Date.now()-this.lastMessageTimestamp>3e4&&this.emitError(new a("Hosted checkout is not responding","host_unresponsive"))},15e3))}isExpired(e){if(!e.expiresAt)return!1;let t=Date.parse(e.expiresAt);return Number.isNaN(t)?!1:Date.now()>t}cleanupOpen(){this.isOpen=!1,this.popupPollTimer!==null&&(window.clearInterval(this.popupPollTimer),this.popupPollTimer=null),this.heartbeatTimer!==null&&(window.clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.detachPostMessageListener(),this.popupWindow&&!this.popupWindow.closed&&this.popupWindow.close(),this.popupWindow=null,this.iframeContainer&&this.iframeContainer.remove(),this.iframeElement=null,this.iframeContainer=null}resetForOpen(){this.session=null,this.currentStatus=null,this.completed=!1}ensureBrowserEnvironment(){if(typeof window>"u"||!window.fetch)throw new a("SwiftPay checkout SDK requires a browser environment","no_browser")}};typeof window<"u"&&(window.SwiftPayCheckout=p);var S=p;})();
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ var m=Object.defineProperty;var f=(u,e,t)=>e in u?m(u,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):u[e]=t;var c=(u,e,t)=>f(u,typeof e!="symbol"?e+"":e,t);var v="swiftpay-checkout";var k="https://sandbox-api.swiftpay.finance",g="https://api.swiftpay.finance",h=u=>u?k:g,y={pending:"payment.pending",partial:"payment.partial",paid:"payment.paid"},a=class extends Error{constructor(t,s){super(t);c(this,"code");this.name="CheckoutSDKError",this.code=s}},d=class extends a{constructor(e="Origin not permitted by SwiftPay"){super(e,"origin_forbidden"),this.name="SwiftPayOriginError"}};var w=(u,e={})=>{if(typeof EventSource>"u")throw new a("EventSource is not available in this environment","no_eventsource");if(!u)throw new a("sessionToken is required","invalid_options");let s=`${e.apiBaseUrl??h(!1)}/v1/checkout/sessions/${encodeURIComponent(u)}/events`,o=new EventSource(s,{withCredentials:!1});return e.onOpen&&o.addEventListener("open",e.onOpen),e.onError&&o.addEventListener("error",e.onError),e.onEvent&&o.addEventListener("message",r=>{try{let i=JSON.parse(r.data);e.onEvent?.(i)}catch{}}),{close:()=>o.close(),get readyState(){return o.readyState}}},p=class{constructor(e){c(this,"options");c(this,"apiBaseUrl");c(this,"session",null);c(this,"currentStatus",null);c(this,"completed",!1);c(this,"popupPollTimer",null);c(this,"heartbeatTimer",null);c(this,"lastMessageTimestamp",0);c(this,"isProcessing",!1);c(this,"isOpen",!1);c(this,"destroyed",!1);c(this,"iframeElement",null);c(this,"iframeContainer",null);c(this,"popupWindow",null);c(this,"postMessageHandler",null);c(this,"listeners",new Map);this.options={autoClose:!0,mode:"popup",...e},this.apiBaseUrl=h(this.options.sandbox??!1),this.assertValidOptions(this.options)}on(e,t){let s=this.listeners.get(e)??new Set;return s.add(t),this.listeners.set(e,s),()=>this.off(e,t)}off(e,t){let s=this.listeners.get(e);s&&(s.delete(t),s.size===0&&this.listeners.delete(e))}async createInvoice(e){if(this.ensureBrowserEnvironment(),this.destroyed)throw new a("Cannot create invoice on a destroyed checkout instance","instance_destroyed");if(!Number.isFinite(e.amount))throw new a("Amount must be a finite number","invalid_options");if(e.amount<=0)throw new a("Amount must be greater than zero","invalid_options");let t=e.token??this.options.token;if(!t||t.trim()==="")throw new a("Token is required (token)","invalid_options");this.resetForOpen();let s={token:t,amount:e.amount};e.reference&&(s.externalRef=e.reference),e.chains?.length?s.chains=e.chains:this.options.chains?.length&&(s.chains=this.options.chains),e.recipient&&(s.recipient=e.recipient),e.metadata&&(s.metadata=e.metadata);let o={...this.options.checkout??{},sandbox:this.options.sandbox??!1};if(this.options.callbackUrl?o.callbackUrl=this.options.callbackUrl:this.options.callbackUrl===void 0&&typeof window<"u"&&window.location?.href&&(o.callbackUrl=window.location.href),Object.keys(o).length>0&&(s.checkout=o),e.expiresAt){let i=e.expiresAt instanceof Date?e.expiresAt:new Date(e.expiresAt);if(Number.isNaN(i.getTime()))throw new a("expiresAt must be a valid date","invalid_options");s.expiresAt=i.toISOString()}try{let i=await this.request("/v1/checkout/sessions",{method:"POST",body:JSON.stringify(s)});return this.session=i,this.currentStatus=i.invoice.status,i}catch(i){throw this.emitError(i),i}}async open(e){if(this.ensureBrowserEnvironment(),this.destroyed)throw new a("Cannot open a destroyed checkout instance","instance_destroyed");if(!this.session)throw new a("Call createInvoice() before open()","no_session");if(this.isOpen||this.isProcessing)return this.session;this.isProcessing=!0;try{let t=this.session;this.currentStatus=t.invoice.status,this.emit("ready",{session:t}),this.options.onLoad?.({session:t});let s=this.buildCheckoutURL(t.checkoutUrl),o=e?.mode??this.options.mode;return this.isOpen=!0,o==="redirect"?(this.emit("redirect",{url:s,session:t}),this.options.onOpen?.({mode:o,checkoutUrl:s,session:t}),this.options.onStatusChange?.({status:t.invoice.status,invoice:t.invoice,session:t}),window.location.assign(s),this.close("redirect"),t):(o==="iframe"?this.createIframe(s):this.openPopup(s),this.emit("open",{mode:o,checkoutUrl:s,session:t}),this.options.onOpen?.({mode:o,checkoutUrl:s,session:t}),this.startListening(),t)}catch(t){let s=this.emitError(t);throw this.close("error"),s}finally{this.isProcessing=!1}}close(e="manual"){let t=this.session??void 0;if(this.cleanupOpen(),this.emit("close",{reason:e,session:t}),this.options.onClose?.({reason:e,session:t}),this.session&&(e==="user_closed"||e==="manual"||e==="error"||e==="expired")){let s={reason:e,session:this.session};this.emit("cancel",s),this.options.onCancel?.(s)}}destroy(){this.destroyed=!0,this.cleanupOpen(),this.listeners.clear()}get sessionToken(){return this.session?.sessionToken??null}getSession(){return this.session}getBranding(){return this.session?.branding??null}getIframeElement(){return this.iframeElement}assertValidOptions(e){if(!e.key||e.key.trim()==="")throw new a("Publishable key is required (key)","invalid_options");if(e.mode!=="iframe"&&e.iframe)throw new a("Iframe options are only valid in iframe mode","invalid_options")}emit(e,t){let s=this.listeners.get(e);if(!(!s||s.size===0))for(let o of s)try{o(t)}catch{}}emitError(e){let t=e instanceof a?e:new a(e instanceof Error?e.message:"Checkout failed","unexpected_error");return this.emit("error",{error:t}),this.options.onError?.(t),t}async request(e,t){let s=await fetch(`${this.apiBaseUrl}${e}`,{...t,headers:{"Content-Type":"application/json","X-Swift-Key":this.options.key},mode:"cors",credentials:"omit"});if(!s.ok){let r=`${s.status} ${s.statusText}`;throw s.status===403?new d(r):new a(r,`http_${s.status}`)}let o=await s.json();if(!o.success)throw new a("Checkout request failed","request_failed");return o.data}buildCheckoutURL(e){return e}createPopupFeatures(e,t){let s=Math.max(0,Math.floor((window.screen.width-e)/2)),o=Math.max(0,Math.floor((window.screen.height-t)/2));return`width=${e},height=${t},top=${o},left=${s},popup=1,dependent=1,toolbar=no,menubar=no,location=no,status=no,scrollbars=yes,resizable=yes`}openPopup(e){if(!window)throw new a("Browser window object is unavailable","no_window");let t=this.options.popup?.width??520,s=this.options.popup?.height??740,o=this.options.popup?.features??this.createPopupFeatures(t,s),r=window.open("about:blank","swiftpay-checkout",o);if(!r)throw new a("Popup blocked by browser","popup_blocked");r.location.href=e,this.popupWindow=r,this.popupPollTimer=window.setInterval(()=>{(!this.popupWindow||this.popupWindow.closed)&&this.close("user_closed")},500)}createIframe(e){if(!document)throw new a("Document is unavailable","no_document");let t=this.resolveContainer(this.options.iframe?.container),s=this.options.iframe?.width??520,o=this.options.iframe?.height??720,r=document.createElement("div");r.style.position="fixed",r.style.inset="0",r.style.display="flex",r.style.alignItems="center",r.style.justifyContent="center",r.style.background="rgba(0, 0, 0, 0.5)",r.style.zIndex="2147483647";let i=document.createElement("iframe");i.src=e,i.title=this.options.iframe?.title??"SwiftPay checkout",i.style.width=typeof s=="number"?`${s}px`:String(s),i.style.height=typeof o=="number"?`${o}px`:String(o),i.style.border="0",i.style.borderRadius="12px",i.style.background="white",i.allow="clipboard-write";let n=document.createElement("button");n.type="button",n.textContent="\xD7",n.style.position="absolute",n.style.top="16px",n.style.right="16px",n.style.border="none",n.style.fontSize="24px",n.style.lineHeight="24px",n.style.padding="4px 8px",n.style.cursor="pointer",n.style.borderRadius="999px",n.style.background="rgba(255, 255, 255, 0.14)",n.style.color="#fff",n.addEventListener("click",()=>this.close("manual"));let l=document.createElement("div");l.style.position="relative",l.appendChild(n),l.appendChild(i),r.appendChild(l),t.style.position=t.style.position||"relative",t.appendChild(r),this.iframeContainer=r,this.iframeElement=i}resolveContainer(e){if(!e)return document.body;if(typeof e=="string"){let t=document.querySelector(e);if(!t)throw new a(`Checkout container not found: ${e}`,"invalid_container");return t}return e}startListening(){this.attachPostMessageListener(),this.options.mode==="iframe"&&this.startHeartbeatCheck()}attachPostMessageListener(){if(this.postMessageHandler||typeof window>"u")return;let e=this.session?.postMessageToken;if(!e)return;let t=(()=>{try{return new URL(this.session.checkoutUrl).origin}catch{return null}})();this.lastMessageTimestamp=Date.now();let s=o=>{if(t&&o.origin!==t)return;let r=o.data;if(!r||typeof r!="object"||r.source!==v||r.token!==e||typeof r.type!="string"||(this.lastMessageTimestamp=Date.now(),!this.isOpen||this.completed||this.destroyed||!this.session))return;let i=r.data??{};switch(r.type){case"ready":{let n=i.session;n&&(this.session=n,this.currentStatus=n.invoice.status);break}case"status":{let n=i;n.session&&n.status&&this.handleStatusUpdate(n.session,n.status,n.previousStatus);break}case"payment.pending":case"payment.partial":case"payment.paid":{let n=i;n.session&&this.handleStatusUpdate(n.session,n.session.invoice.status,this.currentStatus??void 0);break}case"payment.completed":{let n=i;n.session&&this.handleStatusUpdate(n.session,"completed",this.currentStatus??void 0);break}case"error":{let n=i;this.emitError(new a(n.error?.message??"Checkout error",n.error?.code));break}case"close":{let n=i;this.close(n.reason??"manual");break}case"cancel":{let l={reason:i.reason??"manual",session:this.session};this.emit("cancel",l),this.options.onCancel?.(l);break}}};window.addEventListener("message",s),this.postMessageHandler=s}detachPostMessageListener(){this.postMessageHandler&&typeof window<"u"&&window.removeEventListener("message",this.postMessageHandler),this.postMessageHandler=null}handleStatusUpdate(e,t,s){if(this.isExpired(e)){let i={sessionToken:e.sessionToken,session:e};this.emit("expired",i),this.emitError(new a("Checkout session has expired","session_expired")),this.close("expired");return}let o=s??this.currentStatus;if(o!==t){this.emit("status",{status:t,previousStatus:o??void 0,invoice:e.invoice,session:e}),this.options.onStatusChange?.({status:t,previousStatus:o??void 0,invoice:e.invoice,session:e});let i=t==="completed"?void 0:y[t];i&&this.emit(i,{invoice:e.invoice,session:e})}let r=t==="paid"&&this.options.mode==="iframe";if(t==="completed"||r){this.completed=!0,this.emit(r?"payment.paid":"payment.completed",{invoice:e.invoice,session:e}),this.options.onSuccess?.({invoice:e.invoice,session:e}),this.options.autoClose&&this.close("completed");return}this.session=e,this.currentStatus=t}startHeartbeatCheck(){this.heartbeatTimer===null&&(this.heartbeatTimer=window.setInterval(()=>{if(!this.isOpen||this.completed||this.destroyed)return;Date.now()-this.lastMessageTimestamp>3e4&&this.emitError(new a("Hosted checkout is not responding","host_unresponsive"))},15e3))}isExpired(e){if(!e.expiresAt)return!1;let t=Date.parse(e.expiresAt);return Number.isNaN(t)?!1:Date.now()>t}cleanupOpen(){this.isOpen=!1,this.popupPollTimer!==null&&(window.clearInterval(this.popupPollTimer),this.popupPollTimer=null),this.heartbeatTimer!==null&&(window.clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.detachPostMessageListener(),this.popupWindow&&!this.popupWindow.closed&&this.popupWindow.close(),this.popupWindow=null,this.iframeContainer&&this.iframeContainer.remove(),this.iframeElement=null,this.iframeContainer=null}resetForOpen(){this.session=null,this.currentStatus=null,this.completed=!1}ensureBrowserEnvironment(){if(typeof window>"u"||!window.fetch)throw new a("SwiftPay checkout SDK requires a browser environment","no_browser")}};typeof window<"u"&&(window.SwiftPayCheckout=p);var S=p;export{a as CheckoutSDKError,p as SwiftPayCheckout,d as SwiftPayOriginError,S as default,w as subscribeSessionEvents};
@@ -0,0 +1,22 @@
1
+ import { SwiftPayCheckoutOptions, SwiftPayCheckout, CreateInvoiceOptions, CheckoutSessionResponse, CheckoutMode, MerchantBranding, InvoiceStatus, CheckoutSDKError } from './index.js';
2
+
3
+ interface UseSwiftPayCheckoutOptions extends SwiftPayCheckoutOptions {
4
+ key: string;
5
+ }
6
+ interface UseSwiftPayCheckoutResult {
7
+ instance: SwiftPayCheckout | null;
8
+ createInvoice: (options: CreateInvoiceOptions) => Promise<CheckoutSessionResponse | null>;
9
+ open: (openOptions?: {
10
+ mode?: CheckoutMode;
11
+ }) => Promise<CheckoutSessionResponse | null>;
12
+ close: () => void;
13
+ isReady: boolean;
14
+ isLoading: boolean;
15
+ session: CheckoutSessionResponse | null;
16
+ branding: MerchantBranding | null;
17
+ status: InvoiceStatus | null;
18
+ error: CheckoutSDKError | null;
19
+ }
20
+ declare const useSwiftPayCheckout: (options: UseSwiftPayCheckoutOptions) => UseSwiftPayCheckoutResult;
21
+
22
+ export { type UseSwiftPayCheckoutOptions, type UseSwiftPayCheckoutResult, useSwiftPayCheckout as default, useSwiftPayCheckout };
package/dist/react.js ADDED
@@ -0,0 +1 @@
1
+ var x=Object.defineProperty;var R=(u,e,t)=>e in u?x(u,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):u[e]=t;var l=(u,e,t)=>R(u,typeof e!="symbol"?e+"":e,t);import{useCallback as y,useEffect as _,useRef as g,useState as k}from"react";var I="swiftpay-checkout";var T="https://sandbox-api.swiftpay.finance",O="https://api.swiftpay.finance",M=u=>u?T:O,U={pending:"payment.pending",partial:"payment.partial",paid:"payment.paid"},c=class extends Error{constructor(t,n){super(t);l(this,"code");this.name="CheckoutSDKError",this.code=n}},C=class extends c{constructor(e="Origin not permitted by SwiftPay"){super(e,"origin_forbidden"),this.name="SwiftPayOriginError"}};var m=class{constructor(e){l(this,"options");l(this,"apiBaseUrl");l(this,"session",null);l(this,"currentStatus",null);l(this,"completed",!1);l(this,"popupPollTimer",null);l(this,"heartbeatTimer",null);l(this,"lastMessageTimestamp",0);l(this,"isProcessing",!1);l(this,"isOpen",!1);l(this,"destroyed",!1);l(this,"iframeElement",null);l(this,"iframeContainer",null);l(this,"popupWindow",null);l(this,"postMessageHandler",null);l(this,"listeners",new Map);this.options={autoClose:!0,mode:"popup",...e},this.apiBaseUrl=M(this.options.sandbox??!1),this.assertValidOptions(this.options)}on(e,t){let n=this.listeners.get(e)??new Set;return n.add(t),this.listeners.set(e,n),()=>this.off(e,t)}off(e,t){let n=this.listeners.get(e);n&&(n.delete(t),n.size===0&&this.listeners.delete(e))}async createInvoice(e){if(this.ensureBrowserEnvironment(),this.destroyed)throw new c("Cannot create invoice on a destroyed checkout instance","instance_destroyed");if(!Number.isFinite(e.amount))throw new c("Amount must be a finite number","invalid_options");if(e.amount<=0)throw new c("Amount must be greater than zero","invalid_options");let t=e.token??this.options.token;if(!t||t.trim()==="")throw new c("Token is required (token)","invalid_options");this.resetForOpen();let n={token:t,amount:e.amount};e.reference&&(n.externalRef=e.reference),e.chains?.length?n.chains=e.chains:this.options.chains?.length&&(n.chains=this.options.chains),e.recipient&&(n.recipient=e.recipient),e.metadata&&(n.metadata=e.metadata);let i={...this.options.checkout??{},sandbox:this.options.sandbox??!1};if(this.options.callbackUrl?i.callbackUrl=this.options.callbackUrl:this.options.callbackUrl===void 0&&typeof window<"u"&&window.location?.href&&(i.callbackUrl=window.location.href),Object.keys(i).length>0&&(n.checkout=i),e.expiresAt){let o=e.expiresAt instanceof Date?e.expiresAt:new Date(e.expiresAt);if(Number.isNaN(o.getTime()))throw new c("expiresAt must be a valid date","invalid_options");n.expiresAt=o.toISOString()}try{let o=await this.request("/v1/checkout/sessions",{method:"POST",body:JSON.stringify(n)});return this.session=o,this.currentStatus=o.invoice.status,o}catch(o){throw this.emitError(o),o}}async open(e){if(this.ensureBrowserEnvironment(),this.destroyed)throw new c("Cannot open a destroyed checkout instance","instance_destroyed");if(!this.session)throw new c("Call createInvoice() before open()","no_session");if(this.isOpen||this.isProcessing)return this.session;this.isProcessing=!0;try{let t=this.session;this.currentStatus=t.invoice.status,this.emit("ready",{session:t}),this.options.onLoad?.({session:t});let n=this.buildCheckoutURL(t.checkoutUrl),i=e?.mode??this.options.mode;return this.isOpen=!0,i==="redirect"?(this.emit("redirect",{url:n,session:t}),this.options.onOpen?.({mode:i,checkoutUrl:n,session:t}),this.options.onStatusChange?.({status:t.invoice.status,invoice:t.invoice,session:t}),window.location.assign(n),this.close("redirect"),t):(i==="iframe"?this.createIframe(n):this.openPopup(n),this.emit("open",{mode:i,checkoutUrl:n,session:t}),this.options.onOpen?.({mode:i,checkoutUrl:n,session:t}),this.startListening(),t)}catch(t){let n=this.emitError(t);throw this.close("error"),n}finally{this.isProcessing=!1}}close(e="manual"){let t=this.session??void 0;if(this.cleanupOpen(),this.emit("close",{reason:e,session:t}),this.options.onClose?.({reason:e,session:t}),this.session&&(e==="user_closed"||e==="manual"||e==="error"||e==="expired")){let n={reason:e,session:this.session};this.emit("cancel",n),this.options.onCancel?.(n)}}destroy(){this.destroyed=!0,this.cleanupOpen(),this.listeners.clear()}get sessionToken(){return this.session?.sessionToken??null}getSession(){return this.session}getBranding(){return this.session?.branding??null}getIframeElement(){return this.iframeElement}assertValidOptions(e){if(!e.key||e.key.trim()==="")throw new c("Publishable key is required (key)","invalid_options");if(e.mode!=="iframe"&&e.iframe)throw new c("Iframe options are only valid in iframe mode","invalid_options")}emit(e,t){let n=this.listeners.get(e);if(!(!n||n.size===0))for(let i of n)try{i(t)}catch{}}emitError(e){let t=e instanceof c?e:new c(e instanceof Error?e.message:"Checkout failed","unexpected_error");return this.emit("error",{error:t}),this.options.onError?.(t),t}async request(e,t){let n=await fetch(`${this.apiBaseUrl}${e}`,{...t,headers:{"Content-Type":"application/json","X-Swift-Key":this.options.key},mode:"cors",credentials:"omit"});if(!n.ok){let r=`${n.status} ${n.statusText}`;throw n.status===403?new C(r):new c(r,`http_${n.status}`)}let i=await n.json();if(!i.success)throw new c("Checkout request failed","request_failed");return i.data}buildCheckoutURL(e){return e}createPopupFeatures(e,t){let n=Math.max(0,Math.floor((window.screen.width-e)/2)),i=Math.max(0,Math.floor((window.screen.height-t)/2));return`width=${e},height=${t},top=${i},left=${n},popup=1,dependent=1,toolbar=no,menubar=no,location=no,status=no,scrollbars=yes,resizable=yes`}openPopup(e){if(!window)throw new c("Browser window object is unavailable","no_window");let t=this.options.popup?.width??520,n=this.options.popup?.height??740,i=this.options.popup?.features??this.createPopupFeatures(t,n),r=window.open("about:blank","swiftpay-checkout",i);if(!r)throw new c("Popup blocked by browser","popup_blocked");r.location.href=e,this.popupWindow=r,this.popupPollTimer=window.setInterval(()=>{(!this.popupWindow||this.popupWindow.closed)&&this.close("user_closed")},500)}createIframe(e){if(!document)throw new c("Document is unavailable","no_document");let t=this.resolveContainer(this.options.iframe?.container),n=this.options.iframe?.width??520,i=this.options.iframe?.height??720,r=document.createElement("div");r.style.position="fixed",r.style.inset="0",r.style.display="flex",r.style.alignItems="center",r.style.justifyContent="center",r.style.background="rgba(0, 0, 0, 0.5)",r.style.zIndex="2147483647";let o=document.createElement("iframe");o.src=e,o.title=this.options.iframe?.title??"SwiftPay checkout",o.style.width=typeof n=="number"?`${n}px`:String(n),o.style.height=typeof i=="number"?`${i}px`:String(i),o.style.border="0",o.style.borderRadius="12px",o.style.background="white",o.allow="clipboard-write";let s=document.createElement("button");s.type="button",s.textContent="\xD7",s.style.position="absolute",s.style.top="16px",s.style.right="16px",s.style.border="none",s.style.fontSize="24px",s.style.lineHeight="24px",s.style.padding="4px 8px",s.style.cursor="pointer",s.style.borderRadius="999px",s.style.background="rgba(255, 255, 255, 0.14)",s.style.color="#fff",s.addEventListener("click",()=>this.close("manual"));let p=document.createElement("div");p.style.position="relative",p.appendChild(s),p.appendChild(o),r.appendChild(p),t.style.position=t.style.position||"relative",t.appendChild(r),this.iframeContainer=r,this.iframeElement=o}resolveContainer(e){if(!e)return document.body;if(typeof e=="string"){let t=document.querySelector(e);if(!t)throw new c(`Checkout container not found: ${e}`,"invalid_container");return t}return e}startListening(){this.attachPostMessageListener(),this.options.mode==="iframe"&&this.startHeartbeatCheck()}attachPostMessageListener(){if(this.postMessageHandler||typeof window>"u")return;let e=this.session?.postMessageToken;if(!e)return;let t=(()=>{try{return new URL(this.session.checkoutUrl).origin}catch{return null}})();this.lastMessageTimestamp=Date.now();let n=i=>{if(t&&i.origin!==t)return;let r=i.data;if(!r||typeof r!="object"||r.source!==I||r.token!==e||typeof r.type!="string"||(this.lastMessageTimestamp=Date.now(),!this.isOpen||this.completed||this.destroyed||!this.session))return;let o=r.data??{};switch(r.type){case"ready":{let s=o.session;s&&(this.session=s,this.currentStatus=s.invoice.status);break}case"status":{let s=o;s.session&&s.status&&this.handleStatusUpdate(s.session,s.status,s.previousStatus);break}case"payment.pending":case"payment.partial":case"payment.paid":{let s=o;s.session&&this.handleStatusUpdate(s.session,s.session.invoice.status,this.currentStatus??void 0);break}case"payment.completed":{let s=o;s.session&&this.handleStatusUpdate(s.session,"completed",this.currentStatus??void 0);break}case"error":{let s=o;this.emitError(new c(s.error?.message??"Checkout error",s.error?.code));break}case"close":{let s=o;this.close(s.reason??"manual");break}case"cancel":{let p={reason:o.reason??"manual",session:this.session};this.emit("cancel",p),this.options.onCancel?.(p);break}}};window.addEventListener("message",n),this.postMessageHandler=n}detachPostMessageListener(){this.postMessageHandler&&typeof window<"u"&&window.removeEventListener("message",this.postMessageHandler),this.postMessageHandler=null}handleStatusUpdate(e,t,n){if(this.isExpired(e)){let o={sessionToken:e.sessionToken,session:e};this.emit("expired",o),this.emitError(new c("Checkout session has expired","session_expired")),this.close("expired");return}let i=n??this.currentStatus;if(i!==t){this.emit("status",{status:t,previousStatus:i??void 0,invoice:e.invoice,session:e}),this.options.onStatusChange?.({status:t,previousStatus:i??void 0,invoice:e.invoice,session:e});let o=t==="completed"?void 0:U[t];o&&this.emit(o,{invoice:e.invoice,session:e})}let r=t==="paid"&&this.options.mode==="iframe";if(t==="completed"||r){this.completed=!0,this.emit(r?"payment.paid":"payment.completed",{invoice:e.invoice,session:e}),this.options.onSuccess?.({invoice:e.invoice,session:e}),this.options.autoClose&&this.close("completed");return}this.session=e,this.currentStatus=t}startHeartbeatCheck(){this.heartbeatTimer===null&&(this.heartbeatTimer=window.setInterval(()=>{if(!this.isOpen||this.completed||this.destroyed)return;Date.now()-this.lastMessageTimestamp>3e4&&this.emitError(new c("Hosted checkout is not responding","host_unresponsive"))},15e3))}isExpired(e){if(!e.expiresAt)return!1;let t=Date.parse(e.expiresAt);return Number.isNaN(t)?!1:Date.now()>t}cleanupOpen(){this.isOpen=!1,this.popupPollTimer!==null&&(window.clearInterval(this.popupPollTimer),this.popupPollTimer=null),this.heartbeatTimer!==null&&(window.clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.detachPostMessageListener(),this.popupWindow&&!this.popupWindow.closed&&this.popupWindow.close(),this.popupWindow=null,this.iframeContainer&&this.iframeContainer.remove(),this.iframeElement=null,this.iframeContainer=null}resetForOpen(){this.session=null,this.currentStatus=null,this.completed=!1}ensureBrowserEnvironment(){if(typeof window>"u"||!window.fetch)throw new c("SwiftPay checkout SDK requires a browser environment","no_browser")}};typeof window<"u"&&(window.SwiftPayCheckout=m);var A=u=>{let e=g(u),t=g(null),[n,i]=k(!1),[r,o]=k(!1),[s,p]=k(null),[w,f]=k(null),[S,d]=k(null);e.current={onLoad:u.onLoad,onOpen:u.onOpen,onClose:u.onClose,onCancel:u.onCancel,onSuccess:u.onSuccess,onError:u.onError,onStatusChange:u.onStatusChange},_(()=>{let h=new m({...u,onLoad:a=>{p(a.session),f(a.session.invoice.status),d(null),e.current.onLoad?.(a)},onOpen:e.current.onOpen,onClose:e.current.onClose,onCancel:e.current.onCancel,onSuccess:a=>{p(a.session),f(a.invoice.status),e.current.onSuccess?.(a)},onError:a=>{d(a),e.current.onError?.(a)},onStatusChange:a=>{p(a.session),f(a.status),e.current.onStatusChange?.(a)}});return t.current=h,i(!0),()=>{h.destroy(),t.current=null,i(!1),p(null),f(null),d(null)}},[u.key,u.mode,u.sandbox]);let E=y(async h=>{if(!t.current)return null;try{o(!0);let a=await t.current.createInvoice(h);return p(a),f(a.invoice.status),d(null),a}catch(a){let v=a instanceof Error?new c(a.message):a;return d(v),null}finally{o(!1)}},[]),b=y(async h=>{if(!t.current)return null;try{o(!0);let a=await t.current.open(h);return p(a),a}catch(a){let v=a instanceof Error?new c(a.message):a;return d(v),null}finally{o(!1)}},[]),P=y(()=>{t.current&&t.current.close("manual")},[]);return{instance:t.current,createInvoice:E,open:b,close:P,isReady:n,isLoading:r,session:s,branding:s?.branding??null,status:w,error:S}},$=A;export{$ as default,A as useSwiftPayCheckout};
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@swiftpayfi/checkout-sdk",
3
+ "version": "0.1.0-beta.0",
4
+ "description": "SwiftPay browser + React checkout SDK",
5
+ "type": "module",
6
+ "main": "dist/index.js",
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
+ "default": "./dist/index.js"
14
+ },
15
+ "./react": {
16
+ "types": "./dist/react.d.ts",
17
+ "import": "./dist/react.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "LICENSE",
23
+ "README.md",
24
+ "CHANGELOG.md"
25
+ ],
26
+ "sideEffects": false,
27
+ "scripts": {
28
+ "build": "npm run build:types && tsup",
29
+ "build:types": "tsc -p . --emitDeclarationOnly --outDir dist",
30
+ "dev": "tsc -p . --watch",
31
+ "version": "changeset version",
32
+ "release": "npm run build && npm publish"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "peerDependencies": {
38
+ "react": ">=18.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@changesets/changelog-github": "^0.6.0",
42
+ "@changesets/cli": "^2.31.0",
43
+ "@types/react": "^19.2.14",
44
+ "esbuild": "^0.21.5",
45
+ "tsup": "^8.5.1",
46
+ "typescript": "^5.9.3"
47
+ }
48
+ }