Skip to content

VueStripeExpressCheckoutElement ​

Displays wallet payment buttons like Apple Pay, Google Pay, and Link for one-click checkout.

Standalone Element

Unlike VueStripeLinkAuthenticationElement, Express Checkout can process payments independently. It handles the full payment flow through the wallet's native interface.

Requires clientSecret

Express Checkout Element requires a clientSecret from a PaymentIntent. Ensure StripeElements has a valid clientSecret configured.

What This Element Does ​

CapabilityDescription
Wallet ButtonsDisplays Apple Pay, Google Pay, and Link buttons
One-Click CheckoutCustomers pay with saved wallet payment methods
Full Payment FlowHandles payment authorization through wallet interface
Shipping CollectionCan collect shipping address and rates (optional)

Wallet Availability ​

WalletRequirements
Apple PaySafari on macOS/iOS with Apple Wallet configured
Google PayChrome/Android with Google Pay configured
LinkCustomer has a Stripe Link account

Device-Dependent

Express Checkout buttons only appear when wallets are available on the customer's device. Testing typically requires a real device with a configured wallet.

How It Works ​

mermaid
flowchart TD
    A["ExpressCheckoutElement mounts and emits @ready<br/>{ availablePaymentMethods: { applePay, googlePay, link } }"] --> B{Wallets Available?}
    B -->|Yes| C["Shows wallet buttons<br/>(Apple Pay, etc.)"]
    B -->|No| D["Element appears empty<br/>(use PaymentElement below)"]
    C --> E["Customer clicks a wallet button<br/>Emits @click with { expressPaymentType, resolve }"]
    E --> F["<b>CRITICAL:</b> Must call event.resolve()<br/>to show payment sheet<br/>Pass line items, shipping options if needed"]
    F --> G["Native wallet UI appears<br/>(Apple Pay sheet, Google Pay, etc.)<br/>Customer authenticates (Face ID, fingerprint, etc.)"]
    G --> H{Customer Action?}
    H -->|Confirms| I["<b>Customer Confirms</b><br/>Emits @confirm<br/>Call confirmPayment()"]
    H -->|Cancels| J["<b>Customer Cancels</b><br/>Emits @cancel<br/>Show PaymentElement option"]

Usage ​

Basic Usage ​

vue
<template>
  <VueStripeProvider :publishable-key="publishableKey">
    <VueStripeElements :client-secret="clientSecret">
      <VueStripeExpressCheckoutElement
        :options="options"
        @ready="onReady"
        @click="onClick"
        @confirm="onConfirm"
      />
    </VueStripeElements>
  </VueStripeProvider>
</template>

<script setup>
import { ref } from 'vue'
import {
  VueStripeProvider,
  VueStripeElements,
  VueStripeExpressCheckoutElement
} from '@vue-stripe/vue-stripe'

const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY
const clientSecret = 'pi_xxx_secret_xxx' // From your backend

const options = {
  buttonType: {
    applePay: 'buy',
    googlePay: 'buy'
  },
  buttonTheme: {
    applePay: 'black',
    googlePay: 'black'
  }
}

const onReady = (event) => {
  // Check which wallets are available
  console.log('Available:', event.availablePaymentMethods)
}

const onClick = (event) => {
  // Required: Call resolve() to show payment sheet
  event.resolve({})
}

const onConfirm = async (event) => {
  // Payment authorized - confirm with your backend
  console.log('Payment type:', event.expressPaymentType)
}
</script>

Props ​

PropTypeRequiredDescription
optionsStripeExpressCheckoutElementOptionsNoElement configuration options

Options Object ​

ts
interface StripeExpressCheckoutElementOptions {
  buttonType?: {
    applePay?: 'plain' | 'buy' | 'check-out' | 'subscribe' | 'add-money' | 'book' | 'contribute' | 'donate' | 'order' | 'reload' | 'rent' | 'support' | 'tip' | 'top-up'
    googlePay?: 'plain' | 'buy' | 'book' | 'checkout' | 'donate' | 'order' | 'pay' | 'subscribe'
  }
  buttonTheme?: {
    applePay?: 'black' | 'white' | 'white-outline'
    googlePay?: 'black' | 'white'
  }
  layout?: {
    maxColumns?: number  // Max buttons per row
    maxRows?: number     // Max rows to display
    overflow?: 'auto' | 'never'
  }
  paymentMethods?: {
    applePay?: 'auto' | 'never'
    googlePay?: 'auto' | 'never'
    link?: 'auto' | 'never'
  }
}

Events ​

EventPayloadDescription
@readyStripeExpressCheckoutElementReadyEventEmitted when mounted, includes available wallets
@clickStripeExpressCheckoutElementClickEventEmitted when a wallet button is clicked
@confirmStripeExpressCheckoutElementConfirmEventEmitted when payment is authorized
@cancel-Emitted when customer cancels payment sheet
@shippingaddresschangeStripeExpressCheckoutElementShippingAddressChangeEventEmitted when shipping address changes. Call event.resolve() with updated line items/shipping rates, or event.reject() if the address is unsupported
@shippingratechangeStripeExpressCheckoutElementShippingRateChangeEventEmitted when shipping rate changes. Call event.resolve() with updated line items, or event.reject() if the rate is unsupported
@loaderror{ elementType: 'expressCheckout', error: StripeError }Emitted when the element fails to load

