feat(checkout): enhance transfer payment flow with DNI input and validation
This commit is contained in:
@@ -81,9 +81,13 @@ export class CheckoutService {
|
||||
return purchase;
|
||||
}
|
||||
|
||||
async generatePaymentIntent(tenantCode: string, purchaseId: number, method: 'qr' | 'transfer' | 'telepagos'): Promise<any> {
|
||||
async generatePaymentIntent(tenantCode: string, purchaseId: number, method: 'qr' | 'transfer' | 'telepagos', payerDni?: string): Promise<any> {
|
||||
const payload: any = { method };
|
||||
if (payerDni) {
|
||||
payload.payer_dni = payerDni;
|
||||
}
|
||||
const response = await firstValueFrom(
|
||||
this.http.post<any>(`${environment.url}tenants/${tenantCode}/compras/${purchaseId}/payment-intent`, { method })
|
||||
this.http.post<any>(`${environment.url}tenants/${tenantCode}/compras/${purchaseId}/payment-intent`, payload)
|
||||
);
|
||||
if (!response) {
|
||||
throw new Error('Error al generar la intención de pago.');
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
(paymentMethodChange)="selectPaymentMethod($event)"
|
||||
(copyTransferValue)="copyTransferValue($event.field, $event.value)"
|
||||
(cancelStep)="onCancel()"
|
||||
(generateTransferIntent)="generateTransferIntent($event)"
|
||||
(complete)="onComplete()"
|
||||
/>
|
||||
</app-step>
|
||||
|
||||
@@ -86,12 +86,7 @@ export class CheckoutPageComponent implements OnInit {
|
||||
];
|
||||
protected readonly selectedPaymentMethod = signal<PaymentMethod>('qr');
|
||||
protected readonly copiedTransferField = signal<TransferField | null>(null);
|
||||
protected readonly transferAccount = signal<TransferAccount>({
|
||||
titular: 'Nombre y Apellido',
|
||||
entidad: 'TelePagos',
|
||||
cvu: '0000000000000000000000',
|
||||
alias: 'telepagos.ar',
|
||||
});
|
||||
protected readonly transferAccount = signal<TransferAccount | null>(null);
|
||||
|
||||
protected readonly isCreatingPurchase = signal(false);
|
||||
protected readonly createdPurchaseId = signal<number | null>(null);
|
||||
@@ -219,6 +214,11 @@ export class CheckoutPageComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
if (method === 'transfer') {
|
||||
this.transferAccount.set(null);
|
||||
return;
|
||||
}
|
||||
|
||||
this.isGeneratingIntent.set(true);
|
||||
|
||||
try {
|
||||
@@ -230,7 +230,32 @@ export class CheckoutPageComponent implements OnInit {
|
||||
|
||||
if (method === 'qr' && response.qr_data?.qr_code) {
|
||||
this.qrData.set(response.qr_data.qr_code);
|
||||
} else if (method === 'transfer' && response.transfer_data) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to generate payment intent:', error);
|
||||
} finally {
|
||||
this.isGeneratingIntent.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected async generateTransferIntent(dni: string): Promise<void> {
|
||||
const purchaseId = this.createdPurchaseId();
|
||||
const tenant = this.tenantService.tenant();
|
||||
|
||||
if (!purchaseId || !tenant) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isGeneratingIntent.set(true);
|
||||
try {
|
||||
const response = await this.checkoutService.generatePaymentIntent(
|
||||
tenant.codigo,
|
||||
purchaseId,
|
||||
'transfer',
|
||||
dni
|
||||
);
|
||||
|
||||
if (response.transfer_data) {
|
||||
this.transferAccount.set({
|
||||
titular: response.transfer_data.titular,
|
||||
entidad: response.transfer_data.entidad,
|
||||
@@ -239,7 +264,7 @@ export class CheckoutPageComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to generate payment intent:', error);
|
||||
console.error('Failed to generate transfer payment intent:', error);
|
||||
} finally {
|
||||
this.isGeneratingIntent.set(false);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
[transferAccount]="transferAccount()"
|
||||
[copiedTransferField]="copiedTransferField()"
|
||||
(copyTransferValue)="requestCopy($event.field, $event.value)"
|
||||
(submitDni)="generateTransferIntent.emit($event)"
|
||||
/>
|
||||
} @else {
|
||||
<app-checkout-payment-telepagos />
|
||||
@@ -68,7 +69,7 @@
|
||||
type="button"
|
||||
hostClass="checkout-payment__action-btn"
|
||||
buttonClass="w-100"
|
||||
[disabled]="isGeneratingIntent()"
|
||||
[disabled]="isGeneratingIntent() || (selectedPaymentMethod() === 'transfer' && !transferAccount())"
|
||||
(click)="complete.emit()"
|
||||
>
|
||||
Finalizar compra
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
min-width: 0;
|
||||
&__card {
|
||||
width: 100%;
|
||||
max-width: 290px;
|
||||
max-width: 320px;
|
||||
min-height: 269px;
|
||||
padding: 1.5rem 1.75rem;
|
||||
border-radius: 5px;
|
||||
|
||||
@@ -18,7 +18,7 @@ export class CheckoutPaymentStepComponent {
|
||||
readonly paymentMethods = input.required<ReadonlyArray<PaymentMethodOption>>();
|
||||
readonly selectedPaymentMethod = input.required<PaymentMethod>();
|
||||
readonly copiedTransferField = input<TransferField | null>(null);
|
||||
readonly transferAccount = input.required<TransferAccount>();
|
||||
readonly transferAccount = input<TransferAccount | null>(null);
|
||||
readonly isGeneratingIntent = input<boolean>(false);
|
||||
readonly qrData = input<string | null>(null);
|
||||
|
||||
@@ -26,6 +26,7 @@ export class CheckoutPaymentStepComponent {
|
||||
readonly copyTransferValue = output<{ field: TransferField; value: string }>();
|
||||
readonly cancelStep = output<void>();
|
||||
readonly complete = output<void>();
|
||||
readonly generateTransferIntent = output<string>();
|
||||
|
||||
protected selectPaymentMethod(method: PaymentMethod): void {
|
||||
this.paymentMethodChange.emit(method);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.payment-panel__card {
|
||||
width: 100%;
|
||||
max-width: 290px;
|
||||
max-width: 320px;
|
||||
min-height: 269px;
|
||||
padding: 1.5rem 1.75rem;
|
||||
border-radius: 5px;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.payment-panel__card {
|
||||
width: 100%;
|
||||
max-width: 290px;
|
||||
max-width: 320px;
|
||||
min-height: 269px;
|
||||
padding: 1.5rem 1.75rem;
|
||||
border-radius: 5px;
|
||||
|
||||
@@ -1,46 +1,74 @@
|
||||
<div class="payment-panel__card">
|
||||
<h3 class="payment-panel__title">Transferi a la siguiente cuenta desde cualquier billetera</h3>
|
||||
<div class="payment-panel__divider"></div>
|
||||
@if (transferAccount()) {
|
||||
<h3 class="payment-panel__title">Transferi a la siguiente cuenta desde cualquier billetera</h3>
|
||||
<div class="payment-panel__divider"></div>
|
||||
|
||||
<div class="payment-panel__account">
|
||||
<p class="payment-panel__eyebrow">DATOS DE CUENTA:</p>
|
||||
<div class="payment-panel__account">
|
||||
<p class="payment-panel__eyebrow">DATOS DE CUENTA:</p>
|
||||
|
||||
<div class="payment-detail">
|
||||
<span class="payment-detail__label">Titular:</span>
|
||||
<strong class="payment-detail__value">{{ transferAccount().titular }}</strong>
|
||||
<div class="payment-detail">
|
||||
<span class="payment-detail__label">Titular:</span>
|
||||
<strong class="payment-detail__value">{{ transferAccount()!.titular }}</strong>
|
||||
</div>
|
||||
|
||||
<div class="payment-detail">
|
||||
<span class="payment-detail__label">Entidad:</span>
|
||||
<strong class="payment-detail__value">{{ transferAccount()!.entidad }}</strong>
|
||||
</div>
|
||||
|
||||
<div class="payment-detail payment-detail--copy">
|
||||
<span class="payment-detail__label">CVU:</span>
|
||||
<strong class="payment-detail__value">{{ transferAccount()!.cvu }}</strong>
|
||||
<app-icon-button
|
||||
variant="copy"
|
||||
ariaLabel="Copiar CVU"
|
||||
(click)="requestCopy('cvu', transferAccount()!.cvu)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@if (copiedTransferField() === 'cvu') {
|
||||
<small class="payment-detail__feedback">CVU copiado</small>
|
||||
}
|
||||
|
||||
<div class="payment-detail payment-detail--copy">
|
||||
<span class="payment-detail__label">Alias:</span>
|
||||
<strong class="payment-detail__value">{{ transferAccount()!.alias }}</strong>
|
||||
<app-icon-button
|
||||
variant="copy"
|
||||
ariaLabel="Copiar alias"
|
||||
(click)="requestCopy('alias', transferAccount()!.alias)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@if (copiedTransferField() === 'alias') {
|
||||
<small class="payment-detail__feedback">Alias copiado</small>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="payment-detail">
|
||||
<span class="payment-detail__label">Entidad:</span>
|
||||
<strong class="payment-detail__value">{{ transferAccount().entidad }}</strong>
|
||||
} @else {
|
||||
<div class="dni-form-container">
|
||||
<h3 class="dni-form-title">Ingresá DNI de quién va a transferir</h3>
|
||||
<form class="dni-form row gx-2" (ngSubmit)="onSubmitDni()">
|
||||
<div class="col-8">
|
||||
<app-input
|
||||
type="text"
|
||||
placeholder="99.999.999"
|
||||
[value]="dniControl.value"
|
||||
(valueChange)="dniControl.setValue($event.toString()); dniControl.markAsDirty()"
|
||||
[invalid]="dniControl.invalid && (dniControl.dirty || dniControl.touched)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<app-button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
hostClass="w-100 h-100"
|
||||
buttonClass="w-100 h-100 px-0"
|
||||
[disabled]="dniControl.invalid"
|
||||
>
|
||||
Continuar
|
||||
</app-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="payment-detail payment-detail--copy">
|
||||
<span class="payment-detail__label">CVU:</span>
|
||||
<strong class="payment-detail__value">{{ transferAccount().cvu }}</strong>
|
||||
<app-icon-button
|
||||
variant="copy"
|
||||
ariaLabel="Copiar CVU"
|
||||
(click)="requestCopy('cvu', transferAccount().cvu)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@if (copiedTransferField() === 'cvu') {
|
||||
<small class="payment-detail__feedback">CVU copiado</small>
|
||||
}
|
||||
|
||||
<div class="payment-detail payment-detail--copy">
|
||||
<span class="payment-detail__label">Alias:</span>
|
||||
<strong class="payment-detail__value">{{ transferAccount().alias }}</strong>
|
||||
<app-icon-button
|
||||
variant="copy"
|
||||
ariaLabel="Copiar alias"
|
||||
(click)="requestCopy('alias', transferAccount().alias)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@if (copiedTransferField() === 'alias') {
|
||||
<small class="payment-detail__feedback">Alias copiado</small>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
.payment-panel__card {
|
||||
width: 100%;
|
||||
max-width: 290px;
|
||||
max-width: 320px;
|
||||
min-height: 269px;
|
||||
padding: 1.5rem 1.75rem;
|
||||
border-radius: 5px;
|
||||
background: #ffffff;
|
||||
background: #bbb6b6;
|
||||
color: #666666;
|
||||
box-shadow: 0 0 0 1px #f1f1f1;
|
||||
display: flex;
|
||||
@@ -76,3 +76,42 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.dni-form-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.dni-form-title {
|
||||
color: #8a8a8a;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1.25rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dni-form {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
|
||||
app-input {
|
||||
display: block;
|
||||
height: 100%;
|
||||
margin-bottom: 0;
|
||||
|
||||
::ng-deep .input-control,
|
||||
::ng-deep input.form-control {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .btn {
|
||||
min-width: 0 !important;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,37 @@
|
||||
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
||||
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { IconButtonComponent } from '../../../../../../shared/components/icon-button/icon-button.component';
|
||||
import { ButtonComponent } from '../../../../../../shared/components/button/button.component';
|
||||
import { InputComponent } from '../../../../../../shared/components/input/input.component';
|
||||
import { TransferAccount, TransferField } from '../../checkout-page.models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-checkout-payment-transfer',
|
||||
standalone: true,
|
||||
imports: [IconButtonComponent],
|
||||
imports: [IconButtonComponent, ButtonComponent, InputComponent, ReactiveFormsModule],
|
||||
templateUrl: './checkout-payment-transfer.component.html',
|
||||
styleUrl: './checkout-payment-transfer.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CheckoutPaymentTransferComponent {
|
||||
readonly transferAccount = input.required<TransferAccount>();
|
||||
readonly transferAccount = input<TransferAccount | null>(null);
|
||||
readonly copiedTransferField = input<TransferField | null>(null);
|
||||
|
||||
readonly copyTransferValue = output<{ field: TransferField; value: string }>();
|
||||
readonly submitDni = output<string>();
|
||||
|
||||
protected readonly dniControl = new FormControl('', {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required, Validators.pattern(/^\d{7,8}$/)]
|
||||
});
|
||||
|
||||
protected requestCopy(field: TransferField, value: string): void {
|
||||
this.copyTransferValue.emit({ field, value });
|
||||
}
|
||||
|
||||
protected onSubmitDni(): void {
|
||||
if (this.dniControl.valid) {
|
||||
this.submitDni.emit(this.dniControl.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user