How to update readers
This section provides a technical explanation of how to update readers using the SDK in iOS, Android, and React Native. Below, you will find step-by-step instructions to manage a reader update — from creating the configurator to providing the device and configuration.
⚠️ Note: This process applies only to MagicPos and Qpos devices.
These steps cover updates for both EMV configuration and firmware.
Create a Configurator
The first step is to create a Configurator
, which will be responsible for managing the update process.
- iOS
- Android
- React Native
To create a ReaderConfigurator
, use the ReaderConfiguratorFactory
.
let readerConfigurator = ReaderConfiguratorFactory.createConfigurator(delegate: delegate)
To create a SDKReaderConfigurator
, use SDKReaderConfiguratorFactory
to create a new SDKReaderConfigurator
.
SDKReaderConfiguratorFactory.createConfigurator(...)
To create a ReaderConfigurator
, simply use createReaderConfigurator
function provided by the useReadersSdk
hook.
const { createReaderConfigurator } = useReadersSdk()
const readerConfigurator = await createReaderConfigurator()
Each platform requires specific listeners or delegates to handle events during the update process.
During the update process, two important callbacks handle different stages and statuses(for android
implement SDKReaderConfiguratorListener
and react-native
call readerConfigurator.start()
):
onReaderConfiguratorStateChanged
→ Handles configuration process states.onReaderStateChanged
→ Handles reader connection and transaction states.
1.Configuration States (onReaderConfiguratorStateChanged
), these states guide the flow of the update process:
DeviceRequired
A device must be provided to start the update process.ConfigurationRequired
Configuration files (EMV/Firmware) must be provided to continue.RecoverableError
A recoverable error occurred. You can attempt to continue the process by handling this error.NonRecoverableError
A critical error occurred. The update process cannot continue.ConfigurationCompleted
The update process (EMV/Firmware) finished successfully.
2.Reader States (onReaderStateChanged
), these states reflect the connection and transaction status of the reader:
Connecting
The reader is attempting to connect to the selected device.Connected.Idle
The reader is connected and ready for operations.Connected.WaitingForCard
The reader is connected and waiting for a card to be presented.Connected.Processing.Card
The reader is connected and processing a card transaction.Connected.Processing.Pin
The reader is connected and awaiting PIN entry.Connected.UpdatingConfiguration
The reader is connected and updating its EMV/Firmware configuration.NotConnected
The reader is not connected to any device.
In ios
implement the ReaderConfiguratorDelegate
protocol to handle the update flow:
onReaderStateChanged
to handle reader configurator statesonDeviceRequired
to provide the deviceonConfigurationRequired
to provide the configurationonConfigurationCompleted
called when the configuration is completedonRecoverableError
when a recoverable error appearonNonRecoverableError
when a onNonRecoverableError error appear
- iOS
- Android
- React Native
public protocol ReaderConfiguratorDelegate: AnyObject {
func onReaderStateChanged(state: ReaderState)
func onDeviceRequired(provideDevice: @escaping DeviceRequiredCallback)
func onConfigurationRequired(configuration: @escaping ConfigurationRequiredCallback)
func onConfigurationCompleted()
func onRecoverableError(error: ReaderConfiguratorError, recover: @escaping RejectRecoverCallback)
func onNonRecoverableError(error: ReaderConfiguratorError, message: String?)
}
object : SDKReaderConfiguratorListener {
override fun onReaderConfiguratorStateChanged(state: SDKReaderConfiguratorState) {
...
}
override fun onReaderStateChanged(state: SDKReaderState) {
...
}
}
const onReaderConfiguratorStateChanged = async (readerConfigurator: ReaderConfigurator, state: ReaderConfiguratorStates, params) => {
...
}
const onReaderStateChanged = (state, params) => {
...
}
Provide Device
Once requested, you must provide the Device
previously scanned to be updated.
- iOS
- Android
- React Native
Use the provideDevice
callback when onDeviceRequired
is called:
public protocol ReaderConfiguratorDelegate: AnyObject {
func onDeviceRequired(provideDevice: @escaping DeviceRequiredCallback)
}
public typealias DeviceRequiredCallback = (Device) -> Void
func onDeviceRequired(provideDevice: @escaping DeviceRequiredCallback) {
if let device = device {
if device.name.hasPrefix("SR") {
provideDevice(device)
return
}
let deviceConfiguration = DeviceConfigurationBuilder()
.setTerminalCapabilities("6020C8")
.setCountryCode("0032")
.build()
do {
let device = try DeviceConfigurator.deviceWithConfiguration(
device: device,
deviceConfiguration: deviceConfiguration
)
provideDevice(device)
} catch {
print(error)
}
}
}
Provide the device with the provideDevice
method when the state is DeviceRequired
:
val device: SDKDevice = selectedDevice
override fun onReaderConfiguratorStateChanged(state: SDKReaderConfiguratorState) {
when(state) {
is SDKReaderConfiguratorState.DeviceRequired -> state.provideDevice(device)
}
}
Provide the device with the provideDevice
method when DeviceRequired
state is triggered:
const onReaderConfiguratorStateChanged = async (readerConfigurator: ReaderConfigurator, state: ReaderConfiguratorStates, params) => {
switch (state) {
case ReaderConfiguratorStates.DeviceRequired:
await readerConfigurator.provideDevice(reader.id)
break
}
}
Provide Configuration
You need to provide the correct configuration files to proceed with the update.
The configurations required to update the reader (EMV parameters or firmware) are provided as bundles compressed files (ZIP
format) containing all necessary assets for the update process.
Geopagos
delivers these configuration bundles directly to each partner.
- iOS
- Android
- React Native
On onConfigurationRequired
, you must provide a ReaderConfigurationFiles
object, using the corresponding factory depending on the reader and configuration type:
public protocol ReaderConfiguratorDelegate: AnyObject {
func onConfigurationRequired(configuration: @escaping ConfigurationRequiredCallback)
}
public typealias ConfigurationRequiredCallback = (ReaderConfigurationFiles) -> Void
func updateConfiguration(
configurationType: ConfigurationType,
bundleName: String? = nil,
configuration: @escaping ConfigurationRequiredCallback
) {
let bundleName = bundleName != nil ? bundleName : "configurationBundle"
guard let url = getURLFile(resource: bundleName!, type: "zip") else {
fatalError("could not get url from configurationBundle")
}
switch configurationType {
case .qposFirmware:
let readerConfigurationFiles = QposReaderConfigurationFactory.qposFirmwareFiles(
firmware: url
)
configuration(readerConfigurationFiles)
case .qposEmv:
let readerConfigurationFiles = QposReaderConfigurationFactory.qposEmvFiles(
emvConfiguration: url
)
configuration(readerConfigurationFiles)
case .qposFirmwareEmv:
let readerConfigurationFiles = QposReaderConfigurationFactory.qposFirmwareEmvFiles(
firmwareEmv: url
)
configuration(readerConfigurationFiles)
case .magicPosFirmware:
let readerConfigurationFiles = MagicPosReaderConfigurationFactory.magicPosFirmwareFiles(
firmware: url
)
configuration(readerConfigurationFiles)
case .magicPosEmv:
let readerConfigurationFiles = MagicPosReaderConfigurationFactory.magicPosEmvFiles(
emvConfiguration: url
)
configuration(readerConfigurationFiles)
case .magicPosFirmwareEmv:
let readerConfigurationFiles = MagicPosReaderConfigurationFactory.magicPosFirmwareEmvFiles(
firmwareEmv: url
)
configuration(readerConfigurationFiles)
}
}
QPOS
QposReaderConfigurationFactory.qposFirmwareFiles(firmware: url)
Where firmware contains the bundle file provided from Geopagos.
MagicPOS
MagicPosReaderConfigurationFactory.qposFirmwareFiles(firmware: url)
Firmware configuration is the same as QPOS.
In the case that the bundle file does not contain a configuration that matches the configuration type and the reader
requested, it will return a .invalidConfiguration
error.
Qpos mini Firmware update (Edge case)
In case Firmware update failed and reader passes to boot state (non responsive to any pressed key) it is necessary to use a special device to finish the update, to do so we need to add a special mapper first
In the case Firmware update fails, the reader is left in a “boot state” (not response to press any key). In this case it is necessary to use a special device to finish the update.
let readerConfigurator = try? ReaderConfiguratorFactory.createConfigurator(
delegate: self,
additionalMappers: [
QposMiniFirmwareRestoreDeviceMapper()
]
)
and create a QposMiniFirmwareRestoreDevice
// Example:
let readerInfo = ReaderInfo(
id: "",
serial: "",
batteryInfo: BatteryInfo(
percentage: 1.0,
charging: false
),
firmware: "",
hardwareVersion: "A27C_P1",
readerType: .qposMini
)
let qposMiniFirmwareRestoreDevice = QposMiniFirmwareRestoreDevice(
device: device,
readerInfo: readerInfo
)
then the device should be passed in ReaderConfiguratorDelegate.onDeviceRequired(provideDevice:)
You must use SDKConfigurationFile
object to provide the file source to the sdk using one of the creator methods it provides.
Each Reader dependency exposes a ConfigurationFactory
to create the corresponding SDKConfigurationFiles
.
These will only be available if the dependency for that reader is set in the project.
override fun onReaderConfiguratorStateChanged(state: SDKReaderConfiguratorState) {
when (state) {
is SDKReaderConfiguratorState.ConfigurationRequired -> state.provideConfiguration(getConfiguration())
}
}
There are several factories according the configuration type and the reader used, firmware configuration can be done as follows:
QPOS
val bundleFile = SDKConfigurationFile({BUNDLE_FILENAME}, SDKConfigurationFile.Source.Asset)
val qposFirmwareConfiguration = SDKQposReader.ConfigurationFactory.create(bundleFile)
MagicPOS
val bundleFile = SDKConfigurationFile({BUNDLE_FILENAME}, SDKConfigurationFile.Source.Asset)
val magicposEmvConfiguration = SDKMagicPosReader.ConfigurationFactory.create(bundleFile)
Then, you can pass that SDKConfigurationFiles
to the
SDKReaderConfiguratorState.ConfigurationRequired
state.provideConfiguration(configurationFiles)
Call the function readerConfigurator.provideConfiguration()
in ReaderConfiguratorStates.ConfigurationRequired
state.
There are two possible ways to pass the configuration file to the Configurator.
Reading file from apk
const onReaderConfiguratorStateChanged = async (readerConfigurator: ReaderConfigurator, state: ReaderConfiguratorStates, params) => {
switch (state) {
case ReaderConfiguratorStates.ConfigurationRequired:
await readerConfigurator.provideConfiguration({
readerId: reader.id,
filePath: 'configurationBundle.zip',
source: ConfigurationSources.Storage,
})
break
}
}
Reading file from local storage
const onReaderConfiguratorStateChanged = async (readerConfigurator: ReaderConfigurator, state: ReaderConfiguratorStates, params) => {
switch (state) {
case ReaderConfiguratorStates.ConfigurationRequired:
const path = RNFS.DocumentDirectoryPath + '/configurationBundle.zip'
await readerConfigurator.provideConfiguration({
readerId: reader.id,
filePath: path,
source: ConfigurationSources.Storage,
})
break
}
}
Update Error
During an update, two kind of errors can occur: recoverable and not recoverable.
Recoverable Error
In the recoverable, it’s possible to retry the update calling the associate callback, in the not recoverable errors, the update operation failed and it's necessary to start the process again.
- iOS
- Android
- React Native
On the onRecoverableError
callback, use the recover()
method to recover.
public protocol ReaderConfiguratorDelegate: AnyObject {
func onRecoverableError(error: ReaderConfiguratorError, recover: @escaping RejectRecoverCallback)
}
extension ViewController: ReaderConfiguratorDelegate {
func onRecoverableError(error: ReaderConfiguratorError, recover: @escaping RejectRecoverCallback) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
recover()
}
}
}
Call the SDKReaderConfiguratorState.recover()
method in the
SDKReaderConfiguratorState.RecoverableError
state to recover
override fun onReaderConfiguratorStateChanged(state: SDKReaderConfiguratorState) {
when (state) {
is SDKReaderConfiguratorState.RecoverableError -> state.recover()
}
}
Call the method readerConfigurator.recoverFromError()
in ReaderConfiguratorStates.RecoverableError
state to recover.
const onReaderConfiguratorStateChanged = async (readerConfigurator: ReaderConfigurator, state: ReaderConfiguratorStates, params) => {
switch (state) {
case ReaderConfiguratorStates.RecoverableError:
await readerConfigurator.recoverFromError()
break
}
}
Nonrecoverable Error
This state indicates that an error has occurred and its NonRecoverable
.
- iOS
- Android
- React Native
Check for onNonRecoverableError()
.
public protocol ReaderConfiguratorDelegate: AnyObject {
func onNonRecoverableError(error: ReaderConfiguratorError, message: String?)
}
extension ViewController: ReaderConfiguratorDelegate {
func onNonRecoverableError(error: ReaderConfiguratorError, message: String?) {
print("Non Recoverable Error \(error)")
}
}
Check for SDKReaderConfiguratorState.NonRecoverableError
state
override fun onReaderConfiguratorStateChanged(state: SDKReaderConfiguratorState) {
when (state) {
is SDKReaderConfiguratorState.NonRecoverableError -> printl("Non Recoverable Error ${state.error}")
}
}
Check for ReaderConfiguratorStates.NonRecoverableError
state
const onReaderConfiguratorStateChanged = async (readerConfigurator: ReaderConfigurator, state: ReaderConfiguratorStates, params) => {
switch (state) {
case ReaderConfiguratorStates.NonRecoverableError:
console.log('Non Recoverable error:', params['error'])
break
}
}
Update completed
This step indicates that the reader configuration process has been finished.
- iOS
- Android
- React Native
Check the method onConfigurationCompleted()
.
extension ViewController: ReaderConfiguratorDelegate {
func onConfigurationCompleted() {
print("Completed!")
}
}
public protocol ReaderConfiguratorDelegate: AnyObject {
func onConfigurationCompleted()
}
Check for SDKReaderConfiguratorState.ConfigurationCompleted
state.
override fun onReaderConfiguratorStateChanged(state: SDKReaderConfiguratorState) {
when (state) {
is SSDKReaderConfiguratorState.ConfigurationCompleted-> println("Completed!")
}
}
Check for ReaderConfiguratorStates.ConfigurationCompleted
state.
const onReaderConfiguratorStateChanged = async (readerConfigurator: ReaderConfigurator, state: ReaderConfiguratorStates, params) => {
switch (state) {
case ReaderConfiguratorStates.ConfigurationCompleted:
console.log('Configuration completed!!!')
break
}
}