Customize the Frames Android SDK
Last updated: December 21, 2022
When a customer initiates a payment with the Frames Android SDK, their payment information is collected via three customizable forms:
- Payment Form: prompts users for their payment card details
- Billing Form: prompts users for their billing information
- Country Picker: prompts the user to select a country
The styling of these forms can be customized through the use of the PaymentDetailsStyle
, BillingAddressDetailsStyle
and CountryPickerStyle
objects, which are wrapped in the PaymentFormStyle
wrapper.
1public data class PaymentFormStyle(2public var paymentDetailsStyle: PaymentDetailsStyle = PaymentDetailsStyle(),3public var billingFormStyle: BillingFormStyle = BillingFormStyle()4)56public data class BillingFormStyle(7public var billingAddressDetailsStyle: BillingAddressDetailsStyle = BillingAddressDetailsStyle(),8public var countryPickerStyle: CountryPickerStyle = DefaultCountryPickerStyle.light()9)
You can choose between three different styling options for customization, depending on how much control you need over the forms' UI design. In order of increasing complexity these are:
The default integration is the quickest way to display the forms required to collect payment information, with no additional customization required.
Localization support for the following languages is built into the default integration:
- Arabic
- Dutch
- English
- French
- German
- Italian
- Romanian
- Spanish
You can also add your own localized strings to add support for additional languages.
For basic customization, the PaymentDetailsStyle
, BillingFormStyle
, BillingAddressDetailsStyle
and CountryPickerStyle
objects expose properties that allow you to change the text and colors displayed on the forms.
1// Create a default style2val paymentFormStyle = PaymentFormStyle()34// Create a custom input component style5val customInputComponentStyle = DefaultLightStyle.inputComponentStyle(6titleTextId = R.string.custom_card_number_title,7withLeadingIcon = true,8margin = Margin(bottom = 16)9)1011// Modify payment form style with custom input component style12paymentFormStyle.paymentDetailsStyle.cardNumberStyle.inputStyle = customInputComponentStyle1314// Hide flag images for country picker15paymentFormStyle.billingFormStyle.countryPickerStyle.withFlag = false
If you want to customize your forms beyond what is supported by the default integration, you can apply a theme using the Theme
object.
When instantiating a new Theme
object, you must supply the following fields at a minimum:
accentColor
textColor
errorColor
backgroundColor
fieldBackgroundColor
enabledButtonColor
disabledButtonColor
You can then optionally provide additional values to customize other elements including button styling, custom placeholder and error text, and field input controls.
Dynamic font sizing based on the user's device is built into the Theme
object.
1// Create a color object with generic colors which will be used on all screens2private val paymentFormThemeColors = PaymentFormThemeColors(3accentColor = 0XFF00CC2D,4textColor = 0XFFB1B1B1,5errorColor = 0XFFFF0000,6backgroundColor = 0xFF17201E,7fieldBackgroundColor = 0XFF24302D,8enabledButtonColor = 0xFFFFFFFF,9disabledButtonColor = 0XFF00330010)1112// Customize card number component13private val cardNumber = PaymentFormComponentBuilder()14.setPaymentFormField(PaymentFormComponentField.CardNumber)15.setTitleTextId(R.string.cko_card_number_title)16.setSubTitleText("Card number is required")17.build()1819// Customize address line 1 component20private val addressLineOne = PaymentFormComponentBuilder()21.setPaymentFormField(PaymentFormComponentField.AddressLineOne)22.setTitleTextId(R.string.cko_billing_form_input_field_address_line_one)23.setSubTitleText("Eg. street address, apartment number")24.setInfoTextId(R.string.cko_input_field_optional_info)25.setIsFieldOptional(true)26.setIsFieldHidden(false)27.build()2829// Customize address line 2 component30private val addressLineTwo = PaymentFormComponentBuilder()31.setPaymentFormField(PaymentFormComponentField.AddressLineTwo)32.setIsFieldHidden(true)33.build()3435// Customize summary button36private val addBillingSummaryButton = PaymentFormComponentBuilder()37.setPaymentFormField(PaymentFormComponentField.AddBillingSummaryButton)38.setIsFieldOptional(false)39.setTitleText("Add billing address")40.build()4142// Customize billing summary button43private val editBillingSummaryButton = PaymentFormComponentBuilder()44.setPaymentFormField(PaymentFormComponentField.EditBillingSummaryButton)45.setTitleText("Edit billing address")46.build()4748// Create a payment form theme49fun providePaymentFormTheme(): PaymentFormTheme {50return PaymentFormTheme(51paymentFormThemeColors = paymentFormThemeColors,52/53* option 1: Use combination of default and custom components54*/55paymentFormComponents = DefaultPaymentFormTheme.providePaymentFormComponents56cardNumber = cardNumber,57addressLineOne = addressLineOne,58addressLineTwo = addressLineTwo,59addBillingSummaryButton = addBillingSummaryButton,60editBillingSummaryButton = editBillingSummaryButton61),62/63* option 2: Use default components64* paymentFormComponents = DefaultPaymentFormTheme.provideComponents()65*/66paymentFormShape = PaymentFormShape(67inputFieldShape = Shape.RoundCorner,68addressSummaryShape = Shape.Rectangle, buttonShape = Shape.Circle69),70paymentFormCornerRadius = PaymentFormCornerRadius(71inputFieldCornerRadius = CornerRadius(INPUT_FIELD_CORNER_RADIUS),72addressSummaryCornerRadius = CornerRadius(INPUT_FIELD_CORNER_RADIUS)73)74)75}
See our example project on GitHub.
For complete control over how the various UI elements on your forms look, you can use the fully custom solution.
You can use the objects and properties listed below to edit each individual UI component displayed across the forms:
1object CustomPaymentDetailsStyle {23fun providePaymentDetailsStyle() = PaymentDetailsStyle(4paymentDetailsHeaderStyle = provideHeaderStyle(),5cardSchemeStyle = provideCardSchemeStyle(),6cardNumberStyle = provideCardNumberStyle(),7expiryDateStyle = provideExpiryDateStyle(),8cvvStyle = provideCVVStyle(),9addressSummaryStyle = provideAddressSummaryStyle(),10payButtonStyle = providePayButtonStyle(),11fieldsContainerStyle = ContainerStyle(12color = backgroundColor,13padding = Padding(14start = PaymentFormConstants.padding,15end = PaymentFormConstants.padding16)17)18)1920private fun providePayButtonStyle(): PayButtonComponentStyle {21val buttonStyle = DefaultButtonStyle.lightSolid(22textId = R.string.cko_pay,23contentColor = inputFieldColor,24containerColor = textColor,25disabledContentColor = PaymentButtonConstants.disabledContentColor,26disabledContainerColor = PaymentButtonConstants.disabledContainerColor,27contentPadding = PaymentButtonConstants.contentPadding,28fontWeight = FontWeight.Bold,29margin = Margin(top = marginBottom),30shape = Shape.Circle,31cornerRadius = inputFieldCornerRadius32)33return PayButtonComponentStyle(buttonStyle = buttonStyle)34}3536private fun provideAddressSummaryStyle(): AddressSummaryComponentStyle {37var style = DefaultAddressSummaryComponentStyle.light(isOptional = false)38val containerStyle =39ContainerStyle(color = inputFieldColor, shape = Shape.RoundCorner, cornerRadius = inputFieldCornerRadius)4041style = style.copy(42titleStyle = null,43subTitleStyle = null,44containerStyle = containerStyle,45addAddressButtonStyle = DefaultButtonStyle.lightSolid(46textId = R.string.cko_add_billing_address,47trailingIconStyle = DefaultImageStyle.buttonTrailingImageStyle(tinColor = textColor),48fontWeight = FontWeight.SemiBold,49contentColor = textColor50),51summarySectionStyle = style.summarySectionStyle.copy(52editAddressButtonStyle = DefaultButtonStyle.lightSolid(53textId = R.string.cko_edit_billing_address,54trailingIconStyle = DefaultImageStyle.buttonTrailingImageStyle(tinColor = textColor),55fontWeight = FontWeight.SemiBold,56contentColor = textColor57),58containerStyle = containerStyle59)60)61return style62}6364private fun provideCardNumberStyle(): CardNumberComponentStyle {65val inputStyle = DefaultLightStyle.inputComponentStyle(66placeholderResourceTextId = R.string.cko_card_number_title,67withLeadingIcon = false,68)69return CardNumberComponentStyle(70inputStyle = inputStyle.copy(71errorMessageStyle = provideErrorMessageStyle(),72inputFieldStyle = inputStyle.inputFieldStyle.copy(73containerStyle = provideContainerStyle(inputFieldCornerRadius.copy(bottomStart = 0, bottomEnd = 0)),74indicatorStyle = provideIndicatorStyle(),75placeholderStyle = inputStyle.inputFieldStyle.placeholderStyle.copy(76color = placeHolderTextColor77)78)79)80)81}8283private fun provideExpiryDateStyle(): ExpiryDateComponentStyle {84val inputStyle = DefaultLightStyle.inputComponentStyle(85placeholderResourceTextId = R.string.cko_expiry_date_component_title,86margin = Margin(top = 2)87)88return ExpiryDateComponentStyle(89inputStyle.copy(90errorMessageStyle = provideErrorMessageStyle(),91inputFieldStyle = inputStyle.inputFieldStyle.copy(92containerStyle = provideContainerStyle(CornerRadius()),93indicatorStyle = provideIndicatorStyle(),94placeholderStyle = inputStyle.inputFieldStyle.placeholderStyle.copy(95color = placeHolderTextColor96)97)98)99)100}101102private fun provideCVVStyle(): CvvComponentStyle {103val inputStyle = DefaultLightStyle.inputComponentStyle(104placeholderResourceTextId = R.string.cko_cvv_component_title,105margin = Margin(top = 2, bottom = margin)106)107return CvvComponentStyle(108inputStyle.copy(109errorMessageStyle = provideErrorMessageStyle(),110inputFieldStyle = inputStyle.inputFieldStyle.copy(111containerStyle = provideContainerStyle(inputFieldCornerRadius.copy(topStart = 0, topEnd = 0)),112indicatorStyle = provideIndicatorStyle(),113placeholderStyle = inputStyle.inputFieldStyle.placeholderStyle.copy(114color = placeHolderTextColor115)116)117)118)119}120121private fun provideErrorMessageStyle(): TextLabelStyle =122DefaultTextLabelStyle.error(123padding = Padding(124top = ErrorConstants.errorMessageTopPadding,125bottom = ErrorConstants.errorMessageTopPadding126)127)128129private fun provideIndicatorStyle(): InputFieldIndicatorStyle = InputFieldIndicatorStyle.Underline(130focusedUnderlineThickness = 0,131unfocusedUnderlineThickness = 0132)133134private fun provideContainerStyle(cornerRadius: CornerRadius): ContainerStyle {135return ContainerStyle(136shape = inputFieldBorderShape,137color = inputFieldColor,138cornerRadius = cornerRadius139)140}141142private fun provideCardSchemeStyle() = DefaultLightStyle.cardSchemeComponentStyle()143.copy(144containerStyle = ContainerStyle(145margin = Margin(146top = marginTop,147bottom = marginBottom148)149),150titleStyle = DefaultTextLabelStyle.subtitle(textId = R.string.accepted_card_schemes, color = textColor),151imageStyle = null152)153154private fun provideHeaderStyle() = DefaultLightStyle.screenHeader(155textId = R.string.cko_payment_details_title,156imageId = R.drawable.ic_back_arrow,157leadingIconSize = backIconSize,158textColor = textColor,159fontWeight = FontWeight.Bold160).copy(161containerStyle = ContainerStyle(color = backgroundColor)162)163}
See an example on our GitHub project.
1object CustomBillingFormStyle {2private val defaultKeyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)34fun provideBillingFormStyle() = BillingFormStyle(5billingAddressDetailsStyle = provideBillingAddressDetailsStyle(),6countryPickerStyle = provideCountryPickerStyle()7)89private fun provideCountryPickerStyle(): CountryPickerStyle {10var style = DefaultCountryPickerStyle.light()1112style = style.copy(13screenTitleStyle = style.screenTitleStyle.copy(14textStyle = style.screenTitleStyle.textStyle.copy(color = textColor),15leadingIconStyle = style.screenTitleStyle.leadingIconStyle?.copy(tinColor = textColor)16),17containerStyle = style.containerStyle.copy(18color = backgroundColor19),20searchFieldStyle = with(style.searchFieldStyle) {21copy(22containerStyle = provideContainerStyle().copy(23margin = Margin(24start = CountryPickerScreenConstants.margin,25end = CountryPickerScreenConstants.margin,26bottom = CountryPickerScreenConstants.margin27)28),29indicatorStyle = provideIndicatorStyle(),30placeholderStyle = placeholderStyle.copy(color = PaymentFormConstants.placeHolderTextColor),31leadingIconStyle = leadingIconStyle?.copy(32tinColor = textColor33),34trailingIconStyle = trailingIconStyle?.copy(35tinColor = textColor36)37)38},39searchItemStyle = style.searchItemStyle.copy(40textStyle = style.searchItemStyle.textStyle.copy(41color = textColor42)43)44)45return style46}4748private fun provideBillingAddressDetailsStyle() = BillingAddressDetailsStyle(49headerComponentStyle = provideHeaderComponentStyle(),50inputComponentsContainerStyle = InputComponentsContainerStyle(fetchInputComponentStyleValues()),51countryComponentStyle = provideCountryComponentStyle(),52containerStyle = ContainerStyle(color = backgroundColor)53)5455private fun provideCountryComponentStyle(): CountryComponentStyle {56var style = DefaultCountryComponentStyle.light()57var inputStyle = style.inputStyle5859inputStyle = inputStyle.copy(60inputFieldStyle = provideInputFieldStyle(inputStyle.inputFieldStyle),61titleStyle = inputStyle.titleStyle?.copy(62textStyle = inputStyle.titleStyle?.textStyle?.copy(color = textColor) ?: TextStyle(),63containerStyle = ContainerStyle(padding = Padding(start = cornerRadius, bottom = paddingTenDp))64)65)6667style = style.copy(68inputStyle = inputStyle69)7071return style72}7374private fun provideHeaderComponentStyle(): HeaderComponentStyle {75var style = DefaultBillingAddressDetailsStyle.headerComponentStyle()76style = style.copy(77headerTitleStyle = style.headerTitleStyle.copy(78textStyle = style.headerTitleStyle.textStyle.copy(79color = textColor, fontWeight = FontWeight.Bold80),81),82headerButtonStyle = style.headerButtonStyle83.copy(84contentColor = inputFieldColor,85containerColor = textColor,86shape = Shape.Circle,87cornerRadius = inputFieldCornerRadius88)89)9091return style92}9394@Suppress("LongMethod")95private fun fetchInputComponentStyleValues(): LinkedHashMap<BillingFormFields, InputComponentStyle> {96val inputComponentsStyles: LinkedHashMap<BillingFormFields, InputComponentStyle> = linkedMapOf()9798inputComponentsStyles[BillingFormFields.AddressLineOne] = provideInputComponentStyle(99placeholderTextId = R.string.cko_billing_form_input_field_address_line_one,100isFieldOptional = false,101keyboardOptions = defaultKeyboardOptions,102padding = Padding(103start = LightStyleConstants.inputComponentLeftPadding,104end = LightStyleConstants.inputComponentRightPadding,105)106)107108inputComponentsStyles[BillingFormFields.AddressLineTwo] = provideInputComponentStyle(109placeholderTextId = R.string.cko_billing_form_input_field_address_line_two,110infoTextId = R.string.cko_input_field_optional_info,111isFieldOptional = true,112keyboardOptions = defaultKeyboardOptions,113padding = Padding(114start = LightStyleConstants.inputComponentLeftPadding,115end = LightStyleConstants.inputComponentRightPadding,116bottom = LightStyleConstants.inputComponentBottomPadding117)118)119120inputComponentsStyles[BillingFormFields.City] = provideInputComponentStyle(121placeholderTextId = R.string.cko_billing_form_input_field_city,122isFieldOptional = false,123keyboardOptions = defaultKeyboardOptions,124padding = Padding(125start = LightStyleConstants.inputComponentLeftPadding,126end = LightStyleConstants.inputComponentRightPadding127)128)129130inputComponentsStyles[BillingFormFields.State] = provideInputComponentStyle(131placeholderTextId = R.string.cko_billing_form_input_field_state,132infoTextId = R.string.cko_input_field_optional_info,133isFieldOptional = true,134keyboardOptions = defaultKeyboardOptions,135padding = Padding(136start = LightStyleConstants.inputComponentLeftPadding,137end = LightStyleConstants.inputComponentRightPadding,138bottom = LightStyleConstants.inputComponentBottomPadding139)140)141142inputComponentsStyles[BillingFormFields.PostCode] = provideInputComponentStyle(143placeholderTextId = R.string.cko_billing_form_input_field_postcode,144isFieldOptional = false,145keyboardOptions = defaultKeyboardOptions,146padding = Padding(147start = LightStyleConstants.inputComponentLeftPadding,148end = LightStyleConstants.inputComponentRightPadding,149bottom = LightStyleConstants.inputComponentBottomPadding150)151)152153inputComponentsStyles[BillingFormFields.Phone] = provideInputComponentStyle(154placeholderTextId = R.string.cko_billing_form_input_field_phone_title,155isFieldOptional = false,156keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),157padding = Padding(158start = LightStyleConstants.inputComponentLeftPadding,159end = LightStyleConstants.inputComponentRightPadding,160bottom = paddingTenDp161)162)163164return inputComponentsStyles165}166167private fun provideInputComponentStyle(168@StringRes placeholderTextId: Int? = null,169@StringRes infoTextId: Int? = null,170isFieldOptional: Boolean = false,171keyboardOptions: KeyboardOptions = KeyboardOptions.Default,172padding: Padding = Padding()173): InputComponentStyle {174var style = DefaultLightStyle.inputComponentStyle(175placeholderResourceTextId = placeholderTextId,176infoTextId = infoTextId,177padding = padding,178isFieldOptional = isFieldOptional,179keyboardOptions = keyboardOptions180)181182style = style.copy(183infoStyle = style.infoStyle?.copy(184containerStyle = ContainerStyle(185padding = Padding(186top = paddingSixDp, bottom = paddingSixDp, end = cornerRadius187)188)189),190inputFieldStyle = provideInputFieldStyle(style.inputFieldStyle)191)192193return style194}195196private fun provideInputFieldStyle(inputFieldStyle: InputFieldStyle): InputFieldStyle {197return inputFieldStyle.copy(198containerStyle = provideContainerStyle(),199indicatorStyle = provideIndicatorStyle(),200placeholderStyle = inputFieldStyle.placeholderStyle.copy(201color = PaymentFormConstants.placeHolderTextColor202),203trailingIconStyle = inputFieldStyle.trailingIconStyle?.copy(204tinColor = textColor205)206)207}208209private fun provideIndicatorStyle(): InputFieldIndicatorStyle = InputFieldIndicatorStyle.Underline(210focusedUnderlineThickness = 0, unfocusedUnderlineThickness = 0211)212213private fun provideContainerStyle(): ContainerStyle {214return ContainerStyle(215shape = inputFieldBorderShape, color = inputFieldColor, cornerRadius = inputFieldCornerRadius216)217}218}
See an example on our GitHub project.
1PaymentFormStyle(2CustomPaymentDetailsStyle.providePaymentDetailsStyle(),3CustomBillingFormStyle.provideBillingFormStyle()4)
See an example on our GitHub project.