Technical Reference

This document provides technical details and implementation patterns for specific Billy features and integrations.

Payment Status Integration

Overview

Billy implements bidirectional payment status synchronization between Pennylane and various integration sources. This allows payment updates to flow in both directions, ensuring data consistency across systems.

Architecture

Dest to Source (Pennylane → Integration)

When Pennylane updates payment status, Billy syncs this information to the source integration: Requirements:
  • PennylaneIntegration must implement UpdatePaymentStatusIntegration
  • Source integration must implement PushPaymentStatusIntegration
Implementation Steps:
  1. Ensure getAllowedSourcesForInvoiceSynchronization works for your integration
  2. Update getDestToSourceSyncableSourceIntegrations to include your integration
  3. Implement the required interfaces
Testing:
# Test synchronization with Pennylane
pnpm tsx scripts/pennylane/test-sync-dest-invoices.ts

# Debug mode available
pnpm tsx scripts/pennylane/test-sync-dest-invoices.ts --debug

Source to Dest (Integration → Pennylane)

When source systems update payment status, Billy syncs to Pennylane: Requirements:
  • Update getAllowedSourcesForInvoiceSynchronization
  • Specifically update getSourceToDestSyncableSourceIntegrations
  • This prevents querying non-existing fields
Post-filtering Considerations:
  • Check filterDestSyncableInvoices function
  • Review getSyncablePennylaneGateways for gateway restrictions
  • Consider implementing refresh_unpaid_client_invoices or refresh_unpaid_supplier_invoices

Implementation Patterns

UpdatePaymentStatusIntegration

Render this field in your integration component:
<UpdatePaymentStatusField
  integration={integration}
  source={InvoiceIntegrationSource.Pennylane}
/>

PushPaymentStatusIntegration

Render this field in your integration component:
<PushPaymentStatusField
  integration={integration}
  source={InvoiceIntegrationSource.Hyperline}
/>

Development Workflow

# 1. Push invoice to Pennylane
pnpm tsx scripts/pennylane/test-push-client-invoice.ts

# 2. Mark as paid on Pennylane (via UI or API)

# 3. Test synchronization
pnpm tsx scripts/pennylane/test-sync-dest-invoice.ts

Cron Jobs

Payment synchronization is managed by Vercel cron jobs:
  • Pennylane-only sync: /api/invoices/cron/synchronize_payments_with_pennylane_dest_invoices
  • Integration sync (200 at a time): /api/integrations/internal/[source]/cron/synchronize_payments_with_pennylane_dest_invoices
  • Individual integration sync: /api/integrations/internal/[source]/[id]/synchronize_payments_with_pennylane_dest_invoices
View cron jobs: Vercel Dashboard

Database Development

PostgreSQL Constraints

List constraints on a specific table:
SELECT con.conname, pg_get_constraintdef(con.oid)
FROM pg_catalog.pg_constraint con
INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid
INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace
WHERE nsp.nspname = 'public' AND rel.relname = 'invoices';

Database Migrations

# Create new migration
supabase migration new "add_payment_status_field"

# Test locally
supabase migration up --local

# Apply to production
supabase migration up --linked

Integration Development

Webhook Development

Local Testing with Tunnels

For local webhook development, use Cloudflare tunnels:
# Start tunnel for main app
cloudflared tunnel --url https://localhost:3000

# Start tunnel for Supabase storage
cloudflared tunnel --url http://localhost:54321

Permanent Tunnel Setup

# Install cloudflared
brew untap cloudflare/cloudflare # if needed
brew install cloudflared

# Login (requires HeyBilly Cloudflare account access)
cloudflared tunnel login

# Create personal tunnel
cloudflared tunnel create your-name

# Route DNS
cloudflared tunnel route dns your-name your-name.tunnels.heybilly.io
Configuration file .cloudflared/your-name.yml:
url: https://localhost:3000
tunnel: <Tunnel-UUID>
credentials-file: /Users/<Your-user>/.cloudflared/<Tunnel-UUID>.json
Run tunnel:
cloudflared tunnel --config=.cloudflared/your-name.yml run your-name
Environment variables:
# Local tunnels
SUPABASE_STORAGE_TUNNEL_URL=https://your-name-storage.tunnels.heybilly.io
APP_TUNNEL_URL=https://your-name.tunnels.heybilly.io

OAuth Development

Spendesk OAuth

Spendesk doesn’t allow ports in redirect URIs:
# Set port to 443 for HTTPS
PORT=443

HubSpot Development

  1. Go to HubSpot Developer Account
  2. Create application with redirect URI: https://localhost:3000/oauth/hubspot/redirect_uri
  3. Add credentials to .env.local:
# HubSpot [Dev]
HUBSPOT_REDIRECT_URI=https://localhost:3000/oauth/hubspot/redirect_uri
HUBSPOT_PENNYLANE_API_KEY=XXX
HUBSPOT_PENNYLANE_CLIENT_SECRET=XXX
HUBSPOT_PENNYLANE_CLIENT_ID=XXX

Pipedrive Development

  1. Go to Pipedrive Developer Hub
  2. Create OAuth application with redirect URI: https://localhost:3000/oauth/pipedrive/redirect_uri
  3. Add credentials to .env.local:
# Pipedrive x Pennylane [Local]
PIPEDRIVE_REDIRECT_URI=https://localhost:3000/oauth/pipedrive/redirect_uri
PIPEDRIVE_PENNYLANE_CLIENT_ID=xxx
PIPEDRIVE_PENNYLANE_CLIENT_SECRET=xxx

Pipedrive Extensions

Custom Modals

Add these modals to your Pipedrive application:
NameIframe URL
Créer une facture Pennylane<Tunnel URL>/iframes/pipedrive/pennylane/deals
Créer un devis Pennylane<Tunnel URL>/iframes/pipedrive/pennylane/quotes
Associer une facture<Tunnel URL>/iframes/pipedrive/pennylane/associate_invoices
Associer un devis<Tunnel URL>/iframes/pipedrive/pennylane/associate_quotes
Créer un client<Tunnel URL>/iframes/pipedrive/pennylane/organization/create_client
Configuration<Tunnel URL>/iframes/pipedrive/pennylane/configuration_fields
Note: Clear any entrypoint selection in the dropdown for all modals.

Custom Panels

Add these panels to your Pipedrive application:
NameAPI EndpointPanel LocationsMain Action
Pennylane (Your Name [Dev])<Tunnel URL>/iframes/pipedrive/pennylane/invoice_paneldealsDefault
Pennylane (Your Name [Dev])<Tunnel URL>/iframes/pipedrive/pennylane/organization_panelorganizationsDefault
JWT Secret: Use the same value as PIPEDRIVE_PENNYLANE_CLIENT_SECRET if required.

Development Tools

Cloudflare Workers

# Start workers in development mode
pnpm run workers-dev

Hookdeck

# Login to Hookdeck
hookdeck login

# Listen to local port
hookdeck listen 3000

GraphQL Development

# Install watchman for file watching
brew install watchman

# Start GraphQL relay in watch mode
pnpm relay:watch

Gotenberg (PDF Generation)

Gotenberg is accessible at: http://localhost:3045/health

Environment Management

Pennylane Sandbox

  1. Go to Pennylane Sandbox
  2. Create your own sandbox
  3. Get API key from developer settings
  4. Add to .env.local:
FIXTURES_BILLY_PENNYLANE_INTEGRATION_API_TOKEN=YOUR_SECRET_VALUE_GOES_HERE
  1. Reset SQL schema and run pnpm populate

Country Codes

Refresh country list and AlphaCode:
pnpm get-all-country-code