MineSec Pay Application
White-label Integration
Quickstart

Quickstart

This guide walks through setting up your Application class, MainActivity, Screen Providers, and implementing your first screen.


Step 1: Application Class

Create your Application class to initialize the environment and Koin dependency injection:

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
 
        // Configure the environment
        EnvironmentConfig.initialize(
            EnvironmentConfig(
                environment = MsaEnvironment.Staging,  // Use MsaEnvironment.Production for release
                licenseFileName = "your-license-file.license"
            )
        )
 
        // Initialize Koin with your screen providers
        initKoin {
            androidLogger()
            androidContext(this@MyApplication)
            modules(
                module {
                    single<ScreenProviders> { providers }
                }
            )
        }
    }
}

Step 2: Screen Providers

Create a ScreenProviders implementation that maps each screen to your Composable UI. This is the bridge between MSA Core and your app:

val providers = object : ScreenProviders {
 
    override val statusBarColor: @Composable (AppScreen) -> Color = { screenType ->
        when (screenType) {
            LandingScreen -> MsaTheme.colors.mutedForeground
            LoginScreen -> MsaTheme.colors.background
            HomeScreen -> MsaTheme.colors.accent
            SaleScreen, PreAuthScreen, CardRefundScreen -> MsaTheme.colors.primary
            else -> MsaTheme.colors.background
        }
    }
 
    // Core
    override val loginScreen = ComposableScreen { s, a -> LoginScreenUI(s, a) }
    override val landingScreen = ComposableScreen { s, a -> LandingScreenUI(s, a) }
    override val homeScreen = ComposableScreen { s, a -> HomeScreenUI(s, a) }
 
    // Transactions
    override val historyScreen = ComposableScreen { s, a -> HistoryScreen(s, a) }
    override val trxDetailsScreen = ComposableScreen { s, a -> TransactionDetailsScreen(s, a) }
    override val trxResultScreen = ComposableScreen { s, a -> TransactionResultScreen(s, a) }
    override val trxReceiptScreen = ComposableScreen { s, a -> ReceiptUIScreen(s, a) }
 
    // Payments
    override val saleScreen = ComposableScreen { s, a -> SaleScreen(s, a) }
    override val preAuthScreen = ComposableScreen { s, a -> PreAuthScreen(s, a) }
    override val cardRefundScreen = ComposableScreen { s, a -> CardRefundScreen(s, a) }
    override val paymentSelectionScreen = ComposableScreen { s, a -> PaymentSelectionScreen(s, a) }
    override val paymentQRcodeSelectionScreen = ComposableScreen { s, a -> PaymentQRSelectionScreen(s, a) }
 
    // Settlement
    override val settlementScreen = ComposableScreen { s, a -> SettlementBatchScreen(s, a) }
    override val settlementDetailsScreen = ComposableScreen { s, a -> SettlementBatchDetailsScreen(s, a) }
    override val settlementResultScreen = ComposableScreen { s, a -> SettlementBatchResultScreen(s, a) }
 
    // Settings
    override val settingsScreen = ComposableScreen { s, a -> MainSettingScreen(s, a) }
    override val shopInfoScreen = ComposableScreen { s, a -> ShopInfoUIScreen(s, a) }
 
    // App-to-App
    override val activationApp2AppScreen = ComposableScreen { s, a -> App2AppActivationScreen(s, a) }
    override val saleApp2AppScreen = ComposableScreen { s, a -> App2AppSaleScreen(s, a) }
    override val queryApp2AppScreen = ComposableScreen { s, a -> App2AppQueryScreen(s, a) }
    override val settlementApp2AppScreen = ComposableScreen { s, a -> App2AppSettlementScreen(s, a) }
    override val splashApp2AppScreen = ComposableScreen { s, a -> App2AppSplashScreen(s, a) }
}

Step 3: MainActivity

Your main activity extends BaseMsaActivity which handles SDK lifecycle and dependency injection:

class MainActivity : BaseMsaActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            App(screenProviders = providers)
        }
    }
 
    override fun provideAdditionalKoinModules(): List<Module> {
        return listOf(
            provideNFCManager()
        )
    }
}
ComponentPurpose
BaseMsaActivityManages SDK lifecycle, DI setup, and core functionality
App()Entry-point Composable that handles navigation and state distribution
provideNFCManager()Provides NFC card reading capability

Step 4: Implement a Screen — Login Example

Each screen receives a State object with data to display and a triggerAction callback to send user events back to MSA Core. Here's a real-world Login screen implementation:

@Composable
fun LoginScreenUI(
    state: LoginState,
    triggerAction: (LoginAction) -> Unit
) = MsaUiStateHandler(isLoading = state.isLoading, state.deviceErrorState) {
 
    var textFieldValue by remember { mutableStateOf(TextFieldValue("")) }
    val isButtonEnabled = textFieldValue.text.filter { it.isDigit() }.length == 12
 
    // Format passcode as XXXX-XXXX-XXXX
    fun formatWithHyphens(text: String): String {
        val digitsOnly = text.filter { it.isDigit() }.take(12)
        return buildString {
            digitsOnly.forEachIndexed { index, char ->
                append(char)
                if ((index + 1) % 4 == 0 && index != digitsOnly.lastIndex) append('-')
            }
        }
    }
 
    Scaffold(
        modifier = Modifier
            .background(MsaTheme.colors.background)
            .statusBarsPadding()
            .navigationBarsPadding(),
        bottomBar = {
            Column(
                modifier = Modifier
                    .background(MsaTheme.colors.background)
                    .fillMaxWidth()
                    .padding(horizontal = MsaTheme.spacing.md)
                    .padding(bottom = MsaTheme.spacing.md)
            ) {
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.spacedBy(MsaTheme.spacing.md),
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    // Login button
                    MSAButton(
                        text = "Login",
                        enabled = isButtonEnabled,
                        onClick = {
                            triggerAction(
                                LoginAction.LoginClicked(
                                    textFieldValue.text.replace("-", "")
                                )
                            )
                        },
                        modifier = Modifier.weight(1f)
                    )
 
                    // Biometric button (shown if supported and enabled)
                    if (state.isBiometricSupported && state.isBiometricEnabled) {
                        Box(
                            modifier = Modifier
                                .size(MsaTheme.minTouchSize.lg)
                                .clip(MsaTheme.shapes.large)
                                .background(MsaTheme.colors.primary)
                                .clickable { triggerAction(LoginAction.BioMetricClicked) },
                            contentAlignment = Alignment.Center
                        ) {
                            Image(
                                painter = painterResource(R.drawable.fingerprint_icon_button),
                                contentDescription = "Biometric Login",
                                modifier = Modifier.size(MsaTheme.iconSize.xl)
                            )
                        }
                    }
                }
            }
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier
                .padding(paddingValues)
                .fillMaxSize()
                .background(MsaTheme.colors.background)
                .verticalScroll(rememberScrollState())
                .padding(horizontal = MsaTheme.spacing.md)
        ) {
            Text(
                text = "Passcode Login",
                color = MsaTheme.colors.foreground,
                style = MsaTheme.typography.headlineSmall,
            )
 
            Spacer(Modifier.height(MsaTheme.spacing.xl))
 
            // Passcode input with XXXX-XXXX-XXXX formatting
            OutlinedTextField(
                value = textFieldValue,
                onValueChange = { newValue ->
                    val formatted = formatWithHyphens(newValue.text)
                    textFieldValue = TextFieldValue(
                        text = formatted,
                        selection = TextRange(formatted.length)
                    )
                },
                modifier = Modifier.fillMaxWidth(),
                placeholder = { Text("XXXX-XXXX-XXXX") },
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
                singleLine = true
            )
 
            // Error display
            state.errorState?.exception?.errorMessage?.let {
                Text(
                    text = it,
                    style = MsaTheme.typography.labelMedium,
                    color = MsaTheme.colors.error
                )
            }
        }
    }
}

Key Patterns in This Example

PatternDetails
State readingstate.isLoading, state.isBiometricSupported, state.errorState
ActionsLoginAction.LoginClicked(passcode), LoginAction.BioMetricClicked
MsaUiStateHandlerWraps your UI to handle loading overlays and device errors
MsaThemeAccess SDK theme colors, spacing, typography, and shapes

Next Steps