Function bodies 202 total
HourlyQuote function · typescript · L6-L134 (129 LOC)src/pages/hourly/Quote.tsx
export default function HourlyQuote() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const intent = searchParams.get("intent");
// Guard: intentionally strict to prevent hourly intent leakage
// Only allow access when intent=hourly is explicitly set
useEffect(() => {
if (intent !== "hourly") {
navigate("/hourly", { replace: true });
}
}, [intent, navigate]);
const handleWhatsAppContact = () => {
const message = encodeURIComponent(
"Hello, I'm interested in hourly chauffeur service (mise à disposition) in Paris. Could you provide a quote?",
);
window.open(`https://wa.me/33123456789?text=${message}`, "_blank");
};
const handleEmailContact = () => {
window.location.href =
"mailto:[email protected]?subject=Hourly Service Quote Request";
};
if (intent !== "hourly") {
return null;
}
return (
<div className="min-h-screen bg-gradient-to-br from-primary/5 to-white py-12">
NotFound function · typescript · L4-L41 (38 LOC)src/pages/NotFound.tsx
export default function NotFound() {
const navigate = useNavigate();
return (
<div className="min-h-screen bg-background pt-28 pb-12 px-4">
<div className="mx-auto max-w-2xl rounded-2xl border border-border bg-card p-8 text-center shadow-sm">
<p className="text-sm font-semibold uppercase tracking-wide text-secondary">
404
</p>
<h1 className="mt-3 text-3xl font-display font-bold text-primary">
Page not found
</h1>
<p className="mt-3 text-muted-foreground">
The page you requested is not available. You can continue to booking
or go back to the home page.
</p>
<div className="mt-8 flex flex-col gap-3 sm:flex-row sm:justify-center">
<Button
type="button"
onClick={() => navigate("/booking")}
className="w-full sm:w-auto"
>
Go to Booking
</Button>
<Button
type="button"
variPrivacyPage function · typescript · L5-L157 (153 LOC)src/pages/PrivacyPage.tsx
export default function PrivacyPage() {
const { t } = useLanguage();
return (
<div className="min-h-screen bg-gradient-to-b from-white via-champagne/30 to-white">
{/* Hero Section */}
<section className="relative py-20 bg-gradient-to-br from-primary/10 via-champagne/20 to-white">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl">
<div className="text-center">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 mb-6">
<Shield className="w-8 h-8 text-primary" />
</div>
<h1 className="text-4xl md:text-5xl lg:text-6xl font-display font-bold text-secondary mb-6">
Privacy Policy
</h1>
<p className="text-lg md:text-xl text-gray-600 max-w-2xl mx-auto leading-relaxed">
Your privacy is important to us. Learn how we protect your data.
</p>
<p className="text-sm text-gray-500 mt-TermsPage function · typescript · L5-L177 (173 LOC)src/pages/TermsPage.tsx
export default function TermsPage() {
const { t } = useLanguage();
return (
<div className="min-h-screen bg-gradient-to-b from-white via-champagne/30 to-white">
{/* Hero Section */}
<section className="relative py-20 bg-gradient-to-br from-primary/10 via-champagne/20 to-white">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl">
<div className="text-center">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 mb-6">
<FileText className="w-8 h-8 text-primary" />
</div>
<h1 className="text-4xl md:text-5xl lg:text-6xl font-display font-bold text-secondary mb-6">
Terms & Conditions
</h1>
<p className="text-lg md:text-xl text-gray-600 max-w-2xl mx-auto leading-relaxed">
Please read these terms carefully before using our services
</p>
<p className="text-sm text-gray-500 mt-4processBookingEvent function · typescript · L52-L155 (104 LOC)src/services/booking/BookingOrchestrator.ts
export async function processBookingEvent(
booking: BookingContext,
event: BookingEvent,
metadata?: Record<string, any>
): Promise<OrchestrationResult> {
console.log(`[Orchestrator] Processing event ${event} for booking ${booking.id}`);
try {
// 1. Ejecutar transición de estado con validaciones
const transition = executeTransitionWithValidation(
booking.status,
event,
{
payment_mode: booking.payment_mode,
pickup_datetime: booking.pickup_datetime,
hold_status: booking.hold_status,
partner_id: booking.partner_id,
},
{
event,
actor: metadata?.actor || 'system',
...metadata,
}
);
if (!transition.success) {
console.error(`[Orchestrator] Transition failed: ${transition.error}`);
return {
success: false,
booking,
error: transition.error,
};
}
console.log(
`[Orchestrator] State transition: ${transition.from_state} -> ${processBookingEventSequence function · typescript · L167-L187 (21 LOC)src/services/booking/BookingOrchestrator.ts
export async function processBookingEventSequence(
booking: BookingContext,
events: Array<{ event: BookingEvent; metadata?: Record<string, any> }>
): Promise<OrchestrationResult[]> {
const results: OrchestrationResult[] = [];
let currentBooking = booking;
for (const { event, metadata } of events) {
const result = await processBookingEvent(currentBooking, event, metadata);
results.push(result);
if (!result.success) {
console.error(`[Orchestrator] Sequence stopped at event ${event}`);
break;
}
currentBooking = result.booking;
}
return results;
}getBookingById function · typescript · L20-L33 (14 LOC)src/services/booking/BookingRepository.ts
export async function getBookingById(bookingId: string): Promise<BookingContext | null> {
const { data, error } = await supabase
.from('bookings_v312')
.select('*')
.eq('id', bookingId)
.single();
if (error) {
console.error('[Repository] Error fetching booking:', error);
return null;
}
return mapDatabaseToContext(data);
}Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
updateBookingStatus function · typescript · L38-L58 (21 LOC)src/services/booking/BookingRepository.ts
export async function updateBookingStatus(
bookingId: string,
newStatus: BookingStatus,
metadata?: Record<string, any>
): Promise<boolean> {
const { error } = await supabase
.from('bookings_v312')
.update({
status: newStatus,
updated_at: new Date().toISOString(),
...metadata,
})
.eq('id', bookingId);
if (error) {
console.error('[Repository] Error updating booking:', error);
return false;
}
return true;
}logStateTransition function · typescript · L63-L90 (28 LOC)src/services/booking/BookingRepository.ts
export async function logStateTransition(
bookingId: string,
fromState: BookingStatus,
toState: BookingStatus,
event: BookingEvent,
metadata?: Record<string, any>
): Promise<boolean> {
const { error } = await supabase
.from('booking_state_logs')
.insert({
booking_id: bookingId,
from_state: fromState,
to_state: toState,
event,
metadata: {
timestamp: new Date().toISOString(),
...metadata,
},
created_at: new Date().toISOString(),
});
if (error) {
console.error('[Repository] Error logging transition:', error);
return false;
}
return true;
}getBookingStateHistory function · typescript · L95-L103 (9 LOC)src/services/booking/BookingRepository.ts
export async function getBookingStateHistory(
bookingId: string
): Promise<Array<{
from_state: BookingStatus;
to_state: BookingStatus;
event: BookingEvent;
created_at: string;
metadata?: any;
}>> {getBookingsByStatus function · typescript · L121-L138 (18 LOC)src/services/booking/BookingRepository.ts
export async function getBookingsByStatus(
status: BookingStatus,
limit = 100
): Promise<BookingContext[]> {
const { data, error } = await supabase
.from('bookings_v312')
.select('*')
.eq('status', status)
.limit(limit)
.order('created_at', { ascending: false });
if (error) {
console.error('[Repository] Error fetching bookings:', error);
return [];
}
return (data || []).map(mapDatabaseToContext);
}getBookingsNeedingHold function · typescript · L143-L163 (21 LOC)src/services/booking/BookingRepository.ts
export async function getBookingsNeedingHold(): Promise<BookingContext[]> {
const now = new Date();
const in24h = new Date(now.getTime() + 24 * 60 * 60 * 1000);
const in25h = new Date(now.getTime() + 25 * 60 * 60 * 1000);
const { data, error } = await supabase
.from('bookings_v312')
.select('*')
.eq('payment_mode', 'flexible')
.eq('status', 'confirmed')
.is('hold_payment_intent_id', null)
.gte('pickup_datetime', in24h.toISOString())
.lt('pickup_datetime', in25h.toISOString());
if (error) {
console.error('[Repository] Error fetching bookings needing hold:', error);
return [];
}
return (data || []).map(mapDatabaseToContext);
}mapDatabaseToContext function · typescript · L168-L185 (18 LOC)src/services/booking/BookingRepository.ts
function mapDatabaseToContext(data: any): BookingContext {
return {
id: data.id,
status: data.status,
payment_mode: data.payment_mode,
pickup_datetime: data.pickup_datetime,
hold_status: data.hold_status,
partner_id: data.partner_id,
customer_name: data.customer_name,
customer_email: data.customer_email,
customer_phone: data.customer_phone,
route_key: data.route_key,
vehicle_type: data.vehicle_type,
total_price_cents: data.total_price_cents,
hold_amount_cents: data.hold_amount_cents,
confirmation_number: data.confirmation_number,
};
}replaceTemplateVariables function · typescript · L260-L269 (10 LOC)src/services/notifications/NotificationService.ts
function replaceTemplateVariables(template: string, context: NotificationContext): string {
let result = template;
Object.keys(context).forEach((key) => {
const value = context[key] || '';
result = result.replace(new RegExp(`{{${key}}}`, 'g'), String(value));
});
return result;
}sendWhatsApp function · typescript · L274-L297 (24 LOC)src/services/notifications/NotificationService.ts
async function sendWhatsApp(
recipient: NotificationRecipient,
message: string
): Promise<boolean> {
const TWILIO_ACCOUNT_SID = import.meta.env?.VITE_TWILIO_ACCOUNT_SID;
const TWILIO_AUTH_TOKEN = import.meta.env?.VITE_TWILIO_AUTH_TOKEN;
const TWILIO_WHATSAPP_NUMBER = import.meta.env?.VITE_TWILIO_WHATSAPP_NUMBER;
if (!TWILIO_ACCOUNT_SID || !recipient.phone) {
console.warn('[WhatsApp] Missing credentials or phone number');
return false;
}
try {
// TODO: Implementar llamada a Twilio API
console.log('[WhatsApp] Sending to:', recipient.phone);
console.log('[WhatsApp] Message:', message);
return true;
} catch (error) {
console.error('[WhatsApp] Error:', error);
return false;
}
}Open data scored by Repobility · https://repobility.com
sendSMS function · typescript · L302-L325 (24 LOC)src/services/notifications/NotificationService.ts
async function sendSMS(
recipient: NotificationRecipient,
message: string
): Promise<boolean> {
const TWILIO_ACCOUNT_SID = import.meta.env?.VITE_TWILIO_ACCOUNT_SID;
const TWILIO_AUTH_TOKEN = import.meta.env?.VITE_TWILIO_AUTH_TOKEN;
const TWILIO_SMS_NUMBER = import.meta.env?.VITE_TWILIO_SMS_NUMBER;
if (!TWILIO_ACCOUNT_SID || !recipient.phone) {
console.warn('[SMS] Missing credentials or phone number');
return false;
}
try {
// TODO: Implementar llamada a Twilio API
console.log('[SMS] Sending to:', recipient.phone);
console.log('[SMS] Message:', message);
return true;
} catch (error) {
console.error('[SMS] Error:', error);
return false;
}
}sendEmail function · typescript · L330-L354 (25 LOC)src/services/notifications/NotificationService.ts
async function sendEmail(
recipient: NotificationRecipient,
subject: string,
body: string
): Promise<boolean> {
const SENDGRID_API_KEY = import.meta.env?.VITE_SENDGRID_API_KEY;
const SENDGRID_FROM_EMAIL = import.meta.env?.VITE_SENDGRID_FROM_EMAIL;
if (!SENDGRID_API_KEY || !recipient.email) {
console.warn('[Email] Missing credentials or email address');
return false;
}
try {
// TODO: Implementar llamada a SendGrid API
console.log('[Email] Sending to:', recipient.email);
console.log('[Email] Subject:', subject);
console.log('[Email] Body:', body);
return true;
} catch (error) {
console.error('[Email] Error:', error);
return false;
}
}sendNotification function · typescript · L359-L364 (6 LOC)src/services/notifications/NotificationService.ts
export async function sendNotification(
event: BookingEvent,
recipient: NotificationRecipient,
context: NotificationContext,
channels?: NotificationChannel[]
): Promise<{ success: boolean; channels: Record<NotificationChannel, boolean> }> {PriceCalculationService.getInstance method · typescript · L11-L16 (6 LOC)src/services/PriceCalculationService.ts
public static getInstance(): PriceCalculationService {
if (!PriceCalculationService.instance) {
PriceCalculationService.instance = new PriceCalculationService();
}
return PriceCalculationService.instance;
}PriceCalculationService.loadFixedRoutes method · typescript · L23-L40 (18 LOC)src/services/PriceCalculationService.ts
private async loadFixedRoutes(): Promise<void> {
// TODO: Create fixed_routes table in Supabase
// For now, skip loading from database
console.warn('Fixed routes table not available. Using pricing.ts constants only.');
/* COMMENTED OUT: Requires fixed_routes table
const { data, error } = await supabase
.from('fixed_routes')
.select('*');
if (error) throw error;
data?.forEach(route => {
const key = this.generateRouteKey(route.origin_type, route.destination_type);
this.fixedRoutes.set(key, route);
});
*/
}PriceCalculationService.loadServiceLevels method · typescript · L42-L73 (32 LOC)src/services/PriceCalculationService.ts
private async loadServiceLevels(): Promise<void> {
// TODO: Create service_levels table in Supabase
// For now, use vehicles table as fallback
try {
const { data, error } = await supabase
.from('vehicles')
.select('*');
if (error) throw error;
data?.forEach(vehicle => {
// Ensure features is an object, not an array
const vehicleFeatures = Array.isArray(vehicle.features) ? {} : (vehicle.features || {});
this.serviceLevels.set(vehicle.id, {
id: vehicle.id,
name: vehicle.name,
description: { en: vehicle.description || '' },
features: {
wifi: (vehicleFeatures as any)?.wifi || false,
water: (vehicleFeatures as any)?.water || false,
meet_greet: (vehicleFeatures as any)?.meet_greet || false,
flight_tracking: (vehicleFeatures as any)?.flight_tracking || false,
...vehicleFeatures
},
multiplier: 1.0 // DPriceCalculationService.calculatePrice method · typescript · L79-L93 (15 LOC)src/services/PriceCalculationService.ts
public async calculatePrice(request: BookingPriceRequest): Promise<PriceCalculation> {
try {
// 1. Buscar ruta fija
const fixedRoute = this.getFixedRoute(request);
if (fixedRoute) {
return this.calculateFixedRoutePrice(fixedRoute, request);
}
// 2. Si no es ruta fija, calcular precio dinámico
return this.calculateDynamicPrice(request);
} catch (error) {
console.error('Error calculating price:', error);
throw error;
}
}PriceCalculationService.getFixedRoute method · typescript · L95-L101 (7 LOC)src/services/PriceCalculationService.ts
private getFixedRoute(request: BookingPriceRequest): any | null {
const key = this.generateRouteKey(
this.getLocationType(request.origin),
this.getLocationType(request.destination)
);
return this.fixedRoutes.get(key) || null;
}Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
PriceCalculationService.getLocationType method · typescript · L103-L110 (8 LOC)src/services/PriceCalculationService.ts
private getLocationType(location: string): string {
// Implementar lógica para determinar el tipo de ubicación
if (location.toLowerCase().includes('cdg')) return 'cdg_airport';
if (location.toLowerCase().includes('orly')) return 'orly_airport';
if (location.toLowerCase().includes('beauvais')) return 'beauvais_airport';
if (location.toLowerCase().includes('disney')) return 'disney';
return 'other';
}PriceCalculationService.calculateFixedRoutePrice method · typescript · L112-L131 (20 LOC)src/services/PriceCalculationService.ts
private calculateFixedRoutePrice(route: any, request: BookingPriceRequest): PriceCalculation {
const basePrice = request.passengers <= 3 ? route.base_price_1_3 : route.base_price_4_7;
const serviceLevel = this.serviceLevels.get(request.serviceLevel);
if (!serviceLevel) {
throw new Error(`Service level ${request.serviceLevel} not found`);
}
return {
basePrice,
finalPrice: Math.round(basePrice * serviceLevel.multiplier),
multiplier: serviceLevel.multiplier,
currency: 'EUR',
breakdown: {
base: basePrice,
serviceLevelAdjustment: basePrice * (serviceLevel.multiplier - 1),
extras: 0
}
};
}PriceCalculationService.calculateDynamicPrice method · typescript · L133-L154 (22 LOC)src/services/PriceCalculationService.ts
private async calculateDynamicPrice(request: BookingPriceRequest): Promise<PriceCalculation> {
// Aquí implementaremos el cálculo basado en Google Maps y zonas
// Por ahora, usamos un precio base temporal
const basePrice = 100; // Temporal
const serviceLevel = this.serviceLevels.get(request.serviceLevel);
if (!serviceLevel) {
throw new Error(`Service level ${request.serviceLevel} not found`);
}
return {
basePrice,
finalPrice: Math.round(basePrice * serviceLevel.multiplier),
multiplier: serviceLevel.multiplier,
currency: 'EUR',
breakdown: {
base: basePrice,
serviceLevelAdjustment: basePrice * (serviceLevel.multiplier - 1),
extras: 0
}
};
}normalizeBookingStatus function · typescript · L45-L64 (20 LOC)src/services/state-machine/BookingStateMachine.ts
export function normalizeBookingStatus(
status: BookingStatus | LegacyBookingStatus
): BookingStatus {
switch (status) {
case "pending_payment":
return "pending";
case "partner_assigned":
return "driver_assigned";
case "failed":
return "payment_failed";
// hold_pending y hold_confirmed deberían usar FlexibleSubStatus
// pero por compatibilidad temporal los mapeamos a estados existentes
case "hold_pending":
return "confirmed"; // Estado principal mientras hold está pendiente
case "hold_confirmed":
return "driver_assigned"; // Listo para asignar conductor
default:
return status as BookingStatus;
}
}isValidTransition function · typescript · L166-L173 (8 LOC)src/services/state-machine/BookingStateMachine.ts
export function isValidTransition(
currentState: BookingStatus | LegacyBookingStatus,
event: BookingEvent,
): boolean {
const normalizedState = normalizeBookingStatus(currentState);
const allowedTransitions = STATE_TRANSITIONS[normalizedState];
return event in allowedTransitions;
}getNextState function · typescript · L178-L185 (8 LOC)src/services/state-machine/BookingStateMachine.ts
export function getNextState(
currentState: BookingStatus | LegacyBookingStatus,
event: BookingEvent,
): BookingStatus | null {
const normalizedState = normalizeBookingStatus(currentState);
const allowedTransitions = STATE_TRANSITIONS[normalizedState];
return allowedTransitions[event] || null;
}executeTransition function · typescript · L190-L234 (45 LOC)src/services/state-machine/BookingStateMachine.ts
export function executeTransition(
currentState: BookingStatus | LegacyBookingStatus,
event: BookingEvent,
metadata?: Partial<TransitionMetadata>,
): TransitionResult {
const normalizedState = normalizeBookingStatus(currentState);
// Validar que la transición es válida
if (!isValidTransition(normalizedState, event)) {
return {
success: false,
from_state: normalizedState,
to_state: normalizedState,
event,
error: `Invalid transition: ${normalizedState} -> ${event}`,
};
}
// Obtener el siguiente estado
const nextState = getNextState(normalizedState, event);
if (!nextState) {
return {
success: false,
from_state: normalizedState,
to_state: normalizedState,
event,
error: `No next state defined for: ${normalizedState} -> ${event}`,
};
}
// Transición exitosa
return {
success: true,
from_state: normalizedState,
to_state: nextState,
event,
metadata: {
event,
validateBusinessRules function · typescript · L265-L321 (57 LOC)src/services/state-machine/BookingStateMachine.ts
export function validateBusinessRules(
currentState: BookingStatus | LegacyBookingStatus,
event: BookingEvent,
context: {
payment_mode?: "prepaid" | "flexible";
pickup_datetime?: string;
flexible_sub_status?: FlexibleSubStatus;
partner_id?: string;
},
): BusinessValidation {
const normalizedState = normalizeBookingStatus(currentState);
// Regla 1: HOLD_CREATED solo es válido para modo flexible
if (event === "HOLD_CREATED" && context.payment_mode !== "flexible") {
return {
valid: false,
error: "Hold creation is only valid for flexible payment mode",
};
}
// Regla 2: HOLD_CREATED solo dentro de ventana de 24h
if (event === "HOLD_CREATED" && context.pickup_datetime) {
const pickupTime = new Date(context.pickup_datetime).getTime();
const now = Date.now();
const hoursUntilPickup = (pickupTime - now) / (1000 * 60 * 60);
if (hoursUntilPickup > 24 || hoursUntilPickup < 0) {
return {
valid: false,
eAll rows above produced by Repobility · https://repobility.com
executeTransitionWithValidation function · typescript · L326-L356 (31 LOC)src/services/state-machine/BookingStateMachine.ts
export function executeTransitionWithValidation(
currentState: BookingStatus,
event: BookingEvent,
context: {
payment_mode?: "prepaid" | "flexible";
pickup_datetime?: string;
hold_status?: string;
partner_id?: string;
},
metadata?: Partial<TransitionMetadata>,
): TransitionResult {
// Primero validar reglas de negocio
const businessValidation = validateBusinessRules(
currentState,
event,
context,
);
if (!businessValidation.valid) {
return {
success: false,
from_state: currentState,
to_state: currentState,
event,
error: `Business rule violation: ${businessValidation.error}`,
};
}
// Si pasa validaciones, ejecutar transición normal
return executeTransition(currentState, event, metadata);
}getCorsHeaders function · typescript · L12-L22 (11 LOC)supabase/functions/create-booking-payment/index.ts
function getCorsHeaders(req: Request): Record<string, string> {
const origin = req.headers.get('Origin') ?? '';
const allowedOrigin = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
return {
'Access-Control-Allow-Origin': allowedOrigin,
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
'Access-Control-Max-Age': '86400',
'Vary': 'Origin',
};
}classifyError function · typescript · L30-L69 (40 LOC)supabase/functions/create-booking-payment/index.ts
function classifyError(err: unknown): RetryDecision {
const anyErr = err as any;
const code = anyErr?.code ?? anyErr?.error?.code;
const status = anyErr?.status ?? anyErr?.error?.status;
const message = String(anyErr?.message ?? anyErr?.error?.message ?? "");
// Deterministic DB conflicts: NEVER RETRY
if (code === "23505" || code === "23P01" || code === "23503" || code === "23514") {
return { retry: false, status: 409, code: "DB_CONFLICT" };
}
if (status === 409) {
return { retry: false, status: 409, code: "CONFLICT" };
}
if (status === 400) {
return { retry: false, status: 400, code: "BAD_REQUEST" };
}
// Stripe errors: retry only on transient types
const stripeType = anyErr?.type ?? anyErr?.error?.type;
if (typeof stripeType === "string") {
if (
stripeType.includes("api_connection") ||
stripeType.includes("rate_limit") ||
stripeType.includes("api_error")
) {
return { retry: true };
}
return { retry: falerrorResponse function · typescript · L99-L116 (18 LOC)supabase/functions/create-booking-payment/index.ts
function errorResponse(err: unknown, fallbackStatus = 500, corsHeaders: Record<string, string> = {}) {
const anyErr = err as any;
const decision = anyErr?.__retryDecision as RetryDecision | undefined;
const status = decision?.status ?? fallbackStatus;
const code = decision?.code ?? "ERROR";
return new Response(
JSON.stringify({
ok: false,
code,
message: String(anyErr?.message ?? "error"),
details: anyErr?.details ?? anyErr?.error?.details ?? null,
dbCode: anyErr?.code ?? anyErr?.error?.code ?? null,
}),
{ status, headers: { ...corsHeaders, 'Content-Type': 'application/json' } },
);
}generateSecureCode function · typescript · L214-L222 (9 LOC)supabase/functions/create-exit-lead/index.ts
function generateSecureCode(length: number): string {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' // No ambiguous chars (0,O,1,I)
const randomBytes = new Uint8Array(length)
crypto.getRandomValues(randomBytes)
return Array.from(randomBytes)
.map(byte => chars[byte % chars.length])
.join('')
}sendWelcomeEmail function · typescript · L227-L266 (40 LOC)supabase/functions/create-exit-lead/index.ts
async function sendWelcomeEmail(email: string, couponCode: string, language: string) {
try {
const { getEmailTemplate } = await import('./email-templates.ts')
const { subject, html } = getEmailTemplate(couponCode, language)
const RESEND_API_KEY = Deno.env.get('RESEND_API_KEY')
if (!RESEND_API_KEY) {
console.error('RESEND_API_KEY not configured')
return
}
const response = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${RESEND_API_KEY}`
},
body: JSON.stringify({
from: 'Paris Elite Services <[email protected]>',
to: [email],
subject: subject,
html: html
})
})
if (!response.ok) {
const error = await response.text()
console.error('Failed to send email:', error)
throw new Error('Failed to send email')
}
const result = await response.json(getCorsHeaders function · typescript · L10-L20 (11 LOC)supabase/functions/get-stripe-key/index.ts
function getCorsHeaders(req: Request): Record<string, string> {
const origin = req.headers.get('Origin') ?? '';
const allowedOrigin = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
return {
'Access-Control-Allow-Origin': allowedOrigin,
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Expose-Headers': 'Content-Length, X-JSON',
'Vary': 'Origin',
};
}notifyAvailablePartners function · typescript · L144-L151 (8 LOC)supabase/functions/partner-sla-job-v312/index.ts
async function notifyAvailablePartners(supabase: any, booking: any) {
console.log(`[Notify] Sending notifications for booking ${booking.id}`);
// TODO: Implementar lógica de notificación
// - Buscar conductores disponibles en la zona
// - Enviar notificación push/SMS/WhatsApp
// - Registrar en tabla de notificaciones
}Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
autoAssignPartner function · typescript · L156-L163 (8 LOC)supabase/functions/partner-sla-job-v312/index.ts
async function autoAssignPartner(supabase: any, booking: any) {
console.log(`[Auto-Assign] Assigning partner for booking ${booking.id}`);
// TODO: Implementar lógica de asignación automática
// - Buscar conductor con mejor score/disponibilidad
// - Asignar y actualizar estado
// - Notificar al conductor y cliente
}escalateToAdmin function · typescript · L168-L175 (8 LOC)supabase/functions/partner-sla-job-v312/index.ts
async function escalateToAdmin(supabase: any, booking: any) {
console.log(`[Escalate] Escalating booking ${booking.id} to admin`);
// TODO: Implementar lógica de escalación
// - Crear ticket de escalación
// - Notificar a admin
// - Marcar booking como urgente
}emitBookingConfirmedToERP function · typescript · L11-L71 (61 LOC)supabase/functions/_shared/erpIngest.ts
export async function emitBookingConfirmedToERP(args: {
bookingId: string;
erpIngestUrl: string;
ingestSecret: string;
paymentIntentId?: string;
}): Promise<void> {
const { bookingId, erpIngestUrl, ingestSecret, paymentIntentId } = args;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), ERP_INGEST_TIMEOUT_MS);
try {
const payload: Record<string, unknown> = {
event_type: 'booking_confirmed',
version: 'v1',
booking_id: bookingId,
idempotency_key: `booking_confirmed:${bookingId}:v1`,
};
if (paymentIntentId) {
payload.payment_intent_id = paymentIntentId;
}
// Normalizar ERP_INGEST_URL para soportar origin base o ruta completa
const base = erpIngestUrl.replace(/\/+$/, '');
let endpoint = base;
if (/\/functions\/v1\/ingest-booking-confirmed-v1$/.test(base)) {
endpoint = base;
} else if (/\/functions\/v1\/?$/.test(base)) {
endpoint = `${base}/ingest-booemitBookingConfirmedToERP function · typescript · L219-L249 (31 LOC)supabase/functions/stripe-webhooks-v312/index.ts
async function emitBookingConfirmedToERP(
booking: any,
_paymentIntentId: string,
_eventId?: string
): Promise<void> {
try {
const enabled = getEnvBool('ERP_INGEST_ENABLED', true);
const erpIngestUrl = getEnv('ERP_INGEST_URL');
const ingestSecret = getEnv('BOOKING_INGEST_SECRET');
if (!enabled) {
console.log('[webhook] ERP emit disabled via ERP_INGEST_ENABLED=false');
return;
}
if (!erpIngestUrl || !ingestSecret) {
console.log('[webhook] ERP emit skipped: missing env (ERP_INGEST_URL or BOOKING_INGEST_SECRET)');
return;
}
// Call production-grade RGPD-safe implementation (no PII in payload)
void emitToERP({
bookingId: booking.id,
erpIngestUrl,
ingestSecret,
paymentIntentId: _paymentIntentId,
});
} catch (err: any) {
console.error('[webhook] ERP emit setup error:', { message: String(err?.message ?? err) });
}
}handlePaymentIntentSucceeded function · typescript · L254-L294 (41 LOC)supabase/functions/stripe-webhooks-v312/index.ts
async function handlePaymentIntentSucceeded(supabase: any, paymentIntent: any, eventId?: string) {
console.log('[webhook] PaymentIntent succeeded:', paymentIntent.id);
const bookingId = paymentIntent.metadata?.booking_id;
if (!bookingId) {
console.error('[webhook] No booking_id in metadata');
return;
}
// Actualizar booking a confirmed
const update: Record<string, unknown> = {
status: 'confirmed',
payment_status: 'succeeded',
updated_at: new Date().toISOString(),
};
if (paymentIntent.id) {
update.payment_intent_id = paymentIntent.id;
}
const { error } = await supabase.from('bookings').update(update).eq('id', bookingId);
if (error) {
console.error('[webhook] Error actualizando booking:', error);
throw error;
}
console.log('[webhook] Booking confirmado:', bookingId);
// Obtener booking row completa para emit a ERP
const { data: booking, error: fetchError } = await supabase
.from('bookings')
.select('*')
.eq('ihandlePaymentIntentFailed function · typescript · L299-L326 (28 LOC)supabase/functions/stripe-webhooks-v312/index.ts
async function handlePaymentIntentFailed(supabase: any, paymentIntent: any) {
console.log('[webhook] PaymentIntent failed:', paymentIntent.id);
const bookingId = paymentIntent.metadata?.booking_id;
if (!bookingId) {
console.error('[webhook] No booking_id in metadata');
return;
}
// Actualizar booking a payment_failed
const { error } = await supabase
.from('bookings')
.update({
status: 'payment_failed',
payment_status: 'failed',
updated_at: new Date().toISOString(),
})
.eq('id', bookingId);
if (error) {
console.error('[webhook] Error actualizando booking:', error);
throw error;
}
console.log('[webhook] Booking marcado como payment_failed:', bookingId);
// TODO: Enviar notificación al cliente
}handleSetupIntentSucceeded function · typescript · L331-L375 (45 LOC)supabase/functions/stripe-webhooks-v312/index.ts
async function handleSetupIntentSucceeded(supabase: any, setupIntent: any, eventId?: string) {
console.log('[webhook] SetupIntent succeeded:', setupIntent.id);
const bookingId = setupIntent.metadata?.booking_id;
if (!bookingId) {
console.error('[webhook] No booking_id in metadata');
return;
}
// Guardar payment_method_id
const paymentMethodId = setupIntent.payment_method;
const { error } = await supabase
.from('bookings')
.update({
status: 'confirmed',
payment_status: 'setup_succeeded',
stripe_payment_method_id: paymentMethodId,
flexible_sub_status: 'awaiting_hold',
updated_at: new Date().toISOString(),
})
.eq('id', bookingId);
if (error) {
console.error('[webhook] Error actualizando booking:', error);
throw error;
}
console.log('[webhook] Booking flexible confirmado:', bookingId);
// Obtener booking row completa para emit a ERP
const { data: booking, error: fetchError } = await supabase
.frhandleSetupIntentFailed function · typescript · L380-L406 (27 LOC)supabase/functions/stripe-webhooks-v312/index.ts
async function handleSetupIntentFailed(supabase: any, setupIntent: any) {
console.log('[webhook] SetupIntent failed:', setupIntent.id);
const bookingId = setupIntent.metadata?.booking_id;
if (!bookingId) {
console.error('[webhook] No booking_id in metadata');
return;
}
const { error } = await supabase
.from('bookings')
.update({
status: 'payment_failed',
payment_status: 'setup_failed',
updated_at: new Date().toISOString(),
})
.eq('id', bookingId);
if (error) {
console.error('[webhook] Error actualizando booking:', error);
throw error;
}
console.log('[webhook] Booking flexible fallido:', bookingId);
// TODO: Enviar notificación al cliente
}Open data scored by Repobility · https://repobility.com
handleHoldCreated function · typescript · L411-L436 (26 LOC)supabase/functions/stripe-webhooks-v312/index.ts
async function handleHoldCreated(supabase: any, paymentIntent: any) {
console.log('[webhook] Hold created:', paymentIntent.id);
const bookingId = paymentIntent.metadata?.booking_id;
if (!bookingId) {
console.error('[webhook] No booking_id in metadata');
return;
}
const { error } = await supabase
.from('bookings')
.update({
hold_payment_intent_id: paymentIntent.id,
hold_status: 'confirmed',
flexible_sub_status: 'hold_confirmed',
updated_at: new Date().toISOString(),
})
.eq('id', bookingId);
if (error) {
console.error('[webhook] Error actualizando booking:', error);
throw error;
}
console.log('[webhook] Hold confirmado:', bookingId);
}handleHoldCaptured function · typescript · L441-L473 (33 LOC)supabase/functions/stripe-webhooks-v312/index.ts
async function handleHoldCaptured(supabase: any, charge: any) {
console.log('[webhook] Hold captured:', charge.id);
const paymentIntentId = charge.payment_intent;
// Buscar booking por hold_payment_intent_id
const { data: booking, error: findError } = await supabase
.from('bookings')
.select('id')
.eq('hold_payment_intent_id', paymentIntentId)
.single();
if (findError || !booking) {
console.error('[webhook] Booking no encontrado para hold:', paymentIntentId);
return;
}
const { error } = await supabase
.from('bookings')
.update({
hold_status: 'captured',
hold_captured_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
})
.eq('id', booking.id);
if (error) {
console.error('[webhook] Error actualizando booking:', error);
throw error;
}
console.log('[webhook] Hold capturado para booking:', booking.id);
}handlePaymentIntentCanceled function · typescript · L478-L519 (42 LOC)supabase/functions/stripe-webhooks-v312/index.ts
async function handlePaymentIntentCanceled(supabase: any, paymentIntent: any) {
console.log('[webhook] PaymentIntent canceled:', paymentIntent.id);
const bookingId = paymentIntent.metadata?.booking_id;
if (!bookingId) {
// Puede ser un hold, buscar por hold_payment_intent_id
const { data: booking } = await supabase
.from('bookings')
.select('id')
.eq('hold_payment_intent_id', paymentIntent.id)
.single();
if (booking) {
await supabase
.from('bookings')
.update({
hold_status: 'cancelled',
updated_at: new Date().toISOString(),
})
.eq('id', booking.id);
console.log('[webhook] Hold cancelado para booking:', booking.id);
}
return;
}
const { error } = await supabase
.from('bookings')
.update({
status: 'cancelled',
payment_status: 'canceled',
updated_at: new Date().toISOString(),
})
.eq('id', bookingId);
if (error) {
console.error('[