How create a credit card view in .NET MAUI
Tags: dotnet, maui, csharp, ios
Published: 2023-08-26 22:09:19

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:
```bash
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](https://fontawesome.com/download). 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:
```C#
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`:
```xml
<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.
```C#
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.

```C#
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
<?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.

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

```xaml
<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.


![front](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ekdex51mh4ficgois7fu.png)

Tap by card.


![back](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ku1313c1xd8y1rtryk25.png)

You even can tap by another card.


![back 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zk2t5pgeq93eycdbzr5d.png)

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](https://github.com/SergKorol/CreditCardSample).

<a href="https://www.buymeacoffee.com/b9z99kntvtQ" target="_blank"><img src="http://dartfordwaffler.co.uk/wp-content/uploads/2021/03/buy-me-a-beer.jpg" alt="Buy Me A Beer" style="height: 60px !important;width: 317px !important;" ></a>