feat(shopinbit): split tracking links
What changed, and why it matters
This commit is a routine feature improvement for the ShopinBit integration in Stack Wallet. It changes how package tracking links are handled: instead of storing one raw string that may contain several URLs crammed together with commas, pipes, or semicolons, the code now splits that string into a clean list of individual tracking URLs. It also turns empty strings from the server into null values so the rest of the app doesn't treat an empty value as a real link. There is no security fix here and nothing that looks like a vulnerability.
No security action required. Review as a normal feature change if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch adds a helper splitTrackingLinks(String? raw) that tokenizes a raw tracking_link field on the regex [ ,|; ], trims each segment, and discards empties. A new read-only getter trackingLinks on TicketStatus exposes the split list. The JSON deserializer now normalizes an empty-string tracking_link from the API to null via _emptyToNull. This is purely a data-presentation change for the ShopinBit service model.
Changed components
lib/services/shopinbit/src/models/ticket.dartInspect captured patch +31 / −1
diff --git a/lib/services/shopinbit/src/models/ticket.dart b/lib/services/shopinbit/src/models/ticket.dart
index df898b6..e42fb3d 100644
--- a/lib/services/shopinbit/src/models/ticket.dart
+++ b/lib/services/shopinbit/src/models/ticket.dart
@@ -1,5 +1,19 @@
import '../../../../utilities/logger.dart';
+/// Splits a raw `tracking_link` value into individual tracking URLs.
+///
+/// Multiple links may be joined with any of `,`, `|`, or `;` (and a single
+/// value may mix them). Returns an empty list for null/empty input. Each URL is
+/// trimmed and empty segments are discarded.
+List<String> splitTrackingLinks(String? raw) {
+ if (raw == null) return const [];
+ return raw
+ .split(RegExp(r'[,|;]'))
+ .map((s) => s.trim())
+ .where((s) => s.isNotEmpty)
+ .toList();
+}
+
enum TicketState {
newTicket('NEW'),
checking('CHECKING'),
@@ -88,6 +102,14 @@ class TicketStatus {
this.trackingLink,
});
+ /// The tracking link(s) split into individual URLs.
+ ///
+ /// A ticket may carry zero, one, or several tracking URLs. When there are
+ /// several the API joins them into [trackingLink] using any of `,`, `|`, or
+ /// `;` as the separator (mixed separators occur in practice), so we split on
+ /// all three.
+ List<String> get trackingLinks => splitTrackingLinks(trackingLink);
+
factory TicketStatus.fromJson(Map<String, dynamic> json) {
final rawState = json['state'] as String;
return TicketStatus(
@@ -99,7 +121,9 @@ class TicketStatus {
? DateTime.parse(json['last_agent_message_at'] as String)
: null,
paymentInvoiceStatus: json['payment_invoice_status'] as String?,
- trackingLink: json['tracking_link'] as String?,
+ // Production returns "" (not null) when there is no tracking link yet;
+ // normalize so callers can treat it like any other absent value.
+ trackingLink: _emptyToNull(json['tracking_link']),
);
}
@@ -182,3 +206,9 @@ int _toInt(dynamic value) {
if (value is int) return value;
return int.parse(value.toString());
}
+
+String? _emptyToNull(dynamic value) {
+ final s = value?.toString().trim();
+ if (s == null || s.isEmpty) return null;
+ return s;
+}
Why this scored 18/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.