feat(checkout): implement authentication guard for checkout route and add bank account service

This commit is contained in:
ncoronel 2026-07-03 14:07:57 -03:00
parent 0a2d999c00
commit 3971970975
8 changed files with 81 additions and 6 deletions

View File

@ -1,6 +1,10 @@
import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{
path: 'checkout',
renderMode: RenderMode.Client
},
{
path: '**',
renderMode: RenderMode.Server

View File

@ -182,4 +182,16 @@ describe('app routes', () => {
expect(compiled.querySelector('.tenant-status')).not.toBeNull();
expect(compiled.textContent).toContain('No encontramos una tienda para este dominio');
});
it('redirects unauthenticated users from /checkout to /login', async () => {
const { router } = await renderAppAt('/checkout', createTenantServiceStub(), createAuthServiceStub(false));
expect(router.url).toBe('/login');
});
it('allows authenticated users to access /checkout', async () => {
const { router } = await renderAppAt('/checkout', createTenantServiceStub(), createAuthServiceStub(true));
expect(router.url).toBe('/checkout');
});
});

View File

@ -154,7 +154,7 @@ describe('StoreLayoutComponent', () => {
const fixture = TestBed.createComponent(StoreLayoutComponent);
fixture.detectChanges();
fixture.componentInstance.isCartOpen.set(true);
(fixture.componentInstance as any).isCartOpen.set(true);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
@ -166,6 +166,6 @@ describe('StoreLayoutComponent', () => {
fixture.detectChanges();
expect(router.navigate).toHaveBeenCalledWith(['/checkout']);
expect(fixture.componentInstance.isCartOpen()).toBe(false);
expect((fixture.componentInstance as any).isCartOpen()).toBe(false);
});
});

View File

@ -0,0 +1,23 @@
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import { environment } from '../../../environments/environment';
import { BankAccount } from './tenant.interface';
@Injectable({
providedIn: 'root'
})
export class CheckoutService {
private readonly http = inject(HttpClient);
async getSelectedBankAccount(tenantCode: string): Promise<BankAccount> {
const response = await firstValueFrom(
this.http.get<{ data: BankAccount }>(`${environment.url}tenants/${tenantCode}/bank-accounts/selected`)
);
if (!response?.data) {
throw new Error('No se encontraron los datos de la cuenta bancaria seleccionada.');
}
return response.data;
}
}

View File

@ -1,5 +1,14 @@
import { ApiResponse } from './api-response.interface';
export interface BankAccount {
id: number;
tenant_code: string;
titular: string;
entidad: string;
alias: string;
cvu: string;
}
export interface Tenant {
id: number;
codigo: string;
@ -13,6 +22,8 @@ export interface Tenant {
footer_bg_color: string;
header_logo: string;
footer_logo: string;
selected_bank_account_id?: number | null;
selected_bank_account?: BankAccount | null;
}
export type TenantBootstrapResponse = ApiResponse<Tenant>;

View File

@ -15,7 +15,7 @@
[paymentMethods]="paymentMethods"
[selectedPaymentMethod]="selectedPaymentMethod()"
[copiedTransferField]="copiedTransferField()"
[transferAccount]="transferAccount"
[transferAccount]="transferAccount()"
(paymentMethodChange)="selectPaymentMethod($event)"
(copyTransferValue)="copyTransferValue($event.field, $event.value)"
(cancelStep)="onCancel()"

View File

@ -4,6 +4,9 @@ import { Router } from '@angular/router';
import { startWith } from 'rxjs';
import { CartService } from '../../../../core/services/cart/cart.service';
import { TenantService } from '../../../../core/services/tenant.service';
import { CheckoutService } from '../../../../core/services/checkout.service';
import { BankAccount } from '../../../../core/services/tenant.interface';
import { CartItem } from '../../../../core/services/cart/cart.interface';
import { CartComponent, CartItemMock } from '../../../../shared/components/cart/cart.component';
import { StepComponent } from '../../../../shared/components/stepper/step.component';
@ -30,6 +33,8 @@ export class CheckoutPageComponent {
private readonly formBuilder = inject(FormBuilder);
private readonly cartService = inject(CartService);
private readonly router = inject(Router);
private readonly tenantService = inject(TenantService);
private readonly checkoutService = inject(CheckoutService);
@ViewChild(StepperComponent) stepper!: StepperComponent;
@ -63,17 +68,36 @@ export class CheckoutPageComponent {
];
protected readonly selectedPaymentMethod = signal<PaymentMethod>('qr');
protected readonly copiedTransferField = signal<TransferField | null>(null);
protected readonly transferAccount: TransferAccount = {
protected readonly transferAccount = signal<TransferAccount>({
titular: 'Nombre y Apellido',
entidad: 'TelePagos',
cvu: '0000000000000000000000',
alias: 'telepagos.ar'
};
});
constructor() {
this.form.statusChanges
.pipe(startWith(this.form.status))
.subscribe(() => this.isStep1Valid.set(this.form.valid));
void this.loadSelectedBankAccount();
}
private async loadSelectedBankAccount(): Promise<void> {
const tenant = this.tenantService.tenant();
if (!tenant) return;
try {
const data = await this.checkoutService.getSelectedBankAccount(tenant.codigo);
this.transferAccount.set({
titular: data.titular,
entidad: data.entidad,
cvu: data.cvu,
alias: data.alias
});
} catch (error) {
console.error('Failed to load selected bank account:', error);
}
}
private mapCartItemToMock(item: CartItem): CartItemMock {

View File

@ -2,7 +2,7 @@ import { Routes } from '@angular/router';
import { AuthLayoutComponent } from '../../core/layout/auth-layout/auth-layout.component';
import { StoreLayoutComponent } from '../../core/layout/store-layout/store-layout.component';
import { guestOnlyGuard } from '../../core/services/auth/auth.guards';
import { authGuard, guestOnlyGuard } from '../../core/services/auth/auth.guards';
import { LoginPageComponent } from './pages/login-page/login-page.component';
import { RegisterPageComponent } from './pages/register-page/register-page.component';
import { StoreHomePageComponent } from './pages/store-home-page/store-home-page.component';
@ -47,6 +47,7 @@ export const routes: Routes = [
},
{
path: 'checkout',
canActivate: [authGuard],
loadComponent: () =>
import('./pages/checkout-page/checkout-page.component').then(
(m) => m.CheckoutPageComponent