Accept dashed or digits-only RUC input on the backend, store a canonical dashed form, and display it consistently in booking and admin UIs. Co-authored-by: Cursor <cursoragent@cursor.com>
138 lines
4.6 KiB
TypeScript
138 lines
4.6 KiB
TypeScript
import toast from 'react-hot-toast';
|
|
import { PaymentOptionsConfig } from '@/lib/api';
|
|
import {
|
|
CreditCardIcon,
|
|
BanknotesIcon,
|
|
BoltIcon,
|
|
BuildingLibraryIcon,
|
|
} from '@heroicons/react/24/outline';
|
|
import type { PaymentMethod, BookingResult } from '../_types';
|
|
|
|
// Paraguayan RUC: 5-8 digit base + "-" + 1 check digit (DV), e.g. 1234567-9 or 80012345-0
|
|
export const rucPattern = /^\d{5,8}-\d$/;
|
|
|
|
/** Sanitize RUC input: digits and a single user-typed dash, max 10 chars. No dash is auto-inserted. */
|
|
export function formatRuc(value: string): string {
|
|
const cleaned = value.replace(/[^\d-]/g, '');
|
|
const firstDash = cleaned.indexOf('-');
|
|
const oneDash = firstDash === -1
|
|
? cleaned
|
|
: cleaned.slice(0, firstDash + 1) + cleaned.slice(firstDash + 1).replace(/-/g, '');
|
|
return oneDash.slice(0, 10);
|
|
}
|
|
|
|
/** Truncate a long invoice string for display. */
|
|
export function truncateInvoice(invoice: string, chars: number = 20): string {
|
|
if (invoice.length <= chars * 2) return invoice;
|
|
return `${invoice.slice(0, chars)}...${invoice.slice(-chars)}`;
|
|
}
|
|
|
|
/** Copy a Lightning invoice to the clipboard with localized feedback. */
|
|
export function copyInvoiceToClipboard(invoice: string, locale: string): void {
|
|
navigator.clipboard.writeText(invoice).then(() => {
|
|
toast.success(locale === 'es' ? '¡Copiado!' : 'Copied!');
|
|
}).catch(() => {
|
|
toast.error(locale === 'es' ? 'Error al copiar' : 'Failed to copy');
|
|
});
|
|
}
|
|
|
|
export interface PaymentMethodOption {
|
|
id: PaymentMethod;
|
|
icon: typeof CreditCardIcon;
|
|
label: string;
|
|
description: string;
|
|
badge?: string;
|
|
}
|
|
|
|
/** Build the list of selectable payment methods from the event config. */
|
|
export function buildPaymentMethods(
|
|
paymentConfig: PaymentOptionsConfig | null,
|
|
locale: string
|
|
): PaymentMethodOption[] {
|
|
const paymentMethods: PaymentMethodOption[] = [];
|
|
|
|
if (paymentConfig?.lightningEnabled) {
|
|
paymentMethods.push({
|
|
id: 'lightning',
|
|
icon: BoltIcon,
|
|
label: 'Bitcoin Lightning',
|
|
description: locale === 'es' ? 'Pago instantáneo con Bitcoin' : 'Instant payment with Bitcoin',
|
|
badge: locale === 'es' ? 'Instantáneo' : 'Instant',
|
|
});
|
|
}
|
|
|
|
if (paymentConfig?.tpagoEnabled) {
|
|
paymentMethods.push({
|
|
id: 'tpago',
|
|
icon: CreditCardIcon,
|
|
label: locale === 'es' ? 'TPago / Tarjetas de Crédito' : 'TPago / Credit Cards',
|
|
description: locale === 'es' ? 'Pagá con tarjetas de crédito locales o internacionales' : 'Pay with local or international credit cards',
|
|
badge: locale === 'es' ? 'Manual' : 'Manual',
|
|
});
|
|
}
|
|
|
|
if (paymentConfig?.bankTransferEnabled) {
|
|
paymentMethods.push({
|
|
id: 'bank_transfer',
|
|
icon: BuildingLibraryIcon,
|
|
label: locale === 'es' ? 'Transferencia Bancaria Local' : 'Local Bank Transfer',
|
|
description: locale === 'es' ? 'Pago por transferencia bancaria en Paraguay' : 'Pay via Paraguayan bank transfer',
|
|
badge: locale === 'es' ? 'Manual' : 'Manual',
|
|
});
|
|
}
|
|
|
|
if (paymentConfig?.cashEnabled) {
|
|
paymentMethods.push({
|
|
id: 'cash',
|
|
icon: BanknotesIcon,
|
|
label: locale === 'es' ? 'Efectivo en el Evento' : 'Cash at Event',
|
|
description: locale === 'es' ? 'Paga cuando llegues al evento' : 'Pay when you arrive at the event',
|
|
badge: locale === 'es' ? 'Manual' : 'Manual',
|
|
});
|
|
}
|
|
|
|
return paymentMethods;
|
|
}
|
|
|
|
export interface SuccessContent {
|
|
title: string;
|
|
description: string;
|
|
iconColor: string;
|
|
iconTextColor: string;
|
|
}
|
|
|
|
/** Resolve the success-screen copy based on the payment method used. */
|
|
export function getSuccessContent(
|
|
bookingResult: BookingResult | null,
|
|
locale: string,
|
|
t: (key: string) => string
|
|
): SuccessContent {
|
|
if (bookingResult?.paymentMethod === 'cash') {
|
|
return {
|
|
title: locale === 'es' ? '¡Reserva Recibida!' : 'Reservation Received!',
|
|
description: locale === 'es'
|
|
? 'Tu lugar está reservado. El pago se realizará en el evento.'
|
|
: 'Your spot is reserved. Payment will be collected at the event.',
|
|
iconColor: 'bg-yellow-100',
|
|
iconTextColor: 'text-yellow-600',
|
|
};
|
|
}
|
|
if (bookingResult?.paymentMethod === 'lightning') {
|
|
// For Lightning, if we're on success step, payment was confirmed
|
|
return {
|
|
title: locale === 'es' ? '¡Pago Confirmado!' : 'Payment Confirmed!',
|
|
description: locale === 'es'
|
|
? '¡Tu reserva está confirmada! Te esperamos en el evento.'
|
|
: 'Your booking is confirmed! See you at the event.',
|
|
iconColor: 'bg-green-100',
|
|
iconTextColor: 'text-green-600',
|
|
};
|
|
}
|
|
return {
|
|
title: t('booking.success.title'),
|
|
description: t('booking.success.description'),
|
|
iconColor: 'bg-green-100',
|
|
iconTextColor: 'text-green-600',
|
|
};
|
|
}
|