# Processing payments using Stripe SDK iOS

# 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 - shivammaggu/payments-using-stripe](https://github.com/fluidicon.png align="center")](https://github.com/shivammaggu/payments-using-stripe)

# Setup

## Adding the Stripe dependency

1. Open **Terminal** in the project directory and do `pod init`.
    
2. Open the newly created `Podfile` and set the iOS version to *12.0.*
    
    ```plaintext
    # Uncomment the next line to define a global platform for your project
    platform :ios, '12.0'
    ```
    
3. Add the **Stripe** dependency to the `Podfile`.
    
    ```plaintext
    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
    ```
    
4. 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
    
5. Close the `payments with stripe.xcodeproj` and open `payments with stripe.xcworkspace`.
    
6. 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](http://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](https://dashboard.stripe.com/apikeys), 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.
    
    ```swift
    StripeAPI.defaultPublishableKey = "Insert your key here"
    ```
    
    > Use the [test mode](https://stripe.com/docs/keys#obtain-api-keys) keys during the testing and development phase, and the [live mode](https://stripe.com/docs/keys#test-live-modes) 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`.
    
    1. `customerId`
        
    2. `ephemeralKey`
        
    3. `paymentIntent`
        
    4. `publishableKey`
        

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

```swift
// 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)
    }
}
```

* Setup the `PaymentSheet` in your View Controller. Refer to `CourseCheckoutViewController` in the Proof of Concept example on GitHub.
    

```swift
// 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)
    }
}
```

* Handle the conditions after the payment. The following three scenarios can happen during/after the payment.
    

```swift
// 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)
            }
        }
    }
}
```

* 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](https://stripe.com/docs/error-codes).
    
* Payment can also result in cancellation if the user dismisses the `PaymentSheet`.
    

## Address collection

1. We can set up the `AddressViewController` to display a form for collecting names, addresses, cities, countries, postal codes and phone numbers.
    
    ```swift
    // 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)
    }
    ```
    
2. Set up the delegate method to get the address details filled in by the customer.
    
    ```swift
    extension CourseCheckoutViewController: AddressViewControllerDelegate {
        
        func addressViewControllerDidFinish(_ addressViewController: Stripe.AddressViewController, with address: Stripe.AddressViewController.AddressDetails?) {
            addressViewController.dismiss(animated: true) {
                debugPrint(address as Any)
                
                self.address = address
            }
        }
    }
    ```
    
3. 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.
    
4. 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](https://stripe.com/docs/india-accept-international-payments).
    
    ```swift
    var configuration = PaymentSheet.Configuration()
    
    configuration.shippingDetails = { [weak self] in
        return self?.address
    }
    ```
    

## Enable card scanning

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

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

> 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](https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=payment-sheet#ios-apple-pay)
    
2. [Setup Apple Pay button](https://stripe.com/docs/apple-pay)
    

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

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

## 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](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) or [universal link](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content) 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**
    
    ```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
    }
    ```
    
* **SceneDelegate.swift**
    
    ```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.
            }
        }
    }
    ```
    

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

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

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

```swift
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()
}
```

# FAQ

1. Stripe SDK can be installed using CocoaPods, Carthage, and Swift Package Manager.
    
2. The latest version [v23.3.2](https://github.com/stripe/stripe-ios/tree/23.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](https://github.com/stripe/stripe-ios/tree/22.8.4).
    
4. For iOS 11 support use → [v21.13.0](https://github.com/stripe/stripe-ios/tree/21.13.0).
    
5. For iOS 10 support use → [v19.4.0](https://github.com/stripe/stripe-ios/tree/v19.4.0).
    
6. For iOS 9 support use → [v17.0.2](https://github.com/stripe/stripe-ios/tree/v17.0.2).
    
7. Why was my card declined when trying to make a payment? Check the reason for your [decline code](https://stripe.com/docs/declines/codes).
    
8. Got an error message while processing your payment? Check the reason in this [article](https://stripe.com/docs/error-codes).
    

# Resources

1. [Stripe SDK on CocoaPods](https://cocoapods.org/pods/Stripe)
    
2. [Stripe SDK on GitHub](https://github.com/stripe/stripe-ios)
    
3. [Stripe API Reference](https://stripe.com/docs/api)
    
4. [Stripe Documentation](https://stripe.com/docs)
    
5. [Stripe iOS SDK Documentation](https://stripe.dev/stripe-ios/documentation/stripe/)
    
6. [Installing Stripe SDK using CocoaPods](https://stripe.com/docs/libraries/ios)
    
7. [About API Keys](https://stripe.com/docs/keys#obtain-api-keys)
    
8. [Test mode and Live mode](https://stripe.com/docs/keys#test-live-modes)
    
9. [Accepting Payments with Stripe](https://stripe.com/docs/payments/accept-a-payment)
    
10. [Collecting User's Address](https://stripe.com/docs/elements/address-element/collect-addresses)
    
11. [Dummy Credentials for testing payments](https://stripe.com/docs/testing)
    
12. [Accept payment from regions outside of India](https://stripe.com/docs/india-accept-international-payments)
    
13. [Adding multiple payment methods](https://stripe.com/docs/payments/payment-methods/integration-options#payment-method-product-support)
    
14. [Debug card Decline Codes](https://stripe.com/docs/declines/codes)
    
15. [Understanding payment Error Codes](https://stripe.com/docs/error-codes)
    
16. [Collecting user phone numbers through the backend](https://stripe.com/docs/payments/checkout/phone-numbers)
    
17. Adding additional articles on security when processing payments using Stripe:
    
    1. [Strong Customer Authentication](https://stripe.com/docs/strong-customer-authentication)
        
    2. [Security](https://stripe.com/docs/security)
        
    3. [3-D Secure](https://stripe.com/docs/payments/3d-secure)
        
    4. [Privacy Details for iOS](https://support.stripe.com/questions/stripe-ios-sdk-privacy-details)
