Ship internal tools in hours, not weeks. Real auth, users, jobs, audit logs, and cohesive UI included. Early access $249 $499 → [Get it now]

How to integrate PayPal with Angular

Integrating a secure payment gateway like PayPal is a critical requirement for modern e-commerce and SaaS applications.
With over 25 years of experience in software development and as the creator of CoreUI, I have implemented numerous payment systems that prioritize security and user experience.
The most efficient and modern approach is to utilize the PayPal JavaScript SDK to render Smart Payment Buttons directly within your Angular components.
This method ensures PCI compliance by keeping sensitive data on PayPal’s servers while providing a native feel within your application.

Use the PayPal JavaScript SDK to render Smart Payment Buttons within a dedicated Angular component.

// Example of rendering PayPal buttons in an Angular component
declare var paypal: any

paypal.Buttons({
  createOrder: (data: any, actions: any) => {
    return actions.order.create({
      purchase_units: [{
        amount: { value: '10.00' }
      }]
    })
  },
  onApprove: (data: any, actions: any) => {
    return actions.order.capture().then((details: any) => {
      console.log('Transaction completed by ' + details.payer.name.given_name)
    })
  }
}).render('#paypal-button-container')

This approach leverages the official SDK to handle the heavy lifting of the transaction flow, including authentication and authorization, while giving you hooks to manage your application’s state upon success or failure.

1. Load the PayPal SDK in your Project

The first step is to include the PayPal SDK script in your index.html file. It is recommended to load this globally to ensure the paypal object is available when your components initialize. Replace YOUR_CLIENT_ID with your actual PayPal Developer Client ID.

<!-- src/index.html -->
<!doctype html>
<html lang='en'>
<head>
  <meta charset='utf-8'>
  <title>Angular PayPal Integration</title>
  <base href='/'>
  <meta name='viewport' content='width=device-width, initial-scale=1'>
  <!-- PayPal JavaScript SDK -->
  <script src='https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID&currency=USD'></script>
</head>
<body>
  <app-root></app-root>
</body>
</html>

Loading the script in the head ensures that the PayPal global object is ready before Angular tries to render the checkout view. This is essential for preventing “paypal is not defined” errors during the component lifecycle.

2. Create the Checkout Component Template

In your Angular component, you need a container element where the PayPal buttons will be injected. Using CoreUI Cards provides a professional structure for your checkout interface.

<!-- checkout.component.html -->
<c-card>
  <c-card-header>
    <strong>Order Summary</strong>
  </c-card-header>
  <c-card-body>
    <p>Complete your purchase securely via PayPal.</p>
    <div class='mt-4'>
      <!-- The container where PayPal will render the buttons -->
      <div id='paypal-button-container'></div>
    </div>
    @if (loading) {
      <div class='text-center'>
        <c-spinner color='primary'></c-spinner>
      </div>
    }
  </c-card-body>
</c-card>

By using a dedicated div with a specific ID, you allow the PayPal SDK to target this element and render the interactive buttons precisely where you want them. We also include a CoreUI Spinner to provide visual feedback during initialization.

3. Implement the Component Logic

In your TypeScript file, you will interface with the global paypal object. Since the SDK is loaded externally, we declare it at the top of the file to satisfy the TypeScript compiler. The component uses Angular’s standalone API with the necessary CoreUI module imports.

// checkout.component.ts
import { Component, AfterViewInit } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { CardModule, SpinnerModule } from '@coreui/angular'

declare var paypal: any

@Component({
  selector: 'app-checkout',
  standalone: true,
  imports: [CardModule, SpinnerModule],
  templateUrl: './checkout.component.html'
})
export class CheckoutComponent implements AfterViewInit {
  loading = true

  constructor(private http: HttpClient) {}

  ngAfterViewInit(): void {
    this.initPayPalButtons()
  }

  initPayPalButtons(): void {
    paypal.Buttons({
      style: {
        layout: 'vertical',
        color: 'blue',
        shape: 'rect',
        label: 'paypal'
      },
      createOrder: (data: any, actions: any) => {
        return actions.order.create({
          purchase_units: [{
            amount: {
              currency_code: 'USD',
              value: '29.99'
            }
          }]
        })
      },
      onApprove: async (data: any, actions: any) => {
        const order = await actions.order.capture()
        this.handleSuccess(data.orderID, order)
      },
      onError: (err: any) => {
        console.error('PayPal Error', err)
      }
    }).render('#paypal-button-container')

    this.loading = false
  }

  handleSuccess(orderID: string, details: any): void {
    console.log('Payment Successful', details)
    // Send to backend for verification (see Section 5)
  }
}

The ngAfterViewInit hook is the perfect place to initialize the buttons because it guarantees that the #paypal-button-container exists in the DOM. The createOrder function specifies the transaction details, such as the amount and currency.

4. Handling Dynamic Amounts

In a real-world scenario, you won’t hardcode the price. You should pull the order total from your application state or a service. This ensures that the user pays the correct amount calculated in your cart.

// Dynamically setting the transaction value
createOrder: (data: any, actions: any) => {
  const totalAmount = this.cartService.getTotal()
  return actions.order.create({
    purchase_units: [{
      description: 'CoreUI Pro License',
      amount: {
        currency_code: 'USD',
        value: totalAmount.toFixed(2)
      }
    }]
  })
}

When dealing with decimals, ensure you format the number correctly to match PayPal’s expected string format (e.g., "29.99"). Use toFixed(2) to guarantee two decimal places. For more on number formatting, see how to format a number as currency in JavaScript.

5. Security and Backend Validation

Capturing the order on the client side is convenient, but for production apps, you must verify the transaction on your server. This prevents users from tampering with the payment amount in the browser console.

// Recommended pattern: Verify payment on your backend
onApprove: async (data: any, actions: any) => {
  const order = await actions.order.capture()

  // Send the order ID to your server to verify and fulfill the order
  this.http.post('/api/verify-payment', {
    orderID: data.orderID,
    details: order
  }).subscribe({
    next: (response) => {
      // Finalize order in the UI — redirect to success page
      console.log('Payment verified', response)
    },
    error: (err) => {
      console.error('Verification failed', err)
    }
  })
}

This two-step process — client-side capture and server-side validation — is the standard for secure web applications. It ensures that the transaction recorded in your database matches the actual payment received by PayPal. Note the HttpClient must be available via provideHttpClient() in your application config or HttpClientModule in your root module.

Best Practice Note:

Always use environment variables for your Client IDs to avoid accidentally committing sandbox or production credentials to your repository. If you are just starting, ensure you know how to create a new Angular project with the proper structure for scale. In CoreUI templates, we recommend encapsulating this logic in a dedicated PaymentService to keep your components clean and testable. Using the Angular Dashboard Template can provide you with a pre-configured environment where integrating such services is seamless.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author