21 Commits

Author SHA1 Message Date
c0315a705d Merge pull request 'Add human-readable event URL slugs with legacy redirect support.' (#21) from new-slugs into main
Reviewed-on: #21
2026-06-05 04:16:13 +00:00
fbc437a670 Merge pull request 'Fix booking flow scroll position on mobile step changes.' (#20) from dev into main
Reviewed-on: #20
2026-06-05 04:06:39 +00:00
e0f0700398 Merge pull request 'dev' (#19) from dev into main
Reviewed-on: #19
2026-06-04 23:37:15 +00:00
defd9685e0 Merge pull request 'dev' (#18) from dev into main
Reviewed-on: #18
2026-04-27 20:42:36 +00:00
1ed62b0d3f Merge pull request 'Fix db:export ENOBUFS by streaming pg_dump output to file' (#17) from dev into main
Reviewed-on: #17
2026-03-12 19:18:57 +00:00
91de6df04d Merge pull request 'feat(emails): add re-send for all emails, failed tab, and resend indicators' (#16) from dev into main
Reviewed-on: #16
2026-03-12 19:14:36 +00:00
a5d97d65e1 Merge pull request 'Admin: stats privacy toggle, clickable event rows, fix payment method display' (#15) from dev into main
Reviewed-on: #15
2026-03-10 01:14:36 +00:00
f0128f66b0 Merge pull request 'Bug fixes and improvements' (#14) from dev into main
Reviewed-on: #14
2026-03-07 22:53:13 +00:00
b33c68feb0 Merge pull request 'dev' (#13) from dev into main
Reviewed-on: #13
2026-02-19 02:23:19 +00:00
15655e3987 Merge pull request 'dev' (#12) from dev into main
Reviewed-on: #12
2026-02-16 23:11:52 +00:00
d8b3864411 Merge pull request 'Fix stale featured event on homepage: revalidate cache when featured event changes' (#11) from dev into main
Reviewed-on: #11
2026-02-16 22:44:19 +00:00
194cbd6ca8 Merge pull request 'Scanner: close button on valid ticket, camera lifecycle fix' (#10) from dev into main
Reviewed-on: #10
2026-02-14 19:04:42 +00:00
d5445c2282 Merge pull request 'Admin event page: redesign UI, export endpoints, mobile fixes' (#9) from dev into main
Reviewed-on: #9
2026-02-14 18:38:57 +00:00
dcfefc8371 Merge pull request 'feat(admin): add event attendees export (CSV) with status filters' (#8) from dev into main
Reviewed-on: #8
2026-02-14 05:28:24 +00:00
b5f14335c4 Merge pull request 'Mobile scanner redesign + backend live search' (#7) from dev into main
Reviewed-on: #7
2026-02-14 04:28:44 +00:00
d44ac949b5 Merge pull request 'Email queue + async sending; legal settings and placeholders' (#6) from dev into main
Reviewed-on: #6
2026-02-12 21:04:58 +00:00
a5e939221d Merge pull request 'dev' (#5) from dev into main
Reviewed-on: #5
2026-02-12 07:56:37 +00:00
833e3e5a9c Merge pull request 'Fix llms.txt event times: format in America/Asuncion timezone' (#4) from dev into main
Reviewed-on: #4
2026-02-12 06:28:51 +00:00
ba1975dd6d Merge pull request 'dev' (#3) from dev into main
Reviewed-on: #3
2026-02-12 04:55:39 +00:00
3025ef3d21 Merge pull request 'dev' (#2) from dev into main
Reviewed-on: #2
2026-02-12 03:19:06 +00:00
8564f8af83 Merge pull request 'dev' (#1) from dev into main
Reviewed-on: #1
2026-02-12 02:18:08 +00:00
11 changed files with 21 additions and 195 deletions

View File

@@ -259,10 +259,6 @@ async function migrate() {
id TEXT PRIMARY KEY,
tpago_enabled INTEGER NOT NULL DEFAULT 0,
tpago_link TEXT,
tpago_link_2 TEXT,
tpago_link_3 TEXT,
tpago_link_4 TEXT,
tpago_link_5 TEXT,
tpago_instructions TEXT,
tpago_instructions_es TEXT,
bank_transfer_enabled INTEGER NOT NULL DEFAULT 0,
@@ -287,13 +283,6 @@ async function migrate() {
await (db as any).run(sql`ALTER TABLE payment_options ADD COLUMN allow_duplicate_bookings INTEGER NOT NULL DEFAULT 0`);
} catch (e) { /* column may already exist */ }
// Add per-quantity TPago link columns to payment_options if they don't exist
for (const col of ['tpago_link_2', 'tpago_link_3', 'tpago_link_4', 'tpago_link_5']) {
try {
await (db as any).run(sql.raw(`ALTER TABLE payment_options ADD COLUMN ${col} TEXT`));
} catch (e) { /* column may already exist */ }
}
// Event payment overrides table
await (db as any).run(sql`
CREATE TABLE IF NOT EXISTS event_payment_overrides (
@@ -301,10 +290,6 @@ async function migrate() {
event_id TEXT NOT NULL REFERENCES events(id),
tpago_enabled INTEGER,
tpago_link TEXT,
tpago_link_2 TEXT,
tpago_link_3 TEXT,
tpago_link_4 TEXT,
tpago_link_5 TEXT,
tpago_instructions TEXT,
tpago_instructions_es TEXT,
bank_transfer_enabled INTEGER,
@@ -324,13 +309,6 @@ async function migrate() {
)
`);
// Add per-quantity TPago link columns to event_payment_overrides if they don't exist
for (const col of ['tpago_link_2', 'tpago_link_3', 'tpago_link_4', 'tpago_link_5']) {
try {
await (db as any).run(sql.raw(`ALTER TABLE event_payment_overrides ADD COLUMN ${col} TEXT`));
} catch (e) { /* column may already exist */ }
}
await (db as any).run(sql`
CREATE TABLE IF NOT EXISTS contacts (
id TEXT PRIMARY KEY,
@@ -724,10 +702,6 @@ async function migrate() {
id UUID PRIMARY KEY,
tpago_enabled INTEGER NOT NULL DEFAULT 0,
tpago_link VARCHAR(500),
tpago_link_2 VARCHAR(500),
tpago_link_3 VARCHAR(500),
tpago_link_4 VARCHAR(500),
tpago_link_5 VARCHAR(500),
tpago_instructions TEXT,
tpago_instructions_es TEXT,
bank_transfer_enabled INTEGER NOT NULL DEFAULT 0,
@@ -752,23 +726,12 @@ async function migrate() {
await (db as any).execute(sql`ALTER TABLE payment_options ADD COLUMN allow_duplicate_bookings INTEGER NOT NULL DEFAULT 0`);
} catch (e) { /* column may already exist */ }
// Add per-quantity TPago link columns to payment_options if they don't exist
for (const col of ['tpago_link_2', 'tpago_link_3', 'tpago_link_4', 'tpago_link_5']) {
try {
await (db as any).execute(sql.raw(`ALTER TABLE payment_options ADD COLUMN ${col} VARCHAR(500)`));
} catch (e) { /* column may already exist */ }
}
await (db as any).execute(sql`
CREATE TABLE IF NOT EXISTS event_payment_overrides (
id UUID PRIMARY KEY,
event_id UUID NOT NULL REFERENCES events(id),
tpago_enabled INTEGER,
tpago_link VARCHAR(500),
tpago_link_2 VARCHAR(500),
tpago_link_3 VARCHAR(500),
tpago_link_4 VARCHAR(500),
tpago_link_5 VARCHAR(500),
tpago_instructions TEXT,
tpago_instructions_es TEXT,
bank_transfer_enabled INTEGER,
@@ -788,13 +751,6 @@ async function migrate() {
)
`);
// Add per-quantity TPago link columns to event_payment_overrides if they don't exist
for (const col of ['tpago_link_2', 'tpago_link_3', 'tpago_link_4', 'tpago_link_5']) {
try {
await (db as any).execute(sql.raw(`ALTER TABLE event_payment_overrides ADD COLUMN ${col} VARCHAR(500)`));
} catch (e) { /* column may already exist */ }
}
await (db as any).execute(sql`
CREATE TABLE IF NOT EXISTS contacts (
id UUID PRIMARY KEY,

View File

@@ -135,10 +135,6 @@ export const sqlitePaymentOptions = sqliteTable('payment_options', {
// TPago configuration
tpagoEnabled: integer('tpago_enabled', { mode: 'boolean' }).notNull().default(false),
tpagoLink: text('tpago_link'),
tpagoLink2: text('tpago_link_2'),
tpagoLink3: text('tpago_link_3'),
tpagoLink4: text('tpago_link_4'),
tpagoLink5: text('tpago_link_5'),
tpagoInstructions: text('tpago_instructions'),
tpagoInstructionsEs: text('tpago_instructions_es'),
// Bank Transfer configuration
@@ -170,10 +166,6 @@ export const sqliteEventPaymentOverrides = sqliteTable('event_payment_overrides'
// Override flags (null means use global)
tpagoEnabled: integer('tpago_enabled', { mode: 'boolean' }),
tpagoLink: text('tpago_link'),
tpagoLink2: text('tpago_link_2'),
tpagoLink3: text('tpago_link_3'),
tpagoLink4: text('tpago_link_4'),
tpagoLink5: text('tpago_link_5'),
tpagoInstructions: text('tpago_instructions'),
tpagoInstructionsEs: text('tpago_instructions_es'),
bankTransferEnabled: integer('bank_transfer_enabled', { mode: 'boolean' }),
@@ -475,10 +467,6 @@ export const pgPaymentOptions = pgTable('payment_options', {
id: uuid('id').primaryKey(),
tpagoEnabled: pgInteger('tpago_enabled').notNull().default(0),
tpagoLink: varchar('tpago_link', { length: 500 }),
tpagoLink2: varchar('tpago_link_2', { length: 500 }),
tpagoLink3: varchar('tpago_link_3', { length: 500 }),
tpagoLink4: varchar('tpago_link_4', { length: 500 }),
tpagoLink5: varchar('tpago_link_5', { length: 500 }),
tpagoInstructions: pgText('tpago_instructions'),
tpagoInstructionsEs: pgText('tpago_instructions_es'),
bankTransferEnabled: pgInteger('bank_transfer_enabled').notNull().default(0),
@@ -504,10 +492,6 @@ export const pgEventPaymentOverrides = pgTable('event_payment_overrides', {
eventId: uuid('event_id').notNull().references(() => pgEvents.id),
tpagoEnabled: pgInteger('tpago_enabled'),
tpagoLink: varchar('tpago_link', { length: 500 }),
tpagoLink2: varchar('tpago_link_2', { length: 500 }),
tpagoLink3: varchar('tpago_link_3', { length: 500 }),
tpagoLink4: varchar('tpago_link_4', { length: 500 }),
tpagoLink5: varchar('tpago_link_5', { length: 500 }),
tpagoInstructions: pgText('tpago_instructions'),
tpagoInstructionsEs: pgText('tpago_instructions_es'),
bankTransferEnabled: pgInteger('bank_transfer_enabled'),

View File

@@ -748,10 +748,6 @@ export const emailService = {
const defaults = {
tpagoEnabled: false,
tpagoLink: null,
tpagoLink2: null,
tpagoLink3: null,
tpagoLink4: null,
tpagoLink5: null,
tpagoInstructions: null,
tpagoInstructionsEs: null,
bankTransferEnabled: false,
@@ -770,10 +766,6 @@ export const emailService = {
return {
tpagoEnabled: overrides?.tpagoEnabled ?? global.tpagoEnabled,
tpagoLink: overrides?.tpagoLink ?? global.tpagoLink,
tpagoLink2: overrides?.tpagoLink2 ?? global.tpagoLink2,
tpagoLink3: overrides?.tpagoLink3 ?? global.tpagoLink3,
tpagoLink4: overrides?.tpagoLink4 ?? global.tpagoLink4,
tpagoLink5: overrides?.tpagoLink5 ?? global.tpagoLink5,
tpagoInstructions: overrides?.tpagoInstructions ?? global.tpagoInstructions,
tpagoInstructionsEs: overrides?.tpagoInstructionsEs ?? global.tpagoInstructionsEs,
bankTransferEnabled: overrides?.bankTransferEnabled ?? global.bankTransferEnabled,
@@ -893,9 +885,7 @@ export const emailService = {
// Add payment-method specific variables
if (payment.provider === 'tpago') {
// Select the TPago link matching the number of tickets (1-5), falling back to the base link
const tpagoLinkKey = ticketCount <= 1 ? 'tpagoLink' : `tpagoLink${Math.min(ticketCount, 5)}`;
variables.tpagoLink = paymentConfig[tpagoLinkKey] || paymentConfig.tpagoLink || '';
variables.tpagoLink = paymentConfig.tpagoLink || '';
} else {
// Bank transfer
variables.bankName = paymentConfig.bankName || '';

View File

@@ -18,10 +18,6 @@ const booleanOrNumber = z.union([z.boolean(), z.number()]).transform((val) => {
const updatePaymentOptionsSchema = z.object({
tpagoEnabled: booleanOrNumber.optional(),
tpagoLink: z.string().optional().nullable(),
tpagoLink2: z.string().optional().nullable(),
tpagoLink3: z.string().optional().nullable(),
tpagoLink4: z.string().optional().nullable(),
tpagoLink5: z.string().optional().nullable(),
tpagoInstructions: z.string().optional().nullable(),
tpagoInstructionsEs: z.string().optional().nullable(),
bankTransferEnabled: booleanOrNumber.optional(),
@@ -44,10 +40,6 @@ const updatePaymentOptionsSchema = z.object({
const updateEventOverridesSchema = z.object({
tpagoEnabled: booleanOrNumber.optional().nullable(),
tpagoLink: z.string().optional().nullable(),
tpagoLink2: z.string().optional().nullable(),
tpagoLink3: z.string().optional().nullable(),
tpagoLink4: z.string().optional().nullable(),
tpagoLink5: z.string().optional().nullable(),
tpagoInstructions: z.string().optional().nullable(),
tpagoInstructionsEs: z.string().optional().nullable(),
bankTransferEnabled: booleanOrNumber.optional().nullable(),
@@ -76,10 +68,6 @@ paymentOptionsRouter.get('/', requireAuth(['admin']), async (c) => {
paymentOptions: {
tpagoEnabled: false,
tpagoLink: null,
tpagoLink2: null,
tpagoLink3: null,
tpagoLink4: null,
tpagoLink5: null,
tpagoInstructions: null,
tpagoInstructionsEs: null,
bankTransferEnabled: false,
@@ -183,10 +171,6 @@ paymentOptionsRouter.get('/event/:eventId', async (c) => {
const defaults = {
tpagoEnabled: false,
tpagoLink: null,
tpagoLink2: null,
tpagoLink3: null,
tpagoLink4: null,
tpagoLink5: null,
tpagoInstructions: null,
tpagoInstructionsEs: null,
bankTransferEnabled: false,
@@ -209,10 +193,6 @@ paymentOptionsRouter.get('/event/:eventId', async (c) => {
const merged = {
tpagoEnabled: overrides?.tpagoEnabled ?? global.tpagoEnabled,
tpagoLink: overrides?.tpagoLink ?? global.tpagoLink,
tpagoLink2: overrides?.tpagoLink2 ?? global.tpagoLink2,
tpagoLink3: overrides?.tpagoLink3 ?? global.tpagoLink3,
tpagoLink4: overrides?.tpagoLink4 ?? global.tpagoLink4,
tpagoLink5: overrides?.tpagoLink5 ?? global.tpagoLink5,
tpagoInstructions: overrides?.tpagoInstructions ?? global.tpagoInstructions,
tpagoInstructionsEs: overrides?.tpagoInstructionsEs ?? global.tpagoInstructionsEs,
bankTransferEnabled: overrides?.bankTransferEnabled ?? global.bankTransferEnabled,

View File

@@ -631,21 +631,11 @@ ticketsRouter.get('/:id', async (c) => {
(db as any).select().from(payments).where(eq((payments as any).ticketId, id))
);
// Count how many tickets belong to this booking (for per-quantity payment links)
let bookingTicketCount = 1;
if (ticket.bookingId) {
const bookingTickets = await dbAll<any>(
(db as any).select().from(tickets).where(eq((tickets as any).bookingId, ticket.bookingId))
);
bookingTicketCount = bookingTickets.length || 1;
}
return c.json({
ticket: {
...ticket,
event,
payment,
bookingTicketCount,
},
});
});

View File

@@ -6,7 +6,7 @@ import Link from 'next/link';
import { useLanguage } from '@/context/LanguageContext';
import { useAuth } from '@/context/AuthContext';
import { eventsApi, ticketsApi, paymentOptionsApi, Event, PaymentOptionsConfig } from '@/lib/api';
import { formatPrice, formatDateLong, formatTime, getTpagoLink } from '@/lib/utils';
import { formatPrice, formatDateLong, formatTime } from '@/lib/utils';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import Input from '@/components/ui/Input';
@@ -665,7 +665,6 @@ export default function BookingPage() {
const isTpago = bookingResult.paymentMethod === 'tpago';
const ticketCount = bookingResult.ticketCount || 1;
const totalAmount = (event?.price || 0) * ticketCount;
const tpagoLink = getTpagoLink(paymentConfig, ticketCount);
return (
<div className="section-padding">
@@ -756,9 +755,9 @@ export default function BookingPage() {
<h3 className="font-semibold text-gray-900">
{locale === 'es' ? 'Pago con Tarjeta' : 'Card Payment'}
</h3>
{tpagoLink && (
{paymentConfig.tpagoLink && (
<a
href={tpagoLink}
href={paymentConfig.tpagoLink}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 w-full px-6 py-4 bg-blue-600 text-white rounded-btn hover:bg-blue-700 transition-colors font-medium"

View File

@@ -5,7 +5,7 @@ import { useParams, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { useLanguage } from '@/context/LanguageContext';
import { ticketsApi, paymentOptionsApi, Ticket, PaymentOptionsConfig } from '@/lib/api';
import { formatPrice, formatDateLong, formatTime, getTpagoLink } from '@/lib/utils';
import { formatPrice, formatDateLong, formatTime } from '@/lib/utils';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import {
@@ -305,7 +305,6 @@ export default function BookingPaymentPage() {
if (step === 'manual_payment' && ticket && paymentConfig) {
const isBankTransfer = ticket.payment?.provider === 'bank_transfer';
const isTpago = ticket.payment?.provider === 'tpago';
const tpagoLink = getTpagoLink(paymentConfig, ticket.bookingTicketCount || 1);
return (
<div className="section-padding">
@@ -419,9 +418,9 @@ export default function BookingPaymentPage() {
<h3 className="font-semibold text-gray-900">
{locale === 'es' ? 'Pago con Tarjeta' : 'Card Payment'}
</h3>
{tpagoLink && (
{paymentConfig.tpagoLink && (
<a
href={tpagoLink}
href={paymentConfig.tpagoLink}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 w-full px-6 py-4 bg-blue-600 text-white rounded-btn hover:bg-blue-700 transition-colors font-medium"

View File

@@ -1519,32 +1519,15 @@ export default function AdminEventDetailPage() {
<div className="space-y-3 pt-3 border-t">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
{locale === 'es' ? 'Enlaces de Pago TPago (por cantidad de tickets)' : 'TPago Payment Links (per ticket quantity)'}
{locale === 'es' ? 'Enlace de Pago TPago' : 'TPago Payment Link'}
</label>
<p className="text-[10px] text-gray-500 mb-2">
{locale === 'es'
? 'Cada enlace tiene un monto fijo. Un enlace distinto por cantidad de tickets.'
: 'Each link has a fixed amount. One link per ticket quantity.'}
</p>
<div className="space-y-2">
{([1, 2, 3, 4, 5] as const).map((qty) => {
const key = (qty === 1 ? 'tpagoLink' : `tpagoLink${qty}`) as keyof PaymentOptionsConfig;
return (
<div key={qty} className="flex items-center gap-2">
<span className="text-xs font-medium text-gray-600 w-20 flex-shrink-0">
{qty} {qty === 1 ? 'ticket' : 'tickets'}
</span>
<input
type="url"
value={(paymentOverrides[key] as string | null) ?? ''}
onChange={(e) => updatePaymentOverride(key, (e.target.value || null) as any)}
placeholder={(globalPaymentOptions?.[key] as string | null) || 'https://www.tpago.com.py/links?alias=...'}
className="flex-1 px-3 py-2 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
/>
</div>
);
})}
</div>
<input
type="url"
value={paymentOverrides.tpagoLink ?? ''}
onChange={(e) => updatePaymentOverride('tpagoLink', e.target.value || null)}
placeholder={globalPaymentOptions?.tpagoLink || 'https://www.tpago.com.py/links?alias=...'}
className="w-full px-3 py-2 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>

View File

@@ -26,10 +26,6 @@ export default function PaymentOptionsPage() {
const [options, setOptions] = useState<PaymentOptionsConfig>({
tpagoEnabled: false,
tpagoLink: null,
tpagoLink2: null,
tpagoLink3: null,
tpagoLink4: null,
tpagoLink5: null,
tpagoInstructions: null,
tpagoInstructionsEs: null,
bankTransferEnabled: false,
@@ -144,31 +140,13 @@ export default function PaymentOptionsPage() {
<div className="space-y-4 pt-4 border-t">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{locale === 'es' ? 'Enlaces de Pago TPago (por cantidad de tickets)' : 'TPago Payment Links (per ticket quantity)'}
{locale === 'es' ? 'Enlace de Pago TPago' : 'TPago Payment Link'}
</label>
<p className="text-xs text-gray-500 mb-2">
{locale === 'es'
? 'Cada enlace tiene un monto fijo. Usá un enlace distinto para cada cantidad de tickets.'
: 'Each link has a fixed amount. Use a different link for each ticket quantity.'}
</p>
<div className="space-y-2">
{([1, 2, 3, 4, 5] as const).map((qty) => {
const key = (qty === 1 ? 'tpagoLink' : `tpagoLink${qty}`) as keyof PaymentOptionsConfig;
return (
<div key={qty} className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-600 w-24 flex-shrink-0">
{qty} {locale === 'es' ? (qty === 1 ? 'ticket' : 'tickets') : (qty === 1 ? 'ticket' : 'tickets')}
</span>
<Input
value={(options[key] as string | null) || ''}
onChange={(e) => updateOption(key, (e.target.value || null) as any)}
placeholder="https://www.tpago.com.py/links?alias=..."
className="flex-1"
/>
</div>
);
})}
</div>
<Input
value={options.tpagoLink || ''}
onChange={(e) => updateOption('tpagoLink', e.target.value || null)}
placeholder="https://www.tpago.com.py/links?alias=..."
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>

View File

@@ -559,7 +559,6 @@ export interface Event {
export interface Ticket {
id: string;
bookingId?: string; // Groups multiple tickets from same booking
bookingTicketCount?: number; // Total tickets in the booking (for per-quantity payment links)
userId: string;
eventId: string;
attendeeFirstName: string;
@@ -674,10 +673,6 @@ export interface PaymentWithDetails extends Payment {
export interface PaymentOptionsConfig {
tpagoEnabled: boolean;
tpagoLink?: string | null;
tpagoLink2?: string | null;
tpagoLink3?: string | null;
tpagoLink4?: string | null;
tpagoLink5?: string | null;
tpagoInstructions?: string | null;
tpagoInstructionsEs?: string | null;
bankTransferEnabled: boolean;

View File

@@ -165,31 +165,3 @@ export function formatPrice(price: number, currency: string = 'PYG'): string {
export function formatCurrency(amount: number, currency: string = 'PYG'): string {
return formatPrice(amount, currency);
}
// ---------------------------------------------------------------------------
// Payment helpers
// ---------------------------------------------------------------------------
type TpagoLinkConfig = {
tpagoLink?: string | null;
tpagoLink2?: string | null;
tpagoLink3?: string | null;
tpagoLink4?: string | null;
tpagoLink5?: string | null;
};
/**
* Select the TPago payment link that matches the number of tickets being
* purchased (1-5). Each link has a fixed amount baked in, so the quantity
* determines which one to use. Falls back to the base (1-ticket) link when a
* specific quantity link isn't configured.
*/
export function getTpagoLink(
config: TpagoLinkConfig | null | undefined,
ticketCount: number
): string | null {
if (!config) return null;
const count = Math.min(Math.max(1, Math.floor(ticketCount || 1)), 5);
const key = (count <= 1 ? 'tpagoLink' : `tpagoLink${count}`) as keyof TpagoLinkConfig;
return config[key] || config.tpagoLink || null;
}