feature: implement payment verification, make cart as converted after successful payment

This commit is contained in:
kusowl 2026-03-25 15:24:39 +05:30
parent 85b0fbf499
commit aa35fe44b3
5 changed files with 121 additions and 38 deletions

View File

@ -6,10 +6,11 @@ import { Cart } from "@app/shared/components/cart/cart";
import { CartModel } from "@app/core/models/cart.model";
import { map } from "rxjs";
import { AsyncPipe } from "@angular/common";
import { RouterLink } from "@angular/router";
@Component({
selector: "app-header",
imports: [LucideAngularModule, Cart, AsyncPipe],
imports: [LucideAngularModule, Cart, AsyncPipe, RouterLink],
templateUrl: "./header.html",
styleUrl: "./header.css",
})
@ -19,8 +20,7 @@ export class Header {
readonly SearchIcon = Search;
readonly authService = inject(AuthService);
readonly cartService = inject(CartService);
protected readonly AuthState = AuthState;
cartItems$ = this.cartService.cartItems$;
cartItemCount = this.cartItems$.pipe(map((cart: CartModel) => cart.itemsCount ?? 0));
protected readonly AuthState = AuthState;
}

View File

@ -1,9 +1,8 @@
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 { CartItemModel, CartItemRequest, CartModel } from "../models/cart.model";
import { AuthService, AuthState } from "@core/services/auth-service";
import { Cart } from "@app/shared/components/cart/cart";
import { CartItemRequest, CartModel } from "../models/cart.model";
import { AuthService } from "@core/services/auth-service";
import { BehaviorSubject, tap } from "rxjs";
@Injectable({
@ -29,7 +28,7 @@ export class CartService {
});
}
private fetchCart() {
fetchCart() {
return this.http.get<CartModel>(this.apiUrl + "/cart").subscribe({
next: (data) => this._cartItems.next(data),
error: (error: HttpErrorResponse) => {

View File

@ -1,27 +1,47 @@
<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="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" />
} @else {
<lucide-angular [img]="BadgeQuestionMark" class="w-6 h-6 text-red-500" />
}
</div>
<p class="text-xl font-medium text-green-600">Payment Successful</p>
<p class="text-sm text-gray-400">
Your payment processed successfully. You will receive a confirmation email shortly.
<p
[class.text-green-500]="paymentData()!.isSuccess"
[class.text-red-500]="!paymentData()!.isSuccess"
class="text-xl font-medium"
>
{{ paymentData()!.message }}
</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="flex justify-between border-b border-b-gray-200 pb-4">
<p class="font-medium">Amount</p>
<p>Rs. 20000</p>
<p>Rs.{{ paymentData()?.amount }}</p>
</article>
<article class="flex justify-between">
<p class="font-medium">Transaction ID</p>
<p class="truncate">text_ch_989789y789jhhg8h8</p>
<p class="truncate">{{ paymentData()?.transactionId }}</p>
</article>
<article class="flex justify-between">
<p class="font-medium">Payment method</p>
<p>Stripe Checkout</p>
<p>{{ paymentData()?.paymentMethod | uppercase }}</p>
</article>
</article>
}
</div>
</app-loading-spinner>
<app-go-back class="w-min-content! flex-nowrap" route="/" text="Continue Shopping" />
</div>

View File

@ -1,24 +1,73 @@
import { Component, inject } from "@angular/core";
import { BadgeCheck, LucideAngularModule } from "lucide-angular";
import { Component, DestroyRef, inject, OnInit, signal } from "@angular/core";
import { BadgeCheck, BadgeQuestionMark, LucideAngularModule } from "lucide-angular";
import { GoBack } from "@shared/components/go-back/go-back";
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({
selector: "app-confirmation",
imports: [LucideAngularModule, GoBack],
imports: [LucideAngularModule, GoBack, LoadingSpinner, UpperCasePipe],
templateUrl: "./confirmation.html",
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 BadgeQuestionMark = BadgeQuestionMark;
private route = inject(ActivatedRoute);
private orderService = inject(OrderService);
private cartService = inject(CartService);
private orderId: string | null = null;
private sessionId: string | null = null;
ngOnInit() {
this.orderId = this.route.snapshot.paramMap.get("order_id");
this.sessionId = this.route.snapshot.paramMap.get("session_id");
this.orderId = this.route.snapshot.queryParamMap.get("order_id");
this.sessionId = this.route.snapshot.queryParamMap.get("session_id");
this.checkConfirmation();
}
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;
}
}
}
export default Confirmation;

View File

@ -6,6 +6,14 @@ import { FormControl, Validators } from "@angular/forms";
import { tap } from "rxjs";
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 {
success: boolean;
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,
});
}
}