Skip to content

Commit

Permalink
Adds ExpressCheckout component
Browse files Browse the repository at this point in the history
  • Loading branch information
joshnuss committed Nov 16, 2023
1 parent 28ff2fc commit 62ace4e
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The following components are provided:
- `<Iban/>`: Input field for IBAN (International bank account number).
- `<Ideal/>`: Input field for iDEAL (payment system used in the Netherlands).
- `<PaymentElement/>`: All-in-one component that allows the user to choose the type of payment.
- `<ExpressCheckout/>`: Pay with a wallet without leaving the page.
- `<LinkAuthenticationElement/>`: Ability to use saved payment methods stored in [Link](https://link.co).
- `<Address/>`: Component for collecting billing and shipping addresses.

Expand All @@ -27,6 +28,7 @@ The following components are provided:
There is example code for:

- [Payment Element](src/routes/examples/payment-element)
- [Express Checkout](src/routes/examples/express-checkout)
- [Link](src/routes/examples/payment-element)
- [Credit card](src/routes/examples/credit-card)
- [GooglePay](src/routes/examples/payment-request)
Expand Down
73 changes: 73 additions & 0 deletions src/lib/ExpressCheckout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script>
import { mount } from './util'
import { onMount, getContext, createEventDispatcher } from 'svelte'
/** @typedef { import('@stripe/stripe-js').StripeExpressCheckoutElementOptions } Options */
/** @type {Options['buttonHeight']} */
export let buttonHeight = undefined
/** @type {Options['buttonTheme']} */
export let buttonTheme = undefined
/** @type {Options['buttonType']} */
export let buttonType = undefined
/** @type {Options['layout']} */
export let layout = undefined
/** @type {Options['paymentMethodOrder']} */
export let paymentMethodOrder = undefined
/** @type {Options['wallets']} */
export let wallets = undefined
/** @type {import('@stripe/stripe-js').StripeElementBase?} */
export let element = null
/** @type {HTMLElement?} */
let wrapper
const dispatch = createEventDispatcher()
/** @type {import("./types").ElementsContext} */
const { elements } = getContext('stripe')
onMount(() => {
const options = {
buttonHeight,
buttonTheme,
buttonType,
layout,
paymentMethodOrder,
wallets
}
element = mount(wrapper, 'expressCheckout', elements, dispatch, options)
element.on('click', (e) => dispatch('click', e))
element.on('cancel', (e) => dispatch('cancel', e))
element.on('confirm', (e) => dispatch('confirm', e))
element.on('shippingaddresschange', (e) => dispatch('shippingaddresschange', e))
element.on('shippingratechange', (e) => dispatch('shippingratechange', e))
return () => element.destroy()
})
export function blur() {
element.blur()
}
export function clear() {
element.clear()
}
export function destroy() {
element.destroy()
}
export function focus() {
element.focus()
}
</script>

<div bind:this={wrapper} />
1 change: 1 addition & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { default as PaymentElement } from './PaymentElement.svelte'
export { default as LinkAuthenticationElement } from './LinkAuthenticationElement.svelte'
export { default as Address } from './Address.svelte'
export { default as PaymentMethodMessaging } from './PaymentMethodMessaging.svelte'
export { default as ExpressCheckout } from './ExpressCheckout.svelte'
export { default as Elements } from './Elements.svelte'
export { default as Container } from './Container.svelte'
export { isServer } from './util'
1 change: 1 addition & 0 deletions src/routes/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ TODO
All demos are running in test-mode, any of Stripe's [test card numbers](https://stripe.com/docs/testing#cards) will work.

- [PaymentElement](/examples/payment-element)
- [Express Checkout](/examples/express-checkout)
- [Credit Card](/examples/credit-card)
- [Apple Pay](/examples/payment-request)
- [Google Pay](/examples/payment-request)
Expand Down
123 changes: 123 additions & 0 deletions src/routes/examples/express-checkout/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<script>
import { goto } from '$app/navigation'
import { onMount } from 'svelte'
import { loadStripe } from '@stripe/stripe-js'
import { PUBLIC_STRIPE_KEY } from '$env/static/public'
import { Elements, ExpressCheckout } from '$lib'
let stripe = null
let error = null
let elements
let processing = false
onMount(async () => {
stripe = await loadStripe(PUBLIC_STRIPE_KEY)
})
async function createPaymentIntent() {
const response = await fetch('/examples/express-checkout/payment-intent', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({})
})
const { clientSecret } = await response.json()
return clientSecret
}
async function click(event) {
const options = {
emailRequired: true,
phoneNumberRequired: true,
lineItems: [
{
name: 'Rad T-Shirt',
amount: 1099
}
]
}
event.detail.resolve(options)
}
async function confirm() {
// avoid processing duplicates
if (processing) return
processing = true
let result = await elements.submit()
if (result.error) {
// validation failed, notify user
error = result.error
processing = false
return
}
// create payment intent server side
const clientSecret = await createPaymentIntent()
const return_url = new URL('/examples/express-checkout/thanks', window.location.origin).toString()
// confirm payment with stripe
result = await stripe.confirmPayment({
elements,
clientSecret,
confirmParams: {
return_url
}
})
// log results, for debugging
console.log({ result })
if (result.error) {
// payment failed, notify user
error = result.error
processing = false
} else {
// payment succeeded, redirect to "thank you" page
goto('/examples/express-checkout/thanks')
}
}
</script>

<h1>Express Checkout Example</h1>

<nav>
<a href="https://github.com/joshnuss/svelte-stripe/tree/main/src/routes/examples/express-checkout">View code</a>
</nav>

{#if error}
<p class="error">{error.message} Please try again.</p>
{/if}

{#if stripe}
<Elements
{stripe}
mode="payment"
currency="usd"
amount={1099}
bind:elements>

<ExpressCheckout
on:confirm={confirm}
on:click={click}
buttonHeight={50}
buttonTheme={{googlePay: 'white'}}
buttonType={{googlePay: 'donate'}}
paymentMethodOrder={['googlePay', 'link']}/>

</Elements>
{:else}
Loading...
{/if}

<style>
.error {
color: tomato;
margin: 2rem 0 0;
}
</style>
19 changes: 19 additions & 0 deletions src/routes/examples/express-checkout/payment-intent/+server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { json } from '@sveltejs/kit'
import Stripe from 'stripe'
import { SECRET_STRIPE_KEY } from '$env/static/private'

const stripe = new Stripe(SECRET_STRIPE_KEY)

export async function POST() {
const paymentIntent = await stripe.paymentIntents.create({
amount: 1099,
currency: 'usd',
automatic_payment_methods: {
enabled: true
}
})

return json({
clientSecret: paymentIntent.client_secret
})
}
2 changes: 2 additions & 0 deletions src/routes/examples/express-checkout/thanks/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h1>Success!</h1>
<p>Payment was successfully processed.</p>

1 comment on commit 62ace4e

@vercel
Copy link

@vercel vercel bot commented on 62ace4e Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.