Browse Source

Update to add a the ability to apply a coupon code for demo purposes

pull/1657/head
Chris Witte 3 years ago
parent
commit
288a90566b
16 changed files with 148 additions and 69 deletions
  1. +3
    -2
      src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/BasketController.cs
  2. +1
    -0
      src/ApiGateways/Web.Bff.Shopping/aggregator/Models/BasketDataItem.cs
  3. +2
    -0
      src/ApiGateways/Web.Bff.Shopping/aggregator/Models/UpdateBasketRequestItemData.cs
  4. +4
    -2
      src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs
  5. +4
    -2
      src/Services/Basket/Basket.API/Grpc/BasketService.cs
  6. +1
    -0
      src/Services/Basket/Basket.API/Model/BasketItem.cs
  7. +1
    -0
      src/Services/Basket/Basket.API/Proto/basket.proto
  8. +9
    -5
      src/Services/Basket/Basket.UnitTests/Application/CartControllerTest.cs
  9. +16
    -5
      src/Web/WebMVC/Controllers/CartController.cs
  10. +3
    -17
      src/Web/WebMVC/Services/BasketService.cs
  11. +63
    -16
      src/Web/WebMVC/Services/CouponService.cs
  12. +1
    -1
      src/Web/WebMVC/Services/ICouponService.cs
  13. +3
    -0
      src/Web/WebMVC/Startup.cs
  14. +1
    -0
      src/Web/WebMVC/ViewModels/BasketItem.cs
  15. +33
    -18
      src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml
  16. +3
    -1
      src/Web/WebSPA/Client/modules/basket/basket.component.html

+ 3
- 2
src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/BasketController.cs View File

@ -66,8 +66,9 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers
ProductId = catalogItem.Id,
ProductName = catalogItem.Name,
PictureUrl = catalogItem.PictureUri,
UnitPrice = catalogItem.Price,
Quantity = bitem.Quantity
UnitPrice = itemInBasket.UnitPrice,
Quantity = bitem.Quantity,
isDiscounted = false
});
}
else


+ 1
- 0
src/ApiGateways/Web.Bff.Shopping/aggregator/Models/BasketDataItem.cs View File

@ -16,6 +16,7 @@
public int Quantity { get; set; }
public string PictureUrl { get; set; }
public bool isDiscounted { get; internal set; }
}
}

+ 2
- 0
src/ApiGateways/Web.Bff.Shopping/aggregator/Models/UpdateBasketRequestItemData.cs View File

@ -7,5 +7,7 @@
public int ProductId { get; set; } // Catalog item id
public int Quantity { get; set; } // Quantity
public bool isDiscounted { get; set; } // Discount applied
}
}

+ 4
- 2
src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs View File

@ -60,7 +60,8 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
ProductId = item.Productid,
ProductName = item.Productname,
Quantity = item.Quantity,
UnitPrice = (decimal)item.Unitprice
UnitPrice = (decimal)item.Unitprice,
isDiscounted = item.Isdiscounted
});
}
});
@ -92,7 +93,8 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
Productid = item.ProductId,
Productname = item.ProductName,
Quantity = item.Quantity,
Unitprice = (double)item.UnitPrice
Unitprice = (double)item.UnitPrice,
Isdiscounted = item.isDiscounted
});
}
});


+ 4
- 2
src/Services/Basket/Basket.API/Grpc/BasketService.cs View File

@ -72,7 +72,8 @@ namespace GrpcBasket
Productid = item.ProductId,
Productname = item.ProductName,
Quantity = item.Quantity,
Unitprice = (double)item.UnitPrice
Unitprice = (double)item.UnitPrice,
Isdiscounted = item.isDiscounted
}));
return response;
@ -93,7 +94,8 @@ namespace GrpcBasket
ProductId = item.Productid,
ProductName = item.Productname,
Quantity = item.Quantity,
UnitPrice = (decimal)item.Unitprice
UnitPrice = (decimal)item.Unitprice,
isDiscounted = item.Isdiscounted
}));
return response;


+ 1
- 0
src/Services/Basket/Basket.API/Model/BasketItem.cs View File

