Integrations & External Systems
Every external system PIVOT talks to, with the file paths to start
reading. Newer providers live behind the abstraction layer at
../../functions/integration-engine/;
older ones live as standalone folders under
../../functions/integrations/.
1. Point of Sale (POS)
POS providers feed daily sales and employee data into the pos-sync
and sales modules.
Newer providers — registered in integration-engine:
| Provider | Folder | Auth |
|---|---|---|
| Toast | ../../functions/integration-engine/domains/pos/providers/toast/ | — |
Legacy providers — standalone folders under functions/integrations/:
| Provider | Folder | Auth | Notes |
|---|---|---|---|
| Lightspeed | ../../functions/integrations/lightspeed/ | OAuth | Hourly sales cron, employee sync |
| Clover (OAuth) | ../../functions/integrations/clover/ | OAuth | Webhook handler, hourly sales cron |
| Clover (API key) | ../../functions/integrations/clover-api-key/ | API key | Non-OAuth fallback |
| Cluster | ../../functions/integrations/cluster/ | API key (posApiKey) | Hourly sales |
| Myr | ../../functions/integrations/myr/ | API key | Daily sales, 9am health check |
| Givex | ../../functions/integrations/givex/ | API key (via defineSecret) | Hourly sales, webhook re-poll, employee search |
| Maitre'D | ../../functions/integrations/maitreD/ | — | Validated nightly via cron check-maitred-data.ts |
| Veloce | ../../functions/integrations/veloce/ | — | Sales import |
Provider abstraction layer
The unified contract for new POS / reservation providers lives in
../../functions/integration-engine/:
bootstrap.ts— provider registry + plug-in initcore/—BaseProvider,BaseClient,BaseManager,BaseProcessor,BaseOAuthAuthdomains/—pos,reservation(one sub-folder per domain, with provider implementations inside)endpoints/— generic OAuth callback / webhook endpointsobservability/— health monitoring, metrics (state in Firestore — see decisions.md → data-stores)services/— sync-job planner, pipeline factory
When adding a new provider, extend the engine. The legacy folders above predate it.
2. Payroll & HR
Payroll has two halves: export generation (web, client-side) and HR sync (backend cron).
Export formats — src/routes/PayrollNew/export-formats/
All under ../../src/routes/PayrollNew/export-formats/:
generate-powerpay-export.ts— Ceridian PowerPaygenerateAcombaExport.ts— AcombageneratePayevolutionExport.ts— PayEvolutiongenerateQuickBooksOnlineExport.ts— QuickBooks OnlinegenerateQuickBooksTimeExport.ts— QuickBooks TimegenerateByShiftExport.ts— generic by-shift CSVgenerateByWeekExport.ts— generic by-week CSVgeneratePayrollExport.ts— fallback / legacy
HR sync (backend)
| Provider | Folder | Notes |
|---|---|---|
| Nethris / EmployerD | ../../functions/integrations/nethris-employerD/ | Daily sync at 5am (0 5 * * *); init sync flow on CO_NUMBER creation |
| ADP | n/a | 12 secrets defined in get-secret.ts (Connector + SSO + Subscription + certs) but no implementation in this repo |
3. Reservations
| Provider | Folder | Notes |
|---|---|---|
| OpenTable | ../../functions/integration-engine/domains/reservation/providers/opentable/ | OAuth with token refresh; credentials read from process.env.OPENTABLE_CLIENT_ID/SECRET (legacy env-var pattern, not defineSecret) |
| Libro | ../../functions/integrations/libro/ | OAuth (client credentials). Reservation poll every 5 minutes; expected-revenue calc hourly. Standalone — not registered with the integration-engine |
4. Billing — Stripe
- Backend:
../../functions/systems/stripe/handlers/stripe-webhook-handler.ts— webhook entry point; sub-handlers forcustomer.updated, subscription created/updated/deleted, invoice eventsutils/fetch-stripe-subscriptions.ts— subscription fetchutils/company-matching.ts— matches Stripe customers to companiesapi/— Payment Methods (list / attach), Setup Intents (create), Invoices (get), Billing info updates
- Web UI:
../../src/routes/Billing/— uses@stripe/react-stripe-js(Elements,PaymentElement,useStripe,useElements) - Stripe init:
../../src/routes/Billing/utils/stripe.ts—loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY) - API client:
../../src/routes/Billing/utils/api.ts— uses FirebasehttpsCallable(not the shared ky client)
No Stripe Connect, Customer Portal, or Checkout. Subscription + invoice + payment-method flows only.
5. Communications
| Channel | How |
|---|---|
Nodemailer + MJML templates. Service at ../../functions/services/mailer/; templates under ../../functions/services/mailer/templates/. SMTP creds via SMTP_* / NODEMAILER_* secrets | |
| Push (FCM) | Backend: firebase-admin getMessaging() — see ../../functions/services/notification/providers/push/push.transport.ts. Mobile: @react-native-firebase/messaging. iOS deliveries route through Firebase's APNS integration; the APNS_* secrets exist but are not used directly in code |
| Slack | ../../functions/utils/send-slack-notification.ts — error & integration alerts, uses SLACK_BOT_TOKEN |
NotificationChannel.SMS and .EMAIL are declared in the notification-core constants but currently have no provider implementations — only push is wired.
6. Monitoring & Observability
| System | Where |
|---|---|
| Rollbar — backend | ../../functions/utils/rollbar.ts |
| Rollbar — web | Configured in ../../src/index.tsx (RollbarProvider) and ../../src/config.ts; used via ../../src/utils/useHandleError.ts |
| Rollbar — mobile | PIVOT-Mobile/src/utils/rollbar.ts |
| Logger (backend) | ../../functions/shared/infrastructure/logger.ts — wraps Firebase Functions' logger and enriches log lines with module. A loggerStore (AsyncLocalStorage) is set up for richer context but is not yet read at log time |
7. Other External Services
| Service | Used for | Where |
|---|---|---|
WeatherAPI (api.weatherapi.com) | Hourly forecast per company location, written into RTDB | ../../functions/cron/fetch-weather-forecast.ts, ../../functions/cron/write-weather-forecast-for-companies.ts |
| Temporal Cloud | Workflow scheduler used by the AI memory module | ../../functions/modules/ai-memory/ (via @temporalio/client) |
| Pivot AI Agent (external Cloud Run) | LLM-backed company/employee suggestions. The Anthropic SDK lives in functions/package.json but the actual Claude calls run in a separate Cloud Run service, not this repo. ai-memory is the RTDB + Temporal persistence layer that feeds it | functions/modules/ai-memory/ |
| Mapbox | Map rendering on mobile WeatherRadar | @rnmapbox/maps in PIVOT-Mobile (src/routes/WeatherRadar/) |
8. Cron / Scheduled Jobs
Legacy crons at ../../functions/cron/
Mostly v1 functions.pubsub.schedule(); one exception flagged below.
| File | Purpose |
|---|---|
auto-close-payroll.ts | Auto-locks past payroll periods |
check-maitred-data.ts | Validates Maitre'D imports (1 11 * * *) |
disable-inactive-integrations.ts | Marks stale integrations as disabled |
fetch-weather-forecast.ts | Pulls WeatherAPI forecasts |
late-for-shift-notification.ts | Pushes notification if employee is late |
mass-notification.ts | Bulk notification sender |
process-scheduled-posts.ts | v2 onTaskDispatched (not Pub/Sub) — publishes scheduled social posts |
remove-old-drafts.ts | Deletes stale schedule drafts |
remove-unsent-chat-attachments.ts | Cleans up failed uploads |
save-yearly-salary-attendance.js | Backfills annual data |
update-availabilities.js | Recalculates employee availability |
update-salary.js | Refreshes salary data |
write-weather-forecast-for-companies.ts | Writes forecasts to per-company paths |
Module-scoped (Cloud Scheduler v2)
Under each module's endpoints/scheduled/. Example:
../../functions/modules/requests/endpoints/scheduled/expire-requests.ts— closes expired requests at0 2 * * *(America/Toronto)
POS / integration crons (inside each provider folder)
| Provider | Schedule | File |
|---|---|---|
| Lightspeed sales | hourly (0 * * * *) | functions/integrations/lightspeed/cron-lightspeed-daily-sales.ts |
| Clover sales | hourly (0 * * * *) | functions/integrations/clover/cron-clover-daily-sales.ts |
| Cluster sales | hourly (0 * * * *) | functions/integrations/cluster/cron-cluster-daily-sales.ts |
| Givex sales | hourly (0 * * * *) | functions/integrations/givex/cron-get-hourly-sales.ts |
| Myr health check | 9am daily (0 9 * * *) | functions/integrations/myr/check-myr-integrations.ts |
| Nethris/EmployerD sync | 5am daily (0 5 * * *) | functions/integrations/nethris-employerD/index.ts |
| Libro reservation poll | every 5 minutes | functions/integrations/libro/cron/fetch-libro-reservations.ts |
| Libro expected revenue | hourly (0 */1 * * *) | functions/integrations/libro/cron/calculate-libro-expected.ts |
Filenames containing "daily" are historical — most actually run hourly.
9. Secrets
Two patterns coexist:
Legacy: typed accessor — ../../functions/get-secret.ts
ALL_SECRETSis aconst-asserted string array;SecretNameis the derived union type (effectively type-safe, though not anenum).- 56 entries at last count, fetched from Google Secret Manager at cold start via
onInit()and cached. Falls back toprocess.env[name]in dev. - Categories present: URL, RTDB_URL, Stripe (2), Lightspeed (4), Libro (4), Cluster (2), Clover (2), Myr (1), ADP (10), SMTP/Nodemailer (9), AppEncryption, APNS (3), Rollbar, Weather (2), Slack, env copy (3), DB (7).
New: defineSecret() from firebase-functions/params
Used by newer integrations. Examples:
../../functions/integrations/givex/secrets.ts—GIVEX_API_URL,GIVEX_REPOLL_KEY- OpenTable secrets in
../../functions/integration-engine/endpoints/http/apis/opentable-partner.router.ts
For new code prefer defineSecret(). Don't expand the ALL_SECRETS list.
Rule
Never read process.env directly outside dev migrations. A small number of legitimate exceptions exist (e.g. FUNCTIONS_EMULATOR checks).
10. Frontend HTTP to Backend
Three patterns are in active use on web. Mobile mostly mirrors the ky pattern.
A. ky HTTP client — src/services/http.client.ts
- Auto-injects Firebase ID token as
Authorization: Bearer <token>. - Base URL chosen by
REACT_APP_USE_EMULATOR:- emulator:
http://localhost:5001/{projectId}/us-central1 - prod:
https://us-central1-{projectId}.cloudfunctions.net
- emulator:
- Mobile equivalent:
PIVOT-Mobile/src/services/httpClient.ts(same pattern, same URL shapes). - Also wrapped in a per-feature client:
../../src/modules/integrations/services/integration-client/integration.client.ts.
B. Firebase Callable Functions — httpsCallable
Used heavily, primarily in ../../src/utils/api.ts (e.g. findCompanyById, onCallEmployeeBreakToggler, getVeloceSearchedEmployees, acceptEula, printPdf, getLibroCompanies, …) and in Billing's api.ts for all Stripe operations. Different routing path than ky — these hit *.cloudfunctions.net/functionName directly with Firebase's callable envelope.
C. Axios (legacy)
Still used for a handful of endpoints (setEmployeeClockInV2, setEmployeeClockOutV2) and in some action creators (../../src/actions/notifications.ts, ../../src/actions/integrations.ts).
Direct RTDB / Firestore subscriptions
Bypass HTTP entirely. Authorization comes from the security rules in ../../database.rules.json and ../../firestore.rules.