In the following article we will be looking into credit card payment and credit card design using the Stripe element. Stripe is one of the online and in-person payment processing systems. It's not open source. In the below sample, we are using the test version. We can do operations like creating a customer, payment, custom UI element design using the Stripe card element, etc.

In the below sample, I have used the stripe.js npm package for Stripe payment processing in Angular and Stripe REST APIs. And for business logic, I have used the dot net in this article. I have defined back-end functionality.

Package Installation
Use the below npm command to install Stripe in Angular.
npm install @stripe/stripe-js

Create a customer using card tokenization
Creates a new customer in the Stripe system, associating a payment source; basically, it will create a customer after getting card info from users.

In this sample I am using a modal popup to get the card info.

creditcard.component.html
In the below sample, we are loading the card number, expiry date and CVV using stripe element.
<div class="modal-body">
    <!-- Modal content area -->
    <h2>Enter Your Credit Card Information</h2>
    <!-- Title -->

    <form (ngSubmit)="onSubmit()">
        <!-- Angular form submission handler -->

        <!-- Card Number -->
        <label for="card-number">Card Number</label>
        <!-- Label for card number -->
        <div id="card-number" class="stripe-element"></div>
        <!-- Stripe Element placeholder for Card Number -->

        <!-- Expiry Date -->
        <label for="card-expiry">Expiration Date</label>
        <!-- Label for expiration date -->
        <div id="card-expiry" class="stripe-element"></div>
        <!-- Stripe Element placeholder for Expiration Date -->

        <!-- CVC -->
        <label for="card-cvc">CVC</label>
        <!-- Label for CVC -->
        <div id="card-cvc" class="stripe-element"></div>
        <!-- Stripe Element placeholder for CVC -->

        <!-- Errors -->
        <div id="card-errors" role="alert"></div>
        <!-- Placeholder to show card input errors -->

        <!-- Buttons -->
        <div class="modal-buttons">
            <!-- Button container -->
            <button type="button" (click)="vm.closeCardModal()">Cancel</button>
            <!-- Button to cancel the modal -->
            <button type="submit" [disabled]="isSubmitting">Submit</button>
            <!-- Submit button, disabled while `isSubmitting` is true -->
        </div>
    </form>
</div>

creditcard.component.scss
.modal-body {
    background-color: #f9f9f9;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    max-width: 400px;
    margin: auto;
}
.modal-content {
  background: #fff;
  padding: 20px;
  border-radius: 8px;
  width: 400px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  text-align: center;
}

.modal-buttons {
  display: flex;
  justify-content: space-between;
  margin-top: 20px;
}

.requiredIndicate {
color: red; font-weight: bold;
}
h2 {
    text-align: center;
    color: #333;
}

form {
    display: flex;
    flex-direction: column;
}
.error-message{
    font-size: 15px;
    color: #df0707;
    padding: 2px;
}
label {
    margin-top: 10px;
    color: #555;
}

input {
    padding: 10px;
    margin-top: 5px;
    border: 1px solid #ccc;
    border-radius: 4px;
}

button {
    margin-top: 20px;
    padding: 10px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

button[type="submit"] {
    background-color: #28a745;
    color: white;
}

button[type="button"] {
    background-color: #dc3545;
    color: white;
}

button:hover {
    opacity: 0.9;
}
#card-errors {
  color: red;
  font-size: 0.9em;
  margin-top: 5px;
}

creditcard.component.ts
In Component, we are loading the Stripe element to our UI element using Id. Don't hardcode the publish and secret key in the front end. try to get from backend
The mount() method is used to attach a Stripe Element (such as cardNumber, cardExpiry, or cardCvc) to a specific DOM element in your HTML.
Each Stripe Element can only be mounted to one DOM element. If you need to move it to a new location, unmount it first using the unmount() method
loadStripe() The method is used to create an instance of the stripe using the publish key.
displayError() method used to bind the error message to an HTML element.
onSubmit() method used for form submission and external validation if need means.
the createToken method in Stripe.js is used to securely create a single-use token that represents a payment method (like a credit card). This token can then be sent to your server for processing a payment or saving card details

import { AfterViewInit, Component, EventEmitter, OnInit, Output } from '@angular/core';
import { loadStripe, Stripe, StripeCardCvcElement, StripeCardExpiryElement, StripeCardNumberElement, StripeElements } from '@stripe/stripe-js';

@Component({
  selector: 'app-creditcard',
  templateUrl: './creditcard.component.html', // HTML template for the component
  styleUrls: ['./creditcard.component.scss'] // SCSS styles for the component
})
export class CreditcardComponent implements OnInit, AfterViewInit {

