Developers Guides Android SoftPOS SDK

Android SoftPOS SDK

Turn Android devices into payment terminals with the Surfboard Android SoftPOS SDK. Complete integration guide from setup to production.

AndroidKotlinSoftPOSIn-StoreNFC

Add this to your codebase

Paste it into Claude Code, Codex, Cursor or any coding agent. It points the agent at this guide in machine-readable form, so it writes against the real API instead of a guess. Wire up the MCP server once and it can read the rest of the platform too.

Overview

The Surfboard Android SoftPOS SDK enables Android applications to process tap-to-pay transactions using NFC-enabled devices. It provides terminal management, transaction processing, and security — supporting seamless integration for contactless payments.

The SDK is distributed via Surfboard-hosted Maven repositories and ships as separate debug and release variants.

Note: The canonical reference implementation is the Android GAP Example App, which demonstrates the full integration flow.

Prerequisites

Before starting integration, confirm you have the following:

Development Environment

  • Android Studio 4.1 or higher
  • Minimum SDK: API level 29 (Android 10.0)
  • Java: 11 or higher
  • Kotlin: 2.0.21 or higher

Surfboard Account & Credentials

  • Registered Surfboard Partner account
  • Active merchant setup with at least one store
  • Surfboard-issued SoftPOS configuration values:
    • connectionBlob — provided by Surfboard during onboarding
    • tmsPublicKey — retrieved from the Developer Portal after registering your SDK app under Console > SDK Apps
  • Merchant and store identifiers:
    • merchantId
    • storeId
  • Access to the Client Auth Token API for obtaining bearer tokens

Tip: You do not generate connectionBlob or tmsPublicKey yourself. Surfboard shares them as part of onboarding. If you are missing any values, contact Surfboard integrations support.

SDK Setup & Installation

1. Add softpos.properties

Create a softpos.properties file in your Android project root (same level as settings.gradle.kts):

softpos1.mavenurl = MAVEN_URL1
softpos1.mavenusername = MAVEN_USERNAME1
softpos1.mavenpassword = MAVEN_PASSWORD1

softpos2.mavenurl = MAVEN_URL2
softpos2.mavenusername = MAVEN_USERNAME2
softpos2.mavenpassword = MAVEN_PASSWORD2

Add it to .gitignore:

softpos.properties

2. Configure Gradle Repositories

In your root build.gradle.kts, load the properties and configure repositories:

import java.util.Properties
import java.io.FileInputStream

val localProperties = Properties()
val localPropertiesFile = rootProject.file("softpos.properties")

if (localPropertiesFile.exists()) {
    localProperties.load(FileInputStream(localPropertiesFile))
}

allprojects {
    repositories {
        google()
        mavenCentral()
        mavenLocal()

        maven {
            url = uri(localProperties.getProperty("softpos1.mavenurl"))
            credentials {
                username = localProperties.getProperty("softpos1.mavenusername")
                password = localProperties.getProperty("softpos1.mavenpassword")
            }
        }

        maven {
            url = uri(localProperties.getProperty("softpos2.mavenurl"))
            credentials {
                username = localProperties.getProperty("softpos2.mavenusername")
                password = localProperties.getProperty("softpos2.mavenpassword")
            }
            authentication {
                create<BasicAuthentication>("basic")
            }
        }
    }
}

3. Add Dependencies

In gradle/libs.versions.toml:

[versions]
softposSDK = "1.1.4"

[libraries]
softposSDKDebug = { module = "com.surfboardpayments:gapsdk-debug", version.ref = "softposSDK" }
softposSDKRelease = { module = "com.surfboardpayments:gapsdk", version.ref = "softposSDK" }

In app/build.gradle.kts:

dependencies {
    debugImplementation(libs.softposSDKDebug)
    releaseImplementation(libs.softposSDKRelease)
}

4. App Module Configuration

android {
    compileSdk = 35

    defaultConfig {
        applicationId = "com.your.app.id"
        minSdk = 29
        targetSdk = 35
        multiDexEnabled = true
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }

    kotlinOptions {
        jvmTarget = "11"
    }
}

5. Android Manifest

Add the required permissions and features to AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-feature android:name="android.hardware.nfc" android:required="true"/>

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.NFC" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.VIBRATE" />
</manifest>

6. Verify Installation

import com.surfboardpayments.gapsdk.Gap
// If this import compiles without errors, the SDK is properly installed

Credentials & SDK Initialization

1. Create Logger

The SDK requires a logger implementation:

class AppLogger : GapLogger() {
    override fun addDebugLog(log: String) {
        Log.d("SoftPOS", log)
    }

    override fun addErrorLog(log: String) {
        Log.e("SoftPOS", log)
    }

