Getting Started

This guide walks you through installing Shopkeeper, configuring your application, and accepting your first payment.

Installation

Install and configure the package using the following command:

node ace add @foadonis/shopkeeper

Migrations

Shopkeeper ships with a set of database migrations that:

  • Add billing columns (stripe_id, trial_ends_at, etc.) to your users table
  • Create a subscriptions table to track your customers' subscriptions
  • Create a subscription_items table for subscriptions with multiple prices
  • Create a stripe_webhook_events table for idempotent webhook handling

Run the migrations with:

node ace migration:run

Configuration

Billable model

Before using Shopkeeper, add the billable() mixin to your model. This is typically the User model. The mixin provides methods for common billing tasks like creating subscriptions, managing payment methods, and more.

app/models/user.ts
import { compose } from '@adonisjs/core/helpers'
import { BaseModel } from '@adonisjs/lucid/orm'
import { billable } from '@foadonis/shopkeeper/mixins'

export default class User extends compose(BaseModel, billable()) {}

Then, define your model as the customer model in the Shopkeeper configuration:

config/shopkeeper.ts
import { defineConfig } from '@foadonis/shopkeeper'

export default defineConfig({
  customerModel: () => import('#models/user'),
  // ...
})

If you use a model with a different name than User, make sure to update the table name in the generated migration accordingly.

API keys

Configure your Stripe API keys in your application's .env file. You can find your keys in the Stripe dashboard.

.env
STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret

The STRIPE_WEBHOOK_SECRET environment variable is mandatory in production. It secures the Stripe webhook endpoint and ensures incoming events are authentic.

Webhooks

Register the Shopkeeper webhook route and listeners in your application:

start/routes.ts
import shopkeeper from '@foadonis/shopkeeper/services/shopkeeper'

shopkeeper.registerRoutes()
start/events.ts
import shopkeeper from '@foadonis/shopkeeper/services/shopkeeper'

shopkeeper.registerWebhookListeners()

Then, use the shopkeeper:webhook Ace command to create the webhook in Stripe with all the required events:

node ace shopkeeper:webhook

To learn more about webhook configuration and handling custom events, see the Handling Webhooks guide.

Your first checkout

Let's accept your first payment using Stripe Checkout, a hosted payment page provided by Stripe.

Create a price

First, create a price for your product in Stripe. You can do this from the Stripe dashboard or using the Stripe CLI:

stripe prices create \
  --currency=usd \
  --unit-amount=1000 \
  -d "recurring[interval]=month" \
  -d "product_data[name]=Gold Plan"

This creates a recurring price of $10/month. Take note of the price ID (e.g., price_xxxxxxx) returned by Stripe.

Redirect to checkout

Create a route that redirects your user to the Stripe Checkout page:

start/routes.ts
import router from '@adonisjs/core/services/router'
import { urlFor } from '@adonisjs/core/services/url_builder'

router.get('/checkout', async ({ auth, response }) => {
  const user = auth.getUserOrFail()

  const checkout = await user.newSubscription('default', 'price_xxxxxxx').checkout({
    success_url: urlFor('checkout.success'),
    cancel_url: urlFor('checkout.cancel'),
  })

  return response.redirect().status(303).toPath(checkout.asStripeSession().url)
})

Handle the redirect

When a customer completes or cancels their purchase, Stripe redirects them back to your application. Create routes to handle both cases:

start/routes.ts
router
  .get('/checkout/success', async ({ view }) => {
    return view.render('checkout/success')
  })
  .as('checkout.success')

router
  .get('/checkout/cancel', async ({ view }) => {
    return view.render('checkout/cancel')
  })
  .as('checkout.cancel')

That's it! Visit /checkout in your browser and you should be redirected to Stripe's payment page. After completing the payment, Stripe will redirect back to your success page.

Stripe and Shopkeeper update subscription statuses via webhooks. There may be a short delay before the subscription is active in your database after the customer completes their payment.

What's next?

Now that you have a working checkout, explore the guides to build on top of it:

On this page