# Accept card payments without webhooks

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

# React Native


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 uses a single client-to-server flow to take payments, 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:

- **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#should-retry).
- **Manual authentication handling** – 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 [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:

#### 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'
```

### Client-side

The [React Native SDK](https://github.com/stripe/stripe-react-native) is open source and fully documented. Internally, it uses the [native iOS](https://github.com/stripe/stripe-ios) and [Android](https://github.com/stripe/stripe-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):

#### yarn

```bash
yarn add @stripe/stripe-react-native
```

#### npm

```bash
npm install @stripe/stripe-react-native
```

Next, install some other necessary dependencies:

- For iOS, go 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.

> We recommend following the [official TypeScript guide](https://reactnative.dev/docs/typescript#adding-typescript-to-an-existing-project) to add TypeScript support.

### Stripe initialisation

To initialise Stripe in your React Native app, either wrap your payment screen with the `StripeProvider` component, or use the `initStripe` initialisation method. Only the API [publishable key](https://docs.stripe.com/keys.md#obtain-api-keys) in `publishableKey` is required. The following example shows how to initialise Stripe using the `StripeProvider` component.

```jsx
import { useState, useEffect } from 'react';
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>
  );
}
```

> Use your API [test keys](https://docs.stripe.com/keys.md#obtain-api-keys) while you test and develop, and your [live mode](https://docs.stripe.com/keys.md#test-live-modes) 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.
![](https://d37ugbyn3rpeym.cloudfront.net/docs/mobile/ios/card-field.mp4)
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.

```javascript
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* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs) with the details collected by the `CardField` component.

```typescript
import { CardField, useStripe } from '@stripe/stripe-react-native';
function PaymentScreen() {
  const { createPaymentMethod, handleNextAction } = useStripe();
  const pay = async () => {
    // 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.

```javascript
// ...
const pay = async () => {
  // ...
  // 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](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 authorisation 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 <<YOUR_SECRET_KEY>>: \
  -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]

A normal payment succeeds after you *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) it on the server in step 4. However, some payment flows require 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).

​​For cases that require any next actions, the PaymentIntent’s status is `requires_action`. On the client, pass the PaymentIntent’s *client secret* (The client secret is a unique key returned from Stripe as part of a PaymentIntent. This key lets the client access important fields from the PaymentIntent (status, amount, currency) while hiding sensitive ones (metadata, customer)) 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 fulfil the order on your back end and return the *fulfillment* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) 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`.

```javascript
// ...
const pay = async () => {
  // ...
  // 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](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 <<YOUR_SECRET_KEY>>: \
  -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 expiry 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: Re-collect a CVC

When creating subsequent payments with 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. After re-collecting the customer’s CVC information, tokenise the CVC data with `createTokenForCVCUpdate`.

```javascript
function PaymentScreen() {
  // ...
  const { createTokenForCVCUpdate } = useStripe();
  const tokenizeCVC = async () => {
    const { error, tokenId } = await createTokenForCVCUpdate();
    if (error) {
      // handle error
    } else if (tokenId) {
      // pass the token ID to your backend
    }
  };
}
```

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.

```curl
curl https://api.stripe.com/v1/payment_intents \
  -u "<<YOUR_SECRET_KEY>>:" \
  -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.

