Skip to content

Getting Started

Daniel James edited this page Aug 8, 2024 · 6 revisions

Note

This page updated as of SDK version 1.1.2

Important

Valid for United States only

Overview

In this quick start guide, we will learn to quickly take a payment using the iOS Clover Payment SDK and your Clover Go Mobile Credit Card Reader.

Prerequisites

Gotchas

Note

The SDK cannot perform the bluetooth functions it was designed for through simulator, since Apple doesn't permit bluetooth simulation therein. As such, you'll need a physical iPhone or iPad to test communications with a Clover device. You can build, launch, initialize, and login with our SDK in the simulator, but any Clover Go device operations are not possible on the simulator.

Important

ReactNative is not officially supported. However, you may still be able to use this SDK if you're familiar with consuming native Swift XCFrameworks (via CocoaPods) in a ReactNative application.

Tip

Several operations on your Clover Card Reader require at least 30% battery to complete. Charge your device while you configure your iOS project using the instructions in this guide.

Setup Steps

Step 1: Create your test app at Clover Developer Home

  1. Click "Create App" and follow the instructions provided. Use the following settings as a baseline for your test app.
    1. App Type: REST Clients
    2. Requested Permissions: All On
    3. REST Configuration:
      1. Site URL: Location where your apple-app-site-association file will be hosted.
      2. Default OAuth Response: Code (note Code and Token responses are both supported, however Code responses provide an extra level of security and should be used exclusively in production)
    4. Ecommerce Settings: Integrations Enabled: API
  2. Note the APP ID and APP Secret, entered in the configuration for the iOS app in later steps.

Step 2: Create your iOS App and import the SDK

  1. In Xcode, create a new iOS App project titled "Go SDK Intro"
  • Interface: Storyboard
  • Language: Swift
  1. Verify that the Project Format is set to Xcode 13.0-compatible (or greater)
  2. Close your new project
  3. In a terminal window, navigate to the folder containing your project
    pod init
    open -a Xcode Podfile
  1. Overwrite the contents of the podfile with:
  platform :ios, '14.0'
  workspace 'Go SDK Intro'
  use_frameworks!
   
  # ignore all warnings from all pods
  inhibit_all_warnings!
   
  target 'Go SDK Intro' do
    pod 'CloverPayments'
     
    post_install do |installer|
      installer.pods_project.targets.each do |target|
        if target.name == 'RxSwift' || target.name == 'iOSDFULibrary'
          target.build_configurations.each do |config|
            config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
            config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = ['14.0']
          end
        end
      end
    end
  end
  1. Still in the terminal, run the following commands to install the Pod and open the resulting workspace in Xcode
  pod install
  open -a Xcode Go\ SDK\ Intro.xcworkspace

Step 3: Configure OAuth

  1. In Xcode:
    • In Signing & Capabilities for your project, click + Capability.

Capabilities

  • Select Associated Domains.
  • In the new Domains item under Associated Domains, change the default credentials to: applinks:<your domain>, where your domain is the root url where your apple-app-site-association file is installed. For example: myexampleapp.com.
  • Replace the contents of your Scene Delegate with:
//
//  SceneDelegate.swift
//  Go SDK Intro
//
//  Entry point for our incoming URIs, used to receive the OAuth tokens.
//
 
import UIKit
import CloverPayments
 
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
 
    var window: UIWindow?
 
    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
         
        // Check if the userActivity contains a URL
        guard let incomingURL = userActivity.webpageURL else { return }
         
        // Check if this is an OAuth return URL, and then pass along to the OAuthToken Manager
        // During the OAuth Flow, we first kick out to a user login page, which then redirects back to the app using an associated domain.  After the user logs in and selects a merchant account, we'll get a callback via that associated domain here.  We'll check to be sure it's the OAuth URL (associated domains can be used for many things, only one of which is OAuth flows), and if it is we pass it along to the SDK for processing and secure storage.
        if let fullOAUthFlow = configuration.oauthFlowType as? FullOAuth,
           let scheme = incomingURL.scheme,
           let host = incomingURL.host,
           fullOAUthFlow.redirectURI == (scheme + "://" + host + incomingURL.path) {
            CloverPaymentSDK.shared.receivedOAuthCodeOrToken(url: incomingURL)
        }
    }
}

This code provides the input path for the OAuth callback from Clover's login servers back into your app. When we receive it, we'll pass it along to the SDK for handling of the incoming code or token, including keychain storage of these sensitive values.

  • On your server, create an apple-app-site-association file, placing it in the root of the server referenced in the previous step.
{
    "applinks": {
        "apps": [],
        "details": [{
            "appID": "<TEAM_ID>.<BUNDLE_ID>",
            "paths": ["/OAuthResponse/*"]
        }] 
    } 
}

Note

The apple-app-site-association file does not have an extension, but your server still needs to return a content type of application/json when the file is requested or iOS will likely not parse it properly. See the specific documentation for your server for configuration.

Tip

If you are not ready to publish your apple-app-site-association file on your official public server, GitHub pages is available and properly configured to host your file.

Important

The location of your apple-app-site-association, your Associated Domain in your iOS App configuration, and the REST Configuration Site URL in your Clover App configuration all must match for the OAuth flow to work properly.

Step 4: Configure the SDK

  1. Back in Xcode, create a new swift file named Configuration.swift in your project with the following contents:
import CloverPayments  
let configuration = Configuration(
    environment:<#CloverEnvironment.EnvironmentType - this is the server environment that we will use to communicate with Clover.#>,
    appID: <#App ID registered on your Clover Developer Portal, used by the OAuth Flow to match permissions to your app when your merchant logs in.#>,
    appSecret: <#App Secret obtained from your Clover Developer Portal.  This is the secret associated with your App ID.#>,
    apiKey: <#API Key for server access.  Contact your Developer Relations representative for a valid Key and Secret.#>,
    apiSecret: <#API Secret for server access.  Contact your Developer Relations representative for a valid Key and Secret.#>,
    oauthFlowType: FullOAuth(redirectURI: <#URI used in the OAuth flow to get the code/token back to your app.  This should be an Associated Domain registered to your app.  See documentation for details.#> )
)
  1. Populate the file with your configuration:
Parameter Description
environment The environment you want to connect to on Clover's servers. Start with .Sandbox for development.
appID the App ID of your Clover App on Developer Home that you created in step 1
appSecret the App Secret of your Clover App on Developer Home that you created in step 1
apiKey Ask your Developer Relations Representative for this value
apiSecret Ask your Developer Relations Representative for this value
oauthFlowType OAuthFlowType Object. Supported options are FullOAuth, PartialOAuth, and NoOAuth. We'll use FullOAuth for this example. Using FullOAuth, the SDK will call out to the OAuthProvider in the default external browser when needed. You will be required to pass the response string from the external browser back to the SDK when received (See the SceneDelegate.swift code example above). The SDK will attempt to initiate login if the token or refresh tokens become invalidated or expired.

Full OAuth object definition

Parameter Description
OAuthFlowRedirectURI http://<your domain>/OAuthResponse, where your domain is the domain where your apple-app-site-association file is located. When the OAuth flow starts, this URI will be passed to Clover's login servers. After the user authenticates, this is the URI that will be called by Clover's servers to link back to your app. This value will be validated against the Site URL you configured on Clover's servers before it will be called by Clover's servers. This value will be validated against the your apple-app-site-association paths, as well as the Associated Domain configured in your project configuration by iOS.

Note

You can add any path you want here to differentiate this app from your other apps, being sure to also update the path in your apple-app-site-association file.

Important

iOS loads your apple-app-site-association file only once, at first install of your app. If you need to make updates to the apple-app-site-association file, you will also need to uninstall and reinstall your app in order to get the updates.

  1. Initialize the SDK
    • Replace the App Delegate implementation with this:
import UIKit
import CloverPayments
 
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
 
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        Task { @MainActor in
            try? await setupSDK()
        }
        return true
    }
 
 
    // Initialize the SDK, if it needs to be
    public func setupSDK() async throws {
        // The SDK hasn't been initialized yet, so we'll call the initializer
        // The SDK can take care of the OAuth Flow automatically, and in this example we'll make use of that.  To do that, we'll simply initialize the SDK and it will determine that we don't have a token and kick off the OAuth Flow.  The return flow comes back through Associated Domains, and we'll see the success callback called upon successful completion.
        do {
            try await CloverPaymentSDK.shared.setup(configuration: configuration)
            print("CloverPaymentsSDK initialized")
        } catch {
            print("CloverPaymentsSDK initialization failure: \(error)")
            throw(error)
        }
    }
}
  1. Run your app on an iOS device to open the Clover login window. Enter your credentials in the login window. The window redirects back to your app.

Step 5. Scan for Devices and Connect

This step opens a Bluetooth scan, receives a list of devices found, and connects to the first found device.

  • Configure the Privacy Requests

    • Populate the value NSBluetoothAlwaysUsageDescription field for your project.
      • Navigate the project root directory from the Project Navigator
      • Select your app target
      • Select the info tab
      • On the last entry in the Custom iOS Target Properties section, click the plus button.
      • In the Key block, type NSBluetoothAlwaysUsageDescription
      • In the Value block, type "Scan for and connect to Clover Devices" bluetoothPermissions
  • Replace the contents of ViewController.swift with the following:

//
//  ViewController.swift
//  CloverPayments SDK Intro
//
//
  
import UIKit
import CloverPayments
import Combine
  
class ViewController: UIViewController {
   
    //hold the SDK observer subscriptions
    private var subscriptions = Set<AnyCancellable>()
 
    override func viewDidLoad() {
        super.viewDidLoad()
         
        //observe changes to the battery level
        CloverPaymentSDK.shared.readerBatteryPct.sink { pct in
            // ...
            // update label or graphical indication of battery level
            // ...
        }.store(in: &subscriptions)
         
        //observe changes to the connection state of the reader
        CloverPaymentSDK.shared.readerStatus.sink { state in
            switch state {
            case .initializing:
                // Update status label to show "Initializing" state
            case .updatingFW:
                // Update status label to show "Firmware Updating" state
            case .lowBattery:
                // Update status label to show a Low Battery warning
            case .ready:
                // Update status label to show a "Connected" state
            case .disconnected:
                // Handle disconnection state
            default:
                break
            }
        }.store(in: &subscriptions)
    }
 
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
          
        Task { @MainActor in
            do {
                print("Scanning for Clover Go Readers")
 
                var cardReaders = [CardReader]()
 
                // Scans for five seconds, returning the readers it finds
                for try await cardReader in CloverPaymentSDK.shared.scan() {
                    print("Found Reader: \(cardReader.name ?? cardReader.id)")
                    cardReaders.append(cardReader)
                }
  
                // For the simplicity of the example, we'll just connect to the first reader we found
                //
                // Optional: Calling `connect()` will stop an in-progress scan, so you may attempt a
                // connection as soon as you find a specific reader
                //
                // Optional: Accumulating the discovered readers in a collection and then
                // displaying in a tableView for selection by the user may be a more "normal"
                // flow for your app.
                if let cardReader = cardReaders.first {
                    try await CloverPaymentSDK.shared.connect(reader: cardReader)
                    print("Connected to Clover Go Reader")
                }
            } catch {
                print("Error scanning or connecting: \(error)")
            }
        }
    }
}
  • At this point, go ahead and install and run the app. After the app launches, open the Clover login page in Safari and authenticate. The app prompts for Bluetooth permission, scans for devices, and connects to the first found device.

Step 6: Take a Payment

  • Update ViewController from the previous step to add the following function to the class implementation:
func takePayment() {
    // Create a Pay Request.  For this example, we'll hard code it to 100 cents
    var payRequest = PayRequest(amount:100)
    // Set the request to a final sale.  See documentation for other ways to use the final and capture flags
    payRequest.final = true // final sale, captured
 
    Task { @MainActor in
        do {
            for try await paymentEvent in CloverPaymentSDK.shared.charge(payRequest: payRequest) {
                switch paymentEvent {
                     
                case .complete(let payResponse):
                    var messageString = "====================================================================== Payment Successful"
                    if var amount = payResponse.payment?.amount {
                        amount += payResponse.payment?.tipAmount ?? 0
                        amount += payResponse.payment?.taxAmount ?? 0
                        if let additionalCharges = payResponse.payment?.additionalCharges {
                            for thisAdditionalCharge in additionalCharges {
                                amount += thisAdditionalCharge.amount ?? 0
                            }
                        }
                        let amountString = String(format: "%1.2f", (Double(amount) / 100.0))
                        messageString = "====================================================================== $\(amountString) was charged to the card"
                    }
                    print(messageString)
                case .progress(let progressEvent):
                    switch progressEvent {
                    case .insertOrTapCard: print("Insert or Tap Card")
                    case .swipeInsertTapCard: print("Swipe, Insert, or Tap Card")
                    case .removeCard: print("Remove Card")
                    case .cardInserted: print("Card Inserted")
                    case .cardRemoved: print("Card Removed")
                    case .cardSwiped: print("Card Swiped")
                    case .cardTapped: print("Card Tapped")
                    case .emvDipFailedTryAgain: print("EMV Dip Failed Try Again")
                    case .emvDipFailedTrySwipe: print("EMV Dip Failed Try Swipe")
                    case .emvCardSwiped: print("EMV Card Swiped")
                    case .contactlessFailedTryContact: print("Contactless Failed Try Contact")
                    case .swipeFailed: print("Swipe Failed")
                    case .pleaseSeePhone: print("Please See Phone")
                    case .multipleContactlessCardsDetected: print("Multiple Contactless Cards Detected")
                    case .configuringReader: print("Configuring Reader")
                    @unknown default:
                        break
                    }
                @unknown default:
                    break
                }
            }
        } catch {
            print("Payment Error: \(error)")
        }
    }
}
  • After the card reader connect() call, add a call to the takePayment() method you just added:
if let cardReader = cardReaders.first {
    try await CloverPaymentSDK.shared.connect(reader: cardReader)
    print("Connected to Clover Go Reader")
     
    self.takePayment()
}