Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/netplusTeam/NetPOS/llms.txt

Use this file to discover all available pages before exploring further.

Overview

NetPOS implements multiple layers of security to protect sensitive payment data, user credentials, and transaction information. The application follows PCI-DSS compliance requirements for payment card industry security standards.

Encrypted Shared Preferences

Sensitive data is stored using Android’s EncryptedSharedPreferences with AES-256 encryption.
util/EncryptedPrefsUtils.kt:7
object EncryptedPrefsUtils {

    fun putString(context: Context, key: String, value: String) {
        val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)

        val sharedPreferences = EncryptedSharedPreferences.create(
            "encrypted_prefs_name",
            masterKeyAlias,
            context,
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        )

        sharedPreferences.edit().putString(key, value).apply()
    }

    fun getString(context: Context, key: String): String? {
        val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)

        val sharedPreferences = EncryptedSharedPreferences.create(
            "encrypted_prefs_name",
            masterKeyAlias,
            context,
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        )

        return sharedPreferences.getString(key, null)
    }
}
Encryption Algorithms Used:
  • AES256-GCM for value encryption (Galois/Counter Mode)
  • AES256-SIV for key encryption (Synthetic IV mode)
  • Keys managed by Android Keystore system

Authentication Flow

JWT-Based Authentication

NetPOS uses JSON Web Tokens (JWT) for user authentication and session management.
1

User Login

User enters email and password in AuthenticationActivity
2

Credential Validation

viewmodels/AuthViewModel.kt:79
private fun auth(username: String, password: String) {
    val credentials = JsonObject().apply {
        addProperty("username", username)
        addProperty("password", password)
    }
    
    stormApiService.userToken(credentials)
        .flatMap { response ->
            if (!response.success) {
                throw Exception("Login Failed")
            }
            val userToken = response.token
            Prefs.putString(PREF_USER_TOKEN, userToken)
            parseUserFromJWT(userToken)
        }
}
3

JWT Parsing

Extract user claims from JWT token:
val userTokenDecoded = JWT(userToken)
val user = User().apply {
    this.terminal_id = userTokenDecoded.getClaim("terminalId").asString()
    this.business_name = userTokenDecoded.getClaim("businessName").asString()
    this.netplus_id = userTokenDecoded.getClaim("stormId").asString()
    this.mid = userTokenDecoded.getClaim("mid").asString()
    this.partnerId = userTokenDecoded.getClaim("partnerId").asString()
}
4

Session Storage

Store user session and token:
Prefs.putString(PREF_USER, gson.toJson(user))
Prefs.putBoolean(PREF_AUTHENTICATED, true)

JWT Claims Structure

{
  "stormId": "merchant-unique-id",
  "terminalId": "terminal-identifier",
  "businessName": "Merchant Business Name",
  "mid": "merchant-id",
  "partnerId": "partner-identifier",
  "merchantId": "merchant-identifier",
  "netplusPayMid": "netpluspay-mid",
  "phoneNumber": "merchant-phone",
  "business_address": "merchant-address",
  "username": "user-email",
  "iat": 1646383548,
  "exp": 1646469948
}

API Security

Bearer Token Authorization

All authenticated API calls include Bearer token in headers:
di/Module.kt:92
@Named("zenithPayByTransferHeaderInterceptor")
fun providesZenithPayByTransferHeaderInterceptor(): Interceptor = Interceptor { chain ->
    val originalRequest = chain.request()
    val requestWithAuth = originalRequest.newBuilder()
        .addHeader("Authorization", "Bearer ${Prefs.getString(PREF_USER_TOKEN, "")}")
        .build()
    chain.proceed(requestWithAuth)
}

Network Security Configuration

  • Minimum TLS Version: TLS 1.2
  • Certificate Pinning: Configured for production endpoints
  • Clear Text Traffic: Enabled only for development builds
AndroidManifest.xml:29
<application
    android:usesCleartextTraffic="true"
    tools:targetApi="m">
Clear text traffic should be disabled in production builds.

Request Timeout Configuration

di/Module.kt:107
OkHttpClient().newBuilder()
    .connectTimeout(120, TimeUnit.SECONDS)
    .readTimeout(120, TimeUnit.SECONDS)
    .writeTimeout(120, TimeUnit.SECONDS)
    .retryOnConnectionFailure(true)
    .build()

Card Data Security

PAN Masking

Credit card numbers (PAN) are always masked before storage:
data class TransactionResponse(
    val maskedPan: String?,  // e.g., "506085******1234"
    val cardExpiry: String?, // Stored temporarily, cleared after print
    val cardHolder: String?,
    val cardLabel: String?
)
Full PAN is never stored in database or shared preferences. NIBSS EPMS library handles secure card reading.

