DEV Community

Cover image for Processing payments using Stripe SDK iOS
Shivam Maggu
Shivam Maggu

Posted on • Originally published at blog.shivammaggu.com on

Processing payments using Stripe SDK iOS

This guide explains how to install and set up Stripe SDK on your iOS application.

Table of Contents

Aim

Open the inbuilt Address and Payment Sheet of Stripe and start accepting payments from your customer.

Minimum iOS Deployment Version

12.0

Stripe Frameworks

  1. Stripe (22.8.4)

  2. StripeApplePay (22.8.4)

  3. StripeCore (22.8.4)

  4. StripeUICore (22.8.4)

Requirements

  1. MacBook / MacOS

  2. CocoaPods installed on your system

  3. Xcode

  4. Terminal

Proof of Concept

iOS project and test backend for processing payments with Stripe SDK.

GitHub logo shivammaggu / payments-using-stripe

Start accepting payments from your customers through your iOS app using Stripe SDK

payments-using-stripe

Checkout this article for recreating or setting up this demo project

Simulator Screen Recording of the Project




Setup

Adding the Stripe dependency

  • Open Terminal in the project directory and do pod init.

  • Open the newly created Podfile and set the iOS version to 12.0.


 # Uncomment the next line to define a global platform for your project
 platform :ios, '12.0'

Enter fullscreen mode Exit fullscreen mode
  • Add the Stripe dependency to the Podfile.

 target 'payments with stripe' do
   # Comment the next line if you don't want to use dynamic frameworks
   use_frameworks!

   # Pods for payments with stripe

   pod 'Stripe'

 end

Enter fullscreen mode Exit fullscreen mode
  • Save the Podfile and do a pod install on the Terminal. The following dependencies showed be installed.

pod install

Analyzing dependencies
Downloading dependencies
Installing Stripe (22.8.4)
Installing StripeApplePay (22.8.4)
Installing StripeCore (22.8.4)
Installing StripeUICore (22.8.4)
Generating Pods project
Integrating client project

Enter fullscreen mode Exit fullscreen mode
  • Close the payments with stripe.xcodeproj and open payments with stripe.xcworkspace.

  • Select a simulator and run the project. The project should run successfully with a blank screen.

Getting started with Stripe Developers Dashboard

This process should be done once. All the teams will use the same Dashboard. (Backend, iOS, Android etc.)

  1. Go to stripe.com and click on start now.

  2. Follow the steps as directed by the website to complete the registration process.

  3. Complete the email verification and set up your business details for which payments will be accepted by Stripe.

  4. On the Stripe Dashboard, you should be able to see your Publishable key and Secret key for both test mode and live mode.

    1. The publishable key is used on the client side such as in iOS, android or web apps.
    2. The secret key is used on the server side. (Secret key should not be stored on client-facing apps.)

Setup Stripe in your iOS App

  • Open AppDelegate.swift and add the Publishable key inside the didFinishLaunchingWithOptions method. The publishable key can be retrieved from the Stripe Developers Dashboard.

  StripeAPI.defaultPublishableKey = "Insert your key here"

Enter fullscreen mode Exit fullscreen mode

Use the test mode keys during the testing and development phase, and the live mode keys when publishing the app.

  • We need to hit a POST API to create the payment intent and in return receive the following data. This data is used to initialize the customer, Stripe SDK and the PaymentSheet.

    • customerId
    • ephemeralKey
    • paymentIntent
    • publishableKey

Refer to CourseCheckoutViewModel in the Proof of Concept example on GitHub.

// Creates a payment intent for the customer and fetches the important keys and id's for processing a payment

func fetchPaymentIntent(completion: @escaping ((Bool, String?) -> Void)) {

    // This could be the details for the item that the customer wants to buy

    let cartContent: [String: Any] = ["items": [["id": UUID().uuidString]]]

    self.apiClient.createPaymentIntent(cartContent: cartContent) { [weak self] (data, error) in

        guard let self = self else { return }

        guard error == nil else {
            print(error.debugDescription)
            completion(false, error.debugDescription)
            return
        }

        guard
            let customerId = data?.customerId as? String,
            let customerEphemeralKeySecret = data?.ephemeralKey as? String,
            let paymentIntentClientSecret = data?.paymentIntent as? String,
            let publishableKey = data?.publishableKey as? String
        else {
            let error = "Error fetching required data"
            print(error)
            completion(false, error)
            return
        }

        print("Created Payment Intent")

        self.setPublishableKey(publishableKey: publishableKey)
        self.paymentIntentClientSecret = paymentIntentClientSecret
        self.customer = .init(id: customerId,
                              ephemeralKeySecret: customerEphemeralKeySecret)
        completion(true, nil)
    }
}

