Managing Payment Methods

Shopkeeper provides methods to store, retrieve, update, and delete your customers' payment methods. The approach differs slightly depending on whether the payment method is for a subscription or a one-off charge.

Payment methods for subscriptions

When storing a payment method for future subscription use, you need to use Stripe's Setup Intents API. A Setup Intent securely collects payment details without making an immediate charge.

Create a Setup Intent and pass it to your frontend:

router.get('/payment-method', async ({ auth, view }) => {
  const user = auth.getUserOrFail()

  return view.render('pages/payment-method', {
    intent: await user.createSetupIntent(),
  })
})

In your frontend, use Stripe.js to mount a card element and confirm the setup:

<input id="card-holder-name" type="text" />
<div id="card-element"></div>
<button id="card-button" data-secret="{{ intent.client_secret }}">Save Payment Method</button>

<script src="https://js.stripe.com/v3/"></script>
<script>
  const stripe = Stripe('your-stripe-public-key')
  const elements = stripe.elements()
  const cardElement = elements.create('card')
  cardElement.mount('#card-element')

  const cardButton = document.getElementById('card-button')
  cardButton.addEventListener('click', async () => {
    const { setupIntent, error } = await stripe.confirmCardSetup(cardButton.dataset.secret, {
      payment_method: {
        card: cardElement,
        billing_details: {
          name: document.getElementById('card-holder-name').value,
        },
      },
    })

    if (error) {
      // Show error.message to the user
    } else {
      // Send setupIntent.payment_method to your server
    }
  })
</script>

Once you have the payment method ID from the frontend, you can add it to the customer or use it to create a subscription.

For more details, see Stripe's Setup Intents documentation.

Payment methods for single charges

For one-off charges, you don't need a Setup Intent. Instead, use Stripe.js to create a payment method directly:

<input id="card-holder-name" type="text" />
<div id="card-element"></div>
<button id="card-button">Pay</button>

<script src="https://js.stripe.com/v3/"></script>
<script>
  const stripe = Stripe('your-stripe-public-key')
  const elements = stripe.elements()
  const cardElement = elements.create('card')
  cardElement.mount('#card-element')

  const cardButton = document.getElementById('card-button')
  cardButton.addEventListener('click', async () => {
    const { paymentMethod, error } = await stripe.createPaymentMethod('card', cardElement, {
      billing_details: {
        name: document.getElementById('card-holder-name').value,
      },
    })

    if (error) {
      // Show error.message to the user
    } else {
      // Send paymentMethod.id to your server to process the charge
    }
  })
</script>

Pass the paymentMethod.id to your server and use it with the charge methods.

Due to Stripe limitations, you cannot use a customer's stored default payment method for single charges. The customer must enter their payment details each time.

Retrieving payment methods

List all payment methods for a customer:

const paymentMethods = await user.paymentMethods()

To filter by type (e.g., sepa_debit, card):

const paymentMethods = await user.paymentMethods('sepa_debit')

To retrieve the default payment method:

const defaultMethod = await user.defaultPaymentMethod()

To find a specific payment method by its Stripe ID:

const paymentMethod = await user.findPaymentMethod('pm_xxxxxxx')

Checking for payment methods

// Has a default payment method?
if (await user.hasDefaultPaymentMethod()) {
  // ...
}

// Has any payment method?
if (await user.hasPaymentMethod()) {
  // ...
}

// Has a payment method of a specific type?
if (await user.hasPaymentMethod('sepa_debit')) {
  // ...
}

Adding payment methods

To attach a new payment method to a customer:

await user.addPaymentMethod('pm_xxxxxxx')

Updating the default payment method

To set a new default payment method:

await user.updateDefaultPaymentMethod('pm_xxxxxxx')

To sync the default payment method from Stripe (useful if it was changed outside your application):

await user.updateDefaultPaymentMethodFromStripe()

The default payment method is used for invoices and new subscriptions. Due to Stripe limitations, it cannot be used for single charges.

Deleting payment methods

Delete a specific payment method:

// From the PaymentMethod instance
const paymentMethod = await user.findPaymentMethod('pm_xxxxxxx')
await paymentMethod.delete()

// Or by ID
await user.deletePaymentMethod('pm_xxxxxxx')

Delete all payment methods:

await user.deletePaymentMethods()

To delete only payment methods of a specific type:

await user.deletePaymentMethods('sepa_debit')

If a customer has an active subscription, do not allow them to delete their default payment method.

Going further

On this page