Checkout SDK for iOS

Set up Reepay Checkout in your iOS app

Integrate Reepay's pre-built checkout UI into your iOS app with the CheckoutSheet class. See our demo app example on GitHub.

📘

Reepay iOS SDK is compatible with iOS apps supporting iOS 15 or newer


1. Set up Reepay

Create a Reepay account. Register now.

Client-side

To install the SDK, use one of the following ways:

1. In Xcode, select File > Add Package Dependencies... and enter "https://github.com/reepay/reepay-ios" as the repository URL.
  
2. Select the latest version number from "https://github.com/reepay/reepay-ios/releases".

3. Add the ReepayCheckoutSheet framework to the target of your app.
1. Install the latest version of CocoaPods.

2. If you do not have an existing Podfile, run the following command to create one:
$ pod init
     
3. Add this line to your Podfile:
pod 'ReepayCheckoutSheet'
     
4. Run the following command:
$ pod install
     
5. Do not forget to use the .xcworkspace file to open your project in Xcode, instead of .xcodeproj file.
     
6. To update to the latest version of the SDK, run:
$ pod update ReepayCheckoutSheet
Coming soon...
1. Head to our GitHub releases page and download and unzip Reepay.xcframework.zip.

2. Drag "ReepayCheckoutSheet.xcframework" to the "Embedded Binaries" section of the "General" settings in your Xcode project. Remember to select "Copy items if needed".
  
3. To update to the latest version of our SDK, repeat step 1-2.

Note

For more details regarding 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.


2. Add payment methods

Note

Currently, we support Card and Apple Pay payment methods. We are working on adding and supporting all our available payment methods to the iOS SDK in the future.

View your payment methods configurations and enable the payment methods you want to support. You need at least one payment method enabled to create a Checkout Session. We recommend to add a Checkout Configuration to design your Checkout Session for displaying specifically for mobile devices or for each payment method.


3. Add session ID

📘

ReepayCheckoutSheet Version 1.1.0

From ReepayCheckoutSheet version 1.1.0 and newer, it is no longer required to provide accept_url and cancel_url

Once a Checkout Session has been created, add the id from the response to CheckoutConfiguration(sessionId: id):

import Foundation

class SessionModel: ObservableObject {
  @Published var id: String = ""

  init() {
    /// Add Reepay Checkout session ID:
    id = "<reepay_checkout_session_id>"
  }
}

import ReepayCheckoutSheet
import SwiftUI

struct ContentView: View {
  @State var checkoutSheet: CheckoutSheet?
  @StateObject private var sessionModel = SessionModel()

  func prepareCheckoutSheet() {
    /// Set session ID on CheckoutConfiguration
    MyCheckoutConfiguration.shared.setConfiguration(id: sessionModel.id)

    /// Set CheckoutConfiguration on CheckoutSheet
    if let configuration = MyCheckoutConfiguration.shared.getConfiguration() {
      checkoutSheet = CheckoutSheet(configuration: configuration)
    }
  }

  var body: some View {
    VStack {
      Button(action: {

        /// Reuse the CheckoutSheet with the session ID from sessionModel
        /// or pass another session ID to create a new CheckoutSheet

        // sessionModel.id = "<another session ID>"
        prepareCheckoutSheet()

        self.checkoutSheet?.present()
      }) {
        Label("Pay", systemImage: "creditcard.fill")
        .padding()
        .foregroundColor(.white)
        .background(.blue)
        .cornerRadius(10)
      }
    }
  }
}
import ReepayCheckoutSheet

class MyCheckoutConfiguration {
  static let shared = MyCheckoutConfiguration()
  private var configuration: CheckoutConfiguration?

  func getConfiguration() -> CheckoutConfiguration? {
    return configuration
  }

  func setConfiguration(id: String) {
    guard let configuration = CheckoutConfiguration(
      sessionID: id
    ) else {
      fatalError("Invalid session ID")
    }
    self.configuration = configuration
  }
}

4. Optional Set up styles

By default, CheckoutSheet has a default CheckoutStyle e.g. the sheet Mode will be set as Mode.largeSheet. The current styling options for CheckoutSheet and its dismiss button and alert can be found below.

