Reputation: 1205
Same issue with other frontend framework and it fixed in some
I'm trying to use stripe in a Sapper app. Stripe requires to send credit card data using their secure stripe.js which you reference in the head of your page. I use svelte onMount() because stripe submit the data using their script and server is not involved.
I arrive at this payment component after I click on checkout button in my shopping cart component which uses goTo to direct me to this pay page/component:
function checkout(){
// handle shopping cart items
await goto('./checkout/pay');
}
navigateAndSave()
}
My pay component has the following code to collect credit card data:
onMount(async () => {
var stripe = await Stripe('pk_test....');
// call stripe.elements which will create the credit card fields
// use the fields for card #, expiration and cvv
var elements = await stripe.elements();
var card = await elements.create('card');
// mount the card to the dom
card.mount("#card-element");
card.on('change', ({error}) => {
// code the validate the credit card number, expiration date and other data.
}); //end of card.on fn
// code to charge the card. Call
// now that you mounted the stripe credit card Elment. Collect credit
// card details and submit it to stripe to process the payment using
// their api confirmCardPayment
stripe.confirmCardPayment(client_secret, {
payment_method: {
// the stripe card element has the credit card data
card: card,
billing_details: {
name: 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'
}
}
}).then(function(result) {
if (result.error) {
console.log(result.error.message);
} else {
if (result.paymentIntent.status === 'succeeded') {
// update db, send emails to customer and admin...etc
}
}
});
// End of submit card information code
} // end onMount()
My html form is:
<svelte:head>
<script src="https://js.stripe.com/v3/"></script>
</svelte:head>
<h1> clientSecret Checkout </h1>
<form on:submit|preventDefault={onMount}>
<div id="card-element">
<!-- Elements will create input elements here -->
</div>
<!-- We'll put the error messages in this element -->
<div id="card-errors" role="alert"></div>
<button id="submit">Pay </button>
</form>
When I submit the pay form by filling all the fields (which the iframe from stripe) and click the pay button, I receive the following error Uncaught (in promise) IntegrationError: We could not retrieve data from the specified Element. Please make sure the Element you are attempting to use is still mounted.
I see the card fields and I enter the required card details but the error says that the Element is not mounted or the one I'm using is not mounted. So it seems to me that it's something related to mounting the component.
Anyone out there with understanding of svelte, component mounting should be able to help out with this issue. After reading some other similar questions in vue, react and angular, I realized it is something specific about mounting the frontend but I can't figure it out with svlete yet.
Is there an issue with mounting Iframe in Svelte/sapper? Looked at sapper preload fn but how do I access card in my onMount() is another issue I face. If I put anything in preload, how do I have access to card?
Any solution?
Upvotes: 0
Views: 2538
Reputation: 899
You need to handle this in two stages:
At the moment you are trying to do both twice: once when the component is mounted and again when the form is submitted. I can't reproduce your exact issue using the code you provided above because Sapper errors out when I try to call onMount() from the form. I'm not sure why you don't get the same error. I think your error occurs because you replace the element when the form is submitted.
This code logs "Success" when I use a test card number:
<script>
import {loadStripe} from '@stripe/stripe-js';
import { onMount } from 'svelte';
let stripe = null;
let card = null;
let secret = null;
onMount(async () => {
stripe = await loadStripe('pk_test_...');
var elements = await stripe.elements();
card = await elements.create('card');
// mount the card to the dom
card.mount("#card-element");
secret = await fetch('secret.json').then(resp => resp.json());
});
async function handlePayment() {
// code to charge the card. Call
// now that you mounted the stripe credit card Elment. Collect credit
// card details and submit it to stripe to process the payment using
// their api confirmCardPayment
console.log(JSON.stringify(secret));
stripe.confirmCardPayment(secret.client_secret, {
payment_method: {
// the stripe card element has the credit card data
card: card,
billing_details: {
name: 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'
}
}
}).then(function(result) {
if (result.error) {
console.log(result.error.message);
} else {
if (result.paymentIntent.status === 'succeeded') {
// update db, send emails to customer and admin...etc
console.log('Success');
}
}
});
}
</script>
<h1> clientSecret Checkout </h1>
<form on:submit|preventDefault={handlePayment}>
<div id="card-element">
<!-- Elements will create input elements here -->
</div>
<!-- We'll put the error messages in this element -->
<div id="card-errors" role="alert"></div>
<button id="submit">Pay </button>
</form>
Upvotes: 2