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 StripeServer-sideClient-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:
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):
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> ); }
Create your checkout pageClient-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 detailsClient-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 serverClient-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 PaymentIntentServer-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_
. If the payment failed, the status is set back to requires_
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_
appears as requires_
and requires_
appears as requires_
.
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_
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.session
Handle any next actionsClient-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_
. 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_
. 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_
.
// ... 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 againServer-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_
.
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 |
---|---|
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_ . |
For the full list of test cards see our guide on testing.