Skip to content

Commit 575db9d

Browse files
authored
Merge pull request #36 from umbraco/feature/portal-integration
Portal integration updates
2 parents f07d8a2 + 110704d commit 575db9d

File tree

9 files changed

+162
-24
lines changed

9 files changed

+162
-24
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Umbraco.Commerce.Checkout.Models;
4+
using Umbraco.Commerce.Core.Models;
5+
6+
namespace Umbraco.Commerce.Checkout.Extensions;
7+
8+
public static class CustomerExtensions
9+
{
10+
public static Customer GetCustomer(this OrderReadOnly order, OrderPropertyConfig orderPropertyConfig) =>
11+
new Customer(
12+
order.CustomerInfo.FirstName,
13+
order.CustomerInfo.LastName,
14+
order.CustomerInfo.Email,
15+
order.Properties[orderPropertyConfig.Customer.Telephone.Alias]);
16+
17+
public static CustomerAddress GetBillingAddress(this OrderReadOnly order, OrderPropertyConfig orderPropertyConfig)
18+
{
19+
// This extension method should not be used in a context where order is null.
20+
ArgumentNullException.ThrowIfNull(order);
21+
22+
return new CustomerAddress(
23+
order.Properties[orderPropertyConfig.Billing.AddressLine1.Alias],
24+
order.Properties[orderPropertyConfig.Billing.AddressLine2.Alias],
25+
order.Properties[orderPropertyConfig.Billing.City.Alias],
26+
order.Properties[orderPropertyConfig.Billing.ZipCode.Alias]);
27+
}
28+
29+
public static CustomerAddress GetShippingAddress(this OrderReadOnly order, OrderPropertyConfig orderPropertyConfig)
30+
{
31+
// This extension method should not be used in a context where order is null.
32+
ArgumentNullException.ThrowIfNull(order);
33+
34+
return order.Properties["shippingSameAsBilling"] == "1"
35+
? order.GetBillingAddress(orderPropertyConfig)
36+
: new CustomerAddress(
37+
order.Properties[orderPropertyConfig.Shipping.AddressLine1.Alias],
38+
order.Properties[orderPropertyConfig.Shipping.AddressLine2.Alias],
39+
order.Properties[orderPropertyConfig.Shipping.City.Alias],
40+
order.Properties[orderPropertyConfig.Shipping.ZipCode.Alias]);
41+
}
42+
43+
/// <summary>
44+
/// Returns the current string value if not null or whitespace, otherwise returns the fallback value or an empty string.
45+
/// </summary>
46+
/// <param name="value">The original string value.</param>
47+
/// <param name="fallbackValue">The string value for fallback.</param>
48+
/// <returns></returns>
49+
public static string ValueOrFallback(this string value, string fallbackValue) =>
50+
string.IsNullOrWhiteSpace(value)
51+
? string.IsNullOrWhiteSpace(fallbackValue)
52+
? string.Empty
53+
: fallbackValue
54+
: value;
55+
56+
/// <summary>
57+
/// Returns the property value if exists, otherwise returns the fallback value or an empty string.
58+
/// </summary>
59+
/// <param name="properties">The properties dictionary.</param>
60+
/// <param name="key">The looked up record key.</param>
61+
/// <param name="fallbackValue">The string value for fallback</param>
62+
/// <returns></returns>
63+
public static string ValueOrFallback(this IReadOnlyDictionary<string, PropertyValue> properties, string key, string fallbackValue)
64+
{
65+
if (properties is null || !properties.ContainsKey(key))
66+
{
67+
return string.IsNullOrWhiteSpace(fallbackValue) ? string.Empty : fallbackValue;
68+
}
69+
70+
var propertyRecord = properties[key];
71+
if (propertyRecord is null)
72+
{
73+
return string.IsNullOrWhiteSpace(fallbackValue) ? string.Empty : fallbackValue;
74+
}
75+
76+
return propertyRecord.Value.ValueOrFallback(fallbackValue);
77+
}
78+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace Umbraco.Commerce.Checkout.Models;
2+
3+
public record Customer(string FirstName, string LastName, string Email, string Telephone);
4+
5+
public record CustomerAddress(string AddressLine1, string AddressLine2, string City, string ZipCode);

src/Umbraco.Commerce.Checkout/Pipeline/Tasks/CreateUmbracoCommerceCheckoutDocumentTypesTask.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,20 @@ public override async Task<PipelineResult<InstallPipelineContext>> ExecuteAsync(
194194
x.Name = "Hide from Navigation";
195195
x.Description = "Hide the checkout page from the sites main navigation.";
196196
x.SortOrder = 90;
197+
}),
198+
CreatePropertyType(await booleanDataType.Value, x =>
199+
{
200+
x.Alias = "uccRequireLogin";
201+
x.Name = "Require Login";
202+
x.Description = "Customer is required to login before proceeding with the checkout.";
203+
x.SortOrder = 100;
204+
}),
205+
CreatePropertyType(await contentPickerDataType.Value, x =>
206+
{
207+
x.Alias = "uccLoginPage";
208+
x.Name = "Login Page";
209+
x.Description = "The page on the site containing the customer portal login.";
210+
x.SortOrder = 110;
197211
})
198212
];
199213

