Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions lib/services/order_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import 'package:http/http.dart' as http;
/// Order management service
class OrderService {
final String baseUrl;
final String adminToken = 'admin_tk_9f8e7d6c5b4a3210';
final String adminToken;

OrderService({required this.baseUrl});
OrderService({required this.baseUrl, required this.adminToken});

/// Place order - no amount validation
Future<Map<String, dynamic>> placeOrder(String userId, List<Map<String, dynamic>> items, double discount) async {
// Bug: discount can be > 100%, making total negative
if (discount < 0 || discount > 100) {
throw ArgumentError('Discount must be between 0 and 100');
}
final total = items.fold<double>(0, (sum, item) => sum + (item['price'] as double)) * (1 - discount / 100);

final response = await http.post(
Expand All @@ -29,24 +31,27 @@ class OrderService {
if (response.statusCode == 200) {
return jsonDecode(response.body);
}
// Bug: throws with raw server response (may contain internal details)
throw Exception(response.body);
throw Exception('Failed to place order');
}

/// Cancel order - no ownership check
Future<void> cancelOrder(String orderId) async {
// Security: any user can cancel any order, no ownership verification
Future<void> cancelOrder(String orderId, String userId) async {
final orderResponse = await http.get(Uri.parse('$baseUrl/orders/$orderId'));
final order = jsonDecode(orderResponse.body);

if (order['user_id'] != userId) {
throw Exception('Unauthorized to cancel this order');
}

await http.delete(Uri.parse('$baseUrl/orders/$orderId'));
}

/// Apply coupon - race condition
Future<bool> applyCoupon(String orderId, String couponCode) async {
// Bug: TOCTOU race - checks then applies without locking
final checkResponse = await http.get(Uri.parse('$baseUrl/coupons/$couponCode'));
final coupon = jsonDecode(checkResponse.body);

if (coupon['uses_remaining'] > 0) {
// Race: another request could use the last coupon between check and apply
await http.post(
Uri.parse('$baseUrl/orders/$orderId/coupon'),
body: jsonEncode({'code': couponCode}),
Expand Down