A multi-provider payment gateway built with Supabase Edge Functions, supporting Daimo Pay and Aqua payment providers with automatic withdrawal integration.
This service is deployed as Supabase Edge Functions with the following structure:
supabase/
βββ functions/
β βββ payment-api/ # Payment creation & retrieval
β βββ webhook-handler/ # Daimo & Aqua webhook processing
β βββ api-health/ # System health & provider status
β βββ shared/ # Shared utilities & types
βββ migrations/ # Database schema & tables
βββ config.toml # Supabase configuration
Function | Endpoint | Description |
---|---|---|
payment-api |
/functions/v1/payment-api |
Create payments, get payment status |
webhook-handler |
/functions/v1/webhook-handler |
Process Daimo/Aqua webhooks, trigger withdrawals |
api-health |
/functions/v1/api-health |
System health, provider status |
- PostgreSQL (Supabase built-in)
- Row Level Security (RLS) enabled
- Real-time subscriptions for payment updates
- Optimized indexes for performance
- CHECK constraints for data integrity
- Supabase CLI installed
- Deno runtime installed
- Supabase Project created
- API Keys for Daimo and Aqua providers
# macOS
brew install supabase/tap/supabase
# Windows (via scoop)
scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
scoop install supabase
# npm (cross-platform)
npm install -g supabase
# macOS/Linux
curl -fsSL https://deno.land/install.sh | sh
# Windows
iwr https://deno.land/install.ps1 -useb | iex
# Clone this repository
git clone <your-repo-url>
cd payment-api-proxy
# Initialize Supabase
supabase init
# Link to your Supabase project
supabase link --project-ref <your-project-ref>
# Login to Supabase (if not already)
supabase login
Create environment secrets in your Supabase dashboard or use CLI:
# Core Configuration
supabase secrets set NODE_ENV=production
supabase secrets set CORS_ORIGINS="https://yourapp.com,https://yourdomain.com"
# Daimo Provider
supabase secrets set DAIMO_API_KEY="your-daimo-api-key"
supabase secrets set DAIMO_BASE_URL="https://pay.daimo.com"
supabase secrets set DAIMO_WEBHOOK_TOKEN="your-daimo-webhook-token"
# Aqua Provider
supabase secrets set AQUA_BASE_URL="https://api.aqua.network"
supabase secrets set AQUA_API_TOKEN="your-aqua-api-token"
supabase secrets set AQUA_WEBHOOK_TOKEN="your-aqua-webhook-token"
# Withdrawal Integration
supabase secrets set WITHDRAWAL_API_BASE_URL="https://proj-ref.supabase.co"
supabase secrets set WITHDRAWAL_API_JWT_TOKEN="your-withdrawal-jwt-token"
supabase secrets set WITHDRAWAL_INTEGRATION_ENABLED="true"
# Optional: Logging & Monitoring
supabase secrets set LOG_LEVEL="info"
supabase secrets set ENABLE_REQUEST_LOGGING="true"
# Run database migrations
supabase db push
# Verify tables created
supabase db status
# Deploy all functions
supabase functions deploy payment-api
supabase functions deploy webhook-handler
supabase functions deploy api-health
# Or deploy all at once
supabase functions deploy
# Apply Row Level Security policies
supabase db reset --linked
# Test health check
curl https://<your-project-ref>.supabase.co/functions/v1/api-health
# Test payment creation
curl -X POST https://<your-project-ref>.supabase.co/functions/v1/payment-api \
-H "Content-Type: application/json" \
-d '{
"display": {
"intent": "Test payment",
"currency": "USD"
},
"destination": {
"destinationAddress": "0x1234567890abcdef1234567890abcdef12345678",
"chainId": "10",
"amountUnits": "1000000",
"tokenAddress": "0xA0b86a33E6441c8C06DD2a8e8B4A6a0b0b1b1b1b"
}
}'
# Start Supabase locally (includes database + edge functions)
supabase start
# Your local endpoints will be:
# API: http://localhost:54321
# Database: postgresql://postgres:postgres@localhost:54322/postgres
# Functions: http://localhost:54321/functions/v1/
# Serve functions locally with hot reload
supabase functions serve
# Test local function
curl http://localhost:54321/functions/v1/api-health
# Reset local database
supabase db reset
# View database in browser
supabase dashboard db
POST /functions/v1/payment-api
Content-Type: application/json
{
"display": {
"intent": "Coffee purchase",
"currency": "USD"
},
"destination": {
"destinationAddress": "0x742d35Cc6634C0532925a3b8D6Cd1C3b5123456",
"chainId": "10",
"amountUnits": "5500000",
"tokenAddress": "0xA0b86a33E6441c8C06DD2a8e8B4A6a0b0b1b1b1b"
},
"metadata": {
"orderId": "12345"
}
}
GET /functions/v1/payment-api/{paymentId}
GET /functions/v1/payment-api/external-id/{externalId}
POST /functions/v1/webhook-handler?provider=daimo&token=your-webhook-token
Content-Type: application/json
X-Daimo-Signature: sha256=...
{
"type": "payment_completed",
"paymentId": "payment_123",
"chainId": 10,
"txHash": "0x...",
"payment": { ... }
}
POST /functions/v1/webhook-handler?provider=aqua&token=your-webhook-token
Content-Type: application/json
{
"invoice_id": "aqua_invoice_123",
"status": "paid",
"amount": 100.50,
"address": "GXXXXXXX...",
"token_id": "usdc",
"transaction_hash": "stellar_tx_hash"
}
GET /functions/v1/api-health
# Response
{
"status": "healthy",
"timestamp": "2024-01-01T00:00:00.000Z",
"database": "connected",
"providers": {
"daimo": {
"status": "healthy",
"responseTime": 150,
"lastCheck": "2024-01-01T00:00:00.000Z"
},
"aqua": {
"status": "healthy",
"responseTime": 200,
"lastCheck": "2024-01-01T00:00:00.000Z"
}
},
"withdrawal_integration": {
"status": "healthy",
"enabled": true
}
}
CREATE TABLE payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
amount DECIMAL(20,8) NOT NULL CHECK (amount > 0),
currency TEXT NOT NULL,
status payment_status NOT NULL DEFAULT 'payment_unpaid',
external_id TEXT,
withdraw_id TEXT,
provider_name TEXT NOT NULL,
chain_id TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
status_updated_at TIMESTAMPTZ DEFAULT now(),
provider_response JSONB,
metadata JSONB,
original_request JSONB NOT NULL
);
CREATE TYPE payment_status AS ENUM (
'payment_unpaid',
'payment_started',
'payment_completed',
'payment_bounced',
'payment_refunded'
);
-- Main status query optimization
CREATE INDEX idx_payments_status_created_at ON payments(status, created_at DESC);
-- Unique external_id constraint
CREATE UNIQUE INDEX idx_payments_external_id_unique ON payments(external_id) WHERE external_id IS NOT NULL;
-- Stale payment detection
CREATE INDEX idx_payments_status_status_updated ON payments(status, status_updated_at);
-- Provider and chain specific queries
CREATE INDEX idx_payments_provider_status ON payments(provider_name, status);
CREATE INDEX idx_payments_chain_status ON payments(chain_id, status);
-- Partial indexes for specific status values
CREATE INDEX idx_payments_status_unpaid ON payments(created_at DESC) WHERE status = 'payment_unpaid';
CREATE INDEX idx_payments_status_started ON payments(created_at DESC) WHERE status = 'payment_started';
CREATE INDEX idx_payments_status_completed ON payments(created_at DESC) WHERE status = 'payment_completed';
- payments: Public read access for valid payment IDs
- Real-time: Enabled for payment status updates
- API Keys: Function-level authentication
USDC Stellar Payment Completed (Webhook)
β
Eligibility Check (USDC + Stellar + Aqua provider)
β
POST to External Withdrawal API
β
Save withdraw_id to payment record
β
Log Success/Failure
- USDC Stellar β USDC Base β
- Future: USDC Stellar β USDC Solana
# View function logs
supabase functions logs payment-api
supabase functions logs webhook-handler
supabase functions logs api-health
# Real-time logs
supabase functions logs --follow
- Function invocation count
- Error rates
- Response times
- Database query performance
- Provider health status
- Payment processing failures
- Webhook delivery failures
- Provider API downtime
- Withdrawal integration errors
- API Key Authentication: For payment creation
- Webhook Verification: Signature validation (Daimo) + Token auth (Aqua)
- Row Level Security: Database-level access control
- CORS: Configurable allowed origins
- Built-in: Supabase Edge Functions rate limiting
- Custom: Additional rate limiting for webhook endpoints
- DDoS Protection: Supabase infrastructure-level protection
- CHECK constraints: Amount must be positive
- Unique constraints: External IDs are unique
- Input validation: Application-level validation
- Type safety: TypeScript throughout
- Environment secrets configured
- Database migrations applied
- Database improvements applied (
./apply-db-improvements.sh
) - RLS policies enabled
- CORS origins configured
- API keys obtained from providers
- Health check passing
- Test payment flow end-to-end
- Webhook endpoints responding
- Withdrawal integration working
- Monitoring dashboards configured
- Log aggregation setup
- Monitor function performance
- Review error logs regularly
- Update provider API configurations
- Scale function resources as needed
- Regular database maintenance
- Monitor index usage
# Check function syntax
deno check supabase/functions/payment-api/index.ts
# View deployment logs
supabase functions logs payment-api --level error
# Check database status
supabase db status
# Reset database
supabase db reset --linked
# Test provider connectivity
curl -H "Authorization: Bearer $DAIMO_API_KEY" https://pay.daimo.com/health
# Check environment secrets
supabase secrets list
# View webhook logs
supabase functions logs webhook-handler
# Test webhook manually
curl -X POST http://localhost:54321/functions/v1/webhook-handler?provider=daimo \
-H "Content-Type: application/json" \
-d '{"type":"payment_completed","paymentId":"test"}'
- Supabase Edge Functions Documentation
- Deno Runtime Documentation
- Daimo Pay API Documentation
- Aqua Payment Documentation
- API Interface Documentation
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature
) - Test locally with
supabase start
- Commit changes (
git commit -m 'Add amazing feature'
) - Push to branch (
git push origin feature/amazing-feature
) - Open Pull Request
- Use TypeScript for all new code
- Follow existing code patterns
- Add tests for new functionality
- Update documentation for API changes
- Use conventional commit messages
This project is licensed under the MIT License - see the LICENSE file for details.
# Complete deployment in one go
git clone <repo> && cd payment-api-proxy
supabase init && supabase link --project-ref <your-ref>
supabase secrets set DAIMO_API_KEY="your-key" AQUA_API_TOKEN="your-token"
supabase db push && supabase functions deploy
curl https://<your-ref>.supabase.co/functions/v1/api-health
You're now ready to process payments through multiple blockchain networks with automatic withdrawal integration! π