fix(shopinbit): tolerate empty/missing fields when parsing API JSON
What changed, and why it matters
This commit makes the app more forgiving when it receives incomplete or oddly formatted data from the ShopinBit shopping service. It replaces several strict parsing rules with fallback values (for example, using today's date if an expiration date is missing, or 0 if a ticket ID can't be read). The change is defensive and reduces the chance that a malformed API response will crash the wallet, but it also silently hides bad data instead of reporting it. There is no direct evidence this fixes an active security vulnerability or was exploited.
Treat as a routine robustness improvement. Review whether silently defaulting missing fields (especially dates, IDs, and prices) could hide API errors or manipulation; consider adding logging or validation when fallback values are used. No urgent security patch is indicated by the diff alone.
Security signals we found
Strict JSON parsing relaxed to fallback defaults
Missing or malformed date fields silently replaced with current time
Missing or malformed integer IDs silently replaced with 0
Nullable fields introduced where values were previously required
No input validation or logging added for malformed responses
Evidence from the diff
The patch updates Dart JSON deserialization in the ShopinBit integration to tolerate missing/empty fields and type mismatches. Key changes: DateTime.parse() is replaced with DateTime.tryParse() defaulting to DateTime.now(); int.parse() is replaced with int.tryParse() defaulting to 0; several String casts now fall back to empty strings; vatRate becomes nullable; and a helper that threw on unparseable values is removed. The code now swallows malformed API responses rather than throwing. This is a robustness fix, not a clearly security-relevant one, though silent defaults could mask API tampering or data integrity issues.
Changed components
lib/services/shopinbit/src/models/car_research.dartlib/services/shopinbit/src/models/message.dartlib/services/shopinbit/src/models/payment.dartlib/services/shopinbit/src/models/ticket.dartlib/services/shopinbit/src/models/voucher.dartInspect captured patch +31 / −28
diff --git a/lib/services/shopinbit/src/models/car_research.dart b/lib/services/shopinbit/src/models/car_research.dart
index e5bf15b..93a1985 100644
--- a/lib/services/shopinbit/src/models/car_research.dart
+++ b/lib/services/shopinbit/src/models/car_research.dart
@@ -92,7 +92,9 @@ class CarResearchInvoice {
final linksRaw = json['payment_links'] as Map<String, dynamic>? ?? {};
return CarResearchInvoice(
btcpayInvoice: json['btcpay_invoice'] as String,
- expiresAt: DateTime.parse(json['expires_at'] as String),
+ expiresAt:
+ DateTime.tryParse(json['expires_at']?.toString() ?? '') ??
+ DateTime.now(),
paymentLinks: linksRaw.map((k, v) => MapEntry(k, v as String)),
);
}
@@ -114,7 +116,7 @@ class CarResearchPaymentResult {
factory CarResearchPaymentResult.fromJson(Map<String, dynamic> json) {
return CarResearchPaymentResult(
status: json['status'] as String,
- ticketId: json['ticket_id'] as int,
+ ticketId: int.tryParse(json['ticket_id'].toString()) ?? 0,
ticketNumber: json['ticket_number'] as String,
externalCustomerKey: json['external_customer_key'] as String,
);
diff --git a/lib/services/shopinbit/src/models/message.dart b/lib/services/shopinbit/src/models/message.dart
index 1341322..85c1ffa 100644
--- a/lib/services/shopinbit/src/models/message.dart
+++ b/lib/services/shopinbit/src/models/message.dart
@@ -11,9 +11,11 @@ class TicketMessage {
factory TicketMessage.fromJson(Map<String, dynamic> json) {
return TicketMessage(
- timestamp: DateTime.parse(json['timestamp'] as String),
- fromAgent: json['from_agent'] as bool,
- content: json['content'] as String,
+ timestamp:
+ DateTime.tryParse(json['timestamp']?.toString() ?? '') ??
+ DateTime.now(),
+ fromAgent: json['from_agent'] as bool? ?? false,
+ content: json['content'] as String? ?? '',
);
}
diff --git a/lib/services/shopinbit/src/models/payment.dart b/lib/services/shopinbit/src/models/payment.dart
index bd0938d..0633257 100644
--- a/lib/services/shopinbit/src/models/payment.dart
+++ b/lib/services/shopinbit/src/models/payment.dart
@@ -2,7 +2,7 @@ class PaymentInfo {
final String status;
final String customerPrice;
final String partnerPrice;
- final int vatRate;
+ final int? vatRate;
final String currency;
final DateTime? rateLockedUntil;
final Map<String, String> paymentLinks;
@@ -22,23 +22,16 @@ class PaymentInfo {
factory PaymentInfo.fromJson(Map<String, dynamic> json) {
final linksRaw = json['payment_links'] as Map<String, dynamic>? ?? {};
return PaymentInfo(
- status: json['status'] as String,
+ status: (json['status'] ?? '') as String,
customerPrice: (json['customer_price'] ?? '') as String,
partnerPrice: (json['partner_price'] ?? '') as String,
- vatRate: _toInt(json['vat_rate']),
+ vatRate: int.tryParse(json['vat_rate'].toString()),
currency: (json['currency'] ?? 'EUR') as String,
- rateLockedUntil: json['rate_locked_until'] != null
- ? DateTime.parse(json['rate_locked_until'] as String)
- : null,
+ rateLockedUntil: DateTime.tryParse(
+ json['rate_locked_until']?.toString() ?? '',
+ ),
paymentLinks: linksRaw.map((k, v) => MapEntry(k, v as String)),
due: json['due'] as String?,
);
}
}
-
-int _toInt(dynamic v) {
- if (v is int) return v;
- if (v is String) return int.parse(v);
- if (v is double) return v.toInt();
- return 0;
-}
diff --git a/lib/services/shopinbit/src/models/ticket.dart b/lib/services/shopinbit/src/models/ticket.dart
index ca67826..63ad123 100644
--- a/lib/services/shopinbit/src/models/ticket.dart
+++ b/lib/services/shopinbit/src/models/ticket.dart
@@ -51,7 +51,10 @@ class TicketRef {
TicketRef({required this.id, required this.number});
factory TicketRef.fromJson(Map<String, dynamic> json) {
- return TicketRef(id: _toInt(json['id']), number: json['number'] as String);
+ return TicketRef(
+ id: _toInt(json['id']),
+ number: json['number']?.toString() ?? '',
+ );
}
Map<String, dynamic> toMap() {
@@ -85,15 +88,17 @@ class TicketStatus {
});
factory TicketStatus.fromJson(Map<String, dynamic> json) {
- final rawState = json['state'] as String;
+ final rawState = (json['state'] ?? '') as String;
return TicketStatus(
ticketId: _toInt(json['ticket_id']),
state: TicketState.fromString(rawState),
stateRaw: rawState,
- updatedAt: DateTime.parse(json['updated_at'] as String),
- lastAgentMessageAt: json['last_agent_message_at'] != null
- ? DateTime.parse(json['last_agent_message_at'] as String)
- : null,
+ updatedAt:
+ DateTime.tryParse(json['updated_at']?.toString() ?? '') ??
+ DateTime.now(),
+ lastAgentMessageAt: DateTime.tryParse(
+ json['last_agent_message_at']?.toString() ?? '',
+ ),
paymentInvoiceStatus: json['payment_invoice_status'] as String?,
trackingLink: json['tracking_link'] as String?,
);
@@ -142,7 +147,7 @@ class TicketFull {
factory TicketFull.fromJson(Map<String, dynamic> json) {
return TicketFull(
id: _toInt(json['id']),
- number: json['number'] as String,
+ number: json['number']?.toString() ?? '',
productName: json['product_name'] as String?,
customerPrice: json['customer_price'] as String?,
partnerPrice: json['partner_price'] as String?,
@@ -151,7 +156,8 @@ class TicketFull {
netShippingCosts: json['net_shipping_costs'] as String?,
deliveryCountry:
json['delivery_country'] as String? ??
- (json['deliverycountry'] as String),
+ json['deliverycountry'] as String? ??
+ '',
vatRate: int.tryParse(json['vat_rate'].toString()),
);
}
@@ -177,5 +183,5 @@ class TicketFull {
int _toInt(dynamic value) {
if (value is int) return value;
- return int.parse(value.toString());
+ return int.tryParse(value.toString()) ?? 0;
}
diff --git a/lib/services/shopinbit/src/models/voucher.dart b/lib/services/shopinbit/src/models/voucher.dart
index 97d048a..e65b304 100644
--- a/lib/services/shopinbit/src/models/voucher.dart
+++ b/lib/services/shopinbit/src/models/voucher.dart
@@ -62,7 +62,7 @@ class VipRedemptionResult {
return VipRedemptionResult(
ticketId: json['ticket_id'] is int
? json['ticket_id'] as int
- : int.parse(json['ticket_id'].toString()),
+ : int.tryParse(json['ticket_id'].toString()) ?? 0,
ticketNumber: json['ticket_number'] as String,
externalCustomerKey: json['external_customer_key'] as String,
voucherCode: json['voucher_code'] as String,
Why this scored 23/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.