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:

  1. Android Package Name

    • This is the unique identifier for your Android application.
    • Example: com.yourcompany.yourapp
  2. 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:

  1. 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.
  2. 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
  3. Pass the Project Number in the Builder:
    • Use the googleCloudProjectNumber method in the builder to pass the project number.

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:

  1. Create a Huawei Developer Account:
  2. Create an App:
    • Go to AppGallery Connect.
    • Create a new app or use an existing one.
  3. Enable Safety Detect:
    • Go to the AppGallery Connect console.
    • Navigate to Develop > Security Detection.
    • Enable Safety Detect.
  4. 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.
  5. Pass the API Key in the Builder:
    • Use the safetyDetectApiKey method in the builder to pass the Safety Detect API key.

Configuring the Secure Maven Repository / Dependencies

For the ReaderCore library, you can include the following configuration in your root-level settings.gradle(.kts) file:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven(url = "https://developer.huawei.com/repo/")
        maven {
            url = uri("https://gitlab.com/api/v4/projects/37026421/packages/maven")
            credentials(HttpHeaderCredentials::class) {
                name = "Private-Token"
                value = "nearpayPosGitlabReadToken"
            }
            authentication {
                create<HttpHeaderAuthentication>("header")
            }
        }
    }
}

if your project is using Groovy settings.gradle, you can use the following configuration:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url "https://developer.huawei.com/repo/" }
        maven {
            url "https://gitlab.com/api/v4/projects/37026421/packages/maven"
            credentials(HttpHeaderCredentials) {
                name = "Private-Token"
                value = "nearpayPosGitlabReadToken"
            }
            authentication {
                header(HttpHeaderAuthentication)
            }
        }
    }
}

You should include the following dependencies in your Module level build.gradle file:

implementation("io.nearpay:terminalsdk-debug:0.0.38")
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 and the versionName to 75 in your build.gradle file:

    android {
        defaultConfig {
            applicationId = "com.example.terminalSDK1"
            minSdk = 28
            targetSdk = 30
            versionCode = 1
            versionName = "75"
        }
    }

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
...

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
        .googleCloudProjectNumber(12345678)  // Set Google Cloud project number
        .huaweiSafetyDetectApiKey("your_api_key")  // Set Huawei API key
        .country(Country.SA)  // Set country
        .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_LOCATION
  • Manifest.permission.ACCESS_NETWORK_STATE
  • Manifest.permission.INTERNET
  • Manifest.permission.READ_PHONE_STATE
  • Manifest.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("+966532996473")
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 = "+966532996473",
    code = "123456"
)
nearpay.verify(loginData, object : VerifyMobileListener {
    override fun onVerifyMobileSuccess(user: User) {
        Log.d("Login", " onVerifyMobileSuccess ($user.name)")
    }
    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)")
    }
    override fun onVerifyEmailFailure(verifyEmailFailure: VerifyEmailFailure) {
        Log.d("Login", " onVerifyEmailFailure ($verifyMobileFailure)")
    }
})

JWT Verification

val loginData = JWTLoginData(
    jwt = "jwt_token"
)
nearpay.jwtLogin(loginData, object : JWTLoginListener {
            override fun onJWTLoginSuccess(terminal: Terminal) {
                Log.d("Login", "JWT Login success + ${terminal.terminalUUID}")
            }

            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(uuid)
} 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 firstTerminal: Terminal
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>) {
            // Handle success
            terminalsConnection.firstOrNull()?.let { terminal ->
                // Access terminal data
                val terminalName = terminal.terminalConnectionData.name
                // Update UI or store reference
                firstTerminal = terminal
            }
        }

        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

