> ## Documentation Index
> Fetch the complete documentation index at: https://developer.hellgate.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 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](https://central.sonatype.com/artifact/io.hellgate/android-sdk). To install the SDK, add the following lines to your `build.gradle.kts` or respective `settings.gradle.kts` file.

```kotlin theme={null}
// settings.gradle.kts
dependencyResolutionManagement {
  repositories {
    mavenCentral()
  }
}
```

Then add the following to your `build.gradle.kts` file:

```kotlin theme={null}
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.

<Frame>
  <img src="https://mintcdn.com/starfishgmbhcokg/9V4MhqhWl4PxxxCs/images/platform/resources/sdks/android/input-form.png?fit=max&auto=format&n=9V4MhqhWl4PxxxCs&q=85&s=fc9056d1745538db87ac1dc1be19f572" alt="Input form" width="412" height="209" data-path="images/platform/resources/sdks/android/input-form.png" />
</Frame>

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.

```kotlin theme={null}
@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:

```kotlin theme={null}
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.

<Frame>
  <img src="https://mintcdn.com/starfishgmbhcokg/9V4MhqhWl4PxxxCs/images/platform/resources/sdks/android/empty-card-field.png?fit=max&auto=format&n=9V4MhqhWl4PxxxCs&q=85&s=404ef788d0d77ee987c5930b74de3801" alt="Empty card field" width="414" height="74" data-path="images/platform/resources/sdks/android/empty-card-field.png" />

  <img src="https://mintcdn.com/starfishgmbhcokg/9V4MhqhWl4PxxxCs/images/platform/resources/sdks/android/filled-card-field.png?fit=max&auto=format&n=9V4MhqhWl4PxxxCs&q=85&s=f4bbd2c1a854df096ff56ad5a42e149d" alt="Filled card field" width="412" height="76" data-path="images/platform/resources/sdks/android/filled-card-field.png" />

  <img src="https://mintcdn.com/starfishgmbhcokg/9V4MhqhWl4PxxxCs/images/platform/resources/sdks/android/error-card-field.png?fit=max&auto=format&n=9V4MhqhWl4PxxxCs&q=85&s=b4a8b5e2c0dffb46a63bdb409377fe10" alt="Error card field" width="412" height="76" data-path="images/platform/resources/sdks/android/error-card-field.png" />
</Frame>

#### Code sample

```kotlin theme={null}
class MyViewModel : ViewModel() {
  val cardNumberField = CardNumberField("Custom Card Number Label")
  val cardNumberState = MutableStateFlow(FieldState())
  // ...
}

class MyScreen : ComponentActivity() {
  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.value = 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.

<Frame>
  <img src="https://mintcdn.com/starfishgmbhcokg/9V4MhqhWl4PxxxCs/images/platform/resources/sdks/android/empty-expiry-field.png?fit=max&auto=format&n=9V4MhqhWl4PxxxCs&q=85&s=3b7e994245d158354eaff263d068fc01" alt="Empty expiry field" width="300" height="74" data-path="images/platform/resources/sdks/android/empty-expiry-field.png" />

  <img src="https://mintcdn.com/starfishgmbhcokg/9V4MhqhWl4PxxxCs/images/platform/resources/sdks/android/filled-expiry-field.png?fit=max&auto=format&n=9V4MhqhWl4PxxxCs&q=85&s=4e3f7ef0575f8c2506ec04993440b0bf" alt="Filled expiry field" width="300" height="74" data-path="images/platform/resources/sdks/android/filled-expiry-field.png" />

  <img src="https://mintcdn.com/starfishgmbhcokg/9V4MhqhWl4PxxxCs/images/platform/resources/sdks/android/error-expiry-field.png?fit=max&auto=format&n=9V4MhqhWl4PxxxCs&q=85&s=999d653ed99c8a0c9a5420954febdd5b" alt="Error expiry field" width="300" height="74" data-path="images/platform/resources/sdks/android/error-expiry-field.png" />
</Frame>

#### Code sample

```kotlin theme={null}
class MyViewModel : ViewModel() {
  val expiryDataField = ExpiryDateField("Custom Expiry Label")
  val expiryDateState = MutableStateFlow(FieldState())
  // ...
}

class MyScreen : ComponentActivity() {
  val viewModel by viewModels<MyViewModel>()
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Column {
        ExpiryDateField()
      }
    }
  }

  @Composable
  fun ExpiryDateField() {
    viewModel.expiryDataField.ComposeUI(
      onValueChange = { viewModel.expiryDateState.value = it },
      modifier = Modifier.fillMaxWidth(),
      onFocused = { /* handle focus */ },
      onBlur = { /* handle blur */ }
    )
  }
}
```

### 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.

<Frame>
  <img src="https://mintcdn.com/starfishgmbhcokg/9V4MhqhWl4PxxxCs/images/platform/resources/sdks/android/empty-cvc.png?fit=max&auto=format&n=9V4MhqhWl4PxxxCs&q=85&s=e3e6fff6bcd648413a8da52a651e6870" alt="Empty CVC" width="118" height="74" data-path="images/platform/resources/sdks/android/empty-cvc.png" />

