Nuxt Module
mbl-payment · v1.0.1 npm

Payments that just work.

mbl-payment wraps Stripe behind a provider abstraction — checkout sessions, subscriptions, billing portal, and webhook handling all wired up automatically. Drop it in and move on.

Install

terminal
npm install @madebylars.com/mbl-payment

What you get

💳

Stripe checkout sessions

Single-item priceId shorthand or multi-item cart — POST /api/payments/checkout handles both. Zod-validated at the boundary.

🔄

Subscriptions

Start, cancel, and track Stripe subscriptions via useSubscription(). Trial days, customer IDs, and reactive subscription state included.

🏦

Billing portal

GET /api/payments/portal returns a Stripe Billing Portal URL so customers can self-serve plan changes and invoices.

🔔

Webhook handling

POST /api/payments/webhook verifies Stripe signatures and fires Nitro hooks your server plugins can listen to — no middleware wiring needed.

🔌

Provider abstraction

Stripe ships out of the box. Implement PaymentProvider and swap in PayPal, Paddle, or any other provider without touching module internals.

📦

Auto-imported composables

useCheckout(), useSubscription(), usePayments() — all available without imports. Stripe.js is loaded client-side via a plugin.

Configuration

nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@madebylars.com/mbl-payment'],

  payments: {
    defaultProvider: 'stripe',
    providers: {
      stripe: {
        secretKey: process.env.STRIPE_SECRET_KEY,
        webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
        publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
      },
    },
  },
})

useCheckout()

pages/buy.vue
<script setup>
const { redirectToCheckout, createSession, loading, error } = useCheckout()

// Single item — priceId shorthand
await redirectToCheckout({
  priceId: 'price_...',
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel',
})

// Multi-item cart
await redirectToCheckout({
  items: [
    { priceId: 'price_abc', quantity: 2 },
    { priceId: 'price_xyz', quantity: 1 },
  ],
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel',
})

// Or get the session back without redirecting
const session = await createSession({ priceId: 'price_...', successUrl: '...', cancelUrl: '...' })
</script>

useSubscription()

pages/pricing.vue
<script setup>
const { subscribe, cancel, currentSubscription, loading, error } = useSubscription()

// Open Stripe Checkout in subscription mode
await subscribe({
  priceId: 'price_...',
  customerId: 'cus_...',
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel',
  trialDays: 14,
})

// Cancel an active subscription immediately
await cancel('sub_...')
</script>

<template>
  <div v-if="currentSubscription">
    Active until {{ new Date(currentSubscription.currentPeriodEnd * 1000).toLocaleDateString() }}
  </div>
</template>

usePayments()

pages/account.vue
<script setup>
const { stripe, getPortalUrl } = usePayments()

// Redirect to Stripe Billing Portal
const url = await getPortalUrl('cus_...')
if (url) window.location.href = url

// Raw Stripe.js instance for custom flows
stripe?.elements({ clientSecret: '...' })
</script>

Server hooks

After each verified webhook, the module fires a Nitro hook your server plugins can listen to.

server/plugins/payments.ts
// server/plugins/payments.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('payments:checkout:completed', async (data) => {
    // provision access, send welcome email, etc.
  })

  nitroApp.hooks.hook('payments:subscription:created', async (data) => { /* ... */ })
  nitroApp.hooks.hook('payments:subscription:updated', async (data) => { /* ... */ })
  nitroApp.hooks.hook('payments:subscription:cancelled', async (data) => { /* ... */ })
  nitroApp.hooks.hook('payments:payment:failed', async (data) => { /* ... */ })
})

Server API

RouteDescription
POST /api/payments/checkoutCreate a Stripe Checkout session
POST /api/payments/webhookReceive and verify Stripe webhook events
GET /api/payments/portalGet a Stripe Billing Portal URL
POST /api/payments/cancelCancel an active subscription immediately