Skip to content

SDK JavaScript / TypeScript

Exemplos com fetch nativo e interfaces TypeScript.


Interfaces TypeScript

// types.ts

export type DeliveryStatus =
  | "pending"
  | "assigned"
  | "in_progress"
  | "completed"
  | "cancelled";

export type PixKeyType = "cpf" | "phone" | "email" | "random";

export interface Delivery {
  id: string;
  title: string;
  status: DeliveryStatus;
  origin_address: string;
  origin_lat: number;
  origin_lng: number;
  destination_address: string;
  destination_lat: number;
  destination_lng: number;
  distance_km: number | null;
  driver_earnings: string | null;
  driver_id: string | null;
  admin_id: string;
  created_at: string;
  updated_at: string;
  assigned_at: string | null;
  started_at: string | null;
  completed_at: string | null;
  cancelled_at: string | null;
}

export interface CreateDeliveryInput {
  title: string;
  origin_address: string;
  origin_lat: number;
  origin_lng: number;
  destination_address: string;
  destination_lat: number;
  destination_lng: number;
}

export interface Driver {
  id: string;
  full_name: string;
  email: string;
  phone: string | null;
  role: "driver";
  asaas_account_id: string | null;
  pix_key: string | null;
  pix_key_type: PixKeyType | null;
  is_active: boolean;
  created_at: string;
  updated_at: string;
}

export interface Wallet {
  id: string;
  driver_id: string;
  balance: string;
  updated_at: string;
}

export interface Transaction {
  id: string;
  wallet_id: string;
  delivery_id: string | null;
  type: "credit" | "debit";
  amount: string;
  description: string;
  created_at: string;
}

export interface Withdrawal {
  id: string;
  wallet_id: string;
  amount: string;
  pix_key: string;
  pix_key_type: PixKeyType;
  status: "pending" | "completed" | "failed";
  asaas_transfer_id: string | null;
  failure_reason: string | null;
  created_at: string;
  completed_at: string | null;
}

export interface Pricing {
  id: string;
  base_fare: string;
  min_km: number;
  price_per_km: string;
  updated_at: string;
}

Classe FastDelivClient (TypeScript)

// client.ts

import type {
  Delivery,
  CreateDeliveryInput,
  Driver,
  Wallet,
  Transaction,
  Withdrawal,
  Pricing,
} from "./types";

const SUPABASE_URL = process.env.SUPABASE_URL!;
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY!;
const BASE_URL =
  process.env.FAST_DELIV_URL ?? "https://fast-deliv-backend.vercel.app";

interface AuthData {
  access_token: string;
  refresh_token: string;
  expires_in: number;
}

export class FastDelivClient {
  private accessToken: string | null = null;
  private refreshToken: string | null = null;
  private expiresAt = 0;

  constructor(
    private email: string,
    private password: string
  ) {}

  // --- Auth ---

  private async authenticate(): Promise<void> {
    const resp = await fetch(
      `${SUPABASE_URL}/auth/v1/token?grant_type=password`,
      {
        method: "POST",
        headers: { apikey: SUPABASE_ANON_KEY, "Content-Type": "application/json" },
        body: JSON.stringify({ email: this.email, password: this.password }),
      }
    );
    if (!resp.ok) throw new Error(`Auth failed: ${resp.status}`);
    const data: AuthData = await resp.json();
    this.accessToken = data.access_token;
    this.refreshToken = data.refresh_token;
    this.expiresAt = Date.now() + (data.expires_in - 60) * 1000;
  }

  private async refresh(): Promise<void> {
    const resp = await fetch(
      `${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`,
      {
        method: "POST",
        headers: { apikey: SUPABASE_ANON_KEY, "Content-Type": "application/json" },
        body: JSON.stringify({ refresh_token: this.refreshToken }),
      }
    );
    if (!resp.ok) throw new Error(`Refresh failed: ${resp.status}`);
    const data: AuthData = await resp.json();
    this.accessToken = data.access_token;
    this.expiresAt = Date.now() + (data.expires_in - 60) * 1000;
  }

  private async getHeaders(): Promise<Record<string, string>> {
    if (!this.accessToken) await this.authenticate();
    else if (Date.now() >= this.expiresAt) await this.refresh();
    return { Authorization: `Bearer ${this.accessToken}` };
  }

