174 lines
6.3 KiB
JavaScript
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 };
|