EventBuzz_Backend/src/controllers/eventTicketingManual.contro...

174 lines
6.3 KiB
JavaScript

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 };