    override fun addLog(log: String) {
        Log.i("SoftPOS", log)
    }
}

2. Create SDK Instance

val softposSDK = Gap(
    logger = AppLogger(),
    gapCredentials = GapCredentials(
        connectionBlob = BuildConfig.connectionBlob,
        versionNumber = "YOUR_APP_VERSION",
        tmsPublicKey = "YOUR_TMS_PUBLIC_KEY",
        merchantId = BuildConfig.merchantId,
        storeId = BuildConfig.storeId,
        applicationBundleId = "YOUR_BUNDLE_ID"
    ),
    context = applicationContext
)

3. Set Authentication Token

Set the bearer token before any SDK operations. Tokens are fetched from your backend, which calls the Client Auth Token API:

val bearerToken = fetchTokenFromBackend()
softposSDK.setAuthToken(bearerToken)

Warning: Bearer tokens expire every 60 minutes. Implement automatic refresh. Never generate tokens on the device — always fetch them from your backend.

4. Subscribe to Events

softposSDK.subscribeToPublicEvents { wrapper ->
    when (wrapper.event) {
        GapPublicEvent.INITIALIZED -> { /* SDK ready */ }
        GapPublicEvent.TRANSACTION_APPROVED -> { /* Payment approved */ }
        GapPublicEvent.TRANSACTION_DECLINED -> { /* Payment declined */ }
        // Handle other events
    }
}

Terminal Lifecycle & Payment Flow

Step 1: Register Terminal

One-time operation per device. Returns a terminalId used for creating orders:

val result = softposSDK.registerTerminal().await()
result.fold(
    { error -> Log.e("SoftPOS", "Registration failed: ${error.explainError()}") },
    { terminalId -> Log.i("SoftPOS", "Terminal registered: $terminalId") }
)

Step 2: Initialize SDK

softposSDK.initializeGapSDK()
// Wait for INITIALIZED event

Step 3: Fetch Prerequisites (First Run Only)

Downloads merchant branding, EMV configs, and currency data:

softposSDK.fetchPrerequisites()

Note: Only required on first run. Skip on subsequent launches unless you change environment or credentials.

Step 4: Open Terminal Session

Call every time the app comes to the foreground:

softposSDK.openTerminal().onRight { _ ->
    Log.i("SoftPOS", "Terminal session opened")
}

Step 5: Prepare for Transaction

Prepares transaction keys. Valid for 120 seconds — call right before starting a payment:

val result = softposSDK.getReadyForTransaction().await()

Step 6: Create Order

Create an order via the Surfboard Orders API from your backend. Pass the amount in minor units, the ISO numeric currency code, and the terminalId.

Step 7: Start Transaction

val result = softposSDK.startTransaction(
    GapInitiatePayment(
        amount = amountInMinorUnits,
        type = "PURCHASE",
        orderId = orderId,
        currency = Currency.getInstance("SEK")
    )
).await()

result.fold(
    { error -> Log.e("SoftPOS", "Transaction failed: ${error.explainError()}") },
    { paymentId -> Log.i("SoftPOS", "Transaction started: $paymentId") }
)

Transaction Events

During a transaction, handle these events to drive your UI:

when (event) {
    GapPublicEvent.TRANSACTION_STARTED -> { /* Show payment UI */ }
    GapPublicEvent.PRESENT_CARD -> { /* "Tap your card" */ }
    GapPublicEvent.HOLD_CARD -> { /* "Hold card still" */ }
    GapPublicEvent.CARD_READ -> { /* Card read successfully */ }
    GapPublicEvent.TRANSACTION_ENTER_PIN -> { /* PIN screen appears */ }
    GapPublicEvent.TRANSACTION_AUTHORIZING -> { /* Processing */ }
    GapPublicEvent.TRANSACTION_APPROVED -> { /* Show success */ }
    GapPublicEvent.TRANSACTION_DECLINED -> { /* Show declined */ }
    GapPublicEvent.TRANSACTION_COMPLETED -> { /* Show receipt */ }
    GapPublicEvent.TRANSACTION_CANCELLED -> { /* Clean up */ }
}

After displaying the receipt, notify the SDK:

softposSDK.sendCompletedEvent(paymentId, true)

Production Requirements

Before releasing your app:

  1. Google Play Services must be enabled
  2. Play Integrity must be enabled
  3. Application signing must match the registered SHA-256 hash
  4. Debug mode must be disabled
  5. Card scheme logos must be displayed during transactions (Visa, Mastercard)
  6. Battery level recommended above 10%
  7. Screen recording not allowed during transactions

Reference

Ready to get started?

Create a sandbox account and start building your integration today.