Transaction
Transaction flow and transaction intent
The transaction flow you pass when building SDKPaymentsConfiguration (see Installation and configuration — Transaction flow) selects how the SDK runs that attempt internally.
After SDKPayments.init, you hold a SDKPaymentsFlowHandle. Narrow it to the flow you configured and use it for the lifetime of the SDK session.
A SDKTransactionIntent is the object that represents one in-flight transaction and controls whether that transaction can be aborted. You obtain it only after createTransactionIntent succeeds.
Transaction state and reader updates are handled by the listener you pass to createTransactionIntent
Create transaction intent
To start a transaction you create a SDKTransactionIntent through the flow handle you got from SDKPayments.init.
If a transaction is already in progress, the SDK tries to abort it first so you do not run more than one active intent at a time.
- V1
- V2
Call createTransactionIntent on the SDKPaymentsFlowHandle.V1 instance returned from SDKPayments.init (after configuring the flow as SDKPaymentsFlow.V1).
In V1, a transaction is one end-to-end attempt to perform a sale, refund, or cancel through the SDK: from creating an intent until the SDK reaches an approved, declined, or terminal error outcome.
The method receives SDKV1TransactionData, passing the transaction type, and your SDKV1TransactionListener implementation.
val txData = SDKV1TransactionData(SDKV1TransactionType.Sale)
flowHandle.createTransactionIntent(txData, object : SDKV1TransactionListener {
override fun onTransactionCreated(result: SDKV1TransactionIntentResult) {
when (result) {
is SDKV1TransactionIntentResult.Success -> {
currentTx = result.transactionIntent
}
is SDKV1TransactionIntentResult.Error -> {
appendInfo("Error creating a new transaction: ${result.message}")
}
}
}
override fun onTransactionStateChanged(state: SDKV1TransactionState) {
handleTxState(state)
}
override fun onReaderStateChanged(state: SDKReaderState) {
handleReaderState()
}
})
SDKV1TransactionIntentResult.Error is reported when a previous transaction exists and cannot be aborted. The transaction cannot be aborted during reader connection or after confirmation.
Call createTransactionIntent on the SDKPaymentsFlowHandle.V2 instance returned from SDKPayments.init (after configuring the flow as SDKPaymentsFlow.V2).
V2 uses SDKV2TransactionData, which currently requires a paymentIntentId. The app creates this payment intent for a sale and passes its identifier to the SDK, so the SDK does not receive a sale, refund, or cancel type when creating the transaction intent. The paymentIntentId must be a UUID.
val txData = SDKV2TransactionData(paymentIntentId = "<PAYMENT_INTENT_ID>")
flowHandle.createTransactionIntent(txData, object : SDKV2TransactionListener {
override fun onTransactionCreated(result: SDKV2TransactionIntentResult) {
when (result) {
is SDKV2TransactionIntentResult.Success -> {
currentTx = result.transactionIntent
}
is SDKV2TransactionIntentResult.Error.Other -> {
appendInfo("Error creating a new transaction: ${result.message}")
}
is SDKV2TransactionIntentResult.Error.PaymentIntentLimitExceeded -> {
val pendingIntents = result.pendingPaymentIntents.joinToString("\n") {
"• ${it.intentId} (${it.resolution})"
}
appendInfo("Cannot create a new transaction intent: maximum number of pending payment intents exceeded.\n$pendingIntents")
}
}
}
override fun onTransactionStateChanged(state: SDKV2TransactionState) {
handleTxState(state)
}
override fun onReaderStateChanged(state: SDKReaderState) {
handleReaderState()
}
})
SDKV2TransactionIntentResult.Error.PaymentIntentLimitExceeded is returned when the maximum number of pending payment intents has been reached. No new transaction intent can be created until the existing ones are resolved. The pendingPaymentIntents field exposes the current pending intents so the app can decide how to proceed.
| Variant | Meaning |
|---|---|
Error.Other | Generic error while creating the transaction intent; check message for details |
Error.PaymentIntentLimitExceeded | The pending payment intents queue is full; pendingPaymentIntents lists the blocking entries |
Explaining transaction states
The SDK reports transaction progress via state objects. The state type depends on the selected flow:
- V1 uses
SDKV1TransactionState - V2 uses
SDKV2TransactionState
In each state below the narrative is shared between both flows; only the code snippets are split per flow because that is where V1 and V2 differ (class prefix and, in some states, the shape of the state itself).
Provide a device
Provide a device which was previously scanned.
Call the method provideDevice in the DeviceRequired state.
- V1
- V2
fun handleTxState(state: SDKV1TransactionState) {
when (state) {
is SDKV1TransactionState.DeviceRequired -> {
state.provideDevice(selectedDevice!!) // device previously found by the scanner
}
...
}
}
fun handleTxState(state: SDKV2TransactionState) {
when (state) {
is SDKV2TransactionState.DeviceRequired -> {
state.provideDevice(selectedDevice!!) // device previously found by the scanner
}
...
}
}
Provide reader configuration
Create the read configuration object for your flow and call provideReadConfig in the ReadConfigRequired state.
Note: After providing the device, the SDK automatically makes a preliminary request to the
/dummyendpoint before starting the card reading. This request aims to establish the TLS connection with the server before performing the actual transaction, which allows reducing up to 200ms of the TLS handshake time in the first payment request.
- V1
- V2
fun handleTxState(state: SDKV1TransactionState) {
val readConfig = SDKV1ReadConfig(
timeout = 60.seconds,
readModes = setOf(SDKCardReadMode.Chip, SDKCardReadMode.Swipe, SDKCardReadMode.Nfc),
cardInsertionStatus = SDKCardInsertionStatus.NotInserted,
transactionTotals = getSDKV1TransactionTotals()
)
when (state) {
is SDKV1TransactionState.ReadConfigRequired -> {
state.provideReadConfig(readConfig)
}
...
}
}
fun handleTxState(state: SDKV2TransactionState) {
val readConfig = SDKV2ReadConfig(
timeout = 60.seconds,
readModes = setOf(SDKCardReadMode.Chip, SDKCardReadMode.Swipe, SDKCardReadMode.Nfc),
cardInsertionStatus = SDKCardInsertionStatus.NotInserted,
transactionTotals = getSDKV2TransactionTotals()
)
when (state) {
is SDKV2TransactionState.ReadConfigRequired -> {
state.provideReadConfig(readConfig)
}
...
}
}
Providing transaction totals
Both flows include a transactionTotals field in their read configuration, but with different shapes:
- V1
- V2
The SDKV1TransactionTotals, which lets you specify the net transaction amount, taxes and tip in detail.
It is configured within the SDKV1ReadConfig object to ensure that all necessary transaction details are provided for accurate reporting.
private fun getSDKV1TransactionTotalsWithList(tax1 : BigDecimal, tax2 : BigDecimal, tip : BigDecimal): SDKV1TransactionTotals {
return SDKV1TransactionTotals(
net = SDKMoney(TRANSACTION_TOTAL, SDKCurrency.ars()),
taxes = SDKV1Taxes.BreakDown(
listOf(
SDKV1Tax(SDKMoney(tax1, SDKCurrency.ars()), "tax1"),
SDKV1Tax(SDKMoney(tax2, SDKCurrency.ars()), "tax2")
)
),
tip = SDKMoney(tip, SDKCurrency.ars())
)
}
- net: Represents the total amount of the transaction, specified in the
SDKMoneyobject. This includes both the amount and the currency. - taxes: Represents the tax amounts, included within the net amount for reference purposes.
SDKV1Taxes.Total(...)— Use this to specify a single tax amount if applicable.SDKV1Taxes.BreakDown(listOf(... , ...))— Use this option to specify multiple taxes as a list ofSDKV1Taxobjects, each with a labeled description (e.g., "iva" and "iva2" in the example).
- tip: Specifies the tip amount, also provided in an
SDKMoneyobject. This field is optional and can be omitted if a tip is not applicable.
Each component (net, taxes, and tip) uses SDKMoney to ensure accurate and consistent handling of currency and decimal values across all transaction amounts.
The SDKV2Total object only carries the single total amount for the transaction and is configured within SDKV2ReadConfig.
Note: In V2, the other amount-related fields are provided directly in the confirmation object. See Confirm transaction.
private fun getSDKV2TransactionTotals(): SDKV2Total {
return SDKV2Total(
total = SDKMoney(TRANSACTION_TOTAL, SDKCurrency.ars())
)
}
- total: Represents the total amount of the transaction, specified in the
SDKMoneyobject. This includes both the amount and the currency.
SDKMoney ensures accurate and consistent handling of currency and decimal values.
Select EMV app
There are cards that have more than one EMV application internally in the chip. This is a way that allows the standard to have several logic cards in the same plastic (for example, a credit card and a debit card), In this state the Readers SDK provides the list of available applications and you must choose which one you want to use (if the card only has one emv application, this state is not called).
Call the method selectEmvApp in the SelectEmvApp state.
- V1
- V2
fun handleTxState(state: SDKV1TransactionState) {
when (state) {
is SDKV1TransactionState.SelectEmvApp -> {
val availableEmvApps = state.availableEmvApps
customMethodForSelectEmvApp(availableEmvApps) { emvApp ->
state.selectEmvApp(emvApp)
}
}
}
}
fun handleTxState(state: SDKV2TransactionState) {
when (state) {
is SDKV2TransactionState.SelectEmvApp -> {
val availableEmvApps = state.availableEmvApps
customMethodForSelectEmvApp(availableEmvApps) { emvApp ->
state.selectEmvApp(emvApp)
}
}
}
}
Confirm transaction
When the card is read, this data is provided to the Readers SDK user who decides to confirm or reject the transaction, then online processing is performed. Finally, an approved or declined, or error response is obtained,the data necessary to confirm the transaction will be explained in using JSON for confirmation section.
- V1
- V2
In V1, the transaction type is part of SDKV1TransactionData, so the ConfirmTransaction state has three branches: Sale, Refund and Cancel.
Confirm a sale.
fun handleTxState(state: SDKV1TransactionState) {
when (state) {
is SDKV1TransactionState.ConfirmTransaction.Sale -> {
val confirmation: SDKConfirmationJson = getNewSaleConfirmationJson()
state.confirm(confirmation)
}
...
}
}
Confirm a refund.
fun handleTxState(state: SDKV1TransactionState) {
when (state) {
is SDKV1TransactionState.ConfirmTransaction.Refund -> {
val confirmation: SDKConfirmationJson = getNewRefundConfirmationJson()
state.confirm(confirmation)
}
...
}
}
Confirm a cancel.
fun handleTxState(state: SDKV1TransactionState) {
when (state) {
is SDKV1TransactionState.ConfirmTransaction.Cancel -> {
val confirmation: SDKConfirmationJson = getNewCancelConfirmationJson()
state.confirm(confirmation)
}
...
}
}
Confirm the transaction.
V2 only supports sale transactions, so only ConfirmTransaction.Sale is exposed in the SDK.
fun handleTxState(state: SDKV2TransactionState) {
when (state) {
is SDKV2TransactionState.ConfirmTransaction.Sale -> {
val confirmation: SDKConfirmationJson = getNewSaleConfirmationJson()
state.confirm(confirmation)
}
...
}
}
Provide additional data (when required) (V2 only)
This state only exists in V2.
Some integrations may require additional data before the SDK can continue the transaction. In that case, you will receive SDKV2TransactionState.AdditionalDataRequired.
The state exposes a list of SDKV2RequiredAction, where each action describes what the backend is asking for:
type: the type identifier of the required action.requiredDataJson: a JSON string with the data associated with that required action.
The most common required action is installment plan selection: requiredDataJson carries the list of available plans, each one identified by its own reference_number. The app must pick one plan and reply with that reference_number plus the selected payment_plan.
Provide the answer by calling provideAdditionalData(additionalDataJson) with the response JSON, or stop the attempt by calling reject() (which ends the transaction with a recoverable error and allows retrying with a different card).
See Additional data (JSON) for the schema of the response.
fun handleTxState(state: SDKV2TransactionState) {
when (state) {
is SDKV2TransactionState.AdditionalDataRequired -> {
val requiredActions: List<SDKV2RequiredAction> = state.requiredActions
customMethodForAdditionalData(requiredActions) { result ->
when (result) {
is AdditionalDataResult.Provide -> {
state.provideAdditionalData(result.additionalDataJson)
}
is AdditionalDataResult.Reject -> {
state.reject()
}
}
}
}
...
}
}
Transaction approved
If the transaction is approved, the result is reported through the TransactionApproved state.
- V1
- V2
In V1, TransactionApproved is split into a Sale branch and a Refund/Cancel branch.
fun handleTxState(state: SDKV1TransactionState) {
when (state) {
is SDKV1TransactionState.TransactionApproved -> {
if (state is SDKV1TransactionState.TransactionApproved.Sale) {
// Approved sale
} else {
// Approved refund or cancel
}
}
...
}
}
In V2, the transaction type is fixed by the payment intent, so TransactionApproved is a single state that exposes the referenceNumber of the approved transaction.
fun handleTxState(state: SDKV2TransactionState) {
when (state) {
is SDKV2TransactionState.TransactionApproved -> {
// Approved transaction, see state.referenceNumber
}
...
}
}
Transaction error
During a transaction, two kind of errors can occur: recoverable and not recoverable. In the recoverable, it’s possible to retry the transaction calling the associate callback, in the not recoverable errors, the transaction finished and it's necessary to create another one.
Check whether state is RecoverableError or NonRecoverableError. After a NonRecoverableError, create a new transaction intent with createTransactionIntent on the corresponding flow handle (SDKPaymentsFlowHandle.V1 or SDKPaymentsFlowHandle.V2, the handle returned by SDKPayments.init), as you did for the first attempt.
- V1
- V2
fun handleTxState(state: SDKV1TransactionState) {
when (state) {
is SDKV1TransactionState.RecoverableError -> {
...
state.recover() //if you want try recover an error
}
is SDKV1TransactionState.NonRecoverableError -> {
//transaction is finished by an error
}
...
}
}
fun handleTxState(state: SDKV2TransactionState) {
when (state) {
is SDKV2TransactionState.RecoverableError -> {
...
state.recover() //if you want try recover an error
}
is SDKV2TransactionState.NonRecoverableError -> {
//transaction is finished by an error
}
...
}
}
Explaining the SDKReaderState states
SDKReaderState tells the user the current state of the reader
Connecting
Reader is connecting
Connected.Idle
Reader is connected and idle
Connected.WaitingForCard
Reader is connected and waiting for card
Connected.Processing.Pin
Reader is connected and processing Pin
Connected.Processing.Card
Reader is connected and processing card
Connected.UpdatingConfiguration
Reader is connected and updating its configuration
NotConnected
Reader is not connected