# Accept card payments without webhooks

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

# iOS


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 [Client-side] [Server-side]

First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register).

### 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 [Stripe iOS SDK](https://github.com/stripe/stripe-ios) is open source, [fully documented](https://stripe.dev/stripe-ios/index.html), and compatible with apps supporting iOS 13 or above.

#### Swift Package Manager

To install the SDK, follow these steps:

1. In Xcode, select **File** > **Add Package Dependencies…** and enter `https://github.com/stripe/stripe-ios-spm` as the repository URL.
2. Select the latest version number from our [releases page](https://github.com/stripe/stripe-ios/releases).
3. Add the **StripePaymentsUI** product to the [target of your app](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app).

#### Carthage

1. If you haven’t already, install the latest version of [Carthage](https://github.com/Carthage/Carthage#installing-carthage).
2. Add this line to your `Cartfile`:
   ```cartfile
   github "stripe/stripe-ios"
   ```
3. Follow the [Carthage installation instructions](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos). Make sure to embed all of the required frameworks listed [here](https://github.com/stripe/stripe-ios/tree/master/StripePaymentsUI/README.md#manual-linking).
4. In the future, to update to the latest version of the SDK, run the following command:
   ```bash
   carthage update stripe-ios --platform ios
   ```

#### Manual Framework

1. Head to our [GitHub releases page](https://github.com/stripe/stripe-ios/releases/latest) and download and unzip **Stripe.xcframework.zip**.
2. Drag **StripePaymentsUI.xcframework** to the **Embedded Binaries** section of the **General** settings in your Xcode project. Make sure to select **Copy items if needed**.
3. Repeat step 2 for all required frameworks listed [here](https://github.com/stripe/stripe-ios/tree/master/StripePaymentsUI/README.md#manual-linking).
4. In the future, to update to the latest version of our SDK, repeat steps 1–3.

> For details on the latest SDK release and past versions, see the [Releases](https://github.com/stripe/stripe-ios/releases) page on GitHub. To receive notifications when a new release is published, [watch releases](https://help.github.com/en/articles/watching-and-unwatching-releases-for-a-repository#watching-releases-for-a-repository) for the repository.

Configure the SDK with your Stripe [publishable key](https://dashboard.stripe.com/test/apikeys) on app start. This enables your app to make requests to the Stripe API.

#### Swift

```swift
import UIKit
importStripePaymentsUI

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        StripeAPI.defaultPublishableKey = "<<YOUR_PUBLISHABLE_KEY>>"
        // do any other necessary launch configuration
        return true
    }
}
```

> Use your [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 screen [Client-side]

Securely collect card information on the client with [STPPaymentCardTextField](https://stripe.dev/stripe-ios/stripe-payments-ui/Classes/STPPaymentCardTextField.html), a drop-in 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)
Create an instance of the card component and a “Pay” button with the following code:

#### Swift

```swift
import UIKit
import StripePaymentsUI
class CheckoutViewController: UIViewController {
    lazy var cardTextField: STPPaymentCardTextField = {
        let cardTextField = STPPaymentCardTextField()
        return cardTextField
    }()
    lazy var payButton: UIButton = {
        let button = UIButton(type: .custom)
        button.layer.cornerRadius = 5
        button.backgroundColor = .systemBlue
        button.titleLabel?.font = UIFont.systemFont(ofSize: 22)
        button.setTitle("Pay", for: .normal)
        button.addTarget(self, action: #selector(pay), for: .touchUpInside)
        return button
    }()
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        let stackView = UIStackView(arrangedSubviews: [cardTextField, payButton])
        stackView.axis = .vertical
        stackView.spacing = 20
        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView)
        NSLayoutConstraint.activate([
            stackView.leftAnchor.constraint(equalToSystemSpacingAfter: view.leftAnchor, multiplier: 2),
            view.rightAnchor.constraint(equalToSystemSpacingAfter: stackView.rightAnchor, multiplier: 2),
            stackView.topAnchor.constraint(equalToSystemSpacingBelow: view.topAnchor, multiplier: 2),
        ])
    }
    @objc
    func pay() {
        // ...
    }
}
```

Run your app, and make sure your checkout screen shows the card component and pay button.

## 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 card element.

#### Swift

```swift
class CheckoutViewController: UIViewController {
    // ...
    @objc
    func pay() {
        // Collect card details on the client
        STPAPIClient.shared.createPaymentMethod(with: cardTextField.paymentMethodParams) { [weak self] paymentMethod, error in
            // Create PaymentMethod failed
            if let createError = error {
                self?.displayAlert(title: "Payment failed", message: createError.localizedDescription)
            }
            if let paymentMethodId = paymentMethod?.stripeId {
                self?.pay(withPaymentMethod: paymentMethodId)
            }
        }
    }
    func pay(withPaymentMethod paymentMethodId: String? = nil, withPaymentIntent paymentIntentId: String? = nil) {
        // ...continued in the next step
    }
    // ...
}
```

## Create a PaymentIntent [Client-side] [Server-side]

Stripe uses a `PaymentIntent` object to represent your intent to collect payment from a customer, tracking your charge attempts and payment state changes throughout the process.

### On the server

Add an endpoint that creates the PaymentIntent with the following parameters:

- [payment_method_id](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-payment_method) - the id of the PaymentMethod from the previous step
- [return_url](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-return_url) - include if you’ve [set up a return URL](https://docs.stripe.com/payments/3d-secure.md)
- [use_stripe_sdk](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-use_stripe_sdk)
  - For apps that integrate with [Stripe iOS SDK v16.0.0](https://github.com/stripe/stripe-ios/blob/master/CHANGELOG.md#1600-2019-07-18) and above, the value of this parameter must be set to `true`
  - For apps that integrate with older SDK versions, **don’t pass this parameter**
- [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) - Set this to true to *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

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 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.

After the PaymentIntent is created, return the following to the client:

- the PaymentIntent’s [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret)
- `requiresAction: true` if the PaymentIntent [status](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-status) is `requires_action`

> See an [example server implementation](https://github.com/stripe-samples/accept-a-payment/tree/master/custom-payment-flow/server).

### On the client

Request a `PaymentIntent` from your server. This example passes the server a list of items to determine the price. Always decide how much to charge on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

#### Swift

```swift
let BackendUrl = "http://127.0.0.1:4242/"
class CheckoutViewController: UIViewController {
    // ...
    func displayAlert(title: String, message: String, restartDemo: Bool = false) {
        // ...omitted for brevity
    }
    func pay(withPaymentMethod paymentMethodId: String? = nil, withPaymentIntent paymentIntentId: String? = nil) {
        // Create a PaymentIntent on the server
        let url = URL(string: BackendUrl + "pay")!
        var json: [String: Any] = [:]
        if let paymentMethodId = paymentMethodId {
            json = [
                "useStripeSdk": true,
                "paymentMethodId": paymentMethodId,
                "currency": "usd",
                "items": [
                    "id": "photo_subscription"
                ]
            ]
        }
        else if let paymentIntentId = paymentIntentId {
            json = [
                "paymentIntentId": paymentIntentId,
            ]
        }
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try? JSONSerialization.data(withJSONObject: json)
        let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, requestError) in
            guard let response = response as? HTTPURLResponse,
                response.statusCode == 200,
                let data = data,
                let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] else {
                    self?.displayAlert(title: "Payment failed", message: requestError?.localizedDescription ?? "")
                    return
            }
            let payError = json["error"] as? String
            let clientSecret = json["clientSecret"] as? String
            let requiresAction = json["requiresAction"] as? Bool
            // Payment failed
            if let payError = payError {
                self?.displayAlert(title: "Payment failed", message: payError)
            }
            // Payment succeeded, no additional action required
            else if clientSecret != nil && (requiresAction == nil || requiresAction == false) {
                self?.displayAlert(title: "Payment succeeded", message: clientSecret ?? "", restartDemo: true)
            }
            // Payment requires additional action
            else if clientSecret != nil && requiresAction == true && self != nil {
                // ...continued in the next step
            }
        })
        task.resume()
    }
}
```

## Handle any next actions [Client-side]

Write code to handle situations that require your customer to intervene. A normal payment succeeds after you confirm it on the server in [step 4](https://docs.stripe.com/payments/accept-a-payment-synchronously.md#ios-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.

In these cases, the PaymentIntent’s status is set to `requires_action`. On the client, pass the PaymentIntent ID to [STPPaymentHandler handleNextActionForPayment](https://stripe.dev/stripe-ios/stripe-payments/Classes/STPPaymentHandler.html#/c:@M@StripePayments@objc\(cs\)STPPaymentHandler\(im\)handleNextActionForPayment:withAuthenticationContext:returnURL:completion:). `STPPaymentHandler` presents view controllers, using the [STPAuthenticationContext](https://stripe.dev/stripe-ios/stripe-payments/Protocols/STPAuthenticationContext.html) passed in, and walks the customer through authentication. For more details, read about [3D Secure authentication on iOS](https://docs.stripe.com/payments/3d-secure.md?platform=ios).

After handling required actions on the client, the status of the PaymentIntent changes to `requires_confirmation`. This step enables your integration to synchronously *fulfill* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected) 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`.

#### Swift

```swift
class CheckoutViewController: UIViewController {
    // ...
    // Create or confirm a PaymentIntent on the server
    func pay(withPaymentMethod paymentMethodId: String? = nil, withPaymentIntent paymentIntentId: String? = nil) {
        // ...
            // Payment requires additional action
            else if clientSecret != nil && requiresAction == true && self != nil {
                let paymentHandler = STPPaymentHandler.shared()
                paymentHandler.handleNextAction(forPayment: clientSecret!, with: self!, returnURL: nil) { status, paymentIntent, handleActionError in
                    switch (status) {
                    case .failed:
                        self?.displayAlert(title: "Payment failed", message: handleActionError?.localizedDescription ?? "")
                        break
                    case .canceled:
                        self?.displayAlert(title: "Payment canceled", message: handleActionError?.localizedDescription ?? "")
                        break
                    case .succeeded:
                        if let paymentIntent = paymentIntent, paymentIntent.status == STPPaymentIntentStatus.requiresConfirmation {
                            print("Re-confirming PaymentIntent after handling action")
                            self?.pay(withPaymentIntent: paymentIntent.stripeId)
                        }
                        else {
                            self?.displayAlert(title: "Payment succeeded", message: paymentIntent?.description ?? "", restartDemo: true)
                        }
                        break
                    @unknown default:
                        fatalError()
                        break
                    }
                }
            }
        // ...
    }
}
extension CheckoutViewController: STPAuthenticationContext {
    func authenticationPresentingViewController() -> UIViewController {
        return self
    }
}
```

## 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. After re-collecting the customer’s CVC information, tokenize the CVC data using [STPAPIClient createTokenForCVCUpdate](https://stripe.dev/stripe-ios/stripe-payments/Classes/STPAPIClient.html#/c:@CM@StripePayments@StripeCore@objc\(cs\)STPAPIClient\(im\)createTokenForCVCUpdate:completion:).

#### Swift

```swift
import UIKit
import StripePaymentsUI
class CheckoutViewController: UIViewController {
    // ...
    func tokenizeCVC() {
        guard let cvc = cvc else {
          return;
        }
        STPAPIClient.shared.createToken(forCVCUpdate: cvc) { (token, error) in
                if error != nil || token == nil {
                    // handle error
                } else {
                    let tokenId = token?.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.

## See also

- [Accept Apple Pay](https://docs.stripe.com/apple-pay.md)

