I'm trying to use the PayPal JavaScript SDK in my Angular project.Below the complete code.
First of all I have added this line of code in the index.html file:
<script src="https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxx"></script>
paypal.component.html:
<div class="paypal-container"><h2>{{ 'PAYMENT.COINS_PACKAGE' | translate }}</h2><div class="coin-options"><div *ngFor="let option of coinOptions" (click)="selectOption(option)" [class.selected]="selectedOption === option" class="coin-option"><h3>{{ option.coins }} Coins</h3><p>{{ option.price | currency: 'EUR' }}</p></div></div><div id="paypal-button-container"></div> <!-- PayPal button will render here --></div>
paypal.component.ts:
import { Component, OnInit } from '@angular/core';import { PaypalModel } from 'src/app/common/paypal-model';import { PaypalService } from 'src/app/services/paypal.service';@Component({ selector: 'app-paypal', templateUrl: './paypal.component.html', styleUrls: ['./paypal.component.scss']})export class PaypalComponent implements OnInit { // Variable to store the currently selected option (coins and price) selectedOption: any = null; // coinOptions: This is a list (an array) of objects, each representing an option the user can choose, // with a certain number of coins and a corresponding price. coinOptions = [ { coins: 100, price: 5.00 }, { coins: 500, price: 20.00 }, { coins: 1000, price: 35.00 } ]; // Injecting the PaypalService into the component through the constructor constructor( private paypalService: PaypalService, ) { // This is a placeholder for future use if you want to check if a user is signed in // this.isSignedIn = false; } // Lifecycle hook that is called after the component is initialized ngOnInit() { // Load the PayPal SDK script dynamically when the component is initialized this.loadPayPalScript(); } // Function to dynamically load the PayPal SDK script loadPayPalScript() { const script = document.createElement('script'); // Create a new script element script.src = `https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR`; // Set the source to PayPal's SDK URL with the client ID and currency script.onload = () => { // Once the script is loaded, render the PayPal button this.renderPayPalButton(); }; document.body.appendChild(script); // Append the script to the document's body } // Function to render the PayPal button and configure its behavior renderPayPalButton() { (window as any).paypal.Buttons({ // Function to create the PayPal order when the button is clicked createOrder: (data: any, actions: any) => { // Create an order with the selected option's price and currency return actions.order.create({ purchase_units: [{ amount: { value: this.selectedOption.price.toString(), // The price of the selected option currency_code: 'EUR' // Currency code set to EUR } }] }); }, // Function that runs after the payment is approved onApprove: (data: any, actions: any) => { // Capture the payment and get the transaction details return actions.order.capture().then((details: any) => { // Capture the current date and time const paymentDate = new Date().toISOString(); // Create a PaypalModel object with the details of the transaction const paypalModel : PaypalModel = { payerID: data.payerID, // ID of the payer orderID: data.orderID, // ID of the order paymentID: data.paymentID, // ID of the payment coinsPurchased: this.selectedOption.coins, // Number of coins purchased amountPaid: this.selectedOption.price, // Amount paid currency: 'EUR', // Currency of the payment payerName: details.payer.name.given_name +''+ details.payer.name.surname, // Full name of the payer payerEmail: details.payer.email_address, // Email address of the payer status: details.status, // Status of the payment (e.g., "COMPLETED") paymentDate: paymentDate // Date and time of the payment }; // Log the transaction details to the console for debugging // console.log: This logs the transaction details to the browser’s console for debugging. console.log('Transaction Details:', JSON.stringify(paypalModel, null, 2)); // Send the transaction details to the backend server using the PaypalService this.paypalService.paypalPayment(paypalModel).subscribe( response => { // Log a success message if the payload is sent successfully console.log('Payment payload correctly sent to backend'); }, error => { // Log an error message if there was an issue sending the payload console.log('An error occurred when sending the payload to the backend!'); } ); }); } }).render('#paypal-button-container'); // Render the PayPal button inside the container with the specified ID } // Function to set the selected payment option when the user selects one selectOption(option: any) { this.selectedOption = option; // Set the selectedOption to the option chosen by the user }}
paypal.component.scss:
.paypal-container { max-width: 400px; margin: 0 auto; margin-top: 7%; margin-bottom: 4%; padding: 20px; text-align: center; background-color: #f5f5f5; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);}h2 { margin-bottom: 20px; color: #333;}.coin-options { display: flex; flex-direction: column; gap: 10px;}.coin-option { padding: 15px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; transition: background-color 0.3s ease;}.coin-option.selected { background-color: #0070ba; color: #fff;}.coin-option h3 { margin: 0;}.coin-option p { margin: 5px 0 0; font-weight: bold;}.paypal-button { margin-top: 20px; padding: 10px 20px; background-color: #0070ba; color: #fff; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.3s ease;}.paypal-button:disabled { background-color: #ccc; cursor: not-allowed;}.paypal-button:hover:not(:disabled) { background-color: #005b9a;}
paypal-model.ts:
export class PaypalModel { payerID: string; orderID: string; paymentID: string; coinsPurchased: string; amountPaid: string; currency: string; payerName: string; payerEmail: string; status: string; paymentDate: string; }
And finally paypal.service.ts:
import { HttpClient, HttpHeaders } from "@angular/common/http";import { Injectable } from "@angular/core";import { ResponseModel } from '../common/generic-model';import { Observable, catchError, retry, throwError } from "rxjs";import { PaypalModel } from "../common/paypal-model";import { environment } from 'src/environments/environment';const httpOptions = { headers: new HttpHeaders({ "Content-Type": "application/json" }),};@Injectable({ providedIn: "root",})export class PaypalService { constructor(private http: HttpClient) {} language = ""; paypalPayment(paypalModel: PaypalModel): Observable<ResponseModel> { return this.http .post<ResponseModel>( environment.baseUrl +"/payment/paypal", JSON.stringify(paypalModel), httpOptions ) .pipe(retry(0), catchError(this.handleError)); } // Error handling handleError(error: any) { let errorMessage = ""; if (error.error instanceof ErrorEvent) { // Get client-side error errorMessage = error.error.message; } else { // Get server-side error errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`; } return throwError(() => { return errorMessage; }); }}
Now, when I run this code with ng serve
and I go to the page http://localhost:4200/app-ui/payment/paypal
from the browser, when I inspect the page, even before starting the payment simulation, I get the following error when running in debug mode:
Exception: TypeError: Cannot assign to read only property 'name' of function 'function(){if(!e)return e=!0,n.apply(this,arguments)}' at En (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:26592) at kn (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:29224) at Object.register (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:33033) at n (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:117879) at https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:118145 at e.dispatch (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:14426) at e.resolve (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:13483) at e.dispatch (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:14646) at e.resolve (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:13483) at https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:13127message: "Cannot assign to read only property 'name' of function 'function(){if(!e)return e=!0,n.apply(this,arguments)}'"stack: "TypeError: Cannot assign to read only property 'name' of function 'function(){if(!e)return e=!0,n.apply(this,arguments)}'\n at En (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:26592)\n at kn (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:29224)\n at Object.register (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:33033)\n at n (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:117879)\n at https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:118145\n at e.dispatch (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:14426)\n at e.resolve (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:13483)\n at e.dispatch (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:14646)\n at e.resolve (https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:13483)\n at https://www.paypal.com/sdk/js?client-id=xxxxxxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR:3:13127"[[Prototype]]: Errorconstructor: ƒ TypeError()message: ""name: "TypeError"[[Prototype]]: Objectconstructor: ƒ Error()message: ""name: "Error"toString: ƒ toString()[[Prototype]]: Objectthis: undefinede: "anonymous::once"
This is the function that triggers the error:
function En(n, e) { try { delete n.name, n.name = e } catch (n) {} return n.__name__ = n.displayName = e, n }
Which is located in the www.paypal.com > sdk > js?client-id=xxxxxxxxxxxxxxxxxxxxxxx¤cy=EUR
path.
Now, it's not a big problem because the payment flow works fine. But I would like to understand why this behavior.
Thank you!