  // Stripe API keys (publishable key for client-side usage)
  publishKey: string = ""; // Please replace your stripe publish key here.

  // Stripe objects
  stripe: Stripe | null = null; // Stripe instance
  elements: StripeElements | null = null; // Stripe Elements instance
  cardNumber: StripeCardNumberElement | null = null; // Card Number Element
  cardExpiry: StripeCardExpiryElement | null = null; // Expiry Date Element
  cardCvc: StripeCardCvcElement | null = null; // CVC Element

  isSubmitting: boolean = false; // Prevent duplicate submissions

  @Output() tokenGenerated = new EventEmitter<string>(); // Emit the generated token to parent component

  constructor() { }

  ngOnInit(): void {
    // Component initialization logic (if needed)
  }

  async ngAfterViewInit() {
    // Load the Stripe instance with the publishable key
    this.stripe = await loadStripe(this.publishKey);

    if (this.stripe) {
      // Initialize Stripe Elements
      this.elements = this.stripe.elements();

      // Define the custom styles for Stripe Elements
      const style = {
        base: {
          color: '#32325d', // Default text color
          fontFamily: '"Roboto", sans-serif', // Font family
          fontSize: '16px', // Font size
          '::placeholder': {
            color: '#aab7c4', // Placeholder text color
          },
        },
        invalid: {
          color: '#fa755a', // Invalid input text color
          iconColor: '#fa755a', // Invalid icon color
        },
      };

      // Create and mount the Card Number Element
      this.cardNumber = this.elements.create('cardNumber', { style });
      this.cardNumber.mount('#card-number');

      // Create and mount the Expiry Date Element
      this.cardExpiry = this.elements.create('cardExpiry', { style });
      this.cardExpiry.mount('#card-expiry');

      // Create and mount the CVC Element
      this.cardCvc = this.elements.create('cardCvc', { style });
      this.cardCvc.mount('#card-cvc');

      // Attach event listeners to handle real-time validation errors
      this.cardNumber.on('change', this.displayError.bind(this));
      this.cardExpiry.on('change', this.displayError.bind(this));
      this.cardCvc.on('change', this.displayError.bind(this));
    }
  }

  displayError(event: any) {
    // Handle real-time validation errors
    const displayError = document.getElementById('card-errors');
    if (displayError) {
      if (event.error) {
        displayError.textContent = event.error.message; // Show error message
      } else {
        displayError.textContent = ''; // Clear error message
      }
    }
  }

  async onSubmit() {
    // Ensure Stripe and card elements are initialized
    if (!this.stripe || !this.cardNumber) {
      console.error('Stripe or card elements not initialized');
      return;
    }

    this.isSubmitting = true; // Set submission state to true

    // Create a token for the card details
    const { token, error } = await this.stripe.createToken(this.cardNumber);

    if (error) {
      // Display error message if tokenization fails
      const displayError = document.getElementById('card-errors');
      if (displayError && error.message) {
        displayError.textContent = error.message;
      }
    } else {
      // Emit the token to the parent component
      this.tokenGenerated.emit(token?.id);

      // Close the modal (assumes `vm.closeCardModal()` is defined)
      this.vm.closeCardModal();
    }

    this.isSubmitting = false; // Reset submission state
  }
}


Sample Output of UI Element

 

Modal Popup Open/Close
ngx-bootstrap npm package was used to open and close the modal. I have used a modal popup in the payment component to open and close the modal, so I have included it in payment.module.ts and declartion included.

package installation
npm i ngx-bootstrap

Include ModalModule in required module like below,

payment.module.ts

@NgModule({
  declarations: [PaymentComponent,CreditcardComponent],
  imports: [
    CommonModule,ReactiveFormsModule,FormsModule,NgToggleModule,
    PaymentRoutingModule, ModalModule.forRoot(),
  ]
})


payment.component.ts

Used to generate customer from the emitted output function creditcard.component.ts it will trigger customer generation.
openCardModal(): Used to open the modal popup with creditcard.component.ts.

constructor(private modalService: BsModalService,private paymentService:PaymentService)
  {

  }
openCardModal = () => {
  // Opens the CreditcardComponent as a modal
  this.modalRef = this.modalService.show(CreditcardComponent, {
    animated: true, // Enables animation for the modal
    keyboard: false, // Disables closing the modal via the ESC key
    backdrop: 'static', // Prevents clicking outside the modal to close it
    initialState: {
      vm: this.vm, // Passes a custom `vm` object to the modal component's input
    },
  });

  // Subscribes to the `tokenGenerated` EventEmitter from the modal component
  this.modalRef.content.tokenGenerated.subscribe((token: string) => {
    this.createCustomerForToken(token); // Calls the function to handle token processing
  });
};

