fix(shopinbit): remove travel delivery country, pre-fill display name, add name setting
What changed, and why it matters
This commit updates the Stack Wallet app's ShopinBit integration. It removes the 'delivery country' field for travel purchases, pre-fills the user's display name from saved settings, and adds a new settings page option to edit that display name. There is no clear security fix here; it appears to be a routine feature/UX update.
No security action required. Treat as a normal feature/UX commit. If reviewing for privacy, verify that ShopInBitService.setDisplayName/loadDisplayName store the name with the same protection as other sensitive app data.
Security signals we found
No security-relevant keywords in commit title or message
No changes to key generation, storage, or verification logic
No input validation/sanitization changes beyond empty-name guard
Hardcoded fallback 'DE' delivery country for travel orders (functional workaround, not a vulnerability)
No references to CVEs, advisories, or security reports
Evidence from the diff
The diff modifies five Dart files related to the ShopinBit third-party service. Changes include: (1) adding a display name TextEditingController and save logic in mobile and desktop settings views; (2) pre-filling the display name during setup from ShopInBitService.loadDisplayName(); (3) removing the country picker UI and delivery-country validation for the ‘travel’ category; and (4) hardcoding deliveryCountry to ‘DE’ for travel orders because the API still requires the field. No cryptographic, authentication, or input-sanitization changes are visible.
Changed components
lib/pages/shopinbit/shopinbit_settings_view.dartlib/pages/shopinbit/shopinbit_setup_view.dartlib/pages/shopinbit/shopinbit_step_4.dartlib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dartlib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dartInspect captured patch +162 / −9
diff --git a/lib/pages/shopinbit/shopinbit_settings_view.dart b/lib/pages/shopinbit/shopinbit_settings_view.dart
index 4451f9d..0682b74 100644
--- a/lib/pages/shopinbit/shopinbit_settings_view.dart
+++ b/lib/pages/shopinbit/shopinbit_settings_view.dart
@@ -34,14 +34,20 @@ class _ShopInBitSettingsViewState extends ConsumerState<ShopInBitSettingsView> {
final _manualKeyFocusNode = FocusNode();
final _verifyKeyController = TextEditingController();
final _verifyKeyFocusNode = FocusNode();
+ late final TextEditingController _displayNameController;
+ late final FocusNode _displayNameFocusNode;
String? _currentKey;
bool _loading = false;
+ bool _savingName = false;
@override
void initState() {
super.initState();
_currentKey = ShopInBitService.instance.loadCustomerKey();
+ final savedName = ShopInBitService.instance.loadDisplayName();
+ _displayNameController = TextEditingController(text: savedName ?? '');
+ _displayNameFocusNode = FocusNode();
}
@override
@@ -50,9 +56,31 @@ class _ShopInBitSettingsViewState extends ConsumerState<ShopInBitSettingsView> {
_manualKeyFocusNode.dispose();
_verifyKeyController.dispose();
_verifyKeyFocusNode.dispose();
+ _displayNameController.dispose();
+ _displayNameFocusNode.dispose();
super.dispose();
}
+ Future<void> _saveDisplayName() async {
+ final name = _displayNameController.text.trim();
+ if (name.isEmpty) return;
+ setState(() => _savingName = true);
+ try {
+ await ShopInBitService.instance.setDisplayName(name);
+ if (mounted) {
+ unawaited(
+ showFloatingFlushBar(
+ type: FlushBarType.success,
+ message: "Display name updated",
+ context: context,
+ ),
+ );
+ }
+ } finally {
+ if (mounted) setState(() => _savingName = false);
+ }
+ }
+
Future<void> _generate() async {
if (_currentKey != null) {
final proceed = await _showChangeWarning();
@@ -456,6 +484,51 @@ class _ShopInBitSettingsViewState extends ConsumerState<ShopInBitSettingsView> {
),
),
const SizedBox(height: 12),
+ RoundedWhiteContainer(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Display Name",
+ style: STextStyles.titleBold12(context),
+ ),
+ const SizedBox(height: 8),
+ Text(
+ "The name ShopinBit staff will see "
+ "when communicating with you.",
+ style: STextStyles.itemSubtitle12(context),
+ ),
+ const SizedBox(height: 12),
+ ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ child: TextField(
+ controller: _displayNameController,
+ focusNode: _displayNameFocusNode,
+ style: STextStyles.field(context),
+ decoration: standardInputDecoration(
+ "Display name",
+ _displayNameFocusNode,
+ context,
+ ),
+ onChanged: (_) => setState(() {}),
+ ),
+ ),
+ const SizedBox(height: 12),
+ PrimaryButton(
+ label: "Save",
+ enabled:
+ !_savingName &&
+ _displayNameController.text
+ .trim()
+ .isNotEmpty,
+ onPressed: _saveDisplayName,
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 12),
],
),
),
diff --git a/lib/pages/shopinbit/shopinbit_setup_view.dart b/lib/pages/shopinbit/shopinbit_setup_view.dart
index b917b68..3c026a8 100644
--- a/lib/pages/shopinbit/shopinbit_setup_view.dart
+++ b/lib/pages/shopinbit/shopinbit_setup_view.dart
@@ -36,7 +36,10 @@ class _ShopInBitSetupViewState extends State<ShopInBitSetupView> {
void initState() {
super.initState();
_keyFuture = ShopInBitService.instance.ensureCustomerKey();
- _nameController = TextEditingController();
+ final existingName = ShopInBitService.instance.loadDisplayName();
+ _nameController = TextEditingController(
+ text: existingName ?? '',
+ );
_nameFocusNode = FocusNode();
_nameFocusNode.addListener(() {
diff --git a/lib/pages/shopinbit/shopinbit_step_4.dart b/lib/pages/shopinbit/shopinbit_step_4.dart
index 8231417..2288aed 100644
--- a/lib/pages/shopinbit/shopinbit_step_4.dart
+++ b/lib/pages/shopinbit/shopinbit_step_4.dart
@@ -259,8 +259,7 @@ class _ShopInBitStep4State extends State<ShopInBitStep4> {
hasValidDates &&
_adults >= 1 &&
travelBudgetVal != null &&
- travelBudgetVal >= 1000 &&
- _selectedCountryIso != null;
+ travelBudgetVal >= 1000;
}
// generic fallback
return !_submitting &&
@@ -523,13 +522,18 @@ class _ShopInBitStep4State extends State<ShopInBitStep4> {
parts.add("Travelers: ${travelers.join(', ')}");
parts.add("Budget: ${_travelBudgetController.text.trim()} EUR");
- parts.add("Delivery country: $countryIso");
widget.model.requestDescription = parts.join("\n");
} else {
widget.model.requestDescription = _descriptionController.text.trim();
}
- widget.model.deliveryCountry = _selectedCountryIso!;
+ // Travel doesn't collect delivery country — use departure country or "DE"
+ // as a default since the API requires the field.
+ if (widget.model.category == ShopInBitCategory.travel) {
+ widget.model.deliveryCountry = "DE";
+ } else {
+ widget.model.deliveryCountry = _selectedCountryIso!;
+ }
if (widget.model.category == ShopInBitCategory.car) {
if (Util.isDesktop) {
@@ -2174,9 +2178,7 @@ class _ShopInBitStep4State extends State<ShopInBitStep4> {
),
),
- // === Shared fields ===
- SizedBox(height: isDesktop ? 24 : 16),
- _buildCountryPicker(isDesktop),
+ // Travel doesn't need delivery country — destinations are in the form.
SizedBox(height: isDesktop ? 16 : 12),
_buildPrivacyCheckbox(isDesktop),
SizedBox(height: isDesktop ? 16 : 12),
diff --git a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart
index 6ee58b5..54c72a0 100644
--- a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart
+++ b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart
@@ -348,7 +348,10 @@ class _ShopInBitDesktopSetupDialogState
void initState() {
super.initState();
_keyFuture = ShopInBitService.instance.ensureCustomerKey();
- _nameController = TextEditingController();
+ final existingName = ShopInBitService.instance.loadDisplayName();
+ _nameController = TextEditingController(
+ text: existingName ?? '',
+ );
_nameFocusNode = FocusNode();
_nameFocusNode.addListener(() {
diff --git a/lib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart
index d65b088..dc5a6c3 100644
--- a/lib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart
+++ b/lib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart
@@ -34,14 +34,20 @@ class _ShopInBitDesktopSettingsState
final _manualKeyFocusNode = FocusNode();
final _verifyKeyController = TextEditingController();
final _verifyKeyFocusNode = FocusNode();
+ late final TextEditingController _displayNameController;
+ late final FocusNode _displayNameFocusNode;
String? _currentKey;
bool _loading = false;
+ bool _savingName = false;
@override
void initState() {
super.initState();
_currentKey = ShopInBitService.instance.loadCustomerKey();
+ final savedName = ShopInBitService.instance.loadDisplayName();
+ _displayNameController = TextEditingController(text: savedName ?? '');
+ _displayNameFocusNode = FocusNode();
}
@override
@@ -50,9 +56,31 @@ class _ShopInBitDesktopSettingsState
_manualKeyFocusNode.dispose();
_verifyKeyController.dispose();
_verifyKeyFocusNode.dispose();
+ _displayNameController.dispose();
+ _displayNameFocusNode.dispose();
super.dispose();
}
+ Future<void> _saveDisplayName() async {
+ final name = _displayNameController.text.trim();
+ if (name.isEmpty) return;
+ setState(() => _savingName = true);
+ try {
+ await ShopInBitService.instance.setDisplayName(name);
+ if (mounted) {
+ unawaited(
+ showFloatingFlushBar(
+ type: FlushBarType.success,
+ message: "Display name updated",
+ context: context,
+ ),
+ );
+ }
+ } finally {
+ if (mounted) setState(() => _savingName = false);
+ }
+ }
+
Future<void> _generate() async {
if (_currentKey != null) {
final proceed = await _showChangeWarning();
@@ -461,6 +489,50 @@ class _ShopInBitDesktopSettingsState
label: "Set key",
onPressed: _setManualKey,
),
+ const Padding(
+ padding: EdgeInsets.all(10.0),
+ child: Divider(thickness: 0.5),
+ ),
+ Text(
+ "Display Name",
+ style: STextStyles.desktopTextSmall(context),
+ ),
+ const SizedBox(height: 8),
+ Text(
+ "The name ShopinBit staff will see "
+ "when communicating with you.",
+ style: STextStyles.desktopTextExtraExtraSmall(context),
+ ),
+ const SizedBox(height: 16),
+ SizedBox(
+ width: 512,
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ child: TextField(
+ controller: _displayNameController,
+ focusNode: _displayNameFocusNode,
+ style: STextStyles.field(context),
+ decoration: standardInputDecoration(
+ "Display name",
+ _displayNameFocusNode,
+ context,
+ ),
+ onChanged: (_) => setState(() {}),
+ ),
+ ),
+ ),
+ const SizedBox(height: 16),
+ PrimaryButton(
+ width: 210,
+ buttonHeight: ButtonHeight.m,
+ enabled:
+ !_savingName &&
+ _displayNameController.text.trim().isNotEmpty,
+ label: "Save",
+ onPressed: _saveDisplayName,
+ ),
],
),
),
Why this scored 12/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.