protocol CheckoutStyleProtocol {
    var mode: Mode { get set } // Sheet mode
    var sheetHeightFraction: Double { get set } // Fraction of sheet height
    var sheetBackgroundColor: String { get set } // Background of sheet
    var prefersGrabberVisible: Bool { get set } // Sheet grabber
    var hideHeader: Bool { get set } // Hide header of Checkout
    var sheetDismissible: SheetDismissible { get set } // Sheet dismissible behaviour
    var dismissAlertStyle: AlertStyle? { get set } // Dismiss alert
    var dismissButtonStyle: ButtonStyle? { get set } // Dismiss button
}
public enum Mode: String {
    case mediumSheet
    case largeSheet
    case fullScreenCover
    case mediumAndLargeSheet
    case customSheet					// Only available on iOS 16 and up
}
protocol AlertStyleProtocol {
    var title: String { get set }
    var message: String { get set }
    var preferredStyle: UIAlertController.Style { get set }
    var dismissConfirmText: String { get set }
    var dismissConfirmStyle: UIAlertAction.Style { get set }
    var dismissCancelText: String { get set }
    var dismissCancelStyle: UIAlertAction.Style { get set }
}

The CheckoutConfiguration can be extended with CheckoutStyle and the CheckoutStyle can be extended with AlertStyle for the sheet's dismiss alert and with ButtonStyle for the sheet's dismiss button.

import ReepayCheckoutSheet
import SwiftUI

struct ContentView: View {
  @State var checkoutSheet: CheckoutSheet?
  @StateObject private var sessionModel = SessionModel()

  func prepareCheckoutSheet() {
    MyCheckoutConfiguration.shared.setConfiguration(id: sessionModel.id)

    /// Add styles to CheckoutSheet
    MyCheckoutConfiguration.shared.setCheckoutStyle()
    MyCheckoutConfiguration.shared.setAlertStyle()
    MyCheckoutConfiguration.shared.setButtonStyle()

    if let configuration = MyCheckoutConfiguration.shared.getConfiguration() {
      checkoutSheet = CheckoutSheet(configuration: configuration)
      setupSubscribers()
    }
  }

  var body: some View {
    VStack {
      Button(action: {
        prepareCheckoutSheet()
        self.checkoutSheet?.present()
      }) {
        Label("Pay", systemImage: "creditcard.fill")
        .padding()
        .foregroundColor(.white)
        .background(.blue)
        .cornerRadius(10)
      }
    }
  }
}
import ReepayCheckoutSheet

class MyCheckoutConfiguration {
  static let shared = MyCheckoutConfiguration()
  private var configuration: CheckoutConfiguration?

  func getConfiguration() -> CheckoutConfiguration? {
    return configuration
  }

  func setConfiguration(id: String) {
    guard let configuration = CheckoutConfiguration(
      sessionID: id
    ) else {
      fatalError("Invalid session ID")
    }
    self.configuration = configuration
  }

  func setCheckoutStyle() {
    var checkoutStyle = CheckoutStyle()
    checkoutStyle.mode = .customSheet
    checkoutStyle.sheetHeightFraction = 0.7
    checkoutStyle.sheetDismissible = .withAlertOnChanges
    checkoutStyle.hideHeader = true

    if var configuration = configuration {
      configuration.checkoutStyle = checkoutStyle
      self.configuration = configuration
    }
  }

  func setAlertStyle() {
    var alertStyle = AlertStyle()
    alertStyle.title = "Close"
    alertStyle.message = "Are you sure?"
    alertStyle.preferredStyle = .alert
    alertStyle.dismissConfirmText = "Yes!"
    alertStyle.dismissCancelText = "Not really"

    if var configuration = configuration {
      configuration.checkoutStyle.dismissAlertStyle = alertStyle
      self.configuration = configuration
    }
  }
  
  func setButtonStyle() {
    var buttonStyle = ButtonStyle(type: .iconText)
    buttonStyle.horizontalPosition = .right
    buttonStyle.verticalPosition = .above

    /// Example of default font:
    var textStyle = TextStyle(text: "Close payment", size: 15, weight: .semibold)
    
    /// Example of custom font:
    // var textStyle = TextStyle(text: "Close payment", fontName: "ChakraPetch-Regular", size: 20)

    textStyle.color = "0476BA"
    buttonStyle.textStyle = textStyle

    var iconStyle = IconStyle()
    /// Example of adding a custom icon:
    // iconStyle.bundleIdentifier = "com.reepay.ReepayCheckoutExample"
    // iconStyle.name = "octagon-xmark"
    iconStyle.color = "700000"
    iconStyle.position = .right
    buttonStyle.iconStyle = iconStyle

    if var configuration = configuration {
      configuration.checkoutStyle.dismissButtonStyle = buttonStyle
      self.configuration = configuration
    }
  }
}


5. Handle payment events

The CheckoutSheet sends a CheckoutState.accept as part of the CheckoutEvent when the payment completes successfully. Similarly, a CheckoutState.cancel will be sent when the cancel button is pressed at the bottom of the CheckoutSheet.