firstTerminal.connect(
    activity = this,
    listener = object : ConnectTerminalListener {
        override fun onConnectTerminalSuccess(terminal: Terminal) {
            // Terminal connected successfully
            // Store terminal instance for future operations
            terminalInstance = terminal
        }

        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 tid: String = "058594894-4545945-45454"
val terminalInstance = nearpay.getTerminal(
    activity = this,
    tid = tid
)
} 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 transactionUUID = UUID.randomUUID().toString()
terminal.purchase(
    amount = amount,
    scheme = null, // eg.PaymentScheme.VISA, specifying this as null will allow all schemes to be accepted
    transactionUUID = transactionUUID // the transaction UUID should be unique for each transaction and managed by the developer to communicate with the SDK
    readCardListener = object : ReadCardListener {
        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 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 onSendTransactionSuccess(response: TransactionResponse) {
            // Handle successful transaction
            Log.d("Transaction", "Transaction success: $response")
        }

        override fun onSendTransactionFailure(failure: SendTransactionFailure) {
            // Handle transaction failure
            Log.d("Transaction", "Transaction failure: $failure")
        }
    }
)

Refund

Initiates a refund transaction by reading the card and sending the transaction.

    var amount = 1000L
    var transactionUUID = "1234567890"
    var refundUUID = UUID.randomUUID().toString()

terminal.refund(
    amount = amount,
    scheme = null, // eg.PaymentScheme.VISA, specifying this as null will allow all schemes to be accepted
    transactionUUID = transactionUUID, // the same transaction UUID used in the purchase transaction
    refundUUID = refundUUID, // the refund UUID should be unique for each refund transaction and managed by the developer to communicate with the SDK
    readCardListener = object : ReadCardListener {
        // Card reading callbacks
        // Same implementation as purchase
    },
    refundTransactionListener = object : RefundTransactionListener {
        override fun onRefundTransactionSuccess(transactionResponse: TransactionResponse) {
            // Handle successful refund
            Log.d("Refund", "Refund success: $transactionResponse")
        }

        override fun onRefundTransactionFailure(refundTransactionFailure: RefundTransactionFailure) {
            // Handle refund failure
            Log.d("Refund", "Refund failure: $refundTransactionFailure")
        }
    }
)

Cancel Transaction

Cancels a transaction by providing the transaction ID.

var transactionUUID = "transaction-uuid"

terminal.cancelTransaction(
    id = transactionUUID, // Transaction ID
    cancelTransactionListener = object : CancelTransactionListener {
        override fun onCancelTransactionSuccess(canceledState : CanceledState) {
            Log.d("Cancel", "Transaction Cancelled: ${canceledState.canceled}")
        }

        override fun onCancelTransactionFailure(cancelTransactionFailure: CancelTransactionFailure) {
            Log.d("Cancel", "Transaction Cancelled Failure: $cancelTransactionFailure")
        }
    }
)

Reverse Transaction

Reverses a transaction by providing the transaction ID.

var transactionUUID = "transaction-uuid"
terminal.reverseTransaction(
        id = transactionUUID,
        object : ReverseTransactionListener {
            override fun onReverseTransactionSuccess(transactionResponse: TransactionResponse) {
                CoroutineScope(Dispatchers.Main).launch {
                    Log.d("Transaction", "Transaction reversed: $transactionResponse")
                }
            }

            override fun onReverseTransactionFailure(reverseTransactionFailure: ReverseTransactionFailure) {
                    Log.d("Transaction", "Transaction reverse failure: $reverseTransactionFailure")

            }

        }
    )

Get Transaction Details

Retrieves the details of a specific transaction by providing the transaction ID.

var transactionID = "transaction-id"
terminal.getTransaction(
    transactionID, // Transaction ID
    getTransactionListener = object : GetTransactionListener {
        override fun onGetTransactionSuccess(transaction: ReceiptsResponse) {
            Log.d("Transaction", "Transaction Details: $transaction")
        }

        override fun onGetTransactionFailure(error: GetTransactionFailure) {
            Log.d("Transaction", "Transaction Details Failure: $error")
        }
    }
)

Get Transactions List

Retrieves a paginated list of transactions.

terminal.getTransactionsList(
    page = 1,
    pageSize = 10,
    isReconciled = true, // do not specify it if all transactions are needed
    startDate = 1733961600000, endDate = 1735084800000, // Optional date range in timestamp format
    customerReferenceNumber = "customer_reference_number", // Optional customer reference number
    getTransactionsListListener = object : GetTransactionsListListener {
        override fun onGetTransactionsListSuccess(transactionsList: TransactionsResponse) {
            Log.d("Transactions", "Transactions List: $transactionsList")
        }

        override fun onGetTransactionsListFailure(error: GetTransactionsListFailure) {
            Log.d("Transactions", "Transactions List Failure: $error")
        }
    }
)

Reconcile Transactions

Reconciles a terminal's unreconciled transactions.

terminal.reconcile(
    reconcileListener = object : ReconcileTransactionListener {
        override fun onReconcileTransactionSuccess(reconciliationReceiptsResponse: ReconciliationReceiptsResponse) {
            // Handle success
            Log.d("Reconcile", "Reconcile Success: $reconciliationReceiptsResponse")
        }

        override fun onReconcileTransactionFailure(reconcileTransactionFailure: ReconcileTransactionFailure) {
            // Handle failure
            Log.d("Reconcile", "Reconcile Failure: $reconcileTransactionFailure")
        }
    }
)

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")
        }
    }
)

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")
        }
    }
)