createCustomerForToken(token) method
This method processes the token emitted by the modal and uses it to create a customer by calling the paymentService
createCustomerForToken(token: string) {
  // Calls the `createCustomer` method in the payment service
  this.paymentService
    .createCustomer(
      this.paymentService.GenerateCustomer(
        this.cardDetails.Name, // Name of the cardholder
        this.cardDetails.CardNumber, // Card number (masked or partial if passed)
        token // Token generated from Stripe
      )
    )
    .subscribe((data: any) => {
      // You can handle the response here, e.g., show success messages
    });
}


closeCardModal Method
This method closes the modal when the operation is complete or canceled.
closeCardModal = () => {
  this.modalRef?.hide(); // Hides the modal if `modalRef` exists
};

OnInit Method
This method used to initialize the vm's property and method
ngOnInit(): void {
  this.vm = {
    cardHolderName: null,  // Placeholder for the cardholder's name input field (starts with null)
    cardNumber: null,      // Placeholder for the credit card number (starts with null)
    expiryDate: null,      // Placeholder for the card's expiry date (starts with null)
    cvv: null,             // Placeholder for the CVV input field (starts with null)

    // Methods to open and close the card modal
    openCardModal: this.openCardModal,  // Reference to the method that opens the credit card modal
    closeCardModal: this.closeCardModal, // Reference to the method that closes the modal
  };
}


payment.service.ts
Generate an object using the generate customer, and for REST APIs, I am using the previous article.  Create customers with a tokenization block to generate customers based on tokens.
var apiUrl = ""; should be end point of the service.
 return {
    Email: Email,                     // Use the passed 'Email' parameter
    Name: Name,                       // Use the passed 'Name' parameter
    Phone: "1234567890",               // Replace with the actual phone number if needed
    Description: "Customer for test",  // Replace with an appropriate description if needed
    SourceID: token                    // The token passed from payment process
  };

public createCustomer(customer: any) {
  return this.apiHandler.post(apiUrl + "/create-customer-with-tokenisation", customer);
}
 public GeneratePaymentIntent(amount:number,offSession:boolean,paymentMethodID:string)
  {
    return {
      amount:amount,
      paymentMethodID: paymentMethodID,
      customerId: null,
      offSession:offSession
    };
  }
  public createPaymentIntent(paymentMethod:any)
  {
    return this.apiHandler.post(apiUrl + "/create-payment-intent",paymentMethod);
  }
  public createPayment(amount:any)
  {
    return this.apiHandler.post(apiUrl + "/create-payment-intent-without-paymentmethod",{ amount: amount });
  }

Pay using a card with the customer and payment method
Payment template used to pay amount using credit card.
<ng-template #paymentTemplate>
  <div class="modal-header">
    <h3 class="modal-title">Payment Information</h3>
  </div>
  <div class="modal-body">
    <div class="modal-content">
      <form (submit)="handlePayment($event)">
        <label>
          Without PaymentMethod:
          <ng-toggle  [(ngModel)]="isPaymentMethod" name="toggle" required />
        </label>
        <label>
          Card Holder Name:
          <input type="text" [(ngModel)]="cardHolderName" name="cardHolderName" required />
        </label>
        <label>
          Amount:
          <input type="text" [(ngModel)]="amount" name="amount" required />
        </label>
        <div id="pay-card-element" class="card-element"><!-- Stripe Card Element --></div>
        <div id="pay-card-errors" role="alert"></div>
        <div class="pay-modal-buttons">
          <button type="button" class="btn btn-failure" (click)="closeCardModal()">Cancel</button>
          <button type="submit" class="btn btn-success" [disabled]="loading">Pay {{ amount | currency }}</button>
        </div>
      </form>
    </div>
  </div>
</ng-template>

payment.component.ts
In payment component, variable declaration and constructor, Don't hard code publish key and secret key in html side try to get from server side.
// Declaring a dynamic object for storing values related to the modal and card details.
vm: any = {};

// Secret key for server-side Stripe API communication (should not be exposed on the client-side).
secretKey: string = "";

// Publishable key used for client-side Stripe communication.
publishKey: string = "";

// ViewChild decorator for referencing the modal template.
@ViewChild('paymentTemplate') paymentModal!: TemplateRef<any>;

// The modal reference to control its visibility and behavior.
modalRef!: BsModalRef;