These events can be subscribed to by setting up event publishers from CheckoutSheet.

/// Import Combine framework to subscribe to the checkout state:
/// Publishers and Subscribers are part of Apple's Combine framework "https://developer.apple.com/documentation/combine"
import Combine

import ReepayCheckoutSheet
import SwiftUI

struct ContentView: View {
  @State var checkoutSheet: CheckoutSheet?
  
  /// Store Cancellables from the subscriptions
  @State var checkoutEventPublisher: PassthroughSubject<CheckoutEvent, Never>?
  @State var checkoutEventCancellables = Set<AnyCancellable>() /// All checkout event types
  
  /// Specific checkout events:
  // @State var acceptEventCancellables = Set<AnyCancellable>()
  // @State var cancelEventCancellables = Set<AnyCancellable>()
  // @State var closeEventCancellables = Set<AnyCancellable>()
  
  @State private var checkoutState: CheckoutState?
  @StateObject private var sessionModel = SessionModel()
  
  func prepareCheckoutSheet() {
    MyCheckoutConfiguration.shared.setConfiguration(id: sessionModel.id)
    MyCheckoutConfiguration.shared.setCheckoutStyle()
    MyCheckoutConfiguration.shared.setAlertStyle()
   	MyCheckoutConfiguration.shared.setButtonStyle()

    if let configuration = MyCheckoutConfiguration.shared.getConfiguration() {
      checkoutSheet = CheckoutSheet(configuration: configuration)
      setupSubscribers()
    }
  }
  
  var body: some View {
    VStack {
      Button(action: {
        prepareCheckoutSheet()
        self.checkoutSheet?.present()
      }) {
        Label("Pay", systemImage: "creditcard.fill")
        .padding()
        .foregroundColor(.white)
        .background(.blue)
        .cornerRadius(10)
      }
    }
  }
}
extension ContentView {
  private func setupSubscribers() {
    
    if checkoutEventPublisher == nil {
      guard let checkoutEventPublisher = checkoutSheet?.getCheckoutEventPublisher().eventPublisher else {
        fatalError("Could not get Checkout Event Publisher from SDK")
      }
      self.checkoutEventPublisher = checkoutEventPublisher
      self.checkoutEventPublisher?
        .sink(receiveValue: { (event: CheckoutEvent) in
                             	self.handleEvent(event: event)
                            })
        .store(in: &checkoutEventCancellables)
    }
    
   	/// Exmaple of subscribing specific checkut events:
    /*
    checkoutSheet?.getCheckoutEventPublisher().acceptEventPublisher
      .sink(receiveValue: { (_: CheckoutEvent) in })
      .store(in: &acceptEventCancellables)
    checkoutSheet?.getCheckoutEventPublisher().cancelEventPublisher
      .sink(receiveValue: { (_: CheckoutEvent) in })
      .store(in: &cancelEventCancellables)
    checkoutSheet?.getCheckoutEventPublisher().closeEventPublisher
      .sink(receiveValue: { (_: CheckoutEvent) in })
      .store(in: &closeEventCancellables)
    */
  }
  
   private func handleEvent(event: CheckoutEvent) {
     checkoutState = event.state

     switch event.state {
       case CheckoutState.`init`:
       			print("Checkout has initiated")
       case CheckoutState.accept:
            if let data = event.message.data, let invoice = data.invoice {
              print("Invoice with handle '\(invoice)' has been paid")
            }
      		  checkoutSheet?.dismiss()
       case CheckoutState.cancel:
            if let data = event.message.data, let sessionId = data.id {
              print("Session id '\(sessionId)' has been cancelled by user")
            }
       		  checkoutSheet?.dismiss()
       case CheckoutState.close:
       			print("Checkout has closed")
       case CheckoutState.error:
            if let data = event.message.data, let error = data.error {
              print("Session has error: \(error)")
            }
     }

     /// Unsubscribe when events are final states
     if ![CheckoutState.`init`, CheckoutState.error].contains(event.state) {
       removeSubscribers()
       checkoutSheet = nil
     }
   }

  private func removeSubscribers() {
    // acceptEventCancellables.removeAll()
    // cancelEventCancellables.removeAll()
    // closeEventCancellables.removeAll()
    checkoutEventCancellables.removeAll()
    checkoutEventPublisher = nil
  }
}

6. Test the integration

Reepay Checkout is now ready to be presented as a Sheet. Use the test cards from Reepay API reference to perform a card payment.


📘

For more information, check out our iOS demo app using Reepay iOS SDK here