@moneybar.online/moneybar 3.2.0 → 3.4.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.
@@ -1,28 +1,175 @@
1
1
  // MoneyBar Configuration Template
2
2
  // Copy this file and customize for your project
3
3
 
4
+ // ========================================
5
+ // THE 3 MONEY-BLOCKING STAGES FOR INDIE HACKERS
6
+ // ========================================
7
+
8
+ // PROBLEM #1: "Forced Sign-In Before Value" (40-60% conversion boost)
9
+ // Solution: Value-first trials - let users try before they buy
10
+ // Use case: PDF generators, image tools, converters
11
+
12
+ // PROBLEM #2: "Silent Drop-Off" (20-30% conversion boost)
13
+ // Solution: Exit feedback capture - learn why users don't convert
14
+ // Use case: SaaS trials, B2B tools, complex products
15
+
16
+ // PROBLEM #3: "Broken Money Flow" (25-40% conversion boost)
17
+ // Solution: Seamless auth + instant payments
18
+ // Use case: Templates, premium features, instant upgrades
19
+
20
+ // ========================================
21
+ // IMPORT OPTIONS
22
+ // ========================================
23
+
4
24
  // Option 1: Browser CDN Usage (Recommended for simple projects)
5
- // Use this import and add the import map to your HTML
25
+ // Add this import map to your HTML:
6
26
  /*
7
- Add to your HTML:
8
27
  <script type="importmap">
9
28
  {
10
29
  "imports": {
11
- "@supabase/supabase-js": "https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2.86.0/+esm"
30
+ "@supabase/supabase-js": "https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/+esm"
12
31
  }
13
32
  }
14
33
  </script>
15
34
  */
16
-
17
- import { MoneyBar } from 'https://cdn.jsdelivr.net/npm/@moneybar.online/moneybar@3.1.0/dist/index.browser.js';
35
+ import { MoneyBar } from 'https://cdn.jsdelivr.net/npm/@moneybar.online/moneybar/dist/index.browser.js';
18
36
 
19
37
  // Option 2: Self-contained bundle (No import map needed)
20
- // import { MoneyBar } from 'https://cdn.jsdelivr.net/npm/@moneybar.online/moneybar@3.1.0/dist/index.bundle.js';
38
+ // import { MoneyBar } from 'https://cdn.jsdelivr.net/npm/@moneybar.online/moneybar/dist/index.bundle.js';
21
39
 
22
40
  // Option 3: NPM install usage (for projects with bundlers)
23
41
  // import { MoneyBar } from '@moneybar.online/moneybar';
24
42
 