Part 7: Callback Listeners

Callback Listeners

SendOTPMobileListener

  • onSendOTPMobileSuccess(OtpResponse otpResponse)
    Called when OTP is successfully sent.

  • onSendOTPMobileFailure(OTPMobileFailure otpMobileFailure)
    Called when OTP sending fails.

SendOTPEmailListener

  • onSendOTPEmailSuccess(OtpResponse otpResponse)
    Called when OTP is successfully sent to email.

  • onSendOTPEmailFailure(OTPEmailFailure otpEmailFailure)
    Called when OTP sending to email fails.

VerifyMobileListener

  • onVerifyMobileSuccess(User user)
    Called when OTP verification and user authentication succeed.

  • onVerifyMobileFailure(VerifyMobileFailure verifyMobileFailure)
    Called when OTP verification fails.

VerifyEmailListener

  • onVerifyEmailSuccess(User user)
    Called when email OTP verification succeeds.

  • onVerifyEmailFailure(VerifyEmailFailure verifyEmailFailure)
    Called when email OTP verification fails.

GetTerminalsListener

  • onGetTerminalsSuccess(List<TerminalConnection> terminalsConnection)
    Called when terminals are successfully retrieved.

  • onGetTerminalsFailure(GetTerminalsFailure getTerminalsFailure)
    Called when fetching terminals fails.

ConnectTerminalListener

  • onConnectTerminalSuccess(Terminal terminal)
    Called when terminal connection is successful.

  • onConnectTerminalFailure(ConnectTerminalFailure connectTerminalFailure)
    Called when terminal connection fails.

ReadCardListener

  • onReadCardSuccess()
    Called when card reading is successful.

  • onReadCardFailure(ReadCardFailure readCardFailure)
    Called when card reading fails.

  • 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(String error)
    Called when an error occurs in the reader.

SendTransactionListener

  • onSendTransactionSuccess(TransactionResponse transactionResponse)
    Called when the transaction is successfully processed.

  • onSendTransactionFailure(SendTransactionFailure sendTransactionFailure)
    Called when the transaction fails.

GetTransactionsListListener

  • onGetTransactionsListSuccess(TransactionsResponse transactionsList)
    Called when the transactions list is successfully retrieved.

  • onGetTransactionsListFailure(GetTransactionsListFailure error)
    Called when fetching transactions list fails.

GetTransactionListener

  • onGetTransactionSuccess(ReceiptsResponse transaction)
    Called when a specific transaction's details are successfully retrieved.

  • onGetTransactionFailure(GetTransactionFailure error)
    Called when fetching transaction details fails.

ReconcileTransactionListener

  • onReconcileTransactionSuccess(ReconciliationReceiptsResponse reconciliationReceiptsResponse)
    Called when transaction reconciliation is successful.

  • onReconcileTransactionFailure(ReconcileTransactionFailure reconcileTransactionFailure)
    Called when transaction reconciliation fails.

GetReconciliationListListener

  • onGetReconciliationListSuccess(ReconciliationListResponse reconciliationListResponse)
    Called when the reconciliation list is successfully retrieved.

  • onGetReconciliationListFailure(GetReconciliationListFailure error)
    Called when fetching reconciliation list fails.

GetReconciliationListener

  • onGetReconciliationSuccess(ReconciliationReceiptsResponse reconciliationReceiptsResponse)
    Called when specific reconciliation details are successfully retrieved.

  • onGetReconciliationFailure(GetReconciliationFailure error)
    Called when fetching reconciliation details fails.

CancelTransactionListener

  • onCancelTransactionSuccess(Canceled canceled)
    Called when transaction cancellation is successful.

  • onCancelTransactionFailure(CancelTransactionFailure cancelTransactionFailure)
    Called when transaction cancellation fails.

RefundTransactionListener

  • onRefundTransactionSuccess(TransactionResponse transactionResponse)
    Called when refund transaction is successful.

  • onRefundTransactionFailure(RefundTransactionFailure refundTransactionFailure)
    Called when refund transaction fails.