Skip to main content

Requirements

  • Android minSdk 26 or higher
  • Kotlin 2.0+
  • Jetpack Compose

Installation

Add the Benny SDK dependency to your app-level build.gradle.kts:
build.gradle.kts
dependencies {
    implementation("com.bennyapi.sdk:benny-sdk:0.1.0")
}
This single dependency includes both the UI components and the shared core—no additional modules required.

Initialize the SDK

Call BennySdk.initialize() once at app startup, before rendering any SDK components. Your Application.onCreate() or main Activity.onCreate() are both good places for this.
import com.bennyapi.sdk.core.BennySdk
import com.bennyapi.sdk.core.BennySdk.BennySdkConfig
import com.bennyapi.sdk.core.BennySdk.BennySdkConfig.Environment

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        BennySdk.initialize(
            BennySdkConfig(
                organizationId = "your-organization-id",
                environment = Environment.SANDBOX, // or Environment.PRODUCTION
            ),
        )

        // ...
    }
}
organizationId
string
required
Your Benny organization ID.
environment
Environment
default:"PRODUCTION"
Environment.SANDBOX for testing, Environment.PRODUCTION for live transactions.

Card Tokenization

The CardNumberInput composable renders a secure card number field. Card data never touches your servers—it’s tokenized directly by the SDK.
import com.bennyapi.sdk.ui.CardNumberInput
import com.bennyapi.sdk.ui.rememberCardNumberInputController

@Composable
fun CardCollectionScreen(sessionToken: String) {
    var isLoading by remember { mutableStateOf(false) }
    var error by remember { mutableStateOf<String?>(null) }
    var tokenId by remember { mutableStateOf<String?>(null) }

    val controller = rememberCardNumberInputController(btApiKey = sessionToken)

    CardNumberInput(
        controller = controller,
        modifier = Modifier.fillMaxWidth(),
        onLoadingChange = { isLoading = it },
        onError = { error = it },
        onSuccess = { tokenId = it },
    )

    Button(
        onClick = { controller.tokenize() },
        enabled = !isLoading,
    ) {
        Text("Tokenize")
    }

    error?.let { Text(it, color = MaterialTheme.colorScheme.error) }
    tokenId?.let { Text("Token: $it") }
}

How it works

  1. Create a controller with rememberCardNumberInputController(), passing the session token from your backend.
  2. Place CardNumberInput in your layout. It renders a secure text field for the card number.
  3. Call controller.tokenize() when the user is ready to submit.
  4. On success, onSuccess receives a PAN token ID (string) that you can send to your backend.

Callbacks

CallbackTypeDescription
onLoadingChange(Boolean) -> UnitCalled with true when a request starts and false when it completes.
onError(String?) -> UnitCalled with an error message on failure, or null to clear a previous error.
onSuccess(String) -> UnitCalled with the PAN token ID on successful tokenization.

PIN entry and EBT operations

The PinInput composable renders a secure PIN field for EBT balance checks and payments.
import com.bennyapi.sdk.core.networking.client.EbtOperation
import com.bennyapi.sdk.core.networking.client.EbtResult
import com.bennyapi.sdk.ui.PinInput
import com.bennyapi.sdk.ui.rememberPinInputController

@Composable
fun PinEntryScreen(sessionToken: String, panTokenId: String) {
    var isLoading by remember { mutableStateOf(false) }
    var error by remember { mutableStateOf<String?>(null) }
    var result by remember { mutableStateOf<String?>(null) }

    val controller = rememberPinInputController(sessionToken = sessionToken)

    PinInput(
        controller = controller,
        modifier = Modifier.fillMaxWidth(),
        onLoadingChange = { isLoading = it },
        onError = { error = it },
        onSuccess = { ebtResult ->
            result = when (ebtResult) {
                is EbtResult.Balance ->
                    "SNAP: ${ebtResult.balances.snapAvailableBalance ?: 0}"
                is EbtResult.Payment ->
                    "Payment successful"
            }
        },
    )

    // Balance check
    Button(
        onClick = {
            controller.submit(
                panTokenId = panTokenId,
                operation = EbtOperation.BalanceCheck,
            )
        },
        enabled = !isLoading,
    ) {
        Text("Check Balance")
    }

    // Payment
    Button(
        onClick = {
            controller.submit(
                panTokenId = panTokenId,
                operation = EbtOperation.Payment(paymentIntentId = "pi_xxx"),
            )
        },
        enabled = !isLoading,
    ) {
        Text("Pay")
    }
}

How it works

  1. Create a controller with rememberPinInputController(), passing the session token.
  2. Place PinInput in your layout. It renders a secure numeric field for the PIN.
  3. Call controller.submit() with the PAN token ID (from card tokenization) and an EbtOperation.
  4. On success, onSuccess receives an EbtResult with the account balances.

EBT operations

OperationUsage
EbtOperation.BalanceCheckCheck the cardholder’s EBT balance.
EbtOperation.Payment(paymentIntentId)Execute an EBT payment against a payment intent created on your backend.

EBT result

Both balance checks and payments return balance information:
when (ebtResult) {
    is EbtResult.Balance -> {
        ebtResult.balances.snapAvailableBalance     // Int?
        ebtResult.balances.ebtCashAvailableBalance   // Int?
        ebtResult.balances.snapBeginningBalance      // Int?
        ebtResult.balances.ebtCashBeginningBalance   // Int?
    }
    is EbtResult.Payment -> {
        ebtResult.balance.snapAvailableBalance       // Int?
        ebtResult.balance.ebtCashAvailableBalance    // Int?
    }
}
Balance amounts are returned as integers in cents. Divide by 100 to display dollar amounts.