Reputation: 1980
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:
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>
Upvotes: 0
Views: 1623
Reputation: 4930
What's happening is that the buefy model doesn't actually exist in the dom until after it's been opened and rendered.
It's a really easy fix - instead of directly setting isModalActive to true, create a method to open it and instantiate the stripe element in the next tick. This will ensure the dom element has been rendered.
methods: {
openModal() {
this.isModalActive = true;
this.$nextTick(function () {
this.createCardElement()
})
}
}
Upvotes: 1