Available on: Free (Test mode), Professional, Business plans
Stripe is a payment processing platform that allows merchants to accept credit card payments on their apps and websites. When you connect your form to a Stripe account, you can make charges and receive payments from your website.
What is new?
The Stripe plugin (V2) now supports backend validation of payments, variable charges for donations, and optional Strong Customer Authentication.
Strong Customer Authentication (SCA) is a regulation that took effect on September 14, 2019. It requires changes to how European customers authenticate online payments. This regulation only applies to online payments where the customer’s bank and the business are both in the European Economic Area (EEA).
You can tell if you're using the new V2 Stripe plugin if your plugin settings show a Stripe Publishable Key. If you don't see the Publishable Key, you can update your plugin to the latest version by deleting and re-connecting the plugin. No data will be lost.
Step 1: Connecting to Stripe
Test Mode vs Live Mode
The test and live modes function in a nearly identical manner, with a few necessary differences:
- In test mode, payments are not processed by card networks or payment providers, and only our test payment information can be used.
- Some payment methods using Sources have a more nuanced flow in live mode, with more steps required than those in test mode.
- Disputes also have a more nuanced flow in live mode, and a simpler testing process.
- In test mode, webhooks that were not successfully acknowledged are retried three times over a few hours (as opposed to 72 hours for live mode).
To see test charges, go to your Stripe Dashboard and click on View Test Data
Be aware if you want to change from Test Mode to Live Mode you'll need to delete and create the plugin again to get a new valid credentials through Stripe OAuth.
We suggest having a development/staging environment form connected to test mode and a production form connected to live mode.
Setting up the Stripe Plugin
Once you've added a form in Formspree, you can connect to Stripe using the Stripe Plugin. To do so, first go to the Plugins tab of your form. Then click the Stripe Plugin button.
After you clicked the Strip Plugin Button you'll be able to connect in Test or Live mode.
Test mode is available for all plans. Live mode is available only to upgraded plans.
Select what mode you want and click connect. Once you've signed in using your Stripe username and password, you'll be prompted to set the Stripe charge information for this form. Set the currency and the price that will be charged each time the form is submitted.
Clicking allow variable price will allow the developer or site visitor to set the price on the client side. This allows a form to charge a variable amount, such as for a donation. It can also be used to allow one form to charge different prices depending on other factors, such as a selected product. Once allow variable price is clicked, the price will be read from the price form field.
Caution: if variable pricing is enabled, any customer can change this value directly, or by reverse-engineering your code, and create charges for any amount. Be sure to verify charges sent to your Stripe account. We do not validate this value.
Once you've finished setup, click Connect to enable the plugin.
In the future if you'd like to change the description and price or disable the plugin entirely, you can do so by clicking on the Stripe button again to adjust its settings.
Once you've finished setting up and connecting your Stripe plugin, you'll see this message:
Copy the Publishable Key. We'll use it to build our checkout workflow.
Step 2: Build your checkout flow with Stripe Elements (no SCA required)
NOTE: If you're using React, check out our tutorial on How to build a custom React payment form with Stripe.
Now that you've set up your Formspree form with the Stripe Plugin, it's time to build your checkout flow. To charge payments, you must tokenize a credit card to send it safely through to Formspree.
A simple way to do that is using Stripe Elements. It's a set of pre-built UI components, like inputs and buttons, for building your checkout flow. It’s available as a feature of Stripe.js. Stripe.js tokenizes the sensitive information within an Element without ever having it touch your or our servers.
Then you just need to send the tokenized payment method to Formspree that will create and charge the payment.
Here's an example inspired by the Stripe Docs:
HTML file
<head>
<title>Checkout</title>
<script src="https://js.stripe.com/v3/"></script>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
</head>
<body>
<form id="payment-form" action="">
<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>
</body>
JS File
$(document).ready(function () {
// Stripe settings
var stripe = Stripe("YOUR_PUBLISHABLE_KEY_COPIED_FROM_DASHBOARD");
var elements = stripe.elements();
var style = {
base: {
color: "#32325d",
},
};
var card = elements.create("card", {
style: style,
});
card.mount("#card-element");
card.on("change", ({ error }) => {
const displayError = document.getElementById("card-errors");
if (error) {
displayError.textContent = error.message;
} else {
displayError.textContent = "";
}
});
// end stripe
var form = document.getElementById("payment-form");
form.addEventListener("submit", function (event) {
event.preventDefault();
// Tokenize card info
stripe
.createPaymentMethod({
type: "card",
card: card,
billing_details: {
// Include any additional collected billing details.
name: "Jenny Rosen",
email: "jenny@rosen.com",
},
})
.then(stripePaymentMethodHandler);
});
function stripePaymentMethodHandler(result) {
if (result.error) {
// Show error to user
console.log(result.error);
} else {
// Otherwise send payment method to your server
fetch("https://formspree.io/f/FORM_ID", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
// Send the tokenized card
paymentMethod: result.paymentMethod.id,
email: "jenny@rosen.com",
// add here any data that you want to send to Formspree
}),
}).then(function (result) {
// Handle server response
result.json().then(function (json) {
handleServerResponse(json);
});
});
}
}
function handleServerResponse(response) {
if (response.error) {
// Display errors on the payment form
console.log(response.error);
} else {
// Display a success message
console.log("Payment finished successfully");
}
}
});
To style your stripe elements, check out this example, or our example form reset styles here.
At this point, you have a functional form that can make charges to non-EU cardholders.
Step 3: (Optional) Support SCA payments
After completing step 2, your form should correctly submit and create charges in Stripe. However, if SCA is required by the submitter's bank, the form will generate an error. To support SCA we need to add one more step to the previous flow.
In the first request, the Formspree server will try to create a Payment Intent and charge it. If the card requires an additional action like SCA, the charge will fail and the payment intent will return a status requires_action
.
The Formspree server will return an HTTP Status 402 with the following information:
- The created payment intent ID
- A payment intent client secret that will be used to make a request to Stripe to validate the payment
- A
resubmit_key
that will be used to resend the request to Formspree once the payment has been validated
Response body example
{
"message": "requires_action",
"resubmitKey": <key_generated_by_formspree>,
"stripe": {
"paymentIntentId": <id_from_the_payment_intent>,
"paymentIntentClientSecret": <secret_generated_by_stripe>,
"requiresAction": true
}
}
We can now validate the payment by calling Stripe's handleCardAction function, sending the payment_intent_client_secret
. This will perform any front-end validation required, such as opening the 3DS Modal to complete SCA.
The following example demonstrates how to update the handleServerResponse()
function we created in the last step to catch any required actions. It then resubmits the form with the payment intent and new resubmit_key
.
JS Example
function handleServerResponse(response) {
if (response.error) {
// Show error from server on payment form
console.log(response.error);
} else if (response && response.stripe.requiresAction) {
// Stripe require additional actions to charge this card
// Use Stripe.js to handle required card action and open 3DS
stripe
.handleCardAction(response.stripe.paymentIntentClientSecret)
.then(function (stripeResult) {
// TODO: implement this function to resubmit the form
resubmitForm(stripeResult, response.resubmitKey);
});
} else {
console.log("Payment finished successfully");
}
}
Stripe 3DS Modal
If the handleCardAction function succeeds, you can now resubmit the form to Formspree sending the resubmit_key
and the paymentIntent
you received earlier after the first failed attempt.
Example of resubmitting the form:
function resubmitForm(result, resubmitKey) {
console.log("handle Stripe SCA result");
if (result.error) {
// Show error in payment form
console.log("Stripe SCA error");
} else {
// The card action has been handled
// The PaymentIntent can be confirmed again on the server
fetch('https://formspree.io/f/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
paymentIntent: result.paymentIntent.id ,
resubmitKey: resubmitKey,
email: 'jenny@rosen.com',
})
}).then(function(result) {
return result.json();
}).then(handleServerResponse);
}
}
When Formspree receives the second submission, it will again attempt to charge the payment intent with the card information that should have been validated on the client-side through Stripe's SCA flow. You can resend the same fields from the original submission. Any changes to these fields will update the form data.
Be aware that the resubmit_key
will expire if not used in a timely manner. In this scenario, Formspree will return an HTTP 404 response status code.
Summary
In summary, the steps are:
- Try to charge the card. If it requires SCA will be returned 402 response with the payment intent data and resubmit the key.
- Call Stripe handleCardAction with payment intent client secret returned from the first attempt and do 3DS.
- Resubmit the form by sending the resubmit key, payment intent id, and email. Formspree will retry to charge to finish the submission.
Take a look at the complete checkout flow with SCA
JS File (SCA)
$(document).ready(function () {
// Stripe settings
var stripe = Stripe("YOUR_PUBLISHABLE_KEY_COPIED_FROM_DASHBOARD");
var elements = stripe.elements();
var style = {
base: {
color: "#32325d",
},
};
var card = elements.create("card", {
style: style,
});
card.mount("#card-element");
card.on("change", ({ error }) => {
const displayError = document.getElementById("card-errors");
if (error) {
displayError.textContent = error.message;
} else {
displayError.textContent = "";
}
});
// end stripe
var form = document.getElementById("payment-form");
form.addEventListener("submit", function (ev) {
event.preventDefault();
stripe
.createPaymentMethod({
type: "card",
card: card,
billing_details: {
// Include any additional collected billing details.
name: "Jenny Rosen",
email: "jenny@rosen.com",
},
})
.then(stripePaymentMethodHandler);
});
function stripePaymentMethodHandler(result) {
if (result.error) {
console.log("Error creating payment method");
} else {
// First submission attempt
fetch("https://formspree.io/f/FORM_ID", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
paymentMethod: result.paymentMethod.id,
email: "jenny@rosen.com",
}),
}).then(function (response) {
// Handle server response (see Step 4)
response.json().then(function (json) {
handleServerResponse(json);
});
});
}
}
function handleServerResponse(response) {
if (response.stripe && response.stripe.requiresAction) {
// Stripe require additional actions to charge this card
// Use Stripe.js to handle required card action and open 3DS
stripe
.handleCardAction(response.stripe.paymentIntentClientSecret)
.then(function (stripeResult) {
// Get handleCardAction response and resubmit
resubmitForm(stripeResult, response.resubmitKey);
});
} else if (response.error) {
// Show error from server on payment form
console.log(response.error);
} else {
console.log("Payment finished successfully");
}
}
function resubmitForm(result, resubmitKey) {
console.log("handle Stripe SCA result");
if (result.error) {
// Display error in the payment form
console.log("Stripe SCA error");
} else {
// The card action has been handled
// The PaymentIntent can be confirmed again on the server
fetch("https://formspree.io/f/FORM_ID", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
paymentIntent: result.paymentIntent.id,
resubmitKey: resubmitKey,
email: "jenny@rosen.com",
}),
})
.then(function (confirmResult) {
return confirmResult.json();
})
.then(handleServerResponse);
}
}
});
To read more about testing with Stripe and Stripe SCA access https://stripe.com/docs/testing and our Blog Post.