bug fixes done

This commit is contained in:
2026-03-23 19:25:32 +05:30
parent d1038e846e
commit 9d27389bf2
9 changed files with 819 additions and 253 deletions

File diff suppressed because one or more lines are too long

View File

@@ -58,9 +58,10 @@ class StripePaymentBloc extends Bloc<StripePaymentEvent, StripePaymentState> {
paymentIntentClientSecret: clientSecret,
merchantDisplayName: "CityCards",
style: ThemeMode.light,
allowsDelayedPaymentMethods: true,
),
);
await Stripe.instance.presentPaymentSheet();
emit(const StripePaymentSheetReady());
emit(const StripePaymentLoading(
@@ -105,6 +106,8 @@ class StripePaymentBloc extends Bloc<StripePaymentEvent, StripePaymentState> {
paymentIntentClientSecret: event.clientSecret,
merchantDisplayName: "CityCards",
style: ThemeMode.light,
allowsDelayedPaymentMethods: true,
),
);

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../bloc/stripe_payment_bloc.dart';
import '../bloc/stripe_payment_event.dart';
import '../bloc/stripe_payment_state.dart';

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:ui';
import 'package:citycards_customer/attraction_details/widgets/share_bottomsheet.dart';
import 'package:citycards_customer/common_packages/app_bar.dart';
@@ -309,11 +310,15 @@ class _PassAttractionDetailsViewState extends State<PassAttractionDetailsView> {
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
child: QrImageView(
data:
"Details:\nQR No. : ${attraction.qr.qrNumber}\nQR Code : ${attraction.qr.qrCode}\nStatus : ${attraction.qr.qrStatus}\nExpires At: ${attraction.qr.qrExpiresAt}\nChecked In: ${attraction.qr.checkedInDatetime}\nRemaining : ${attraction.qr.qrRemainingMinutes} mins\nIs Active : ${attraction.qr.isQrActive ? "Yes" : "No"}",
version: QrVersions.auto,
size: 200.w,
child: ImageFiltered(
imageFilter: _isCheckedIn
? ImageFilter.blur(sigmaX: 0, sigmaY: 0) // clear when checked in
: ImageFilter.blur(sigmaX: 6, sigmaY: 6), // blurred before check-in
child: QrImageView(
data: "Details:\nQR No. : ${attraction.qr.qrNumber}\nQR Code : ${attraction.qr.qrCode}\nStatus : ${attraction.qr.qrStatus}\nExpires At: ${attraction.qr.qrExpiresAt}\nChecked In: ${attraction.qr.checkedInDatetime}\nRemaining : ${attraction.qr.qrRemainingMinutes} mins\nIs Active : ${attraction.qr.isQrActive ? "Yes" : "No"}",
version: QrVersions.auto,
size: 200.w,
),
),
),
SizedBox(height: 16.h),

View File

@@ -1,8 +1,8 @@
class ApiUrls {
// static const baseUrl = "https://devapi.citycards.betadelivery.com";//Normal API
static const baseUrl = "https://testingapi.citycards.betadelivery.com";// Test API
// static const baseUrl = "https://uatapi.citycard.betadelivery.com";// Production Lvl API
// static const baseUrl = "https://testingapi.citycards.betadelivery.com";// Test API
static const baseUrl = "https://uatapi.citycard.betadelivery.com";// Production Lvl API
static const refreshToken = "$baseUrl/auth/refresh";

View File

@@ -5,6 +5,7 @@ import 'package:citycards_customer/postcard/widgets/front_card_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../StripePayment/bloc/stripe_payment_bloc.dart';
import '../../StripePayment/bloc/stripe_payment_event.dart';
@@ -128,251 +129,105 @@ class _PostcardCheckoutPageViewState extends State<PostcardCheckoutPageView> {
/// 🆕 Handle payment flow with client secret
Future<void> _handlePaymentFlow(BuildContext context, String clientSecret) async {
// Show payment bottom sheet with BLoC
final paymentSuccess = await showModalBottomSheet<bool>(
context: context,
isDismissible: false,
enableDrag: false,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (bottomSheetContext) {
return BlocProvider(
create: (_) => StripePaymentBloc(stripeService: StripeService())
..add(InitiatePaymentWithClientSecret(clientSecret: clientSecret)),
child: BlocConsumer<StripePaymentBloc, StripePaymentState>(
listener: (context, state) {
if (state is StripePaymentSuccess) {
Navigator.of(bottomSheetContext).pop(true);
} else if (state is StripePaymentFailure || state is StripePaymentCancelled) {
Navigator.of(bottomSheetContext).pop(false);
}
},
builder: (context, state) {
return Container(
height: MediaQuery.of(context).size.height * 0.5,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (state is StripePaymentLoading) ...[
const CircularProgressIndicator(
color: Color(0xffF95F62),
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation<Color>(
Color(0xFFF95F62),
),
),
const SizedBox(height: 24),
const Text(
"Processing payment...",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF333333),
),
),
] else if (state is StripePaymentSuccess) ...[
const Icon(
Icons.check_circle,
color: Colors.green,
size: 64,
),
const SizedBox(height: 16),
const Text(
"Payment Successful!",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF333333),
),
),
] else if (state is StripePaymentFailure) ...[
const Icon(
Icons.error,
color: Colors.red,
size: 64,
),
const SizedBox(height: 16),
const Text(
"Payment Failed",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF333333),
),
),
const SizedBox(height: 8),
Text(
state.error,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
] else if (state is StripePaymentCancelled) ...[
const Icon(
Icons.cancel,
color: Colors.orange,
size: 64,
),
const SizedBox(height: 16),
const Text(
"Payment Cancelled",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF333333),
),
),
],
const SizedBox(height: 32),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 16,
),
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: const Color(0xFFE0E0E0),
),
),
child: Column(
children: [
Text(
"Payment Amount",
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
const SizedBox(height: 8),
Text(
"\$${widget.totalAmount.toStringAsFixed(2)}",
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Color(0xFF333333),
),
),
],
),
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.lock_outline,
size: 16,
color: Colors.grey[600],
),
const SizedBox(width: 6),
Text(
"Secured by Stripe",
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
],
),
),
),
);
},
try {
// Step 1: Initialize Stripe's native payment sheet
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
paymentIntentClientSecret: clientSecret,
merchantDisplayName: 'CityCards',
allowsDelayedPaymentMethods: true, // ← enables UPI, netbanking, etc.
style: ThemeMode.light,
),
);
// Step 2: Present Stripe's native UI (card, UPI, wallets, etc.)
await Stripe.instance.presentPaymentSheet();
// Step 3: Payment succeeded
if (!mounted) return;
_onPaymentSuccess(context);
} on StripeException catch (e) {
if (!mounted) return;
if (e.error.code == FailureCode.Canceled) {
_onPaymentCancelled(context);
} else {
_onPaymentFailed(context, e.error.message ?? 'Payment failed');
}
} catch (e) {
if (!mounted) return;
_onPaymentFailed(context, e.toString());
}
}
void _onPaymentSuccess(BuildContext context) {
context.read<MyPostCardBloc>().add(CheckLoginStatus());
if (widget.isEditMode) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => OrderSuccessPageView(
isEditMode: true,
isCartMode: widget.isCartMode,
pcImage: widget.pcImage,
pcContent: widget.pcContent,
pcState: widget.stateName,
pcCountry: widget.countryName,
pcCity: widget.cityName,
pcZipCode: widget.zipCode,
pcName: widget.fullname,
pcAddress: widget.address1,
senderName: widget.senderName,
senderCity: widget.senderCity,
senderCountry: widget.senderCountry,
),
);
},
),
);
context.read<PostcardCheckoutBloc>().add(
ConfirmPaymentEvent(
stripeStatus: 'succeeded',
paymentStatus: 'success',
),
);
} else {
context.read<PostcardCreationBloc>().add(GoToNextStep());
context.read<PostcardCheckoutBloc>().add(
ConfirmPaymentEvent(
stripeStatus: 'succeeded',
paymentStatus: 'success',
),
);
}
}
void _onPaymentCancelled(BuildContext context) {
// User dismissed the sheet — just save draft if in edit mode
if (widget.isEditMode) {
context.read<PostcardCheckoutBloc>().add(SaveAsDraftEvent());
context.read<PostcardCheckoutBloc>().add(
UpdateCheckoutDataEvent(
postcardId: widget.postcardId,
),
);
}
}
void _onPaymentFailed(BuildContext context, String error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Payment failed: $error'),
backgroundColor: Colors.red,
),
);
// Handle payment result
if (!mounted) return;
if (paymentSuccess == true) {
context.read<MyPostCardBloc>().add(CheckLoginStatus());
if (widget.isEditMode) {
// For edit mode, navigate directly to OrderSuccessPageView
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => OrderSuccessPageView(
isEditMode: true,
isCartMode: widget.isCartMode,
// Front
pcImage: widget.pcImage,
// Back
pcContent: widget.pcContent,
pcState: widget.stateName,
pcCountry: widget.countryName,
pcCity: widget.cityName,
pcZipCode: widget.zipCode,
pcName: widget.fullname,
pcAddress: widget.address1,
senderName: widget.senderName,
senderCity: widget.senderCity,
senderCountry: widget.senderCountry,
),
),
);
final bloc = context.read<PostcardCheckoutBloc>();
bloc.add(
ConfirmPaymentEvent(
stripeStatus: 'succeeded',
paymentStatus: 'success',
),
);
} else {
// For new orders, use the normal step flow
context.read<PostcardCreationBloc>().add(GoToNextStep());
final bloc = context.read<PostcardCheckoutBloc>();
bloc.add(
ConfirmPaymentEvent(
stripeStatus: 'succeeded',
paymentStatus: 'success',
),
);
}
} else {
if (widget.isEditMode) {
context.read<PostcardCheckoutBloc>().add(SaveAsDraftEvent());
context.read<PostcardCheckoutBloc>().add(
UpdateCheckoutDataEvent(
postcardId: widget.postcardId, // pass the id from widget
),
);
}
// Payment failed or cancelled - go to MyPostCardsView
// Navigator.pushReplacement(
// context,
// MaterialPageRoute(
// builder: (context) => const MyPostCardsView(),
// ),
// );
// final bloc = context.read<PostcardCheckoutBloc>();
// bloc.add(
// ConfirmPaymentEvent(
// stripeStatus: 'requires_payment_method',
// paymentStatus: 'failed',
// ),
// );
if (widget.isEditMode) {
context.read<PostcardCheckoutBloc>().add(SaveAsDraftEvent());
context.read<PostcardCheckoutBloc>().add(
UpdateCheckoutDataEvent(
postcardId: widget.postcardId,
),
);
}
}