Enter fullscreen mode Exit fullscreen mode
  • Setup the PaymentSheet in your View Controller. Refer to CourseCheckoutViewController in the Proof of Concept example on GitHub.

// Create and configure the payment sheet

private func createPaymentSheet() {
    var configuration = PaymentSheet.Configuration()

    // Autofills the shipping details with the one we collected from user through AddressViewController

    configuration.shippingDetails = { [weak self] in
        return self?.address
    }

    // Configure button for Apple Pay in PaymentSheet

    configuration.applePay = .init(merchantId: "com.example.appname",
                                    merchantCountryCode: "US")

    // Provides checkbox for saving card details

    configuration.savePaymentMethodOptInBehavior = .requiresOptIn

    // Sets the return url. Used when user needs to go outside the app for authentication

    configuration.returnURL = "payments-with-stripe://stripe-redirect"

    // Configures the color of the pay button

    configuration.primaryButtonColor = .blue

    // Shows the name of the company collecting the payment

    configuration.merchantDisplayName = "Dummy Corp Inc"

    // Allows payments which requires some time to confirm

    configuration.allowsDelayedPaymentMethods = true

    // Adds default billing details for user to the payment object

    configuration.defaultBillingDetails.email = "dummy@corp.com"
    configuration.defaultBillingDetails.name = self.address?.name
    configuration.defaultBillingDetails.phone = self.address?.phone
    configuration.defaultBillingDetails.address.country = self.address?.address.country
    configuration.defaultBillingDetails.address.city = self.address?.address.city
    configuration.defaultBillingDetails.address.line1 = self.address?.address.line1
    configuration.defaultBillingDetails.address.line2 = self.address?.address.line2
    configuration.defaultBillingDetails.address.state = self.address?.address.state
    configuration.defaultBillingDetails.address.postalCode = self.address?.address.postalCode

    // Sets customer object to payment object

    if let customer = self.viewModel.getCustomer() {
        configuration.customer = customer
    }

    // Initiaises the PaymentSheet with the above config and client secret received from payment intent API

    if let paymentIntentClientSecret = self.viewModel.getPaymentIntentClientSecret() {
        self.paymentSheet = PaymentSheet(paymentIntentClientSecret: paymentIntentClientSecret,
                                        configuration: configuration)
    }
}

Enter fullscreen mode Exit fullscreen mode
  • Handle the conditions after the payment. The following three scenarios can happen during/after the payment.

// Present user with the payment sheet and handles the various payment scenarios

