Compare commits
1 Commits
1.3
...
tpago-upda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc4af38e8a |
@@ -259,6 +259,10 @@ async function migrate() {
|
|||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
tpago_enabled INTEGER NOT NULL DEFAULT 0,
|
tpago_enabled INTEGER NOT NULL DEFAULT 0,
|
||||||
tpago_link TEXT,
|
tpago_link TEXT,
|
||||||
|
tpago_link_2 TEXT,
|
||||||
|
tpago_link_3 TEXT,
|
||||||
|
tpago_link_4 TEXT,
|
||||||
|
tpago_link_5 TEXT,
|
||||||
tpago_instructions TEXT,
|
tpago_instructions TEXT,
|
||||||
tpago_instructions_es TEXT,
|
tpago_instructions_es TEXT,
|
||||||
bank_transfer_enabled INTEGER NOT NULL DEFAULT 0,
|
bank_transfer_enabled INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -283,6 +287,13 @@ async function migrate() {
|
|||||||
await (db as any).run(sql`ALTER TABLE payment_options ADD COLUMN allow_duplicate_bookings INTEGER NOT NULL DEFAULT 0`);
|
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 */ }
|
} 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
|
// Event payment overrides table
|
||||||
await (db as any).run(sql`
|
await (db as any).run(sql`
|
||||||
CREATE TABLE IF NOT EXISTS event_payment_overrides (
|
CREATE TABLE IF NOT EXISTS event_payment_overrides (
|
||||||
@@ -290,6 +301,10 @@ async function migrate() {
|
|||||||
event_id TEXT NOT NULL REFERENCES events(id),
|
event_id TEXT NOT NULL REFERENCES events(id),
|
||||||
tpago_enabled INTEGER,
|
tpago_enabled INTEGER,
|
||||||
tpago_link TEXT,
|
tpago_link TEXT,
|
||||||
|
tpago_link_2 TEXT,
|
||||||
|
tpago_link_3 TEXT,
|
||||||
|
tpago_link_4 TEXT,
|
||||||
|
tpago_link_5 TEXT,
|
||||||
tpago_instructions TEXT,
|
tpago_instructions TEXT,
|
||||||
tpago_instructions_es TEXT,
|
tpago_instructions_es TEXT,
|
||||||
bank_transfer_enabled INTEGER,
|
bank_transfer_enabled INTEGER,
|
||||||
@@ -309,6 +324,13 @@ 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`
|
await (db as any).run(sql`
|
||||||
CREATE TABLE IF NOT EXISTS contacts (
|
CREATE TABLE IF NOT EXISTS contacts (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
@@ -702,6 +724,10 @@ async function migrate() {
|
|||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
tpago_enabled INTEGER NOT NULL DEFAULT 0,
|
tpago_enabled INTEGER NOT NULL DEFAULT 0,
|
||||||
tpago_link VARCHAR(500),
|
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 TEXT,
|
||||||
tpago_instructions_es TEXT,
|
tpago_instructions_es TEXT,
|
||||||
bank_transfer_enabled INTEGER NOT NULL DEFAULT 0,
|
bank_transfer_enabled INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -726,12 +752,23 @@ async function migrate() {
|
|||||||
await (db as any).execute(sql`ALTER TABLE payment_options ADD COLUMN allow_duplicate_bookings INTEGER NOT NULL DEFAULT 0`);
|
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 */ }
|
} 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`
|
await (db as any).execute(sql`
|
||||||
CREATE TABLE IF NOT EXISTS event_payment_overrides (
|
CREATE TABLE IF NOT EXISTS event_payment_overrides (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
event_id UUID NOT NULL REFERENCES events(id),
|
event_id UUID NOT NULL REFERENCES events(id),
|
||||||
tpago_enabled INTEGER,
|
tpago_enabled INTEGER,
|
||||||
tpago_link VARCHAR(500),
|
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 TEXT,
|
||||||
tpago_instructions_es TEXT,
|
tpago_instructions_es TEXT,
|
||||||
bank_transfer_enabled INTEGER,
|
bank_transfer_enabled INTEGER,
|
||||||
@@ -751,6 +788,13 @@ 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`
|
await (db as any).execute(sql`
|
||||||
CREATE TABLE IF NOT EXISTS contacts (
|
CREATE TABLE IF NOT EXISTS contacts (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
|
|||||||
@@ -135,6 +135,10 @@ export const sqlitePaymentOptions = sqliteTable('payment_options', {
|
|||||||
// TPago configuration
|
// TPago configuration
|
||||||
tpagoEnabled: integer('tpago_enabled', { mode: 'boolean' }).notNull().default(false),
|
tpagoEnabled: integer('tpago_enabled', { mode: 'boolean' }).notNull().default(false),
|
||||||
tpagoLink: text('tpago_link'),
|
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'),
|
tpagoInstructions: text('tpago_instructions'),
|
||||||
tpagoInstructionsEs: text('tpago_instructions_es'),
|
tpagoInstructionsEs: text('tpago_instructions_es'),
|
||||||
// Bank Transfer configuration
|
// Bank Transfer configuration
|
||||||
@@ -166,6 +170,10 @@ export const sqliteEventPaymentOverrides = sqliteTable('event_payment_overrides'
|
|||||||
// Override flags (null means use global)
|
// Override flags (null means use global)
|
||||||
tpagoEnabled: integer('tpago_enabled', { mode: 'boolean' }),
|
tpagoEnabled: integer('tpago_enabled', { mode: 'boolean' }),
|
||||||
tpagoLink: text('tpago_link'),
|
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'),
|
tpagoInstructions: text('tpago_instructions'),
|
||||||
tpagoInstructionsEs: text('tpago_instructions_es'),
|
tpagoInstructionsEs: text('tpago_instructions_es'),
|
||||||
bankTransferEnabled: integer('bank_transfer_enabled', { mode: 'boolean' }),
|
bankTransferEnabled: integer('bank_transfer_enabled', { mode: 'boolean' }),
|
||||||
@@ -467,6 +475,10 @@ export const pgPaymentOptions = pgTable('payment_options', {
|
|||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey(),
|
||||||
tpagoEnabled: pgInteger('tpago_enabled').notNull().default(0),
|
tpagoEnabled: pgInteger('tpago_enabled').notNull().default(0),
|
||||||
tpagoLink: varchar('tpago_link', { length: 500 }),
|
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'),
|
tpagoInstructions: pgText('tpago_instructions'),
|
||||||
tpagoInstructionsEs: pgText('tpago_instructions_es'),
|
tpagoInstructionsEs: pgText('tpago_instructions_es'),
|
||||||
bankTransferEnabled: pgInteger('bank_transfer_enabled').notNull().default(0),
|
bankTransferEnabled: pgInteger('bank_transfer_enabled').notNull().default(0),
|
||||||
@@ -492,6 +504,10 @@ export const pgEventPaymentOverrides = pgTable('event_payment_overrides', {
|
|||||||
eventId: uuid('event_id').notNull().references(() => pgEvents.id),
|
eventId: uuid('event_id').notNull().references(() => pgEvents.id),
|
||||||
tpagoEnabled: pgInteger('tpago_enabled'),
|
tpagoEnabled: pgInteger('tpago_enabled'),
|
||||||
tpagoLink: varchar('tpago_link', { length: 500 }),
|
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'),
|
tpagoInstructions: pgText('tpago_instructions'),
|
||||||
tpagoInstructionsEs: pgText('tpago_instructions_es'),
|
tpagoInstructionsEs: pgText('tpago_instructions_es'),
|
||||||
bankTransferEnabled: pgInteger('bank_transfer_enabled'),
|
bankTransferEnabled: pgInteger('bank_transfer_enabled'),
|
||||||
|
|||||||
@@ -748,6 +748,10 @@ export const emailService = {
|
|||||||
const defaults = {
|
const defaults = {
|
||||||
tpagoEnabled: false,
|
tpagoEnabled: false,
|
||||||
tpagoLink: null,
|
tpagoLink: null,
|
||||||
|
tpagoLink2: null,
|
||||||
|
tpagoLink3: null,
|
||||||
|
tpagoLink4: null,
|
||||||
|
tpagoLink5: null,
|
||||||
tpagoInstructions: null,
|
tpagoInstructions: null,
|
||||||
tpagoInstructionsEs: null,
|
tpagoInstructionsEs: null,
|
||||||
bankTransferEnabled: false,
|
bankTransferEnabled: false,
|
||||||
@@ -766,6 +770,10 @@ export const emailService = {
|
|||||||
return {
|
return {
|
||||||
tpagoEnabled: overrides?.tpagoEnabled ?? global.tpagoEnabled,
|
tpagoEnabled: overrides?.tpagoEnabled ?? global.tpagoEnabled,
|
||||||
tpagoLink: overrides?.tpagoLink ?? global.tpagoLink,
|
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,
|
tpagoInstructions: overrides?.tpagoInstructions ?? global.tpagoInstructions,
|
||||||
tpagoInstructionsEs: overrides?.tpagoInstructionsEs ?? global.tpagoInstructionsEs,
|
tpagoInstructionsEs: overrides?.tpagoInstructionsEs ?? global.tpagoInstructionsEs,
|
||||||
bankTransferEnabled: overrides?.bankTransferEnabled ?? global.bankTransferEnabled,
|
bankTransferEnabled: overrides?.bankTransferEnabled ?? global.bankTransferEnabled,
|
||||||
@@ -885,7 +893,9 @@ export const emailService = {
|
|||||||
|
|
||||||
// Add payment-method specific variables
|
// Add payment-method specific variables
|
||||||
if (payment.provider === 'tpago') {
|
if (payment.provider === 'tpago') {
|
||||||
variables.tpagoLink = paymentConfig.tpagoLink || '';
|
// 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 || '';
|
||||||
} else {
|
} else {
|
||||||
// Bank transfer
|
// Bank transfer
|
||||||
variables.bankName = paymentConfig.bankName || '';
|
variables.bankName = paymentConfig.bankName || '';
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ const booleanOrNumber = z.union([z.boolean(), z.number()]).transform((val) => {
|
|||||||
const updatePaymentOptionsSchema = z.object({
|
const updatePaymentOptionsSchema = z.object({
|
||||||
tpagoEnabled: booleanOrNumber.optional(),
|
tpagoEnabled: booleanOrNumber.optional(),
|
||||||
tpagoLink: z.string().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(),
|
tpagoInstructions: z.string().optional().nullable(),
|
||||||
tpagoInstructionsEs: z.string().optional().nullable(),
|
tpagoInstructionsEs: z.string().optional().nullable(),
|
||||||
bankTransferEnabled: booleanOrNumber.optional(),
|
bankTransferEnabled: booleanOrNumber.optional(),
|
||||||
@@ -40,6 +44,10 @@ const updatePaymentOptionsSchema = z.object({
|
|||||||
const updateEventOverridesSchema = z.object({
|
const updateEventOverridesSchema = z.object({
|
||||||
tpagoEnabled: booleanOrNumber.optional().nullable(),
|
tpagoEnabled: booleanOrNumber.optional().nullable(),
|
||||||
tpagoLink: z.string().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(),
|
tpagoInstructions: z.string().optional().nullable(),
|
||||||
tpagoInstructionsEs: z.string().optional().nullable(),
|
tpagoInstructionsEs: z.string().optional().nullable(),
|
||||||
bankTransferEnabled: booleanOrNumber.optional().nullable(),
|
bankTransferEnabled: booleanOrNumber.optional().nullable(),
|
||||||
@@ -68,6 +76,10 @@ paymentOptionsRouter.get('/', requireAuth(['admin']), async (c) => {
|
|||||||
paymentOptions: {
|
paymentOptions: {
|
||||||
tpagoEnabled: false,
|
tpagoEnabled: false,
|
||||||
tpagoLink: null,
|
tpagoLink: null,
|
||||||
|
tpagoLink2: null,
|
||||||
|
tpagoLink3: null,
|
||||||
|
tpagoLink4: null,
|
||||||
|
tpagoLink5: null,
|
||||||
tpagoInstructions: null,
|
tpagoInstructions: null,
|
||||||
tpagoInstructionsEs: null,
|
tpagoInstructionsEs: null,
|
||||||
bankTransferEnabled: false,
|
bankTransferEnabled: false,
|
||||||
@@ -171,6 +183,10 @@ paymentOptionsRouter.get('/event/:eventId', async (c) => {
|
|||||||
const defaults = {
|
const defaults = {
|
||||||
tpagoEnabled: false,
|
tpagoEnabled: false,
|
||||||
tpagoLink: null,
|
tpagoLink: null,
|
||||||
|
tpagoLink2: null,
|
||||||
|
tpagoLink3: null,
|
||||||
|
tpagoLink4: null,
|
||||||
|
tpagoLink5: null,
|
||||||
tpagoInstructions: null,
|
tpagoInstructions: null,
|
||||||
tpagoInstructionsEs: null,
|
tpagoInstructionsEs: null,
|
||||||
bankTransferEnabled: false,
|
bankTransferEnabled: false,
|
||||||
@@ -193,6 +209,10 @@ paymentOptionsRouter.get('/event/:eventId', async (c) => {
|
|||||||
const merged = {
|
const merged = {
|
||||||
tpagoEnabled: overrides?.tpagoEnabled ?? global.tpagoEnabled,
|
tpagoEnabled: overrides?.tpagoEnabled ?? global.tpagoEnabled,
|
||||||
tpagoLink: overrides?.tpagoLink ?? global.tpagoLink,
|
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,
|
tpagoInstructions: overrides?.tpagoInstructions ?? global.tpagoInstructions,
|
||||||
tpagoInstructionsEs: overrides?.tpagoInstructionsEs ?? global.tpagoInstructionsEs,
|
tpagoInstructionsEs: overrides?.tpagoInstructionsEs ?? global.tpagoInstructionsEs,
|
||||||
bankTransferEnabled: overrides?.bankTransferEnabled ?? global.bankTransferEnabled,
|
bankTransferEnabled: overrides?.bankTransferEnabled ?? global.bankTransferEnabled,
|
||||||
|
|||||||
@@ -631,11 +631,21 @@ ticketsRouter.get('/:id', async (c) => {
|
|||||||
(db as any).select().from(payments).where(eq((payments as any).ticketId, id))
|
(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({
|
return c.json({
|
||||||
ticket: {
|
ticket: {
|
||||||
...ticket,
|
...ticket,
|
||||||
event,
|
event,
|
||||||
payment,
|
payment,
|
||||||
|
bookingTicketCount,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Link from 'next/link';
|
|||||||
import { useLanguage } from '@/context/LanguageContext';
|
import { useLanguage } from '@/context/LanguageContext';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
import { eventsApi, ticketsApi, paymentOptionsApi, Event, PaymentOptionsConfig } from '@/lib/api';
|
import { eventsApi, ticketsApi, paymentOptionsApi, Event, PaymentOptionsConfig } from '@/lib/api';
|
||||||
import { formatPrice, formatDateLong, formatTime } from '@/lib/utils';
|
import { formatPrice, formatDateLong, formatTime, getTpagoLink } from '@/lib/utils';
|
||||||
import Card from '@/components/ui/Card';
|
import Card from '@/components/ui/Card';
|
||||||
import Button from '@/components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
import Input from '@/components/ui/Input';
|
import Input from '@/components/ui/Input';
|
||||||
@@ -665,6 +665,7 @@ export default function BookingPage() {
|
|||||||
const isTpago = bookingResult.paymentMethod === 'tpago';
|
const isTpago = bookingResult.paymentMethod === 'tpago';
|
||||||
const ticketCount = bookingResult.ticketCount || 1;
|
const ticketCount = bookingResult.ticketCount || 1;
|
||||||
const totalAmount = (event?.price || 0) * ticketCount;
|
const totalAmount = (event?.price || 0) * ticketCount;
|
||||||
|
const tpagoLink = getTpagoLink(paymentConfig, ticketCount);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="section-padding">
|
<div className="section-padding">
|
||||||
@@ -755,9 +756,9 @@ export default function BookingPage() {
|
|||||||
<h3 className="font-semibold text-gray-900">
|
<h3 className="font-semibold text-gray-900">
|
||||||
{locale === 'es' ? 'Pago con Tarjeta' : 'Card Payment'}
|
{locale === 'es' ? 'Pago con Tarjeta' : 'Card Payment'}
|
||||||
</h3>
|
</h3>
|
||||||
{paymentConfig.tpagoLink && (
|
{tpagoLink && (
|
||||||
<a
|
<a
|
||||||
href={paymentConfig.tpagoLink}
|
href={tpagoLink}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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"
|
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"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useParams, useSearchParams } from 'next/navigation';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useLanguage } from '@/context/LanguageContext';
|
import { useLanguage } from '@/context/LanguageContext';
|
||||||
import { ticketsApi, paymentOptionsApi, Ticket, PaymentOptionsConfig } from '@/lib/api';
|
import { ticketsApi, paymentOptionsApi, Ticket, PaymentOptionsConfig } from '@/lib/api';
|
||||||
import { formatPrice, formatDateLong, formatTime } from '@/lib/utils';
|
import { formatPrice, formatDateLong, formatTime, getTpagoLink } from '@/lib/utils';
|
||||||
import Card from '@/components/ui/Card';
|
import Card from '@/components/ui/Card';
|
||||||
import Button from '@/components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
import {
|
import {
|
||||||
@@ -305,6 +305,7 @@ export default function BookingPaymentPage() {
|
|||||||
if (step === 'manual_payment' && ticket && paymentConfig) {
|
if (step === 'manual_payment' && ticket && paymentConfig) {
|
||||||
const isBankTransfer = ticket.payment?.provider === 'bank_transfer';
|
const isBankTransfer = ticket.payment?.provider === 'bank_transfer';
|
||||||
const isTpago = ticket.payment?.provider === 'tpago';
|
const isTpago = ticket.payment?.provider === 'tpago';
|
||||||
|
const tpagoLink = getTpagoLink(paymentConfig, ticket.bookingTicketCount || 1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="section-padding">
|
<div className="section-padding">
|
||||||
@@ -418,9 +419,9 @@ export default function BookingPaymentPage() {
|
|||||||
<h3 className="font-semibold text-gray-900">
|
<h3 className="font-semibold text-gray-900">
|
||||||
{locale === 'es' ? 'Pago con Tarjeta' : 'Card Payment'}
|
{locale === 'es' ? 'Pago con Tarjeta' : 'Card Payment'}
|
||||||
</h3>
|
</h3>
|
||||||
{paymentConfig.tpagoLink && (
|
{tpagoLink && (
|
||||||
<a
|
<a
|
||||||
href={paymentConfig.tpagoLink}
|
href={tpagoLink}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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"
|
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"
|
||||||
|
|||||||
@@ -1519,15 +1519,32 @@ export default function AdminEventDetailPage() {
|
|||||||
<div className="space-y-3 pt-3 border-t">
|
<div className="space-y-3 pt-3 border-t">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||||
{locale === 'es' ? 'Enlace de Pago TPago' : 'TPago Payment Link'}
|
{locale === 'es' ? 'Enlaces de Pago TPago (por cantidad de tickets)' : 'TPago Payment Links (per ticket quantity)'}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<p className="text-[10px] text-gray-500 mb-2">
|
||||||
type="url"
|
{locale === 'es'
|
||||||
value={paymentOverrides.tpagoLink ?? ''}
|
? 'Cada enlace tiene un monto fijo. Un enlace distinto por cantidad de tickets.'
|
||||||
onChange={(e) => updatePaymentOverride('tpagoLink', e.target.value || null)}
|
: 'Each link has a fixed amount. One link per ticket quantity.'}
|
||||||
placeholder={globalPaymentOptions?.tpagoLink || 'https://www.tpago.com.py/links?alias=...'}
|
</p>
|
||||||
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 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>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ export default function PaymentOptionsPage() {
|
|||||||
const [options, setOptions] = useState<PaymentOptionsConfig>({
|
const [options, setOptions] = useState<PaymentOptionsConfig>({
|
||||||
tpagoEnabled: false,
|
tpagoEnabled: false,
|
||||||
tpagoLink: null,
|
tpagoLink: null,
|
||||||
|
tpagoLink2: null,
|
||||||
|
tpagoLink3: null,
|
||||||
|
tpagoLink4: null,
|
||||||
|
tpagoLink5: null,
|
||||||
tpagoInstructions: null,
|
tpagoInstructions: null,
|
||||||
tpagoInstructionsEs: null,
|
tpagoInstructionsEs: null,
|
||||||
bankTransferEnabled: false,
|
bankTransferEnabled: false,
|
||||||
@@ -140,13 +144,31 @@ export default function PaymentOptionsPage() {
|
|||||||
<div className="space-y-4 pt-4 border-t">
|
<div className="space-y-4 pt-4 border-t">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
{locale === 'es' ? 'Enlace de Pago TPago' : 'TPago Payment Link'}
|
{locale === 'es' ? 'Enlaces de Pago TPago (por cantidad de tickets)' : 'TPago Payment Links (per ticket quantity)'}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<p className="text-xs text-gray-500 mb-2">
|
||||||
value={options.tpagoLink || ''}
|
{locale === 'es'
|
||||||
onChange={(e) => updateOption('tpagoLink', e.target.value || null)}
|
? 'Cada enlace tiene un monto fijo. Usá un enlace distinto para cada cantidad de tickets.'
|
||||||
placeholder="https://www.tpago.com.py/links?alias=..."
|
: '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>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -559,6 +559,7 @@ export interface Event {
|
|||||||
export interface Ticket {
|
export interface Ticket {
|
||||||
id: string;
|
id: string;
|
||||||
bookingId?: string; // Groups multiple tickets from same booking
|
bookingId?: string; // Groups multiple tickets from same booking
|
||||||
|
bookingTicketCount?: number; // Total tickets in the booking (for per-quantity payment links)
|
||||||
userId: string;
|
userId: string;
|
||||||
eventId: string;
|
eventId: string;
|
||||||
attendeeFirstName: string;
|
attendeeFirstName: string;
|
||||||
@@ -673,6 +674,10 @@ export interface PaymentWithDetails extends Payment {
|
|||||||
export interface PaymentOptionsConfig {
|
export interface PaymentOptionsConfig {
|
||||||
tpagoEnabled: boolean;
|
tpagoEnabled: boolean;
|
||||||
tpagoLink?: string | null;
|
tpagoLink?: string | null;
|
||||||
|
tpagoLink2?: string | null;
|
||||||
|
tpagoLink3?: string | null;
|
||||||
|
tpagoLink4?: string | null;
|
||||||
|
tpagoLink5?: string | null;
|
||||||
tpagoInstructions?: string | null;
|
tpagoInstructions?: string | null;
|
||||||
tpagoInstructionsEs?: string | null;
|
tpagoInstructionsEs?: string | null;
|
||||||
bankTransferEnabled: boolean;
|
bankTransferEnabled: boolean;
|
||||||
|
|||||||
@@ -165,3 +165,31 @@ export function formatPrice(price: number, currency: string = 'PYG'): string {
|
|||||||
export function formatCurrency(amount: number, currency: string = 'PYG'): string {
|
export function formatCurrency(amount: number, currency: string = 'PYG'): string {
|
||||||
return formatPrice(amount, currency);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user