Requirements
- Android
minSdk 26 or higher
- Kotlin 2.0+
- Jetpack Compose
Installation
Add the Benny SDK dependency to your app-level 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
),
)
// ...
}
}
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
- Create a controller with
rememberCardNumberInputController(), passing the session token from your backend.
- Place
CardNumberInput in your layout. It renders a secure text field for the card number.
- Call
controller.tokenize() when the user is ready to submit.
- On success,
onSuccess receives a PAN token ID (string) that you can send to your backend.
Callbacks
| Callback | Type | Description |
|---|
onLoadingChange | (Boolean) -> Unit | Called with true when a request starts and false when it completes. |
onError | (String?) -> Unit | Called with an error message on failure, or null to clear a previous error. |
onSuccess | (String) -> Unit | Called 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
- Create a controller with
rememberPinInputController(), passing the session token.
- Place
PinInput in your layout. It renders a secure numeric field for the PIN.
- Call
controller.submit() with the PAN token ID (from card tokenization) and an EbtOperation.
- On success,
onSuccess receives an EbtResult with the account balances.
EBT operations
| Operation | Usage |
|---|
EbtOperation.BalanceCheck | Check 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.