Payment webook
payment_intent = stripe.PaymentIntent.confirm( payment_intent_id, payment_method=payment_method_id, return_url=“https://your-region-your-project.cloudfunctions.net/payment_return" )
import stripe
import json
from firebase_functions import https_fn, options
from firebase_admin import initialize_app, firestore
initialize_app()
stripe.api_key = "sk_test_..."
@https_fn.on_request(cors=options.CorsOptions(
cors_origins=["*"],
cors_methods=["POST", "GET"]
))
def create_payment_intent(req: https_fn.Request) -> https_fn.Response:
"""Create PaymentIntent for redirect-based payments"""
try:
data = req.get_json()
# Create PaymentIntent with return URL
payment_intent = stripe.PaymentIntent.create(
amount=int(data['amount'] * 100), # Convert to cents
currency=data['currency'],
payment_method_types=[data['payment_method_type']], # e.g., 'ideal', 'bancontact'
metadata={
'user_id': data.get('user_id'),
'order_id': data.get('order_id')
}
)
return https_fn.Response(
json.dumps({
'client_secret': payment_intent.client_secret,
'payment_intent_id': payment_intent.id
}),
headers={'Content-Type': 'application/json'}
)
except Exception as e:
return https_fn.Response(
json.dumps({'error': str(e)}),
status=400,
headers={'Content-Type': 'application/json'}
)
@https_fn.on_request(cors=options.CorsOptions(
cors_origins=["*"],
cors_methods=["POST"]
))
def confirm_payment(req: https_fn.Request) -> https_fn.Response:
"""Confirm payment with redirect handling"""
try:
data = req.get_json()
# Confirm the PaymentIntent
payment_intent = stripe.PaymentIntent.confirm(
data['payment_intent_id'],
payment_method=data['payment_method_id'],
return_url=data['return_url'] # Your app's deep link
)
return https_fn.Response(
json.dumps({
'status': payment_intent.status,
'next_action': payment_intent.next_action,
'client_secret': payment_intent.client_secret
}),
headers={'Content-Type': 'application/json'}
)
except Exception as e:
return https_fn.Response(
json.dumps({'error': str(e)}),
status=400,
headers={'Content-Type': 'application/json'}
)
@https_fn.on_request()
def payment_webhook(req: https_fn.Request) -> https_fn.Response:
"""Handle Stripe webhooks"""
payload = req.data
sig_header = req.headers.get('stripe-signature')
endpoint_secret = "whsec_..." # Your webhook secret
try:
event = stripe.Webhook.construct_event(
payload, sig_header, endpoint_secret
)
except ValueError:
return https_fn.Response("Invalid payload", status=400)
except stripe.error.SignatureVerificationError:
return https_fn.Response("Invalid signature", status=400)
# Handle payment events
if event['type'] == 'payment_intent.succeeded':
payment_intent = event['data']['object']
# Update Firestore with payment success
update_payment_status(payment_intent['id'], 'succeeded')
elif event['type'] == 'payment_intent.payment_failed':
payment_intent = event['data']['object']
update_payment_status(payment_intent['id'], 'failed')
return https_fn.Response("Success")
def update_payment_status(payment_intent_id: str, status: str):
"""Update payment status in Firestore"""
db = firestore.client()
db.collection('payments').document(payment_intent_id).set({
'status': status,
'updated_at': firestore.SERVER_TIMESTAMP
}, merge=True)
// lib/services/payment_service.dart
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:url_launcher/url_launcher.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class PaymentService {
final String baseUrl = 'https://your-region-your-project.cloudfunctions.net';
Future<Map<String, dynamic>> createPaymentIntent({
required double amount,
required String currency,
required String paymentMethodType,
String? userId,
String? orderId,
}) async {
final response = await http.post(
Uri.parse('$baseUrl/create_payment_intent'),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'amount': amount,
'currency': currency,
'payment_method_type': paymentMethodType,
'user_id': userId,
'order_id': orderId,
}),
);
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to create payment intent');
}
}
Future<void> processRedirectPayment({
required String paymentMethodType,
required double amount,
required String currency,
}) async {
try {
// 1. Create PaymentIntent
final paymentData = await createPaymentIntent(
amount: amount,
currency: currency,
paymentMethodType: paymentMethodType,
);
// 2. Create payment method
final paymentMethod = await _createPaymentMethod(paymentMethodType);
// 3. Confirm payment with return URL
final confirmResult = await http.post(
Uri.parse('$baseUrl/confirm_payment'),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'payment_intent_id': paymentData['payment_intent_id'],
'payment_method_id': paymentMethod['id'],
'return_url': '$baseUrl/payment_return', // Firebase function URL
}),
);
if (confirmResult.statusCode == 200) {
final result = json.decode(confirmResult.body);
if (result['next_action'] != null) {
// Handle redirect
await _handleRedirect(result['next_action'], paymentData['payment_intent_id']);
}
}
} catch (e) {
throw Exception('Payment failed: $e');
}
}
Future<Map<String, dynamic>> _createPaymentMethod(String type) async {
// Create payment method based on type
switch (type) {
case 'ideal':
return {'id': 'pm_ideal_test', 'type': 'ideal'};
case 'bancontact':
return {'id': 'pm_bancontact_test', 'type': 'bancontact'};
default:
throw Exception('Unsupported payment method');
}
}
Future<void> _handleRedirect(Map<String, dynamic> nextAction, String paymentIntentId) async {
if (nextAction['type'] == 'redirect_to_url') {
final redirectUrl = nextAction['redirect_to_url']['url'];
// Launch external browser for payment
if (await canLaunch(redirectUrl)) {
await launch(redirectUrl, forceSafariVC: false, forceWebView: false);
// Listen for payment completion via Firestore
_listenForPaymentCompletion(paymentIntentId);
}
}
}
void _listenForPaymentCompletion(String paymentIntentId) {
FirebaseFirestore.instance
.collection('payments')
.doc(paymentIntentId)
.snapshots()
.listen((snapshot) {
if (snapshot.exists) {
final status = snapshot.data()?['status'];
if (status == 'succeeded') {
_handlePaymentSuccess();
} else if (status == 'failed') {
_handlePaymentFailure();
}
}
});
}
void _handlePaymentSuccess() {
// Navigate to success screen
print('Payment successful!');
}
void _handlePaymentFailure() {
// Handle payment failure
print('Payment failed!');
}
}
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// Handle deep links
onGenerateRoute: (settings) {
if (settings.name?.startsWith('/payment') == true) {
return MaterialPageRoute(
builder: (context) => PaymentResultScreen(
uri: Uri.parse(settings.name!),
),
);
}
return null;
},
);
}
}
class PaymentResultScreen extends StatelessWidget {
final Uri uri;
const PaymentResultScreen({Key? key, required this.uri}) : super(key: key);
@override
Widget build(BuildContext context) {
final paymentIntentId = uri.queryParameters['payment_intent'];
final status = uri.queryParameters['status'];
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
status == 'succeeded' ? Icons.check_circle : Icons.error,
size: 64,
color: status == 'succeeded' ? Colors.green : Colors.red,
),
Text(
status == 'succeeded' ? 'Payment Successful!' : 'Payment Failed',
style: Theme.of(context).textTheme.headlineSmall,
),
Text('Payment Intent: $paymentIntentId'),
],
),
),
);
}
}
<!-- android/app/src/main/AndroidManifest.xml -->
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme">
<!-- Deep link intent filter -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourapp" />
</intent-filter>
</activity>