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
PaymentsConfiguration
to 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
onReaderStateChanged
provides 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
)