25
- // Configuration object (matches examples structure)
43
+ // ========================================
44
+ // CONFIGURATION EXAMPLES BY USE CASE
45
+ // ========================================
46
+
47
+ // 🎯 USE CASE 1: VALUE-FIRST DEMO (Problem #1 Solution)
48
+ // Perfect for: PDF generators, image tools, converters
49
+ const VALUE_FIRST_CONFIG = {
50
+ actionFunction: 'generatePDF',
51
+ appId: 'pdf-generator',
52
+ freeAttemptLimit: 3, // Give enough attempts to prove value
53
+ supabase: {
54
+ url: 'your-supabase-url',
55
+ anonKey: 'your-anon-key'
56
+ },
57
+ payment: [{
58
+ provider: 'dodo',
59
+ productId: 'your-product-id',
60
+ mode: 'test'
61
+ }],
62
+ theme: {
63
+ name: 'emerald', // Trustworthy, fresh
64
+ primaryColor: '#059669'
65
+ },
66
+ titleBar: {
67
+ title: '💰 PDF Generator',
68
+ links: [
69
+ { text: 'How it Works', url: '#how', target: '_self' },
70
+ { text: 'Examples', url: '#examples', target: '_self' }
71
+ ]
72
+ },
73
+ successMessage: {
74
+ enabled: true,
75
+ title: 'PDF Generated!',
76
+ message: 'Your PDF has been created successfully. Try more examples!'
77
+ }
78
+ };
79
+
80
+ // 🎯 USE CASE 2: FEEDBACK INTELLIGENCE (Problem #2 Solution)
81
+ // Perfect for: SaaS trials, B2B tools, complex products
82
+ const FEEDBACK_INTELLIGENCE_CONFIG = {
83
+ actionFunction: 'runAnalysis',
84
+ appId: 'saas-analytics',
85
+ freeAttemptLimit: 3,
86
+ supabase: {
87
+ url: 'your-supabase-url',
88
+ anonKey: 'your-anon-key'
89
+ },
90
+ payment: [{
91
+ provider: 'dodo',
92
+ productId: 'your-product-id',
93
+ mode: 'test'
94
+ }],
95
+ // KEY FEATURE: Feedback collection on cancel/exit
96
+ feedback: {
97
+ form: {
98
+ title: 'Quick Feedback',
99
+ description: 'What stopped you from upgrading?',
100
+ option1: 'Too complex to implement',
101
+ option2: 'Not enough ROI for my team',
102
+ option3: 'Need more features first'
103
+ },
104
+ email: [
105
+ {
106
+ provider: 'resend',
107
+ apiKey: 'your-resend-api-key',
108
+ fromEmail: 'feedback@yourapp.com'
109
+ }
110
+ // Future providers can be added:
111
+ // {
112
+ // provider: 'sendgrid',
113
+ // apiKey: 'your-sendgrid-api-key',
114
+ // fromEmail: 'feedback@yourapp.com'
115
+ // }
116
+ ]
117
+ },
118
+ theme: {
119
+ name: 'corporate', // Professional B2B
120
+ primaryColor: '#0066cc'
121
+ },
122
+ titleBar: {
123
+ title: '📈 Analytics Tool',
124
+ links: [
125
+ { text: 'Features', url: '#features', target: '_self' },
126
+ { text: 'ROI Calculator', url: '#roi', target: '_self' }
127
+ ]
128
+ }
129
+ };
130
+
131
+ // 🎯 USE CASE 3: INSTANT PAYMENT FLOW (Problem #3 Solution)
132
+ // Perfect for: Templates, premium features, instant upgrades
133
+ const INSTANT_PAYMENT_CONFIG = {
134
+ actionFunction: 'createTemplate',
135
+ appId: 'template-creator',
136
+ freeAttemptLimit: 5, // Lower to trigger payment faster
137
+ supabase: {
138
+ url: 'your-supabase-url',
139
+ anonKey: 'your-anon-key'
140
+ },
141
+ payment: [{
142
+ provider: 'dodo',
143
+ productId: 'your-product-id',
144
+ mode: 'test'
145
+ }],
146
+ theme: {
147
+ name: 'dark', // Modern, premium feel
148
+ primaryColor: '#7c3aed'
149
+ },
150
+ titleBar: {
151
+ title: '⚡ Template Creator',
152
+ links: [
153
+ { text: 'Templates', url: '#templates', target: '_self' },
154
+ { text: 'Pricing', url: '#pricing', target: '_self' }
155
+ ]
156
+ },
157
+ successMessage: {
158
+ enabled: true,
159
+ title: 'Template Created!',
160
+ message: 'Your professional template is ready. Download and customize!'
161
+ }
162
+ };
163
+
164
+ // ========================================
165
+ // CHOOSE YOUR CONFIGURATION
166
+ // ========================================
167
+
168
+ // Pick one of the configs above or create your own:
169
+ window.APP_CONFIG = FEEDBACK_INTELLIGENCE_CONFIG; // Change this to your preferred config
170
+
171
+ // Or create a custom configuration:
172
+ /*
26
173
  window.APP_CONFIG = {
27
174
  // REQUIRED: Function name that gets called
28
175
  actionFunction: 'myAction',
@@ -31,7 +178,7 @@ window.APP_CONFIG = {
31
178
  appId: 'my-app',
32
179
 
33
180
  // REQUIRED: Number of free attempts before paywall
34
- freeDownloadLimit: 3,
181
+ freeAttemptLimit: 3,
35
182
 
36
183
  // REQUIRED: Supabase configuration
37
184
  supabase: {
@@ -40,10 +187,11 @@ window.APP_CONFIG = {
40
187
  },
41
188
 
42
189
  // REQUIRED: Payment configuration
43
- payment: {
190
+ payment: [{
191
+ provider: 'dodo',
44
192
  productId: 'your-dodo-payments-product-id',
45
193
  mode: 'test' // Change to 'live' for production
46
- },
194
+ }],
47
195
 
48
196
  // OPTIONAL: UI Theme (29 themes available)
49
197
  theme: {
@@ -68,7 +216,7 @@ window.APP_CONFIG = {
68
216
  message: 'Your action completed successfully!'
69
217
  },
70
218
 
71
- // OPTIONAL: Feedback collection
219
+ // OPTIONAL: Feedback collection (Problem #2 solution)
72
220
  feedback: {
73
221
  form: {
74
222
  title: 'Quick Feedback',
@@ -77,12 +225,20 @@ window.APP_CONFIG = {
77
225
  option2: 'Need more features',
78
226
  option3: 'Just testing'
79
227
  },
80
- email: {
81
- resendApiKey: 'your-resend-api-key',
82
- fromEmail: 'hello@yourapp.com'
83
- }
228
+ email: [
229
+ {
230
+ provider: 'resend',
231
+ apiKey: 'your-resend-api-key',
232
+ fromEmail: 'hello@yourapp.com'
233
+ }
234
+ ]
84
235
  }
85
236
  };
237
+ */
238
+
239
+ // ========================================
240
+ // INITIALIZE MONEYBAR
241
+ // ========================================
86
242
 
