In this article, I want to show how to create views of both sides of the credit card. Similar to some bank applications, we likely saw e-credit cards with hidden CVC. We will also develop similar cards and implement number card validation by type. Let's begin.
To start, you need to create a .NET MAUI project:
dotnet new maui -n CreditCardSample
Following the step, you need to download iconic fonts to add credit card logos—the fonts you can find by link. Choose fonts for the desktop. You need to get FontAwesome6-Brands.otf
and FontAwesome6-Regular.otf
and copy them to Resources>Fonts
folder. Next step, you should register fonts. Go to the MainProgram.cs
and add this code:
fonts.AddFont("FontAwesome6-Brands.otf", "FA6Brands");
fonts.AddFont("FontAwesome6-Regular.otf", "FA6Regular");
Also, for convenience, let's set the colors that we'll be using. In the Resources folder, you can find the Styles folder You'll also find Colors.xaml
:
<Color x:Key="AmericanExpress">#3177CB</Color>
<Color x:Key="DinersClub">#1B4F8F</Color>
<Color x:Key="Discover">#E9752F</Color>
<Color x:Key="JCB">#9E2911</Color>
<Color x:Key="MasterCard">#394854</Color>
<Color x:Key="Visa">#2867BA</Color>
<Color x:Key="Default">#75849D</Color>
<Color x:Key="HeaderLabelTextColor">#BCBCBC</Color>
<Color x:Key="InfoLabelTextColor">#FFFFFF</Color>
Now, let's create a couple of helpers and create a Helpers folder. The first helper will check card numbers by the pay system.
internal static class CardValidationHelper
{
public static Regex AmericanExpress
= new(@"^3[47][0-9]{13}$");
public static Regex DinersClub
= new(@"^3(?:0[0-5]|[68][0-9])[0-9]{11}$");
public static Regex Discover
= new(@"^6(?:011|5[0-9]{2})[0-9]{12}$");
public static Regex JCB
= new(@"^(?:2131|1800|35\d{3})\d{11}$");
public static Regex MasterCard
= new(@"^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$");
public static Regex Visa
= new(@"^4[0-9]{12}(?:[0-9]{3})?$");
}
The second helper allows set colors by resource name.
internal static class StringExtensions
{
public static Color ToColorFromResourceKey(this string resourceKey)
{
return Application.Current?.Resources
.MergedDictionaries.First()[resourceKey] as Color;
}
}
And finally, let's create a Views folder and create CreditCardView. And now add this markup and I'll explain this code.
<?xml version="1.0" encoding="utf-8"?>
<Frame xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CreditCardApp.Views.CreditCardView"
HasShadow="False"
BorderColor="White"
CornerRadius="0">
<Grid>
<Frame x:Name="CardFront"
Grid.Row="0"
Grid.Column="0"
HasShadow="True"
BorderColor="Grey"
HorizontalOptions="Center"
VerticalOptions="Start"
WidthRequest="300"
HeightRequest="200"
CornerRadius="8"
Margin="40,10,40,30"
Padding="20">
<Frame.Resources>
<Style TargetType="Label"
x:Key="HeaderLabelStyle">
<Setter Property="LineBreakMode"
Value="TailTruncation" />
<Setter Property="FontSize"
Value="12" />
<Setter Property="TextColor"
Value="{StaticResource HeaderLabelTextColor}" />
</Style>
<Style TargetType="Label"
x:Key="InfoLabelStyle">
<Setter Property="FontSize"
Value="20" />
<Setter Property="TextColor"
Value="{StaticResource InfoLabelTextColor}" />
</Style>
<Style TargetType="Label"
x:Key="CreditCardImageLabelStyle">
<Setter Property="FontSize"
Value="48" />
<Setter Property="TextColor"
Value="#FFFFFF" />
<Setter Property="HorizontalOptions"
Value="EndAndExpand" />
</Style>
</Frame.Resources>
<Grid ColumnDefinitions="*,*"
ColumnSpacing="30"
RowDefinitions="Auto,Auto,40,Auto,40"
RowSpacing="0">
<Label x:Name="CreditCardImageLabel"
Style="{StaticResource CreditCardImageLabelStyle}"
Grid.Row="0"
Grid.Column="1" />
<Label Text="Card Number"
Style="{StaticResource HeaderLabelStyle}"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2" />
<Label x:Name="CreditCardNumber"
Style="{StaticResource InfoLabelStyle}"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2" />
<Label Text="Expiration"
Style="{StaticResource HeaderLabelStyle}"
Grid.Row="3"
Grid.Column="0" />
<Label x:Name="ExpirationDateLabel"
Style="{StaticResource InfoLabelStyle}"
Grid.Row="4"
Grid.Column="0" />
</Grid>
</Frame>
<Frame x:Name="CardBack"
Grid.Row="0"
HasShadow="True"
BorderColor="Grey"
HorizontalOptions="Center"
VerticalOptions="Start"
WidthRequest="300"
HeightRequest="200"
CornerRadius="8"
Margin="40,10,40,30">
<Frame.Resources>
<Style TargetType="Label"
x:Key="InfoLabelStyle">
<Setter Property="FontSize"
Value="20" />
<Setter Property="TextColor"
Value="{StaticResource InfoLabelTextColor}" />
</Style>
</Frame.Resources>
<StackLayout>
<Grid VerticalOptions="EndAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<BoxView BackgroundColor="Grey"
Grid.Column="1"
WidthRequest="100"
HeightRequest="40"
HorizontalOptions="End"
VerticalOptions="End" />
<Label x:Name="CardValidationCodeLabel"
Style="{StaticResource InfoLabelStyle}"
Grid.Column="1"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
</StackLayout>
</Frame>
<Frame x:Name="Streak" Grid.Row="0" BackgroundColor="#5d3623" WidthRequest="300"
HeightRequest="60" CornerRadius="0" HasShadow="False" VerticalOptions="Start" Margin="40">
</Frame>
</Grid>
</Frame>
Because we want to show both sides of the cards, we need to use different frames. Each frame has its own state and doesn't depend from another frame. Also, it allows the implementation of different views in one. I split the view into three parts: the front side, the back side, and the decoration frame.
public partial class CreditCardView
{
public static readonly BindableProperty CardNumberProperty
= BindableProperty.Create(nameof(CardNumber),
typeof(string), typeof(CreditCardView),
propertyChanged: OnCardNumberChanged);
public string CardNumber
{
get => (string)GetValue(CardNumberProperty);
set => SetValue(CardNumberProperty, value);
}
public static readonly BindableProperty ExpirationDateProperty
= BindableProperty.Create(nameof(ExpirationDate),
typeof(DateTime), typeof(CreditCardView), DateTime.Now,
propertyChanged: OnExpirationDateChanged);
public DateTime ExpirationDate
{
get => (DateTime)GetValue(ExpirationDateProperty);
set => SetValue(ExpirationDateProperty, value);
}
public static readonly BindableProperty CardValidationCodeProperty
= BindableProperty.Create(nameof(CardValidationCode),
typeof(string), typeof(CreditCardView), "-",
propertyChanged: OnCardValidationCodeChanged);
public string CardValidationCode
{
get => (string)GetValue(CardValidationCodeProperty);
set => SetValue(CardValidationCodeProperty, value);
}
private static void OnCardNumberChanged(BindableObject bindable,
object oldValue, object newValue)
{
if (bindable is not CreditCardView creditCardView)
return;
creditCardView.SetCreditCardNumber();
}
private static void OnExpirationDateChanged(BindableObject bindable,
object oldValue, object newValue)
{
if (bindable is not CreditCardView creditCardView)
return;
creditCardView.SetExpirationDate();
}
private static void OnCardValidationCodeChanged(BindableObject bindable,
object oldValue, object newValue)
{
if (bindable is not CreditCardView creditCardView)
return;
creditCardView.SetCardValidationCode();
}
public CreditCardView()
{
InitializeComponent();
CardFront.BackgroundColor = "Default".ToColorFromResourceKey();
CardBack.BackgroundColor = "Default".ToColorFromResourceKey();
CreditCardImageLabel.Text = "\uf09d";
CreditCardImageLabel.FontFamily = "FA6Regular";
ExpirationDateLabel.Text = "-";
CardValidationCodeLabel.Text = $"CVC: -";
CardBack.IsVisible = false;
CardFront.IsVisible = true;
Streak.IsVisible = false;
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += OnCardTapped;
this.GestureRecognizers.Add(tapGestureRecognizer);
}
private void SetCreditCardNumber()
{
if (string.IsNullOrEmpty(CardNumber))
{
CardFront.BackgroundColor = (Color)Application.Current?.Resources["Default"];
CardBack.BackgroundColor = (Color)Application.Current?.Resources["Default"];
CreditCardImageLabel.Text = "\uf09d";
CreditCardImageLabel.FontFamily = "FA6Regular";
}
if (long.TryParse(CardNumber, out long cardNumberAsLong))
{
CreditCardNumber.Text =
string.Format("{0:0000 0000 0000 0000}", cardNumberAsLong);
}
else
{
CreditCardNumber.Text = "-";
}
if (CardNumber != null)
{
var normalizedCardNumber = CardNumber.Replace("-", string.Empty);
if (CardValidationHelper.AmericanExpress.IsMatch(normalizedCardNumber))
{
CardFront.BackgroundColor = "AmericanExpress".ToColorFromResourceKey();
CardBack.BackgroundColor = "AmericanExpress".ToColorFromResourceKey();
CreditCardImageLabel.Text = "\uf1f3";
CreditCardImageLabel.FontFamily = "FA6Brands";
}
else if (CardValidationHelper.DinersClub.IsMatch(normalizedCardNumber))
{
CardFront.BackgroundColor = "DinersClub".ToColorFromResourceKey();
CardBack.BackgroundColor = "DinersClub".ToColorFromResourceKey();
CreditCardImageLabel.Text = "\uf24c";
CreditCardImageLabel.FontFamily = "FA6Brands";
}
else if (CardValidationHelper.Discover.IsMatch(normalizedCardNumber))
{
CardFront.BackgroundColor = "Discover".ToColorFromResourceKey();
CardBack.BackgroundColor = "Discover".ToColorFromResourceKey();
CreditCardImageLabel.Text = "\uf1f2";
CreditCardImageLabel.FontFamily = "FA6Brands";
}
else if (CardValidationHelper.JCB.IsMatch(normalizedCardNumber))
{
CardFront.BackgroundColor = "JCB".ToColorFromResourceKey();
CardBack.BackgroundColor = "JCB".ToColorFromResourceKey();
CreditCardImageLabel.Text = "\uf24b";
CreditCardImageLabel.FontFamily = "FA6Brands";
}
else if (CardValidationHelper.MasterCard.IsMatch(normalizedCardNumber))
{
CardFront.BackgroundColor = "MasterCard".ToColorFromResourceKey();
CardBack.BackgroundColor = "MasterCard".ToColorFromResourceKey();
CreditCardImageLabel.Text = "\uf1f1";
CreditCardImageLabel.FontFamily = "FA6Brands";
}
else if (CardValidationHelper.Visa.IsMatch(normalizedCardNumber))
{
CardFront.BackgroundColor = "Visa".ToColorFromResourceKey();
CardBack.BackgroundColor = "Visa".ToColorFromResourceKey();
CreditCardImageLabel.Text = "\uf1f0";
CreditCardImageLabel.FontFamily = "FA6Brands";
}
else
{
CardFront.BackgroundColor = "Default".ToColorFromResourceKey();
CardBack.BackgroundColor = "Default".ToColorFromResourceKey();
CreditCardImageLabel.Text = "\uf09d";
CreditCardImageLabel.FontFamily = "FA6Regular";
}
}
}
private void SetExpirationDate()
{
ExpirationDateLabel.Text = ExpirationDate.ToString("MM/yy",
CultureInfo.InvariantCulture);
}
private void SetCardValidationCode()
{
CardValidationCodeLabel.Text = $"CVC: {CardValidationCode}";
}
public static readonly BindableProperty IsFlippedProperty =
BindableProperty.Create(nameof(IsFlipped), typeof(bool), typeof(CreditCardView), false);
public bool IsFlipped
{
get => (bool)GetValue(IsFlippedProperty);
set
{
SetValue(IsFlippedProperty, value);
UpdateCardSideVisibility();
}
}
private void UpdateCardSideVisibility()
{
if (IsFlipped)
{
CardFront.IsVisible = false;
CardBack.IsVisible = true;
Streak.IsVisible = true;
}
else
{
CardFront.IsVisible = true;
CardBack.IsVisible = false;
Streak.IsVisible = false;
}
}
private void OnCardTapped(object sender, EventArgs e)
{
IsFlipped = !IsFlipped;
}
}
Let's explain what's going on in this file. The CardNumberProperty
bind a card number. The ExpirationDateProperty
binds an expiration date. The CardValidationCodeProperty
binds the security code on the backward side. The IsFlippedProperty
bind card state. The SetCreditCardNumber
sets styles for each card. The SetExpirationDate
formats expiration date. The SetCardValidationCode
joins the security code with the title as more convenient. The OnCardNumberChanged
, OnExpirationDateChanged
, OnCardValidationCodeChanged
, OnCardTapped
observable changes. The UpdateCardSideVisibility
switch frames.
The last step, let's add credit card views with passing data.
<VerticalStackLayout>
<views:CreditCardView CardNumber="371449635398431"
ExpirationDate="2024-12-01"
CardValidationCode="123"/>
<views:CreditCardView CardNumber="38520000023237"
ExpirationDate="2025-12-01"
CardValidationCode="456"/>
<views:CreditCardView CardNumber="6011000990139424"
ExpirationDate="2026-12-01"
CardValidationCode="789"/>
<views:CreditCardView CardNumber="3566002020360505"
ExpirationDate="2027-12-01"
CardValidationCode="321"/>
<views:CreditCardView CardNumber="5555555555554444"
ExpirationDate="2028-12-01"
CardValidationCode="654"/>
<views:CreditCardView CardNumber="4012888888881881"
ExpirationDate="2028-12-01"
CardValidationCode="987"/>
<views:CreditCardView />
</VerticalStackLayout>
Let's check it out.
Tap by card.
You even can tap by another card.
If this topic is interesting, I show how to make animation in .NET MAUI in the next article. And now that's all. See you in the next article and happy coding!
The source code you can find by link.