first commit
This commit is contained in:
278
backend/src/routes/payment-options.ts
Normal file
278
backend/src/routes/payment-options.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
import { Hono } from 'hono';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { z } from 'zod';
|
||||
import { db, paymentOptions, eventPaymentOverrides, events } from '../db/index.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { requireAuth } from '../lib/auth.js';
|
||||
import { generateId, getNow } from '../lib/utils.js';
|
||||
|
||||
const paymentOptionsRouter = new Hono();
|
||||
|
||||
// Schema for updating global payment options
|
||||
const updatePaymentOptionsSchema = z.object({
|
||||
tpagoEnabled: z.boolean().optional(),
|
||||
tpagoLink: z.string().optional().nullable(),
|
||||
tpagoInstructions: z.string().optional().nullable(),
|
||||
tpagoInstructionsEs: z.string().optional().nullable(),
|
||||
bankTransferEnabled: z.boolean().optional(),
|
||||
bankName: z.string().optional().nullable(),
|
||||
bankAccountHolder: z.string().optional().nullable(),
|
||||
bankAccountNumber: z.string().optional().nullable(),
|
||||
bankAlias: z.string().optional().nullable(),
|
||||
bankPhone: z.string().optional().nullable(),
|
||||
bankNotes: z.string().optional().nullable(),
|
||||
bankNotesEs: z.string().optional().nullable(),
|
||||
lightningEnabled: z.boolean().optional(),
|
||||
cashEnabled: z.boolean().optional(),
|
||||
cashInstructions: z.string().optional().nullable(),
|
||||
cashInstructionsEs: z.string().optional().nullable(),
|
||||
});
|
||||
|
||||
// Schema for event-level overrides
|
||||
const updateEventOverridesSchema = z.object({
|
||||
tpagoEnabled: z.boolean().optional().nullable(),
|
||||
tpagoLink: z.string().optional().nullable(),
|
||||
tpagoInstructions: z.string().optional().nullable(),
|
||||
tpagoInstructionsEs: z.string().optional().nullable(),
|
||||
bankTransferEnabled: z.boolean().optional().nullable(),
|
||||
bankName: z.string().optional().nullable(),
|
||||
bankAccountHolder: z.string().optional().nullable(),
|
||||
bankAccountNumber: z.string().optional().nullable(),
|
||||
bankAlias: z.string().optional().nullable(),
|
||||
bankPhone: z.string().optional().nullable(),
|
||||
bankNotes: z.string().optional().nullable(),
|
||||
bankNotesEs: z.string().optional().nullable(),
|
||||
lightningEnabled: z.boolean().optional().nullable(),
|
||||
cashEnabled: z.boolean().optional().nullable(),
|
||||
cashInstructions: z.string().optional().nullable(),
|
||||
cashInstructionsEs: z.string().optional().nullable(),
|
||||
});
|
||||
|
||||
// Get global payment options
|
||||
paymentOptionsRouter.get('/', requireAuth(['admin']), async (c) => {
|
||||
const options = await (db as any)
|
||||
.select()
|
||||
.from(paymentOptions)
|
||||
.get();
|
||||
|
||||
// If no options exist yet, return defaults
|
||||
if (!options) {
|
||||
return c.json({
|
||||
paymentOptions: {
|
||||
tpagoEnabled: false,
|
||||
tpagoLink: null,
|
||||
tpagoInstructions: null,
|
||||
tpagoInstructionsEs: null,
|
||||
bankTransferEnabled: false,
|
||||
bankName: null,
|
||||
bankAccountHolder: null,
|
||||
bankAccountNumber: null,
|
||||
bankAlias: null,
|
||||
bankPhone: null,
|
||||
bankNotes: null,
|
||||
bankNotesEs: null,
|
||||
lightningEnabled: true,
|
||||
cashEnabled: true,
|
||||
cashInstructions: null,
|
||||
cashInstructionsEs: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return c.json({ paymentOptions: options });
|
||||
});
|
||||
|
||||
// Update global payment options
|
||||
paymentOptionsRouter.put('/', requireAuth(['admin']), zValidator('json', updatePaymentOptionsSchema), async (c) => {
|
||||
const data = c.req.valid('json');
|
||||
const user = (c as any).get('user');
|
||||
const now = getNow();
|
||||
|
||||
// Check if options exist
|
||||
const existing = await (db as any)
|
||||
.select()
|
||||
.from(paymentOptions)
|
||||
.get();
|
||||
|
||||
if (existing) {
|
||||
// Update existing
|
||||
await (db as any)
|
||||
.update(paymentOptions)
|
||||
.set({
|
||||
...data,
|
||||
updatedAt: now,
|
||||
updatedBy: user.id,
|
||||
})
|
||||
.where(eq((paymentOptions as any).id, existing.id));
|
||||
} else {
|
||||
// Create new
|
||||
const id = generateId();
|
||||
await (db as any).insert(paymentOptions).values({
|
||||
id,
|
||||
...data,
|
||||
updatedAt: now,
|
||||
updatedBy: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
const updated = await (db as any)
|
||||
.select()
|
||||
.from(paymentOptions)
|
||||
.get();
|
||||
|
||||
return c.json({ paymentOptions: updated, message: 'Payment options updated successfully' });
|
||||
});
|
||||
|
||||
// Get payment options for a specific event (merged with global)
|
||||
paymentOptionsRouter.get('/event/:eventId', async (c) => {
|
||||
const eventId = c.req.param('eventId');
|
||||
|
||||
// Get the event first to verify it exists
|
||||
const event = await (db as any)
|
||||
.select()
|
||||
.from(events)
|
||||
.where(eq((events as any).id, eventId))
|
||||
.get();
|
||||
|
||||
if (!event) {
|
||||
return c.json({ error: 'Event not found' }, 404);
|
||||
}
|
||||
|
||||
// Get global options
|
||||
const globalOptions = await (db as any)
|
||||
.select()
|
||||
.from(paymentOptions)
|
||||
.get();
|
||||
|
||||
// Get event overrides
|
||||
const overrides = await (db as any)
|
||||
.select()
|
||||
.from(eventPaymentOverrides)
|
||||
.where(eq((eventPaymentOverrides as any).eventId, eventId))
|
||||
.get();
|
||||
|
||||
// Merge global with overrides (override takes precedence if not null)
|
||||
const defaults = {
|
||||
tpagoEnabled: false,
|
||||
tpagoLink: null,
|
||||
tpagoInstructions: null,
|
||||
tpagoInstructionsEs: null,
|
||||
bankTransferEnabled: false,
|
||||
bankName: null,
|
||||
bankAccountHolder: null,
|
||||
bankAccountNumber: null,
|
||||
bankAlias: null,
|
||||
bankPhone: null,
|
||||
bankNotes: null,
|
||||
bankNotesEs: null,
|
||||
lightningEnabled: true,
|
||||
cashEnabled: true,
|
||||
cashInstructions: null,
|
||||
cashInstructionsEs: null,
|
||||
};
|
||||
|
||||
const global = globalOptions || defaults;
|
||||
|
||||
// Merge: override values take precedence if they're not null/undefined
|
||||
const merged = {
|
||||
tpagoEnabled: overrides?.tpagoEnabled ?? global.tpagoEnabled,
|
||||
tpagoLink: overrides?.tpagoLink ?? global.tpagoLink,
|
||||
tpagoInstructions: overrides?.tpagoInstructions ?? global.tpagoInstructions,
|
||||
tpagoInstructionsEs: overrides?.tpagoInstructionsEs ?? global.tpagoInstructionsEs,
|
||||
bankTransferEnabled: overrides?.bankTransferEnabled ?? global.bankTransferEnabled,
|
||||
bankName: overrides?.bankName ?? global.bankName,
|
||||
bankAccountHolder: overrides?.bankAccountHolder ?? global.bankAccountHolder,
|
||||
bankAccountNumber: overrides?.bankAccountNumber ?? global.bankAccountNumber,
|
||||
bankAlias: overrides?.bankAlias ?? global.bankAlias,
|
||||
bankPhone: overrides?.bankPhone ?? global.bankPhone,
|
||||
bankNotes: overrides?.bankNotes ?? global.bankNotes,
|
||||
bankNotesEs: overrides?.bankNotesEs ?? global.bankNotesEs,
|
||||
lightningEnabled: overrides?.lightningEnabled ?? global.lightningEnabled,
|
||||
cashEnabled: overrides?.cashEnabled ?? global.cashEnabled,
|
||||
cashInstructions: overrides?.cashInstructions ?? global.cashInstructions,
|
||||
cashInstructionsEs: overrides?.cashInstructionsEs ?? global.cashInstructionsEs,
|
||||
};
|
||||
|
||||
return c.json({
|
||||
paymentOptions: merged,
|
||||
hasOverrides: !!overrides,
|
||||
});
|
||||
});
|
||||
|
||||
// Get event payment overrides (admin only)
|
||||
paymentOptionsRouter.get('/event/:eventId/overrides', requireAuth(['admin', 'organizer']), async (c) => {
|
||||
const eventId = c.req.param('eventId');
|
||||
|
||||
const overrides = await (db as any)
|
||||
.select()
|
||||
.from(eventPaymentOverrides)
|
||||
.where(eq((eventPaymentOverrides as any).eventId, eventId))
|
||||
.get();
|
||||
|
||||
return c.json({ overrides: overrides || null });
|
||||
});
|
||||
|
||||
// Update event payment overrides
|
||||
paymentOptionsRouter.put('/event/:eventId/overrides', requireAuth(['admin', 'organizer']), zValidator('json', updateEventOverridesSchema), async (c) => {
|
||||
const eventId = c.req.param('eventId');
|
||||
const data = c.req.valid('json');
|
||||
const now = getNow();
|
||||
|
||||
// Verify event exists
|
||||
const event = await (db as any)
|
||||
.select()
|
||||
.from(events)
|
||||
.where(eq((events as any).id, eventId))
|
||||
.get();
|
||||
|
||||
if (!event) {
|
||||
return c.json({ error: 'Event not found' }, 404);
|
||||
}
|
||||
|
||||
// Check if overrides exist
|
||||
const existing = await (db as any)
|
||||
.select()
|
||||
.from(eventPaymentOverrides)
|
||||
.where(eq((eventPaymentOverrides as any).eventId, eventId))
|
||||
.get();
|
||||
|
||||
if (existing) {
|
||||
await (db as any)
|
||||
.update(eventPaymentOverrides)
|
||||
.set({
|
||||
...data,
|
||||
updatedAt: now,
|
||||
})
|
||||
.where(eq((eventPaymentOverrides as any).id, existing.id));
|
||||
} else {
|
||||
const id = generateId();
|
||||
await (db as any).insert(eventPaymentOverrides).values({
|
||||
id,
|
||||
eventId,
|
||||
...data,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
}
|
||||
|
||||
const updated = await (db as any)
|
||||
.select()
|
||||
.from(eventPaymentOverrides)
|
||||
.where(eq((eventPaymentOverrides as any).eventId, eventId))
|
||||
.get();
|
||||
|
||||
return c.json({ overrides: updated, message: 'Event payment overrides updated successfully' });
|
||||
});
|
||||
|
||||
// Delete event payment overrides (revert to global)
|
||||
paymentOptionsRouter.delete('/event/:eventId/overrides', requireAuth(['admin', 'organizer']), async (c) => {
|
||||
const eventId = c.req.param('eventId');
|
||||
|
||||
await (db as any)
|
||||
.delete(eventPaymentOverrides)
|
||||
.where(eq((eventPaymentOverrides as any).eventId, eventId));
|
||||
|
||||
return c.json({ message: 'Event payment overrides removed' });
|
||||
});
|
||||
|
||||
export default paymentOptionsRouter;
|
||||
Reference in New Issue
Block a user