87
243
  // Initialize MoneyBar with the configuration
88
244
  const moneyBar = new MoneyBar(window.APP_CONFIG);
@@ -98,36 +254,74 @@ moneyBar.attachToButton('#action-btn', (userContext) => {
98
254
  }
99
255
  });
100
256
 
101
- // Global function for easy HTML usage (matches actionFunction)
102
- window.myAction = function(userContext) {
103
- console.log('User context:', userContext);
257
+ // ========================================
258
+ // ACTION FUNCTIONS BY USE CASE
259
+ // ========================================
260
+
261
+ // 🎯 USE CASE 1: Value-First Demo Actions
262
+ window.generatePDF = function(userContext) {
263
+ console.log('🎯 VALUE-FIRST: Generating PDF...');
104
264
 
105
265
  if (userContext.isPremium) {
106
- alert('Premium features unlocked!');
107
- // Your premium functionality here
266
+ console.log('Premium PDF with all features');
267
+ alert('Premium PDF generated with watermark removal, custom fonts, and high quality!');
268
+ // Your premium PDF generation logic here
108
269
  } else {
109
- alert(`Free trial: ${userContext.remaining} uses left`);
110
- // Your free tier functionality here
270
+ console.log(`📄 Basic PDF generated. ${userContext.remaining} attempts remaining`);
271
+ alert(`Basic PDF generated! ${userContext.remaining} free attempts left.`);
272
+ // Your basic PDF generation logic here
273
+ }
274
+ };
275
+
276
+ // 🎯 USE CASE 2: Feedback Intelligence Actions
277
+ window.runAnalysis = function(userContext) {
278
+ console.log('🎯 FEEDBACK INTELLIGENCE: Running analysis...');
279
+
280
+ if (userContext.isPremium) {
281
+ console.log('✅ Full analytics suite access');
282
+ alert('Complete analysis ready! Access to all metrics, exports, and AI insights.');
283
+ // Your premium analytics logic here
284
+ } else {
285
+ console.log(`📊 Basic analysis complete. ${userContext.remaining} attempts remaining`);
286
+ alert(`Analysis complete! Limited data shown. ${userContext.remaining} attempts left.`);
287
+ // Your basic analytics logic here
111
288
  }
112
289
  };
113
290
 
114
- // Example alternative action functions
291
+ // 🎯 USE CASE 3: Instant Payment Actions
115
292
  window.createTemplate = function(userContext) {
293
+ console.log('🎯 INSTANT PAYMENT: Creating template...');
294
+
116
295
  if (userContext.isPremium) {
117
- console.log('Creating premium template...');
118
- alert('Premium template created!');
296
+ console.log(' Premium template with full customization');
297
+ alert('Premium template created! Full customization, commercial license, and PSD files included.');
298
+ // Your premium template logic here
119
299
  } else {
120
- console.log('Creating basic template...');
121
- alert(`Basic template created! ${userContext.remaining} uses remaining`);
300
+ console.log(`🎨 Basic template created. ${userContext.remaining} attempts remaining`);
301
+ alert(`Template created! Basic version only. ${userContext.remaining} attempts left.`);
302
+ // Your basic template logic here
122
303
  }
123
304
  };
124
305
 
125
- window.downloadFile = function(userContext) {
306
+ // 🎯 DEFAULT: Fallback Action
307
+ window.myAction = function(userContext) {
308
+ console.log('User context:', userContext);
309
+
126
310
  if (userContext.isPremium) {
127
- console.log('Downloading premium file...');
128
- // Premium download logic
311
+ alert('Premium features unlocked!');
312
+ // Your premium functionality here
129
313
  } else {
130
- console.log('Downloading basic file...');
131
- // Basic download logic
314
+ alert(`Free trial: ${userContext.remaining} uses left`);
315
+ // Your free tier functionality here
132
316
  }
133
- };
317
+ };
318
+
319
+ // ========================================
320
+ // QUICK REFERENCE: Available Themes
321
+ // ========================================
322
+
323
+ // Light themes: light, cupcake, bumblebee, emerald, corporate, garden
324
+ // Dark themes: dark, synthwave, retro, cyberpunk, valentine, halloween
325
+ // Professional: corporate, business, luxury
326
+ // Creative: fantasy, cyberpunk, synthwave
327
+ // And 20+ more themes available!
package/package.json CHANGED
@@ -1,17 +1,16 @@
1
1
  {
2
2
  "name": "@moneybar.online/moneybar",
3
- "version": "3.2.0",
3
+ "version": "3.4.0",
4
4
  "description": "The navbar of monetization. Fix the 3 money-blocking stages: forced sign-ins, silent drop-offs, and broken payment flows. Turn browsers into buyers.",
5
- "main": "dist/index.cjs.js",
5
+ "main": "dist/index.esm.js",
6
6
  "module": "dist/index.esm.js",
7
7
  "browser": "dist/index.browser.js",
8
- "unpkg": "dist/index.umd.js",
8
+ "unpkg": "dist/index.bundle.js",
9
9
  "types": "dist/index.d.ts",
10
10
  "type": "module",
11
11
  "exports": {
12
12
  ".": {
13
13
  "import": "./dist/index.esm.js",
14
- "require": "./dist/index.cjs.js",
15
14
  "browser": "./dist/index.browser.js",
16
15
  "types": "./dist/index.d.ts"
17
16
  }
package/src/index.ts CHANGED
@@ -23,13 +23,13 @@ import { createClient } from '@supabase/supabase-js';
23
23
  *
24
24
  * const moneyBar = new MoneyBar({
25
25
  * appId: 'my-pdf-app',
26
- * freeDownloadLimit: 3,
26
+ * freeAttemptLimit: 3,
27
27
  * supabase: { url: 'https://xyz.supabase.co', anonKey: 'anon_key' },
28
28
  * payment: { productId: 'prod_xyz' }
29
29
  * });
30
30
  *
31
31
  * // Single button - handles all monetization automatically
32
- * moneyBar.attachToButton('#download-btn', (userContext) => {
32
+ * moneyBar.attachToButton('#action-btn', (userContext) => {
33
33
  * // Your action logic here - with user context for personalization
34
34
  * if (userContext.isPremium) {
35
35
  * generatePremiumPDF();
@@ -243,10 +243,10 @@ export class MoneyBar {
243
243
 
244
244
  const status: DownloadStatus = {
245
245
  currentCount: count,
246
- limit: this.config.freeDownloadLimit,
246
+ limit: this.config.freeAttemptLimit,
247
247
  isPremium,
248
248
  isAuthenticated: !!this.currentUser,
249
- remaining: Math.max(0, this.config.freeDownloadLimit - count)
249
+ remaining: Math.max(0, this.config.freeAttemptLimit - count)
250
250
  };
251
251
 
252
252
  // Cache the status
@@ -259,10 +259,10 @@ export class MoneyBar {
259
259
  // Return safe defaults on error
260
260
  const errorStatus: DownloadStatus = {
261
261
  currentCount: 0,
262
- limit: this.config.freeDownloadLimit,
262
+ limit: this.config.freeAttemptLimit,
263
263
  isPremium: false,
264
264
  isAuthenticated: false,
265
- remaining: this.config.freeDownloadLimit
265
+ remaining: this.config.freeAttemptLimit
266
266
  };
267
267
 
268
268
  // Cache error status for short time to avoid repeated failures
@@ -333,26 +333,37 @@ export class MoneyBar {
333
333
  throw new Error('Payment configuration not provided');
334
334
  }
335
335
 
336
+ const payment = this.config.payment?.find(p => p.provider === 'dodo');
337
+ if (!payment) {
338
+ throw new Error('No dodo payment provider configured');
339
+ }
340
+
336
341
  const requestBody = {
337
342
  email: this.currentUser.email,
338
- product_id: this.config.payment.productId,
339
- mode: this.config.payment.mode || 'test',
343
+ product_id: payment.productId,
344
+ mode: payment.mode || 'test',
340
345
  app_id: this.config.appId,
341
346
  return_url: `${window.location.origin}${window.location.pathname}?payment=success`
342
347
  };
343
348
 
344
- console.log(`🔥 [SUPABASE API] createPayment called for email: ${this.currentUser.email} | Time: ${new Date().toISOString()}`);
349
+ console.log(`🔥 [PAYMENT API] createPayment called for provider: ${payment.provider}, email: ${this.currentUser.email} | Time: ${new Date().toISOString()}`);
345
350
 
346
351
  try {
347
- const response = await fetch(`${this.config.supabase.url}/functions/v1/create-payment`, {
348
- method: 'POST',
349
- headers: {
350
- 'Content-Type': 'application/json',
351
- 'Authorization': `Bearer ${this.config.supabase.anonKey}`
352
- },
353
- body: JSON.stringify(requestBody)
354
- });
355
- console.log(`🔥 [SUPABASE API] createPayment response: ${response.status}`);
352
+ // Route to appropriate payment API based on provider
353
+ let response;
354
+ if (payment.provider === 'dodo') {
355
+ response = await fetch(`${this.config.supabase.url}/functions/v1/create-payment`, {
356
+ method: 'POST',
357
+ headers: {
358
+ 'Content-Type': 'application/json',
359
+ 'Authorization': `Bearer ${this.config.supabase.anonKey}`
360
+ },
361
+ body: JSON.stringify(requestBody)
362
+ });
363
+ } else {
364
+ throw new Error(`Payment provider '${payment.provider}' is not yet supported`);
365
+ }
366
+ console.log(`🔥 [PAYMENT API] createPayment response for ${payment.provider}: ${response.status}`);
356
367
 
357
368
  if (this.config.options?.debug) {
358
369
  //console.log('🔍 DEBUG: createPayment response status:', response.status);
@@ -595,8 +606,8 @@ export class MoneyBar {
595
606
  const newCount = await this.incrementDownloadCount();
596
607
 
597
608
  // Double-check: if server returned count exceeding limit, something went wrong
598
- if (newCount > this.config.freeDownloadLimit) {
599
- console.error(`⚠️ Server returned count (${newCount}) exceeding limit (${this.config.freeDownloadLimit})`);
609
+ if (newCount > this.config.freeAttemptLimit) {
610
+ console.error(`⚠️ Server returned count (${newCount}) exceeding limit (${this.config.freeAttemptLimit})`);
600
611
  // Don't update UI with invalid state
601
612
  return;
602
613
  }
@@ -2441,7 +2452,25 @@ export class MoneyBar {
2441
2452
  }
2442
2453
 
2443
2454
  private async sendFeedbackEmail(feedbackData: any): Promise<void> {
2444
- const emailConfig = this.config.feedback?.email || this.config.email;
2455
+ // Get email config - handle both new array format and legacy format
2456
+ let emailConfig;
2457
+ if (this.config.feedback?.email) {
2458
+ // New array format - use first provider (typically resend)
2459
+ if (Array.isArray(this.config.feedback.email)) {
2460
+ emailConfig = this.config.feedback.email[0];
2461
+ } else {
2462
+ // Legacy format fallback
2463
+ emailConfig = this.config.feedback.email as any;
2464
+ }
2465
+ } else if (this.config.email) {
2466
+ // Deprecated legacy email config
2467
+ emailConfig = {
2468
+ provider: 'resend',
2469
+ apiKey: (this.config.email as any).resendApiKey,
2470
+ fromEmail: (this.config.email as any).fromEmail
2471
+ };
2472
+ }
2473
+
2445
2474
  if (!emailConfig) {
2446
2475
  throw new Error('Email configuration not available');
2447
2476
  }
@@ -2456,19 +2485,25 @@ Timestamp: ${feedbackData.timestamp}
2456
2485
  User Agent: ${feedbackData.userAgent}
2457
2486
  `.trim();
2458
2487
 
2459
- const response = await fetch('https://api.resend.com/emails', {
2460
- method: 'POST',
2461
- headers: {
2462
- 'Content-Type': 'application/json',
2463
- 'Authorization': `Bearer ${emailConfig.resendApiKey}`
2464
- },
2465
- body: JSON.stringify({
2466
- from: emailConfig.fromEmail,
2467
- to: [emailConfig.fromEmail], // Send feedback to yourself
2468
- subject: `Feedback from ${this.config.appId}`,
2469
- text: emailBody
2470
- })
2471
- });
2488
+ // Handle different email providers
2489
+ let response;
2490
+ if (emailConfig.provider === 'resend' || !emailConfig.provider) {
2491
+ response = await fetch('https://api.resend.com/emails', {
2492
+ method: 'POST',
2493
+ headers: {
2494
+ 'Content-Type': 'application/json',
2495
+ 'Authorization': `Bearer ${emailConfig.apiKey || (emailConfig as any).resendApiKey}`
2496
+ },
2497
+ body: JSON.stringify({
2498
+ from: emailConfig.fromEmail,
2499
+ to: [emailConfig.fromEmail], // Send feedback to yourself
2500
+ subject: `Feedback from ${this.config.appId}`,
2501
+ text: emailBody
2502
+ })
2503
+ });
2504
+ } else {
2505
+ throw new Error(`Unsupported email provider: ${emailConfig.provider}`);
2506
+ }
2472
2507
 
2473
2508
  if (!response.ok) {
2474
2509
  throw new Error(`Email API error: ${response.status}`);
@@ -2959,8 +2994,8 @@ User Agent: ${feedbackData.userAgent}
2959
2994
  if (!this.config.appId) throw new Error('appId is required');
2960
2995
  if (!this.config.supabase?.url) throw new Error('supabase.url is required');
2961
2996
  if (!this.config.supabase?.anonKey) throw new Error('supabase.anonKey is required');
2962
- if (typeof this.config.freeDownloadLimit !== 'number' || this.config.freeDownloadLimit < 0) {
2963
- throw new Error('freeDownloadLimit must be a non-negative number');
2997
+ if (typeof this.config.freeAttemptLimit !== 'number' || this.config.freeAttemptLimit < 0) {
2998
+ throw new Error('freeAttemptLimit must be a non-negative number');
2964
2999
  }
2965
3000
  }
2966
3001
 
@@ -2986,7 +3021,15 @@ User Agent: ${feedbackData.userAgent}
2986
3021
  }
2987
3022
 
2988
3023
  // Validate product ID format
2989
- const productId = this.config.payment.productId;
3024
+ const payment = this.config.payment?.find(p => p.provider === 'dodo');
3025
+ if (!payment) {
3026
+ this.paymentConnectionFailed = true;
3027
+ if (this.config.options?.debug) {
3028
+ console.warn('No dodo payment provider configured');
3029
+ }
3030
+ return;
3031
+ }
3032
+ const productId = payment.productId;
2990
3033
  if (!productId || !productId.startsWith('pdt_') || productId.length < 10) {
2991
3034
  this.paymentConnectionFailed = true;
2992
3035
  if (this.config.options?.debug) {
package/src/types.ts CHANGED
@@ -3,7 +3,7 @@ export interface MoneyBarConfig {
3
3
  appId: string;
4
4
 
5
5
  /** Number of free attempts allowed before paywall */
6
- freeDownloadLimit: number;
6
+ freeAttemptLimit: number;
7
7
 
8
8
  /** Supabase configuration */
9
9
  supabase: {
@@ -11,11 +11,14 @@ export interface MoneyBarConfig {
11
11
  anonKey: string;
12
12
  };
13
13
 
14
- /** DodoPayments product configuration */
15
- payment?: {
14
+ /** Payment configuration - extensible for multiple providers */
15
+ payment?: Array<{
16
+ provider: 'dodo' | 'stripe' | 'paypal' | 'square' | 'paddle'; // Extensible for future providers
16
17
  productId: string;
17
18
  mode?: 'test' | 'live';
18
- };
19
+ apiKey?: string; // For providers that need API keys
20
+ publishableKey?: string; // For client-side keys (Stripe, etc.)
21
+ }>;
19
22
 
20
23
  /** Optional: Feedback form configuration */
21
24
  feedback?: {
@@ -26,10 +29,11 @@ export interface MoneyBarConfig {
26
29
  option2: string;
27
30
  option3: string;
28
31
  };
29
- email: {
30
- resendApiKey: string;
32
+ email: Array<{
33
+ provider: 'resend' | 'sendgrid' | 'mailgun' | 'ses'; // Extensible for future providers
34
+ apiKey: string;
31
35
  fromEmail: string;
32
- };
36
+ }>;
33
37
  };
34
38
 
35
39
  /** @deprecated Use feedback.email instead - Email service configuration (for feedback and notifications) */