private func presentPaymentSheet() {
    guard let paymentSheet = paymentSheet else { return }

    DispatchQueue.main.async {
        paymentSheet.present(from: self) { [weak self] (paymentResult) in

            guard let self = self else { return }

            switch paymentResult {
            case .completed:
                self.displayAlert(title: "Payment complete!")
            case .canceled:
                self.displayAlert(title: "Payment canceled!")
            case .failed(let error):
                self.displayAlert(title: "Payment failed", message: error.localizedDescription)
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode
  • On tap of the pay now button, present the payment sheet, add the payment details and click on pay.

  • Either the payment will be completed or it will fail if there are issues with the payment method. We can debug the failure reasons in the following link.

  • Payment can also result in cancellation if the user dismisses the PaymentSheet.

Address collection

  • We can set up the AddressViewController to display a form for collecting names, addresses, cities, countries, postal codes and phone numbers.

 // Setup and display the address sheet

 private func presentAddressCollectionController(_ controller: CourseCheckoutViewController) {
     let addressConfiguration = AddressViewController.Configuration(additionalFields: .init(name: .required,
                                                                                            phone: .required),
                                                                     allowedCountries: [],
                                                                     title: "Billing Address")
     let addressViewController = AddressViewController(configuration: addressConfiguration,
                                                       delegate: controller)

     let navigationController = UINavigationController(rootViewController: addressViewController)
     self.rootViewController.present(navigationController, animated: true)
 }

Enter fullscreen mode Exit fullscreen mode
  • Set up the delegate method to get the address details filled in by the customer.

 extension CourseCheckoutViewController: AddressViewControllerDelegate {

     func addressViewControllerDidFinish(_ addressViewController: Stripe.AddressViewController, with address: Stripe.AddressViewController.AddressDetails?) {
         addressViewController.dismiss(animated: true) {
             debugPrint(address as Any)

             self.address = address
         }
     }
 }

Enter fullscreen mode Exit fullscreen mode
  1. The address details collected from the user should be passed to PaymentSheet Configuration. This will auto-fill the details in the PaymentSheet and also send the customer details to Stripe Dashboard.

  2. Collecting these details is essential if the Stripe account is registered in India but the users are paying in international currency. More on this here.


 var configuration = PaymentSheet.Configuration()

 configuration.shippingDetails = { [weak self] in
     return self?.address
 }

Enter fullscreen mode Exit fullscreen mode

Enable card scanning

To enable the option for scanning debit/credit cards, add the following key to the info.plist file.


  <key>NSCameraUsageDescription</key>
  <string>Allow the app to scan cards.</string>

Enter fullscreen mode Exit fullscreen mode

This feature is available on iOS 13.0 and above.

Process payments using Apple Pay

Steps for integrating Apple Pay are well documented in the links below.

Please check out this tutorial for adding Apple Pay as a payment option to your app.

  1. Apple Pay with Payment Sheet

  2. Setup Apple Pay button

Add an Apple pay button on your PaymentSheet by editing the configuration as follows.


var configuration = PaymentSheet.Configuration()
configuration.applePay = .init(merchantId: "merchant.com.your_app_name",
                               merchantCountryCode: "US")

Enter fullscreen mode Exit fullscreen mode

Return to the app automatically after external authentication

A customer may go out of your app for authentication purposes, for example, in Safari or their banking app to complete payment.

To allow them to automatically return to your app after authenticating, configure a custom URL scheme or universal link and set up your app delegate/scene delegate

(If using scene delegate for iOS 13 and above) to forward the URL to the SDK.

  • AppDelegate.swift

  // This method handles opening custom URL schemes
  // (for example, "your-app://stripe-redirect")

  func application(_ app: UIApplication,
                    open url: URL, 
                    options: [UIApplication.OpenURLOptionsKey : Any] = [:]
  ) -> Bool {

      let stripeHandled = StripeAPI.handleURLCallback(with: url)

      if !stripeHandled {

          /*
            This was not a stripe url, do whatever url handling your app
            normally does, if any.
            */
      }

      return true
  }

  // This method handles opening universal link URLs
  // (for example, "https://example.com/stripe_ios_callback")

  func application(_ application: UIApplication,
                    continue userActivity: NSUserActivity,
                    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
  ) -> Bool  {

      if userActivity.activityType == NSUserActivityTypeBrowsingWeb {

          if let url = userActivity.webpageURL {

              let stripeHandled = StripeAPI.handleURLCallback(with: url)

              guard !stripeHandled else { return true }

              /*
                This was not a stripe url, do whatever url handling your app
                normally does, if any.
                */
          }
      }

      return false
  }

Enter fullscreen mode Exit fullscreen mode
  • SceneDelegate.swift

  func scene(_ scene: UIScene,
              openURLContexts URLContexts: Set<UIOpenURLContext>) {

      if let urlContext = URLContexts.first {

          let url = urlContext.url
          let stripeHandled = StripeAPI.handleURLCallback(with: url)

          if !stripeHandled {
              // This was not a stripe url, do whatever url handling your app
              // normally does, if any.
          }
      }
  }

Enter fullscreen mode Exit fullscreen mode

Also, a returnURL should be configured in the PaymentSheet.Configuration object to the URL for your app.


var configuration = PaymentSheet.Configuration()
configuration.returnURL = "payments-with-stripe://stripe-redirect"

Enter fullscreen mode Exit fullscreen mode

Clear cookies on logout

PaymentSheet related cookies and other authentication data should be cleared when a user logs out of the app. Add the following code to your logout function.


import Stripe

// Resets all payment and user related cache
// Should be called when user logs out of the app

func logout() {

    /*
      Perform all other logout related steps here
      */

    PaymentSheet.resetCustomer()
}

Enter fullscreen mode Exit fullscreen mode

FAQ

  1. Stripe SDK can be installed using CocoaPods, Carthage, and Swift Package Manager.

  2. The latest version v23.3.2 or above of Stripe iOS SDK requires Xcode 13.2.1 or later and is compatible with apps targeting iOS 13 or above.

  3. For iOS 12 support use v22.8.4.

  4. For iOS 11 support use v21.13.0.

  5. For iOS 10 support use v19.4.0.

  6. For iOS 9 support use v17.0.2.

  7. Why was my card declined when trying to make a payment? Check the reason for your decline code.

  8. Got an error message while processing your payment? Check the reason in this article.

Resources

  1. Stripe SDK on CocoaPods

  2. Stripe SDK on GitHub

  3. Stripe API Reference

  4. Stripe Documentation

  5. Stripe iOS SDK Documentation

  6. Installing Stripe SDK using CocoaPods

  7. About API Keys

  8. Test mode and Live mode

  9. Accepting Payments with Stripe

  10. Collecting User's Address

  11. Dummy Credentials for testing payments

  12. Accept payment from regions outside of India

  13. Adding multiple payment methods

  14. Debug card Decline Codes

  15. Understanding payment Error Codes

  16. Collecting user phone numbers through the backend

  17. Adding additional articles on security when processing payments using Stripe:

    1. Strong Customer Authentication
    2. Security
    3. 3-D Secure
    4. Privacy Details for iOS

Top comments (0)