feat: add advanced analytics and territory management system
- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
196
reactrebuild0825/functions/lib/checkout.js
Normal file
196
reactrebuild0825/functions/lib/checkout.js
Normal file
@@ -0,0 +1,196 @@
|
||||
"use strict";
|
||||
const __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createCheckout = void 0;
|
||||
const https_1 = require("firebase-functions/v2/https");
|
||||
const firebase_functions_1 = require("firebase-functions");
|
||||
const firestore_1 = require("firebase-admin/firestore");
|
||||
const stripe_1 = __importDefault(require("stripe"));
|
||||
const stripe = new stripe_1.default(process.env.STRIPE_SECRET_KEY, {
|
||||
apiVersion: "2024-11-20.acacia",
|
||||
});
|
||||
const db = (0, firestore_1.getFirestore)();
|
||||
const PLATFORM_FEE_BPS = parseInt(process.env.PLATFORM_FEE_BPS || "300");
|
||||
/**
|
||||
* Creates a Stripe Checkout Session for a connected account
|
||||
* POST /api/checkout/create
|
||||
*/
|
||||
exports.createCheckout = (0, https_1.onRequest)({
|
||||
cors: true,
|
||||
enforceAppCheck: false,
|
||||
region: "us-central1",
|
||||
}, async (req, res) => {
|
||||
if (req.method !== "POST") {
|
||||
res.status(405).json({ error: "Method not allowed" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { orgId, eventId, ticketTypeId, qty, purchaserEmail, successUrl, cancelUrl, } = req.body;
|
||||
// Validate input
|
||||
if (!orgId || !eventId || !ticketTypeId || !qty || qty <= 0) {
|
||||
res.status(400).json({
|
||||
error: "Missing required fields: orgId, eventId, ticketTypeId, qty",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!successUrl || !cancelUrl) {
|
||||
res.status(400).json({
|
||||
error: "Missing required URLs: successUrl, cancelUrl",
|
||||
});
|
||||
return;
|
||||
}
|
||||
firebase_functions_1.logger.info("Creating checkout session", {
|
||||
orgId,
|
||||
eventId,
|
||||
ticketTypeId,
|
||||
qty,
|
||||
purchaserEmail: purchaserEmail ? "provided" : "not provided",
|
||||
});
|
||||
// Get organization payment info
|
||||
const orgDoc = await db.collection("orgs").doc(orgId).get();
|
||||
if (!orgDoc.exists) {
|
||||
res.status(404).json({ error: "Organization not found" });
|
||||
return;
|
||||
}
|
||||
const orgData = orgDoc.data();
|
||||
const stripeAccountId = orgData.payment?.stripe?.accountId;
|
||||
if (!stripeAccountId) {
|
||||
res.status(400).json({
|
||||
error: "Organization has no connected Stripe account",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Validate account is properly onboarded
|
||||
if (!orgData.payment?.stripe?.chargesEnabled) {
|
||||
res.status(400).json({
|
||||
error: "Stripe account is not ready to accept payments",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Get event
|
||||
const eventDoc = await db.collection("events").doc(eventId).get();
|
||||
if (!eventDoc.exists) {
|
||||
res.status(404).json({ error: "Event not found" });
|
||||
return;
|
||||
}
|
||||
const eventData = eventDoc.data();
|
||||
if (eventData.orgId !== orgId) {
|
||||
res.status(403).json({ error: "Event does not belong to organization" });
|
||||
return;
|
||||
}
|
||||
// Get ticket type
|
||||
const ticketTypeDoc = await db.collection("ticket_types").doc(ticketTypeId).get();
|
||||
if (!ticketTypeDoc.exists) {
|
||||
res.status(404).json({ error: "Ticket type not found" });
|
||||
return;
|
||||
}
|
||||
const ticketTypeData = ticketTypeDoc.data();
|
||||
if (ticketTypeData.orgId !== orgId || ticketTypeData.eventId !== eventId) {
|
||||
res.status(403).json({
|
||||
error: "Ticket type does not belong to organization/event",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Check inventory
|
||||
const available = ticketTypeData.inventory - (ticketTypeData.sold || 0);
|
||||
if (available < qty) {
|
||||
res.status(400).json({
|
||||
error: `Not enough tickets available. Requested: ${qty}, Available: ${available}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Calculate application fee
|
||||
const subtotal = ticketTypeData.priceCents * qty;
|
||||
const applicationFeeAmount = Math.round((subtotal * PLATFORM_FEE_BPS) / 10000);
|
||||
firebase_functions_1.logger.info("Checkout calculation", {
|
||||
priceCents: ticketTypeData.priceCents,
|
||||
qty,
|
||||
subtotal,
|
||||
platformFeeBps: PLATFORM_FEE_BPS,
|
||||
applicationFeeAmount,
|
||||
});
|
||||
// Create Stripe Checkout Session
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
mode: "payment",
|
||||
payment_method_types: ["card"],
|
||||
customer_email: purchaserEmail || undefined,
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
currency: "usd",
|
||||
product_data: {
|
||||
name: `${eventData.name} – ${ticketTypeData.name}`,
|
||||
description: `Tickets for ${eventData.name}`,
|
||||
},
|
||||
unit_amount: ticketTypeData.priceCents,
|
||||
},
|
||||
quantity: qty,
|
||||
},
|
||||
],
|
||||
success_url: `${successUrl}?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: cancelUrl,
|
||||
metadata: {
|
||||
orgId,
|
||||
eventId,
|
||||
ticketTypeId,
|
||||
qty: String(qty),
|
||||
purchaserEmail: purchaserEmail || "",
|
||||
},
|
||||
payment_intent_data: {
|
||||
application_fee_amount: applicationFeeAmount,
|
||||
metadata: {
|
||||
orgId,
|
||||
eventId,
|
||||
ticketTypeId,
|
||||
qty: String(qty),
|
||||
},
|
||||
},
|
||||
}, { stripeAccount: stripeAccountId });
|
||||
// Create placeholder order for UI polling
|
||||
const orderData = {
|
||||
orgId,
|
||||
eventId,
|
||||
ticketTypeId,
|
||||
qty,
|
||||
sessionId: session.id,
|
||||
status: "pending",
|
||||
totalCents: subtotal,
|
||||
createdAt: new Date(),
|
||||
purchaserEmail: purchaserEmail || null,
|
||||
paymentIntentId: null,
|
||||
stripeAccountId,
|
||||
};
|
||||
await db.collection("orders").doc(session.id).set(orderData);
|
||||
firebase_functions_1.logger.info("Checkout session created", {
|
||||
sessionId: session.id,
|
||||
url: session.url,
|
||||
orgId,
|
||||
eventId,
|
||||
stripeAccountId,
|
||||
});
|
||||
const response = {
|
||||
url: session.url,
|
||||
sessionId: session.id,
|
||||
};
|
||||
res.status(200).json(response);
|
||||
}
|
||||
catch (error) {
|
||||
firebase_functions_1.logger.error("Error creating checkout session", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
if (error instanceof stripe_1.default.errors.StripeError) {
|
||||
res.status(400).json({
|
||||
error: `Stripe error: ${error.message}`,
|
||||
code: error.code,
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.status(500).json({
|
||||
error: "Internal server error creating checkout session",
|
||||
});
|
||||
}
|
||||
});
|
||||
// # sourceMappingURL=checkout.js.map
|
||||
Reference in New Issue
Block a user