Warenkorb & Bestellungen API
This commit is contained in:
@@ -1,6 +1,88 @@
|
||||
namespace ShopAPI.Controllers
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using ShopAPI.DTOs;
|
||||
using ShopAPI.Models;
|
||||
using ShopAPI.Services;
|
||||
using System.Security.Claims;
|
||||
namespace ShopAPI.Controllers
|
||||
{
|
||||
public class OrderController
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class OrderController : ControllerBase
|
||||
{
|
||||
private readonly OrderService _orderService;
|
||||
|
||||
public OrderController(OrderService orderService)
|
||||
{
|
||||
_orderService = orderService;
|
||||
}
|
||||
|
||||
// hilfsmethode - eingeloggten User ID bekommen
|
||||
private int GetUserId() => int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
|
||||
// Get api/order (Admin -> alle Bestellungen)
|
||||
[HttpGet]
|
||||
[Authorize(Roles = "Admin")]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
var orders = await _orderService.GetAllAsync();
|
||||
return Ok(orders);
|
||||
}
|
||||
|
||||
// Get api/order/my (Customer -> meine Bestellungen)
|
||||
[HttpGet("my")]
|
||||
public async Task<IActionResult> GetMyOrders()
|
||||
{
|
||||
var orders = await _orderService.GetMyOrderAsync(GetUserId());
|
||||
return Ok(orders);
|
||||
}
|
||||
|
||||
// Get api/order/{id}
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
var order = await _orderService.GetByIdAsync(id);
|
||||
if(order == null) return NotFound(new {message = "Bestellung nicht gefunden"});
|
||||
return Ok(order);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(OrderDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var order = await _orderService.CreateAsync(GetUserId(), dto);
|
||||
return CreatedAtAction(nameof(GetById), new { id = order.Id }, order);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new { message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}/cancel")]
|
||||
public async Task<IActionResult> Cancel(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var order = await _orderService.CancelAsync(id, GetUserId());
|
||||
if (order == null) return NotFound(new { message = "Bestellung nicht gefunden" });
|
||||
return Ok(order);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new { message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}/status")]
|
||||
[Authorize(Roles = "Admin")]
|
||||
public async Task<IActionResult> UpdateStatus(int id, UpdateStatusDto dto)
|
||||
{
|
||||
var order = await _orderService.UpdatesStatusAsync(id, dto.Status);
|
||||
if (order == null) return NotFound(new { message = "Bestellung nicht gefunden" });
|
||||
return Ok(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,37 @@
|
||||
namespace ShopAPI.Controllers
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using ShopAPI.DTOs;
|
||||
using ShopAPI.Services;
|
||||
using System.Security.Claims;
|
||||
namespace ShopAPI.Controllers
|
||||
{
|
||||
public class PaymentController
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class PaymentController : ControllerBase
|
||||
{
|
||||
private readonly PaymentService _paymentService;
|
||||
|
||||
public PaymentController(PaymentService paymentService)
|
||||
{
|
||||
_paymentService = paymentService;
|
||||
}
|
||||
|
||||
private int GetUserId() => int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
|
||||
// Post api/payment/pay/{orderId} (Bestellung bezahlen)
|
||||
[HttpPost("pay/{orderId}")]
|
||||
public async Task<IActionResult> Pay(int orderId, PaymentDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var payment = await _paymentService.PayAsync(orderId, GetUserId(), dto);
|
||||
return Ok(payment);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
namespace ShopAPI.DTOs
|
||||
using ShopAPI.Models;
|
||||
|
||||
namespace ShopAPI.DTOs
|
||||
{
|
||||
public class OrderDto
|
||||
{
|
||||
public List<OrderItemDto> Items { get; set; } = new();
|
||||
}
|
||||
}
|
||||
|
||||
8
ShopAPI/ShopAPI/DTOs/OrderItemDto.cs
Normal file
8
ShopAPI/ShopAPI/DTOs/OrderItemDto.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace ShopAPI.DTOs
|
||||
{
|
||||
public class OrderItemDto
|
||||
{
|
||||
public int ProductId { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
}
|
||||
}
|
||||
7
ShopAPI/ShopAPI/DTOs/PaymentDto.cs
Normal file
7
ShopAPI/ShopAPI/DTOs/PaymentDto.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ShopAPI.DTOs
|
||||
{
|
||||
public class PaymentDto
|
||||
{
|
||||
public string Method { get; set; } = string.Empty; // "CreditCard", "PayPal", "Cach".
|
||||
}
|
||||
}
|
||||
7
ShopAPI/ShopAPI/DTOs/UpdateStatusDto.cs
Normal file
7
ShopAPI/ShopAPI/DTOs/UpdateStatusDto.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ShopAPI.DTOs
|
||||
{
|
||||
public class UpdateStatusDto
|
||||
{
|
||||
public string Status { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -40,13 +40,19 @@ namespace ShopAPI
|
||||
});
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddControllers()
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.ReferenceHandler =
|
||||
System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles;
|
||||
});
|
||||
|
||||
// Hier: jeder benutzer bekommt nur seine Daten , und keine vermischung zwischen Requests
|
||||
builder.Services.AddScoped<AuthService>();
|
||||
builder.Services.AddScoped<ProductService>();
|
||||
builder.Services.AddScoped<CategoryService>();
|
||||
|
||||
builder.Services.AddScoped<OrderService>();
|
||||
builder.Services.AddScoped<PaymentService>();
|
||||
var app = builder.Build();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
@@ -1,6 +1,141 @@
|
||||
namespace ShopAPI.Services
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ShopAPI.Data;
|
||||
using ShopAPI.DTOs;
|
||||
using ShopAPI.Models;
|
||||
namespace ShopAPI.Services
|
||||
{
|
||||
public class OrderService
|
||||
{
|
||||
public readonly AppDbContext _db;
|
||||
|
||||
public OrderService(AppDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
// Get ALL Orders for a User
|
||||
public async Task<List<Order>> GetAllAsync()
|
||||
{
|
||||
return await _db.Orders
|
||||
.Include(o => o.User)
|
||||
.Include(o => o.OrderItems)
|
||||
.ThenInclude(oi => oi.Product)
|
||||
.Include(o => o.Payment)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
// Get My Orders (Customer) : Gib mir alle Bestellungen von diesem User
|
||||
public async Task<List<Order>> GetMyOrderAsync(int userId)
|
||||
{
|
||||
return await _db.Orders
|
||||
.Where(o => o.UserId == userId)
|
||||
.Include(o => o.OrderItems)
|
||||
.ThenInclude(oi => oi.Product)
|
||||
.Include(o => o.Payment)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
// Get By ID : Gib mir Die eine Bestellung mit dieser ID
|
||||
public async Task<Order?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _db.Orders
|
||||
.Include(o => o.User)
|
||||
.Include(o => o.OrderItems)
|
||||
.ThenInclude(oi => oi.Product)
|
||||
.Include(o => o.Payment)
|
||||
.FirstOrDefaultAsync(o => o.Id == id);
|
||||
}
|
||||
|
||||
// Create Order
|
||||
public async Task<Order> CreateAsync(int userId, OrderDto dto)
|
||||
{
|
||||
foreach (var item in dto.Items)
|
||||
{
|
||||
var product = await _db.Products.FindAsync(item.ProductId);
|
||||
if (product == null)
|
||||
{
|
||||
throw new Exception($"Produkt {item.ProductId} nciht gefunden");
|
||||
}
|
||||
|
||||
if (product.Stock < item.Quantity)
|
||||
{
|
||||
throw new Exception($"Nicht genung lager für {product.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
// Bestellung erstellen
|
||||
var order = new Order
|
||||
{
|
||||
UserId = userId,
|
||||
Createdat = DateTime.UtcNow,
|
||||
Status = "Pending"
|
||||
};
|
||||
|
||||
_db.Orders.Add(order);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
// OrderItems
|
||||
decimal total = 0;
|
||||
foreach (var item in dto.Items)
|
||||
{
|
||||
var product = await _db.Products.FindAsync(item.ProductId);
|
||||
|
||||
var orderItem = new OrderItem
|
||||
{
|
||||
OrderId = order.Id,
|
||||
ProductId = item.ProductId,
|
||||
Quantity = item.Quantity,
|
||||
UnitPrice = product!.Price
|
||||
};
|
||||
|
||||
product.Stock -= item.Quantity; // Lager aktualisieren
|
||||
|
||||
_db.OrderItems.Add(orderItem);
|
||||
}
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return await GetByIdAsync(order.Id) ?? order;
|
||||
}
|
||||
|
||||
// Cancel order
|
||||
public async Task<Order?> CancelAsync(int id, int userId)
|
||||
{
|
||||
var order = await _db.Orders
|
||||
.Include(o => o.OrderItems)
|
||||
.FirstOrDefaultAsync(o => o.Id == id && o.UserId == userId);
|
||||
|
||||
if (order == null) return null;
|
||||
if (order.Status == "Shipped")
|
||||
throw new Exception("Versendete Bestellung kann nicht storniert werden");
|
||||
|
||||
// lager zurückgeben
|
||||
foreach (var item in order.OrderItems)
|
||||
{
|
||||
var product = await _db.Products.FindAsync(item.ProductId);
|
||||
if(product != null)
|
||||
{
|
||||
product.Stock += item.Quantity;
|
||||
}
|
||||
}
|
||||
|
||||
order.Status = "Cancelled";
|
||||
await _db.SaveChangesAsync();
|
||||
return order;
|
||||
|
||||
}
|
||||
|
||||
// Update status (Admin)
|
||||
public async Task<Order?> UpdatesStatusAsync(int id, string status)
|
||||
{
|
||||
var order = await _db.Orders.FindAsync(id);
|
||||
if(order == null) return null;
|
||||
|
||||
order.Status = status;
|
||||
await _db.SaveChangesAsync();
|
||||
return order;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
53
ShopAPI/ShopAPI/Services/PaymentService.cs
Normal file
53
ShopAPI/ShopAPI/Services/PaymentService.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ShopAPI.Data;
|
||||
using ShopAPI.DTOs;
|
||||
using ShopAPI.Models;
|
||||
|
||||
namespace ShopAPI.Services
|
||||
{
|
||||
public class PaymentService
|
||||
{
|
||||
public readonly AppDbContext _db;
|
||||
|
||||
public PaymentService(AppDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
// Pay Order
|
||||
public async Task<Payment> PayAsync(int orderId, int uderId, PaymentDto dto)
|
||||
{
|
||||
var order = await _db.Orders
|
||||
.Include(o => o.OrderItems)
|
||||
.FirstOrDefaultAsync(o => o.Id == orderId && o.UserId == uderId);
|
||||
|
||||
if(order == null)
|
||||
throw new Exception("Order not found");
|
||||
if(order.Status == "Cancelled")
|
||||
throw new Exception("Stornierte Bestellung kann nciht bezahlt werden");
|
||||
|
||||
// Prüfen ob schon bezahlt
|
||||
var existing = await _db.Payments.FirstOrDefaultAsync(p => p.OrderId == orderId);
|
||||
if(existing != null)
|
||||
throw new Exception("Bestellung wurde bereits bezahlt");
|
||||
|
||||
// Gesamtbetrag berechnen
|
||||
var amount = order.OrderItems.Sum(oi => oi.Quantity * oi.UnitPrice);
|
||||
|
||||
var payment = new Payment
|
||||
{
|
||||
|
||||
OrderId = orderId,
|
||||
Amount = amount,
|
||||
Method = dto.Method,
|
||||
Status = "Paid",
|
||||
PaidAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
order.Status = "Paid";
|
||||
_db.Payments.Add(payment);
|
||||
await _db.SaveChangesAsync();
|
||||
return payment;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,7 +15,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("ShopAPI")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+384d7e0287f40c1ce9e61d3123377dc5917ef6b6")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d297055cc4ab7e3c8a93d2b98beca4da08aba908")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("ShopAPI")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("ShopAPI")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
e04359f0bf9216ce6ea4f06fefbd8b64b5dbf14e78bc9a52d8c538097c953810
|
||||
4ace5e3dd2679b7f8d6ba402a9a151576ba9bd2f4940c60938bf7c3a5fa4b2f7
|
||||
|
||||
@@ -1 +1 @@
|
||||
76e1eaf859bca5d672b1fac1789f7abe6bda99f7cc154bb78899e5780554f7a0
|
||||
4ae0a6b08c2111e583c69b63b26d342f1e34844cf7b3ca861744608079c9e142
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"UbUS1vR6wGATGwMhgD7zMeswN1vTEJ19YTFurt+qa38=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["qXK9XBukmOKR4bC0/OSWUQmD7NVDnODrTNbVfODoSOA=","RsfaIcSdxUsyxkHT0X7TBDvWAE\u002BekQ7N\u002BCKYPxhpGog=","q4x5tw5ZdEVI3wN3ORljilxzvA3pVT3snlXNH\u002Bh3MZw=","hqxvnAagyda0d6U9WqiUEHNFsi/9qmMo9aedinq8ZQA=","TRUWKVmfrqI3Da5OlFteES1pBI/d\u002Bef88\u002BcbmmwH4mw=","AMewKZACPMF34A9M/c\u002BnjYkuddQPOVXhAdpXf\u002BloS2s=","GrwxjTxxr29ICyUcOpweOHua4V5l\u002BtKseEVsI30NOeU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"UbUS1vR6wGATGwMhgD7zMeswN1vTEJ19YTFurt+qa38=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["qXK9XBukmOKR4bC0/OSWUQmD7NVDnODrTNbVfODoSOA=","RsfaIcSdxUsyxkHT0X7TBDvWAE\u002BekQ7N\u002BCKYPxhpGog=","mwxpKH5s5WhH59E7QrRxfDY8/18lLYY3KNsyd1uFNcs=","q4x5tw5ZdEVI3wN3ORljilxzvA3pVT3snlXNH\u002Bh3MZw=","hqxvnAagyda0d6U9WqiUEHNFsi/9qmMo9aedinq8ZQA=","TRUWKVmfrqI3Da5OlFteES1pBI/d\u002Bef88\u002BcbmmwH4mw=","AMewKZACPMF34A9M/c\u002BnjYkuddQPOVXhAdpXf\u002BloS2s=","jLjjNkMAcR6ICPvLUJeO8hvccCstVj2PSPKA7Fc04j0="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"8Bes/LQh4hq2du9moDLYcP3dWOcm8118awBX2grZ2jA=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["qXK9XBukmOKR4bC0/OSWUQmD7NVDnODrTNbVfODoSOA=","RsfaIcSdxUsyxkHT0X7TBDvWAE\u002BekQ7N\u002BCKYPxhpGog=","q4x5tw5ZdEVI3wN3ORljilxzvA3pVT3snlXNH\u002Bh3MZw=","hqxvnAagyda0d6U9WqiUEHNFsi/9qmMo9aedinq8ZQA=","TRUWKVmfrqI3Da5OlFteES1pBI/d\u002Bef88\u002BcbmmwH4mw=","AMewKZACPMF34A9M/c\u002BnjYkuddQPOVXhAdpXf\u002BloS2s=","GrwxjTxxr29ICyUcOpweOHua4V5l\u002BtKseEVsI30NOeU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"8Bes/LQh4hq2du9moDLYcP3dWOcm8118awBX2grZ2jA=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["qXK9XBukmOKR4bC0/OSWUQmD7NVDnODrTNbVfODoSOA=","RsfaIcSdxUsyxkHT0X7TBDvWAE\u002BekQ7N\u002BCKYPxhpGog=","mwxpKH5s5WhH59E7QrRxfDY8/18lLYY3KNsyd1uFNcs=","q4x5tw5ZdEVI3wN3ORljilxzvA3pVT3snlXNH\u002Bh3MZw=","hqxvnAagyda0d6U9WqiUEHNFsi/9qmMo9aedinq8ZQA=","TRUWKVmfrqI3Da5OlFteES1pBI/d\u002Bef88\u002BcbmmwH4mw=","AMewKZACPMF34A9M/c\u002BnjYkuddQPOVXhAdpXf\u002BloS2s=","jLjjNkMAcR6ICPvLUJeO8hvccCstVj2PSPKA7Fc04j0="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
Reference in New Issue
Block a user