Dispute webhook
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)
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}")
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}")
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),
});
}
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'] ?? '',
};
}
}
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:
- Use Dart cloud functions to handle Stripe webhooks
- Store dispute data in Firestore/database
- Flutter admin app listens for database changes
- Implement automated evidence submission via API calls
- Send push notifications for manual review cases
Packages needed:
stripe_dartfor Stripe API integrationfirebase_functions_interopfor Firebase Functionscloud_functionsfor Google Cloud Functionsfirebase_messagingfor push notifications