TerminalSDK Library Documentation
The TerminalSDK library provides a set of classes and methods for handling user authentication, terminal connections, and card transactions.
Part 1: Preparing the Required Assets
To use the library, you must provide specific assets and configurations.
Information to Provide to the Nearpay Team
When setting up your integration with Nearpay, ensure that you provide the following details:
-
Android Package Name
- This is the unique identifier for your Android application.
- Example:
com.yourcompany.yourapp
-
PEM Certificate
- A PEM (Privacy-Enhanced Mail) certificate is required for secure communication.
- You can generate a PEM certificate by following the steps outlined here (Replace with the actual link to instructions). here.
Ensure these details are accurate before submitting them to Nearpay for a smooth integration process.
Google Play Integrity and Huawei Safety Detect
This library uses Google Play Integrity (Mandatory) and Huawei Safety Detect (Optional) to verify the integrity of the device.
Google Play Integrity (Mandatory)
To make the library work, you need to have a Google Cloud project with Play Integrity enabled and pass the Google Cloud project number in the builder. Here are the steps to do so:
Create a Google Cloud Project:
- Go to the Google Cloud Console.
- Create a new project or use an existing one.
- Get the Project Number:
- Go to the Google Cloud Console.
- Click on the project you created.
- Go to the project settings.
- Copy the project number.
- Enable Play Integrity API:
- Go to the Google Play Console.
- Navigate to Release > App Integrity.
- Under the Play Integrity API, select Link a Cloud project.
- Choose the Cloud project you want to link to your app, which will enable Play Integrity API responses.
- This may change in the future, so please refer to the official documentation here: Google Play Integrity documentation
- Pass the Project Number in the Builder:
- Use the
googleCloudProjectNumbermethod in the builder to pass the project number.
- Use the
Huawei Safety Detect (Optional)
To use Huawei Safety Detect, you need to have a Huawei Developer account and pass the Safety Detect API key in the builder. Here are the steps to do so:
- Create a Huawei Developer Account:
- Go to the Huawei Developer Console.
- Create a new account or use an existing one.
- Create an App:
- Go to AppGallery Connect.
- Create a new app or use an existing one.
- Enable Safety Detect:
- Go to the AppGallery Connect console.
- Navigate to Develop > Security Detection.
- Enable Safety Detect.
- Get the Safety Detect API Key:
- Go to the AppGallery Connect console.
- Navigate to Develop > Security Detection.
- Click on the Safety Detect tab.
- Copy the API key.
- Pass the API Key in the Builder:
- Use the
safetyDetectApiKeymethod in the builder to pass the Safety Detect API key.
- Use the
Configuring the Secure Maven Repository / Dependencies
For the ReaderCore library, you can include the following configuration in your root-level settings.gradle(.kts) file:
if your project is using Groovy settings.gradle, you can use the following configuration:
Very important:
To get the private token , you need to contact Nearpay to get the token for the private repository.
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven(url = "https://developer.huawei.com/repo/")
maven {
url = uri("https://git.nearpay.io/api/v4/projects/165/packages/maven")
credentials(HttpHeaderCredentials::class) {
name = "Private-Token"
value = nearpayPosGitlabReadToken // The token provided by Nearpay goes here.
}
authentication {
create<HttpHeaderAuthentication>("header")
}
}
}
}
You should include the following dependencies in your Module level build.gradle file:
implementation("io.nearpay:terminalsdk-release:0.2.2")
implementation("com.google.android.gms:play-services-location:20.0.0")
implementation("com.huawei.hms:location:6.4.0.300")
In addition, you have to change the minSdk version to 28 in your build.gradle file:
android {
defaultConfig {
applicationId = "com.example.example"
minSdk = 28
}
}
Contact Nearpay to register your applicationId and get the necessary credentials.
AndroidManifest.xml Configuration
In your AndroidManifest.xml file, add the following line:
<application
android:allowBackup="true"
tools:replace="android:allowBackup" // Add this line to avoid manifest merger issues
Make sure the following tools namespace is present in your <manifest> tag:// xmlns:tools="http://schemas.android.com/tools"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> // this tools namespace
...
Part 2: Getting Started with TerminalSDK
To start using TerminalSDK, initialize it with the necessary configurations.
// initializing the terminalSDK may throw an exception, so wrap it in a try-catch block
try {
val nearpay = TerminalSDK.Builder()
.activity(this) // Sets the activity context
.environment(SdkEnvironment.SANDBOX) // Choose SANDBOX or PRODUCTION or INTERNAL
.googleCloudProjectNumber(12345678) // Set Google Cloud project number
.huaweiSafetyDetectApiKey("your_api_key") // Set Huawei API key
.country(Country.SA) // Set country SA, TR, USA, KEN ,
.networkConfiguration(NetworkConfiguration.DEFAULT) // DEFAULT | SIM_PREFERRED | SIM_ONLY
.uiDockPosition(UiDockPosition.BOTTOM_CENTER) // Optional: set the location of the Tap to Pay modal
.supportSecondDisplay(SupportSecondDisplay.ENABLE) // Optional: enable support for second display
.secondDisplayDockPosition(UiDockPosition.TOP_RIGHT) // Optional: set the location of the Tap to Pay modal on the second display
.build()
} catch (e: Throwable) {
Timber.e("Error initializing TerminalSDK: $e")
}
Permissions and Requirements
Check Required Permissions
The SDK provides methods to check and verify required permissions and device capabilities.
Check all required permissions and return a list of PermissionStatus objects.
// Check all required permissions
val permissionStatuses = nearpay.checkRequiredPermissions()
permissionStatuses.forEach { status ->
if (!status.isGranted) {
// Handle missing permission
Log.d("Permissions", "Missing permission: ${status.permission}")
}
}
The required permissions include:
Manifest.permission.ACCESS_FINE_LOCATIONManifest.permission.ACCESS_NETWORK_STATEManifest.permission.INTERNETManifest.permission.READ_PHONE_STATEManifest.permission.NFC
The following code snippets show how to check permissions, then ask for missing permissions if needed, and also check if NFC and WiFi are enabled.
var PERMISSIONS_REQUEST_CODE = 0
// Check permissions
val missingPermissions =
nearpay.checkRequiredPermissions().filter { !it.isGranted }.map { it.permission }
if (missingPermissions.isNotEmpty()) {
// Request missing permissions
ActivityCompat.requestPermissions(
this, missingPermissions.toTypedArray(), PERMISSIONS_REQUEST_CODE //0 for example
)
}
// Check NFC
if (!nearpay.isNfcEnabled(this)) {
Log.d("MainActivity", "NFC is disabled");
}
// Check WiFi
if (!nearpay.isWifiEnabled(this)) {
Log.d("MainActivity", "WiFi is disabled");
}
Part 3: Authentication
Send OTP
The SDK supports both mobile and email OTP authentication.
Mobile Authentication
val mobileLogin = MobileLogin("+966500000000")
nearpay.sendOTP(mobileLogin, object : SendOTPMobileListener {
override fun onSendOTPMobileSuccess(otpResponse: OtpResponse) {
Log.d("Login", " onSendOTPMobileSuccess ($otpResponse)")
}
override fun onSendOTPMobileFailure(otpMobileFailure: OTPMobileFailure) {
Log.d("Login", " onSendOTPMobileFailure ($otpMobileFailure)")
}
})
Email Authentication
val emailLogin = EmailLogin("[email protected]")
nearpay.sendOTP(emailLogin, object : SendOTPEmailListener {
override fun onSendOTPEmailSuccess(otpResponse: OtpResponse) {
Log.d("Login", " onSendOTPEmailSuccess ($otpResponse)")
}
override fun onSendOTPEmailFailure(otpEmailFailure: OTPEmailFailure) {
Log.d("Login", " onSendOTPEmailFailure ($otpEmailFailure)")
}
})
Verify OTP
After sending the OTP, verify it to authenticate the user.
Mobile Verification
val loginData = LoginData(
mobile = "+966500000000",
code = "123456"
)
nearpay.verify(loginData, object : VerifyMobileListener {
override fun onVerifyMobileSuccess(user: User) {
Log.d("Login", " onVerifyMobileSuccess ${user.name}")
userUuid = user.userUUID() // Retrieve the UUID of the currently logged-in user. This value can be reused later to fetch user details using getUserByUUID().
}
override fun onVerifyMobileFailure(verifyMobileFailure: VerifyMobileFailure) {
Log.d("Login", " onVerifyMobileFailure ($failure)")
}
})
Email Verification
val loginData = LoginData(
email = "[email protected]",
code = "123456"
)
nearpay.verify(loginData, object : VerifyEmailListener {
override fun onVerifyEmailSuccess(user: User) {
Log.d("Login", " onVerifyEmailSuccess ($user.name)")
userUuid = user.userUUID() // Retrieve the UUID of the currently logged-in user. This value can be reused later to fetch user details using getUserByUUID().
}
override fun onVerifyEmailFailure(verifyEmailFailure: VerifyEmailFailure) {
Log.d("Login", " onVerifyEmailFailure ($verifyMobileFailure)")
}
})
JWT Verification
For creating a JWT token, please visit this link : JWT Token Generator
val loginData = JWTLoginData(
jwt = "jwt_token"
)
nearpay.jwtLogin(loginData, object : JWTLoginListener {
override fun onJWTLoginSuccess(terminal: Terminal) {
Log.d("Login", "JWT Login success + ${terminal.terminalUUID}")
terminalUUID = terminal.terminalUUID // Retrieve the UUID of the currently terminal session. This value can be reused later to fetch terminal session details using getTerminal().
}
override fun onJWTLoginFailure(jwtLoginFailure: JWTLoginFailure) {
Log.d("Login", "JWT Login failure: $jwtLoginFailure")
}
})
Get User
You can also get a User instance after calling the getUserByUUID method if the user has already been authenticated before using the SDK and you have their UUID from the previously returned User instance.
Saving the user UUID is the responsibility of the developer, not the SDK.
try{
val user = nearpay.getUserByUUID(userUuid)
} catch (e: Exception) {
Log.d("UserSDK", "User connection failed: ${e}")
}
// The returned user object also becomes the active user
Logout User
To log out a user and delete its instance from memory, call the logout method.
Takes a User UUID as a parameter and logs out the user.
try {
nearpay.logout(uuid)
} catch (e: Exception) {
Log.d("UserSDK", "User logout failed: ${e}")
}
Part 4: User Operations
The User class is initialized internally by the SDK and provides methods for managing terminals.
List Terminals
Retrieves a paginated list of terminals associated with the user.
Usage
lateinit var fetchedTerminals : List<TerminalConnection>
userInstance.listTerminals(
page = 1,
pageSize = 10,
filter = null, // You can pass the terminal ID to get a specific terminal
object : GetTerminalsListener {
override fun onGetTerminalsSuccess(terminalsConnection: List<TerminalConnection>, pagination: Pagination) {
fetchedTerminals = terminalsConnection // list of terminals that assigned to the connected user
}
override fun onGetTerminalsFailure(getTerminalsFailure: GetTerminalsFailure) {
// Handle failure
Log.d("Terminals", "Terminals list failure: $getTerminalsFailure")
}
},
)
Part 5: Terminal Connection Operations
Connect Terminal
Establishes a connection with a terminal.
Usage
var firstTerminal = fetchedTerminals.terminals[0] ; // Retrieve the specific terminal connection from the list of fetched terminals. This terminal will be used to perform operations.
firstTerminal.connect(
activity = this,
listener = object : ConnectTerminalListener {
override fun onConnectTerminalSuccess(terminal: Terminal) {
// Terminal connected successfully
// Store terminal instance for future operations
terminalInstance = terminal
terminalUUID = terminalInstance.terminalUUID // Retrieve the UUID of the currently terminal session. This value can be reused later to fetch terminal session details using getTerminal().
}
override fun onConnectTerminalFailure(connectTerminalFailure: ConnectTerminalFailure) {
// Handle failure
Log.d("Terminal", "Terminal connection failed: $connectTerminalFailure")
}
}
)
Part 6: Terminal Operations
Before getting into Terminal class functions, you can also get a Terminal instance after calling the getTerminal method and passing the terminal's ID instead of using a TerminalConnection instance.
Get Terminal
Retrieves a Terminal instance for a specific terminal ID.
try {
private var terminalUUID: String = "7d74bc33-d14a-4111-bba3-b33707d57ae4"
val terminalInstance = nearpay.getTerminal(
activity = this,
uuid = terminalUUID
)
} catch (e: Exception) {
Log.d("TerminalSDK", "Terminal connection failed: ${e}")
}
Purchase
Initiates a purchase transaction by reading the card and sending the transaction.
var amount = 100
var intentUuid = UUID.randomUUID().toString() // the intent UUID should be unique for each transaction and managed by the developer to communicate with the SDK
var customerReferenceNumber = "" //[optional] any number you want to add as a refrence
terminal.purchase(
amount = amount,
scheme = null, // eg.PaymentScheme.VISA, specifying this as null will allow all schemes to be accepted
intentUUID = transactionUUID,
customerReferenceNumber = customerReferenceNumber,
readCardListener = object : ReadCardListener {
override fun onReaderDismissed() {
// Reader dismissed by user
Log.d("ReadCard", "Reader dismissed by user")
}
override fun onReadCardSuccess() {
// Card read successfully
Log.d("ReadCard", "Card read successfully")
}
// Called when the card reading process fails - issues with the specific card or its interaction
// Examples: card removed too quickly, unreadable card, wrong card orientation
override fun onReadCardFailure(readCardFailure: ReadCardFailure) {
// Handle card read failure
Log.d("ReadCard", "Card read failure: $readCardFailure")
}
override fun onReaderDisplayed() {
Log.d("ReaderCard", "Reader Displayed")
}
override fun onReaderClosed() {
Log.d("ReaderCard", "Reading Closed")
}
override fun onReaderWaiting() {
// Reader waiting for card
Log.d("ReadCard", "Reader waiting for card")
}
override fun onReaderReading() {
// Reading card in progress
Log.d("ReadCard", "Reading card in progress")
}
override fun onReaderRetry() {
// Reader retry needed
Log.d("ReadCard", "Reader retry needed")
}
override fun onPinEntering() {
// PIN entry in progress
Log.d("ReadCard", "PIN entry in progress")
}
override fun onReaderFinished() {
// Card read completed
Log.d("ReadCard", "Card read completed")
}
override fun onReadingStarted() {
// Card read started
Log.d("ReadCard", "Card read started")
}
// Called when the card reader device itself encounters an error
// Examples: hardware malfunction, connection issues, device not ready
override fun onReaderError(error: String?) {
// Handle reader error
Log.d("ReadCard", "Reader error: $error")
}
},
sendTransactionListener = object : SendTransactionListener {
override fun onSendTransactionCompleted(purchaseResponse: PurchaseResponse) {
// Handle completed transaction
//PurchaseResponse will return all transaction with same intent id "transactionUUID"
//purchaseResponse.status will return the status of last transaction of the same intent id
Log.d("Transaction", "Transaction completed: $purchaseResponse")
// To get the approved receipt based on the country you can got it like :
// purchaseResponse.getLastReceipt().getMadaReceipt() for Saudi Arabia
// purchaseResponse.getLastReceipt().getEPXReceipt() for USA
// purchaseResponse.getLastReceipt().getBKMReceipt() for Turkey
}
override fun onSendTransactionFailure(failure: SendTransactionFailure) {
// Handle transaction failure
Log.d("Transaction", "Transaction failure: $failure")
}
}
)
Find PurchaseResponse model here.
Find SendTransactionFailure model here.
Purchase with QR code
Initiates a purchase transaction by reading the QR code and sending the transaction.
var amount = "100"
var id = UUID.randomUUID().toString() // the intent UUID should be unique for each transaction and managed by the developer to communicate with the SDK
var customerReferenceNumber = "" //[optional] any number you want to add as a refrence
var enableUiDismiss = true // set to true to allow user to dismiss the QR code UI
terminal.generateQrCodeWithUi(
id = id,
amount = amount,
customerReferenceNumber = customerReferenceNumber,
enableUiDismiss = enableUiDismiss,
qrPaymentUiListener = object : QrPaymentUiListener {
override fun onQrUiPresented(qrCode: GeneratedQRCode) {
}
override fun onQrPaymentStatusUpdated(status: QrStatusResponse) {
}
override fun onQrPaymentStatusExpired(status: QrStatusResponse) {
}
override fun onQrPaymentStatusCompleted(status: QrStatusResponse) {
}
override fun onQrPaymentApproved(intent: IntentDetails) {
}
override fun onQrPaymentDeclined(intent: IntentDetails?) {
}
override fun onQrPaymentFailure(message: String) {
}
override fun onQrPaymentStatusFailed(status: QrStatusResponse) {
}
}
)
You can find QrStatusResponse model here.
You can find GeneratedQRCode model here.
You can find IntentDetails model here.
Purchase Void with QR code
Initiates a purchase void transaction by reading the QR code and sending the transaction.
val voidIntentId = UUID.randomUUID().toString()
terminal.qrVoid(
intentId = voidIntentId,
originalIntentId = originalIntentId,
qrVoidListener = object : QrVoidListener {
override fun onQrVoidSuccess(response: QrStatusResponse) {
Timber.d("QR void completed successfully.")
}
override fun onQrVoidFailure(failure: QrVoidFailure) {
val message = when (failure) {
is QrVoidFailure.GeneralFailure ->
failure.message ?: "Unknown error"
}
Timber.e("QR void failed: $message")
}
override fun onQrVoidQrCodeGenerated(qrCode: GeneratedQRCode) {
Timber.d("QR code generated successfully." +
"Void Intent ID: ${qrCode.intentId}" +
"Amount: ${qrCode.amount}" +
"Transaction ID: ${qrCode.transactionId}" +
"Payload: ${qrCode.qrCode}"
)
}
}
// write in english
You can find GeneratedQRCode model here.
You can find QrStatusResponse model here.
Refund with QR code
Initiates a refund transaction by reading the QR code and sending the transaction.
val refundIntentId = UUID.randomUUID().toString()
terminal.qrRefund(
intentUUID = originalIntentId,
refundUUID = refundIntentId,
qrRefundListener = object : QrRefundListener {
override fun onQrRefundSuccess(response: QrStatusResponse) {
Timber.d("QR refund completed: $response")
}
override fun onQrRefundFailure(failure: QrRefundFailure) {
val message = when (failure) {
is QrRefundFailure.GeneralFailure ->
failure.message ?: "Unknown error"
}
Timber.e("QR refund failed: $message")
}
}
)
You can find QrStatusResponse model here.
You can find the QrRefundFailure model here.
Purchase with Installments
Initiates a purchase transaction with installments by reading the card and sending the transaction.
var amount = 100
var numberOfInstallments = 3
var intentUuid = UUID.randomUUID().toString() // the intent UUID should be unique for each transaction and managed by the developer to communicate with the SDK
var customerReferenceNumber = "" //[optional] any number you want to add as a refrence
terminal.installments(
amount = amount,
numberOfInstallments = numberOfInstallments,
scheme = null, // eg.PaymentScheme.VISA, specifying this as null will allow all schemes to be accepted
intentUUID = transactionUUID,
customerReferenceNumber = customerReferenceNumber,
readCardListener = object : ReadCardListener {
override fun onReaderDismissed() {
// Reader dismissed by user
Log.d("ReadCard", "Reader dismissed by user")
}
override fun onReadCardSuccess() {
// Card read successfully
Log.d("ReadCard", "Card read successfully")
}
// Called when the card reading process fails - issues with the specific card or its interaction
// Examples: card removed too quickly, unreadable card, wrong card orientation
override fun onReadCardFailure(readCardFailure: ReadCardFailure) {
// Handle card read failure
Log.d("ReadCard", "Card read failure: $readCardFailure")
}
override fun onReaderDisplayed() {
Log.d("ReaderCard", "Reader Displayed")
}
override fun onReaderClosed() {
Log.d("ReaderCard", "Reading Closed")
}
override fun onReaderWaiting() {
// Reader waiting for card
Log.d("ReadCard", "Reader waiting for card")
}
override fun onReaderReading() {
// Reading card in progress
Log.d("ReadCard", "Reading card in progress")
}
override fun onReaderRetry() {
// Reader retry needed
Log.d("ReadCard", "Reader retry needed")
}
override fun onPinEntering() {
// PIN entry in progress
Log.d("ReadCard", "PIN entry in progress")
}
override fun onReaderFinished() {
// Card read completed
Log.d("ReadCard", "Card read completed")
}
override fun onReadingStarted() {
// Card read started
Log.d("ReadCard", "Card read started")
}
// Called when the card reader device itself encounters an error
// Examples: hardware malfunction, connection issues, device not ready
override fun onReaderError(error: String?) {
// Handle reader error
Log.d("ReadCard", "Reader error: $error")
}
},
installmentsListener = object : InstallmentsListener {
override fun onInstallmentsCompleted(purchaseResponse: PurchaseResponse) {
// Handle completed transaction
//PurchaseResponse will return all transaction with same intent id "transactionUUID"
//purchaseResponse.status will return the status of last transaction of the same intent id
Log.d("Transaction", "Transaction completed: $purchaseResponse")
// To get the approved receipt based on the country you can got it like :
// purchaseResponse.getLastReceipt().getMadaReceipt() for Saudi Arabia
// purchaseResponse.getLastReceipt().getEPXReceipt() for USA
// purchaseResponse.getLastReceipt().getBKMReceipt() for Turkey
}
override fun onInstallmentsFailure(failure: SendTransactionFailure) {
// Handle transaction failure
Log.d("Transaction", "Transaction failure: $failure")
}
}
)
Find PurchaseResponse model here.
Find SendTransactionFailure model here.
Payment Flow with External Gateway and BIN Routing
In BIN-routed transactions, after reading the card, the BIN number is queried by the SDK and, according to predefined routing rules, the transaction is routed to an external payment gateway (External Gateway) instead of the Nearpay infrastructure.
This is a two-step process: first, retrieve the routing profile, then perform routing lookup and routing execute operations.
- Retrieve Routing Profile: The RoutingProfile contains information such as the registered terminals at the bank and their properties. This information is used to route the transaction.
terminal.getRoutingProfile(
object : GetRoutingProfileListener {
override fun onGetRoutingProfileSuccess(profile: RoutingProfile) {
// Routing profile retrieved successfully
// You can use the profile information to determine how to route the transaction
Log.d("RoutingProfile", "Routing profile retrieved: $profile")
terminalProfile = profile
}
override fun onGetRoutingProfileFailure(error: GetRoutingProfileFailure) {
val errorMessage = when (error) {
is GetRoutingProfileFailure.GeneralFailure -> error.message
}
Log.e("RoutingProfile", "Failed to retrieve routing profile: $errorMessage")
}
}
)
You can find the RoutingProfile model here.
You can find the GetRoutingProfileFailure model here.
- Routing Lookup and Execute: After the card is read, call the terminal.routingLookup() method. This method is used to obtain appropriate routing options based on the card's BIN number. Then, you can perform the transaction using the selected routing option with the terminal.routingExecute() method.
var amount = 100
var intentUUID = UUID.randomUUID().toString() // Benzersiz intent UUID
var customerReferenceNumber = "" // [opsiyonel] referans numarasΔ±
terminal.routingLookup(
amount = amount,
intentUUID = intentUUID,
readCardListener = object : ReadCardListener {
// Kart okuma geri Γ§aΔΔ±rΔ±mlarΔ± (ΓΆnceki ΓΆrneklerde gΓΆsterildiΔi gibi uygulayΔ±n)
},
routingLookupListener = object : RoutingLookupListener {
override fun onRoutingLookupCompleted(
response: RoutingResponse,
session: RoutingSession
) {
// SeΓ§ilen acquirer terminal ID'sini alΔ±n
// select terminal based on issuer id matching (if available) to increase chances of successful routing execute, otherwise fallback to random selection
val issuerId = response.binInformation?.issuerId
val matchedTerminal =
terminalProfile.terminals.firstOrNull { terminal ->
terminal.terminalInfo.acquirerId.equals(
issuerId,
ignoreCase = true
)
}
val selectedTid: String? =
matchedTerminal?.terminalInfo?.acquirerTerminalId?.trim()
?: terminalProfile.terminals
.shuffled()
.firstOrNull()
?.terminalInfo
?.acquirerTerminalId
?.trim()
terminal.routingExecute(
session = session,
selectedAcquirerTerminalId = selectedTid,
customerReferenceNumber = customerReferenceNumber,
sendTransactionListener = object : SendTransactionListener {
override fun onSendTransactionCompleted(
purchaseResponse: PurchaseResponse
) {
// Δ°Εlem tamamlandΔ±
// PurchaseResponse, aynΔ± intent id'ye sahip tΓΌm iΕlemleri dΓΆndΓΌrΓΌr
// purchaseResponse.status, son iΕlemin durumunu dΓΆndΓΌrΓΌr
Log.d("Transaction", "Transaction completed: $purchaseResponse")
// Onaylanan slipi ΓΌlkeye gΓΆre almak iΓ§in:
// purchaseResponse.getLastReceipt().getMadaReceipt() (Suudi Arabistan)
// purchaseResponse.getLastReceipt().getEPXReceipt() (ABD)
// purchaseResponse.getLastReceipt().getBKMReceipt() (TΓΌrkiye)
}
override fun onSendTransactionFailure(
sendTransactionFailure: SendTransactionFailure
) {
// Δ°Εlem baΕarΔ±sΔ±z oldu
Log.d("Transaction", "Transaction failure: $sendTransactionFailure")
val errorMessage = when (sendTransactionFailure) {
is SendTransactionFailure.TransactionFailure ->
"Routing execute failed for $selectedTid: ${sendTransactionFailure.message}"
else -> "Routing execute failed for $selectedTid."
}
// Hata mesajΔ±nΔ± burada kullanabilirsiniz
}
}
)
}
override fun onRoutingLookupFailure(routingLookupFailure: RoutingLookupFailure) {
val errorMsg = when (routingLookupFailure) {
is RoutingLookupFailure.Failure -> routingLookupFailure.message
}
Log.d("RoutingLookup", "Routing lookup failure: $errorMsg")
}
}
)
You can find the RoutingResponse model here.
You can find the RoutingSession model here.
You can find the PurchaseResponse model here.
You can find the SendTransactionFailure model here.
You can find the RoutingLookupFailure model here.
Payment Flow with External Gateway and BIN Routing with Installments
In BIN-routed transactions, after reading the card, the BIN number is queried by the SDK and, according to predefined routing rules, the installment transaction is routed to an external payment gateway (External Gateway) instead of the Nearpay infrastructure.
This is a two-step process: first, retrieve the routing profile, then perform routing lookup and routingInstallments operations.
- Retrieve Routing Profile: The RoutingProfile contains information such as the registered terminals at the bank and their properties. This information is used to route the transaction.
terminal.getRoutingProfile(
object : GetRoutingProfileListener {
override fun onGetRoutingProfileSuccess(profile: RoutingProfile) {
// Routing profile retrieved successfully
// You can use the profile information to determine how to route the transaction
Log.d("RoutingProfile", "Routing profile retrieved: $profile")
terminalProfile = profile
}
override fun onGetRoutingProfileFailure(error: GetRoutingProfileFailure) {
val errorMessage = when (error) {
is GetRoutingProfileFailure.GeneralFailure -> error.message
}
Log.e("RoutingProfile", "Failed to retrieve routing profile: $errorMessage")
}
}
)
RoutingProfili modelini buradan bulabilirsiniz.
GetRoutingProfileFailure modelini buradan bulabilirsiniz.
- Routing Lookup and Installments: After the card is read, call the terminal.routingLookup() method. This method is used to obtain appropriate routing options based on the card's BIN number. Then, you can perform the transaction using the selected routing option and present installment options to the customer with the terminal.routingInstallments() method.
var amount = 100
var intentUUID = UUID.randomUUID().toString() // Unique intent UUID
var customerReferenceNumber = "" // [optional] reference number
terminal.routingLookup(
amount = amount,
intentUUID = intentUUID,
readCardListener = object : ReadCardListener {
// Card reading callbacks (implement as shown in previous examples)
},
routingLookupListener = object : RoutingLookupListener {
override fun onRoutingLookupCompleted(
response: RoutingResponse,
session: RoutingSession
) {
// Get the selected acquirer terminal ID
// select terminal based on issuer id matching (if available) to increase chances of successful routing execute, otherwise fallback to random selection
val issuerId = response.binInformation?.issuerId
val matchedTerminal =
terminalProfile.terminals.firstOrNull { terminal ->
terminal.terminalInfo.acquirerId.equals(
issuerId,
ignoreCase = true
)
}
val selectedTid: String? =
matchedTerminal?.terminalInfo?.acquirerTerminalId?.trim()
?: terminalProfile.terminals
.shuffled()
.firstOrNull()
?.terminalInfo
?.acquirerTerminalId
?.trim()
val sampleOptions = listOf(
InstallmentOption(installmentCount = 1, installmentPercentage = 0.0, paymentPlan = "1 x 100 = 100", totalCalculatedAmount = 100.0),
InstallmentOption(installmentCount = 2, installmentPercentage = 2.0, paymentPlan = "2 x 51 = 102", totalCalculatedAmount = 102.0),
InstallmentOption(installmentCount = 3, installmentPercentage = 3.0, paymentPlan = "3 x 34.33 = 103", totalCalculatedAmount = 103.0),
InstallmentOption(installmentCount = 6, installmentPercentage = 6.0, paymentPlan = "6 x 17.67 = 106", totalCalculatedAmount = 106.0),
InstallmentOption(installmentCount = 8, installmentPercentage = 2.0, paymentPlan = "2 x 51 = 102", totalCalculatedAmount = 102.0),
InstallmentOption(installmentCount = 9, installmentPercentage = 9.0, paymentPlan = "9 x 12.11 = 109", totalCalculatedAmount = 109.0)
)
terminal.routingInstallments(
session = session,
installmentOptions = sampleOptions,
selectedAcquirerTerminalId = selectedTid,
customerReferenceNumber = customerReferenceNumber,
sendTransactionListener = object : SendTransactionListener {
override fun onSendTransactionCompleted(
purchaseResponse: PurchaseResponse
) {
// Transaction completed
// PurchaseResponse returns all transactions with the same intent id
// purchaseResponse.status returns the status of the last transaction with the same intent id
Log.d("Transaction", "Transaction completed: $purchaseResponse")
// To get the approved receipt based on the country:
// purchaseResponse.getLastReceipt().getMadaReceipt() (Saudi Arabia)
// purchaseResponse.getLastReceipt().getEPXReceipt() (USA)
// purchaseResponse.getLastReceipt().getBKMReceipt() (Turkey)
}
override fun onSendTransactionFailure(
sendTransactionFailure: SendTransactionFailure
) {
// Transaction failed
Log.d("Transaction", "Transaction failure: $sendTransactionFailure")
val errorMessage = when (sendTransactionFailure) {
is SendTransactionFailure.TransactionFailure ->
"Routing execute failed for $selectedTid: ${sendTransactionFailure.message}"
else -> "Routing execute failed for $selectedTid."
}
// You can use the error message here
}
}
)
}
override fun onRoutingLookupFailure(routingLookupFailure: RoutingLookupFailure) {
val errorMsg = when (routingLookupFailure) {
is RoutingLookupFailure.Failure -> routingLookupFailure.message
}
Log.d("RoutingLookup", "Routing lookup failure: $errorMsg")
}
}
)
You can find the RoutingResponse model here.
You can find the RoutingSession model here.
You can find the PurchaseResponse model here.
You can find the SendTransactionFailure model here.
You can find the RoutingLookupFailure model here.
You can find the InstallmentOption model here.
Purchase Void
Voids a purchase transaction by providing the transaction ID.
var intentUUID = "intent-uuid"
terminal.purchaseVoid(
amount = amount,
scheme = scheme,
customerReferenceNumber = customerReferenceNumber,
intentUUID = intentUuid,
readCardListener = object : ReadCardListener {
override fun onReaderClosed() {
Log.d("PurchaseVoidOperation", "Card read successfully")
}
override fun onReaderDisplayed() {
Log.d("PurchaseVoidOperation", "Reader Displayed")
}
override fun onReadCardSuccess() {
Log.d("PurchaseVoidOperation", "Card read successfully")
}
override fun onReadCardFailure(readCardFailure: ReadCardFailure) {
Log.d("PurchaseVoidOperation", "Card read failure: $readCardFailure")
}
override fun onReaderWaiting() {
Log.d("PurchaseVoidOperation", "Reader waiting for card")
}
override fun onReaderReading() {
Log.d("PurchaseVoidOperation", "Reading card in progress")
}
override fun onReaderRetry() {
Log.d("PurchaseVoidOperation", "Reader retry needed")
}
override fun onPinEntering() {
Log.d("PurchaseVoidOperation", "PIN entry in progress")
}
override fun onReaderFinished() {
Log.d("PurchaseVoidOperation", "Card read completed")
}
override fun onReaderError(error: String?) {
Log.d("PurchaseVoidOperation", "Reader error: $error")
}
override fun onReadingStarted() {
Log.d("PurchaseVoidOperation", "Card read started")
}
},
sendPurchaseVoidListener = object : SendPurchaseVoidListener {
override fun onSendPurchaseVoidCompleted(purchaseVoidResponse: IntentResponseTurkey) {
Log.d("PurchaseVoidOperation", "Purchase void completed: $purchaseVoidResponse"
}
override fun onSendPurchaseVoidFailure(sendPurchaseVoidFailure: SendTransactionFailure) {
Log.d("PurchaseVoidOperation", "Purchase void failure: $sendPurchaseVoidFailure")
}
},
)
You can find the IntentResponseTurkey model here.
You can find the SendTransactionFailure model here.
Refund
Initiates a refund transaction by reading the card and sending the transaction.
var amount = 1000L
var intentUuid = "1234567890" // the same transaction UUID used in the purchase transaction "Intent ID"
var refundUUID = UUID.randomUUID().toString() // the refund UUID should be unique for each refund transaction and managed by the developer to communicate with the SDK
var customerReferenceNumber = "" //[optional] any number you want to add as a refrence
terminal.refund(
amount = amount,
scheme = null, // eg.PaymentScheme.VISA, specifying this as null will allow all schemes to be accepted
intentUUID = transactionUUID,
refundUUID = refundUUID,
customerReferenceNumber = customerReferenceNumber,
readCardListener = object : ReadCardListener {
// Card reading callbacks
// Same implementation as purchase
},
refundTransactionListener = object : RefundTransactionListener {
override fun onRefundTransactionCompleted(refundResponse: RefundResponse) {
// Handle successful refund
Log.d("Refund", "Refund success: $refundResponse.getLastReceipt()")
}
override fun onRefundTransactionFailure(refundTransactionFailure: RefundTransactionFailure) {
// Handle refund failure
Log.d("Refund", "Refund failure: $refundTransactionFailure")
}
}
)
Find RefundResponse model here.
Find RefundTransactionFailure model here.
Reverse Transaction
Reverses a transaction by providing the intent ID.
terminal.reverseTransaction(
intentId = intentUuid,
object : ReverseTransactionListener {
override fun onReverseTransactionCompleted(reverseResponse: ReverseResponse) {
CoroutineScope(Dispatchers.Main).launch {
Log.d("Transaction", "Transaction reversed: $reverseResponse")
}
}
override fun onReverseTransactionFailure(reverseTransactionFailure: ReverseTransactionFailure) {
Log.d("Transaction", "Transaction reverse failure: $reverseTransactionFailure")
}
}
)
Find ReverseResponse model here.
Find ReverseTransactionFailure model here.
Reconcile
Reconciles a terminal's unreconciled transactions.
terminal.reconcile(
reconcileListener = object : ReconcileListener {
override fun onReconcileCompleted(reconciliationReceiptsResponse: ReconciliationReceiptsResponse) {
// Handle success
Log.d("Reconcile", "Reconcile Success: $reconciliationReceiptsResponse")
}
override fun onReconcileFailure(reconcileFailure: ReconcileFailure) {
// Handle failure
Log.d("Reconcile", "Reconcile Failure: $reconcileFailure")
}
}
)
Find ReconciliationReceiptsResponse model here.
Find ReconcileFailure model here.
Add Tip to purchase
Add tip to purchase transaction
var amount = 100
var transactionUuid = "123456" // the purchase UUID that will add tip to
terminal.tipTransaction(
id = transactionUuid,
amount = amount,
tipTransactionListener = object : TipTransactionListener{
override fun onTipTransactionSuccess(purchaseResponse: PurchaseResponse) {
// Handle completed transaction
Log.d("Tip", "Tip completed: ${purchaseResponse.getLastReceipt()}" )
}
override fun onTipTransactionFailure(failure: TipTransactionFailure) {
// Handle transaction failure
Log.d("Tip", "Tip failure: $failure")
}
}
)
Find PurchaseResponse model here.
Find TipTransactionFailure model here.
Get Intents List
Retrieves a paginated list of intents.
terminal.getIntentsList(
page = 1,
pageSize = 10,
isReconciled = true, // do not specify it if all transactions are needed
startDate: "2025-09-27 18:00:00.000",
endDate: "2025-10-04 18:00:00.000",
customerReferenceNumber = "customer_reference_number", // Optional customer reference number
getIntentsListListener = object : GetIntentsListListener {
override fun onGetIntentsListSuccess(intentsList: IntentsListResponse) {
Log.d("intentsList", "intents List : $intentsList")
}
override fun onGetIntentsListFailure(error: GetIntentsListFailure) {
Log.d("intentsList", "intents List Failure: $error")
}
}
)
Find GetIntentsList model here.
Find GetIntentsListFailure model here.
Get Intent Details
Retrieves the details of a specific intent by providing the intent ID.
var intentID = "cc8b303d-b448-4ef9-80e6-e8e51bb43766"
terminal.getIntent(
intentID, // intent ID
getIntentListener = object : GetIntentListener {
override fun onGetIntentSuccess(intent: IntentDetails) {
Log.d("intent", "Intent Details: $intent")
}
override fun onGetIntentFailure(error: GetIntentFailure) {
Log.d("Transaction", "Transaction Details Failure: $error")
}
}
)
Find IntentDetails model here.
Find GetIntentsFailure model here.
Get Reconciliation List
Retrieves a paginated list of reconciliations.
terminal.getReconciliationList(
page = 1,
pageSize = 10,
startDate = null, //in timestamp format
endDate = null, //in timestamp format
getReconciliationListListener = object : GetReconciliationListListener {
override fun onGetReconciliationListSuccess(reconciliationListResponse: ReconciliationListResponse) {
// Handle success
Log.d("Reconciliation", "Reconciliation List: $reconciliationListResponse")
}
override fun onGetReconciliationListFailure(error: GetReconciliationListFailure) {
// Handle failure
Log.d("Reconciliation", "Reconciliation List Failure: $error")
}
}
)
Find ReconciliationListResponse model here.
Find GetReconciliationListFailure model here.
Get Reconciliation Details
Retrieves the details of a specific reconciliation by providing the reconciliation ID.
terminal.getReconciliation(
"reconciliation-id", // Reconciliation ID
getReconciliationListListener = object : GetReconciliationListener {
override fun onGetReconciliationSuccess(reconciliationReceiptsResponse: ReconciliationReceiptsResponse) {
// Handle success
Log.d("handleReadCard", "GetReconciliation success $reconciliationReceiptsResponse")
}
override fun onGetReconciliationFailure(error: GetReconciliationFailure) {
// Handle failure
Log.d("handleReadCard", "GetReconciliation failure $error")
}
}
)
Find ReconciliationReceiptsResponse model here.
Find GetReconciliationFailure model here.
Converting receipt to image for printing
You can convert the receipt to an image so you can print it.
val madaReceipt = receipt.getMadaReceipt()
val receiptWidth = 384 // this is the width of the image
val fontSize = 1 // this is the size of the receipt text, it is from 1 to 10
madaReceipt.toImage(this@MainActivity, 384, 1, { bitmap ->
if (bitmap != null) {
printBitmap(bitmap)
}
})
Part 7: Callback Listeners
Callback Listeners
SendOTPMobileListener
onSendOTPMobileSuccess
Called when OTP is successfully sent.
- Parameter:
OtpResponse
onSendOTPMobileFailure
Called when OTP sending fails.
- Parameter:
OTPMobileFailure
SendOTPEmailListener
onSendOTPEmailSuccess
Called when OTP is successfully sent to email.
- Parameter:
OtpResponse
onSendOTPEmailFailure
Called when OTP sending to email fails.
- Parameter:
OTPEmailFailure
VerifyMobileListener
onVerifyMobileSuccess
Called when OTP verification and user authentication succeed.
- Parameter:
User
onVerifyMobileFailure
Called when OTP verification fails.
- Parameter:
VerifyMobileFailure
VerifyEmailListener
onVerifyEmailSuccess
Called when email OTP verification succeeds.
- Parameter:
User
onVerifyEmailFailure
Called when email OTP verification fails.
- Parameter:
VerifyEmailFailure
GetTerminalsListener
onGetTerminalsSuccess
Called when terminals are successfully retrieved.
- Parameter:
List<TerminalConnection>
onGetTerminalsFailure
Called when fetching terminals fails.
- Parameter:
GetTerminalsFailure
ConnectTerminalListener
onConnectTerminalSuccess
Called when terminal connection is successful.
- Parameter:
Terminal
onConnectTerminalFailure
Called when terminal connection fails.
- Parameter:
ConnectTerminalFailure
ReadCardListener
onReadCardSuccess
Called when card reading is successful.
onReadCardFailure
Called when card reading fails.
- Parameter:
ReadCardFailure
onReaderDisplayed
Called when the reader is displayed.
onReaderClosed
Called when the reader is closed.
onReaderWaiting
Called when the reader is waiting for a card.
onReaderReading
Called when the reader is actively reading the card.
onReaderRetry
Called when the reader is retrying.
onPinEntering
Called when the reader prompts for PIN entry.
onReaderFinished
Called when the reader operation is completed.
onReaderError
Called when an error occurs in the reader.
- Parameter:
String
SendTransactionListener
onSendTransactionCompleted
Called when the transaction is successfully processed.
- Parameter:
TransactionResponse
onSendTransactionFailure
Called when the transaction fails.
- Parameter:
SendTransactionFailure
GetTransactionsListListener
onGetTransactionsListSuccess
Called when the transactions list is successfully retrieved.
- Parameter:
TransactionsResponse
onGetTransactionsListFailure
Called when fetching transactions list fails.
- Parameter:
GetTransactionsListFailure
GetTransactionListener
onGetTransactionSuccess
Called when a specific transaction's details are successfully retrieved.
- Parameter:
ReceiptsResponse
onGetTransactionFailure
Called when fetching transaction details fails.
- Parameter:
GetTransactionFailure
ReconcileListener
onReconcileCompleted
Called when transaction reconciliation is successful.
- Parameter:
ReconciliationReceiptsResponse
onReconcileFailure
Called when transaction reconciliation fails.
- Parameter:
ReconcileFailure
GetReconciliationListListener
onGetReconciliationListSuccess
Called when the reconciliation list is successfully retrieved.
- Parameter:
ReconciliationListResponse
onGetReconciliationListFailure
Called when fetching reconciliation list fails.
- Parameter:
GetReconciliationListFailure
GetReconciliationListener
onGetReconciliationSuccess
Called when specific reconciliation details are successfully retrieved.
- Parameter:
ReconciliationReceiptsResponse
onGetReconciliationFailure
Called when fetching reconciliation details fails.
- Parameter:
GetReconciliationFailure
CancelTransactionListener
onCancelTransactionSuccess
Called when transaction cancellation is successful.
- Parameter:
Canceled
onCancelTransactionFailure
Called when transaction cancellation fails.
- Parameter:
CancelTransactionFailure
RefundTransactionListener
onRefundTransactionCompleted
Called when refund transaction is successful.
- Parameter:
TransactionResponse
onRefundTransactionFailure
Called when refund transaction fails.
- Parameter:
RefundTransactionFailure