fix(shopinbit): retry the real car ticket longer, offer a My Requests shortcut
What changed, and why it matters
This commit is a routine user-experience improvement for a cryptocurrency wallet's car-research payment flow. It makes the app wait a bit longer for a backend ticket to appear and adds a 'My Requests' shortcut button so users don't have to navigate manually. There is no security-relevant change in the diff.
No security action required; treat as normal UX/maintenance review.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors shopinbit_car_research_payment_view.dart to: (1) extract a reusable _showFinalizingFallback() dialog with a new SecondaryButton ‘Close’ and PrimaryButton ‘My Requests’; (2) add _goToMyRequests() navigation helper; (3) increase polling attempts from 5×1.5s to 12×3s when looking up the real car ticket after payment; and (4) add error logging in the catch block. No cryptographic, authentication, authorization, input-validation, or network-trust changes are present.
Changed components
lib/pages/shopinbit/shopinbit_car_research_payment_view.dartInspect captured patch +68 / −37
diff --git a/lib/pages/shopinbit/shopinbit_car_research_payment_view.dart b/lib/pages/shopinbit/shopinbit_car_research_payment_view.dart
index e2cac8b..285e96c 100644
--- a/lib/pages/shopinbit/shopinbit_car_research_payment_view.dart
+++ b/lib/pages/shopinbit/shopinbit_car_research_payment_view.dart
@@ -16,6 +16,7 @@ import '../../utilities/text_styles.dart';
import '../../utilities/util.dart';
import '../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../widgets/desktop/primary_button.dart';
+import '../../widgets/desktop/secondary_button.dart';
import '../../widgets/dialogs/s_dialog.dart';
import '../../widgets/icon_widgets/copy_icon.dart';
import '../../widgets/qr.dart';
@@ -215,6 +216,56 @@ class _ShopInBitCarResearchPaymentViewState
});
}
+ /// Pop the car payment flow and land the user directly on the requests list,
+ /// pushing it only if it isn't already in the stack (e.g. the resume flow
+ /// entered from there).
+ void _goToMyRequests() {
+ final navigator = Navigator.of(context);
+ bool landedOnTickets = false;
+ navigator.popUntil((route) {
+ final name = route.settings.name;
+ if (name == ShopInBitTicketsView.routeName) {
+ landedOnTickets = true;
+ return true;
+ }
+ return name == ServicesView.routeName || route.isFirst;
+ });
+ if (!landedOnTickets) {
+ unawaited(navigator.pushNamed(ShopInBitTicketsView.routeName));
+ }
+ }
+
+ /// Shown when the real car ticket hasn't surfaced in time. Keeps the user
+ /// informed but offers a one-tap shortcut straight to My Requests rather
+ /// than making them dismiss and navigate there by hand.
+ Future<void> _showFinalizingFallback() async {
+ if (!mounted) return;
+ final goToRequests = await showDialog<bool>(
+ context: context,
+ useRootNavigator: Util.isDesktop,
+ builder: (context) => StackDialog(
+ title: "Payment received",
+ message:
+ "We're finalizing your car research request. It will appear in "
+ "My Requests shortly.",
+ leftButton: SecondaryButton(
+ label: "Close",
+ onPressed: () => Navigator.of(context).pop(false),
+ ),
+ rightButton: PrimaryButton(
+ label: "My Requests",
+ onPressed: () => Navigator.of(context).pop(true),
+ ),
+ ),
+ );
+ if (!mounted) return;
+ if (goToRequests == true) {
+ _goToMyRequests();
+ } else {
+ _popToTickets();
+ }
+ }
+
Future<void> _pollStatus() async {
try {
final resp = await ref
@@ -291,23 +342,9 @@ class _ShopInBitCarResearchPaymentViewState
if (logResp.hasError || logResp.value == null) {
// Payment is confirmed but we could not log it. The webhook will
- // finalize it server side, so send the user to their requests where
- // the finalized ticket will appear.
- if (mounted) {
- await showDialog<void>(
- context: context,
- useRootNavigator: Util.isDesktop,
- builder: (context) => StackOkDialog(
- title: "Payment received",
- maxWidth: Util.isDesktop ? 500 : null,
- message:
- "We're finalizing your car research request. It will "
- "appear in My Requests shortly.",
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- }
- if (mounted) _popToTickets();
+ // finalize it server side, so offer the user a shortcut to their
+ // requests where the finalized ticket will appear.
+ await _showFinalizingFallback();
return;
}
@@ -315,12 +352,13 @@ class _ShopInBitCarResearchPaymentViewState
// log-payment gives us the fee receipt id, which the customer key can't
// poll; the real car ticket is a separate id. Find and open it, retrying
- // since it can take a beat to show up in by-customer.
+ // every 3s for a while since it can take a beat to show up in
+ // by-customer.
int? realId;
- for (int attempt = 0; attempt < 5 && realId == null; attempt++) {
+ for (int attempt = 0; attempt < 12 && realId == null; attempt++) {
realId = await service.adoptRealCarTicket(result.ticketId);
- if (realId == null && attempt < 4) {
- await Future<void>.delayed(const Duration(milliseconds: 1500));
+ if (realId == null && attempt < 11) {
+ await Future<void>.delayed(const Duration(seconds: 3));
}
}
@@ -334,23 +372,16 @@ class _ShopInBitCarResearchPaymentViewState
).pushNamed(ShopInBitOrderCreated.routeName, arguments: realId),
);
} else {
- // The real ticket hasn't surfaced yet; the requests list will pick it
- // up on its next refresh.
- await showDialog<void>(
- context: context,
- useRootNavigator: Util.isDesktop,
- builder: (context) => StackOkDialog(
- title: "Payment received",
- maxWidth: Util.isDesktop ? 500 : null,
- message:
- "We're finalizing your car research request. It will appear "
- "in My Requests shortly.",
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- if (mounted) _popToTickets();
+ // The real ticket hasn't surfaced yet; offer a shortcut to the
+ // requests list, which will pick it up on its next refresh.
+ await _showFinalizingFallback();
}
- } catch (e) {
+ } catch (e, s) {
+ Logging.instance.e(
+ "Failed to process car research payment",
+ error: e,
+ stackTrace: s,
+ );
if (mounted) {
setState(() => _flowState = _PaymentFlowState.error);
await showDialog<void>(
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.