Android SDK
The Android SDK enables you to capture credit card data, additional personal data, and process them to create a Hellgate® Token. This token can then be used to process payments with the Hellgate® API.
The SDK is written in Kotlin and is available as an Android dependency. It aims to provide a set of UI components and helpers that can be used to collect payment method data from the user with ease.
Installation
The SDK is available on Maven Central.
To install the SDK, add the following lines to your build.gradle.kts
or respective settings.gradle.kts
file.
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}
Then add the following to your build.gradle.kts
file:
dependencies {
implementation("io.hellgate:android-sdk:<version>")
}
Usage
Initialization of the UI Components
The SDK provides a set of UI components that can be used to collect payment method data from the user. These could look similar to the following image, depending on the design of your application.
The form in the picture is composed of the following separate fields:
CardNumberField
- A class that collects card details from the user.ExpiryDateField
- A class that collects the expiry date of the card from the user.CvcNumberField
- A class that collects the CVC of the card from the user.
These classes are intended to be set up in a viewModel while the ComposeUI
function
of the class should be called in your compose UI code to actually draw the fields as part of your user interface.
With the onValueChange
callback, you can listen to changes in the field state and update your viewModel accordingly.
@Composable
fun ComposeUI(
onValueChange: (FieldState) -> Unit,
modifier: Modifier = Modifier,
onFocused: () -> Unit = {},
onBlur: () -> Unit = {},
colors: TextFieldColors = TextFieldDefaults.colors(errorTextColor = MaterialTheme.colorScheme.error),
shape: Shape = OutlinedTextFieldDefaults.shape
)
FieldState
The field state is a data class that holds the state of the field. It contains the following properties and will be updated as the user interacts with the field:
data class FieldState(
val valid: Boolean = false,
val empty: Boolean = true,
val error: List<FieldError> = listOf(FieldError(FieldError.ErrorType.BLANK))
)
data class FieldError(
val errorType: ErrorType,
) {
enum class ErrorType {
INVALID,
INCOMPLETE,
BLANK
}
}
Styling
The ComposeUI
function allows you to pass in an androidx.compose.material3.TextFieldColors
object to style the field and overwrite default colors.
If not overridden, the default colors will be used, with the MaterialTheme.colorScheme.error
color being used for the error state.
The shape
parameter allows you to pass in a androidx.compose.ui.graphics.Shape
object to style the field and overwrite the default shape. If not overridden,
the OutlinedTextFieldDefautls.shape
will be used.
CardNumberField
The CardNumberField
class is used to collect the card number from the user. It can be initialized with a label that will be displayed on/above the input field.
The following pictures give examples of how the field could look empty / filled and with a luhn invalid number.
Code sample
class MyViewModel : ViewModel() {
val cardNumberField = CardNumberField("Custom Card Number Label")
var cardNumberState by mutableStateOf(FieldState())
...
}
class MyScreen : ComponentActivty() {
val viewModel by viewModels<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
...
Column {
CardNumberField()
}
}
}
@Composable
fun CardNumberField() {
viewModel.cardNumberField.ComposeUI(
onValueChange = { viewModel.cardNumberState = it },
modifier = Modifier.fillMaxWidth(),
onFocused = { /* handle focus */ },
onBlur = { /* handle blur */ },
// shape = RoundedCornerShape(10.dp)
// colors = TextFieldDefaults.colors(errorTextColor = Color.Red)
)
}
}
ExpiryDateField
The ExpiryDateField
class is used to collect the expiry date of the card from the user.
The following pictures give examples of how the field could look empty / filled and with an invalid date.
Code sample
class MyViewModel : ViewModel() {
val expiryDataField = ExpiryDateField("Custom Card Number Label")
var expiryDateState by mutableStateOf(FieldState())
...
}
class MyScreen : ComponentActivty() {
val viewModel by viewModels<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
...
Column {
ExpiryDateField()
}
}
}
@Composable
fun ExpiryDateField() {
viewModel.expiryDataField.ComposeUI(
onValueChange = { viewModel.cardNumberState = it },
modifier = Modifier.fillMaxWidth(),
onFocused = { /* handle focus */ },
onBlur = { /* handle blur */ },
// shape = RoundedCornerShape(10.dp)
// colors = TextFieldDefaults.colors(errorTextColor = Color.Red)
)
}
}
CvcNumberField
The CvcNumberField
class is used to collect the CVC of the card from the user.
The following pictures give examples of how the field could look empty / filled and with an invalid CVC.
Code sample
In case you would like to use the CvcNumberField
to also accept CVV (4-digit codes), you will need to pass in the maximum length of the CVC/CVV as a flow according to the card brand you are expecting. This flow can be retrieved from the CardNumberField
class, which will try to imply the brand of the card based on the card number.
If no flow is provided, the default maximum length of 3 digits will be used.
class MyViewModel : ViewModel() {
val cardNumberField = CardNumberField("Custom Card Number Label")
val cvcField = CvcNumberField(cardNumberField.maxBrandCvcLength)
var cvcFieldState by mutableStateOf(FieldState())
...
}
class MyScreen : ComponentActivty() {
val viewModel by viewModels<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
...
Column {
CardNumberField()
}
}
}
@Composable
fun CardNumberField() {
viewModel.expiryDataField.ComposeUI(
onValueChange = { viewModel.cvcFieldState = it },
modifier = Modifier.fillMaxWidth(),
onFocused = { /* handle focus */ },
onBlur = { /* handle blur */ },
// shape = RoundedCornerShape(10.dp)
// colors = TextFieldDefaults.colors(errorTextColor = Color.Red)
)
}
}
Additional Fields
The SDK also provides an AdditionalFields class that can be used to collect additional information from the user. The class is initialized with a type of the field and can be used to collect the following types of data:
CARDHOLDER_NAME
- to collect the cardholder name from the user.
In the future, the following fields will be added:
EMAIL
- to collect the email from the user.BILLING_ADDRESS_LINE_1
- to collect the address details from the user.BILLING_ADDRESS_LINE_2
- to collect the address details from the user.BILLING_ADDRESS_LINE_3
- to collect the address details from the user.BILLING_ADDRESS_POSTAL_CODE
- to collect the address details from the user.BILLING_ADDRESS_CITY
- to collect the address details from the user.
Code sample
class MyViewModel : ViewModel() {
val cardholderNameField = DataField(AdditionalDataTypes.CARDHOLDER_NAME)
var cardholderError by mutableStateOf(false)
var cardholderNameState by mutableStateOf(AdditionalDataFieldState())
...
}
Tokenization
Once the user has filled in the fields, the data can be tokenized using the tokenizeCard()
function of the CardHandler
interface.
To create an instance of the CardHandler
interface, you can use the cardHandler()
function of the Hellgate
SDK object. This section is followed by the SDK-interface for reference. Please make sure to provide the base URL of the Hellgate® API as a parameter according to your Hellgate® usage scenario. As a second parameter,
you will need to provide the session_id
that you received from the Hellgate® API when you initialized the session on the server side.
See the section Tokenize in Web for more information.
Once created, you can initialize a Hellgate® Object which helps you to handle a session.
SDK Interface
fun initHellgate(hgBaseUrl: String = HELLGATE_URL, sessionId: String) : Hellgate
interface Hellgate {
@MainThread
suspend fun fetchSessionStatus(): SessionState
@MainThread
suspend fun cardHandler(): Result<CardHandler>
}
enum class SessionState {
REQUIRE_TOKENIZATION,
COMPLETE,
WAITING,
UNKNOWN
}
Code sample
import javax.swing.text.View
class MyViewModel : ViewModel() {
var sessionState by mutableStateOf<SessionState?>(null)
fun createNewSession() {
viewModelScope.launch {
val sessionId: String = yourShopBackendClient().createSession().sessionId.orEmpty()
sessionState = SessionState.UNKNOWN
hellgate = initHellgate(HELLGATE_BASE_URL, sessionId)
}
}
fun fetchSessionStatus() {
viewModelScope.launch {
val sessionStatus = hellgate.fetchSessionStatus()
sessionState = sessionStatus
}
}
}
After acquiring a Hellgate® object, you can fetch the session status and create a card handler object to tokenize the card data.
Fetching the session status will return a SessionState
object which can be used to determine the state of the session.
If the session is in the state REQUIRE_TOKENIZATION
you can proceed with tokenizing the card data.
After the card data was handed in successfully, the session state will change to COMPLETE
.
To hand in card data please create a cardhandler object by calling the cardHandler()
function of the Hellgate
object.
Now thetokenizeCard()
function can be used to tokenize the card data by handing over the classes of the card number, expiry date and CVC number fields.
Also in case you collected additional data, you can hand over a list of DataField
objects to the function. The function will return a TokenizeCardResponse
object.
In case the tokenization was successful, the response will be of type TokenizeCardResponse.Success
and contain the ID of the token. In case the tokenization
failed, the response will be of type TokenizeCardResponse.Failure
and contain a message and an optional throwable.
SDK Interface
interface CardHandler {
suspend fun tokenizeCard(
cardNumberField: CardNumberField,
cvcNumberField: CvcNumberField,
expiryDateField: ExpiryDateField,
additionalData: List<DataField> = emptyList(),
): TokenizeCardResponse
}
sealed class TokenizeCardResponse {
data class Success(val id: String) : TokenizeCardResponse()
data class Failure(
val message: String,
val throwable: Throwable? = null,
val validationErrors: List<CardDataValidationError> = emptyList(),
) : TokenizeCardResponse()
}
// Possible validation errors
sealed interface CardDataValidationError {
val message: String
}
data object InvalidCardNumber : CardDataValidationError {
override val message: String = "Invalid card number"
}
data object InvalidExpiryDate : CardDataValidationError {
override val message: String = "Invalid expiry date"
}
data object InvalidCvc : CardDataValidationError {
override val message: String = "Invalid cvc"
}
Code sample
fun submit() {
viewModelScope.launch {
val cardHandler = hellgate.cardHandler().fold(
onSuccess = {
val hgToken = it.tokenizeCard(
cardNumberField,
cvcField,
expiryDateField,
if (cardholderNameState.empty) emptyList() else listOf(cardholderNameField),
)
debugLog("$TAG cardHandler response: $hgToken")
textValue = hgToken.toString()
fetchSessionStatus()
},
onFailure = {
// handle error
debugLog(it.message.toString())
},
)
debugLog("$TAG cardHandler response: $cardHandler")
}
}