Accept a payment
Collecting payments in your iOS app consists of creating an object to track a payment on your server, collecting card information in your app, and submitting the payment to Stripe for processing.
Stripe uses a payment object, called a PaymentIntent
, to track and handle all the states of the payment until it’s completed, including situations like two-factor authentication when the bank requires customer intervention.
The steps in this guide are fully implemented on GitHub in the using-webhooks
directory. Clone the repo and follow the instructions to run the demo app.
Set up StripeServer-sideClient-side
First, you need a Stripe account. Register now.
Server-side
This integration requires endpoints on your server that talk to the Stripe API. Use the official libraries for access to the Stripe API from your server:
# Available as a gem sudo gem install stripe
# If you use bundler, you can add this line to your Gemfile gem 'stripe'
Client-side
The iOS SDK is open source, fully documented, and compatible with apps supporting iOS 11 or above.
- If you haven’t already, install the latest version of CocoaPods.
- If you don’t have an existing Podfile, run the following command to create one:Terminal
pod init
- Add this line to your
Podfile
:Podfilepod 'Stripe'
- Run the following command:Terminal
pod install
- Don’t forget to use the
.xcworkspace
file to open your project in Xcode, instead of the.xcodeproj
file, from here on out. - In the future, to update to the latest version of the SDK, just run:Terminal
pod update Stripe
For details on the latest SDK release and past versions, see the Releases page on GitHub. To receive notifications when a new release is published, watch releases for the repository.
When your app starts, configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API.
import UIKit import Stripe @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { StripeAPI.defaultPublishableKey =
// do any other necessary launch configuration return true } }"pk_test_TYooMQauvdEDq54NiTphI7jx"
Create your checkout pageClient-side
Securely collect card information on the client with STPPaymentCardTextField, a drop-in UI component provided by the SDK.
STPPaymentCardTextField performs real-time validation and formatting.
Create an instance of the card component and a Pay button with the following code:
import UIKit import Stripe 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 page shows the card component and pay button.
Create a PaymentIntentServer-sideClient-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.
Server-side
On your server, make an endpoint that creates a PaymentIntent with an amount and currency. 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.
curl https://api.stripe.com/v1/payment_intents \ -u
: \ -d "amount"=1099 \ -d "currency"="usd"sk_test_4eC39HqLyjWDarjtT1zdp7dc
Instead of passing the entire PaymentIntent object to your app, only return its client secret. The PaymentIntent’s client secret is a unique key that lets you confirm the payment and update card details on the client, without allowing manipulation of sensitive information, like payment amount.
Client-side
On the client, request a PaymentIntent from your server and store its client secret.
class CheckoutViewController: UIViewController { var paymentIntentClientSecret: String? // ...continued from previous step override func viewDidLoad() { // ...continued from previous step startCheckout() } func startCheckout() { // Request a PaymentIntent from your server and store its client secret // Click Open on GitHub to see a full implementation } }
Submit the payment to StripeClient-side
When the customer taps the Pay button, confirm the PaymentIntent
to complete the payment.
First, assemble a STPPaymentIntentParams object with:
- The card text field’s payment method details
- The
PaymentIntent
client secret from your server
Rather than sending the entire PaymentIntent object to the client, use its client secret. This is different from your API keys that authenticate Stripe API requests. The client secret is a string that lets your app access important fields from the PaymentIntent (e.g., status
) while hiding sensitive ones (e.g., customer
).
The client secret should still be handled carefully because it can complete the charge. Don’t log it, embed it in URLs, or expose it to anyone but the customer.
Next, complete the payment by calling the STPPaymentHandler confirmPayment method.
class CheckoutViewController: UIViewController { // ... @objc func pay() { guard let paymentIntentClientSecret = paymentIntentClientSecret else { return; } // Collect card details let cardParams = cardTextField.cardParams let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: nil, metadata: nil) let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntentClientSecret) paymentIntentParams.paymentMethodParams = paymentMethodParams // Submit the payment let paymentHandler = STPPaymentHandler.shared() paymentHandler.confirmPayment(withParams: paymentIntentParams, authenticationContext: self) { (status, paymentIntent, error) in switch (status) { case .failed: self.displayAlert(title: "Payment failed", message: error?.localizedDescription ?? "") break case .canceled: self.displayAlert(title: "Payment canceled", message: error?.localizedDescription ?? "") break case .succeeded: self.displayAlert(title: "Payment succeeded", message: paymentIntent?.description ?? "", restartDemo: true) break @unknown default: fatalError() break } } } } extension CheckoutViewController: STPAuthenticationContext { func authenticationPresentingViewController() -> UIViewController { return self } }
If authentication is required by regulation such as Strong Customer Authentication, STPPaymentHandler
presents view controllers using the STPAuthenticationContext passed in and walks the customer through that process. See Supporting 3D Secure Authentication on iOS to learn more.
If the payment succeeds, the completion handler is called with a status of .succeeded
. If it fails, the status is .failed
and you can display the error.localizedDescription
to the user.
You can also check the status of a PaymentIntent in the Dashboard or by inspecting the status property on the object.
Test the integration
By this point you should have a basic card integration that collects card details and makes a payment.
There are several test cards you can use in test mode to make sure this integration is ready. Use them with any CVC, postal code, and future expiration date.
Number | Description |
---|---|
Succeeds and immediately processes the payment. | |
Requires authentication. Stripe will trigger 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.