feature: implement payment verification, make cart as converted after successful payment
This commit is contained in:
parent
85b0fbf499
commit
aa35fe44b3
@ -6,10 +6,11 @@ import { Cart } from "@app/shared/components/cart/cart";
|
|||||||
import { CartModel } from "@app/core/models/cart.model";
|
import { CartModel } from "@app/core/models/cart.model";
|
||||||
import { map } from "rxjs";
|
import { map } from "rxjs";
|
||||||
import { AsyncPipe } from "@angular/common";
|
import { AsyncPipe } from "@angular/common";
|
||||||
|
import { RouterLink } from "@angular/router";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-header",
|
selector: "app-header",
|
||||||
imports: [LucideAngularModule, Cart, AsyncPipe],
|
imports: [LucideAngularModule, Cart, AsyncPipe, RouterLink],
|
||||||
templateUrl: "./header.html",
|
templateUrl: "./header.html",
|
||||||
styleUrl: "./header.css",
|
styleUrl: "./header.css",
|
||||||
})
|
})
|
||||||
@ -19,8 +20,7 @@ export class Header {
|
|||||||
readonly SearchIcon = Search;
|
readonly SearchIcon = Search;
|
||||||
readonly authService = inject(AuthService);
|
readonly authService = inject(AuthService);
|
||||||
readonly cartService = inject(CartService);
|
readonly cartService = inject(CartService);
|
||||||
protected readonly AuthState = AuthState;
|
|
||||||
|
|
||||||
cartItems$ = this.cartService.cartItems$;
|
cartItems$ = this.cartService.cartItems$;
|
||||||
cartItemCount = this.cartItems$.pipe(map((cart: CartModel) => cart.itemsCount ?? 0));
|
cartItemCount = this.cartItems$.pipe(map((cart: CartModel) => cart.itemsCount ?? 0));
|
||||||
|
protected readonly AuthState = AuthState;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
|
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
|
||||||
import { effect, inject, Injectable, signal } from "@angular/core";
|
import { effect, inject, Injectable } from "@angular/core";
|
||||||
import { API_URL } from "../tokens/api-url-tokens";
|
import { API_URL } from "../tokens/api-url-tokens";
|
||||||
import { CartItemModel, CartItemRequest, CartModel } from "../models/cart.model";
|
import { CartItemRequest, CartModel } from "../models/cart.model";
|
||||||
import { AuthService, AuthState } from "@core/services/auth-service";
|
import { AuthService } from "@core/services/auth-service";
|
||||||
import { Cart } from "@app/shared/components/cart/cart";
|
|
||||||
import { BehaviorSubject, tap } from "rxjs";
|
import { BehaviorSubject, tap } from "rxjs";
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -29,7 +28,7 @@ export class CartService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchCart() {
|
fetchCart() {
|
||||||
return this.http.get<CartModel>(this.apiUrl + "/cart").subscribe({
|
return this.http.get<CartModel>(this.apiUrl + "/cart").subscribe({
|
||||||
next: (data) => this._cartItems.next(data),
|
next: (data) => this._cartItems.next(data),
|
||||||
error: (error: HttpErrorResponse) => {
|
error: (error: HttpErrorResponse) => {
|
||||||
|
|||||||
@ -1,27 +1,47 @@
|
|||||||
<div class="flex flex-col gap-4 items-center justify-center">
|
<div class="flex flex-col gap-4 items-center justify-center">
|
||||||
|
<app-loading-spinner [isLoading]="isProcessing()">
|
||||||
<div class="card max-w-md text-center flex items-center flex-col gap-4">
|
<div class="card max-w-md text-center flex items-center flex-col gap-4">
|
||||||
<div class="rounded-full bg-green-100 p-4">
|
<div
|
||||||
|
[class.bg-green-100]="paymentData()!.isSuccess"
|
||||||
|
[class.bg-red-100]="!paymentData()!.isSuccess"
|
||||||
|
class="rounded-full p-4"
|
||||||
|
>
|
||||||
|
@if (paymentData()!.isSuccess) {
|
||||||
<lucide-angular [img]="BadgeCheck" class="w-6 h-6 text-green-500" />
|
<lucide-angular [img]="BadgeCheck" class="w-6 h-6 text-green-500" />
|
||||||
|
} @else {
|
||||||
|
<lucide-angular [img]="BadgeQuestionMark" class="w-6 h-6 text-red-500" />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xl font-medium text-green-600">Payment Successful</p>
|
<p
|
||||||
<p class="text-sm text-gray-400">
|
[class.text-green-500]="paymentData()!.isSuccess"
|
||||||
Your payment processed successfully. You will receive a confirmation email shortly.
|
[class.text-red-500]="!paymentData()!.isSuccess"
|
||||||
|
class="text-xl font-medium"
|
||||||
|
>
|
||||||
|
{{ paymentData()!.message }}
|
||||||
</p>
|
</p>
|
||||||
|
<p class="text-sm text-gray-400">
|
||||||
|
@if (paymentData()!.isSuccess) { Your payment processed successfully. You will receive a
|
||||||
|
confirmation email shortly. } @else { If money is debited then wait for few hours then check
|
||||||
|
payment status. }
|
||||||
|
</p>
|
||||||
|
@if (paymentData()!.isSuccess) {
|
||||||
<article class="rounded-xl bg-gray-100 px-3 py-4 text-gray-500 text-sm w-full space-y-4">
|
<article class="rounded-xl bg-gray-100 px-3 py-4 text-gray-500 text-sm w-full space-y-4">
|
||||||
<article class="flex justify-between border-b border-b-gray-200 pb-4">
|
<article class="flex justify-between border-b border-b-gray-200 pb-4">
|
||||||
<p class="font-medium">Amount</p>
|
<p class="font-medium">Amount</p>
|
||||||
<p>Rs. 20000</p>
|
<p>Rs.{{ paymentData()?.amount }}</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="flex justify-between">
|
<article class="flex justify-between">
|
||||||
<p class="font-medium">Transaction ID</p>
|
<p class="font-medium">Transaction ID</p>
|
||||||
<p class="truncate">text_ch_989789y789jhhg8h8</p>
|
<p class="truncate">{{ paymentData()?.transactionId }}</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="flex justify-between">
|
<article class="flex justify-between">
|
||||||
<p class="font-medium">Payment method</p>
|
<p class="font-medium">Payment method</p>
|
||||||
<p>Stripe Checkout</p>
|
<p>{{ paymentData()?.paymentMethod | uppercase }}</p>
|
||||||
</article>
|
</article>
|
||||||
</article>
|
</article>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</app-loading-spinner>
|
||||||
<app-go-back class="w-min-content! flex-nowrap" route="/" text="Continue Shopping" />
|
<app-go-back class="w-min-content! flex-nowrap" route="/" text="Continue Shopping" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,24 +1,73 @@
|
|||||||
import { Component, inject } from "@angular/core";
|
import { Component, DestroyRef, inject, OnInit, signal } from "@angular/core";
|
||||||
import { BadgeCheck, LucideAngularModule } from "lucide-angular";
|
import { BadgeCheck, BadgeQuestionMark, LucideAngularModule } from "lucide-angular";
|
||||||
import { GoBack } from "@shared/components/go-back/go-back";
|
import { GoBack } from "@shared/components/go-back/go-back";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { LoadingSpinner } from "@shared/components/loading-spinner/loading-spinner";
|
||||||
|
import {
|
||||||
|
OrderService,
|
||||||
|
PaymentVerificationResponse,
|
||||||
|
} from "@app/features/checkout/services/order-service";
|
||||||
|
import { finalize, tap } from "rxjs";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
|
import { UpperCasePipe } from "@angular/common";
|
||||||
|
import { CartService } from "@core/services/cart-service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-confirmation",
|
selector: "app-confirmation",
|
||||||
imports: [LucideAngularModule, GoBack],
|
imports: [LucideAngularModule, GoBack, LoadingSpinner, UpperCasePipe],
|
||||||
templateUrl: "./confirmation.html",
|
templateUrl: "./confirmation.html",
|
||||||
styleUrl: "./confirmation.css",
|
styleUrl: "./confirmation.css",
|
||||||
})
|
})
|
||||||
class Confirmation {
|
export default class Confirmation implements OnInit {
|
||||||
|
destroyRef = inject(DestroyRef);
|
||||||
|
protected paymentData = signal<PaymentVerificationResponse | null>(null);
|
||||||
|
protected isProcessing = signal(false);
|
||||||
protected readonly BadgeCheck = BadgeCheck;
|
protected readonly BadgeCheck = BadgeCheck;
|
||||||
|
protected readonly BadgeQuestionMark = BadgeQuestionMark;
|
||||||
private route = inject(ActivatedRoute);
|
private route = inject(ActivatedRoute);
|
||||||
|
private orderService = inject(OrderService);
|
||||||
|
private cartService = inject(CartService);
|
||||||
private orderId: string | null = null;
|
private orderId: string | null = null;
|
||||||
private sessionId: string | null = null;
|
private sessionId: string | null = null;
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.orderId = this.route.snapshot.paramMap.get("order_id");
|
this.orderId = this.route.snapshot.queryParamMap.get("order_id");
|
||||||
this.sessionId = this.route.snapshot.paramMap.get("session_id");
|
this.sessionId = this.route.snapshot.queryParamMap.get("session_id");
|
||||||
}
|
this.checkConfirmation();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Confirmation;
|
private checkConfirmation() {
|
||||||
|
if (!this.orderId || !this.sessionId) {
|
||||||
|
console.error("Missing order ID or session ID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.isProcessing.set(true);
|
||||||
|
const orderId = parseInt(this.orderId!);
|
||||||
|
this.orderService
|
||||||
|
.verifyPayment(orderId, this.sessionId!)
|
||||||
|
.pipe(
|
||||||
|
tap((data) => {
|
||||||
|
this.paymentData.set(data);
|
||||||
|
if (data && data.isSuccess) {
|
||||||
|
this.cartService.fetchCart();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
finalize(() => this.isProcessing.set(false)),
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: () => {
|
||||||
|
console.log("Payment verified successfully");
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
this.isProcessing.set(false);
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,14 @@ import { FormControl, Validators } from "@angular/forms";
|
|||||||
import { tap } from "rxjs";
|
import { tap } from "rxjs";
|
||||||
import { SessionStorageService } from "@core/services/session-storage.service";
|
import { SessionStorageService } from "@core/services/session-storage.service";
|
||||||
|
|
||||||
|
export interface PaymentVerificationResponse {
|
||||||
|
isSuccess: boolean;
|
||||||
|
message: string;
|
||||||
|
amount: number | null;
|
||||||
|
transactionId: string | null;
|
||||||
|
paymentMethod: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CheckoutResponse {
|
export interface CheckoutResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
amount: number;
|
amount: number;
|
||||||
@ -61,4 +69,11 @@ export class OrderService {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verifyPayment(orderId: number, sessionId: string) {
|
||||||
|
return this.http.post<PaymentVerificationResponse>(`${this.apiUrl}/payments/verify`, {
|
||||||
|
orderId: orderId,
|
||||||
|
sessionId: sessionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user