Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Diego31-10/TableOrderApp/llms.txt

Use this file to discover all available pages before exploring further.

TableOrder integrates with multiple external services to provide maps, payments, PDF generation, and notifications. All external API calls are isolated in the lib/services/ layer.

Service architecture

Services are organized by domain:
lib/services/
├── mapboxService.ts     # Directions API + polyline decoding
├── pdfService.ts        # HTML-to-PDF ticket generation
└── telegramService.ts   # Bot API document upload
Each service is a standalone module that can be tested, mocked, or replaced independently.

Mapbox service

Handles route calculation, polyline decoding, and shipping cost estimation. File: src/lib/services/mapboxService.ts

Route calculation

Fetches a driving route between two coordinates using the Mapbox Directions API:
import polyline from '@mapbox/polyline';
import { Config } from '@/src/lib/core/config';
import { Coordinates, DeliveryInfo } from '@/src/lib/core/types';

const MAPBOX_BASE = 'https://api.mapbox.com/directions/v5/mapbox/driving';

export async function getDeliveryRoute(
  origin: Coordinates,
  destination: Coordinates
): Promise<DeliveryInfo> {
  const { token } = Config.mapbox;

  if (!token) {
    throw new Error('EXPO_PUBLIC_MAPBOX_TOKEN is not configured.');
  }

  // Mapbox expects coordinates in lon,lat order
  const coords = `${origin.longitude},${origin.latitude};${destination.longitude},${destination.latitude}`;
  const url = `${MAPBOX_BASE}/${coords}?geometries=polyline&overview=full&access_token=${token}`;

  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`Mapbox API error: ${response.status}`);
  }

  const data = await response.json();

  if (data.code !== 'Ok' || data.routes.length === 0) {
    throw new Error('No route found between the two points.');
  }

  const route = data.routes[0];

  return {
    distanceKm: parseFloat((route.distance / 1000).toFixed(2)),
    etaMinutes: Math.ceil(route.duration / 60),
    polyline: route.geometry,
    decodedRoute: polyline.decode(route.geometry).map(([lat, lng]) => ({
      latitude: lat,
      longitude: lng,
    })),
  };
}
GET /directions/v5/mapbox/driving/-74.0060,40.7128;-73.9851,40.7589
  ?geometries=polyline
  &overview=full
  &access_token=pk.ey...
Parameters:
  • geometries=polyline — Return compressed polyline instead of GeoJSON
  • overview=full — Include all route points, not simplified

Polyline decoding

Mapbox returns routes as compressed polylines (precision 5). The service decodes them into coordinate arrays:
import polyline from '@mapbox/polyline';

// Encoded: "_p~iF~ps|U_ulLnnqC_mqNvxq`@"
// Decoded: [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]]

const decodedPairs: [number, number][] = polyline.decode(route.geometry);

// Convert to react-native-maps format
const decodedRoute: Coordinates[] = decodedPairs.map(([lat, lng]) => ({
  latitude: lat,
  longitude: lng,
}));
Polyline encoding reduces payload size by ~90%. A 500-point route that would be 20KB as JSON is only 2KB as an encoded polyline.

Shipping cost calculation

Calculates delivery cost based on distance and a per-kilometer rate:
export function calculateShippingCost(distanceKm: number): number {
  return parseFloat((distanceKm * Config.restaurant.costPerKm).toFixed(2));
}

// Example: 7.42 km × $1.50/km = $11.13
Usage in checkout flow:
const deliveryInfo = await getDeliveryRoute(restaurant, userLocation);
const shippingCost = calculateShippingCost(deliveryInfo.distanceKm);

useCartStore.getState().setShippingCost(shippingCost);
useLocationStore.getState().setDeliveryRoute(deliveryInfo);

PDF service

Generates branded PDF receipts using expo-print with HTML templates. File: src/lib/services/pdfService.ts

Template structure

The service builds an HTML template with embedded CSS and dynamic content:
import * as Print from 'expo-print';
import { CartItem } from '@/src/lib/core/types';

export interface TicketData {
  items: CartItem[];
  subtotal: number;
  discount: number;
  total: number;
  shippingCost: number;
  serviceType: 'TABLE' | 'DELIVERY';
  tableName?: string;
  timestamp?: string;
}

export async function generateTicketPDF(data: TicketData): Promise<string> {
  const html = buildTicketHTML(data);
  const { uri } = await Print.printToFileAsync({ html, base64: false });
  return uri;
}

HTML template

The template includes:

Header

