Skip to main content
The payment page collects card details using Stripe Elements and confirms the subscription payment. Stripe handles all PCI-compliant card data collection — your server never touches raw card numbers. Strong Customer Authentication (SCA) and 3D Secure challenges are handled automatically.

How the page works

The page receives a client_secret query parameter set by the plan selection step. This secret is tied to a specific PaymentIntent created when the subscription was initiated.
1

Stripe Elements is initialized

The <Elements> provider is mounted with your Stripe publishable key via loadStripe. The clientSecret is then passed directly as a prop to the <CardSubscription> component inside the provider.
app/routes/subscribe.tsx
import { loadStripe } from "@stripe/stripe-js";
import { Elements } from "@stripe/react-stripe-js";

const stripePromise = loadStripe("pk_test_...");

<Elements stripe={stripePromise}>
  <CardSubscription clientSecret={client_secret} />
</Elements>
2

User enters card details

The <CardElement> component renders a single, secure iframe-based input that collects the card number, expiry date, and CVC. Card data is tokenized client-side and never sent to your server.
3

User submits the form

On submit, the form calls stripe.confirmCardPayment with the clientSecret and the card element. A cardholder name is included in the billing details.
app/components/cardsubscription.tsx
const handleSubmit = async (e) => {
  e.preventDefault();
  const cardElement = elements.getElement(CardElement);
  const { error } = await stripe.confirmCardPayment(clientSecret, {
    payment_method: {
      card: cardElement,
      billing_details: {
        name: name,
      },
    },
  });
  if (error) {
    setMessage(error.message);
    return;
  }
  navigate("/account", { replace: false });
};
4

SCA / 3D Secure is handled automatically

If the card issuer requires additional authentication (SCA/3DS), confirmCardPayment automatically presents the bank’s authentication dialog. No extra code is needed — Stripe manages the redirect or modal flow and resolves the promise once authentication completes.
5

On success, user is sent to their account

When confirmCardPayment resolves without an error, the subscription is now active in Stripe. The user is navigated to /account to view their subscription details.

Error handling

If confirmCardPayment returns an error (declined card, insufficient funds, failed authentication), the error message from Stripe is displayed inline on the payment form. The user can correct their details and try again without restarting the subscription flow.
Do not navigate away or disable the form permanently on error. The PaymentIntent can be retried with a different card by calling confirmCardPayment again with the same clientSecret.

Test cards

Use these card numbers in Stripe’s test mode to simulate different payment scenarios. Use any future expiry date, any 3-digit CVC, and any postal code.
Card numberBehavior
4242 4242 4242 4242Payment succeeds immediately
4000 0025 0000 3155Requires SCA / 3D Secure authentication
4000 0000 0000 9995Payment is declined (insufficient funds)
4000 0000 0000 0002Payment is declined (generic decline)
Use the 4000 0025 0000 3155 card to verify that your SCA flow works end to end. Stripe will present a test authentication dialog that you can approve or decline.

Security

PCI compliance

Card details are collected inside a Stripe-hosted iframe (CardElement). Raw card data never touches your application server.

SCA / 3DS

confirmCardPayment handles the full Strong Customer Authentication flow required by regulations such as PSD2 in Europe.

Next step

After a successful payment, users land on the account page where they can view their active subscriptions.

Build docs developers (and LLMs) love