From 9383a2fb09ffb60cfe63683106945bd688affa59 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Wed, 1 Jun 2022 21:13:43 +0200 Subject: feat: Initial commit after clean slate --- src/Controllers/OrdersController.cs | 339 ++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 src/Controllers/OrdersController.cs (limited to 'src/Controllers/OrdersController.cs') diff --git a/src/Controllers/OrdersController.cs b/src/Controllers/OrdersController.cs new file mode 100644 index 0000000..39c0818 --- /dev/null +++ b/src/Controllers/OrdersController.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using IOL.Helpers; +using IOL.VippsEcommerce; +using IOL.VippsEcommerce.Models.Api; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using VSH.Data; +using VSH.Data.Database; +using VSH.Data.Dtos; +using VSH.Data.Enums; +using VSH.Data.Results; +using VSH.Services; +using VSH.Utilities; + +namespace VSH.Controllers; + +public class OrdersController : MainControllerBase +{ + private readonly MainDbContext _context; + private readonly OrderService _orderService; + private readonly IVippsEcommerceService _vippsService; + private readonly EmailService _emailService; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + public OrdersController( + MainDbContext context, + IConfiguration configuration, + OrderService orderService, + IVippsEcommerceService vippsService, + EmailService emailService, + ILogger logger + ) { + _context = context; + _configuration = configuration; + _orderService = orderService; + _vippsService = vippsService; + _logger = logger; + _emailService = emailService; + } + + [HttpGet] + public ActionResult GetOrders(string filter = default) { + return filter switch { + "not-cancelled" => Ok(_context.Orders.Where(c => c.Status != OrderStatus.CANCELLED) + .OrderByDescending(c => c.Created)), + _ => Ok(_context.Orders.OrderByDescending(c => c.Created)) + }; + } + + [HttpPost("submit")] + public async Task SubmitOrder(Order payload) { + var validationResult = _orderService.ValidateOrder(payload); + if (!validationResult.IsValid) + return BadRequest(validationResult); + payload.SetBaseValues(); + var orderRef = RandomString.Generate(6); + var isOrderRefUsed = _context.Orders.Any(c => c.OrderReference == orderRef); + + while (isOrderRefUsed) { + orderRef = RandomString.Generate(6); + isOrderRefUsed = _context.Orders.Any(c => c.OrderReference == orderRef); + } + + payload.OrderReference = orderRef; + + foreach (var payloadProduct in payload.Products) { + var dbProduct = _context.Products.SingleOrDefault(c => c.Id == payloadProduct.Id); + if (dbProduct == default) { + throw new ApplicationException("Could not find product in order payload that passed validation"); + } + + payloadProduct.PriceAtTimeOfOrder = dbProduct.Price; + } + + switch (payload.PaymentType) { + case OrderPaymentType.VIPPS: { + payload.Id = Guid.NewGuid(); + payload.Status = OrderStatus.AWAITING_VIPPS; + await _context.Orders.AddAsync(payload); + await _context.SaveChangesAsync(); + var vippsUrl = await _orderService.GetVippsPaymentUrlAsync(payload, CurrentHost); + return Ok(vippsUrl); + } + case OrderPaymentType.INVOICE_BY_EMAIL: { + payload.Status = OrderStatus.AWAITING_INVOICE; + await _context.Orders.AddAsync(payload); + await _context.SaveChangesAsync(); + + await _emailService.SendEmailAsync("Ny ordre - VSH", + payload.GetAdminMailContent(CurrentHost), + _configuration.GetOrderStatusEmailRecipients()); + await _emailService.SendEmailAsync("Din ordre hos Vinjesvingen Handel AS", + payload.GetCustomerMailContent(CurrentHost), + payload.ContactInfo.EmailAddress); + return Ok("/status/" + payload.OrderReference + "?clearCart=true"); + } + default: { + return BadRequest("No PaymentType was specified"); + } + } + } + + [HttpGet("{orderId}/details")] + public ActionResult GetOrderDetails(Guid orderId) { + var order = _context.Orders.SingleOrDefault(c => c.Id == orderId); + if (order == default) { + return NotFound(); + } + + var products = new List(); + + foreach (var orderProduct in order.Products) { + var dbProduct = _context.Products.SingleOrDefault(c => c.Id == orderProduct.Id); + if (dbProduct == default) + continue; + products.Add(new AdminViewOrderDto.OrderDetailProduct { + Id = orderProduct.Id, + Count = orderProduct.NumberOfItems, + PayedPrice = orderProduct.PriceAtTimeOfOrder, + Name = dbProduct.Name + }); + } + + var x = new AdminViewOrderDto { + Comment = order.Comment, + Id = order.Id, + Products = products, + Status = order.Status, + ContactInformation = order.ContactInfo, + OrderDate = order.Created, + OrderReference = order.OrderReference, + PaymentType = order.PaymentType, + VippsId = order.VippsTransactionId, + VippsStatus = order.VippsStatus.ToString(), + VippsTransactionStatus = order.VippsTransactionStatus.ToString() + }; + + return x; + } + + + [HttpGet("{orderId}/capture")] + public async Task CaptureVippsPayment(Guid orderId) { + var order = _context.Orders.SingleOrDefault(c => c.Id == orderId); + if (order == default) + return NotFound(); + try { + var response = await _vippsService.CapturePaymentAsync(order.OrderReference, + new VippsPaymentActionRequest { + Transaction = new TTransaction { + TransactionText = + order.OrderReference + + "_" + + DateTime.UtcNow.ToString("O") + } + }); + if (response == default) + return StatusCode(500); + order.VippsStatus = response.TransactionInfo.Status; + await _context.SaveChangesAsync(); + return Ok(); + } catch (Exception e) { + Console.WriteLine(e); + e.Data.Add("orderId", orderId); + return StatusCode(500); + } + } + + [HttpGet("{orderId}/cancel")] + public async Task CancelVippsPayment(Guid orderId) { + var order = _context.Orders.SingleOrDefault(c => c.Id == orderId); + if (order == default) + return NotFound(); + try { + var response = await _vippsService.CancelPaymentAsync(order.OrderReference, + new VippsPaymentActionRequest { + Transaction = new TTransaction { + TransactionText = + order.OrderReference + + "_" + + DateTime.UtcNow.ToString("O") + } + }); + if (response == default) + return StatusCode(500); + order.VippsStatus = response.TransactionInfo.Status; + await _context.SaveChangesAsync(); + return Ok(); + } catch (Exception e) { + Console.WriteLine(e); + e.Data.Add("orderId", orderId); + return StatusCode(500); + } + } + + [HttpGet("{orderId}/refund")] + public async Task RefundVippsPayment(Guid orderId) { + var order = _context.Orders.SingleOrDefault(c => c.Id == orderId); + if (order == default) + return NotFound(); + try { + var response = await _vippsService.RefundPaymentAsync(order.OrderReference, + new VippsPaymentActionRequest { + Transaction = new TTransaction { + TransactionText = + order.OrderReference + + "_" + + DateTime.UtcNow.ToString("O") + } + }); + if (response == default) + return StatusCode(500); + order.VippsStatus = response.TransactionInfo.Status; + await _context.SaveChangesAsync(); + return Ok(); + } catch (Exception e) { + Console.WriteLine(e); + e.Data.Add("orderId", orderId); + return StatusCode(500); + } + } + + [HttpPost("validate-products")] + public ActionResult ValidateOrderProducts(Order payload) { + return _orderService.ValidateOrderProducts(payload); + } + + [HttpPost("validate")] + public ActionResult ValidateOrder(Order payload) { + return _orderService.ValidateOrder(payload); + } + + /// + /// This is used for all payment updates performed at vipps and can be called anytime from vipps + /// This endpoint updates the order status and save the body as an VippsResponses.InitiationResponse in db + /// + /// + /// + [HttpPost("vipps/callbacks-for-payment-update/v2/payments/{orderReference}")] + public async Task VippsPaymentUpdateCallback([FromRoute] string orderReference) { + var bodyContent = await new StreamReader(Request.Body).ReadToEndAsync(); + _logger.LogInformation("Recieved vipps payment update callback for order: " + orderReference); + _logger.LogDebug("With body: " + bodyContent); + + var order = _context.Orders.SingleOrDefault(c => c.OrderReference == orderReference); + if (order == default) { + _logger.LogWarning("Could not find order: " + orderReference + ", in database"); + return Ok(); + } + + try { + var response = JsonSerializer.Deserialize(bodyContent); + if (response == default) { + _logger.LogCritical("Could not deserialize vipps response into " + + nameof(VippsPaymentInitiationCallbackResponse)); + return Ok(); + } + + order.VippsTransactionId = response.TransactionInfo.TransactionId; + order.VippsTransactionStatus = response.TransactionInfo.StatusEnum(); + order.Status = order.VippsTransactionStatus switch { + ETransactionStatus.SALE or ETransactionStatus.RESERVED => OrderStatus.COMPLETED, + ETransactionStatus.REJECTED or + ETransactionStatus.CANCELLED or + ETransactionStatus.AUTO_CANCEL => OrderStatus.CANCELLED, + ETransactionStatus.SALE_FAILED or ETransactionStatus.RESERVE_FAILED => OrderStatus.FAILED, + _ => OrderStatus.FAILED + }; + await _context.SaveChangesAsync(); + + if (order.Status != OrderStatus.COMPLETED) { + _logger.LogWarning("OrderStatus is unsatisfactory for this stage of payment history, returning to cart page with error message"); + if (response.TransactionInfo != default) { + _logger.LogWarning("Vipps TransactionStatus is " + response.TransactionInfo?.Status); + } else { + _logger.LogWarning("Vipps TransactionInfo is empty"); + } + } + + return Ok(); + } catch (Exception e) { + Console.WriteLine(e); + e.Data.Add("orderid", order.Id); + _logger.LogCritical(e, "WHOOOOOOOOOOOPS"); + return Ok(); + } + } + + /// + /// This endpoint is specified in the intiation request to vipps, regardless of the result of a capture in vipps, vipps will send customers to this endpoint. + /// This endpoint decides where a customer is sent based on the result of a vipps payment request + /// + /// + /// + [HttpGet("vipps/payment-callback/{orderReference}")] + public async Task VippsPaymentCallback([FromRoute] string orderReference) { + _logger.LogInformation("Recieved vipps payment callback for order: " + orderReference); + + var order = _context.Orders.SingleOrDefault(c => c.OrderReference == orderReference); + if (order == default) { + _logger.LogWarning("Could not find order: " + orderReference + ", in database"); + return Redirect("/handlekorg?error=failed"); + } + + try { + if (order.Status == OrderStatus.CANCELLED) { + return Redirect("/handlekorg?error=cancelled"); + } + + if (order.Status != OrderStatus.COMPLETED) { + _logger.LogWarning("OrderStatus is unsatisfactory for this stage of payment history, returning to cart page with error message"); + + return Redirect("/handlekorg?error=failed"); + } + + await _emailService.SendEmailAsync("Ny ordre - VSH", + order.GetAdminMailContent(CurrentHost), + _configuration.GetOrderStatusEmailRecipients()); + + await _emailService.SendEmailAsync("Din ordre hos Vinjesvingen Handel AS", + order.GetCustomerMailContent(CurrentHost), + order.ContactInfo.EmailAddress); + + return Redirect("/status/" + orderReference + "?clearCart=true"); + } catch (Exception e) { + Console.WriteLine(e); + e.Data.Add("orderid", order.Id); + _logger.LogCritical(e, "WHOOOOOOOOOOOPS"); + return Redirect("/handlekorg?error=failed"); + } + } +} -- cgit v1.3