Skip to content

Commit

Permalink
Merge pull request #107 from commercetools/CheckoutApp-Example
Browse files Browse the repository at this point in the history
Checkout App Example
  • Loading branch information
MicheleRezk authored Jan 14, 2022
2 parents 4133235 + 43c4aa8 commit 95e7e66
Show file tree
Hide file tree
Showing 83 changed files with 41,248 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ bld/

# Visual Studio 2015 cache/options directory
.vs/
.vscode/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ The SDK consists of the following projects:
In addition, the SDK has the following directories:
* `/IntegrationTests`: Integration tests for the SDK. A good way for anyone using the .NET SDK to understand it further.
* `/Tests`: Unit Tests for serialization and request builders.
* `/Examples`: Contains `commercetools.Api.ConsoleApp` as an example of how to use the SDK in a console app and create a client using ClientFactory.

* `/Examples`:
1. `commercetools.Api.ConsoleApp` as an example of how to use the SDK in a console app and create a client using ClientFactory.
2. `commercetools.Api.CheckoutApp`, it's developed
to illustrate how to deal with the me endpoints, like listing and adding products
to cart as anonymous user, user can login and switch to password token flow,
it's storing the token in the cookie right now, cookies only used
as POC, it maybe not the best option for production purposes.

## Getting Started with the .NET SDK

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Threading.Tasks;
using commercetools.Api.CheckoutApp.Models;
using commercetools.Api.CheckoutApp.Services;
using commercetools.Base.Client;
using commercetools.Base.Client.Tokens;
using Microsoft.AspNetCore.Mvc;

