ACT Phase I - Ordering System
A complete quote-to-project management system with automated Stripe quotes, e-signatures, and payment processing. Built with Python Flask and Firebase, integrated with ACT's existing project management workflow.
> Note: This system uses Stripe for all quote generation and payment processing.
Features
Technology Stack
Workflow
Stripe Quote Created โ Sent for Signature โ Signed โ Accepted โ
Payment โ In Progress โ CompletedDetailed Status Flow:
1. requested - Customer submits quote request 2. quote_priced - Admin sets pricing 3. estimate_sent / sent - Stripe quote created and sent for signature 4. accepted / approved - Customer signs via Dropbox Sign (auto-updates status) 5. in_progress - Payment received, work begins 6. completed - Project finished 7. cancelled / declined - Cancelled or declined at any stageSignature & Payment Flow:
Invoice Automation:
Prerequisites
Quick Start
1. Firebase Setup
Already configured for project `act-phase-i`:
2. Stripe Setup
1. Sign up at Stripe 2. Get your API keys from the dashboard (Developers โ API keys) 3. Enable Payment Links and Payment Methods in your Stripe dashboard 4. Update `.env` file with your secret and publishable keys
3. Dropbox Sign Setup
1. Sign up at Dropbox Sign 2. Go to API Settings โ Create an API key 3. (Optional) Create a template for proposals with custom fields 4. Configure webhook URL: `https://your-domain.com/api/dropbox-sign/webhook` 5. Update `.env` file
4. Environment Configuration
The `.env` file configuration:
# Firebase
FIREBASE_PROJECT_ID=act-phase-i
FIREBASE_CREDENTIALS_PATH=./service-account-key.json/act-phase-i-6644998ca560.jsonStripe Configuration (Quotes & Payments)
STRIPE_SECRET_KEY=sk_test_your_stripe_secret_key
STRIPE_PUBLISHABLE_KEY=pk_test_your_stripe_publishable_keyDropbox Sign Configuration
DROPBOX_SIGN_API_KEY=your_dropbox_sign_api_key
DROPBOX_SIGN_CLIENT_ID=your_client_id # Optional
DROPBOX_SIGN_TEST_MODE=true # Set to false for production
DROPBOX_SIGN_TEMPLATE_ID=your_template_id # Optional - uses template if providedApplication
APP_URL=https://your-domain.com
EMAIL_SENDING_ENABLED=true
ORDER_CC_EMAIL=karenf@act.earth6
4. Install and Run
# Install dependencies
pip install -r requirements.txtRun the application
python3 app.pyAccess the application:
API Endpoints
Authentication
Quotes (Primary Admin Workflow)
Quote Management (Customer Portal)
Orders
Estimates
Invoices
Customer Portal Endpoints
Payments (Stripe)
Clients
Webhooks
---
Quote Pricing API (iOS App)
This is the only endpoint needed to price a project and automatically generate + send a Stripe quote with e-signature.
Overview
When you set a price, the system automatically: 1. โ Saves the price to the order in Firestore 2. โ Creates a professional Stripe quote with all project details 3. โ Sends the quote to the client via Dropbox Sign for e-signature 4. โ Updates project status to "proposal pending"
Endpoint
POST /api/quotes/by-project//price Authentication
Request Body
Minimal (most common):
{
"price": 1500
}With Optional Fields:
{
"price": 1500,
"notes": "Includes soil sampling and analysis",
"valid_days": 14
}Request Parameters
| Field | Type | Required | Description | |-------|------|----------|-------------| | `price` | number | Yes | Total price for the project in USD | | `notes` | string | No | Internal pricing notes (not shown to client) | | `valid_days` | number | No | Quote validity in days. Only used if order doesn't have `turnaround_days` set. Default: 14 days |
Success Response (200)
{
"success": true,
"order_id": "abc123",
"ACTProjectNumber": "11160",
"status": "proposal pending",
"stripe_quote_id": "qt_1234567890",
"stripe_quote_pdf_url": "https://files.stripe.com/v1/...",
"signature_request_id": "fa123abc456",
"message": "Quote created and sent successfully"
}Response Fields
| Field | Type | Description | |-------|------|-------------| | `success` | boolean | Always `true` for successful requests | | `order_id` | string | Firestore order document ID | | `ACTProjectNumber` | string | ACT project number (e.g., "11160") | | `status` | string | New project status: `"proposal pending"` | | `stripe_quote_id` | string | Stripe quote ID for tracking | | `stripe_quote_pdf_url` | string | URL to download the quote PDF | | `signature_request_id` | string | Dropbox Sign signature request ID (null if signature sending failed) | | `message` | string | Success message |
Error Responses
404 - Project Not Found:
{
"error": "Order not found for ACTProjectNumber"
}400 - Missing Client:
{
"error": "Client not found for this order"
}400 - Stripe Not Configured:
{
"error": "Stripe not configured"
}400 - Missing Price:
{
"error": "Items are required for pricing"
}401 - Unauthorized:
{
"error": "Unauthorized"
}403 - Not Admin:
{
"error": "Admin access required"
}Example: iOS Swift Request
func sendQuote(projectNumber: String, price: Double) async throws {
let url = URL(string: "https://ordering.act.earth/api/quotes/by-project/\(projectNumber)/price")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = ["price": price]
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw QuoteError.invalidResponse
}
if httpResponse.statusCode == 200 {
let result = try JSONDecoder().decode(QuoteResponse.self, from: data)
print("โ
Quote sent! Status: \(result.status)")
} else {
let error = try JSONDecoder().decode(ErrorResponse.self, from: data)
throw QuoteError.apiError(error.error)
}
}struct QuoteResponse: Codable {
let success: Bool
let orderId: String
let ACTProjectNumber: String
let status: String
let stripeQuoteId: String
let stripeQuotePdfUrl: String?
let signatureRequestId: String?
let message: String
enum CodingKeys: String, CodingKey {
case success
case orderId = "order_id"
case ACTProjectNumber
case status
case stripeQuoteId = "stripe_quote_id"
case stripeQuotePdfUrl = "stripe_quote_pdf_url"
case signatureRequestId = "signature_request_id"
case message
}
}
struct ErrorResponse: Codable {
let error: String
}
Example: cURL Request
curl -X POST https://ordering.act.earth/api/quotes/by-project/11160/price \
-H "Authorization: Bearer YOUR_FIREBASE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"price": 1500}'What Happens After Sending
1. Client receives email from Dropbox Sign with the quote PDF to review and sign 2. When client signs: - Order status automatically updates to `"accepted"` - If payment terms = "upon authorization", invoice is auto-created and sent - Project status changes to `"active"` (ready for work) 3. Client can view and pay the invoice from the customer portal
Important Notes
Firebase Collections
`clients`
{
client_id: "firebase_uid",
name: "John Doe",
email: "john@example.com",
phone: "+1234567890",
company: "ACME Corp",
address: "123 Main St",
payment_terms: "upon authorization", // Default payment terms for quotes
created_at: Timestamp,
stripe_customer_id: "cus_stripe_id"
}`orders`
{
order_id: "uuid",
ACTProjectNumber: "10891",
client_id: "firebase_uid",
client_name: "John Doe",
address: "Project address",
tax_parcels: ["Block 123 Lot 45", "Block 123 Lot 46"],
customer_project_number: "CLIENT-2024-001", // Optional customer reference
status: "requested",
payment_status: "not_required",
estimate_id: "uuid",
invoice_id: "uuid",
items: [{description, quantity, unit_price}],
// Stripe Quote fields
stripe_quote_id: "qt_stripe_id",
stripe_quote_pdf_url: "https://...",
stripe_quote_status: "open",
stripe_quote_created_at: Timestamp,
// Stripe Invoice fields (auto-populated when quote accepted)
stripe_invoice_id: "in_stripe_id",
stripe_invoice_url: "https://...", // Hosted payment page URL
stripe_invoice_status: "open",
stripe_invoice_created_at: Timestamp,
stripe_invoice_sent_at: Timestamp,
created_at: Timestamp
}`proposals`
{
proposal_id: "PROP-123",
order_id: "uuid",
client_id: "firebase_uid",
amount: 2500.00,
status: "sent", // sent, accepted, approved, declined
accepted: false,
accepted_at: Timestamp,
approved: false, // For backward compatibility
approved_at: Timestamp,
signature_request_id: "dropbox_sign_request_id",
signature_status: "pending", // pending, viewed, signed, declined
signature_sent_at: Timestamp,
signature_completed_at: Timestamp,
stripe_quote_id: "qt_stripe_id",
created_at: Timestamp
}`projectManagement` (Existing Collection - Integrated)
{
ACTProjectNumber: "10891", // Document ID
order_id: "uuid", // Link to orders
clientName: "John Doe",
address: "Project address",
status: "requested",
createdBy: "user@email.com",
createdAt: Timestamp,
statusUpdatedAt: Timestamp,
proposal: {},
database: {},
inspection: {},
historicals: {},
adjacentLots: {},
report: {},
files: []
}Deployment to Cloud Run
Using Deployment Script
chmod +x deploy.sh
./deploy.shManual Deployment
# Build and push image
gcloud builds submit --tag gcr.io/act-phase-i/ordering-systemDeploy to Cloud Run
gcloud run deploy ordering-system \
--image gcr.io/act-phase-i/ordering-system \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars FIREBASE_PROJECT_ID=act-phase-i,STRIPE_SECRET_KEY=your-stripe-keyProject Structure
Ordering_system/
โโโ app.py # Main Flask application
โโโ config.py # Configuration management
โโโ requirements.txt # Python dependencies
โโโ Dockerfile # Container configuration
โโโ .env # Environment variables
โโโ services/ # Business logic services
โ โโโ firebase_service.py # Firebase Auth & Firestore
โ โโโ order_service.py # Order & project management
โ โโโ client_service.py # Client management
โ โโโ estimate_service.py # PDF estimate generation
โ โโโ invoice_service.py # PDF invoice generation
โ โโโ stripe_service.py # Stripe quote & payment processing
โ โโโ dropbox_sign_service.py # Dropbox Sign e-signatures
โ โโโ payment_service.py
โ โโโ braintree_service.py
โโโ routes/ # API route handlers
โ โโโ auth_routes.py # Authentication endpoints
โ โโโ quote_routes.py # Quote request endpoints
โ โโโ order_routes.py # Order management endpoints
โ โโโ estimate_routes.py # Estimate endpoints
โ โโโ invoice_routes.py # Invoice endpoints
โ โโโ client_routes.py # Client management endpoints
โ โโโ dropbox_sign_routes.py # Dropbox Sign webhook endpoints
โ โโโ payment_routes.py
โ โโโ braintree_routes.py
โโโ middleware/ # Authentication middleware
โ โโโ auth_middleware.py # JWT token verification
โโโ static/ # Frontend files
โ โโโ index.html # Customer portal
โโโ generated_pdfs/ # Generated PDF filesKey Integrations
Stripe Quote & Payment System
Dropbox Sign Integration
Payment Terms Management
Stripe Payment Processing
Invoice Automation
Customer Portal
Security
Testing
Test Firebase Connection
python3 test_firebase.pyTest Stripe Quote & Dropbox Sign Integration
python3 test_stripe_quote_signature.pyReview Project Schema
python3 review_schema.pyTroubleshooting
Permission Errors
If you get `403 Missing or insufficient permissions`: 1. Go to https://console.cloud.google.com/iam-admin/iam?project=act-phase-i 2. Find service account: `firebase-adminsdk-fbsvc@act-phase-i.iam.gserviceaccount.com` 3. Add roles: Cloud Datastore User and Firebase AdminImport Errors
Make sure all dependencies are installed:pip install -r requirements.txtDropbox Sign Webhook Issues
Stripe Quote Not Creating
Documentation
Detailed integration guides available:
Support
For issues and questions, contact the development team or open an issue in the repository.
License
MIT License