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
andcancel_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
Optional
Set up stylesBy 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
Updated 8 months ago