@ -12,6 +12,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
public decimal OldUnitPrice { get; set; }
public int Quantity { get; set; }
public string PictureUrl { get; set; }
public bool isDiscounted { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();


+ 1
- 0
src/Services/Basket/Basket.API/Proto/basket.proto View File

@ -31,4 +31,5 @@ message BasketItemResponse {
double oldunitprice = 5;
int32 quantity = 6;
string pictureurl = 7;
bool isdiscounted = 8;
}

+ 9
- 5
src/Services/Basket/Basket.UnitTests/Application/CartControllerTest.cs View File

@ -17,6 +17,7 @@ namespace UnitTest.Basket.Application
private readonly Mock<ICatalogService> _catalogServiceMock;
private readonly Mock<IBasketService> _basketServiceMock;
private readonly Mock<IIdentityParser<ApplicationUser>> _identityParserMock;
private readonly Mock<ICouponService> _couponServiceMock;
private readonly Mock<HttpContext> _contextMock;
public CartControllerTest()
@ -24,6 +25,7 @@ namespace UnitTest.Basket.Application
_catalogServiceMock = new Mock<ICatalogService>();
_basketServiceMock = new Mock<IBasketService>();
_identityParserMock = new Mock<IIdentityParser<ApplicationUser>>();
_couponServiceMock = new Mock<ICouponService>;
_contextMock = new Mock<HttpContext>();
}
@ -33,6 +35,7 @@ namespace UnitTest.Basket.Application
//Arrange
var fakeBuyerId = "1";
var action = string.Empty;
var couponCode = string.Empty;
var fakeBasket = GetFakeBasket(fakeBuyerId);
var fakeQuantities = new Dictionary<string, int>()
{
@ -47,9 +50,9 @@ namespace UnitTest.Basket.Application
.Returns(Task.FromResult(fakeBasket));
//Act
var cartController = new CartController(_basketServiceMock.Object, _catalogServiceMock.Object, _identityParserMock.Object);
var cartController = new CartController(_basketServiceMock.Object, _catalogServiceMock.Object, _identityParserMock.Object, _couponServiceMock.Object);
cartController.ControllerContext.HttpContext = _contextMock.Object;
var actionResult = await cartController.Index(fakeQuantities, action);
var actionResult = await cartController.Index(fakeQuantities, couponCode, action);
//Assert
var viewResult = Assert.IsType<ViewResult>(actionResult);
@ -61,6 +64,7 @@ namespace UnitTest.Basket.Application
//Arrange
var fakeBuyerId = "1";
var action = "[ Checkout ]";
var couponCode = string.Empty;
var fakeBasket = GetFakeBasket(fakeBuyerId);
var fakeQuantities = new Dictionary<string, int>()
{
@ -75,9 +79,9 @@ namespace UnitTest.Basket.Application
.Returns(Task.FromResult(fakeBasket));
//Act
var orderController = new CartController(_basketServiceMock.Object, _catalogServiceMock.Object, _identityParserMock.Object);
var orderController = new CartController(_basketServiceMock.Object, _catalogServiceMock.Object, _identityParserMock.Object, _couponServiceMock.Object);
orderController.ControllerContext.HttpContext = _contextMock.Object;
var actionResult = await orderController.Index(fakeQuantities, action);
var actionResult = await orderController.Index(fakeQuantities, couponCode, action);
//Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(actionResult);
@ -95,7 +99,7 @@ namespace UnitTest.Basket.Application
.Returns(Task.FromResult(1));
//Act
var orderController = new CartController(_basketServiceMock.Object, _catalogServiceMock.Object, _identityParserMock.Object);
var orderController = new CartController(_basketServiceMock.Object, _catalogServiceMock.Object, _identityParserMock.Object, _couponServiceMock.Object);
orderController.ControllerContext.HttpContext = _contextMock.Object;
var actionResult = await orderController.AddToCart(fakeCatalogItem);


+ 16
- 5
src/Web/WebMVC/Controllers/CartController.cs View File

@ -14,13 +14,15 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{
private readonly IBasketService _basketSvc;
private readonly ICatalogService _catalogSvc;
private readonly ICouponService _couponSvc;
private readonly IIdentityParser<ApplicationUser> _appUserParser;
public CartController(IBasketService basketSvc, ICatalogService catalogSvc, IIdentityParser<ApplicationUser> appUserParser)
public CartController(IBasketService basketSvc, ICatalogService catalogSvc, IIdentityParser<ApplicationUser> appUserParser, ICouponService couponService)
{
_basketSvc = basketSvc;
_catalogSvc = catalogSvc;
_appUserParser = appUserParser;
_couponSvc = couponService;
}
public async Task<IActionResult> Index()
@ -40,14 +42,24 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
return View();
}
[HttpPost]
public async Task<IActionResult> Index(Dictionary<string, int> quantities, string action)
public async Task<IActionResult> Index(Dictionary<string, int> quantities, string couponCode, string action)
{
try
{
var user = _appUserParser.Parse(HttpContext.User);
var basket = await _basketSvc.SetQuantities(user, quantities);
var vm = await _basketSvc.GetBasket(user);
if (action == "[ Submit Coupon ]")
{
var updatedBasket = await _couponSvc.Apply(vm, couponCode);
await _basketSvc.ApplyCoupon(updatedBasket);
}
if (action == "[ Update ]")
{
var basket = await _basketSvc.SetQuantities(user, quantities);
}
if (action == "[ Checkout ]")
{
return RedirectToAction("Create", "Order");
@ -60,7 +72,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
return View();
}
public async Task<IActionResult> AddToCart(CatalogItem productDetails)
{
try


+ 3
- 17
src/Web/WebMVC/Services/BasketService.cs View File

@ -57,22 +57,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{
var uri = API.Basket.UpdateBasket(_basketByPassUrl);
var basketUpdate = new
{
BuyerId = basket.BuyerId,
Items = basket.Items.Select(item => new
{
Id = item.Id,
ProductId = item.ProductId,
ProductName = item.ProductName,
UnitPrice = item.UnitPrice * (decimal)0.10,
OldUnitPrice = item.UnitPrice,
Quantity = item.Quantity,
PictureUrl = item.PictureUrl
}).ToArray()
};
var basketContent = new StringContent(JsonConvert.SerializeObject(basketUpdate), System.Text.Encoding.UTF8, "application/json");
var basketContent = new StringContent(JsonConvert.SerializeObject(basket), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(uri, basketContent);
@ -137,7 +122,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{
CatalogItemId = productId,
BasketId = user.Id,
Quantity = 1
Quantity = 1,
isDiscounted = false
};
var basketContent = new StringContent(JsonConvert.SerializeObject(newItem), System.Text.Encoding.UTF8, "application/json");


+ 63
- 16
src/Web/WebMVC/Services/CouponService.cs View File

@ -5,6 +5,7 @@ using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using WebMVC.Infrastructure;
@ -14,23 +15,69 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{
public class CouponService : ICouponService
{
public Basket Apply(Basket basket)
private readonly IOptions<AppSettings> _settings;
private readonly HttpClient _apiClient;
private readonly ILogger<CouponService> _logger;
public CouponService(HttpClient httpClient, IOptions<AppSettings> settings, ILogger<CouponService> logger)
{
_apiClient = httpClient;
_settings = settings;
_logger = logger;
}
public async Task<Basket> Apply(Basket basket, string couponCode)
{
//Basket updatedBasket = new Basket();
//updatedBasket.BuyerId = basket.BuyerId;
//// Todo: Stub for now, should reach out to a coupon microservice
//if (basket.CouponCode == "SM360")
//{
// foreach (BasketItem item in basket.Items)
// {
// item.UnitPrice = item.UnitPrice - (item.UnitPrice * (decimal)0.1);
// }
//}
//else
//{
throw new System.NotImplementedException();
//}
//return basket;
decimal discount = 0;
List<BasketItem> Items = new List<BasketItem>();
// Todo: Stub for now, should reach out to a coupon microservice
// Coupon for smart hotel 360 provides a 10% discount
if (couponCode == "SH360")
{
foreach (BasketItem Item in basket.Items.Select(item => item).ToList())
{
if (Item.isDiscounted == false)
{
Items.Add(
new BasketItem
{
Id = Item.Id,
ProductId = Item.ProductId,
ProductName = Item.ProductName,
UnitPrice = Item.UnitPrice * (decimal)(1 - 0.1),
OldUnitPrice = Item.UnitPrice,
Quantity = Item.Quantity,
PictureUrl = Item.PictureUrl,
isDiscounted = true
});
}
}
}
else
{
foreach (BasketItem Item in basket.Items.Select(item => item).ToList())
{
Items.Add(
new BasketItem
{
Id = Item.Id,
ProductId = Item.ProductId,
ProductName = Item.ProductName,
UnitPrice = Item.UnitPrice,
OldUnitPrice = Item.OldUnitPrice,
Quantity = Item.Quantity,
PictureUrl = Item.PictureUrl,
isDiscounted = Item.isDiscounted
});
}
}
Basket basketUpdate = new Basket
{
BuyerId = basket.BuyerId,
Items = Items
};
return basketUpdate;
}
}
}

+ 1
- 1
src/Web/WebMVC/Services/ICouponService.cs View File

@ -8,6 +8,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{
public interface ICouponService
{
Basket Apply(Basket basket);
Task<Basket> Apply(Basket basket, string couponCode);
}
}

+ 3
- 0
src/Web/WebMVC/Startup.cs View File

@ -160,6 +160,9 @@ namespace Microsoft.eShopOnContainers.WebMVC
services.AddHttpClient<ICatalogService, CatalogService>()
.AddDevspacesSupport();
services.AddHttpClient<ICouponService, CouponService>()
.AddDevspacesSupport();
services.AddHttpClient<IOrderingService, OrderingService>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddHttpMessageHandler<HttpClientRequestIdDelegatingHandler>()


+ 1
- 0
src/Web/WebMVC/ViewModels/BasketItem.cs View File

@ -7,6 +7,7 @@
public string ProductName { get; init; }
public decimal UnitPrice { get; init; }
public decimal OldUnitPrice { get; init; }
public bool isDiscounted { get; set; }
public int Quantity { get; init; }
public string PictureUrl { get; init; }
}


+ 33
- 18
src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml View File

@ -34,23 +34,27 @@
{
var item = Model.Items[i];
<article class="esh-basket-items row">
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
<img class="esh-basket-image" src="@item.PictureUrl" />
</section>
<section class="esh-basket-item esh-basket-item--middle col-3">@item.ProductName</section>
<section class="esh-basket-item esh-basket-item--middle col-2">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-basket-item esh-basket-item--middle col-2">
<input type="hidden" name="@("quantities[" + i +"].Key")" value="@item.Id" />
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + i +"].Value")" value="@item.Quantity" />
</section>
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-2">$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString("N2")</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
<img class="esh-basket-image" src="@item.PictureUrl" />
</section>
<section class="esh-basket-item esh-basket-item--middle col-3">@item.ProductName</section>
<section class="esh-basket-item esh-basket-item--middle col-2">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-basket-item esh-basket-item--middle col-2">
<input type="hidden" name="@("quantities[" + i +"].Key")" value="@item.Id" />
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + i +"].Value")" value="@item.Quantity" />
</section>
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-2">$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString("N2")</section>
</article>
<div class="esh-basket-items--border row">
@if (item.OldUnitPrice != 0)
@if (item.OldUnitPrice != 0 && item.isDiscounted == false)
{
<div class="alert alert-warning esh-basket-margin12" role="alert">&nbsp;Coupon code was not valid.</div>
}
@if (item.OldUnitPrice != 0 && item.isDiscounted == true)
{
<div class="alert alert-warning esh-basket-margin12" role="alert">&nbsp;Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was $ @item.OldUnitPrice </div>
<div class="alert alert-warning esh-basket-margin12" role="alert">&nbsp;Coupon Applied Original Price $ @item.OldUnitPrice; New Price @item.UnitPrice </div>
}
</div>
<br />
@ -68,9 +72,20 @@
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-7"></section>
<section class="esh-basket-item col-7">
<section class="esh-basket-item col-1">
<input type="text"
class="form-input form-input-medium"
value="Coupon Code" name="couponCode" />
<input type="submit"
class="btn esh-basket-checkout"
value="[ Submit Coupon ]" name="action" />
</section>
</section>
<section class="esh-basket-item col-2">
<button class="btn esh-basket-checkout" name="name" value="" type="submit">[ Update ]</button>
<input type="submit"
class="btn esh-basket-checkout"
value="[ Update ]" name="action" />
</section>
<section class="esh-basket-item col-3">
<input type="submit"
@ -80,7 +95,7 @@
</article>
</div>
}
</div>

+ 3
- 1
src/Web/WebSPA/Client/modules/basket/basket.component.html View File

@ -36,7 +36,9 @@
</article>
<br/>
<div class="esh-basket-items-margin-left1 row">
<div class="alert alert-warning" role="alert" *ngIf="item.oldUnitPrice > 0">&nbsp;Note that the price of this article changed in our Catalog. The old price when you originally added it to the basket was $ {{item.oldUnitPrice}} </div>
<div class="alert alert-warning" role="alert" *ngIf="item.oldUnitPrice > 0 && item.isDiscounted == false">&nbsp;Note that the application is jacked... added it to the basket was $ {{item.oldUnitPrice}} </div>
<div class="alert alert-warning" role="alert" *ngIf="item.oldUnitPrice > 0 && item.isDiscounted == true">&nbsp;Coupon was applied. New price: $ {{item.UnitPrice}} Old Price: $ {{item.oldUnitPrice}} </div>
</div>
</div>
</div>


Loading…
Cancel
Save