fix(shopinbit): require remaining required fields across models
What changed, and why it matters
This commit tightens how the Stack Wallet app reads data from its ShopInBit partner service. Previously, several fields were treated as optional and replaced with safe defaults (empty strings, false, current time) when missing. Now the code requires those fields to be present and correctly typed. This is a defensive correctness fix: it makes the app fail earlier and more visibly if the server sends unexpected or malformed data, rather than silently continuing with placeholder values. There is no direct evidence this fixes an active security vulnerability, but it reduces the risk of logic errors or misleading UI state caused by missing fields.
Treat as a hardening/correctness fix. Review whether the ShopInBit API contract guarantees these fields are always present and non-null before deploying, because stricter parsing may introduce crashes if the server can legitimately omit values. Add integration tests covering missing/null/maltyped fields. No urgent security patch is indicated by the diff alone.
Security signals we found
Removal of default fallbacks in deserialization can prevent silent propagation of placeholder/trusted state
Direct casts may surface malformed or missing server data as runtime exceptions rather than hidden logic errors
No explicit security framing, CVE, or attacker-controlled input path is present in the diff
Potential availability concern: stricter parsing could crash the app on unexpected API responses
Evidence from the diff
The patch removes nullable/default fallbacks in JSON deserialization for the ShopInBit integration. Fields such as status, content, from_agent, valid, finalized, has_request_payload, and several price/currency fields are now cast directly to their expected non-null types. Date parsing for TicketMessage.timestamp switches from DateTime.tryParse with a fallback to DateTime.now() to DateTime.parse, which will throw on malformed input. Similarly, ticket_number is cast directly to String instead of calling toString(). The change enforces schema conformance and prevents the app from constructing objects with synthetic default values when the server omits or mistypes data.
Changed components
lib/services/shopinbit/src/client.dartlib/services/shopinbit/src/models/car_research.dartlib/services/shopinbit/src/models/message.dartlib/services/shopinbit/src/models/payment.dartlib/services/shopinbit/src/models/voucher.dartInspect captured patch +13 / −15
diff --git a/lib/services/shopinbit/src/client.dart b/lib/services/shopinbit/src/client.dart
index 9d817db..c475cfe 100644
--- a/lib/services/shopinbit/src/client.dart
+++ b/lib/services/shopinbit/src/client.dart
@@ -120,7 +120,7 @@ class ShopInBitClient {
id: json['ticket_id'] is int
? json['ticket_id'] as int
: int.parse(json['ticket_id'].toString()),
- number: json['ticket_number'].toString(),
+ number: json['ticket_number'] as String,
);
},
customerKey: externalCustomerKey,
diff --git a/lib/services/shopinbit/src/models/car_research.dart b/lib/services/shopinbit/src/models/car_research.dart
index 70e61b3..895b1f1 100644
--- a/lib/services/shopinbit/src/models/car_research.dart
+++ b/lib/services/shopinbit/src/models/car_research.dart
@@ -47,11 +47,11 @@ class CarResearchCurrentInvoice {
final createdRaw = json['created_at'] as String?;
return CarResearchCurrentInvoice(
invoiceId: json['invoice_id'] as String,
- status: json['status'] as String? ?? '',
+ status: json['status'] as String,
additional: json['additional'] as String?,
expiresAt: expiresRaw == null ? null : DateTime.tryParse(expiresRaw),
paymentLinks: linksRaw.map((k, v) => MapEntry(k, v as String)),
- hasRequestPayload: json['has_request_payload'] as bool? ?? false,
+ hasRequestPayload: json['has_request_payload'] as bool,
createdAt: createdRaw == null ? null : DateTime.tryParse(createdRaw),
);
}
@@ -145,9 +145,9 @@ class CarResearchInvoiceStatus {
}
return CarResearchInvoiceStatus(
- status: json['status']?.toString() ?? '',
+ status: json['status'] as String,
additional: json['additional']?.toString(),
- finalized: json['finalized'] == true,
+ finalized: json['finalized'] as bool,
receiptTicketId: toIntOrNull(json['receipt_ticket_id']),
receiptTicketNumber: json['receipt_ticket_number']?.toString(),
realTicketId: toIntOrNull(json['real_ticket_id']),
diff --git a/lib/services/shopinbit/src/models/message.dart b/lib/services/shopinbit/src/models/message.dart
index 85c1ffa..1341322 100644
--- a/lib/services/shopinbit/src/models/message.dart
+++ b/lib/services/shopinbit/src/models/message.dart
@@ -11,11 +11,9 @@ class TicketMessage {
factory TicketMessage.fromJson(Map<String, dynamic> json) {
return TicketMessage(
- timestamp:
- DateTime.tryParse(json['timestamp']?.toString() ?? '') ??
- DateTime.now(),
- fromAgent: json['from_agent'] as bool? ?? false,
- content: json['content'] as String? ?? '',
+ timestamp: DateTime.parse(json['timestamp'] as String),
+ fromAgent: json['from_agent'] as bool,
+ content: json['content'] as String,
);
}
diff --git a/lib/services/shopinbit/src/models/payment.dart b/lib/services/shopinbit/src/models/payment.dart
index 0633257..05c8648 100644
--- a/lib/services/shopinbit/src/models/payment.dart
+++ b/lib/services/shopinbit/src/models/payment.dart
@@ -22,11 +22,11 @@ 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,
- customerPrice: (json['customer_price'] ?? '') as String,
- partnerPrice: (json['partner_price'] ?? '') as String,
+ status: json['status'] as String,
+ customerPrice: json['customer_price'] as String,
+ partnerPrice: json['partner_price'] as String,
vatRate: int.tryParse(json['vat_rate'].toString()),
- currency: (json['currency'] ?? 'EUR') as String,
+ currency: json['currency'] as String,
rateLockedUntil: DateTime.tryParse(
json['rate_locked_until']?.toString() ?? '',
),
diff --git a/lib/services/shopinbit/src/models/voucher.dart b/lib/services/shopinbit/src/models/voucher.dart
index 97d048a..fa7e9a4 100644
--- a/lib/services/shopinbit/src/models/voucher.dart
+++ b/lib/services/shopinbit/src/models/voucher.dart
@@ -29,7 +29,7 @@ class VoucherInfo {
factory VoucherInfo.fromJson(Map<String, dynamic> json) {
return VoucherInfo(
- valid: json['valid'] as bool? ?? false,
+ valid: json['valid'] as bool,
voucherCode: json['voucher_code'] as String?,
discountAmount: (json['discount_amount'] as num?)?.toDouble(),
voucherType: json['voucher_type'] as String?,
Why this scored 26/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.