@grahammorbydev Hey, I’ve been in the same boat as you! The default Stripe JavaScript SDK isn’t great to work with in that it’s asynchronous and not an NPM module you can install and import… unless React devs /sideeyes
How I’ve approached this in my own apps is to have a StripeCardElement element component that other components can use and interact with. The card element component is pretty “dumb”—all it does is initialise Stripe and the element:
<!-- resources/js/components/StripeCardElement.vue -->
<template>
<div>
<div id="card-element" ref="cardElement" />
<div id="card-error" role="alert" if="error" v-text="error" />
</div>
</template>
<script>
export default {
created() {
this.stripe = window.Stripe(this.publishableKey);
this.cardElement = this.stripe.elements().create('card');
},
data() {
return {
cardElement: null,
stripe: null
};
},
mounted() {
this.cardElement.mount(this.$refs.cardElement);
},
props: {
error: {
default: null,
required: false,
type: String
},
publishableKey: {
required: true,
type: String,
validator: (key) => key.startsWith('pk_')
}
}
}
</script>
When the component is created, it initialises Stripe, and then when mounted actually mounts the card element.
So, in other components (say, a checkout component), you can import this component, give it a reference, and interact with it. Here’s how I do that:
<!-- resources/js/components/SimpleCheckout.vue -->
<template>
<form method="POST" v-on:submit.prevent="onSubmit">
<fieldset>
<div class="form-group">
<label>Cardholder name</label>
<input type="text" name="cardholder_name" v-model.trim="cardholderName" autocomplete="cc-name" required />
</div>
<div class="form-group">
<stripe-card-element
ref="stripeCardElement"
:error="cardError"
:publishable-key="stripePublishableKey"
/>
</div>
</fieldset>
</form>
</template>
<script>
import StripeCardElement from './StripeCardElement.vue';
export default {
components: {
StripeCardElement
},
data() {
return {
cardError: null,
cardholderName: ''
};
},
methods: {
onSubmit() {
// Interact with Stripe card element here
const data = {
payment_method: {
billing_details: {
name: this.cardholderName
}
}
};
this.$refs.stripeCardElement.confirmCardPayment(clientSecret, data).then((paymentIntent) => {
console.log(paymentIntent);
// Check PaymentIntent was successful
// Show success message if it was
}).catch((error) => {
console.error(error);
this.cardError = error.message;
});
}
},
props: {
stripePublishableKey: {
required: true,
type: String
}
}
}
</script>
You may have noticed my confirmCardPayment intent looks a little different to the one in the Stripe documentation. That’s because I proxy it in the StripeCardElement component:
methods: {
confirmCardPayment(clientSecret, data) {
// Payment method can be either object or string (ID of existing payment method)
// If is object, set reference to card element
if (data.hasOwnProperty('payment_method') && typeof data.payment_method === 'object') {
data.payment_method.card = this.cardElement;
}
return new Promise((resolve, reject) => {
// Call the actual stripe.confirmCardPayment method
// Returns a Promise that either:
// a. resolves with PaymentIntent
// b. rejects with error
// Improvement on Stripe SDK, which just resolves and you have to check yourself
// if result was error or successful
this.stripe.confirmCardPayment(clientSecret, data).then((result) => {
result.error ? reject(result.error) : resolve(result.paymentIntent);
});
});
}
}
And that‘s pretty much it!
I appreciate this is a lot of code and a lot to get your head around, so if you do have any questions about the above or working with Stripe in Vue components, let me know and I’ll be happy to help!