refactor: migrate Stripe checkout to external browser with lifecycle-based subscription refresh

This commit is contained in:
pradeepkumar
2026-04-12 13:08:36 +05:30
parent 1a08730b83
commit 09cee9931b

View File

@@ -1,12 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:real_estate_mobile/core/constants/app_colors.dart';
import 'package:real_estate_mobile/features/profile/presentation/providers/profile_provider.dart';
import 'package:real_estate_mobile/features/profile/presentation/screens/stripe_checkout_screen.dart';
class BillingTab extends ConsumerStatefulWidget {
const BillingTab({super.key});
@@ -15,16 +13,33 @@ class BillingTab extends ConsumerStatefulWidget {
ConsumerState<BillingTab> createState() => _BillingTabState();
}
class _BillingTabState extends ConsumerState<BillingTab> {
class _BillingTabState extends ConsumerState<BillingTab>
with WidgetsBindingObserver {
Map<String, dynamic>? _subscription;
bool _isLoadingSubscription = false;
bool _openedExternalCheckout = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_loadSubscription();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed && _openedExternalCheckout) {
_openedExternalCheckout = false;
_loadSubscription();
}
}
Future<void> _loadSubscription() async {
if (_isLoadingSubscription) return;
setState(() => _isLoadingSubscription = true);
@@ -51,7 +66,10 @@ class _BillingTabState extends ConsumerState<BillingTab> {
_subscription!['status'] == 'ACTIVE';
final plan = _subscription?['plan'] as Map<String, dynamic>?;
return ListView(
return RefreshIndicator(
color: AppColors.accentOrange,
onRefresh: _loadSubscription,
child: ListView(
padding: const EdgeInsets.fromLTRB(24, 10, 24, 30),
children: [
const Center(
@@ -109,6 +127,7 @@ class _BillingTabState extends ConsumerState<BillingTab> {
const SizedBox(height: 24),
_buildNeedHelpSection(),
],
),
);
}
@@ -371,6 +390,15 @@ class _BillingTabState extends ConsumerState<BillingTab> {
),
],
),
const SizedBox(height: 12),
Text(
'By clicking this button you\'ll be taken to our website to complete your purchase.',
style: TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 12,
color: AppColors.primaryDark.withValues(alpha: 0.5),
),
),
const SizedBox(height: 16),
const Text(
'Include Features',
@@ -555,18 +583,13 @@ class _BillingTabState extends ConsumerState<BillingTab> {
final repo = ref.read(profileRepositoryProvider);
final url = await repo.createPortalSession();
if (!mounted) return;
await Navigator.of(context, rootNavigator: true).push<Map<String, dynamic>?>(
MaterialPageRoute(
builder: (_) => StripeCheckoutScreen(
url: url,
title: 'Manage Subscription',
),
),
_openedExternalCheckout = true;
await launchUrl(
Uri.parse(url),
mode: LaunchMode.externalApplication,
);
if (mounted) {
_loadSubscription();
}
} catch (e) {
_openedExternalCheckout = false;
if (mounted) _showSnackBar('Failed to open billing portal: $e', isError: true);
}
}
@@ -582,22 +605,13 @@ class _BillingTabState extends ConsumerState<BillingTab> {
final planId = plans.first['id'] as String;
final url = await repo.createCheckoutSession(planId);
if (!mounted) return;
final result = await Navigator.of(context, rootNavigator: true).push<Map<String, dynamic>?>(
MaterialPageRoute(
builder: (_) => StripeCheckoutScreen(
url: url,
title: 'Checkout',
successUrlPattern: 'confirmation',
),
),
_openedExternalCheckout = true;
await launchUrl(
Uri.parse(url),
mode: LaunchMode.externalApplication,
);
if (result != null && result['success'] == true && mounted) {
final sessionId = result['sessionId'] as String?;
context.go('/payment-success${sessionId != null ? '?session_id=$sessionId' : ''}');
} else if (mounted) {
_loadSubscription();
}
} catch (e) {
_openedExternalCheckout = false;
if (mounted) _showSnackBar('Failed to start checkout: $e', isError: true);
}
}