Accepting Payments

The easiest way to start accepting payments is with Stripe Checkout, a hosted payment page that handles card forms, validation, and 3D Secure for you. This guide covers the different checkout flows available in Shopkeeper.

Product checkout

To charge a customer for an existing product, use the checkout method on a billable model. Chain addLineItem and sessionParams on the returned builder:

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

router.get('/buy', async ({ auth, response }) => {
  const user = auth.getUserOrFail()
  const checkout = await user
    .checkout()
    .addLineItem('price_tshirt')
    .sessionParams({
      success_url: urlFor('checkout.success'),
      cancel_url: urlFor('checkout.cancel'),
    })

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

You can also specify a quantity:

const checkout = await user
  .checkout()
  .addLineItem('price_tshirt', 5)
  .sessionParams({
    success_url: urlFor('checkout.success'),
    cancel_url: urlFor('checkout.cancel'),
  })

Redirect URLs

success_url and cancel_url are required. The builder throws an InvalidArgumentError if they are missing.

Retrieving the checkout session

If you need to access the checkout session on the success page (for example, to display the order details), you can use the {CHECKOUT_SESSION_ID} placeholder in your success_url. Stripe will replace it with the actual session ID:

import { urlFor } from '@adonisjs/core/services/url_builder'
import shopkeeper from '@foadonis/shopkeeper/services/shopkeeper'

router.get('/buy', async ({ auth, response }) => {
  const user = auth.getUserOrFail()
  const checkout = await user
    .checkout()
    .addLineItem('price_tshirt')
    .sessionParams({
      success_url: urlFor('checkout.success', { session_id: '{CHECKOUT_SESSION_ID}' }),
      cancel_url: urlFor('checkout.cancel'),
    })

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

router
  .get('/checkout/success', async ({ request, view }) => {
    const qs = request.qs()
    const stripe = shopkeeper.stripe
    const session = await stripe.checkout.sessions.retrieve(qs.session_id)

    return view.render('checkout/success', { session })
  })
  .as('checkout.success')

Subscription checkout

To start a subscription through Stripe Checkout, use the subscription builder's checkout method:

import { urlFor } from '@adonisjs/core/services/url_builder'

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

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

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

Subscription checkout requires the customer.subscription.created webhook event to be enabled in your Stripe dashboard. This webhook creates the subscription record in your database. See Handling Webhooks for setup instructions.

Trial periods

You can offer a trial period on a subscription checkout using trialDays:

const checkout = await user
  .newSubscription('default', 'price_monthly')
  .trialDays(7)
  .checkout({
    success_url: urlFor('checkout.success'),
    cancel_url: urlFor('checkout.cancel'),
  })
Stripe Checkout requires a minimum trial period of 48 hours.

To learn more about trials, see the Offering Trials guide.

Promotion codes

By default, Stripe Checkout does not show a promotion code field. You can enable it with allowPromotionCodes:

const checkout = await user
  .checkout()
  .addLineItem('price_tshirt')
  .withAllowPromotionsCodes()
  .sessionParams({
    success_url: urlFor('checkout.success'),
    cancel_url: urlFor('checkout.cancel'),
  })

This also works for subscription checkouts:

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

Single charge checkout

If you need to sell a product that doesn't exist in your Stripe dashboard, you can use checkoutCharge with an amount, a product name, and an optional quantity:

router.get('/buy-custom', async ({ auth, response }) => {
  const user = auth.getUserOrFail()
  const checkout = await user.checkoutCharge(1200, 'T-Shirt', 5).sessionParams({
    success_url: urlFor('checkout.success'),
    cancel_url: urlFor('checkout.cancel'),
  })

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

checkoutCharge creates a new product and price in your Stripe dashboard every time it is called. For products you sell regularly, create them in the Stripe dashboard and use the checkout method instead.

Collecting tax IDs

To let customers provide their tax ID during checkout, use withTaxIdsCollect on the builder:

const checkout = await user
  .checkout()
  .addLineItem('price_tshirt')
  .withTaxIdsCollect()
  .sessionParams({
    success_url: urlFor('checkout.success'),
    cancel_url: urlFor('checkout.cancel'),
  })

If you have automatic tax collection enabled, the tax ID field is shown automatically.

Guest checkouts

You can create checkout sessions for visitors who don't have an account using Checkout.guest():

import { Checkout } from '@foadonis/shopkeeper'
import { urlFor } from '@adonisjs/core/services/url_builder'

router.get('/guest-checkout', async ({ response }) => {
  const checkout = await Checkout.guest()
    .addLineItem('price_tshirt')
    .sessionParams({
      success_url: urlFor('checkout.success'),
      cancel_url: urlFor('checkout.cancel'),
    })

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

You can also apply promotion codes to guest checkouts:

const checkout = await Checkout.guest()
  .addLineItem('price_tshirt')
  .withPromotionCode('promo_code_id')
  .sessionParams({
    success_url: urlFor('checkout.success'),
    cancel_url: urlFor('checkout.cancel'),
  })

After a guest checkout completes, Stripe dispatches a checkout.session.completed webhook event. Make sure this event is enabled in your Stripe webhook configuration so you can fulfill the order. See Handling Webhooks to learn how to listen to this event.

Going further

On this page