NIBSS Key Management

Cryptographic keys for NIBSS transactions are managed securely:
val hostConfig = HostConfig(
    NetPosTerminalConfig.getTerminalId(),
    NetPosTerminalConfig.connectionData,
    NetPosTerminalConfig.getKeyHolder()!!,  // Encrypted keys
    NetPosTerminalConfig.getConfigData()!!
)

Secure Storage

User Credentials

Encrypted Storage (via EncryptedPrefsUtils):
  • Terminal configuration keys
  • Merchant sensitive identifiers
Regular SharedPreferences (non-sensitive):
  • User token (JWT)
  • User profile (business name, terminal ID)
  • App preferences and settings
// Secure storage usage
EncryptedPrefsUtils.putString(context, "terminal_master_key", masterKey)

// Regular storage
Prefs.putString(PREF_USER_TOKEN, userToken)

Database Encryption

The Room database is not encrypted by default. Consider using SQLCipher for database-level encryption in production:
implementation "net.zetetic:android-database-sqlcipher:4.5.4"
implementation "androidx.sqlite:sqlite-ktx:2.3.1"

Password Security

Password Reset Flow

viewmodels/AuthViewModel.kt:378
fun resetPassword() {
    val username = usernameLiveData.value
    if (username.isNullOrEmpty()) {
        _message.value = Event("Please enter your email address")
        return
    }

    val payload = JsonObject().apply {
        addProperty("username", username)
    }
    
    stormApiService.passwordReset(payload)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe { response, error ->
            response?.let {
                if (it.code() == 200) {
                    _message.value = Event("Password reset email sent to $username")
                }
            }
        }
}
Password reset is handled server-side. Client only initiates the request.

Permissions

NetPOS requests the following sensitive permissions:
AndroidManifest.xml:6
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  • Location: Geolocation tagging for transactions (fraud prevention)
  • Camera: QR code scanning for contactless payments
  • Phone State: Device identification for terminal registration
  • Storage: Receipt PDF generation and export
  • Internet: API communication for transaction processing

Logging Security

Debug vs Release Logging

app/NetPosApp.kt:26
override fun onCreate() {
    super.onCreate()
    if (BuildConfig.DEBUG) {
        Timber.plant(Timber.DebugTree())
    }
}
Sensitive data logging is disabled in release builds. Use Timber instead of Log for automatic filtering.

HTTP Logging

di/Module.kt:86
@Named("loginInterceptor")
fun providesLoginInterceptor(): Interceptor = 
    HttpLoggingInterceptor().apply {
        setLevel(HttpLoggingInterceptor.Level.BODY)
    }
In production, change logging level to BASIC or NONE to prevent exposure of sensitive data in logs.

Firebase Security

Cloud Messaging

app/NetPosApp.kt:59
Firebase.messaging.subscribeToTopic("netpos_campaign")
    .addOnCompleteListener { task ->
        if (task.isSuccessful) {
            Prefs.putBoolean("notification_campaign", true)
        }
    }

Push Notification Service

services/MyFirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        // Validate message source before processing
        remoteMessage.data.isNotEmpty().let {
            // Process secure notification
        }
    }
}

Security Best Practices

Input Validation

Validate all user inputs before API calls:
if (!Patterns.EMAIL_ADDRESS.matcher(username).matches()) {
    _message.value = Event("Please enter a valid email")
    return
}

Error Handling

Never expose sensitive error details to UI:
error?.let {
    _message.value = Event("Login failed. Please try again.")
    Timber.e(it) // Log full error securely
}

Session Management

Implement token expiration and refresh:
  • JWT tokens expire after 24 hours
  • Force re-authentication on token expiry

Secure Communication

All API calls use HTTPS with certificate validation

Code Obfuscation

build.gradle:86
release {
    minifyEnabled false
    shrinkResources false
    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
Security Risk: Code obfuscation is currently disabled. Enable ProGuard/R8 for production:
minifyEnabled true
shrinkResources true

Security Checklist

1

Enable code obfuscation

Set minifyEnabled true in release build
2

Disable clear text traffic

Remove usesCleartextTraffic="true" for production
3

Implement certificate pinning

Pin SSL certificates for critical API endpoints
4

Enable database encryption

Integrate SQLCipher for encrypted Room database
5

Reduce logging in production

Set HTTP logging to NONE in release builds
6

Implement biometric authentication

Add fingerprint/face unlock for app access

Architecture

Application architecture overview

Models

Secure data model definitions