  <img src="https://mintcdn.com/starfishgmbhcokg/9V4MhqhWl4PxxxCs/images/platform/resources/sdks/android/filled-cvc.png?fit=max&auto=format&n=9V4MhqhWl4PxxxCs&q=85&s=6227ae7136529e40768c6165ddb2d3fd" alt="Filled CVC" width="118" height="74" data-path="images/platform/resources/sdks/android/filled-cvc.png" />

  <img src="https://mintcdn.com/starfishgmbhcokg/9V4MhqhWl4PxxxCs/images/platform/resources/sdks/android/error-cvc.png?fit=max&auto=format&n=9V4MhqhWl4PxxxCs&q=85&s=13adccf9567e9b3ebd340d3b2f862347" alt="Error CVC" width="118" height="74" data-path="images/platform/resources/sdks/android/error-cvc.png" />
</Frame>

#### 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.

```kotlin theme={null}
class MyViewModel : ViewModel() {
  val cardNumberField = CardNumberField("Custom Card Number Label")
  val cvcField = CvcNumberField(cardNumberField.maxBrandCvcLength)
  val cvcState = MutableStateFlow(FieldState())
  // ...
}

class MyScreen : ComponentActivity() {
  val viewModel by viewModels<MyViewModel>()
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Column {
        CardNumberField()
        CvcNumberField()
      }
    }
  }

  @Composable
  fun CvcNumberField() {
    viewModel.cvcField.ComposeUI(
      onValueChange = { viewModel.cvcState.value = it },
      modifier = Modifier.fillMaxWidth(),
      onFocused = { /* handle focus */ },
      onBlur = { /* handle blur */ }
    )
  }
}
```

### 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

```kotlin theme={null}
class MyViewModel : ViewModel() {
  val cardholderNameField = DataField(AdditionalDataTypes.CARDHOLDER_NAME)
  val cardholderError = MutableStateFlow(false)
  val cardholderNameState = MutableStateFlow(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.

Once created, you can initialize a Hellgate® Object which helps you to handle a session.

#### SDK Interface

```kotlin theme={null}
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

```kotlin theme={null}
class MyViewModel : ViewModel() {
  val sessionState = MutableStateFlow<SessionState?>(null)

  fun createNewSession() {
    viewModelScope.launch {
      val sessionId: String = yourShopBackendClient().createSession().sessionId.orEmpty()
      sessionState.value = SessionState.UNKNOWN
      hellgate = initHellgate(HELLGATE_BASE_URL, sessionId)
    }
  }

  fun fetchSessionStatus() {
    viewModelScope.launch {
      val sessionStatus = hellgate.fetchSessionStatus()
      sessionState.value = 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 the`tokenizeCard()` 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

```kotlin theme={null}
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 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

```kotlin theme={null}
fun submit() {
  viewModelScope.launch {
    hellgate.cardHandler().fold(
      onSuccess = {
        debugLog("$TAG Success, cardHandler created")
        val response = it.tokenizeCard(
          cardNumberField,
          cvcField,
          expiryDateField,
          if (cardholderNameState.value.empty) emptyList() else listOf(cardholderNameField),
        )
        handleResponse(response)
        textValue = response.toString()
      },
      onFailure = {
        debugLog("$TAG Failure, cardHandler not created: ${it.message}")
      },
    )
  }
}

private fun handleResponse(response: TokenizeCardResponse) {
  when (response) {
    is TokenizeCardResponse.Success -> {
      debugLog("$TAG Tokenization successful, token ID: ${response.id}")
      textValue = "Token ID: ${response.id}"
      sessionState.value = SessionState.COMPLETED
    }
    is TokenizeCardResponse.Failure -> {
      debugLog("$TAG Tokenization failed: ${response.message}, Validation Errors: ${response.validationErrors}")
      textValue = "Error: ${response.message}"
      sessionState.value = SessionState.FAILURE
    }
  }
}
```
