Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Developer tools
Get started
Payments
Finance automation
Get started
Payments
Finance automation
Platforms and marketplaces
Money management

Accept card payments without webhooks

Learn how to confirm a card payment on your server and handle card authentication requests.

For a wider range of support and future proofing, use the standard integration for asynchronous payments.

This integration uses a single client-to-server flow to take payments, without using webhooks or processing offline events. While it may seem simpler, this integration is difficult to scale as your business grows and has several limitations:

  • 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.
  • Manual authentication handling—Cards with 3D Secure or those that are subject to regulations such as Strong Customer Authentication require extra steps on the client.

Keep these limitations in mind if you decide to use this integration. Otherwise, use the standard integration.

Set up Stripe
Server-side
Client-side

Server-side

This integration requires endpoints on your server that talk to the Stripe API. Use our official libraries for access to the Stripe API from your server:

Command Line
Ruby
# Available as a gem sudo gem install stripe
Gemfile
Ruby
# If you use bundler, you can add this line to your Gemfile gem 'stripe'

Client-side

The React Native SDK is open source and fully documented. Internally, it uses the native iOS and Android SDKs. To install Stripe’s React Native SDK, run one of the following commands in your project’s directory (depending on which package manager you use):

Command Line
yarn add @stripe/stripe-react-native

Next, install some other necessary dependencies:

  • For iOS, navigate to the ios directory and run pod install to ensure that you also install the required native dependencies.
  • For Android, there are no more dependencies to install.

Stripe initialization

To initialize Stripe in your React Native app, either wrap your payment screen with the StripeProvider component, or use the initStripe initialization method. Only the API publishable key in publishableKey is required. The following example shows how to initialize Stripe using the StripeProvider component.

