Skip to main content

Android SDK

The Android SDK enables you to:

  • Capture credit card data and 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

To install the SDK, add the following lines to your build.gradle.kts or respective settings.gradle.kts file. Credentials are required to access the SDK and can be retrieved from the Starfish team.

dependencyResolutionManagement {
repositories {
...
maven {
val sdkReadUsername: String by settings
val sdkReadToken: String by settings
name = "Starfish Github Packages: hgate2-headless-android-sdk"
url = uri("https://maven.pkg.github.com/starfish-codes/hgate2-headless-android-sdk")
credentials {
username = sdkReadUsername
password = sdkReadToken
}
}
}
}

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 a 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())
...
}

Doing the 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 = HG_STAGING_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")
}
}