View File

@@ -48,7 +48,7 @@ class _WriteMessageStepPageViewState extends State<WriteMessageStepPageView> {
TextPosition(offset: _controller.text.length));
final fonts = [
{"name": "Default", "font": GoogleFonts.caveat(), "cleanName": "Caveat"},
{"name": "Default", "font": GoogleFonts.poppins(), "cleanName": "Poppins"},
{"name": "Patrick Hand", "font": GoogleFonts.patrickHand(), "cleanName": "Patrick Hand"},
{"name": "Indie Flower", "font": GoogleFonts.indieFlower(), "cleanName": "Indie Flower"},
{"name": "Gloria Hallelujah", "font": GoogleFonts.gloriaHallelujah(), "cleanName": "Gloria Hallelujah"},

View File

@@ -9,6 +9,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
app_links:
dependency: "direct main"
description:
name: app_links
sha256: "3462d9defc61565fde4944858b59bec5be2b9d5b05f20aed190adb3ad08a7abc"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
app_links_linux:
dependency: transitive
description:
name: app_links_linux
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
url: "https://pub.dev"
source: hosted
version: "1.0.3"
app_links_platform_interface:
dependency: transitive
description:
name: app_links_platform_interface
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
app_links_web:
dependency: transitive
description:
name: app_links_web
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
url: "https://pub.dev"
source: hosted
version: "1.0.4"
archive:
dependency: transitive
description:
@@ -597,6 +629,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.8"
gtk:
dependency: transitive
description:
name: gtk
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
url: "https://pub.dev"
source: hosted
version: "2.1.0"
html:
dependency: transitive
description:
@@ -1588,4 +1628,4 @@ packages:
version: "3.1.3"
sdks:
dart: ">=3.10.0 <4.0.0"
flutter: ">=3.38.0"
flutter: ">=3.38.1"

View File

@@ -71,6 +71,7 @@ dependencies:
libphonenumber_plugin: ^0.3.3
phone_numbers_parser: ^9.0.20
qr_flutter: ^4.1.0
app_links: ^7.0.0
dev_dependencies:
flutter_test: