Payments
An easy to integrate solution to accomplish payments, refunds and cancellations alongside a card reader
Overview
Payments SDK provides functionalities to be able to connect to a hardware card reader, and perform transactions using a credit or debit card with any read mode (magnetic band, EMV chip, or NFC contactless )
Installation
CocoaPods
CocoaPods is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website.
To integrate the SDK into your Xcode project using CocoaPods, first add the souces to the top of your  Podfile:
source 'https://bitbucket.org/geopagos-sdk/ios-specs.git'
source 'https://github.com/CocoaPods/Specs.git'
Then add the pod to your target, as an example we use version 16.0.22:
  pod 'Payments', '16.0.22'
If you want to use magicpos reader then you need to add its pod. You can specify a version in case you needed to
  pod 'MagicPosHardware', '10.0.18'
If you want to use qpos reader then you need to add its pod. You can specify a version in case you needed to
  pod 'QPosHardware', '10.0.18'
Example full  Podfile, if your target is named MyTarget:
source 'https://bitbucket.org/geopagos-sdk/ios-specs.git'
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '13.0'
target 'MyTarget' do
  use_frameworks!
  # Pods for MyTarget
  pod 'Payments', '16.0.22'
  pod 'MagicPosHardware', '10.0.18'
  pod 'QPosHardware', '10.0.18'
  
end
Using the Payments SDK
Importing the SDK
- Import the Payments framework in your integrating class or struct
 
import Payments
- Create an instance of 
PaymentsConfigurationto configure the endpoint, readerInstallers (optionally a logger object, plugins and SwipePinDecider implementation) - Call 
PaymentsSDK.configure(...)to configure the SDK 
do {
    let paymentsConfiguration = try PaymentsConfiguration.Builder(
        endpoint: <A valid URL>,
        readerInstallers: [
            <QposReaderInstaller>,
            <MagicPosReaderInstaller>
        ]
    ).build()
    PaymentsSDK.configure(
            paymentsConfiguration: paymentsConfiguration
    )
} catch {
    // Error initializing SDK
}
Note: To create readerInstallers it is necessary to import the module that corresponds to each Reader
For Magic Pos
import MagicPosHardware 
   
let magicPosReaderInstaller = MagicPosReaderInstaller()
For Qpos
import QPosHardware
   
let qposReaderInstaller = QposReaderInstaller()
Scan for readers
To begin a transaction, first we need to scan for the available bluetooth card readers. To do so we instantiate the class DeviceScanService 
let deviceService = DeviceScanService()
deviceService.scan { (result: DeviceResult) in
   switch result {
   case let .success(devicesList, isScanning):
       for device: Device in devicesList {
           // Select and retain the reader's device that will be used during the transaction
       }
   case let .error(errorType, isScanning):
       // handle errors
   }
}
to stop scanning for readers call
   deviceService.stopScanning()
Once you have selected a reader from the scannedd readers list, a transaction can be started
Start a transaction
To start a transaction we need to create a TransactionIntent instance, using the TransactionIntentFactory class
TransactionIntentFactory.createTransactionIntent(
    type: <A TransactionType>, 
    listener: <A SDKTransactionListener Implementation>,
    completion: { [weak self] result in
            switch result {
            case let .success(aTransactionIntent):
                // Retain the transaction
                self?.transaction = aTransactionIntent
            case let .failure(error):
                // Do something with the error
            }
        }
    )
This method recieves 3 arguments:
TransactionType:(.payments, .refund, .cancel)SDKTransactionListener: Reader and transaction states listener(TransactionIntentResult) -> Void: Completion block which receives the result of the transaction creation (success or failure) as argument
Once the transactionIntent is created, the listener injected in the transaction creation, that conformed to SDKTransactionListener will begin to be called, representing states of the recently created transaction. The SDKTransactionListener methods guide you through the data that must be provided
The implementation of SDKTransactionListener will start being updated with changes of states of both the reader and transaction
Listening to reader states
- the method 
onReaderStateChangedprovides feedback for the reader states 
    func onReaderStateChanged(state: ReaderState) {
        switch state {
        case let .notConnected:
        case let .connecting(device):
        case let .connected(device, readerInfo, .idle):
        case let .connected(device, readerInfo, .waitingForCard):
        case let .connected(device, readerInfo, .processing(mode)):
        case let .connected(device, readerInfo, .emvRequestingPin):
    
        @unknown default:
            break
        }
    }
