POWERBACK uses Stripe for payment processing, implementing an escrow-based system where donations are held until celebration conditions are met. The payment system handles payment method collection, payment intent creation, and secure payment processing with FEC compliance validation. Releasing funds to campaigns and filing with the FEC are done manually by the PAC operator, not by the app.
- Payment Method Setup: User provides payment method via Stripe Elements
- Payment Intent Creation: Payment intent created but not charged
- Escrow Holding: Funds held in Stripe until celebration resolved
- Payment Confirmation: Payment confirmed when celebration condition met
- Fund Release: Funds are released to the politician campaign by the PAC operator (manual process; not automated)
- Frontend: Stripe Elements for payment method collection
- Backend: Stripe API for payment processing
- Webhooks: Real-time payment event processing
- Compliance: FEC validation before payment creation
Frontend (usePaymentProcessing.ts):
- User enters payment details via Stripe Elements
- Payment method tokenized securely
- Setup intent created for payment method collection
Backend (setupIntent.js):
- Creates Stripe setup intent
- Returns client secret for frontend
- Allows secure payment method collection
Backend (setPaymentMethod.js):
- Sets default payment method for Stripe customer
- Stores payment method for future use
- Associates payment method with user account
Backend (createPayment.js):
- Creates Stripe payment intent with total amount (donation + tip + fee)
- Uses payment method from request or customer's default
- Sets
up_future_usageto allow reuse - Includes idempotency key to prevent duplicates
- Payment intent created but not charged until resolution
Frontend (usePaymentProcessing.ts):
- Confirms payment using client secret from payment intent
- Handles payment confirmation success/failure
- Updates UI based on payment status
- PAC limit: when validation returns
pacLimitInfo, the hook callsshowPACLimitConfirm(data)from DonationLimits context; contract (when called, payload shape, caller responsibility) is documented in the hook file.
Backend (Webhook):
- Receives
charge.succeededevent from Stripe - Updates celebration status
- Processes payment completion
- Purpose: Hold funds in escrow until celebration resolved (release to campaign is manual)
- Status: Created but not charged until condition met
- Amount: Donation + tip + Stripe processing fee
- Currency: USD
- Purpose: Securely collect payment methods
- Usage: One-time setup for payment method collection
- Security: Tokenized payment method, never stored directly
- Purpose: Associate payment methods with users
- Storage: Stripe customer ID stored in user document
- Default Payment Method: Stored for future use
- Purpose: Real-time payment event processing
- Events:
charge.succeeded,payment_intent.created, etc. - Security: Signature verification required
- Processing: Updates celebration status when payment confirmed
- Backend Validation: FEC compliance checked before payment intent creation
- Tier Limits: Per-donation, annual, and per-election limits enforced
- PAC Limits: Tip limits validated ($5,000 annual)
- Donor Validation: Donor information validated per compliance tier
- Validation Failures: Payment rejected if compliance violated
- Error Messages: Clear error messages for users
- PAC Limit Modal: Special handling for PAC limit violations
Total = Donation + Tip + Stripe Processing Fee
- Rate: 2.9% + $0.30 per transaction
- Calculation: Applied to donation + tip amount
- Transparency: Fee shown to user before payment
- Dollars to Cents: All amounts converted to cents for Stripe
- Precision: Uses
Math.floor()to ensure integer cents
- Purpose: Prevent duplicate charges
- Generation: Unique key per payment attempt
- Storage: Stored in celebration document
- Stripe: Passed to Stripe API for idempotent processing
- Stripe Errors: Handled and returned to frontend
- Network Errors: Retry logic for transient failures
- Validation Errors: Clear error messages for users
- Client Logging: Frontend payment hooks (
usePaymentProcessing, payment forms, and related funnel components) use the shared client logging helper (logError/logWarnfrom@Utils) instead of rawconsole.error/console.warn. In production, logs contain only high-level messages (no Stripe responses, card details, or request bodies); full error objects are available only in development.
- Signature Verification: Invalid signatures rejected
- Processing Errors: Logged but don't break webhook processing
- Retry Logic: Stripe automatically retries failed webhooks
- Tokenization: Payment methods tokenized by Stripe
- No Storage: Card details never stored in application
- PCI Compliance: Handled by Stripe
- Signature Verification: All webhooks verified using Stripe signing secret
- Raw Body Parsing: Preserves signature for validation
- Error Handling: Invalid signatures rejected
- Skip Processing: Users at PAC limit skip webhook processing
- Performance: Reduces database load for users who can't tip
- Early Return: Returns success to prevent Stripe retries
- Backend Authority: Backend validates PAC limits
- Frontend Display: Frontend shows limit information
- Annual Reset: Limits reset on January 1st
- Stripe Test Keys: Use test keys in development
- Test Cards: Stripe provides test card numbers
- Webhook Testing: Stripe CLI for local webhook testing
// Create payment intent
const { createPayment } = require('./controller/payments/createPayment');
await createPayment(req, res);
// Setup payment method
const { setupIntent } = require('./controller/payments/setupIntent');
await setupIntent(req, res);- FEC Compliance Guide - Compliance validation
- Donation Limits - Donation limit system
- Webhook System - Webhook processing
- API Documentation - Payment API endpoints
- Celebration System - Celebration lifecycle
- Client Utils - Client logging and Stripe helpers (e.g.
logError/logWarn,loadStripeKeyfrom@Utils)