@funnelfox/billing 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Funnelfox
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,548 @@
1
+ # @funnelfox/billing
2
+
3
+ A modern JavaScript SDK for subscription payments with Primer integration.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Modern API**: Clean, Promise-based interface with event-driven architecture
8
+ - 🔄 **Dynamic Pricing**: Update prices without page reload
9
+ - 🛡️ **Type-Safe**: Complete JSDoc coverage with TypeScript definitions
10
+ - 🎯 **Event-Driven**: Handle success, errors, and status changes with ease
11
+ - 🔧 **Robust**: Built-in error handling, retries, and validation
12
+ - 📦 **Lightweight**: Minimal dependencies (15KB minified), browser-optimized
13
+
14
+ ## Installation
15
+
16
+ ### Via CDN
17
+
18
+ ```html
19
+ <!-- Include Primer SDK first -->
20
+ <script src="https://sdk.primer.io/web/v2.54.0/Primer.min.js"></script>
21
+ <link rel="stylesheet" href="https://sdk.primer.io/web/v2.0.0/Checkout.css" />
22
+
23
+ <!-- Include Funnelfox Billing SDK -->
24
+ <script src="https://unpkg.com/@funnelfox/billing@latest/dist/funnelfox-billing.min.js"></script>
25
+ ```
26
+
27
+ ### Via NPM
28
+
29
+ ```bash
30
+ npm install @funnelfox/billing @primer-io/checkout-web
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```javascript
36
+ import { Billing } from '@funnelfox/billing';
37
+
38
+ await Billing.createCheckout({
39
+ orgId: 'your-org-id',
40
+ priceId: 'price_123',
41
+ customer: {
42
+ externalId: 'user_456',
43
+ email: 'user@example.com',
44
+ },
45
+ container: '#checkout-container',
46
+ });
47
+ ```
48
+
49
+ ## API Reference
50
+
51
+ ### `configure(config)`
52
+
53
+ Configure global SDK settings.
54
+
55
+ ```javascript
56
+ import { configure } from '@funnelfox/billing';
57
+
58
+ configure({
59
+ orgId: 'your-org-id', // Required
60
+ baseUrl: 'https://custom.api', // Optional, defaults to https://billing.funnelfox.com
61
+ region: 'us-east-1', // Optional, defaults to 'default'
62
+ });
63
+ ```
64
+
65
+ **Parameters:**
66
+
67
+ - `config.orgId` (string, required) - Your organization identifier
68
+ - `config.baseUrl` (string, optional) - Custom API URL
69
+ - `config.region` (string, optional) - Region, defaults to 'default'
70
+
71
+ ---
72
+
73
+ ### `createCheckout(options)`
74
+
75
+ Creates a new checkout instance.
76
+
77
+ ```javascript
78
+ const checkout = await createCheckout({
79
+ // Required
80
+ priceId: 'price_123',
81
+ customer: {
82
+ externalId: 'user_456',
83
+ email: 'user@example.com',
84
+ countryCode: 'US', // Optional
85
+ },
86
+ container: '#checkout-container',
87
+
88
+ // Optional
89
+ orgId: 'your-org-id', // If not configured globally
90
+ clientMetadata: { source: 'web' },
91
+ universalCheckoutOptions: {
92
+ // Primer SDK options
93
+ paypal: {
94
+ buttonColor: 'blue',
95
+ },
96
+ },
97
+
98
+ // Callbacks (alternative to events)
99
+ onSuccess: result => {
100
+ /* ... */
101
+ },
102
+ onError: error => {
103
+ /* ... */
104
+ },
105
+ onStatusChange: (state, oldState) => {
106
+ /* ... */
107
+ },
108
+ });
109
+ ```
110
+
111
+ **Parameters:**
112
+
113
+ - `options.priceId` (string, required) - Price identifier
114
+ - `options.customer` (object, required)
115
+ - `customer.externalId` (string, required) - Your user identifier
116
+ - `customer.email` (string, required) - Customer email
117
+ - `customer.countryCode` (string, optional) - ISO country code
118
+ - `options.container` (string, required) - CSS selector for checkout container
119
+ - `options.orgId` (string, optional) - Org ID (if not configured globally)
120
+ - `options.clientMetadata` (object, optional) - Custom metadata
121
+ - `options.universalCheckoutOptions` (object, optional) - Primer SDK options
122
+ - `options.onSuccess` (function, optional) - Success callback
123
+ - `options.onError` (function, optional) - Error callback
124
+ - `options.onStatusChange` (function, optional) - State change callback
125
+
126
+ **Returns:** `Promise<CheckoutInstance>`
127
+
128
+ ---
129
+
130
+ ### `createClientSession(params)`
131
+
132
+ Create a client session manually (for advanced integrations).
133
+
134
+ ```javascript
135
+ import { createClientSession } from '@funnelfox/billing';
136
+
137
+ const session = await createClientSession({
138
+ priceId: 'price_123',
139
+ externalId: 'user_456',
140
+ email: 'user@example.com',
141
+ orgId: 'your-org-id', // Optional if configured
142
+ });
143
+
144
+ console.log(session.clientToken); // Use with Primer SDK
145
+ console.log(session.orderId);
146
+ ```
147
+
148
+ **Returns:** `Promise<{ clientToken: string, orderId: string, type: string }>`
149
+
150
+ ---
151
+
152
+ ### `showUniversalCheckout(clientToken, options)`
153
+
154
+ Direct Primer integration (for advanced use cases).
155
+
156
+ ```javascript
157
+ import { showUniversalCheckout } from '@funnelfox/billing';
158
+
159
+ const primerCheckout = await showUniversalCheckout(clientToken, {
160
+ container: '#checkout',
161
+ onTokenizeSuccess: async (data, handler) => {
162
+ // Handle payment...
163
+ handler.handleSuccess();
164
+ },
165
+ });
166
+ ```
167
+
168
+ ---
169
+
170
+ ### CheckoutInstance
171
+
172
+ #### Properties
173
+
174
+ - `id` (string) - Unique checkout identifier
175
+ - `state` (string) - Current state: `initializing`, `ready`, `processing`, `completed`, `error`
176
+ - `orderId` (string) - Order identifier (available after initialization)
177
+ - `isDestroyed` (boolean) - Whether checkout has been destroyed
178
+
179
+ #### Events
180
+
181
+ ##### `'success'`
182
+
183
+ Emitted when payment completes successfully.
184
+
185
+ ```javascript
186
+ checkout.on('success', result => {
187
+ console.log('Order ID:', result.orderId);
188
+ console.log('Status:', result.status); // 'succeeded'
189
+ console.log('Transaction:', result.transactionId);
190
+ });
191
+ ```
192
+
193
+ ##### `'error'`
194
+
195
+ Emitted when payment fails or encounters an error.
196
+
197
+ ```javascript
198
+ checkout.on('error', error => {
199
+ console.error('Error:', error.message);
200
+ console.error('Code:', error.code);
201
+ console.error('Request ID:', error.requestId); // For support
202
+ });
203
+ ```
204
+
205
+ ##### `'status-change'`
206
+
207
+ Emitted when checkout state changes.
208
+
209
+ ```javascript
210
+ checkout.on('status-change', (newState, oldState) => {
211
+ console.log(`${oldState} → ${newState}`);
212
+ // States: initializing, ready, processing, action_required, completed, error
213
+ });
214
+ ```
215
+
216
+ ##### `'destroy'`
217
+
218
+ Emitted when checkout is destroyed.
219
+
220
+ ```javascript
221
+ checkout.on('destroy', () => {
222
+ console.log('Checkout cleaned up');
223
+ });
224
+ ```
225
+
226
+ #### Methods
227
+
228
+ ##### `updatePrice(priceId)`
229
+
230
+ Updates the checkout to use a different price.
231
+
232
+ ```javascript
233
+ await checkout.updatePrice('price_yearly');
234
+ ```
235
+
236
+ **Note:** Cannot update price while payment is processing.
237
+
238
+ ##### `getStatus()`
239
+
240
+ Returns current checkout status.
241
+
242
+ ```javascript
243
+ const status = checkout.getStatus();
244
+ console.log(status.id); // Checkout ID
245
+ console.log(status.state); // Current state
246
+ console.log(status.orderId); // Order ID
247
+ console.log(status.priceId); // Current price ID
248
+ console.log(status.isDestroyed); // Cleanup status
249
+ ```
250
+
251
+ ##### `destroy()`
252
+
253
+ Destroys the checkout instance and cleans up resources.
254
+
255
+ ```javascript
256
+ await checkout.destroy();
257
+ ```
258
+
259
+ ##### `isReady()`
260
+
261
+ Check if checkout is ready for payment.
262
+
263
+ ```javascript
264
+ if (checkout.isReady()) {
265
+ console.log('Ready to accept payment');
266
+ }
267
+ ```
268
+
269
+ ##### `isProcessing()`
270
+
271
+ Check if payment is being processed.
272
+
273
+ ```javascript
274
+ if (checkout.isProcessing()) {
275
+ console.log('Payment in progress...');
276
+ }
277
+ ```
278
+
279
+ ---
280
+
281
+ ## Complete Example
282
+
283
+ ```html
284
+ <!DOCTYPE html>
285
+ <html>
286
+ <head>
287
+ <title>Funnelfox Checkout</title>
288
+ <script src="https://sdk.primer.io/web/v2.54.0/Primer.min.js"></script>
289
+ <link
290
+ rel="stylesheet"
291
+ href="https://sdk.primer.io/web/v2.0.0/Checkout.css"
292
+ />
293
+ <script src="https://unpkg.com/@funnelfox/billing@latest/dist/funnelfox-billing.min.js"></script>
294
+ </head>
295
+ <body>
296
+ <div id="price-selector">
297
+ <button onclick="selectPrice('price_monthly')">Monthly - $9.99</button>
298
+ <button onclick="selectPrice('price_yearly')">Yearly - $99.99</button>
299
+ </div>
300
+
301
+ <div id="checkout-container"></div>
302
+
303
+ <script>
304
+ let currentCheckout = null;
305
+
306
+ // Configure SDK once
307
+ Billing.configure({
308
+ orgId: 'your-org-id',
309
+ });
310
+
311
+ async function selectPrice(priceId) {
312
+ try {
313
+ if (currentCheckout && currentCheckout.isReady()) {
314
+ // Update existing checkout
315
+ await currentCheckout.updatePrice(priceId);
316
+ } else {
317
+ // Destroy old checkout if exists
318
+ if (currentCheckout) {
319
+ await currentCheckout.destroy();
320
+ }
321
+
322
+ // Create new checkout
323
+ currentCheckout = await Billing.createCheckout({
324
+ priceId: priceId,
325
+ customer: {
326
+ externalId: generateUserId(),
327
+ email: getUserEmail(),
328
+ },
329
+ container: '#checkout-container',
330
+ });
331
+
332
+ // Handle success
333
+ currentCheckout.on('success', result => {
334
+ alert('Payment successful!');
335
+ window.location.href = '/success?order=' + result.orderId;
336
+ });
337
+
338
+ // Handle errors
339
+ currentCheckout.on('error', error => {
340
+ alert('Payment failed: ' + error.message);
341
+ });
342
+
343
+ // Track state changes
344
+ currentCheckout.on('status-change', state => {
345
+ console.log('Checkout state:', state);
346
+ });
347
+ }
348
+ } catch (error) {
349
+ console.error('Checkout error:', error);
350
+ alert('Failed to initialize checkout');
351
+ }
352
+ }
353
+
354
+ function generateUserId() {
355
+ return 'user_' + Math.random().toString(36).substr(2, 9);
356
+ }
357
+
358
+ function getUserEmail() {
359
+ return 'user@example.com'; // Get from your auth system
360
+ }
361
+ </script>
362
+ </body>
363
+ </html>
364
+ ```
365
+
366
+ ## Error Handling
367
+
368
+ The SDK provides specific error classes for different scenarios:
369
+
370
+ ```javascript
371
+ import {
372
+ ValidationError,
373
+ APIError,
374
+ PrimerError,
375
+ CheckoutError,
376
+ NetworkError,
377
+ } from '@funnelfox/billing';
378
+
379
+ try {
380
+ const checkout = await createCheckout(config);
381
+ } catch (error) {
382
+ if (error instanceof ValidationError) {
383
+ // Invalid input
384
+ console.log('Field:', error.field);
385
+ console.log('Value:', error.value);
386
+ console.log('Message:', error.message);
387
+ } else if (error instanceof APIError) {
388
+ // API error
389
+ console.log('Status:', error.statusCode);
390
+ console.log('Error Code:', error.errorCode); // e.g., 'double_purchase'
391
+ console.log('Error Type:', error.errorType); // e.g., 'api_exception'
392
+ console.log('Request ID:', error.requestId); // For support
393
+ console.log('Message:', error.message);
394
+ } else if (error instanceof PrimerError) {
395
+ // Primer SDK error
396
+ console.log('Primer error:', error.message);
397
+ console.log('Original:', error.primerError);
398
+ } else if (error instanceof CheckoutError) {
399
+ // Checkout lifecycle error
400
+ console.log('Phase:', error.phase);
401
+ console.log('Message:', error.message);
402
+ } else if (error instanceof NetworkError) {
403
+ // Network/connectivity error
404
+ console.log('Network error:', error.message);
405
+ console.log('Original:', error.originalError);
406
+ }
407
+ }
408
+ ```
409
+
410
+ ### Common Error Codes
411
+
412
+ - `double_purchase` - User already has an active subscription
413
+ - `invalid_price` - Price ID not found
414
+ - `invalid_customer` - Customer data validation failed
415
+ - `payment_failed` - Payment processing failed
416
+
417
+ ## TypeScript Support
418
+
419
+ The SDK includes comprehensive TypeScript definitions:
420
+
421
+ ```typescript
422
+ import {
423
+ configure,
424
+ createCheckout,
425
+ CheckoutInstance,
426
+ PaymentResult,
427
+ CheckoutConfig,
428
+ } from '@funnelfox/billing';
429
+
430
+ // Configure
431
+ configure({
432
+ orgId: 'your-org-id',
433
+ });
434
+
435
+ // Create checkout with type safety
436
+ const checkout: CheckoutInstance = await createCheckout({
437
+ priceId: 'price_123',
438
+ customer: {
439
+ externalId: 'user_456',
440
+ email: 'user@example.com',
441
+ countryCode: 'US',
442
+ },
443
+ container: '#checkout',
444
+ clientMetadata: {
445
+ source: 'web',
446
+ campaign: 'summer-sale',
447
+ },
448
+ });
449
+
450
+ // Type-safe event handlers
451
+ checkout.on('success', (result: PaymentResult) => {
452
+ console.log('Order:', result.orderId);
453
+ console.log('Status:', result.status);
454
+ console.log('Transaction:', result.transactionId);
455
+ });
456
+ ```
457
+
458
+ ## Advanced Usage
459
+
460
+ ### Using Callbacks Instead of Events
461
+
462
+ ```javascript
463
+ const checkout = await createCheckout({
464
+ priceId: 'price_123',
465
+ customer: {
466
+ externalId: 'user_456',
467
+ email: 'user@example.com',
468
+ },
469
+ container: '#checkout',
470
+
471
+ // Callback style (alternative to .on() events)
472
+ onSuccess: result => {
473
+ console.log('Success!', result.orderId);
474
+ },
475
+ onError: error => {
476
+ console.error('Error!', error.message);
477
+ },
478
+ onStatusChange: (newState, oldState) => {
479
+ console.log(`${oldState} → ${newState}`);
480
+ },
481
+ });
482
+ ```
483
+
484
+ ### Custom Primer Options
485
+
486
+ ```javascript
487
+ const checkout = await createCheckout({
488
+ priceId: 'price_123',
489
+ customer: {
490
+ externalId: 'user_456',
491
+ email: 'user@example.com',
492
+ },
493
+ container: '#checkout',
494
+
495
+ // Pass options to Primer SDK
496
+ universalCheckoutOptions: {
497
+ paypal: {
498
+ buttonColor: 'gold',
499
+ paymentFlow: 'PREFER_VAULT',
500
+ },
501
+ style: {
502
+ // Custom styling...
503
+ },
504
+ },
505
+ });
506
+ ```
507
+
508
+ ### Manual Session Creation
509
+
510
+ For advanced integrations where you want to control the Primer SDK directly:
511
+
512
+ ```javascript
513
+ import { createClientSession, showUniversalCheckout } from '@funnelfox/billing';
514
+
515
+ // Step 1: Create session
516
+ const session = await createClientSession({
517
+ priceId: 'price_123',
518
+ externalId: 'user_456',
519
+ email: 'user@example.com',
520
+ orgId: 'your-org-id',
521
+ });
522
+
523
+ // Step 2: Use with Primer directly
524
+ const primerCheckout = await showUniversalCheckout(session.clientToken, {
525
+ container: '#checkout',
526
+ onTokenizeSuccess: async (data, handler) => {
527
+ // Your custom payment logic...
528
+ handler.handleSuccess();
529
+ },
530
+ });
531
+ ```
532
+
533
+ ## Browser Support
534
+
535
+ - Chrome 60+
536
+ - Firefox 55+
537
+ - Safari 12+
538
+ - Edge 79+
539
+
540
+ ## Examples
541
+
542
+ See the [examples directory](./examples/) for more complete examples:
543
+
544
+ - [Basic Checkout](./examples/basic/) - Simple checkout integration
545
+
546
+ ## License
547
+
548
+ MIT © Funnelfox
@@ -0,0 +1,2 @@
1
+ // Re-export all types for CJS build
2
+ export * from './funnelfox-billing';