Skip to main content

Stripe Integration Summary

Quick Reference Guide for Issue #202 and #216


🎯 Key Decision: Credit Burndown Model​

Answer to your question: Stripe integration happens ONLY during balance top-up, NOT during balance consumption.

Why?​

Balance Top-Up (Stripe API):
User clicks "Add $50" β†’ Stripe processes payment β†’ Balance updated in Firestore
Frequency: Low (maybe once per month)
Stripe Cost: ~2.9% + $0.30 per transaction

Balance Consumption (Local Only):
User runs task β†’ Check local balance β†’ Deduct from Firestore β†’ No Stripe API
Frequency: High (100s per day)
Stripe Cost: $0 (all local)

Result: Simple, fast, cheap! πŸš€


πŸ“Š Architecture Diagram​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ USER ACTIONS β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
[Top Up $50] [Run Task $2.50]
β”‚ β”‚
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ TOP-UP FLOW β”‚ β”‚ CONSUMPTION FLOW β”‚
β”‚ (Stripe Integration) β”‚ β”‚ (Local Only) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β”‚ 1. Create PaymentIntent β”‚ 1. Check balance
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β–Ί Stripe API β”œβ”€β”€β”€β”€β”€β”€β”€β”€β–Ί Firestore
β”‚ β”‚
β”‚ 2. Confirm payment (frontend) β”‚ 2. Deduct amount
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β–Ί Stripe.js β”œβ”€β”€β”€β”€β”€β”€β”€β”€β–Ί Firestore
β”‚ β”‚
β”‚ 3. Webhook: payment_succeeded β”‚ 3. Create transaction
◄───────── Stripe β”œβ”€β”€β”€β”€β”€β”€β”€β”€β–Ί Firestore
β”‚ β”‚
β”‚ 4. Update balance β”‚ 4. Return new balance
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β–Ί Firestore ◄───────── Firestore
β”‚ β”‚
β”‚ 5. Create CREDIT transaction β”‚ (Done - no more API calls)
└────────► Firestore β”‚

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ FIRESTORE COLLECTIONS β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ billing_profiles β”‚ billing_transactions β”‚
β”‚ - user@example.com β”‚ - txn_123 (CREDIT: +$50) β”‚
β”‚ - available_balance: $300 β”‚ - txn_124 (DEBIT: -$2.50) β”‚
β”‚ - stripe_customer_id β”‚ - txn_125 (DEBIT: -$1.20) β”‚
β”‚ - complimentary_credits β”‚ - ... β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

βœ… What You Already Have​

Backend:

  • βœ… billing.proto - Complete schema
  • βœ… BillingServiceImpl - Fully implemented (mock mode)
  • βœ… StripeService - Ready for real mode
  • βœ… Firestore collections - Set up and working
  • βœ… Transaction tracking - Working
  • βœ… Balance deduction - Atomic and safe

Frontend:

  • βœ… Billing page UI
  • βœ… Balance display in top bar
  • βœ… Transaction history
  • βœ… Expense tracking by project

Dependencies:

  • βœ… Stripe Java SDK (v29.5.0) in pom.xml

πŸš€ What You Need to Do​

Phase 1: Development Setup (This Week)​

1. Create Stripe Test Account

# Visit: https://dashboard.stripe.com/register
# Complete signup
# Toggle to "Test mode"

2. Get API Keys

# From Stripe Dashboard β†’ Developers β†’ API keys
STRIPE_PUBLISHABLE_KEY=pk_test_51xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_SECRET_KEY=sk_test_51xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

πŸ” Security Note: Test vs. Production Keys

Key TypePrefixCan Process Real Payments?Safe to Commit?Storage
Test Secretsk_test_❌ No (test mode only)⚠️ Not recommendedUse Secret Manager
Test Publishablepk_test_❌ No (test mode only)⚠️ Not recommendedUse Secret Manager
Prod Secretsk_live_βœ… YES! Real money!❌ NEVER!MUST use Secret Manager
Prod Publishablepk_live_Used with secret keyβœ… Safe (client-side)Can commit (domain-restricted)

Why NOT commit even test keys?

  • Someone could spam your Stripe test account
  • Leaked patterns help attackers
  • Accidentally promoting testβ†’prod with keys in code
  • Best practice: No secrets in git, ever

3. Configure Environment

⚠️ IMPORTANT: Secret Storage Strategy

Even for test/dev environments, use Google Secret Manager (not vars.yaml):

# DO NOT add Stripe keys to vars.yaml (it's committed to git!)
# Instead, use Secret Manager for ALL environments:

# Development environment
ENV=dev
GCP_PROJECT_ID="construction-code-expert-${ENV}"

# Create secrets (test keys for dev)
echo -n "sk_test_51xxxxx" | gcloud secrets create stripe-secret-key \
--data-file=- \
--project=${GCP_PROJECT_ID}

# NOTE: Get webhook secret AFTER creating webhook endpoint (see step 3a below)
echo -n "whsec_xxxxx" | gcloud secrets create stripe-webhook-secret \
--data-file=- \
--project=${GCP_PROJECT_ID}

echo -n "pk_test_51xxxxx" | gcloud secrets create stripe-publishable-key \
--data-file=- \
--project=${GCP_PROJECT_ID}

3a. How to Get Webhook Secret:

The webhook secret is NOT in your API keys - you get it when creating a webhook endpoint:

For Local Development (using Stripe CLI):

# Run Stripe CLI webhook forwarding
stripe listen --forward-to localhost:8080/v1/billing/webhook

# This outputs a webhook signing secret like:
# > Ready! Your webhook signing secret is whsec_1234567890abcdef...
# Copy this secret and use it in the command above

For Cloud Run (deployed environments):

  1. Deploy your backend first (so you have a webhook URL)
  2. Go to Stripe Dashboard β†’ Developers β†’ Webhooks
  3. Click "Add endpoint"
  4. Enter webhook URL: https://your-app.run.app/v1/billing/webhook
  5. Click "Select events" and choose:
    • payment_intent.succeeded
    • payment_intent.payment_failed
  6. Click "Add endpoint"
  7. On the endpoint details page, click "Reveal" next to "Signing secret"
  8. Copy the secret (starts with whsec_)
  9. Store it in Secret Manager using the command above

Order of operations:

  1. βœ… Get API keys from Stripe Dashboard (available immediately)
  2. βœ… Store API keys in Secret Manager
  3. βœ… Deploy backend to get webhook URL
  4. βœ… Create webhook endpoint in Stripe Dashboard
  5. βœ… Get webhook secret from webhook endpoint details
  6. βœ… Store webhook secret in Secret Manager
  7. βœ… Redeploy backend with webhook secret mounted

Update vars.yaml to enable Stripe (but NOT store keys):

# env/dev/gcp/cloud-run/grpc/vars.yaml
STRIPE_ENABLED: "true" # Enable Stripe integration
# Note: Actual keys loaded from Secret Manager, not here!

Update deployment to mount secrets:

# When deploying, mount secrets as environment variables
gcloud run deploy construction-code-expert-grpc \
--set-env-vars-file=env/dev/gcp/cloud-run/grpc/vars.yaml \
--set-secrets="STRIPE_SECRET_KEY=stripe-secret-key:latest,STRIPE_WEBHOOK_SECRET=stripe-webhook-secret:latest" \
--project=construction-code-expert-dev

Why use Secret Manager even for test keys?

  • βœ… Practice good habits in all environments
  • βœ… Prevents accidental exposure in public repos
  • βœ… Easy to rotate keys without changing code
  • βœ… Audit trail of who accessed secrets
  • βœ… Same pattern for dev, test, and prod

Frontend publishable key (exception):

The STRIPE_PUBLISHABLE_KEY for frontend can go in setvars.sh:

# env/dev/firebase/m3/setvars.sh
export STRIPE_PUBLISHABLE_KEY=pk_test_51xxxxx

This is acceptable because:

  • βœ… Publishable keys are meant to be public (used in browser JavaScript)
  • βœ… They can't process payments without the secret key
  • βœ… You can restrict them to specific domains in Stripe Dashboard
  • βœ… Frontend environment files are typically committed

However, backend secret keys (sk_test_, sk_live_) MUST use Secret Manager!

4. Update BillingServiceImpl

// Change from:
this.stripeService = new StripeService(); // Mock mode

// To:
String secretKey = System.getenv("STRIPE_SECRET_KEY");
String webhookSecret = System.getenv("STRIPE_WEBHOOK_SECRET");
boolean enabled = "true".equals(System.getenv("STRIPE_ENABLED"));

if (enabled && secretKey != null) {
this.stripeService = new StripeService(secretKey, webhookSecret, false);
} else {
this.stripeService = new StripeService(); // Mock mode
}

5. Install Stripe.js in Frontend

cd web-ng-m3
npm install @stripe/stripe-js

6. Test Payment Flow

1. Load billing page
2. Click "Add Funds"
3. Enter: $25
4. Card: 4242 4242 4242 4242
5. Verify: Balance updates to $325

πŸ“‹ Implementation Checklist​

Backend​

  • Update BillingServiceImpl to read env vars
  • Test Stripe customer creation
  • Test PaymentIntent creation
  • Implement webhook endpoint
  • Test webhook signature verification
  • Test balance update on webhook

Frontend​

  • Add Stripe.js package
  • Add publishable key to environment
  • Create payment dialog component
  • Add Stripe Elements (card input)
  • Implement payment confirmation
  • Handle 3D Secure authentication
  • Add error handling

Testing​

  • Test with test card 4242...
  • Test with 3D Secure card 4000 0027 6000 3184
  • Test with declined card 4000 0000 0000 0002
  • Test webhook locally with Stripe CLI
  • Test concurrent payments
  • Test balance reconciliation

Production​

  • Activate Stripe live account
  • Store secrets in Secret Manager
  • Configure production webhook
  • Deploy to production
  • Test with small real payment
  • Monitor for issues

πŸ” Security Best Practices​

βœ… DO:

  • Store secret keys in Google Secret Manager
  • Validate webhook signatures
  • Use HTTPS everywhere
  • Log payment events (without sensitive data)
  • Implement rate limiting
  • Use atomic Firestore transactions

❌ DON'T:

  • Commit secret keys to git
  • Log card numbers or CVV
  • Skip webhook signature verification
  • Allow negative balances
  • Process payments without authentication
  • Store unnecessary PII

πŸ“ž Support & Troubleshooting​

Common Issues​

"Payment processing failed"

  • Check Stripe Dashboard β†’ Logs
  • Verify API keys are correct
  • Check card details are valid

"Webhook not received"

  • Check webhook URL is publicly accessible
  • Verify webhook secret matches
  • Check Stripe Dashboard β†’ Webhooks β†’ Recent deliveries

"Balance not updating"

  • Check webhook processed successfully
  • Look for errors in Cloud Run logs
  • Verify Firestore transaction completed

Useful Commands​

# Test Stripe connectivity
stripe customers list --limit 1

# Forward webhooks locally
stripe listen --forward-to localhost:8080/v1/billing/webhook

# Trigger test webhook
stripe trigger payment_intent.succeeded

# Check Cloud Run logs
gcloud logging read "resource.type=cloud_run_revision" \
--project=construction-code-expert-dev \
--limit=20 | grep -i stripe

πŸ“š Resources​

Documentation:

Support:


πŸ’‘ Key Takeaways​

  1. Stripe is ONLY for payments - Not for tracking usage
  2. Top-up is the only Stripe API call - Everything else is local
  3. Test mode first - Use test cards, no real money
  4. Webhooks are critical - They update the balance
  5. Security matters - Never commit secrets, always validate signatures

Ready to start? Follow the Full Stripe Integration Plan!