src/Umbraco.Commerce.Checkout/Views/UmbracoCommerceCheckout/UmbracoCommerceCheckoutInformationPage.cshtml

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
@using System.Text.Json
2+
@using Umbraco.Commerce.Checkout.Extensions
23
@inherits UmbracoViewPage
34
@{
45
Layout = "UmbracoCommerceCheckoutLayout.cshtml";
56

67
var store = Model.GetStore();
78

89
var currentOrder = await UmbracoCommerceApi.Instance.GetCurrentOrderAsync(store.Id);
10+
var orderPropertyConfig = await UmbracoCommerceApi.Instance.GetOrderPropertyConfigAsync(store.Alias);
11+
12+
var customer = currentOrder.GetCustomer(orderPropertyConfig);
13+
var billingAddress = currentOrder.GetBillingAddress(orderPropertyConfig);
914

1015
var countries = await UmbracoCommerceApi.Instance.GetCountriesAsync(store.Id);
1116

@@ -43,7 +48,7 @@
4348

4449
<h3 class="text-xl font-medium mb-4">@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.ContactInformation", "Contact Information")</h3>
4550
<input name="email" type="email" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.Email", "Email")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 w-full"
46-
value="@(currentOrder.CustomerInfo.Email)" required />
51+
value="@(currentOrder.CustomerInfo.Email.ValueOrFallback(customer.Email))" required />
4752
<label class="flex items-center mb-2 cursor-pointer">
4853
<input name="marketingOptIn" type="checkbox" value="true" class="mr-2" @Html.Raw(currentOrder.Properties["marketingOptIn"] == "1" ? "checked=\"checked\"" : "") /> @Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.MarketingOptIn", "Keep me up to date on news and exclusive offers")
4954
</label>
@@ -52,17 +57,17 @@
5257

5358
<div class="flex -mx-1">
5459
<input name="billingAddress.Firstname" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.FirstName", "First Name")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 mx-1 w-full"
55-
value="@(currentOrder.CustomerInfo.FirstName)" required />
60+
value="@(currentOrder.CustomerInfo.FirstName.ValueOrFallback(customer.FirstName))" required />
5661
<input name="billingAddress.Lastname" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.LastName", "Last Name")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 mx-1 w-full"
57-
value="@(currentOrder.CustomerInfo.LastName)" required />
62+
value="@(currentOrder.CustomerInfo.LastName.ValueOrFallback(customer.LastName))" required />
5863
</div>
5964

6065
<input name="billingAddress.Line1" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.Address1", "Address (Line 1)")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 w-full"
61-
value="@(currentOrder.Properties["billingAddressLine1"])" required />
66+
value="@(currentOrder.Properties.ValueOrFallback("billingAddressLine1", billingAddress.AddressLine1))" required />
6267
<input name="billingAddress.Line2" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.Address2", "Address (Line 2)")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 w-full"
63-
value="@(currentOrder.Properties["billingAddressLine2"])" />
68+
value="@(currentOrder.Properties.ValueOrFallback("billingAddressLine2", billingAddress.AddressLine2))" />
6469
<input name="billingAddress.City" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.City", "City")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 w-full"
65-
value="@(currentOrder.Properties["billingCity"])" required />
70+
value="@(currentOrder.Properties.ValueOrFallback("billingCity", billingAddress.City))" required />
6671

6772
<div class="flex -mx-1">
6873
<select name="billingAddress.Country" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 mx-1 w-full" required>
@@ -82,15 +87,17 @@
8287
<select name="billingAddress.Region" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 mx-1 w-full disabled:hidden"
8388
data-value="@currentOrder.PaymentInfo.RegionId" data-placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.SelectRegion", "-- Select a Region --")" required disabled></select>
8489
<input name="billingAddress.ZipCode" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.ZipCode", "Postcode")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 mx-1 w-full"
85-
value="@(currentOrder.Properties["billingZipCode"])" required />
90+
value="@(currentOrder.Properties.ValueOrFallback("billingZipCode", billingAddress.ZipCode))" required />
8691
</div>
8792
<input name="billingAddress.Telephone" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.Telephone", "Phone")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 w-full"
88-
value="@(currentOrder.Properties["billingTelephone"])" />
89-
90-
if (checkoutPage.Value<bool>("uccCollectShippingInfo"))
91-
{
92-
<label class="flex items-center mb-2 cursor-pointer">
93-
<input name="shippingSameAsBilling" type="checkbox" class="mr-2" value="true" @Html.Raw(currentOrder.Properties["shippingSameAsBilling"] == "1" || !currentOrder.Properties.ContainsKey("shippingSameAsBilling") ? "checked=\"checked\"" : "") /> @Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.ShippingSameAsBilling", "Shipping address is same as billing address")
93+
value="@(currentOrder.Properties.ValueOrFallback("billingTelephone", customer.Telephone))" />
94+
95+
if (checkoutPage.Value<bool>("uccCollectShippingInfo"))
96+
{
97+
var shippingAddress = currentOrder.GetShippingAddress(orderPropertyConfig);
98+
99+
<label class="flex items-center mb-2 cursor-pointer">
100+
<input name="shippingSameAsBilling" type="checkbox" class="mr-2" value="true" @Html.Raw(currentOrder.Properties["shippingSameAsBilling"] == "1" || !currentOrder.Properties.ContainsKey("shippingSameAsBilling") ? "checked=\"checked\"" : "") /> @Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.ShippingSameAsBilling", "Shipping address is same as billing address")
94101
</label>
95102

96103
<div id="shipping-info" class="hidden">
@@ -99,17 +106,17 @@
99106

100107
<div class="flex -mx-1">
101108
<input name="shippingAddress.Firstname" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.FirstName", "First Name")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 mx-1 w-full"
102-
value="@(currentOrder.Properties["shippingFirstName"])" required />
109+
value="@(currentOrder.Properties.ValueOrFallback("shippingFirstName", customer.FirstName))" required />
103110
<input name="shippingAddress.Lastname" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.LastName", "Last Name")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 mx-1 w-full"
104-
value="@(currentOrder.Properties["shippingLastName"])" required />
111+
value="@(currentOrder.Properties.ValueOrFallback("shippingLastName", customer.LastName))" required />
105112
</div>
106113

107114
<input name="shippingAddress.Line1" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.Address1", "Address (Line 1)")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 w-full"
108-
value="@(currentOrder.Properties["shippingAddressLine1"])" required />
115+
value="@(currentOrder.Properties.ValueOrFallback("shippingAddressLine1", shippingAddress.AddressLine1))" required />
109116
<input name="shippingAddress.Line2" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.Address2", "Address (Line 2)")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 w-full"
110-
value="@(currentOrder.Properties["shippingAddressLine2"])" />
117+
value="@(currentOrder.Properties.ValueOrFallback("shippingAddressLine2", shippingAddress.AddressLine2))" />
111118
<input name="shippingAddress.City" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.City", "City")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 w-full"
112-
value="@(currentOrder.Properties["shippingCity"])" required />
119+
value="@(currentOrder.Properties.ValueOrFallback("shippingAddressLine2", shippingAddress.City))" required />
113120

114121
<div class="flex -mx-1">
115122
<select name="shippingAddress.Country" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 mx-1 w-full" required>
@@ -129,10 +136,10 @@
129136
<select name="shippingAddress.Region" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 mx-1 w-full disabled:hidden"
130137
data-value="@currentOrder.ShippingInfo.RegionId" data-placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.SelectRegion", "-- Select a Region --")" required disabled></select>
131138
<input name="shippingAddress.ZipCode" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.ZipCode", "Postcode")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 mx-1 w-full"
132-
value="@(currentOrder.Properties["shippingZipCode"])" required />
139+
value="@(currentOrder.Properties.ValueOrFallback("shippingZipCode", shippingAddress.ZipCode))" required />
133140
</div>
134141
<input name="shippingAddress.Telephone" type="text" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.Telephone", "Phone")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 w-full"
135-
value="@(currentOrder.Properties["shippingTelephone"])" />
142+
value="@(currentOrder.Properties.ValueOrFallback("shippingTelephone", customer.Telephone))" />
136143

137144
</div>
138145
}

src/Umbraco.Commerce.Checkout/Web/Controllers/UmbracoCommerceCheckoutBaseController.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading;
23
using System.Threading.Tasks;
34
using Microsoft.AspNetCore.Mvc;
45
using Microsoft.AspNetCore.Mvc.ViewEngines;
@@ -40,8 +41,6 @@ public virtual Task<IActionResult> Index()
4041

4142
protected async Task<bool> IsValidCartAsync()
4243
{
43-
ArgumentNullException.ThrowIfNull(CurrentPage);
44-
4544
StoreReadOnly store = CurrentPage.GetStore();
4645
OrderReadOnly order = !IsConfirmationPageType(CurrentPage)
4746
? await UmbracoCommerceApi.Instance.GetCurrentOrderAsync(store.Id)
@@ -55,6 +54,14 @@ protected async Task<bool> IsValidCartAsync()
5554
return true;
5655
}
5756

57+
protected bool IsLoginRequired(out string loginPageUrl)
58+
{
59+
IPublishedContent checkoutPage = CurrentPage.GetCheckoutPage();
60+
IPublishedContent loginPage = CurrentPage.GetLoginPage();
61+
loginPageUrl = loginPage?.Url(Thread.CurrentThread.CurrentCulture.Name) ?? string.Empty;
62+
return checkoutPage?.Value<bool>("uccRequireLogin") ?? false;
63+
}
64+
5865
private static bool IsConfirmationPageType(IPublishedContent node)
5966
{
6067
if (!node.HasProperty("uccStepType"))

src/Umbraco.Commerce.Checkout/Web/Controllers/UmbracoCommerceCheckoutCheckoutPageController.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
22
using System.Linq;
3+
using System.Threading;
34
using System.Threading.Tasks;
45
using Microsoft.AspNetCore.Mvc;
56
using Microsoft.AspNetCore.Mvc.ViewEngines;
67
using Microsoft.Extensions.Logging;
8+
using Umbraco.Cms.Web.Common.Filters;
79
using Umbraco.Extensions;
810

911
namespace Umbraco.Commerce.Checkout.Web.Controllers
@@ -23,13 +25,22 @@ public UmbracoCommerceCheckoutCheckoutPageController(ILogger<UmbracoCommerceChec
2325

2426
public override async Task<IActionResult> Index()
2527
{
28+
ArgumentNullException.ThrowIfNull(CurrentPage);
29+
30+
// Check if login is required
31+
if (IsLoginRequired(out string loginPageUrl) && (!User.Identity?.IsAuthenticated ?? false))
32+
{
33+
return string.IsNullOrEmpty(loginPageUrl)
34+
? Unauthorized()
35+
: Redirect(loginPageUrl);
36+
}
37+
2638
// Check the cart is valid before continuing
2739
if (!await IsValidCartAsync())
2840
{
2941
return Redirect(InvalidCartRedirectUrl);
3042
}
3143

32-
ArgumentNullException.ThrowIfNull(CurrentPage);
3344
// If the page has a template, use it
3445
if (CurrentPage.TemplateId.HasValue && CurrentPage.TemplateId.Value > 0)
3546
{

src/Umbraco.Commerce.Checkout/Web/Controllers/UmbracoCommerceCheckoutCheckoutStepPageController.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
using Microsoft.AspNetCore.Mvc;
33
using Microsoft.AspNetCore.Mvc.ViewEngines;
44
using Microsoft.Extensions.Logging;
5+
using Umbraco.Cms.Core.Services;
6+
using Umbraco.Commerce.Core.Api;
7+
using Umbraco.Commerce.Extensions;
58
using Umbraco.Extensions;
69

710
namespace Umbraco.Commerce.Checkout.Web.Controllers
@@ -21,6 +24,14 @@ public UmbracoCommerceCheckoutCheckoutStepPageController(ILogger<UmbracoCommerce
2124

2225
public override async Task<IActionResult> Index()
2326
{
27+
// Check if login is required
28+
if (IsLoginRequired(out string loginPageUrl) && (!User.Identity?.IsAuthenticated ?? false))
29+
{
30+
return string.IsNullOrEmpty(loginPageUrl)
31+
? Unauthorized()
32+
: Redirect(loginPageUrl);
33+
}
34+
2435
// Check the cart is valid before continuing
2536
if (!await IsValidCartAsync())
2637
{

src/Umbraco.Commerce.Checkout/Web/PublishedContentExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ public static IPublishedContent GetCheckoutBackPage(this IPublishedContent conte
3030
return GetCheckoutPage(content).Value<IPublishedContent>("uccBackPage")!;
3131
}
3232

33+
public static IPublishedContent GetLoginPage(this IPublishedContent content)
34+
{
35+
return GetCheckoutPage(content).Value<IPublishedContent>("uccLoginPage")!;
36+
}
37+
3338
public static string GetThemeColor(this IPublishedContent content)
3439
{
3540
// Check if the checkout page has a custom theme color set

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
3-
"version": "16.1.0",
3+
"version": "16.2.0",
44
"assemblyVersion": {
55
"precision": "build"
66
},

0 commit comments

Comments
 (0)