Ready Event ​

ts
interface StripeExpressCheckoutElementReadyEvent {
  availablePaymentMethods?: {
    applePay?: boolean
    googlePay?: boolean
    link?: boolean
  }
}

Click Event (Critical!) ​

The @click event must call event.resolve() to continue with the payment sheet:

ts
interface StripeExpressCheckoutElementClickEvent {
  expressPaymentType: 'apple_pay' | 'google_pay' | 'link'
  resolve: (options?: ClickResolveOptions) => void
}

interface ClickResolveOptions {
  lineItems?: Array<{
    name: string
    amount: number
  }>
  shippingOptions?: Array<{
    id: string
    label: string
    amount: number
  }>
  applePay?: {
    recurringPaymentRequest?: { ... }
  }
}

Confirm Event ​

ts
interface StripeExpressCheckoutElementConfirmEvent {
  expressPaymentType: 'apple_pay' | 'google_pay' | 'link'
  paymentMethodId?: string
  billingDetails?: {
    name?: string
    email?: string
    phone?: string
    address?: { ... }
  }
  shippingAddress?: { ... }
  shippingRate?: { ... }
}

Exposed Properties ​

PropertyTypeDescription
elementRef<StripeExpressCheckoutElement | null>The Stripe element instance
loadingRef<boolean>Whether the element is loading
errorRef<string | null>Error message if creation failed

Slots ​

Loading Slot ​

vue
<VueStripeExpressCheckoutElement>
  <template #loading>
    <div class="skeleton">Loading wallet buttons...</div>
  </template>
</VueStripeExpressCheckoutElement>

Error Slot ​

vue
<VueStripeExpressCheckoutElement>
  <template #error="{ error }">
    <div class="error">{{ error }}</div>
  </template>
</VueStripeExpressCheckoutElement>

Examples ​

Click Event Handling ​

The click event must resolve before the payment sheet appears:

vue
<script setup>
const onClick = (event) => {
  // Simple case: just show payment sheet
  event.resolve({})
}

// Or with line items (for Apple Pay)
const onClickWithItems = (event) => {
  event.resolve({
    lineItems: [
      { name: 'Product', amount: 1999 },
      { name: 'Tax', amount: 200 }
    ]
  })
}

// Or with shipping options
const onClickWithShipping = (event) => {
  event.resolve({
    shippingOptions: [
      { id: 'standard', label: 'Standard Shipping', amount: 500 },
      { id: 'express', label: 'Express Shipping', amount: 1500 }
    ]
  })
}
</script>

Confirm and Payment Completion ​

vue
<script setup>
import { useStripe, useStripeElements } from '@vue-stripe/vue-stripe'

// Must be inside StripeElements context
const { stripe } = useStripe()
const { elements } = useStripeElements()

const onConfirm = async (event) => {
  if (!stripe.value || !elements.value) return

  const { error } = await stripe.value.confirmPayment({
    elements: elements.value,
    clientSecret,
    confirmParams: {
      return_url: window.location.href
    },
    redirect: 'if_required'
  })

  if (error) {
    console.error('Payment failed:', error.message)
  } else {
    console.log('Payment succeeded!')
  }
}
</script>

Shipping Address Collection ​

vue
<script setup>
const options = {
  // Enable shipping collection in Payment Intent options
}

const onShippingAddressChange = (event) => {
  // event.address / event.name describe the customer's selected address
  console.log('Shipping to:', event.name, event.address)

  // Recalculate shipping rates based on the address, then either:
  event.resolve({
    shippingRates: [
      { id: 'standard', displayName: 'Standard Shipping', amount: 500 },
      { id: 'express', displayName: 'Express Shipping', amount: 1500 }
    ]
  })
  // ...or reject if the address is unsupported:
  // event.reject()
}

const onShippingRateChange = (event) => {
  // event.shippingRate is the rate the customer selected
  console.log('Selected shipping:', event.shippingRate.displayName)

  // Update line items for the new rate, then resolve (or reject if unsupported)
  event.resolve({
    lineItems: [
      { name: 'Subtotal', amount: 1999 },
      { name: 'Shipping', amount: event.shippingRate.amount }
    ]
  })
}
</script>

<template>
  <VueStripeExpressCheckoutElement
    @shippingaddresschange="onShippingAddressChange"
    @shippingratechange="onShippingRateChange"
  />
</template>

Custom Button Styling ​

vue
<script setup>
const options = {
  buttonType: {
    applePay: 'check-out',  // "Check out with Apple Pay"
    googlePay: 'checkout'    // "Checkout"
  },
  buttonTheme: {
    applePay: 'white-outline',
    googlePay: 'white'
  },
  layout: {
    maxColumns: 2,
    maxRows: 1
  }
}
</script>