import { StripeProvider } from '@stripe/stripe-react-native'; function App() { const [publishableKey, setPublishableKey] = useState(''); const fetchPublishableKey = async () => { const key = await fetchKey(); // fetch key from your server here setPublishableKey(key); }; useEffect(() => { fetchPublishableKey(); }, []); return ( <StripeProvider publishableKey={publishableKey} merchantIdentifier="merchant.identifier" // required for Apple Pay urlScheme="your-url-scheme" // required for 3D Secure and bank redirects > // Your app code here </StripeProvider> ); }

Note

Use your API test keys while you test and develop, and your live mode keys when you publish your app.

Create your checkout page
Client-side

Securely collect card information on the client with CardField, a UI component provided by the SDK that collects the card number, expiration date, CVC, and postal code.

CardField performs real-time validation and formatting.

Add the CardField component to your payment screen to securely collect card details from your customers. Use the onCardChange callback to inspect non-sensitive information about the card, like the brand, and whether the details are complete.

import { CardField, useStripe } from '@stripe/stripe-react-native'; function PaymentScreen() { // ... return ( <View> <CardField postalCodeEnabled={true} placeholders={{ number: '4242 4242 4242 4242', }} cardStyle={{ backgroundColor: '#FFFFFF', textColor: '#000000', }} style={{ width: '100%', height: 50, marginVertical: 30, }} onCardChange={(cardDetails) => { console.log('cardDetails', cardDetails); }} onFocus={(focusedField) => { console.log('focusField', focusedField); }} /> </View> ); }

Run your app and make sure your checkout page shows the CardField component.

Collect card details
Client-side

When your customer is ready to check out, create a PaymentMethod with the details collected by the CardField component.

import { CardField, useStripe } from '@stripe/stripe-react-native'; function PaymentScreen() { const { createPaymentMethod, handleNextAction } = useStripe(); const pay = () => { // Gather customer billing information (for example, email) const billingDetails: CreatePaymentMethod.BillingDetails = { email: 'email@stripe.com', phone: '+48888000888', addressCity: 'Houston', addressCountry: 'US', addressLine1: '1459 Circle Drive', addressLine2: 'Texas', addressPostalCode: '77063', }; // Create payment method const { paymentMethod, error } = await createPaymentMethod({ paymentMethodType: 'Card', paymentMethodData: { billingDetails, } }); }; // ... }

Submit the PaymentMethod to your server
Client-side

If the PaymentMethod was created successfully, send its ID to your server.

// ... const pay = () => { // ... // Send the PaymentMethod to your server to create a PaymentIntent const response = await fetch(`/pay`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ paymentMethodId: paymentMethod.id }), }); const { error, requires_action, payment_intent_client_secret } = await response.json(); if (error) { // Error creating or confirming PaymentIntent Alert.alert('Error', paymentIntentError); return; } if (payment_intent_client_secret && !requires_action) { // Payment succeeded Alert.alert('Success', 'The payment was confirmed successfully!'); } if (payment_intent_client_secret && requires_action) { // ...continued below } }; // ...

Create a PaymentIntent
Server-side

Set up an endpoint on your server to receive the request. This endpoint will also be used later to handle cards that require an extra step of authentication.

Create a new PaymentIntent with the ID of the PaymentMethod created on your client. You can confirm the PaymentIntent by setting the confirm property to true when the PaymentIntent is created or by calling confirm after creation. Separate authorization and capture 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.

Note

On versions of the API before 2019-02-11, requires_payment_method appears as requires_source and requires_action appears as requires_source_action.

Command Line
curl
curl https://api.stripe.com/v1/payment_intents \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -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 to store the PaymentMethod and pass the following additional parameters when creating the PaymentIntent:

  • customer. Set to the ID of the Customer.
  • setup_future_usage. Set to off_session to tell Stripe that you plan to reuse this PaymentMethod for off-session payments when your customer is not 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 for more details.

Handle any next actions
Client-side

A normal payment succeeds after you confirm it on the server in step 4. However, some payment flows require additional action from the customer, such as authenticating with 3D Secure.

​​For cases that require any next actions, the PaymentIntent’s status is requires_action. On the client, pass the PaymentIntent’s client secret to handleNextAction. The native handler presents a view and walks the customer through the authentication flow. After handling required actions on the client, the status of the PaymentIntent changes to requires_confirmation. This enables your integration to fulfill the order on your backend and return the fulfillment result to your client.

Send the PaymentIntent ID to your backend and confirm it again within one hour to finalize the payment. Otherwise, the payment attempt fails and transitions back to requires_payment_method.

// ... const pay = () => { // ... // If PaymentIntent requires action, call handleNextAction if (payment_intent_client_secret && requires_action) { const { error, paymentIntent } = await handleNextAction(payment_intent_client_secret); if (error) { Alert.alert(`Error code: ${error.code}`, error.message); } else if (paymentIntent) { if ( paymentIntent.status === PaymentIntents.Status.RequiresConfirmation ) { // Confirm the PaymentIntent again on your server const response = await fetch(`/pay`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ payment_intent_id: paymentIntent.id }), }); const { error, success } = await response.json(); if (error) { // Error during confirming Intent Alert.alert('Error', error); } else if (success) { Alert.alert('Success', 'The payment was confirmed successfully!'); } } else { // Payment succedeed Alert.alert('Success', 'The payment was confirmed successfully!'); } } } }; // ...

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, confirm the PaymentIntent again to finalize the payment and fulfill 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.

Command Line
curl
curl https://api.stripe.com/v1/payment_intents/{{PAYMENT_INTENT_ID}}/confirm \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -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.

NumberDescription
Succeeds and immediately processes the payment.
Requires authentication. Stripe triggers a modal asking for the customer to authenticate.
Always fails with a decline code of insufficient_funds.

For the full list of test cards see our guide on testing.

OptionalRe-collect a CVC

Was this page helpful?
YesNo
Need help? Contact Support.
Join our early access programme.
Check out our changelog.
Questions? Contact Sales.
LLM? Read llms.txt.
Powered by Markdoc