Installation and configuration
Installation
Add this to your buildScript repositories declaration
repositories {
maven { url 'https://nexus.devops.geopagos.com/repository/android-geopagos_public/' }
google()
mavenCentral()
jcenter()
maven { url 'https://jitpack.io' }
}
And this to your module dependencies
dependencies {
implementation "com.geopagos.payments.sdk:payments-core:$paymentVersion"
}
And for each type of reader you want to support (you may choose to support one or more)
- QPOS
- MagicPOS
- Urovo SmartPOS
- MoreFun SmartPOS
dependencies {
implementation "com.geopagos.payments.sdk:sdk-reader-qpos:$paymentVersion"
}
dependencies {
implementation "com.geopagos.payments.sdk:sdk-reader-magicpos:$paymentVersion"
}
dependencies {
implementation "com.geopagos.payments.sdk:sdk-reader-urovo:$paymentVersion"
}
dependencies {
implementation "com.geopagos.payments.sdk:sdk-reader-morefun:$paymentVersion"
}
Configure the SDK
This consists of initializing the Readers SDK, provide data acording to code documentation.
First of all, SDK initialization is required, there is a Builder SDKPaymentsConfiguration.Builder to configure the required data:
val sdkPaymentsConfiguration = SDKPaymentsConfiguration.Builder()
.setLogger(<LOGGER>)
.setSwipePinDecider(<SWIPE_PIN_DECIDER>)
.build(
<CONTEXT>,
<ENDPOINT>,
setOf(<READER_INSTALLER_SET>),
setOf(<PLUGIN_INSTALLER_SET>),
<FLOW>
)
val flowHandle = SDKPayments.init(sdkPaymentsConfiguration)
Where:
- CONTEXT is the ApplicationContext
- ENDPOINT is a String with the URL path of the processor URL. Geopagos will provide this URL
- READER_INSTALLER_SET is a set of reader installers. According to the dependencies you added
- PLUGIN_INSTALLER_SET is a set of optional plugins. According to the dependencies you added. Geopagos will provide the corresponding documentation according to the contracted plugins
- FLOW is the transaction processing flow to use (
SDKPaymentsFlow). Integrators must pass an instance of the flow variant described below (V1 or V2). - LOGGER is a custom logger you can provide to get additional information about what the sdk is doing (is useful for debugging purposes)
- SWIPE_PIN_DECIDER is an interface that allows you to decide if the SDK should ask pin on swipe transactions. This is optional. By default the SDK is loaded with an implementation that decides if a Swipe Card requires PIN based on the last ServiceCode Digit
SDKPayments.initreturns aSDKPaymentsFlowHandlethat you must keep to create transaction intents for the selected flow. Depending on the selected flow, the result must be cast to the same flow version.
Transaction flow
The transaction flow is the high-level variant of how the SDK carries a transaction from start to finish on the inside. It shapes internal transaction processing so newer behaviour can be introduced without breaking apps that still select an older flow.
- V1
- V2
V1 is the transaction pipeline that matches what the SDK has shipped and supported in integrations until today—the behaviour integrators already rely on in production. Newer flows may be added later; V1 is expected to become the legacy flow once those alternatives are generally available, while remaining supported for existing apps.
Use SDKPaymentsFlow.V1 to run the V1 pipeline. It is a class with one optional constructor parameter:
| Parameter | Default | Meaning |
|---|---|---|
mustConfirmOnline | true | When true, after an approved online result the SDK performs the online confirmation step against the host. When false, confirmation is handled locally without that online confirm path. |
Example:
val flow = SDKPaymentsFlow.V1(mustConfirmOnline = true)
// or simply: SDKPaymentsFlow.V1() // same as mustConfirmOnline = true
V2 is the new transaction processing flow and is expected to replace the V1 flow as the default processing path for new integrations. Use SDKPaymentsFlow.V2 to select it:
Note: V2 is an experimental API, subject to change, and requires authorization from Geopagos before it can be used.
val flow = SDKPaymentsFlow.V2(
pendingPaymentIntents = object : SDKV2PendingPaymentIntents { ... }
)
Resolve payment intents
In V2, after a transaction intent goes online the SDK notifies the backend
of the final result through a resolve call.
Synchronous
The SDK delivers the resolve before reporting the terminal transaction state. SDKV2TransactionState.TransactionApproved is only emitted once the resolve has been acknowledged by the backend.
If the resolve cannot be delivered, the SDK reports SDKV2TransactionState.NonRecoverableError with the new error SDKV2TransactionError.ManualHistoryCheckNeeded(intentId).
Asynchronous
The SDK tracks the pending payment intents whose resolve has not yet been acknowledged by the backend. They are persisted across SDK re-initialization and app restarts, so delivery resumes automatically when the integrating app starts again.
While delivering a pending payment intent, the SDK asks the configured SDKV2PendingPaymentIntents for a token via getToken() whenever a request needs to be authenticated. If a pending payment intent is dropped without being successfully delivered, the SDK invokes onPendingPaymentIntentDiscarded(intentId) so the app can react.
There is a maximum amount of pending payment intents; if a new transaction intent is created once that limit has been reached, the SDK returns SDKV2TransactionIntentResult.Error.PaymentIntentLimitExceeded, exposing the current pending entries.
You can inspect or clear the pending payment intents at any time through SDKPaymentsFlowHandle.V2.paymentIntentService (see getPendingIntents() / clearAll()).
interface SDKV2PendingPaymentIntents {
/**
* Returns an [SDKV2TokenResult] describing whether a fresh token could be obtained.
*/
suspend fun getToken(): SDKV2TokenResult
/**
* Invoked with the [intentId] of a pending payment intent that was discarded.
*/
fun onPendingPaymentIntentDiscarded(intentId: String)
}
| Method | Meaning |
|---|---|
getToken() | Delivers an SDKV2TokenResult to resolve a payment intent: data class Available(val token: String) or Unavailable otherwise |
onPendingPaymentIntentDiscarded(intentId) | Notifies when a pending payment intent could not be delivered, returning its intentId |
SDKPaymentsFlowHandle.V2 has SDKV2PendingPaymentIntentService
interface V2 : SDKPaymentsFlowHandle {
/**
* Creates a new transaction intent for the V2 flow.
* @param sdkTransactionData required transaction data
* @param listener listener to receive transaction state changes
*/
fun createTransactionIntent(sdkTransactionData: SDKV2TransactionData, listener: SDKV2TransactionListener)
/**
* Service that exposes the pending payment intent the SDK is trying to deliver to the backend.
* Used by the integrating app to inspect or clear the queue.
*/
val paymentIntentService: SDKV2PendingPaymentIntentService
}
SDKV2PendingPaymentIntentService has two methods
interface SDKV2PendingPaymentIntentService {
/**
* Returns the set of pending payments intents.
*/
fun getPendingIntents(): Set<SDKV2PendingPaymentIntent>
/**
* Removes every pending payments intents from the SDK and returns the snapshot that was
* cleared, so the integrating app can decide what to do with them.
*
* Use with care: any intents cleared this way will not be delivered to the backend.
*/
fun clearAll(): Set<SDKV2PendingPaymentIntent>
}
| Method | Meaning |
|---|---|
getPendingIntents() | Returns a set of pending payment intents |
clearAll() | Clear all the pending payment intents |
SDKV2PendingPaymentIntent has two attributes
data class SDKV2PendingPaymentIntent(
val intentId: String,
val resolution: SDKV2PendingIntentResolution,
)
| Attribute | Meaning |
|---|---|
intentId | Pending payment intent's id |
resolution | Pending payment intent's resolution (see below) |
enum class SDKV2PendingIntentResolution { CONFIRMED, FAILED }
At initialization, the SDK checks that every configured plugin is compatible with the resolved internal flow type. If a plugin does not support the selected flow, init throws IllegalArgumentException naming the incompatible installers.
| Plugin Compatibility | V1 | V2 |
|---|---|---|
| SDKForcePinDeciderDeciderPlugin | Yes | Yes |
| SDKBinCvmContactlessDeciderPlugin | Yes | Yes |
About installers
Each reader has its own installer.
- QPOS
- MagicPOS
- Urovo SmartPOS
- MoreFun SmartPOS
SDKQposReader.getInstaller()
SDKMagicPosReader.getInstaller()
SDKUrovoReader.getInstaller(
<SDK_FILE_WRAPPER>,
<PIN_KEYBOARD_CONFIGURATION>
)
Where:
- SDK_FILE_WRAPPER allows you to provide a configuration file to the getInstaller
sealed class SDKFileWrapper(val context: Context) {
/**
* You should use this class if the file that you want to provide is stored as an asset on your application
*
* @param context the application context
* @param name the file name of the asset
* **/
class Asset(context: Context, val name: String) : SDKFileWrapper(context)
/**
* You should use this class if the file that you want to provide is stored as a file on your device
*
* @param context the application context
* @param path the path of the file
* **/
class File(context: Context, val path: String) : SDKFileWrapper(context)
}
//Example
SDKFileWrapper.Asset(applicationContext, "urovo_configuration.json")
- PIN_KEYBOARD_CONFIGURATION allows you to customize the buttons of the PIN keyboard. You can customize the texts and its sizes, the margins and the background colors as illustrated in the example below (see also customize PIN keyboard).
data class SDKUrovoPinTexts(
val title : String,
val message : String,
val pinRetriesText: String,
val okText : String,
val cancelText : String,
val deleteText : String
)
//Example
val pinKeyboardConfiguration = SDKPinKeyboardConfiguration.Builder().build(
SDKBasicPinKeyboardConfiguration(
SDKUrovoPinTexts(
"Enter pin",
"Enter pin",
"Retries left",
"OK",
"Cancel",
"Delete"
),
backgroundColor = listOf(
0xff37394f.toInt(),
0xffff00ff.toInt(),
0xffffff00.toInt(),
0xffff0000.toInt(),
0xffef464d.toInt(),
0xffcead03.toInt(),
0xff38ae93.toInt(),
)
)
)
SDKMoreFunReader.getInstaller(
<PIN_KEYBOARD_CONFIGURATION>,
<SDK_FILE_WRAPPER>
)
Where:
- SDK_FILE_WRAPPER allows you to provide a configuration file to the getInstaller.
sealed class SDKFileWrapper(val context: Context) {
/**
* You should use this class if the file that you want to provide is stored as an asset on your application
*
* @param context the application context
* @param name the file name of the asset
* **/
class Asset(context: Context, val name: String) : SDKFileWrapper(context)
/**
* You should use this class if the file that you want to provide is stored as a file on your device
*
* @param context the application context
* @param path the path of the file
* **/
class File(context: Context, val path: String) : SDKFileWrapper(context)
}
// example
SDKFileWrapper.Asset(applicationContext, "morefun_smartpos_reader_config.json")
- PIN_KEYBOARD_CONFIGURATION enables you to personalize the buttons on the PIN keyboard according to your preferences. You have the flexibility to customize the displayed text as illustrated in the example below (see also customize PIN keyboard ):
data class SDKMoreFunKeyboardTexts(
val title: String,
val okText: String,
val cancelText: String,
val deleteText: String,
val pinOfflineLastTryMessage: String,
val layoutId: Int = 0
)
// example
SDKMoreFunKeyboardTexts(
title = "enter PIN",
okText = "ok",
cancelText = "cancel",
deleteText = "delete",
pinOfflineLastTryMessage = "delete"
)
Initialization Example
- V1
- V2
val swipePinDecider = object : SDKSwipePinDecider{
override fun decide(card: SDKTransactionCard.Magnetic, decision: (pinRequire: Boolean) -> Unit) {
decision(true)
}
}
val sdkPaymentsConfiguration = SDKPaymentsConfiguration.Builder()
.setLogger(myCustomLogger)
.setSwipePinDecider(swipePinDecider)
.build(
applicationContext,
"https://example.net/",
setOf(readerInstallers),
setOf(pluginInstallers),
SDKPaymentsFlow.V1(mustConfirmOnline = true)
)
val flowHandle: SDKPaymentsFlowHandle = SDKPayments.init(sdkPaymentsConfiguration)
(flowHandle as SDKPaymentsFlowHandle.V1).createTransactionIntent(sdkV1TransactionData, sdkV1TransactionListener)
val swipePinDecider = object : SDKSwipePinDecider{
override fun decide(card: SDKTransactionCard.Magnetic, decision: (pinRequire: Boolean) -> Unit) {
decision(true)
}
}
val pendingPaymentIntents = object : SDKV2PendingPaymentIntents {
override suspend fun getToken(): SDKV2TokenResult {
return SDKV2TokenResult.Available(provideToken())
}
override fun onPendingPaymentIntentDiscarded(intentId: String) {
// payment intent could not be delivered successfully
}
}
val sdkPaymentsConfiguration = SDKPaymentsConfiguration.Builder()
.setLogger(myCustomLogger)
.setSwipePinDecider(swipePinDecider)
.build(
applicationContext,
"https://example.net/",
setOf(readerInstallers),
setOf(pluginInstallers),
SDKPaymentsFlow.V2(pendingPaymentIntents = pendingPaymentIntents)
)
val flowHandle: SDKPaymentsFlowHandle = SDKPayments.init(sdkPaymentsConfiguration)
(flowHandle as SDKPaymentsFlowHandle.V2).createTransactionIntent(sdkV2TransactionData, sdkV2TransactionListener)