  private async request<T>(
    method: string,
    path: string,
    body?: unknown
  ): Promise<T> {
    const headers = await this.getHeaders();
    if (body) headers["Content-Type"] = "application/json";

    const resp = await fetch(`${BASE_URL}${path}`, {
      method,
      headers,
      body: body ? JSON.stringify(body) : undefined,
    });

    if (!resp.ok) {
      const err = await resp.json().catch(() => ({ detail: resp.statusText }));
      throw new Error(`HTTP ${resp.status}: ${err.detail ?? resp.statusText}`);
    }

    return resp.json() as T;
  }

  // --- Entregas ---

  async listDeliveries(status?: string): Promise<Delivery[]> {
    const qs = status ? `?status=${status}` : "";
    return this.request<Delivery[]>("GET", `/api/v1/deliveries${qs}`);
  }

  async createDelivery(data: CreateDeliveryInput): Promise<Delivery> {
    return this.request<Delivery>("POST", "/api/v1/deliveries", data);
  }

  async assignDelivery(id: string): Promise<Delivery> {
    return this.request<Delivery>("PATCH", `/api/v1/deliveries/${id}/assign`);
  }

  async startDelivery(id: string): Promise<Delivery> {
    return this.request<Delivery>("PATCH", `/api/v1/deliveries/${id}/start`);
  }

  async completeDelivery(id: string): Promise<Delivery> {
    return this.request<Delivery>("PATCH", `/api/v1/deliveries/${id}/complete`);
  }

  async cancelDelivery(id: string): Promise<Delivery> {
    return this.request<Delivery>("PATCH", `/api/v1/deliveries/${id}/cancel`);
  }

  // --- Motoristas ---

  async listDrivers(): Promise<Driver[]> {
    return this.request<Driver[]>("GET", "/api/v1/drivers");
  }

  // --- Carteira ---

  async getWallet(): Promise<Wallet> {
    return this.request<Wallet>("GET", "/api/v1/wallets/me");
  }

  async getTransactions(): Promise<Transaction[]> {
    return this.request<Transaction[]>("GET", "/api/v1/wallets/me/transactions");
  }

  async withdraw(
    amount: string,
    pixKey: string,
    pixKeyType: string
  ): Promise<Withdrawal> {
    return this.request<Withdrawal>("POST", "/api/v1/wallets/withdraw", {
      amount,
      pix_key: pixKey,
      pix_key_type: pixKeyType,
    });
  }

  // --- Precos ---

  async getPricing(): Promise<Pricing> {
    return this.request<Pricing>("GET", "/api/v1/pricing");
  }

  async updatePricing(data: Partial<Omit<Pricing, "id" | "updated_at">>): Promise<Pricing> {
    return this.request<Pricing>("PUT", "/api/v1/pricing", data);
  }
}

Uso

const client = new FastDelivClient("admin@empresa.com", "senha123");

const deliveries = await client.listDeliveries("pending");
console.log(`${deliveries.length} entregas pendentes`);

const delivery = await client.createDelivery({
  title: "Entrega Setor Sul",
  origin_address: "Rua das Flores, 100, Goiania",
  origin_lat: -16.6869,
  origin_lng: -49.2648,
  destination_address: "Av. T-63, 800, Goiania",
  destination_lat: -16.7012,
  destination_lng: -49.2789,
});
console.log(`Criada: ${delivery.id}`);

Supabase Realtime — localizacao em tempo real

import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);

interface DriverLocationUpdate {
  driver_id: string;
  lat: number;
  lng: number;
  heading: number | null;
  speed_kmh: number | null;
  updated_at: string;
}

function subscribeToDriverLocations(
  onUpdate: (location: DriverLocationUpdate) => void
) {
  return supabase
    .channel("driver_locations")
    .on(
      "postgres_changes",
      { event: "UPDATE", schema: "public", table: "driver_locations" },
      (payload) => {
        onUpdate(payload.new as DriverLocationUpdate);
      }
    )
    .subscribe();
}

// Usar com um mapa MapLibre, Leaflet, etc.
const channel = subscribeToDriverLocations((loc) => {
  console.log(`Driver ${loc.driver_id}: ${loc.lat}, ${loc.lng}`);
  // updateMarkerOnMap(loc.driver_id, loc.lat, loc.lng);
});

// Ao desmontar o componente
// await supabase.removeChannel(channel);