Place Express Checkout above PaymentElement as a fast-path option:

vue
<template>
  <VueStripeProvider :publishable-key="publishableKey">
    <VueStripeElements :client-secret="clientSecret">
      <!-- Fast checkout option -->
      <div class="express-section">
        <h3>Express Checkout</h3>
        <VueStripeExpressCheckoutElement
          @click="e => e.resolve({})"
          @confirm="onExpressConfirm"
        />
      </div>

      <div class="divider">Or pay with card</div>

      <!-- Traditional payment fallback -->
      <div class="card-section">
        <VueStripePaymentElement @change="onChange" />
        <button @click="handleSubmit">Pay</button>
      </div>
    </VueStripeElements>
  </VueStripeProvider>
</template>

Detect Wallet Availability ​

vue
<script setup>
import { ref } from 'vue'

const hasWallets = ref(false)
const availableWallets = ref([])

const onReady = (event) => {
  if (event.availablePaymentMethods) {
    const methods = Object.entries(event.availablePaymentMethods)
      .filter(([, available]) => available)
      .map(([method]) => method)

    availableWallets.value = methods
    hasWallets.value = methods.length > 0
  }
}
</script>

<template>
  <div>
    <VueStripeExpressCheckoutElement @ready="onReady" />

    <div v-if="!hasWallets" class="no-wallets-message">
      <p>No wallet payment methods available on this device.</p>
      <p>Use the card payment form below instead.</p>
    </div>
  </div>
</template>

TypeScript ​

ts
import { ref } from 'vue'
import { VueStripeExpressCheckoutElement } from '@vue-stripe/vue-stripe'
import type {
  StripeExpressCheckoutElement as StripeExpressCheckoutElementType,
  StripeExpressCheckoutElementOptions,
  StripeExpressCheckoutElementReadyEvent,
  StripeExpressCheckoutElementClickEvent,
  StripeExpressCheckoutElementConfirmEvent,
  StripeExpressCheckoutElementShippingAddressChangeEvent,
  StripeExpressCheckoutElementShippingRateChangeEvent,
  StripeError
} from '@stripe/stripe-js'

// Options
const options: StripeExpressCheckoutElementOptions = {
  buttonType: {
    applePay: 'buy',
    googlePay: 'buy'
  },
  buttonTheme: {
    applePay: 'black',
    googlePay: 'black'
  },
  layout: {
    maxColumns: 3,
    maxRows: 1
  }
}

// Event handlers
const handleReady = (event: StripeExpressCheckoutElementReadyEvent) => {
  console.log('Available wallets:', event.availablePaymentMethods)
}

const handleClick = (event: StripeExpressCheckoutElementClickEvent) => {
  event.resolve({})
}

const handleConfirm = (event: StripeExpressCheckoutElementConfirmEvent) => {
  console.log('Payment type:', event.expressPaymentType)
}

const handleShippingAddressChange = (
  event: StripeExpressCheckoutElementShippingAddressChangeEvent
) => {
  // event.resolve({ shippingRates, lineItems }) or event.reject()
  event.resolve({})
}

const handleShippingRateChange = (
  event: StripeExpressCheckoutElementShippingRateChangeEvent
) => {
  // event.resolve({ lineItems }) or event.reject()
  event.resolve({})
}

const handleLoadError = (
  event: { elementType: 'expressCheckout'; error: StripeError }
) => {
  console.error('Express checkout failed to load:', event.error.message)
}

// Template ref
const expressCheckoutRef = ref<StripeExpressCheckoutElementType | null>(null)

Comparison with PaymentElement ​

FeatureExpress CheckoutPayment Element
Use CaseFast-path for wallet usersFull payment method support
User ActionOne-click (saved wallet)Enter payment details
Wallet SupportPrimary focusSecondary option
Card InputNoYes
Bank TransfersNoYes
PlacementTop of checkoutPrimary payment form

Requirements ​

RequirementDetails
clientSecretFrom PaymentIntent
HTTPSRequired (except localhost)
Wallet EnabledMust be enabled in Stripe Dashboard
Real DeviceWallet buttons typically require real devices with configured wallets

Pairing Recommendation ​

Recommended Pattern

Place Express Checkout Element above StripePaymentElement to offer customers a fast checkout option while maintaining a fallback for those without wallets configured.

┌─────────────────────────────────────┐
│  Express Checkout (optional fast    │
│  path for wallet users)             │
├─────────────────────────────────────┤
│  ─── Or pay with card ───           │
├─────────────────────────────────────┤
│  Payment Element (full payment      │
│  form for all customers)            │
└─────────────────────────────────────┘
ComponentRelationship
StripeElementsRequired parent - Provides Elements context
StripePaymentElementRecommended pair - Fallback payment form
StripeLinkAuthenticationElementAlternative - Email-based Link authentication

See Also ​

Released under the MIT License.