---
title: Bitovi Academy - CVC
description: Apply what you’ve learned from previous sections to the `cvc` field.
---

- ![AI implementation](https://www.bitovi.com/hubfs/AIConsultingIcon.svg)
  
  [AI implementation](https://www.bitovi.com/services/ai-consulting?hsLang=en-us)
- ![Systems engineering](https://www.bitovi.com/hubfs/icon%20-%20backend.svg)
  
  [Systems engineering](https://www.bitovi.com/services/systems-engineering-consulting?hsLang=en-us)
- ![Project management](https://www.bitovi.com/hubfs/icon%20-%20PM.svg)
  
  [Project management](https://www.bitovi.com/services/agile-project-management-consulting?hsLang=en-us)
- ![Product design](https://www.bitovi.com/hubfs/icon%20-%20design.svg)
  
  [Product design](https://www.bitovi.com/services/product-design-consulting?hsLang=en-us)
- ![Frontend development](https://www.bitovi.com/hubfs/icon%20-%20frontend.svg)
  
  [Frontend development](https://www.bitovi.com/services/frontend-development-consulting?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/digital-consulting-services?hsLang=en-us)

We're Experts in...

- [JavaScript](https://www.bitovi.com/services/frontend/javascript-consulting?hsLang=en-us)
- [AI training](https://www.bitovi.com/ai-training-for-software-engineers?hsLang=en-us)
- [Angular](https://www.bitovi.com/services/frontend/angular-consulting?hsLang=en-us)
- [Design Systems](https://www.bitovi.com/services/axure-figma-migration?hsLang=en-us)
- [React](https://www.bitovi.com/services/frontend/react-consulting?hsLang=en-us)
- [Temporal](https://www.bitovi.com/services/backend/temporal-consulting?hsLang=en-us)
- [React Native](https://www.bitovi.com/services/frontend/react-consulting/react-native?hsLang=en-us)
- [Node.js](https://www.bitovi.com/services/backend/nodejs-consulting-1?hsLang=en-us)

Showcase

![Yum! Brands](https://www.bitovi.com/hubfs/yum-showcase-link-1.png)

[View case study](https://www.bitovi.com/en/bitovi-yum-case-study?hsLang=en-us)

More Projects

- [![Levi's](https://www.bitovi.com/hubfs/levis.svg)](https://www.bitovi.com/web-application-consulting-work/levis-ecommerce-responsive-redesign?hsLang=en-us)
- [![Christie's International Real Estate](https://www.bitovi.com/hubfs/christies.svg)](https://design.bitovi.com/christies)
- [![BAFS](https://www.bitovi.com/hubfs/bafs.svg)](https://www.bitovi.com/ux-design-consulting/ux-case-studies/bafs-ppp?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/our-software-consulting-work?hsLang=en-us)

Open Source Tools

We build powerful tools and open source them to support the community.

[See what we've built →](https://www.bitovi.com/open-source?hsLang=en-us)

- [![Blog](https://www.bitovi.com/hubfs/icon%20-%20blog.svg)
  
  BlogWe post about perfecting software delivery
  
  ](https://bitovi.com/blog)
- [![Partnerships](https://www.bitovi.com/hubfs/Handshake-1.svg)
  
  PartnershipsLearn about Bitovi's technology partners
  
  ](https://www.bitovi.com/partnerships?hsLang=en-us)
- [![Academy](https://www.bitovi.com/hubfs/limbo/icons/icon-academy.svg)
  
  AcademyFree courses to build delivery skills
  
  ](https://www.bitovi.com/academy/?hsLang=en-us)
- [![Open source tools](https://www.bitovi.com/hubfs/icon%20-%20open%20source.svg)
  
  Open source toolsUse our tools or contribute to our open source mission.
  
  ](https://www.bitovi.com/open-source?hsLang=en-us)

Let's Connect

- [![Discord](https://www.bitovi.com/hubfs/DiscordLogo.svg)
  
  Discord
  
  ](https://discord.gg/J7ejFsZnJ4)
- [![LinkedIn](https://www.bitovi.com/hubfs/LinkedinLogo.svg)
  
  LinkedIn
  
  ](https://www.linkedin.com/company/bitovi/)
- [![GitHub](https://www.bitovi.com/hubfs/GithubLogo.svg)
  
  GitHub
  
  ](https://github.com/bitovi/)

![Eggbot](https://www.bitovi.com/hubfs/build_assets/bitovi-limbo-cms-react/338/js_client_assets/assets/eggbot-LTGhdSGL.png)

Name *

Work Email *

Phone

What's your project?

Send

### Contact Us

(312) 620-0386contact@bitovi.com

- [ Advanced JavaScript ](https://www.bitovi.com/academy/learn-advanced-javascript.html?hsLang=en-us)
- [ DOM and jQuery ](https://www.bitovi.com/academy/learn-dom-jquery.html?hsLang=en-us)
- [ Debug JavaScript ](https://www.bitovi.com/academy/learn-to-debug-javascript.html?hsLang=en-us)
- [ Learn TypeScript ](https://www.bitovi.com/academy/learn-typescript.html?hsLang=en-us)
- [ Learn React Native ](https://www.bitovi.com/academy/learn-react-native.html?hsLang=en-us)
- [ Learn Web Components ](https://www.bitovi.com/academy/learn-web-components.html?hsLang=en-us)
- [ Agile Program Management With Jira ](https://www.bitovi.com/academy/learn-agile-program-management-with-jira.html?hsLang=en-us)
- [ Learn RxJS ](https://www.bitovi.com/academy/learn-rxjs.html?hsLang=en-us) 
    - [ Basics ](https://www.bitovi.com/academy/learn-rxjs/basics.html?hsLang=en-us)
    - [ RxJS in Angular ](https://www.bitovi.com/academy/learn-rxjs/angular.html?hsLang=en-us)
    - [ Clean card number ](https://www.bitovi.com/academy/learn-rxjs/clean-card-number.html?hsLang=en-us)
    - [ Card error ](https://www.bitovi.com/academy/learn-rxjs/card-error.html?hsLang=en-us)
    - [ Debugging ](https://www.bitovi.com/academy/learn-rxjs/debugging.html?hsLang=en-us)
    - [ Error on blur ](https://www.bitovi.com/academy/learn-rxjs/error-on-blur.html?hsLang=en-us)
    - [ Expiry ](https://www.bitovi.com/academy/learn-rxjs/expiry.html?hsLang=en-us)
    - [ CVC ](https://www.bitovi.com/academy/learn-rxjs/cvc.html?hsLang=en-us)
    - [ Disable pay button ](https://www.bitovi.com/academy/learn-rxjs/disable-pay-button.html?hsLang=en-us)
    - [ Request payment ](https://www.bitovi.com/academy/learn-rxjs/request-payment.html?hsLang=en-us)
    - [ Show paying ](https://www.bitovi.com/academy/learn-rxjs/show-paying.html?hsLang=en-us)
    - [ Disable while pending ](https://www.bitovi.com/academy/learn-rxjs/disable-while-pending.html?hsLang=en-us)
- [ Giving a Training ](https://www.bitovi.com/academy/training.html?hsLang=en-us)
- [ Learn Technology Consulting ](https://www.bitovi.com/academy/learn-technology-consulting.html?hsLang=en-us)
- [ Learn React ](https://www.bitovi.com/academy/learn-react.html?hsLang=en-us)
- [ Learn NgRx ](https://www.bitovi.com/academy/learn-ngrx.html?hsLang=en-us)
- [ Learn Graphql ](https://www.bitovi.com/academy/learn-graphql.html?hsLang=en-us)
- [ Learn Docker ](https://www.bitovi.com/academy/learn-docker.html?hsLang=en-us)
- [ Learn CanJS ](https://www.bitovi.com/academy/learn-canjs.html?hsLang=en-us)
- [ Learn Angular ](https://www.bitovi.com/academy/learn-angular.html?hsLang=en-us)
- [ Advanced TypeScript ](https://www.bitovi.com/academy/advanced-typescript.html?hsLang=en-us)

[View More Courses](https://www.bitovi.com/academy/#courses) [ ]

# CVC `page`

Apply what you’ve learned from previous sections to the `cvc` field.

## The problem

In this section, we will do for `cvc` what was done for `cardNumber` and `expiry`.

## How to solve this problem

First:

- Write the `<input name="cvc"/>` value to a `BehaviorSubject` called `this.userCVC$`.
- Create a `this.cvc$` observable that is just a reference to `this.userCVC$`. *We are not going to worry about input cleanup for this field*.

Second:

- Display an error message if the user has not entered the cvc correctly.
- Create a `cvcError$` observable that represents this error. You can use this function to get the error from `userCVC`:
  
  ```
  (cvc) => {
    if (!cvc) {
      return 'There is no CVC code';
    }
    if (cvc.length !== 3) {
      return 'The CVC must be at least 3 numbers';
    }
    if (isNaN(parseInt(cvc))) {
      return 'The CVC must be numbers';
    }
  };
  ```
- `cvcError$` should be displayed within the `<div class="message">` element.

Finally:

- Only show the `cvcError$` error if the user blurs the cvc input. Once the input blurs, we will update the displayed cvc error, if there is one, on every future keystroke.
- Add class="is-error" to the input when it has an error.
- Create a `userCVCBlurred$` `Subject` that emits when the `cvc` input is blurred.
- Create a `showCVCError$` that emits true when the `cvc` error should be shown.

## What you need to know

You already know everything you need to know. Apply what you learned from `cardNumber$`, `cardError$` and `showCardError$` to `cvc$`, `cvcError$`, and `showCVCError$`.

## The solution

Click to see the solution

```
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.4.0/rxjs.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/3.19.0/core.js"></script>
<script src="https://unpkg.com/@angular/core@12.2.16/bundles/core.umd.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.11.4/zone.min.js"></script>
<script src="https://unpkg.com/@angular/common@12.2.16/bundles/common.umd.js"></script>
<script src="https://unpkg.com/@angular/compiler@12.2.16/bundles/compiler.umd.js"></script>
<script src="https://unpkg.com/@angular/platform-browser@12.2.16/bundles/platform-browser.umd.js"></script>
<script src="https://unpkg.com/@angular/platform-browser-dynamic@12.2.16/bundles/platform-browser-dynamic.umd.js"></script>
<my-app></my-app>
<script type="typescript">
  // app.js
  const { Component, VERSION } = ng.core;
  const { BehaviorSubject, Subject, merge } = rxjs;
  const { map, tap, scan } = rxjs.operators;

  const cleanCardNumber = map((card) => {
    if (card) {
      return card.replace(/[\s-]/g, "");
    }
  });

  const validateCard = map((card) => {
    if (!card) {
      return "There is no card";
    }
    if (card.length !== 16) {
      return "There should be 16 characters in a card";
    }
  });

  const log = (name) => {
    return tap((value) => console.log(name, value));
  };

  function showOnlyWhenBlurredOnce(error$, blurred$) {
    const errorEvents$ = error$.pipe(
      map((error) => {
        return { type: error ? "invalid" : "valid" };
      })
    );

    const focusEvents$ = blurred$.pipe(
      map((isBlurred) => {
        return { type: isBlurred ? "blurred" : "focused" };
      })
    );

    const events$ = merge(errorEvents$, focusEvents$);

    const eventsToState = scan(
      (previous, event) => {
        switch (event.type) {
          case "valid":
            return { ...previous, isValid: true, showCardError: false };
          case "invalid":
            return {
              ...previous,
              isValid: false,
              showCardError: previous.hasBeenBlurred,
            };
          case "blurred":
            return {
              ...previous,
              hasBeenBlurred: true,
              showCardError: !previous.isValid,
            };
          default:
            return previous;
        }
      },
      {
        hasBeenBlurred: false,
        showCardError: false,
        isValid: false,
      }
    );

    const state$ = events$.pipe(eventsToState);

    return state$.pipe(map((state) => state.showCardError));
  }

  const expiryParts = map((expiry) => {
    if (expiry) {
      return expiry.split("-");
    }
  });

  const validateExpiry = map((expiry) => {
    if (!expiry) {
      return "There is no expiry. Format  MM-YY";
    }
    if (
      expiry.length !== 2 ||
      expiry[0].length !== 2 ||
      expiry[1].length !== 2
    ) {
      return "Expiry must be formatted like MM-YY";
    }
  });

  const validateCVC = map((cvc) => {
    if (!cvc) {
      return "There is no CVC code";
    }
    if (cvc.length !== 3) {
      return "The CVC must be at least 3 numbers";
    }
    if (isNaN(parseInt(cvc))) {
      return "The CVC must be numbers";
    }
  });

  @Component({
    selector: 'my-app',
    template: `
      <form>
        <div class="message" *ngIf="showCardError$ | async">{{ cardError$ | async }}</div>

        <div class="message" *ngIf="showExpiryError$ | async">{{ expiryError$ | async }}</div>

        <div class="message" *ngIf="showCVCError$ | async">{{ cvcError$ | async }}</div>

        <input
          type="text"
          name="cardNumber"
          placeholder="Card Number"
          (input)="userCardNumber$.next($event.target.value)"
          (blur)="userCardNumberBlurred$.next(true)"
          [class.is-error]="showCardError$ | async"
        />

        <input
          type="text"
          name="expiry"
          placeholder="MM-YY"
          (input)="userExpiry$.next($event.target.value)"
          (blur)="userExpiryBlurred$.next(true)"
          [class.is-error]="showExpiryError$ | async"
        />

        <input
          type="text"
          name="cvc"
          placeholder="CVC"
          (input)="userCVC$.next($event.target.value)"
          (blur)="userCVCBlurred$.next(true)"
          [class.is-error]="showCVCError$ | async"
        />

        <button>
          PAY
        </button>
      </form>
      UserCardNumber: {{ userCardNumber$ | async }} <br />
      CardNumber: {{ cardNumber$ | async }} <br />
    `
  })
  class AppComponent {
    userCardNumber$ = new BehaviorSubject<string>();
    userCardNumberBlurred$ = new Subject<boolean>();

    userExpiry$ = new BehaviorSubject<[]>();
    userExpiryBlurred$ = new Subject<boolean>();

    userCVC$ = new BehaviorSubject<string>();
    userCVCBlurred$ = new Subject<boolean>();

    constructor() {
      this.cardNumber$ = this.userCardNumber$
        .pipe(cleanCardNumber)
        .pipe(log("cardNumber"));
      this.cardError$ = this.cardNumber$.pipe(validateCard);
      this.showCardError$ = showOnlyWhenBlurredOnce(this.cardError$, this.userCardNumberBlurred$);

      this.expiry$ = this.userExpiry$.pipe(expiryParts);
      this.expiryError$ = this.expiry$.pipe(validateExpiry);
      this.showExpiryError$ = showOnlyWhenBlurredOnce(this.expiryError$, this.userExpiryBlurred$);

      this.cvc$ = this.userCVC$;
      this.cvcError$ = this.cvc$.pipe(validateCVC);
      this.showCVCError$ = showOnlyWhenBlurredOnce(this.cvcError$, this.userCVCBlurred$);
    }
  }

  // main.js
  const { BrowserModule } = ng.platformBrowser;
  const { NgModule } = ng.core;
  const { CommonModule } = ng.common;

  @NgModule({
    imports: [
      BrowserModule,
      CommonModule,
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent],
    providers: []
  })
  class AppModule {}

  const { platformBrowserDynamic } = ng.platformBrowserDynamic;

  platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.error(err));
</script>
<style>
  @import url('https://fonts.googleapis.com/css?family=Raleway:400,500');
  body {
    background-color: rgba(8, 211, 67, 0.3);
    padding: 2%;
    font-family: 'Raleway', sans-serif;
    font-size: 1em;
  }
  input {
    display: block;
    width: 100%;
    box-sizing: border-box;
    font-size: 1em;
    font-family: 'Raleway', sans-serif;
    font-weight: 500;
    padding: 12px;
    border: 1px solid #ccc;
    outline-color: white;
    transition: background-color 0.5s ease;
    transition: outline-color 0.5s ease;
  }
  input[name='cardNumber'] {
    border-bottom: 0;
  }
  input[name='expiry'],
  input[name='cvc'] {
    width: 50%;
  }
  input[name='expiry'] {
    float: left;
    border-right: 0;
  }
  input::placeholder {
    color: #999;
    font-weight: 400;
  }
  input:focus {
    background-color: rgba(130, 245, 249, 0.1);
    outline-color: #82f5f9;
  }
  input.is-error {
    background-color: rgba(250, 55, 55, 0.1);
  }
  input.is-error:focus {
    outline-color: #ffbdbd;
  }
  button {
    font-size: 1em;
    font-family: 'Raleway', sans-serif;
    background-color: #08d343;
    border: 0;
    box-shadow: 0px 1px 3px 1px rgba(51, 51, 51, 0.16);
    color: white;
    font-weight: 500;
    letter-spacing: 1px;
    margin-top: 30px;
    padding: 12px;
    text-transform: uppercase;
    width: 100%;
  }
  button:disabled {
    opacity: 0.4;
    background-color: #999999;
  }
  form {
    background-color: white;
    box-shadow: 0px 17px 22px 1px rgba(51, 51, 51, 0.16);
    padding: 40px;
    margin: 0 auto;
    max-width: 500px;
  }
  .message {
    margin-bottom: 20px;
    color: #fa3737;
  }
</style>

```

###### On this page...

###### Get help

If you need [help](https://discord.gg/J7ejFsZnJ4) with an exercise, please reach out to us on [Discord](https://discord.gg/J7ejFsZnJ4)

If you find a bug, please [create an issue](https://github.com/bitovi/academy/issues/new) or email [contact@bitovi.com](mailto:contact@bitovi.com?Subject=Training%20Suggestion)

## Join the Bitovi Newsletter

 Do you want to receive updates about new Bitovi Academy courses, Bitovi open source projects, and dev community news? If so, please provide your email. We won’t spam you or give it away for any reason!  
*By providing your email you agree to receive communications from Bitovi*

![](https://bitovi.github.io/academy/static/img/close.svg)

```json
{
     "@context": "http://schema.org",
     "@type": "Organization",
     "mainEntityOfPage":{
          "@type":"WebPage",
          "@id":"https://www.bitovi.com/academy/learn-rxjs/cvc.html",
          "description": "Apply what you’ve learned from previous sections to the `cvc` field. 
"
     },
     "url": "http://bitovi.com",
     "logo": "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg",
     "image": "https://www.bitovi.com/hubfs/bitovi-logo-x2.png",
     "name": "Bitovi Web App Consulting",
     "alternateName": "Bitovi",
      "naics": "541511",
      "description": "Bitovi is a UX, UI design and front-end JavaScript development consulting company",
     "address": {
          "@type": "PostalAddress",
          "streetAddress": "1134 Pine Tree Lane ",
          "addressLocality": "Libertyville",
          "addressRegion": "IL",
          "addressCountry": "United States",
          "postalCode": "60048"
     },
     "areaServed": {
          "@type": "GeoCircle",
          "geoMidpoint": {
               "@type": "GeoCoordinates",
               "latitude": "41.8781",
               "longitude": "87.6298"
          },
          "geoRadius": "5000 km"
     },
     "telephone": "312-620-0386",
     "email": "contact@bitovi.com",
     "sameAs": [
          "https://www.facebook.com/BitoviLLC/",
          "https://twitter.com/bitovi",
          "https://www.linkedin.com/company/bitovi"
     ]
}
```