// Stripe object initialized for Stripe.js integration (holds the Stripe instance).
private stripe: Stripe | null = null;

// The card element used for securely collecting card details.
private cardElement: any;

// States for loading, error messages, and cardholder's name.
public loading = false;
public error = '';
public cardHolderName = '';

// Amount to be processed for payment.
public amount = 1;

// A flag to track if it's a payment method or another type of payment action.
isPaymentMethod: boolean = true;

// Constructor injecting modal service for modal control and payment service for backend communication.
constructor(private modalService: BsModalService, private paymentService: PaymentService) {}

// ngOnInit lifecycle hook, currently not used but reserved for any initialization logic.
ngOnInit(): void {
  // Any initialization logic goes here if needed.
}


loadStripe() method

This method initializes Stripe Elements for securely collecting payment details.

  • loadStripe(this.publishKey): Asynchronously loads Stripe.js with the public key for client-side operations.
  • this.stripe.elements(): Creates an instance of Stripe Elements which provides a secure method to collect card information.
  • this.cardElement.mount('#pay-card-element'): Mounts the cardElement (a Stripe UI element) into the DOM, allowing users to enter credit card information.

async loadStripe() {
  // Load Stripe.js with the public key for client-side operations.
  this.stripe = await loadStripe(this.publishKey);

  // Check if Stripe was successfully initialized.
  if (this.stripe) {
    // Initialize Stripe Elements, which will be used to securely collect card details.
    const elements = this.stripe.elements();

    // Create a 'card' element for collecting card details, hiding postal code field.
    this.cardElement = elements.create('card', {
      hidePostalCode: true
    });

    // Mount the card element onto the HTML element with id 'pay-card-element' to display it on the page.
    this.cardElement.mount('#pay-card-element');
  } else {
    // Log an error if Stripe is not initialized properly.
    console.error('Stripe is not initialized');
  }
}

handlePayment() Method

This method handles the payment process, triggered when the user submits the payment form

  • event.preventDefault(): Prevents the form submission to allow custom handling via JavaScript.
  • this.paymentService.createPayment(this.amount): Calls a service to create a payment request with the backend, sending the payment amount.
  • this.stripe.confirmCardPayment(): Uses Stripe's API to confirm the payment using the card details entered by the user.
  • Error handling: Sets the error message if payment fails, and resets the loading state.
  • this.loading = false: Updates the loading state to stop the spinner once the payment process is finished.

async handlePayment(event: Event) {
  // Prevent default form submission behavior.
  event.preventDefault();

  // Set loading state to true to show a loading spinner or message.
  this.loading = true;

  // Check if Stripe is initialized before proceeding.
  if (this.stripe) {

    // Call the backend to create a payment, passing the amount to charge.
    this.paymentService.createPayment(this.amount).subscribe(async (data: any) => {

      // Ensure Stripe is initialized before proceeding with payment confirmation.
      if (this.stripe) {
        // Use Stripe to confirm the payment using the card details and secret key.
        const { error } = await this.stripe.confirmCardPayment(this.secretKey, {
          payment_method: {
            card: this.cardElement, // The card element that holds the user's card information.
            billing_details: {
              name: this.cardHolderName, // The name of the cardholder.
            },
          },
        });

        // Handle error if payment confirmation fails.
        if (error) {
          this.error = error.message ? error.message : 'An unknown error occurred';
          this.loading = false; // Reset loading state.
        } else {
          this.error = ''; // Clear any previous errors.
          this.loading = false; // Reset loading state.
        }
      } else {
        // Error handling if Stripe is not initialized.
        this.error = 'Stripe is not initialized';
        this.loading = false; // Reset loading state.
      }
    });

  } else {
    // Error handling if Stripe is not initialized.
    this.error = 'Stripe is not initialized';
    this.loading = false; // Reset loading state.
  }
}

openpaymentModal() Method
This method opens the payment modal where the user can enter payment details.
openpaymentModal = () => {
  // Show the modal using the BsModalService, passing the initial state with the modal data.
  this.modalRef = this.modalService.show(this.paymentModal, {
    animated: true, // Enable animations for the modal.
    keyboard: false, // Disable closing the modal via the keyboard.
    backdrop: 'static', // Prevent closing the modal by clicking outside.
    initialState: {
      vm: this.vm // Pass initial data (vm) to the modal component.
    }
  });

  // Call loadStripe to initialize Stripe and display the payment form inside the modal.
  this.loadStripe();
}


