Telephony Billing

Gestione Tariffari Telefonici e Prefissi Internazionali E.164 con Firebase Functions v2 e CSV

Nella gestione di una piattaforma di chiamate VoIP come **Africall**, ogni destinazione telefonica mondiale possiede un costo al minuto differente che varia in base al paese ed all'operatore di destinazione (es. Orange Mali, Malitel, MTN Costa d'Avorio). Determinare il costo esatto di una chiamata prima che l'utente avvii la conversazione richiede un algoritmo di **Longest Prefix Matching** eseguito su un file CSV di Rate Deck in ambiente **Firebase Cloud Functions v2**.

1. Struttura del File CSV Rate Deck

I provider di fonia (come Telnyx o Twilio) forniscono listini prezzi contenenti il prefisso telefonico in formato E.164, la descrizione della rotta ed il costo base:

prefix,destination_name,rate_per_minute,origination_prefix
223,"Mali Mobile Orange",0.18,""
2236,"Mali Mobile Malitel",0.16,""
22177,"Senegal Mobile Orange",0.22,""
393,"Italy Mobile",0.04,""

2. Algoritmo di Longest Prefix Matching in TypeScript

Carichiamo il file CSV in memoria una sola volta all'avvio dell'istanza serverless di Cloud Functions e ricerchiamo la corrispondenza di prefisso più specifica:

import * as fs from "fs";
import * as path from "path";
import { parse } from "csv-parse/sync";

interface RateRow {
  prefix: string;
  destinationName: string;
  ratePerMinute: number;
}

class RateDeckService {
  private static instance: RateDeckService;
  private ratesMap: Map = new Map();

  private constructor() {
    this.loadCSV();
  }

  public static getInstance(): RateDeckService {
    if (!RateDeckService.instance) {
      RateDeckService.instance = new RateDeckService();
    }
    return RateDeckService.instance;
  }

  private loadCSV() {
    const csvPath = path.join(__dirname, "../data/global_rates.csv");
    const fileContent = fs.readFileSync(csvPath, "utf-8");
    const records = parse(fileContent, { columns: true, skip_empty_lines: true });

    for (const row of records) {
      const prefix = row.prefix.replace("+", "").trim();
      this.ratesMap.set(prefix, {
        prefix: prefix,
        destinationName: row.destination_name,
        ratePerMinute: parseFloat(row.rate_per_minute),
      });
    }
    console.log(`Caricate ${this.ratesMap.size} rotte tariffarie in memoria.`);
  }

  public getCallPricing(phoneNumberE164: string): { destination: string; costPerMinute: number } | null {
    // Rimuove il simbolo + iniziale
    const cleanNumber = phoneNumberE164.replace("+", "").trim();

    // Cerca il prefisso più lungo corrispondente (es. da 15 cifre a scalare fino a 1)
    for (let len = cleanNumber.length; len > 0; len--) {
      const subPrefix = cleanNumber.substring(0, len);
      if (this.ratesMap.has(subPrefix)) {
        const rateInfo = this.ratesMap.get(subPrefix)!;
        
        // Applicazione del margine di profitto (es. +20%)
        const finalPrice = rateInfo.ratePerMinute * 1.20;
        
        return {
          destination: rateInfo.destinationName,
          costPerMinute: Math.round(finalPrice * 1000) / 1000,
        };
      }
    }

    return null; // Rotta non supportata
  }
}

3. Esposizione Endpoint Callable per App iOS

Esponiamo la funzione getCallPricing per consentire alla vista SwiftUI di mostrare la tariffa prima dell'avvio della chiamata:

import { onCall, HttpsError } from "firebase-functions/v2/https";

export const getCallPricing = onCall(async (request) => {
  const destinationNumber = request.data.destinationNumber;
  if (!destinationNumber) {
    throw new HttpsError("invalid-argument", "Numero di destinazione mancante.");
  }

  const rateDeck = RateDeckService.getInstance();
  const pricing = rateDeck.getCallPricing(destinationNumber);

  if (!pricing) {
    throw new HttpsError("not-found", "Destinazione telefonica non coperta.");
  }

  return pricing;
});

Conclusione

L'indicizzazione in-memory con Longest Prefix Match garantisce risposte al millisecondo ed azzera i costi di calcolo su database per ogni stima tariffaria. Per realizzare motori di fatturazione fonia e Rate Deck personalizzati, scrivi a diallooyunus@gmail.com.

Yunus Diallo (DialloDev)

Fondatore di Dywtal Digital a Parma (Italia), sviluppatore dell'app Africall ed esperto in architetture di billing telefonico.