Listening to transaction states
func onTransactionStateChanged(state: SDKTransactionState) {
    switch state {
    case let .provideDevice(provideDeviceCallback: provideDeviceCallback):
        // select a device from the scanned devices and pass it in the callback
        provideDeviceCallback(aSelectedDeviceFromTheScannedList)
    case let .provideReadConfig(provideReadConfigCallback: provideReadConfigCallback):
        // Provide a reader configuration containing information the reader needs to perform the transaction
        let transactionTotal = TransactionTotal(...)
        let readConfig = ReadConfig(
                readModes: [.swipe, .chip, .nfc], 
                timeout: 10,
                transactionTotal: transactionTotal
        )
        provideReadConfig(readConfig)
    case let .selectEMVApp(availableAIDs: availableAIDs, selectEMVAppCallback: selectEMVAppCallback):
        // Select an EMV app if more than one are available, and pass it in the callback
        if let emvApp = availableAIDs.first {
            selectEmvApp(emvApp)
        }
    case let .confirmPayment(card: card, confirmCallback: confirmCallback, rejectCallback: rejectCallback):
        // After reading the card for a Sale transaction, a confirmation is requested, in order for the transaction to go online
        let confirmation = Confirmation(...)
        let paymentConfirmation = PaymentConfirmation(confirmation: confirmation, installments: nil, paymentPlan: nil)
        accept(paymentConfirmation)
    case let .confirmRefund(card: card, confirmCallback: confirmCallback, rejectCallback: rejectCallback):
        // After reading the card for a refund transaction, a confirmation is requested, in order for the transaction to go online
        let confirmation = Confirmation(...)
        let refundConfirmation = RefundConfirmation(confirmation: confirmation, parentRefNumber: parentRefNumber)
       accept(refundConfirmation)
    case let .confirmCancel(card: card, confirmCallback: confirmCallback, rejectCallback: rejectCallback):
        // After reading the card for a refund transaction, a confirmation is requested, in order for the transaction to go online
        let confirmation = Confirmation(...)
        let cancelConfirmation = CancelConfirmation(confirmation: confirmation, parentRefNumber: parentRefNumber)
        accept(cancelConfirmation)
    case let .approved(transactionApproved: transactionApproved):
        switch transactionApproved {
        case let .sale(transactionCard, saleConfirmation, paymentData):
            // Sale approved
        case let .refund(transactionCard, refundCOnfirmation, paymentData):
            // Refund approved
        case let .cancel(transactionCard, cancelConfirmation, paymentData):
            // Cancel approved
        @unknown default: break
        }
    case let .recoverableError(error: error, recoverCallback: recoverCallback):
        // A recoverable error occurred in any of the transaction states. A `TransactionIntentError` error is provided along with a recoverable callback
    case let .nonRecoverableError(error: error, message: message):
        // A Non recoverable error occurred in any of the transaction states, A `TransactionIntentError` error is provided. Since this error can not be recovered, a new transaction must be started
    @unknown default:
        break
    }
}
Reader Configuration and firmware Update
Reader configuration can be updated using the appropriate configuration files provided by Geopagos team.
To create a ReaderConfigurator, use the ReaderConfiguratorFactory
let readerConfigurator = ReaderConfiguratorFactory.createConfigurator(
        delegate: delegate
)
Where the delegate implements the ReaderConfiguratorDelegate protocol.
Each delegate callback will guide you into completing the configuration, and asking for needed data.
On the onConfigurationRequired method, you must provide a ReaderConfigurationFiles objects, different factories must be used, depending on the configuration type and the reader to be updated. Firmware configuration can be done as follows:
For Qpos
QposReaderConfigurationFactory.qposFirmwareFiles(firmware: url)
where firmware is the URL to the qPos bundle file provided by geopagos
For Magicpos
MagicPosReaderConfigurationFactory.qposFirmwareFiles(firmware: url)
and the URL to the magicPos bundle file provided by geopagos
If the bundle file does not contain a configuration that matches with the configuration type and the reader requested, a recoverableError of type .invalidConfiguration will be called.
Keeping the Reader charging
Some configuration updates result in a lengthy operation (specially firmware updates) that can take between 4 and 10 minutes to complete. Because of this, it's recommended to ask the user to keep the reader connected to a power source before start updating to avoid a disconnection in the middle of the update due to low battery charge. In the qpos reader it is mandatory to connect it to a power source when firmware updates are done
Configure Pin decider
In case of magnetic stripe transactions, it is possible to provide custom logic for requesting pin, To do so, SwipePinDecider protocol must be implemented.
struct CustomtSwipePinDecider: SwipePinDecider {
    func decide(card: TransactionCard) -> Bool {
        //...
    }
}
the decide method receives the card data and returns a boolean, this custom implementation may be provided during SDK configuration.
    let paymentsConfiguration = PaymentsConfiguration.Builder(
            endpoint: <A valid URL>, 
            readerInstallers: [
                    <QposReaderInstaller>,
                    <MagicPosReaderInstaller>
                ]
            )
            .setSwipePinDecider(
                swipePinDecider: customtSwipePinDecider
            )
            .build()
            
    PaymentsSDK.configure(
        paymentsConfiguration: paymentsConfiguration
    )
Configure logger
The Payments SDK provides a mechanism for collecting logs, Protocol SDKLogger must be implemented as follows:
struct CustomSDKLogger: SDKLogger {
    func log(message: String, level: LogLevel) {
        //...
    }
}
the log method receives the log message and log level(the logs can be printed through the console, saved in a file, etc), this custom implementation may be provided during SDK configuration
    let paymentsConfiguration = PaymentsConfiguration.Builder(
                endpoint: <A valid URL>, 
                readerInstallers: [
                        <QposReaderInstaller>,
                        <MagicPosReaderInstaller>
                    ]
    )
    .setLogger(logger: customLogger)
    .build()
            
    PaymentsSDK.configure(
        paymentsConfiguration: paymentsConfiguration
    )