refactor: migrate Stripe checkout to external browser with lifecycle-based subscription refresh
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user