svelte-stripe
Everything you need to add Stripe Elements to your Svelte & SvelteKit projects.
Links: npm github changelog license
Installation
To configure your project, add these 3 packages:
pnpm install -D stripe @stripe/stripe-js svelte-stripe
- stripe is Stripe’s official server-side library.
- @stripe/stripe-js is Stripe’s official client-side library.
- svelte-stripe is the community-supported wrapper for Stripe Elements.
Docs
Set up Stripe
Add your private and public keys to your environment:
PUBLIC_STRIPE_KEY=pk_test_...
SECRET_STRIPE_KEY=sk_test_...
In your payment page, initialize Stripe and add a <Elements>
component:
<script>
import { loadStripe } from '@stripe/stripe-js'
import { Elements } from 'svelte-stripe'
import { onMount } from 'svelte'
import { PUBLIC_STRIPE_KEY } from '$env/static/public'
let stripe = null
onMount(async () => {
stripe = await loadStripe(PUBLIC_STRIPE_KEY)
})
</script>
<Elements {stripe}>
<!-- this is where your Stripe components go -->
</Elements>
Creating a payment intent
Before making a charge, Stripe should be notified by creating a payment intent. It’s a way to tell Stripe what amount to capture and to attach any relavent metadata, for example, the products they are buying. This must happen server-side to avoid anyone tampering with the amount.
Let’s add an endpoint src/routes/create-payment-intent/+server.js
to create the “payment intent”:
import Stripe from 'stripe'
import { SECRET_STRIPE_KEY } from '$env/static/private'
// initialize Stripe
const stripe = new Stripe(SECRET_STRIPE_KEY)
// handle POST /create-payment-intent
export async function POST() {
// create the payment intent
const paymentIntent = await stripe.paymentIntents.create({
amount: 2000,
// note, for some EU-only payment methods it must be EUR
currency: 'usd',
// specify what payment methods are allowed
// can be card, sepa_debit, ideal, etc...
payment_method_types: ['card']
})
// return the clientSecret to the client
return {
body: {
clientSecret: paymentIntent.client_secret
}
}
}
Accepting payments
There are several types of payment you can accept:
Payment Element
An all-in-one component that supports credit cards, SEPA, GooglePay and ApplePay.
To use it, drop a <PaymentElement>
component in your form:
<form on:submit|preventDefault="{submit}">
<Elements {stripe} {clientSecret} bind:elements>
<PaymentElement options={...} />
</Elements>
<button>Pay</button>
</form>
Then when creating the payment intent, enable the automatic_payment_methods:
option:
const paymentIntent = await stripe.paymentIntents.create({
amount: 2000,
currency: 'eur',
automatic_payment_methods: {
enabled: true
}
})
Once the form is submitted, call stripe.confirmPayment()
const result = await stripe.confirmPayment({
elements,
// specify redirect: 'if_required' or a `return_url`
redirect: 'if_required'
})
Link Authentication
With Link, customer’s don’t have to re-enter payment and address details for each purchase. Their details are retreived based on their e-mail address.
Once they enter their e-mail they receive an SMS code to verify their identity.
It works in conjuction with <PaymentElement>
:
<form on:submit|preventDefault="{submit}">
<Elements {stripe} {clientSecret} bind:elements>
<LinkAuthenticationElement />
<PaymentElement />
</Elements>
<button>Pay</button>
</form>
Credit Cards
These use the <CardNumber>
, <CardExpiry>
and <CardCvc>
components:
<Elements {stripe}>
<form on:submit|preventDefault="{submit}">
<CardNumber bind:element="{cardElement}" />
<CardExpiry />
<CardCvc />
<button>Pay</button>
</form>
</Elements>
When the form submits, pass the cardElement
to stripe.confirmCardPayment()
, ie:
const result = await stripe
.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
...
}
}
})
GooglePay & ApplePay
To display a GooglePay or ApplePay button, use the <PaymentRequestButton/>
.
<Elements {stripe}>
<PaymentRequestButton {paymentRequest} on:paymentmethod="{pay}" />
</Elements>
It requires that you pass metadata using the paymentRequest
prop:
// declare payment metadata (amounts must match payment intent)
const paymentRequest = {
country: 'US',
currency: 'usd',
total: { label: 'Demo total', amount: 1099 },
requestPayerName: true,
requestPayerEmail: true
}
And define an event handler for the on:paymentmethod
event:
async function pay(e) {
const paymentMethod = e.detail.paymentMethod
let result = await stripe.confirmCardPayment(clientSecret, {
payment_method: paymentMethod.id
})
if (result.error) {
// mark failed
e.detail.complete('fail')
// payment failed, notify user
error = result.error
} else {
// mark succeeded
e.detail.complete('success')
// payment succeeded, redirect to "thank you" page
goto('/thanks')
}
}
SEPA
To process SEPA debits, use the <Iban>
component:
<Elements {stripe}>
<form on:submit|preventDefault="{submit}">
<input name="name" bind:value="{name}" placeholder="Name" />
<!-- customize the list of countries, or use "SEPA" to allow all supported countries -->
<Iban supportedCountries={['SEPA']} bind:element={ibanElement}/>
<button>Pay</button>
</form>
</Elements>
To process the payment use stripe.confirmSepaDebitPayment()
:
const result = await stripe.confirmSepaDebitPayment(clientSecret, {
payment_method: {
sepa_debit: ibanElement,
billing_details: {
name,
email
}
}
})
iDEAL
To accept iDEAL payments, use the <Ideal>
component:
<Elements {stripe}>
<form on:submit|preventDefault="{submit}">
<input name="name" bind:value="{name}" placeholder="Name" />
<input name="email" bind:value="{email}" placeholder="E-mail" type="email" />
<Ideal bind:element="{idealElement}" />
<button>Pay</button>
</form>
</Elements>
To complete the payment call stripe.confirmIdealPayment()
, and make sure the pass a return_url
:
const result = await stripe.confirmIdealPayment(clientSecret, {
payment_method: {
ideal: idealElement,
billing_details: {
name,
email
}
},
return_url: `${window.location.origin}/return`
})
Webhooks
After the payment succeeds or fails, Stripe will send out a webhook, which can be used to provision or fulfill the purchase.
The webhook payload contains a signature that should be verified to ensure the data originated from Stripe.
Here’s an example of handling a charge.succeeded
webhook with SvelteKit:
// in src/routes/stripe/webhooks/+server.js
import Stripe from 'stripe'
import { error, json } from '@sveltejs/kit'
import { env } from '$env/dynamic/private'
// init api client
const stripe = new Stripe(env.SECRET_STRIPE_KEY)
// endpoint to handle incoming webhooks
export async function POST({ request }) {
// extract body
const body = await request.text()
// get the signature from the header
const signature = request.headers.get('stripe-signature')
// var to hold event data
let event
// verify it
try {
event = stripe.webhooks.constructEvent(body, signature, env.STRIPE_WEBHOOK_SECRET)
} catch (err) {
// signature is invalid!
console.warn('⚠️ Webhook signature verification failed.', err.message)
// return, because it's a bad request
throw error(400, 'Invalid request')
}
// signature has been verified, so we can process events
// full list of events: https://stripe.com/docs/api/events/list
if (event.type == 'charge.succeeded') {
// get data object
const charge = event.data.object
// TODO: fulfill the order here
console.log(`✅ Charge succeeded ${charge.id}`)
}
// return a 200 with an empty JSON response
return json()
}
In development mode, webhooks can be routed to your dev machine using Stripe’s CLI. Example:
stripe listen --forward-to localhost:5173/stripe/webhooks
For more information on webhooks, see Stripe’s Webhook Docs.
Styling
Components can be styled by setting attributes on the <Elements/>
container.
<Elements
theme="flat"
labels="floating"
variables={{ colorPrimary: 'pink' }}
rules={...}
/>
See appearance docs for more examples.
Examples
All demos are running in test-mode, any of Stripe’s test card numbers will work.
- Payment Element
- Express Checkout
- Embedded Checkout
- Credit Card
- Apple Pay
- Google Pay
- Microsoft Pay
- SEPA
- iDEAL
- Alipay
- WeChat Pay
- Konbini
- Klarna
- Sofort
- Afterpay/Clearpay
- PaymentMethodMessaging
Sponsors
This project is made possible by: