Firebase Function Setup

import stripe
import json
from firebase_functions import https_fn
from firebase_admin import firestore
import firebase_admin

# Initialize Firebase Admin SDK
if not firebase_admin._apps:
    firebase_admin.initialize_app()

stripe.api_key = "key"
webhook_secret = "whsec_your_webhook_secret"

@https_fn.on_request()
def stripe_webhook(req: https_fn.Request) -> https_fn.Response:
    payload = req.get_data()
    sig_header = req.headers.get('stripe-signature')
    
    try:
        # Verify webhook signature
        event = stripe.Webhook.construct_event(
            payload, sig_header, webhook_secret
        )
    except ValueError:
        return https_fn.Response("Invalid payload", status=400)
    except stripe.error.SignatureVerificationError:
        return https_fn.Response("Invalid signature", status=400)
    
    # Handle dispute events
    if event['type'] == 'charge.dispute.created':
        handle_dispute_created(event['data']['object'])
    elif event['type'] == 'charge.dispute.updated':
        handle_dispute_updated(event['data']['object'])
    elif event['type'] == 'charge.dispute.funds_withdrawn':
        handle_dispute_funds_withdrawn(event['data']['object'])
    elif event['type'] == 'charge.dispute.funds_reinstated':
        handle_dispute_funds_reinstated(event['data']['object'])
    
    return https_fn.Response("Success", status=200)

Dispute Handling Functions

def handle_dispute_created(dispute):
    """Handle new dispute creation with automation"""
    
    dispute_id = dispute['id']
    charge_id = dispute['charge']
    
    # 1. Store dispute in Firestore
    store_dispute_data(dispute)
    
    # 2. Gather and submit evidence automatically
    submit_automated_evidence(dispute)
    
    # 3. Send notifications
    send_dispute_notifications(dispute)
    
    # 4. Update order status
    update_order_status(charge_id, 'disputed')

def store_dispute_data(dispute):
    """Store dispute information in Firestore"""
    
    db = firestore.client()
    
    dispute_data = {
        'id': dispute['id'],
        'charge_id': dispute['charge'],
        'amount': dispute['amount'],
        'currency': dispute['currency'],
        'reason': dispute['reason'],
        'status': dispute['status'],
        'created': firestore.SERVER_TIMESTAMP,
        'evidence_due_by': dispute['evidence_details']['due_by'],
        'has_evidence': dispute['evidence_details']['has_evidence'],
        'submission_count': dispute['evidence_details']['submission_count'],
        'customer_id': dispute.get('customer'),
        'needs_response': True
    }
    
    db.collection('disputes').document(dispute['id']).set(dispute_data)

def submit_automated_evidence(dispute):
    """Automatically gather and submit evidence"""
    
    try:
        # Retrieve charge details for evidence gathering
        charge = stripe.Charge.retrieve(dispute['charge'])
        
        # Gather evidence from your system
        evidence = gather_dispute_evidence(charge, dispute)
        
        if evidence:
            # Submit evidence to Stripe
            stripe.Dispute.modify(
                dispute['id'],
                evidence=evidence
            )
            
            # Update Firestore
            db = firestore.client()
            db.collection('disputes').document(dispute['id']).update({
                'evidence_submitted': True,
                'evidence_submission_date': firestore.SERVER_TIMESTAMP,
                'auto_submitted': True
            })
            
    except Exception as e:
        print(f"Error submitting evidence for dispute {dispute['id']}: {e}")
        # Log error for manual review

def gather_dispute_evidence(charge, dispute):
    """Collect relevant evidence for the dispute"""
    
    evidence = {}
    
    try:
        # Get order/transaction data from Firestore
        db = firestore.client()
        
        # Find order by charge ID
        orders = db.collection('orders').where('stripe_charge_id', '==', charge['id']).get()
        
        if orders:
            order = orders[0].to_dict()
            
            # Service documentation
            if 'service_documentation_url' in order:
                evidence['service_documentation'] = order['service_documentation_url']
            
            # Receipt
            if 'receipt_url' in order:
                evidence['receipt'] = order['receipt_url']
            
            # Shipping information
            if charge.get('shipping') and 'tracking_number' in order:
                evidence['shipping_documentation'] = order['tracking_number']
                evidence['shipping_date'] = order.get('shipped_date')
                evidence['shipping_tracking_number'] = order['tracking_number']
            
            # Customer communication
            evidence['customer_communication'] = get_customer_communication(
                charge.get('customer'), 
                charge['id']
            )
            
            # Refund policy
            evidence['refund_policy'] = get_refund_policy_url()
            
            # Terms of service
            evidence['terms_of_service'] = get_terms_of_service_url()
            
    except Exception as e:
        print(f"Error gathering evidence: {e}")
    
    return evidence

def get_customer_communication(customer_id, charge_id):
    """Retrieve customer communication logs"""
    
    if not customer_id:
        return None
    
    try:
        db = firestore.client()
        
        # Get customer support tickets/emails related to this charge
        communications = db.collection('customer_communications')\
            .where('customer_id', '==', customer_id)\
            .where('charge_id', '==', charge_id)\
            .get()
        
        if communications:
            # Combine all communication into a single text
            comm_text = ""
            for comm in communications:
                data = comm.to_dict()
                comm_text += f"Date: {data.get('date')}\n"
                comm_text += f"Subject: {data.get('subject')}\n"
                comm_text += f"Message: {data.get('message')}\n\n"
            
            return comm_text
            
    except Exception as e:
        print(f"Error retrieving customer communication: {e}")
    
    return None

def send_dispute_notifications(dispute):
    """Send notifications about new dispute"""
    
    # Send to admin team via Cloud Messaging or email
    notification_data = {
        'type': 'dispute_created',
        'dispute_id': dispute['id'],
        'charge_id': dispute['charge'],
        'amount': dispute['amount'],
        'reason': dispute['reason'],
        'due_date': dispute['evidence_details']['due_by']
    }
    
    # Store notification for admin app
    db = firestore.client()
    db.collection('admin_notifications').add({
        'type': 'dispute',
        'data': notification_data,
        'created': firestore.SERVER_TIMESTAMP,
        'read': False
    })

def update_order_status(charge_id, status):
    """Update order status when dispute is created"""
    
    try:
        db = firestore.client()
        
        # Find and update order
        orders = db.collection('orders').where('stripe_charge_id', '==', charge_id).get()
        
        for order in orders:
            order.reference.update({
                'status': status,
                'dispute_created': firestore.SERVER_TIMESTAMP
            })
            
    except Exception as e:
        print(f"Error updating order status: {e}")

For Connect Accounts

def handle_dispute_created(dispute):
    """Handle disputes for Connect platforms"""
    
    # Check if this is a Connect charge
    charge = stripe.Charge.retrieve(dispute['charge'])
    
    if charge.get('on_behalf_of'):
        connected_account = charge['on_behalf_of']
        
        # Handle based on account type
        account = stripe.Account.retrieve(connected_account)
        
        if account['type'] == 'express':
            # For Express accounts, platform handles disputes
            submit_automated_evidence_for_connect(dispute, connected_account)
        elif account['type'] == 'custom':
            # For Custom accounts, forward to their webhook
            forward_dispute_to_connect_account(dispute, connected_account)
    else:
        # Standard dispute handling
        submit_automated_evidence(dispute)

def submit_automated_evidence_for_connect(dispute, account_id):
    """Submit evidence for connected Express accounts"""
    
    try:
        evidence = gather_connect_evidence(dispute['charge'], account_id)
        
        if evidence:
            stripe.Dispute.modify(
                dispute['id'],
                evidence=evidence,
                stripe_account=account_id  # Submit on behalf of connected account
            )
            
    except Exception as e:
        print(f"Error submitting evidence for connected account {account_id}: {e}")

2. Firebase Functions with Dart

Using Firebase Functions for Dart:

import 'package:firebase_functions_interop/firebase_functions_interop.dart';
import 'package:stripe_dart/stripe_dart.dart';

void main() {
  functions['stripeWebhook'] = functions.https.onRequest(handleStripeWebhook);
}

Future<void> handleStripeWebhook(ExpressHttpRequest request) async {
  final rawBody = await request.body;
  final signature = request.headers['stripe-signature'];
  
  try {
    final event = Stripe.constructEvent(
      payload: rawBody,
      sigHeader: signature,
      endpointSecret: functions.config().stripe.webhook_secret,
    );
    
    switch (event.type) {
      case 'charge.dispute.created':
        await processDisputeCreated(event.data.object);
        break;
      case 'charge.dispute.updated':
        await processDisputeUpdated(event.data.object);
        break;
    }
    
    request.response.write('{"received": true}');
  } catch (e) {
    request.response.statusCode = 400;
    request.response.write('Webhook Error: $e');
  }
}

Future<void> processDisputeCreated(Map<String, dynamic> dispute) async {
  // Automated dispute handling
  final disputeId = dispute['id'];
  final chargeId = dispute['charge'];
  
  // Send push notifications to admin app
  await sendPushNotification({
    'type': 'dispute_created',
    'disputeId': disputeId,
    'chargeId': chargeId,
    'amount': dispute['amount'],
    'reason': dispute['reason'],
  });
  
  // Update Firestore
  await FirebaseFirestore.instance
      .collection('disputes')
      .doc(disputeId)
      .set({
    'id': disputeId,
    'chargeId': chargeId,
    'status': 'needs_response',
    'createdAt': FieldValue.serverTimestamp(),
    'dueBy': DateTime.fromMillisecondsSinceEpoch(
        dispute['evidence_details']['due_by'] * 1000),
  });
}

3. Flutter Admin App Integration

Create a Flutter admin app that responds to dispute notifications:

class DisputeManager {
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;
  final FirebaseMessaging _messaging = FirebaseMessaging.instance;
  
  void initializeDisputeListening() {
    // Listen for dispute updates in Firestore
    _firestore
        .collection('disputes')
        .where('status', isEqualTo: 'needs_response')
        .snapshots()
        .listen(_handleDisputeUpdate);
    
    // Handle push notifications
    FirebaseMessaging.onMessage.listen(_handleDisputeNotification);
  }
  
  Future<void> _handleDisputeUpdate(QuerySnapshot snapshot) async {
    for (final doc in snapshot.docChanges) {
      if (doc.type == DocumentChangeType.added) {
        final dispute = doc.doc.data() as Map<String, dynamic>;
        await _autoProcessDispute(dispute);
      }
    }
  }
  
  Future<void> _autoProcessDispute(Map<String, dynamic> dispute) async {
    try {
      // Gather evidence from local database
      final evidence = await _gatherEvidence(dispute['chargeId']);
      
      // Submit evidence via Stripe API
      final response = await http.put(
        Uri.parse('https://api.stripe.com/v1/disputes/${dispute['id']}'),
        headers: {
          'Authorization': 'Bearer $stripeSecretKey',
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: evidence,
      );
      
      if (response.statusCode == 200) {
        // Update status in Firestore
        await _firestore
            .collection('disputes')
            .doc(dispute['id'])
            .update({'status': 'evidence_submitted'});
      }
    } catch (e) {
      print('Auto-processing failed: $e');
    }
  }
  
  Future<Map<String, String>> _gatherEvidence(String chargeId) async {
    // Query local database for evidence
    final orderDoc = await _firestore
        .collection('orders')
        .where('chargeId', isEqualTo: chargeId)
        .get();
    
    if (orderDoc.docs.isEmpty) return {};
    
    final order = orderDoc.docs.first.data();
    
    return {
      'evidence[receipt]': order['receiptUrl'] ?? '',
      'evidence[service_documentation]': order['serviceDoc'] ?? '',
      'evidence[shipping_documentation]': order['shippingDoc'] ?? '',
    };
  }
}

Key Considerations

Limitations:

  • Flutter web apps can’t directly receive webhooks (need server component)
  • Mobile apps require background processing capabilities
  • Real-time webhook processing needs server-side functions

Recommended Architecture:

  1. Use Dart cloud functions to handle Stripe webhooks
  2. Store dispute data in Firestore/database
  3. Flutter admin app listens for database changes
  4. Implement automated evidence submission via API calls
  5. Send push notifications for manual review cases

Packages needed:

  • stripe_dart for Stripe API integration
  • firebase_functions_interop for Firebase Functions
  • cloud_functions for Google Cloud Functions
  • firebase_messaging for push notifications