const Event = require('../models/Event'); const { success, error } = require('../utils/response'); const { defaultManualEnablementFeeInrFromEnv } = require('../utils/manualTicketingFee'); const { manualQrFieldsSatisfied } = require('../utils/ticketingManualPayment'); const { createManualEnablementCheckoutOrder } = require('../services/ticketingManualEnablement.service'); async function assertEventAccess(eventId, user) { const event = await Event.findById(eventId).select('organization').lean(); if (!event) return { err: (h) => h.response(error('NOT_FOUND', 'Event not found')).code(404) }; if ( user.role !== 'super_admin' && String(event.organization) !== String(user.organization?._id || user.organization || '') ) { return { err: (h) => h.response(error('FORBIDDEN', 'Access denied')).code(403) }; } return { event }; } function shapeTicketingManualResponse(mp) { const raw = mp && typeof mp === 'object' ? mp : {}; /** Legacy field; buyers are no longer charged a per-order Eventbuzz fee (enablement fee covers the organiser). */ const fee = raw.platformFeeInr != null && Number.isFinite(Number(raw.platformFeeInr)) ? Number(raw.platformFeeInr) : 0; const mode = raw.manualQrMode === 'dynamic' ? 'dynamic' : 'upload'; return { enabled: Boolean(raw.enabled), manualQrMode: mode, upiQrImageUrl: raw.upiQrImageUrl || '', upiId: raw.upiId || '', payeeName: raw.payeeName || '', instructions: raw.instructions || '', platformFeeInr: fee, platformFeeInrIsDefault: raw.platformFeeInr == null, enablementFeeInr: defaultManualEnablementFeeInrFromEnv(), enablementPaid: Boolean(raw.enablementFeePaidAt), enablementPaidAt: raw.enablementFeePaidAt || null, }; } const patch = async (request, h) => { const { eventId } = request.params; const user = request.auth.credentials.user; const r = await assertEventAccess(eventId, user); if (r.err) return r.err(h); const p = request.payload; const doc = await Event.findById(eventId); if (!doc) return h.response(error('NOT_FOUND', 'Event not found')).code(404); const prev = doc.ticketingManualPayment && typeof doc.ticketingManualPayment === 'object' ? doc.ticketingManualPayment : {}; const next = { ...prev }; if (p.enabled !== undefined) next.enabled = Boolean(p.enabled); if (p.manualQrMode !== undefined) { next.manualQrMode = p.manualQrMode === 'dynamic' ? 'dynamic' : 'upload'; } if (p.upiQrImageUrl !== undefined) next.upiQrImageUrl = String(p.upiQrImageUrl || '').trim(); if (p.upiId !== undefined) next.upiId = String(p.upiId || '').trim(); if (p.payeeName !== undefined) next.payeeName = String(p.payeeName || '').trim(); if (p.instructions !== undefined) next.instructions = String(p.instructions || ''); if (p.platformFeeInr !== undefined) { if (p.platformFeeInr === null) next.platformFeeInr = null; else { const n = Number(p.platformFeeInr); if (!Number.isFinite(n) || n < 0) { return h.response(error('VALIDATION_ERROR', 'platformFeeInr must be a non-negative number or null')).code(400); } next.platformFeeInr = n; } } const enablementOk = Boolean(next.enablementFeePaidAt || prev.enablementFeePaidAt); if (next.enabled && !enablementOk) { return h .response( error( 'PAYMENT_REQUIRED', 'Pay the manual UPI enablement fee for this event before enabling manual checkout. The paid transaction appears under Settings → Billing.', ), ) .code(402); } if (next.enabled) { const mode = next.manualQrMode === 'dynamic' ? 'dynamic' : 'upload'; if (mode === 'upload' && !String(next.upiQrImageUrl || '').trim()) { return h.response(error('VALIDATION_ERROR', 'Add a UPI QR image or URL before enabling upload mode')).code(400); } if (mode === 'dynamic' && !String(next.upiId || '').trim()) { return h.response(error('VALIDATION_ERROR', 'Enter a UPI ID (VPA) before enabling dynamic QR mode')).code(400); } } if (next.enabled && !manualQrFieldsSatisfied(next)) { return h.response(error('VALIDATION_ERROR', 'Complete QR settings before enabling')).code(400); } doc.ticketingManualPayment = next; await doc.save(); return h.response( success({ ticketingManualPayment: shapeTicketingManualResponse(doc.ticketingManualPayment), }), ); }; const get = async (request, h) => { const { eventId } = request.params; const user = request.auth.credentials.user; const r = await assertEventAccess(eventId, user); if (r.err) return r.err(h); const doc = await Event.findById(eventId).select('ticketingManualPayment').lean(); if (!doc) return h.response(error('NOT_FOUND', 'Event not found')).code(404); return h.response( success({ ticketingManualPayment: shapeTicketingManualResponse(doc.ticketingManualPayment || {}), }), ); }; const checkoutEnablementOrder = async (request, h) => { const { eventId } = request.params; const user = request.auth.credentials.user; const r = await assertEventAccess(eventId, user); if (r.err) return r.err(h); const orgId = user.organization?._id || user.organization; if (!orgId) return h.response(error('VALIDATION_ERROR', 'No organisation')).code(400); try { const result = await createManualEnablementCheckoutOrder({ organizationId: orgId, userId: user._id, eventId, }); return h .response( success({ keyId: result.keyId, orderId: result.orderId, amount: result.amount, currency: result.currency, transactionId: result.transaction._id.toString(), checkoutSource: result.checkoutSource, enablementFeeInr: defaultManualEnablementFeeInrFromEnv(), }), ) .code(201); } catch (e) { if (e.code === 'ALREADY_PAID') { return h.response(error('VALIDATION_ERROR', e.message)).code(409); } if (e.code === 'INVALID_CONFIG') { return h.response(error('VALIDATION_ERROR', e.message)).code(400); } if ( e.code === 'NOT_FOUND' || e.code === 'FORBIDDEN' || e.code === 'NO_GATEWAY' || e.code === 'PLATFORM_RAZORPAY_MISSING' || e.code === 'INVALID_AMOUNT' || e.code === 'RAZORPAY_SDK_MISSING' ) { return h.response(error('VALIDATION_ERROR', e.message)).code(400); } throw e; } }; module.exports = { patch, get, checkoutEnablementOrder };