namespace commercetools.Api.CheckoutApp.Controllers
{
public class BaseController : Controller
{
protected readonly IClient _client;
protected readonly IUserCredentialsStoreManager _userCredentialsStore;
protected readonly MeServices _meServices;
protected readonly CartServices _cartServices;

public BaseController(IClient client,
IUserCredentialsStoreManager userCredentialsStore,
MeServices meServices,
CartServices cartServices)
{
this._client = client;
this._userCredentialsStore = userCredentialsStore;
this._meServices = meServices;
this._cartServices = cartServices;
}

public async Task<CustomerProfileViewModel> GetCurrentCustomerProfile()
{
var profileViewModel = new CustomerProfileViewModel();
var meProfile = await _meServices.GetMyProfile();
profileViewModel.ActiveCart = await _cartServices.GetActiveCartViewModel();
if (meProfile != null)
{
var baseInfo = new BaseCustomer(meProfile);
profileViewModel.Customer = baseInfo;
}

return profileViewModel;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Threading.Tasks;
using commercetools.Api.CheckoutApp.Models;
using commercetools.Api.CheckoutApp.Services;
using commercetools.Api.Models.Me;
using commercetools.Api.Models.Products;
using commercetools.Base.Client;
using commercetools.Base.Client.Tokens;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;

namespace commercetools.Api.CheckoutApp.Controllers
{
public class HomeController : BaseController
{
private readonly ProductServices _productServices;
public HomeController(IClient client,
IUserCredentialsStoreManager userCredentialsStore,
MeServices meServices,
CartServices cartServices,ProductServices productServices)
: base(client, userCredentialsStore, meServices, cartServices)
{
this._productServices = productServices;
}

public async Task<IActionResult> Index()
{
var products = await _productServices.GetAllProducts();
var customerProfile = await GetCurrentCustomerProfile();
var homeModel = new HomeViewModel(products, customerProfile);
return View(homeModel);
}

public async Task<IActionResult> AddToCart(Product product)
{
var cartDraft = GetCartDraft();
var cart = await _cartServices.CreateCartForCurrentCustomer(cartDraft);
await _cartServices.AddProductToCurrentActiveCart(cart, product);
return RedirectToAction("Index");
}

private IMyCartDraft GetCartDraft()
{
var cartDraft = new MyCartDraft
{
Currency = "EUR",
};
return cartDraft;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Threading.Tasks;
using commercetools.Api.CheckoutApp.Models;
using commercetools.Api.CheckoutApp.Services;
using commercetools.Base.Client;
using commercetools.Base.Client.Tokens;
using Microsoft.AspNetCore.Mvc;

namespace commercetools.Api.CheckoutApp.Controllers
{
public class MeController : BaseController
{
public MeController(IClient client,
MeServices meServices,
CartServices cartServices,
IUserCredentialsStoreManager userCredentialsStoreManager)
: base(client, userCredentialsStoreManager, meServices, cartServices)
{
}

public async Task<IActionResult> SignIn()
{
var customerProfile = await GetCurrentCustomerProfile();
return View(new SignInCustomerViewModel(customerProfile));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SignIn(SignInCustomerViewModel model)
{
if (ModelState.IsValid)
{
var signInResult = await _meServices.SignIn(model.Email, model.Password);
if (signInResult.IsValidCredentials)
{
//after signin, the anonymous access token and refresh token are immediately invalid
//we need to get new access and refresh tokens with the password flow
_userCredentialsStore.StoreUserCredentialsAndClearToken(model.Email, model.Password);
return RedirectToAction("Index", "MyCart");
}

else
{
model.ResetAfterLoginFailed();
}
}
return View("SignIn", model);
}

public async Task<IActionResult> Logout()
{
_userCredentialsStore.ClearCredentialsAndToken();
return RedirectToAction("Index", "Home");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Threading.Tasks;
using commercetools.Api.CheckoutApp.Services;
using commercetools.Base.Client;
using commercetools.Base.Client.Tokens;
using Microsoft.AspNetCore.Mvc;

namespace commercetools.Api.CheckoutApp.Controllers
{
public class MyCartController : BaseController
{
public MyCartController(IClient client,
CartServices cartServices,
MeServices meServices,
IUserCredentialsStoreManager userCredentialsStoreManager)
: base(client, userCredentialsStoreManager, meServices, cartServices)
{
}

public async Task<IActionResult> Index()
{
var customerProfile = await GetCurrentCustomerProfile();
return View(customerProfile);
}

public async Task<IActionResult> Delete(string lineItemId)
{
var myCart = await _cartServices.DeleteLineItem(lineItemId);
return RedirectToAction("Index");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.Net.Http;
using commercetools.Api.Models.Errors;
using commercetools.Base.Client;
using commercetools.Base.Client.Tokens;
using commercetools.Sdk.Api;
using commercetools.Sdk.Api.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace commercetools.Api.CheckoutApp.Extensions
{
public static class ClientInjectionSetup
{
/// <summary>
/// Inject Scoped Client per each Request
/// </summary>
/// <param name="services">services container</param>
/// <param name="configuration"></param>
/// <param name="clientName"></param>
public static IHttpClientBuilder UseCommercetoolsScopedClient(this IServiceCollection services,
IConfiguration configuration,
string clientName)
{
services.UseCommercetoolsApiSerialization();
services.UseScopedCredentialsStoreManagers(clientName, configuration);
services.UseScopedTokenProvider(clientName, configuration);

//Create Client with proper TokenProvider as scoped service
services.AddScoped(serviceProvider =>
{
var clientConfiguration = configuration.GetSection(clientName).Get<ClientConfiguration>();
var tokenProvider = serviceProvider.GetService<ITokenProvider>();
var client = ClientFactory.Create(clientName, clientConfiguration,
serviceProvider.GetService<IHttpClientFactory>(),
serviceProvider.GetService<SerializerService>(),
tokenProvider);
client.Name = clientName;
return client;
});

//Add the httpClient and setup the Middlewares
return services.SetupClient(clientName,
errorTypeMapper => typeof(ErrorResponse),
serviceProvider => serviceProvider.GetService<SerializerService>());
}


/// <summary>
/// Inject two instance, one for IAnonymousCredentialsStoreManager and other for IUserCredentialsStoreManager
/// </summary>
/// <param name="services"></param>
/// <param name="clientName"></param>
/// <param name="configuration"></param>
public static void UseScopedCredentialsStoreManagers(this IServiceCollection services,
string clientName,
IConfiguration configuration)
{
services.AddScoped<IAnonymousCredentialsStoreManager>(serviceProvider =>
{
var inCookiesStoreManager = serviceProvider.GetService<InCookiesStoreManager>();
var inSessionStoreManager = serviceProvider.GetService<InSessionStoreManager>();
var anonymousIdStore = new InSessionAnonymousCredentialsStoreManager(inSessionStoreManager,inCookiesStoreManager);
return anonymousIdStore;
});
services.AddScoped<IUserCredentialsStoreManager>(serviceProvider =>
{
var inCookiesStoreManager = serviceProvider.GetService<InCookiesStoreManager>();
var inSessionStoreManager = serviceProvider.GetService<InSessionStoreManager>();
var userCredentialsStore = new InSessionUserCredentialsStoreManager(inSessionStoreManager,inCookiesStoreManager);
return userCredentialsStore;
});
}

/// <summary>
/// Inject scoped tokenProvider based on current tokenFlow
/// </summary>
/// <param name="services"></param>
/// <param name="clientName"></param>
/// <param name="configuration"></param>
public static void UseScopedTokenProvider(this IServiceCollection services,
string clientName,
IConfiguration configuration)
{
services.AddScoped(serviceProvider =>
{
var clientConfiguration = configuration.GetSection(clientName).Get<ClientConfiguration>();
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
var currentTokenFlow = GetCurrentTokenFlow(serviceProvider);
ITokenProvider tokenProvider = null;
if (currentTokenFlow == TokenFlow.Password)
{
var userCredentialsStore = serviceProvider.GetService<IUserCredentialsStoreManager>();
tokenProvider = TokenProviderFactory.CreatePasswordTokenProvider(
clientConfiguration, httpClientFactory, userCredentialsStore);
}
else
{
var anonymousIdStore = serviceProvider.GetService<IAnonymousCredentialsStoreManager>();
if (anonymousIdStore != null && anonymousIdStore.Token == null)
{
anonymousIdStore.AnonymousId = Guid.NewGuid().ToString();
}
tokenProvider = TokenProviderFactory.CreateAnonymousSessionTokenProvider(
clientConfiguration, httpClientFactory, anonymousIdStore);
}
return tokenProvider;
});
}

public static TokenFlow GetCurrentTokenFlow(IServiceProvider serviceProvider)
{
var tokenFlow = TokenFlow.AnonymousSession;
var userCredentialsStore = serviceProvider.GetService<IUserCredentialsStoreManager>();
if (userCredentialsStore != null && !string.IsNullOrEmpty(userCredentialsStore.Username))
{
tokenFlow = TokenFlow.Password;
}
return tokenFlow;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using commercetools.Api.CheckoutApp.Services;
using Microsoft.AspNetCore.Http;

namespace commercetools.Api.CheckoutApp.Extensions
{
public class InCookiesStoreManager
{
private readonly IHttpContextAccessor _httpContextAccessor;

public InCookiesStoreManager(IHttpContextAccessor httpContextAccessor)
{
this._httpContextAccessor = httpContextAccessor;
}

public void SetInCookie(string key,string value)
{
var response = _httpContextAccessor?.HttpContext?.Response;
response?.Cookies.Append(key, value,
new CookieOptions()
{
Path = "/",
Secure = true,
HttpOnly = true,
Expires = DateTimeOffset.Now.AddDays(Settings.ExpireInDays)
});
}
public string GetFromCookie(string key)
{
var request = _httpContextAccessor?.HttpContext?.Request;
return request?.Cookies[key];
}
public void ClearCookie(string key)
{
var response = _httpContextAccessor?.HttpContext?.Response;
response?.Cookies.Delete(key);
}
}
}
Loading

0 comments on commit 95e7e66

Please sign in to comment.