npm package: @moneybar.online/moneybar
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.
Author: Ravi Cloudworks
Latest version: 11.4.0

# MoneyBar

**The navbar that gets you money faster.** Complete backend infrastructure included. Setup in 1 minute.

MoneyBar handles authentication, payment processing, usage limits, and subscription management - all in one navbar component. No backend coding required.

---

## Quick Start (2 Steps)

### Step 1: Copy & Customize Config

Copy `moneybar-config-template.js` to your project and customize these settings:

```javascript
const CONFIG = {
  // ========================================
  // CUSTOMIZABLE SETTINGS
  // ========================================

  // Security key - Get yours from Supabase
  // Run: SELECT generate_security_key('my-app', 'yourdomain.com', 'your@email.com', 3)
  security_key: 'sk_...',

  // What are users doing? (shown in UI: "PDF Generations: 2/3")
  actionLabel: 'PDF Generations', // Examples: 'Image Exports', 'Downloads', 'AI Requests'

  // How many free attempts before paywall?
  freeAttemptLimit: 3,

  // Payment setup (optional - add later if needed)
  payment: [{
    provider: 'dodo',
    productId: 'pdt_xxx', // Get from Dodo Payments dashboard
    mode: 'test' // 'test' or 'live'
  }],

  // Feedback form (optional)
  feedback: {
    form: {
      title: 'Quick Feedback',
      description: 'What stopped you from upgrading?',
      option1: 'Too expensive',
      option2: 'Not enough features',
      option3: 'Need more time to evaluate'
    },
    email: [{
      provider: 'resend',
      apiKey: 'your-resend-api-key',
      fromEmail: 'feedback@yourapp.com'
    }]
  },

  // UI Theme (optional)
  theme: {
    name: 'emerald', // DaisyUI theme name
    primaryColor: '#059669'
  },

  // Title bar (optional)
  titleBar: {
    title: '🚀 My App',
    links: [
      { text: 'Features', url: '#features', target: '_self' },
      { text: 'Pricing', url: '#pricing', target: '_self' }
    ]
  },

  // ⚠️ DO NOT MODIFY - Required for MoneyBar to work
  supabase: {
    url: 'https://qhlzdkwcrrjjcqvupyaa.supabase.co',
    anonKey: 'eyJ...'
  }
};
```

---

### Step 2: Add to HTML & Attach to Buttons

Add these scripts to your HTML:

```html
<!DOCTYPE html>
<html>
<head>
  <title>My App</title>
</head>
<body>
  <!-- Your app content -->
  <button id="my-custom-btn">Generate PDF</button>

  <!-- Step 2a: Load MoneyBar config -->
  <script type="module" src="moneybar-config-template.js"></script>

  <!-- Step 2b: Attach to your buttons -->
  <script type="module">
    // Access the MoneyBar instance
    window.moneyBar.attachToButton('#my-custom-btn', (userContext) => {

      // Check if user is premium
      if (userContext.isPremium) {
        // ✅ Premium user - no limits!
        generatePDF(); // Your custom function

      } else {
        // Free user - show remaining attempts
        alert(`Free user. ${userContext.remaining} attempts remaining`);

        if (userContext.remaining > 0) {
          generatePDF(); // Your custom function
        }
      }
    });

    // Your custom function
    function generatePDF() {
      console.log('Generating PDF...');
      // Your PDF generation logic here
    }
  </script>
</body>
</html>
```

---

## Understanding UserContext

When you attach to a button, you get a `userContext` object with all user info:

```javascript
window.moneyBar.attachToButton('#my-btn', (userContext) => {

  // Premium status
  userContext.isPremium          // true/false - Does user have premium access?
  userContext.isAuthenticated    // true/false - Is user signed in?

  // Usage limits
  userContext.currentCount       // Number - How many times user has used the feature
  userContext.remaining          // Number - Remaining free attempts
  userContext.limit              // Number - Total free attempt limit

  // User info
  userContext.email              // String - User's email (if signed in)
  userContext.name               // String - User's name (if available)
  userContext.user               // Object - Full Supabase user object

  // Subscription details (only for subscription users)
  userContext.subscriptionType          // 'monthly', 'quarterly', 'yearly'
  userContext.subscriptionId            // String - Subscription ID
  userContext.subscriptionExpiresAt     // ISO date string - When subscription expires
  userContext.subscriptionDaysRemaining // Number - Days until expiry
  userContext.isSubscriptionActive      // true/false - Active or cancelled
  userContext.nextBillingDate           // ISO date string - Next billing date
  userContext.billingFrequency          // String - 'monthly', 'quarterly', etc.
  userContext.customerId                // String - Customer ID for portal access

});
```

### UserContext for Different Payment Types

**One-Time Payment:**
```javascript
{
  isPremium: true,
  subscriptionType: 'one-time',  // Indicates one-time payment
  // No subscription fields (expiresAt, nextBillingDate, etc.)
}
```

**Subscription Payment:**
```javascript
{
  isPremium: true,
  subscriptionType: 'monthly',            // or 'quarterly', 'yearly'
  subscriptionId: 'sub_xxx',
  subscriptionExpiresAt: '2026-02-06',
  subscriptionDaysRemaining: 31,
  isSubscriptionActive: true,             // false if cancelled
  nextBillingDate: '2026-02-06',
  billingFrequency: 'monthly',
  customerId: 'cus_xxx'                   // For customer portal
}
```

**Note:** Payment amounts, transaction IDs, and billing details are available in your Dodo Payments dashboard.

---

## Common Patterns

### Pattern 1: Premium vs Free

```javascript
window.moneyBar.attachToButton('#export-btn', (userContext) => {
  if (userContext.isPremium) {
    // Full access
    exportHighQuality();
  } else {
    // Limited access
    exportLowQuality();
  }
});
```

### Pattern 2: Check Remaining Attempts

```javascript
window.moneyBar.attachToButton('#convert-btn', (userContext) => {
  if (userContext.isPremium) {
    convert();
  } else if (userContext.remaining > 0) {
    convert();
    console.log(`${userContext.remaining} attempts left`);
  } else {
    console.log('No attempts remaining - upgrade required');
  }
});
```

### Pattern 3: Multiple Buttons

```javascript
// Attach to multiple buttons with the same logic
window.moneyBar.attachToButton('#btn1', handleAction);
window.moneyBar.attachToButton('#btn2', handleAction);
window.moneyBar.attachToButton('#btn3', handleAction);

function handleAction(userContext) {
  if (userContext.isPremium) {
    performAction();
  }
}
```

### Pattern 4: Different Actions per Button

```javascript
// Button 1: Export
window.moneyBar.attachToButton('#export-btn', (ctx) => {
  if (ctx.isPremium) exportPDF();
});

// Button 2: Convert
window.moneyBar.attachToButton('#convert-btn', (ctx) => {
  if (ctx.isPremium) convertImage();
});
```

---

## Integrating Your Existing Functions

MoneyBar works with your existing functions. Here's how to integrate them properly:

### Basic Integration

```javascript
// Your existing function
async function generatePDF(userId, template) {
  const response = await fetch('/api/generate-pdf', {
    method: 'POST',
    body: JSON.stringify({ userId, template })
  });

  if (!response.ok) {
    throw new Error('PDF generation failed');
  }

  return await response.json();
}

// Integrate with MoneyBar
window.moneyBar.attachToButton('#generate-pdf-btn', async (userContext) => {

  if (userContext.isPremium) {
    // Premium: Unlimited access
    try {
      const result = await generatePDF(userContext.email, 'premium-template');
      alert('✅ PDF generated successfully!');
    } catch (error) {
      alert('❌ Error: ' + error.message);
      throw error; // Re-throw to prevent count increment
    }

  } else {
    // Free: Limited attempts
    try {
      const result = await generatePDF(userContext.email, 'basic-template');
      const remaining = userContext.remaining - 1;
      alert(`✅ PDF generated! ${remaining} attempts remaining.`);
    } catch (error) {
      alert(`❌ Error: ${error.message}\n\nYou still have ${userContext.remaining} attempts.`);
      throw error; // Re-throw to prevent count increment
    }
  }
});
```