Full code snippet of payment.compoent.ts
import { trigger, state, style, transition, animate } from '@angular/animations';
import { Component, OnInit, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { CreditcardComponent } from '../creditcard/creditcard.component';
import { PaymentService } from './payment.service';
import { loadStripe, Stripe } from '@stripe/stripe-js';

@Component({
  selector: 'app-payment',
  standalone: false,
  encapsulation:ViewEncapsulation.None,
  templateUrl: './payment.component.html',
  styleUrl: './payment.component.scss'

})
export class PaymentComponent implements OnInit{

  vm: any={};
  secretKey:string ="sk_test_51Q2TApRw3m8aFNMV8RYvLabAEmwYOwHtiNWXrP1pR88IlVeuItDrhoxbb80YcCm45BoN9ncle0Mw84wv74Vc4rV700Y0gwbdh7";
  publishKey:string ="pk_test_51Q2TApRw3m8aFNMVKcnlDcs0EH3pu0EuhPpKyYe99eaBXE2ex9Nd1aSkCi4dxZQ3jBZwjlyrUAU30RN8nuGth6dv00HGMKk8vH";


  @ViewChild('paymentTemplate') paymentModal !: TemplateRef<any> ;
  modalRef!: BsModalRef;
  private stripe: Stripe | null = null;
private cardElement: any;
public loading = false;
public error = '';
public cardHolderName = '';
public amount = 1;
 isPaymentMethod:boolean =true;
  constructor(private modalService: BsModalService,private paymentService:PaymentService)
  {

  }

  ngOnInit(): void {
    this.vm = {
      cardHolderName: null,
      cardNumber: null,
      expiryDate: null,
      cvv: null,
      openCardModal: this.openCardModal,
      closeCardModal: this.closeCardModal,
    };
  }

  openCardModal=()=> {
    this.modalRef = this.modalService.show(CreditcardComponent,{ animated: true,
      keyboard: false,
      backdrop : 'static',  initialState: {
        vm: this.vm
      }});
  this.modalRef.content.tokenGenerated.subscribe((token: string) => {
  this.createCustomerForToken(token);
  });
  }
  createCustomerForToken(token: string)
  {
    this.paymentService.createCustomer(this.paymentService.GenerateCustomer('', '', token)).subscribe((data: any) => {

    });
}
  closeCardModal=()=> {
    this.modalRef?.hide();
  }

async loadStripe() {
  this.stripe = await loadStripe(this.publishKey);
  if (this.stripe) {
    const elements = this.stripe.elements();
    this.cardElement = elements.create('card', {
      hidePostalCode: true
    });
    this.cardElement.mount('#pay-card-element');
  } else {
    console.error('Stripe is not initialized');
  }
}

async handlePayment(event: Event) {
  event.preventDefault();
  this.loading = true;

  if (this.stripe) {
    if(!this.isPaymentMethod){
      const { paymentMethod, error } = await this.stripe.createPaymentMethod({
        type: 'card',
        card: this.cardElement,
        billing_details: { name: this.cardHolderName },
      });

      if (error) {
        const displayError = document.getElementById('pay-card-errors');
      if (displayError && error.message) {
        displayError.textContent = error.message;
        this.loading = false;
      }
      } else {
        // Send payment method to backend

        this.makePayment(paymentMethod.id);
      }
    }
    else
    {
      this.paymentService.createPayment(this.amount).subscribe(async (data: any) => {

      if (this.stripe) {
        const { error } = await this.stripe.confirmCardPayment(this.secretKey, {
          payment_method: {
            card: this.cardElement,
            billing_details: {
              name: this.cardHolderName,
            },
          },
        });

        if (error) {
          this.error = error.message ? error.message : 'An unknown error occurred';
          this.loading = false;
        } else {
          this.error = '';
          this.loading = false;
        }
      } else {
        this.error = 'Stripe is not initialized';
        this.loading = false;
      }
    });
    }
  } else {
    this.error = 'Stripe is not initialized';
    this.loading = false;
  }

}

makePayment(paymentMethodId: string) {
  this.paymentService.createPaymentIntent(this.paymentService.GeneratePaymentIntent(this.amount, false, paymentMethodId)).subscribe((data: any) => {

  });
}
openpaymentModal=()=> {
  this.modalRef = this.modalService.show(this.paymentModal,{ animated: true,
    keyboard: false,
    backdrop : 'static',  initialState: {
      vm: this.vm
    }});
    this.loadStripe();
}
}


Sample UI

Conclusion
This approach enables the creation of a robust payment system integrated with Stripe, giving you the ability to scale and manage payments efficiently within your application. Attached is the sample code.