TableOrder logo with branded colors (#E25822)

Metadata

Order ID, service type, date/time

Items table

Product name, quantity, and prices

Totals section

Subtotal, discount, shipping, and grand total
Key template features:
function buildTicketHTML(data: TicketData): string {
  const orderId = `TO-${Date.now().toString(36).toUpperCase()}`;
  const dateStr = new Date().toLocaleString('es-ES');
  
  const serviceLabel = data.serviceType === 'TABLE'
    ? `Mesa: ${data.tableName ?? 'Sin nombre'}`
    : `Delivery`;
  
  const discountAmount = data.subtotal - (data.total - data.shippingCost);
  const showDiscount = data.discount > 0 && discountAmount > 0;
  
  const itemsRows = data.items
    .map((item) => `
      <tr>
        <td class="item-name">${item.quantity}x ${item.product.name}</td>
        <td class="item-price">$${(item.product.price * item.quantity).toFixed(2)}</td>
      </tr>`)
    .join('');
  
  return `<!DOCTYPE html>...`;
}
<div class="header">
  <div class="logo">TableOrder</div>
  <div class="logo-sub">Comprobante de pago</div>
</div>
Styled with the brand color (#E25822) and bold typography.

Generated PDF example

The service returns a file URI that can be shared or uploaded:
const pdfUri = await generateTicketPDF({
  items: cartItems,
  subtotal: 28.48,
  discount: 0.15,
  total: 24.21,
  shippingCost: 0,
  serviceType: 'TABLE',
  tableName: 'Salon 05',
  timestamp: new Date().toISOString(),
});

// Returns: "file:///var/mobile/.../ticket_1234567890.pdf"

Telegram service

Sends PDF receipts to a Telegram chat using the Bot API. File: src/lib/services/telegramService.ts

Document upload

Uses multipart/form-data to upload PDF files:
import { Config } from '@/src/lib/core/config';

const TELEGRAM_API = 'https://api.telegram.org';

export async function sendTicketToTelegram(pdfUri: string): Promise<void> {
  const { botToken, chatId } = Config.telegram;

  if (!botToken || !chatId) {
    console.warn('[Telegram] Bot credentials not configured.');
    return;
  }

  try {
    const formData = new FormData();
    formData.append('chat_id', chatId);

    // React Native requires this specific object shape for file uploads
    formData.append('document', {
      uri: pdfUri,
      name: `ticket_orden_${Date.now()}.pdf`,
      type: 'application/pdf',
    } as unknown as Blob);

    formData.append(
      'caption',
      `Ticket de pago — ${Config.restaurant.name}\n${new Date().toLocaleString('es-ES')}`
    );

    const response = await fetch(
      `${TELEGRAM_API}/bot${botToken}/sendDocument`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'multipart/form-data' },
        body: formData,
      }
    );

    if (!response.ok) {
      const body = await response.text();
      console.error(`[Telegram] sendDocument failed: ${response.status}`, body);
    }
  } catch (err) {
    // Silent failure — never block the payment success flow
    console.error('[Telegram] sendTicketToTelegram error:', err);
  }
}
Telegram delivery is non-blocking. If the upload fails, it’s logged to console but doesn’t affect the user’s payment success flow.

FormData structure

{
  chat_id: "-1001234567890",
  document: {
    uri: "file:///path/to/ticket.pdf",
    name: "ticket_orden_1234567890.pdf",
    type: "application/pdf"
  },
  caption: "Ticket de pago — Restaurant Name\n3/3/2026, 14:35:21"
}
API endpoint:
POST https://api.telegram.org/bot{TOKEN}/sendDocument
Content-Type: multipart/form-data

Integration flow

After successful payment, the app orchestrates PDF generation and Telegram upload:
// 1. Generate PDF
const pdfUri = await generateTicketPDF(ticketData);

// 2. Send to Telegram (non-blocking)
await sendTicketToTelegram(pdfUri);

// 3. Show local notification
await NotificationService.showPaymentSuccess(total);

// 4. Navigate to success screen
router.push('/success');

Error handling patterns

All services implement consistent error handling:
if (!token) {
  throw new Error('EXPO_PUBLIC_MAPBOX_TOKEN is not configured.');
}

if (!response.ok) {
  throw new Error(`Mapbox API error: ${response.status}`);
}

if (data.code !== 'Ok' || data.routes.length === 0) {
  throw new Error('No route found between the two points.');
}
Strategy: Throw errors immediately. Caller handles with try/catch.

Service testing

Services are pure functions that can be tested in isolation:
import { calculateShippingCost } from '@/src/lib/services/mapboxService';
import { generateTicketPDF } from '@/src/lib/services/pdfService';

describe('mapboxService', () => {
  it('calculates shipping cost correctly', () => {
    // Config.restaurant.costPerKm = 1.50
    expect(calculateShippingCost(7.42)).toBe(11.13);
    expect(calculateShippingCost(0)).toBe(0);
  });
});

describe('pdfService', () => {
  it('generates PDF with correct order ID format', async () => {
    const uri = await generateTicketPDF(mockTicketData);
    expect(uri).toMatch(/^file:\/\//);
  });
});
Mock external APIs in tests using jest.mock() or MSW (Mock Service Worker).

Configuration

All services read credentials from lib/core/config.ts, which loads from environment variables:
export const Config = {
  mapbox: {
    token: process.env.EXPO_PUBLIC_MAPBOX_TOKEN ?? '',
  },
  telegram: {
    botToken: process.env.EXPO_PUBLIC_TELEGRAM_BOT_TOKEN ?? '',
    chatId: process.env.EXPO_PUBLIC_TELEGRAM_CHAT_ID ?? '',
  },
  restaurant: {
    name: 'TableOrder Restaurant',
    costPerKm: 1.50,
    coordinates: { latitude: 40.7128, longitude: -74.0060 },
  },
};
See the Environment setup guide for configuration details.

Next steps

Architecture overview

Understand the overall system design

State management

Learn how Zustand stores work

Build docs developers (and LLMs) love