### How Success/Error Handling Works

**Critical: Count increments ONLY if your function succeeds**

```javascript
window.moneyBar.attachToButton('#my-btn', async (userContext) => {

  try {
    // Your async function runs FIRST
    await yourCustomFunction();

    // ✅ SUCCESS PATH:
    // - Function completed without error
    // - MoneyBar increments count AFTER this
    // - User consumed 1 attempt

  } catch (error) {
    // ❌ ERROR PATH:
    // - Function threw an error
    // - Re-throw the error to prevent count increment
    // - User does NOT consume an attempt

    console.error('Function failed:', error);
    throw error; // IMPORTANT: Re-throw to prevent count increment
  }
});
```

### Real-World Example: API Call with Error Handling

```javascript
// Your existing API function
async function processImage(imageUrl, format) {
  const response = await fetch('https://api.yourapp.com/process', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ imageUrl, format })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.message || 'Processing failed');
  }

  return await response.json();
}

// Integrate with MoneyBar
window.moneyBar.attachToButton('#process-image-btn', async (userContext) => {

  const imageUrl = document.getElementById('image-url').value;
  const format = userContext.isPremium ? 'high-quality' : 'standard';

  try {
    const result = await processImage(imageUrl, format);

    // Success
    if (userContext.isPremium) {
      alert('✅ Image processed in high quality!');
    } else {
      const remaining = userContext.remaining - 1;
      alert(`✅ Image processed! ${remaining} attempts remaining.`);
    }

  } catch (error) {
    // Error - API failed, network issue, etc.
    if (userContext.isPremium) {
      alert('❌ Processing failed: ' + error.message);
    } else {
      alert(`❌ Processing failed: ${error.message}\n\nNo attempt used. You still have ${userContext.remaining} attempts.`);
    }

    throw error; // Re-throw to prevent count increment
  }
});
```

### Key Points

1. **Async Functions**: MoneyBar automatically waits for async functions to complete
2. **Error Handling**: If your function throws an error, count does NOT increment
3. **Re-throw Errors**: Always `throw error;` in catch blocks to prevent count increment
4. **Success/Error Feedback**: Show different messages based on outcome and user type
5. **userContext.isPremium**: Use this to provide different features/quality for premium users

---

## What MoneyBar Handles Automatically

✅ **Authentication** - Google sign-in (more providers coming)
✅ **Usage Tracking** - Counts attempts per user per app
✅ **Payment Flow** - Checkout, webhooks, confirmation
✅ **Subscription Management** - Auto-renewal, expiry, cancellation
✅ **Customer Portal** - Self-service subscription management
✅ **Caching** - 24h cache for premium status, 60s for usage counts
✅ **Multi-app Support** - Same user, different apps, separate limits

---

## Configuration Reference

| Setting | Required | Purpose | Example |
|---------|----------|---------|---------|
| `security_key` | ✅ Yes | App identifier + auth | `'sk_...'` |
| `actionLabel` | ✅ Yes | What users are doing | `'PDF Generations'` |
| `freeAttemptLimit` | ✅ Yes | Free attempts before paywall | `3` |
| `payment` | ⬜ Optional | Payment provider config | Dodo Payments |
| `feedback` | ⬜ Optional | Collect user feedback | Email to you |
| `theme` | ⬜ Optional | Customize colors | DaisyUI themes |
| `titleBar` | ⬜ Optional | Navbar title & links | Your branding |
| `supabase` | ✅ Yes | Backend (DO NOT CHANGE) | Pre-configured |

---

## Getting Your Security Key

**MoneyBar is currently in private beta.**

To get a security key for beta testing, please contact the authors:
- **Email**: ravi@cloudworks.com
- **GitHub**: [Open an issue](https://github.com/ravi-cloudworks/moneybar/issues)

We'll provide you with a security key and help you get set up!

---

## Support

- **Issues**: [GitHub Issues](https://github.com/ravi-cloudworks/moneybar/issues)
- **Docs**: [moneybar.online](https://moneybar.online)

---

## License

MIT