July 4, 2024, written by
Andrii Minchekov
In a previous article, the benefits and use cases for the Stripe Payment Intent API flow were covered. Now, I dive deeper into enhancing this custom payment flow's security using Stripe Elements. Discover how to leverage Stripe's pre-built UI components to create a secure and seamless payment experience.
Usage of iframes for each secure field.
Tokenization
Secure Transmission
PCI Compliance
Security Features
Tokenization is the process Stripe uses to collect sensitive card or bank account details, or personally identifiable information (PII), directly from your customers in a secure manner. A token representing this information is returned to your server to use. Use Stripe recommended payments integrations to perform this process on the client-side. This guarantees that no sensitive card data touches your server, and allows your integration to operate in a PCI-compliant way.
If you can’t use client-side tokenization, you can also create tokens using the Tokens API with either your publishable or secret API key. If your integration uses this method, you’re responsible for any PCI compliance that it might require, and you must keep your secret API key safe. Unlike with client-side tokenization, your customer’s information isn’t sent directly to Stripe, so you are responsible to handle or store it securely.
An iframe (short for inline frame) is an HTML element that allows you to embed another HTML document within the current document. This embedded document can be isolated from the parent document, providing a layer of separation that enhances security and prevents tampering.
Creation of Secure Input Fields:
Embedding iframes:
Cross-Origin Isolation:
Content Security Policy (CSP):
Isolation of Sensitive Data:
JavaScript Security:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stripe Payment</title>
<script src="https://js.stripe.com/v3/"></script>
</head>
<body>
<form id="payment-form">
<div id="card-element"><!-- Stripe Elements will create input fields here --></div>
<button id="submit">Pay</button>
</form>
<script>
const stripe = Stripe('your-publishable-key');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
billing_details: {
name: 'Cardholder Name',
},
});
if (error) {
console.error(error);
} else {
// Send the paymentMethod.id to your server
const response = await fetch('/create-payment-intent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentMethodId: paymentMethod.id }),
});
const { clientSecret } = await response.json();
// Confirm the payment on the client side
const { error: confirmError, paymentIntent } = await stripe.confirmCardPayment(clientSecret);
if (confirmError) {
console.error(confirmError);
} else if (paymentIntent.status === 'succeeded') {
console.log('Payment succeeded');
}
}
});
</script>
</body>
</html>
Key points of example:
stripe.createPaymentMethod
or stripe.createToken
, the card details collected by Stripe Elements are automatically tokenized under the hood and sent to Stripe’s servers securely.Stripe.js
library takes care of the tokenization process when you create a PaymentMethod or confirm a Payment Intent.This is how Stripe iframe code looks like after mounting to the DOM.
<div class="form-control StripeElement StripeElement--empty" id="cc_number">
<div class="__PrivateStripeElement" style="margin: 0px !important; padding: 0px !important; border: none !important; display: block !important; background: transparent !important; position: relative !important; opacity: 1 !important;">
<iframe name="__privateStripeFrame2365" src="https://js.stripe.com/v3/elements-inner-card-4c3fbb0b6f5096dd4a3a7a3ec37002fe.html#wait=true&showIcon=true&style[base][iconColor]=%23235fc6&style[base][fontWeight]=500&style[base][fontFamily]=Roboto%2C+sans-serif&style[base][fontSize]=16px&rtl=false&componentName=cardNumber&keyMode=test&apiKey=pk_XXXXX&referrer=https%3A%2F%2Fdevbenoit.bridgebase.com%2Fpurchase%2Fpay.php&controllerId=__privateStripeController2361"
title="Secure card number input frame" style="border: none !important; margin: 0px !important; padding: 0px !important; width: 1px !important; min-width: 100% !important; overflow: hidden !important; display: block !important; user-select: none !important; will-change: transform !important; height: 19.2px;"></iframe>
<input class="__PrivateStripeElement-input" aria-hidden="true" aria-label=" " autocomplete="false" maxlength="1" style="border: none !important; display: block !important; position: absolute !important; height: 1px !important; top: -1px !important; left: 0px !important; padding: 0px !important; margin: 0px !important; width: 100% !important; opacity: 0 !important; background: transparent !important; pointer-events: none !important; font-size: 16px !important;"></div>
</div>
Stripe Elements uses iframes to securely collect and tokenize card details, ensuring that these details are isolated from your webpage and protected from tampering. This approach leverages the Same-Origin Policy, Content Security Policy, and robust encryption methods to provide a secure environment for handling sensitive payment information, thereby enhancing security and simplifying compliance with PCI DSS requirements.