Looking to see if I can utilise Stripe Elements on a Vue SPA single file component. However, I am challenged by the following error:
IntegrationError: Missing argument. Make sure to call mount() with a valid DOM element or selector.
at new t (https://js.stripe.com/v3/:1:10765)
at t.<anonymous> (https://js.stripe.com/v3/:1:97008)
at t.<anonymous> (https://js.stripe.com/v3/:1:26038)
at VueComponent.createCardElement (webpack-internal:///./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/stripe/card-modal.vue?vue&type=script&lang=js&:143:17)
at VueComponent.stripePubKey (webpack-internal:///./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/stripe/card-modal.vue?vue&type=script&lang=js&:177:14)
at Watcher.run (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:4562:19)
at flushSchedulerQueue (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:4304:13)
at Array.eval (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:1979:12)
at flushCallbacks (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:1905:14)
The code executes, failing right at the .mount() function, whining about an inexistent DOM element/selector.
Below are the following methods I've attempted:
- The code is executed post-mounted of the component's lifecycle (My component is a Buefy modal and the stripe element mount logic executes when the modal is visible to the user).
- The code is executed immediately according to Stripe's official Vue example
- The code is executed immediately after the property 'stripePubKey' is not null (Notice that the 'stripePubKey' is updated at the mounted segment of the Vue lifecycle).
Here's the component as of scenario 3
<template>
<div>
<b-button class="button is-info is-rounded"
size="is-medium"
icon-left="credit-card"
:loading="isLoading"
@click="isModalActive = true" v-if="!cardId">
Add a card
</b-button>
<button class="button is-warning"
:loading="isLoading"
@click="isModalActive = true" v-else>
Edit
</button>
<b-modal has-modal-card trap-focus :active.sync="isModalActive">
<b-loading :active.sync="isModalLoading" :can-cancel="false" />
<!--https://stackoverflow.com/questions/48028718/using-event-modifier-prevent-in-vue-to-submit-form-without-redirection-->
<form v-on:submit.prevent="create()" class="has-text-justified">
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title" v-if="!cardId">Add a card</p>
<p class="modal-card-title" v-else>Edit a card</p>
</header>
<section class="modal-card-body">
<div ref="cardo"></div>
<p v-show="elementsError" id="card-errors" v-text="elementsError" />
</section>
<footer class="modal-card-foot">
<button class="button" type="button" @click="isModalActive = false">Close</button>
<button class="button is-primary" type="submit" :disabled="!complete">Add</button>
</footer>
</div>
</form>
</b-modal>
</div>
</template>
<script>
import {mapActions, mapGetters} from 'vuex';
import {NotificationProgrammatic as Notification} from 'buefy';
import PaymentService from "@/services/auth/PaymentService";
export default {
name: "stripe-card-modal",
props: {
currentRoute: window.location.href, // https://forum.vuejs.org/t/how-to-get-path-from-route-instance/26934/2
cardId: {
type: String,
default: null
}
},
computed: {
...mapGetters('oidcStore', [
'oidcUser'
])
},
data: function () {
return {
isLoading: true,
isModalActive: false,
isModalLoading: false,
complete: false,
// Stripe variables
card: null,
elementsError: null,
paymentMethod: 'card',
stripe: null,
stripePubKey: ''
}
},
methods: {
...mapActions('oidcStore', ['authenticateOidc', 'signOutOidc']),
createCardElement: function() {
let self = this;
// Check if stripe is up, else set it up
if (!self.stripe && self.stripePubKey) {
self.stripe = Stripe(self.stripePubKey);
} else {
return;
}
// Get stripe elements up
const elements = self.stripe.elements({
// Use Roboto from Google Fonts
fonts: [
{
cssSrc: 'https://fonts.googleapis.com/css?family=Roboto',
},
],
// Detect the locale automatically
locale: 'auto',
});
// Define CSS styles for Elements
const style = {
base: {
fontSize: '15px',
fontFamily: 'Roboto',
fontSmoothing: 'antialiased',
color: '#525f7f',
'::placeholder': {
color: '#AAB7C4',
},
},
// Styles when the Element has invalid input
invalid: {
color: '#cc5b7a',
iconColor: '#cc5b7a'
}
};
// Create the card element, then attach it to the DOM
self.card = elements.create('card', {style});
self.card.mount(this.$refs.cardo);
// Add an event listener: check for error messages as we type
self.card.addEventListener('change', ({error}) => {
if (error) {
self.elementsError = error.message;
} else {
self.elementsError = '';
}
});
},
create: function () {
this.isModalLoading = true;
let self = this;
},
},
mounted: function() {
let self = this;
PaymentService.getStripePubKey()
.then(function(res) {
self.stripePubKey = res.data;
})
.finally(function() {
self.isLoading = false;
});
},
watch: {
stripePubKey(newVal, oldVal) {
let self = this;
if (newVal && !oldVal) { // If the modal is up
self.createCardElement();
}
}
},
}
</script>
Additionally, the component file given above attempts to mount the card element via $refs while the official Stripe VueJS sample uses '#card-element' to do so. That was already attempted as well!
Here's my index.html for my SPA just in case (I have also attempted to place it at the end of the tag.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Roboto:100:300,400,500,700,900|Material+Icons" rel="stylesheet">
<title>Nozomi</title>
</head>
<body>
<noscript>
<strong>We're sorry but Nozomi doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
<script src="https://js.stripe.com/v3/"></script>
</html>