# Accept card payments without webhooks
Learn how to confirm a card payment on your server and handle card authentication requests.
# Web
> Stripe recommends using the newer [Payment Element](https://docs.stripe.com/payments/quickstart-checkout-sessions.md) instead of the Card Element. It allows you to accept multiple payment methods with a single Element. Learn more about [when to use the Card Element and Payment Element](https://docs.stripe.com/payments/payment-card-element-comparison.md).
For a wider range of support and future proofing, use the [standard integration](https://docs.stripe.com/payments/accept-a-payment.md) for asynchronous payments.
This integration waits for the returned response from the client and finalizes a payment on the server, without using *webhooks* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) or processing offline events. While it might seem simpler, this integration is difficult to scale as your business grows and has several limitations:
If you’re migrating an existing Stripe integration from the Charges API, follow the [migration guide](https://docs.stripe.com/payments/payment-intents/migration.md).
- **Only supports cards**—You’ll have to write more code to support ACH and popular regional payment methods separately.
- **Double-charge risk**—By synchronously creating a new PaymentIntent each time your customer attempts to pay, you risk accidentally double-charging your customer. Be sure to follow [best practices](https://docs.stripe.com/error-low-level.md#idempotency).
- **Extra trip to client**—Cards with 3D Secure or those that are subject to regulations such as *Strong Customer Authentication* (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase) require extra steps on the client.
Keep these limitations in mind if you decide to use this integration. Otherwise, use the [standard integration](https://docs.stripe.com/payments/accept-a-payment.md).
## Set up Stripe
First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register).
Use our official libraries for access to the Stripe API from your application:
#### Ruby
```bash
# Available as a gem
sudo gem install stripe
```
```ruby
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
```
## Collect card details [Client-side]
Collect card information on the client with Stripe.js and Stripe Elements. Elements is a set of prebuilt UI components for collecting and validating card number, postal code, and expiration date.
A Stripe Element contains an iframe that securely sends the payment information to Stripe over an HTTPS connection. The checkout page address must also start with https:// rather than http:// for your integration to work.
You can test your integration without using HTTPS. [Enable it](https://docs.stripe.com/security/guide.md#tls) when you’re ready to accept live payments.
#### HTML + JS
Include the [Stripe.js](https://docs.stripe.com/js.md) script in the head of every page on your site. Elements is automatically available as a feature of Stripe.js.
```html
```
Including the script on every page of your site lets you take advantage of Stripe’s [advanced fraud functionality](https://docs.stripe.com/radar.md) and ability to detect anomalous browsing behavior.
### Build the payment form
To securely collect card details from your customers, Elements creates UI components for you that are hosted by Stripe. They’re then placed into your payment form as an iframe. To determine where to insert these components, create empty DOM elements (containers) with unique IDs within your payment form.
#### HTML
```html
```
Next, create an instance of the [Stripe object](https://docs.stripe.com/js.md#stripe-function), providing your publishable [API key](https://docs.stripe.com/keys.md) as the first parameter. Afterwards, create an instance of the [Elements object](https://docs.stripe.com/js.md#stripe-elements) and use it to [mount](https://docs.stripe.com/js.md#element-mount) a Card element in the relevant placeholder in the page.
```javascript
const stripe = Stripe('<>');
const elements = stripe.elements();
// Set up Stripe.js and Elements to use in checkout form
const style = {
base: {
color: "#32325d",
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#aab7c4"
}
},
invalid: {
color: "#fa755a",
iconColor: "#fa755a"
},
};
const cardElement = elements.create('card', {style});
cardElement.mount('#card-element');
```
The `card` Element simplifies the form and minimizes the number of fields required by inserting a single, flexible input field that securely collects all necessary card details.
Otherwise, combine `cardNumber`, `cardExpiry`, and `cardCvc` Elements for a flexible, multi-input card form.
> Always collect a postal code to increase card acceptance rates and reduce fraud.
>
> The [single line Card Element](https://docs.stripe.com/js/element/other_element?type=card) automatically collects and sends the customer’s postal code to Stripe. If you build your payment form with split Elements ([Card Number](https://docs.stripe.com/js/element/other_element?type=cardNumber), [Expiry](https://docs.stripe.com/js/element/other_element?type=cardExpiry), [CVC](https://docs.stripe.com/js/element/other_element?type=cardCvc)), add a separate input field for the customer’s postal code.
### Create a PaymentMethod
Finally, use [stripe.createPaymentMethod](https://docs.stripe.com/js/payment_methods/create_payment_method) on your client to collect the card details and create a [PaymentMethod](https://docs.stripe.com/api/payment_methods.md) when the user clicks the submit button.
```javascript
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
event.preventDefault();
const result = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
billing_details: {
// Include any additional collected billing details.
name: 'Jenny Rosen',
},
});
stripePaymentMethodHandler(result);
});
```
#### React
#### npm
Install [React Stripe.js](https://www.npmjs.com/package/@stripe/react-stripe-js) and the [Stripe.js loader](https://www.npmjs.com/package/@stripe/stripe-js) from the npm public registry.
```bash
npm install --save @stripe/react-stripe-js @stripe/stripe-js
```
#### umd
We also provide a UMD build for sites that don’t use npm or modules.
Include the Stripe.js script, which exports a global `Stripe` function, and the UMD build of React Stripe.js, which exports a global `ReactStripe` object. Always load the Stripe.js script directly from **js.stripe.com** to remain PCI compliant. Don’t include the script in a bundle or host a copy of it yourself.
```html
```
> The [demo in CodeSandbox](https://codesandbox.io/s/react-stripe-official-q1loc?fontsize=14&hidenavigation=1&theme=dark) lets you try out React Stripe.js without having to create a new project.
### Add Stripe.js and Elements to your page
To use Element components, wrap your checkout page component in an [Elements provider](https://docs.stripe.com/sdks/stripejs-react.md#elements-provider). Call `loadStripe` with your publishable key and pass the returned `Promise` to the `Elements` provider.
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
import {Elements} from '@stripe/react-stripe-js';
import {loadStripe} from '@stripe/stripe-js';
import CheckoutForm from './CheckoutForm';
// Make sure to call `loadStripe` outside of a component's render to avoid
// recreating the `Stripe` object on every render.
const stripePromise = loadStripe('<>');
function App() {
return (
);
};
ReactDOM.render(, document.getElementById('root'));
```
### Add and configure a CardElement component
Use individual Element components, such as `CardElement`, to build your form.
#### JSX
```jsx
/**
* Use the CSS tab above to style your Element's container.
*/
import React from 'react';
import {CardElement} from '@stripe/react-stripe-js';
import './Styles.css'
const CARD_ELEMENT_OPTIONS = {
style: {
base: {
color: "#32325d",
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#aab7c4",
},
},
invalid: {
color: "#fa755a",
iconColor: "#fa755a",
},
},
};
function CardSection() {
return (
);
};
export default CardSection;
```
Elements are completely customizable. You can [style Elements](https://docs.stripe.com/js/elements_object/create_element?type=card#elements_create-options) to match the look and feel of your site, providing seamless checkout for your customers. It’s also possible to style various input states, for example when the Element has focus.
The `CardElement` simplifies the form and minimizes the number of required fields by inserting a single, flexible input field that securely collects all necessary card and billing details. Otherwise, combine `CardNumberElement`, `CardExpiryElement`, and `CardCvcElement` elements for a flexible, multi-input card form.
> Always collect a postal code to increase card acceptance rates and reduce fraud.
>
> The [single line Card Element](https://docs.stripe.com/js/element/other_element?type=card) automatically collects and sends the customer’s postal code to Stripe. If you build your payment form with split Elements ([Card Number](https://docs.stripe.com/js/element/other_element?type=cardNumber), [Expiry](https://docs.stripe.com/js/element/other_element?type=cardExpiry), [CVC](https://docs.stripe.com/js/element/other_element?type=cardCvc)), add a separate input field for the customer’s postal code.
### Create a PaymentMethod
In your payment form’s submit handler, use [stripe.createPaymentMethod](https://docs.stripe.com/js/payment_methods/create_payment_method) to collect the card details and create a [PaymentMethod](https://docs.stripe.com/api/payment_methods.md).
To call `stripe.createPaymentMethod` from your payment form component, use the [useStripe](https://docs.stripe.com/sdks/stripejs-react.md#usestripe-hook) and [useElements](https://docs.stripe.com/sdks/stripejs-react.md#useelements-hook) hooks.
If you prefer traditional class components over hooks, you can instead use an [ElementsConsumer](https://docs.stripe.com/sdks/stripejs-react.md#elements-consumer).
#### Hooks
```jsx
import React from 'react';
import {useStripe, useElements, CardElement} from '@stripe/react-stripe-js';
import CardSection from './CardSection';
export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
event.preventDefault();
if (!stripe || !elements) {
// Stripe.js hasn't yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}const result = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardElement),
billing_details: {
// Include any additional collected billing details.
name: 'Jenny Rosen',
},
});
stripePaymentMethodHandler(result);
};
return (
);
}
```
#### Class Components
```jsx
import React from 'react';
import {ElementsConsumer, CardElement} from '@stripe/react-stripe-js';
import CardSection from './CardSection';
class CheckoutForm extends React.Component {
handleSubmit = async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
event.preventDefault();
const {stripe, elements} = this.props
if (!stripe || !elements) {
// Stripe.js hasn't yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}const result = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardElement),
billing_details: {
// Include any additional collected billing details.
name: 'Jenny Rosen',
}
});
stripePaymentMethodHandler(result);
};
render() {
const {stripe} = this.props;
return (
);
}
}
export default function InjectedCheckoutForm() {
return (
{({stripe, elements}) => (
)}
);
}
```
## Submit the PaymentMethod to your server [Client-side]
If the *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs) was created successfully, send its ID to your server.
```javascript
const stripePaymentMethodHandler = async (result) => {
if (result.error) {
// Show error in payment form
} else {
// Otherwise send paymentMethod.id to your server (see Step 4)
const res = await fetch('/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
payment_method_id: result.paymentMethod.id,
}),
})
const paymentResponse = await res.json();
// Handle server response (see Step 4)
handleServerResponse(paymentResponse);
}
}
```
## Create a PaymentIntent [Server-side]
Set up an endpoint on your server to receive the request. This endpoint will also be used [later](https://docs.stripe.com/payments/accept-a-payment-synchronously.md#confirm-payment) to handle cards that require an extra step of authentication.
[Create a new PaymentIntent](https://docs.stripe.com/payments/payment-intents.md#creating-a-paymentintent) with the ID of the [PaymentMethod](https://docs.stripe.com/api/payment_methods/object.md) created on your client. You can *confirm* (Confirming a PaymentIntent indicates that the customer intends to pay with the current or provided payment method. Upon confirmation, the PaymentIntent attempts to initiate a payment) the PaymentIntent by setting the [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) property to true when the PaymentIntent is created or by calling [confirm](https://docs.stripe.com/api/payment_intents/confirm.md) after creation. [Separate authorization and capture](https://docs.stripe.com/payments/place-a-hold-on-a-payment-method.md) is also supported for card payments.
If the payment requires additional actions such as 3D Secure authentication, the PaymentIntent’s status will be set to `requires_action`. If the payment failed, the status is set back to `requires_payment_method` and you should show an error to your user. If the payment doesn’t require any additional authentication then a charge is created and the PaymentIntent status is set to `succeeded`.
> On versions of the API before [2019-02-11](https://docs.stripe.com/upgrades.md#2019-02-11), `requires_payment_method` appears as `requires_source` and `requires_action` appears as `requires_source_action`.
#### curl
```bash
curl https://api.stripe.com/v1/payment_intents \
-u <>: \
-d "payment_method"="{{PAYMENT_METHOD_ID}}" \
-d "amount"=1099 \
-d "currency"="usd" \
-d "confirmation_method"="manual" \
-d "confirm"="true"
```
If you want to save the card to reuse later, create a [Customer](https://docs.stripe.com/api/customers/create.md) to store the *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs) and pass the following additional parameters when creating the PaymentIntent:
- [customer](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer). Set to the ID of the *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments).
- [setup_future_usage](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-setup_future_usage). Set to `off_session` to tell Stripe that you plan to reuse this PaymentMethod for *off-session payments* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information) when your customer isn’t present. Setting this property saves the PaymentMethod to the Customer after the PaymentIntent is confirmed and any required actions from the user are complete. See the code sample on [saving cards after a payment](https://github.com/stripe-samples/saving-card-after-payment/tree/master/without-webhooks) for more details.
## Handle any next actions [Client-side]
Write code to handle situations that require your customer to intervene. A payment normally succeeds after you confirm it on the server in [step 4](https://docs.stripe.com/payments/accept-a-payment-synchronously.md#create-payment-intent). However, when the PaymentIntent requires additional action from the customer, such as authenticating with *3D Secure* (3D Secure (3DS) provides an additional layer of authentication for credit card transactions that protects businesses from liability for fraudulent card payments), this code comes into play.
Use [stripe.handleCardAction](https://docs.stripe.com/js/payment_intents/handle_card_action) to trigger the UI for handling customer action. If authentication succeeds, the PaymentIntent has a status of `requires_confirmation`. Confirm the PaymentIntent again on your server to finish the payment.
While testing, use a [test card number](https://docs.stripe.com/testing.md#regulatory-cards) that requires authentication (for example, 4000002760003184) to force this flow. Using a card that doesn’t require authentication (for example, 4242424242424242) skips this part of the flow and completes at step 4.
```javascript
const handleServerResponse = async (response) => {
if (response.error) {
// Show error from server on payment form
} else if (response.requires_action) {
// Use Stripe.js to handle the required card action
const { error: errorAction, paymentIntent } =
await stripe.handleCardAction(response.payment_intent_client_secret);
if (errorAction) {
// Show error from Stripe.js in payment form
} else {
// The card action has been handled
// The PaymentIntent can be confirmed again on the server
const serverResponse = await fetch('/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payment_intent_id: paymentIntent.id })
});
handleServerResponse(await serverResponse.json());
}
} else {
// Show success message
}
}
```
> `stripe.handleCardAction` might take several seconds to complete. During that time, disable your form from being resubmitted and show a waiting indicator like a spinner. If you receive an error, show it to the customer, re-enable the form, and hide the waiting indicator. If the customer must perform additional steps to complete the payment, such as authentication, Stripe.js walks them through that process.
## Confirm the PaymentIntent again [Server-side]
This code is only executed when a payment requires additional authentication—just like the handling in the previous step. The code itself isn’t optional because any payment could require this extra step.
Using the same endpoint you set up [above](https://docs.stripe.com/payments/accept-a-payment-synchronously.md#create-payment-intent), *confirm* (Confirming an intent indicates that the customer intends to use the current or provided payment method. Upon confirmation, the intent attempts to initiate the portions of the flow that have real-world side effects) the PaymentIntent again to finalize the payment and *fulfill* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) the order. Make sure this confirmation happens within one hour of the payment attempt. Otherwise, the payment fails and transitions back to `requires_payment_method`.
#### curl
```bash
curl https://api.stripe.com/v1/payment_intents/{{PAYMENT_INTENT_ID}}/confirm \
-u <>: \
-X "POST"
```
## Test the integration
Several test cards are available for you to use in a sandbox to make sure this integration is ready. Use them with any CVC and an expiration date in the future.
| Number | Description |
| ---------------- | ----------------------------------------------------------------------------------------- |
| 4242424242424242 | Succeeds and immediately processes the payment. |
| 4000002500003155 | Requires authentication. Stripe triggers a modal asking for the customer to authenticate. |
| 4000000000009995 | Always fails with a decline code of `insufficient_funds`. |
For the full list of test cards see our guide on [testing](https://docs.stripe.com/testing.md).
## Optional: Recollect a CVC
When creating subsequent payments on a saved card, you might want to re-collect the CVC of the card as an additional fraud measure to verify the user.
Start by [listing](https://docs.stripe.com/api/payment_methods/list.md) the payment methods associated with your *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments) to determine which of them to show for CVC re-collection. On your client, use the `cardCvc` Element to re-collect a CVC value from your user for one of their payment methods, and then tokenize the CVC data using [stripe.createToken](https://docs.stripe.com/js/tokens/create_token?type=cvc_update).
After sending the CVC token to your server, create a PaymentIntent on your server with the amount, currency, and the CVC token in the `payment_method_options[card][cvc_token]` parameter. As with all other tokens, you can’t use CVC tokens more than once, so each PaymentIntent should use its own unique CVC token.
```curl
curl https://api.stripe.com/v1/payment_intents \
-u "<>:" \
-d payment_method={{PAYMENT_METHOD_ID}} \
-d customer={{CUSTOMER_ID}} \
-d amount=1099 \
-d currency=usd \
-d confirmation_method=manual \
-d confirm=true \
-d "payment_method_options[card][cvc_token]={{CVC_TOKEN_ID}}"
```
A payment might succeed even with a failed CVC check. To prevent this, configure your [Radar rules](https://docs.stripe.com/radar/rules.md#traditional-bank-checks) to block payments when CVC verification fails.