carnet checkout updates
This commit is contained in:
parent
98acaa709b
commit
6787450019
@ -15,6 +15,7 @@ export const serverRoutes: ServerRoute[] = [
|
||||
{ path: ':appId/add-holder', renderMode: RenderMode.Client },
|
||||
{ path: ':appId/edit-holder/:holderid', renderMode: RenderMode.Client },
|
||||
{ path: ':appId/terms', renderMode: RenderMode.Client },
|
||||
{ path: ':appId/checkout', renderMode: RenderMode.Client },
|
||||
{
|
||||
path: '**',
|
||||
renderMode: RenderMode.Prerender
|
||||
|
||||
@ -23,6 +23,7 @@ export const routes: Routes = [
|
||||
{ path: 'extend-carnet/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
|
||||
{ path: 'additional-sets/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
|
||||
{ path: 'terms', loadComponent: () => import('./carnet/terms-conditions/terms-conditions.component').then(m => m.TermsConditionsComponent) },
|
||||
{ path: 'checkout', loadComponent: () => import('./carnet/checkout/checkout.component').then(m => m.CheckoutComponent) },
|
||||
{ path: '', redirectTo: 'home', pathMatch: 'full' }
|
||||
],
|
||||
canActivate: [AuthGuard, AppIdGuard]
|
||||
|
||||
60
src/app/carnet/checkout/checkout.component.html
Normal file
60
src/app/carnet/checkout/checkout.component.html
Normal file
@ -0,0 +1,60 @@
|
||||
<div class="checkout-container">
|
||||
<mat-card class="card-body" appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Checkout</mat-card-title>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<!-- Carnet Summary -->
|
||||
<div class="order-summary">
|
||||
<div *ngIf="estimatedFees.basicFee" class="summary-item">
|
||||
<span>Basic fee: </span>
|
||||
<span>{{estimatedFees.basicFee | currency}}</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="estimatedFees.counterFoilFee" class="summary-item">
|
||||
<span>Counterfoil fee: </span>
|
||||
<span>{{estimatedFees.counterFoilFee | currency}}</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="estimatedFees.continuationSheetFee" class="summary-item">
|
||||
<span>Continuation sheet fee: </span>
|
||||
<span>{{estimatedFees.continuationSheetFee | currency}}</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="estimatedFees.expeditedFee" class="summary-item">
|
||||
<span>Expedited fee: </span>
|
||||
<span>{{estimatedFees.expeditedFee | currency}}</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="estimatedFees.shippingFee" class="summary-item">
|
||||
<span>Shipping fee: </span>
|
||||
<span>{{estimatedFees.shippingFee | currency}}</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="estimatedFees.bondPremium" class="summary-item">
|
||||
<span>Bond Premium: </span>
|
||||
<span>{{estimatedFees.bondPremium | currency}}</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="estimatedFees.cargoPremium" class="summary-item">
|
||||
<span>Cargo Premium: </span>
|
||||
<span>{{estimatedFees.cargoPremium | currency}}</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="estimatedFees.ldiPremium" class="summary-item">
|
||||
<span>LDI Premium: </span>
|
||||
<span>{{estimatedFees.ldiPremium | currency}}</span>
|
||||
</div>
|
||||
<div class="summary-item total">
|
||||
<strong>Total:</strong>
|
||||
<strong>{{ orderTotal | currency }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="paypal-container">
|
||||
<div #paypalcontrol></div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
36
src/app/carnet/checkout/checkout.component.scss
Normal file
36
src/app/carnet/checkout/checkout.component.scss
Normal file
@ -0,0 +1,36 @@
|
||||
.checkout-container {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
|
||||
.mat-mdc-card-header {
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
mat-card-title {
|
||||
color: var(--mat-sys-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.order-summary {
|
||||
.summary-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.summary-item.total {
|
||||
border-top: 1px solid #ddd;
|
||||
padding-top: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.paypal-container {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
162
src/app/carnet/checkout/checkout.component.ts
Normal file
162
src/app/carnet/checkout/checkout.component.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import { Component, ElementRef, inject, ViewChild } from '@angular/core';
|
||||
import { PaymentService } from '../../core/services/carnet/payment.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Fees } from '../../core/models/carnet/fee';
|
||||
import { CarnetService } from '../../core/services/carnet/carnet.service';
|
||||
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
|
||||
import { NavigationService } from '../../core/services/common/navigation.service';
|
||||
import { NotificationService } from '../../core/services/common/notification.service';
|
||||
import { StorageService } from '../../core/services/common/storage.service';
|
||||
import { finalize } from 'rxjs';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
|
||||
// This is necessary to tell TypeScript that a global 'paypal' object exists.
|
||||
declare var paypal: any;
|
||||
|
||||
@Component({
|
||||
selector: 'app-checkout',
|
||||
imports: [CommonModule, AngularMaterialModule],
|
||||
templateUrl: './checkout.component.html',
|
||||
styleUrl: './checkout.component.scss'
|
||||
})
|
||||
export class CheckoutComponent {
|
||||
paymentCompleted: boolean = false;
|
||||
paymentError: boolean = false;
|
||||
isLoading: boolean = false;
|
||||
paymentDetails: any = null;
|
||||
currentApplicationDetails: { headerid: number, applicationName: string, applicationType: string } | null = null;
|
||||
changeInProgress: boolean = false;
|
||||
estimatedFees: Fees = {};
|
||||
|
||||
@ViewChild('paypalcontrol', { static: true }) paypalElement!: ElementRef;
|
||||
|
||||
orderDetails = {
|
||||
price: '',
|
||||
description: '',
|
||||
};
|
||||
|
||||
orderTotal = 0.00;
|
||||
currency = 'USD';
|
||||
|
||||
private paymentService = inject(PaymentService);
|
||||
private notificationService = inject(NotificationService);
|
||||
private errorHandler = inject(ApiErrorHandlerService);
|
||||
private storageService = inject(StorageService);
|
||||
private navigationService = inject(NavigationService);
|
||||
private carnetService = inject(CarnetService);
|
||||
|
||||
constructor() {
|
||||
this.currentApplicationDetails = this.storageService.get<{ headerid: number, applicationName: string, applicationType: string }>('currentapplication')
|
||||
this.orderDetails.description = ` 'Carnet Application - ${this.currentApplicationDetails?.applicationName}`
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.currentApplicationDetails?.headerid) {
|
||||
this.carnetService.getEstimatedFees(this.currentApplicationDetails?.headerid).subscribe({
|
||||
next: (data: Fees) => {
|
||||
if (data) {
|
||||
this.estimatedFees = data;
|
||||
this.calculateTotal();
|
||||
}
|
||||
},
|
||||
error: (error: any) => {
|
||||
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to get estimated fees');
|
||||
this.notificationService.showError(errorMessage);
|
||||
console.error('Error getting estimated fees:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.initConfig();
|
||||
}
|
||||
|
||||
initConfig(): void {
|
||||
paypal
|
||||
.Buttons({
|
||||
style: {
|
||||
layout: 'vertical', // or 'horizontal'
|
||||
color: 'gold',
|
||||
shape: 'pill',
|
||||
label: 'pay',
|
||||
height: 35 // Height in pixels (default is ~35-45px)
|
||||
|
||||
// 'paypal' – Standard PayPal button [defoult]
|
||||
// 'checkout' – "Checkout with PayPal"
|
||||
// 'buynow' – "Buy Now with PayPal"
|
||||
// 'pay' – "Pay with PayPal"
|
||||
// 'installment' – Displays installment information
|
||||
// 'subscribe' – For subscriptions
|
||||
// 'donate' – For donations
|
||||
// pay : Pay with [PayPal]
|
||||
// buynow : [PayPal] Buy Now
|
||||
// Error: Expected style.height to be between 25px and 55px - got 10px
|
||||
// width width usually doesn’t need to be set or must be reasonable.
|
||||
},
|
||||
// 1. Call your server to set up the transaction
|
||||
createOrder: async (data: any, actions: any) => {
|
||||
try {
|
||||
// Call the createOrder method from your service
|
||||
const orderId = await this.paymentService.createOrder(this.orderDetails);
|
||||
// console.log('Order ID created by server:', orderId);
|
||||
return orderId;
|
||||
} catch (error) {
|
||||
this.notificationService.showError('Failed to initiate the payment');
|
||||
console.error('Error creating order:', error);
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
// 2. Call your server to finalize the transaction
|
||||
onApprove: async (data: any, actions: any) => {
|
||||
try {
|
||||
// data.orderID is the ID of the transaction from PayPal
|
||||
const captureDetails = await this.paymentService.completePayment(data.orderID);
|
||||
|
||||
// console.log('Capture details:', captureDetails);
|
||||
|
||||
// You can now redirect the user or update the UI
|
||||
this.changeInProgress = true;
|
||||
this.carnetService.submitApplication(this.currentApplicationDetails?.headerid!).pipe(finalize(() => {
|
||||
this.changeInProgress = false;
|
||||
})).subscribe({
|
||||
next: () => {
|
||||
this.notificationService.showSuccess('Application submitted successfully');
|
||||
this.storageService.removeItem('currentapplication');
|
||||
this.navigationService.navigate(["home"]);
|
||||
},
|
||||
error: (error) => {
|
||||
this.notificationService.showError('Failed to submit application');
|
||||
console.error('Error submitting the application', error);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
this.notificationService.showError('Failed to complete the payment');
|
||||
console.error('Error completing the payment:', error);
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
// 3. Handle errors
|
||||
onError: (error: any) => {
|
||||
this.notificationService.showError('Payment transaction failed');
|
||||
console.error('An error occurred during the transaction:', error);
|
||||
},
|
||||
|
||||
// 4. Handle cancellation
|
||||
onCancel: (data: any) => {
|
||||
}
|
||||
}).render(this.paypalElement.nativeElement)
|
||||
.catch((error: any) => {
|
||||
console.error('Failed to render PayPal Buttons:', error);
|
||||
});
|
||||
}
|
||||
|
||||
calculateTotal(): void {
|
||||
const validFees = Object.values(this.estimatedFees).filter(
|
||||
(fee): fee is number => typeof fee === 'number'
|
||||
);
|
||||
this.orderTotal = validFees.reduce((total, fee) => total + fee, 0);
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,8 @@ import { StorageService } from '../../core/services/common/storage.service';
|
||||
import { CarnetService } from '../../core/services/carnet/carnet.service';
|
||||
import { Fees } from '../../core/models/carnet/fee';
|
||||
import { finalize } from 'rxjs';
|
||||
import { ShippingService } from '../../core/services/carnet/shipping.service';
|
||||
import { Shipping } from '../../core/models/carnet/shipping';
|
||||
|
||||
@Component({
|
||||
selector: 'app-terms-conditions',
|
||||
@ -26,18 +28,20 @@ import { finalize } from 'rxjs';
|
||||
styleUrl: './terms-conditions.component.scss'
|
||||
})
|
||||
export class TermsConditionsComponent {
|
||||
|
||||
isLoading: boolean = false;
|
||||
currentDate = new Date();
|
||||
hasReadAllTerms = false;
|
||||
currentApplicationDetails: { headerid: number, applicationName: string, applicationType: string } | null = null;
|
||||
changeInProgress: boolean = false;
|
||||
estimatedFees: Fees = {};
|
||||
needCheckOut: boolean = false;
|
||||
|
||||
private notificationService = inject(NotificationService);
|
||||
private errorHandler = inject(ApiErrorHandlerService);
|
||||
private storageService = inject(StorageService);
|
||||
private navigationService = inject(NavigationService);
|
||||
private carnetService = inject(CarnetService);
|
||||
private shippingService = inject(ShippingService);
|
||||
|
||||
constructor() {
|
||||
this.currentApplicationDetails = this.storageService.get<{ headerid: number, applicationName: string, applicationType: string }>('currentapplication')
|
||||
@ -57,25 +61,31 @@ export class TermsConditionsComponent {
|
||||
console.error('Error getting estimated fees:', error);
|
||||
}
|
||||
});
|
||||
|
||||
this.getPaymentType(this.currentApplicationDetails?.headerid);
|
||||
}
|
||||
}
|
||||
|
||||
onAccept(): void {
|
||||
this.changeInProgress = true;
|
||||
this.carnetService.submitApplication(this.currentApplicationDetails?.headerid!).pipe(finalize(() => {
|
||||
this.changeInProgress = false;
|
||||
})).subscribe({
|
||||
next: () => {
|
||||
this.notificationService.showSuccess('Application submitted successfully');
|
||||
this.storageService.removeItem('currentapplication');
|
||||
this.navigationService.navigate(["home"]);
|
||||
},
|
||||
error: (error) => {
|
||||
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to submit application');
|
||||
this.notificationService.showError(errorMessage);
|
||||
console.error('Error submitting the application', error);
|
||||
}
|
||||
});
|
||||
if (this.needCheckOut) {
|
||||
this.navigationService.navigate(['checkout']);
|
||||
} else {
|
||||
this.changeInProgress = true;
|
||||
this.carnetService.submitApplication(this.currentApplicationDetails?.headerid!).pipe(finalize(() => {
|
||||
this.changeInProgress = false;
|
||||
})).subscribe({
|
||||
next: () => {
|
||||
this.notificationService.showSuccess('Application submitted successfully');
|
||||
this.storageService.removeItem('currentapplication');
|
||||
this.navigationService.navigate(["home"]);
|
||||
},
|
||||
error: (error) => {
|
||||
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to submit application');
|
||||
this.notificationService.showError(errorMessage);
|
||||
console.error('Error submitting the application', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onDecline(): void {
|
||||
@ -100,4 +110,24 @@ export class TermsConditionsComponent {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getPaymentType(headerid: number): void {
|
||||
this.isLoading = true;
|
||||
this.shippingService.getShippingData(headerid).pipe(
|
||||
finalize(() => {
|
||||
this.isLoading = false;
|
||||
})
|
||||
).subscribe({
|
||||
next: (shippingData: Shipping) => {
|
||||
if (!shippingData) { // do nothing if empty
|
||||
return;
|
||||
}
|
||||
this.needCheckOut = shippingData.paymentMethod !== 'ACH' && shippingData.paymentMethod !== 'INV';
|
||||
},
|
||||
error: (error: any) => {
|
||||
console.error('Error loading shipping data', error);
|
||||
this.errorHandler.handleApiError(error, 'Failed to load shipping data');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
29
src/app/core/services/carnet/payment.service.ts
Normal file
29
src/app/core/services/carnet/payment.service.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PaymentService {
|
||||
private apiUrl = environment.apiUrl;
|
||||
private apiDb = environment.apiDb;
|
||||
|
||||
private http = inject(HttpClient);
|
||||
|
||||
createOrder(orderDetails: any): Promise<string> {
|
||||
return firstValueFrom(
|
||||
this.http.post<{ id: string }>(`${this.apiUrl}/${this.apiDb}/InitiatePayment`, {})
|
||||
).then(order => order.id);
|
||||
}
|
||||
|
||||
completePayment(orderId: string): Promise<any> {
|
||||
const data = {
|
||||
P_ORDERID: orderId
|
||||
}
|
||||
|
||||
return firstValueFrom(
|
||||
this.http.post(`${this.apiUrl}/oracle/CompletePayment`, data));
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,8 @@
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<script
|
||||
src="https://www.paypal.com/sdk/js?client-id=AQwF2kbt4tIxi0TUeXbALMzbxDdeMh5vy9YssHAhR9jjjdQ7ackCS1lFBZo9D7yeuvNHfNVd5J8Mn03W&components=buttons"></script>
|
||||
</head>
|
||||
|
||||
<body class="mat-typography">
|
||||
|
||||
@ -111,4 +111,8 @@ body {
|
||||
/* Sort */
|
||||
@include mat.sort-overrides((arrow-color: var(--mat-sys-on-primary),
|
||||
));
|
||||
}
|
||||
|
||||
.paypal-buttons {
|
||||
z-index: 5;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user