Thank you Matt,
Here’s the Customer Information razor page + controller change that worked for me.
UmbracoCommerceCheckoutInformationPage.cshtml
@using Newtonsoft.Json
@using Umbraco.Commerce.Checkout.Web
@using Microsoft.AspNetCore.Http
@using Umbraco.Commerce.Extensions
@using Umbraco15.Store.Demo.Core.Extensions
@inherits UmbracoViewPage
@inject IHttpContextAccessor context;
@inject IMemberService memberService;
@inject OrderReadOnlyExtensions orderReadOnlyExtensions;
@{
Layout = "UmbracoCommerceCheckoutLayout.cshtml";
var isLoggedIn = Context.User?.Identity?.IsAuthenticated ?? false;
if (!isLoggedIn)
{
context.HttpContext!.Response.Redirect("/my-account/login");
return;
}
var store = Model.GetStore();
var currentOrder = await UmbracoCommerceApi.Instance.GetCurrentOrderAsync(store.Id);
//Assign order to signed-in member
var member = memberService.GetByEmail(Context.User!.Identity!.GetEmail()!);
if (member is null) return;
currentOrder = await orderReadOnlyExtensions.AssignToCustomerAsync(currentOrder, member);
var countries = await UmbracoCommerceApi.Instance.GetCountriesAsync(store.Id);
var checkoutPage = Model.GetCheckoutPage();
var nextStepPage = Model.GetNextStepPage();
var themeColor = Model.GetThemeColor();
}
@section aside {
<div class="pb-4 mb-4 border-b border-gray-300 ">
@using (Html.BeginUmbracoForm("ApplyDiscountOrGiftCardCode", "UmbracoCommerceCheckoutSurface", null, new { @class = "flex w-full m-0" }))
{
<input type="text" name="code" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.DiscountGiftCode", "Discount / Gift Card Code")" class="flex-1 py-2 px-4 border border-gray-300 rounded placeholder-gray-700" />
<button class="ml-4 bg-@(themeColor) text-white px-4 rounded hover:bg-gray-900" type="submit">@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.Apply", "Apply")</button>
}
@if (currentOrder.DiscountCodes.Count > 0 || currentOrder.GiftCards.Count > 0)
{
<ul class="mt-4 block">
@foreach (var discountCode in currentOrder.DiscountCodes)
{
<li class="inline-block mr-2"><a href="@Url.SurfaceAction("RemoveDiscountOrGiftCardCode", "UmbracoCommerceCheckoutSurface", new { Code = discountCode.Code })" class="inline-flex bg-gray-300 px-2 py-1 rounded hover:bg-gray-900 hover:text-white"><svg viewBox="0 0 20 20" class="inline-block w-5 h-5 fill-current mr-1"><use href="#ico-delete" /></svg> @discountCode.Code</a></li>
}
@foreach (var giftCard in currentOrder.GiftCards)
{
<li class="inline-block mr-2"><a href="@Url.SurfaceAction("RemoveDiscountOrGiftCardCode", "UmbracoCommerceCheckoutSurface", new { Code = giftCard.Code })" class="inline-flex bg-gray-300 px-2 py-1 rounded hover:bg-gray-900 hover:text-white"><svg viewBox="0 0 20 20" class="inline-block w-5 h-5 fill-current mr-1"><use href="#ico-delete" /></svg> @giftCard.Code</a></li>
}
</ul>
}
</div>
}
@using (Html.BeginUmbracoForm("UpdateOrderInformation", "UmbracoCommerceCheckoutSurface"))
{
<input type="hidden" name="nextStep" value="@(nextStepPage?.Key)" />
<h3 class="text-xl font-medium mb-4">@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.ContactInformation", "Contact Information")</h3>
<label>@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.Email", "Email")</label>
<input name="email" type="hidden" value="@(@Context?.User?.Identity?.Name)" required />
<label class="flex items-center mb-2 cursor-pointer">
<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")
</label>
<h3 class="text-xl font-medium mb-4 mt-8">@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.BillingAddress", "Billing Address")</h3>
<div class="flex -mx-1">
<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"
value="@(currentOrder.CustomerInfo.FirstName)" required />
<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"
value="@(currentOrder.CustomerInfo.LastName)" required />
</div>
<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"
value="@(currentOrder.Properties["billingAddressLine1"])" required />
<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"
value="@(currentOrder.Properties["billingAddressLine2"])" />
<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"
value="@(currentOrder.Properties["billingCity"])" required />
<div class="flex -mx-1">
<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>
<option value="" data-regions="[]" disabled selected>@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.SelectCountry", "-- Select a Country --")</option>
@foreach (var country in countries)
{
<!option value="@(country.Id)" @Html.Raw(currentOrder.PaymentInfo.CountryId == country.Id ? "selected=\"selected\"" : "")
data-regions="@(JsonConvert.SerializeObject((await UmbracoCommerceApi.Instance.GetRegionsAsync(country.StoreId, country.Id)).Select(x => new
{
id = x.Id,
name = x.Name
})))">
@(country.Name)
</!option>
}
</select>
<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"
data-value="@currentOrder.PaymentInfo.RegionId" data-placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.SelectRegion", "-- Select a Region --")" required disabled></select>
<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"
value="@(currentOrder.Properties["billingZipCode"])" required />
</div>
<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"
value="@(currentOrder.Properties["billingTelephone"])" />
if (checkoutPage.Value<bool>("uccCollectShippingInfo"))
{
<label class="flex items-center mb-2 cursor-pointer">
<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")
</label>
<div id="shipping-info" class="hidden">
<h3 class="text-xl font-medium mb-4 mt-8">@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.ShippingAddress", "Shipping Address")</h3>
<div class="flex -mx-1">
<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"
value="@(currentOrder.Properties["shippingFirstName"])" required />
<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"
value="@(currentOrder.Properties["shippingLastName"])" required />
</div>
<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"
value="@(currentOrder.Properties["shippingAddressLine1"])" required />
<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"
value="@(currentOrder.Properties["shippingAddressLine2"])" />
<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"
value="@(currentOrder.Properties["shippingCity"])" required />
<div class="flex -mx-1">
<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>
<option value="" data-regions="[]" disabled selected>@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.SelectCountry", "-- Select a Country --")</option>
@foreach (var country in countries)
{
<!option value="@(country.Id)" @Html.Raw(currentOrder.ShippingInfo.CountryId == country.Id ? "selected=\"selected\"" : "")
data-regions="@(JsonConvert.SerializeObject((await UmbracoCommerceApi.Instance.GetRegionsAsync(country.StoreId, country.Id)).Select(x => new
{
id = x.Id,
name = x.Name
})))">
@(country.Name)
</!option>
}
</select>
<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"
data-value="@currentOrder.ShippingInfo.RegionId" data-placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.SelectRegion", "-- Select a Region --")" required disabled></select>
<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"
value="@(currentOrder.Properties["shippingZipCode"])" required />
</div>
<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"
value="@(currentOrder.Properties["shippingTelephone"])" />
</div>
}
<h3 class="text-xl font-medium mb-4 mt-8">@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.Comments", "Comments")</h3>
<textarea name="comments" placeholder="@Umbraco.GetDictionaryValueOrDefault("UmbracoCommerceCheckout.Information.EnterComments", "Enter comments here")" class="block placeholder-gray-700 border border-gray-300 rounded py-2 px-4 mb-2 w-full h-32">@(currentOrder.Properties["comments"])</textarea>
@Html.Partial("~/Views/UmbracoCommerceCheckout/Partials/UmbracoCommerceCheckoutPrevNext.cshtml")
}
OrderReadOnlyExtensions.cs
using Umbraco.Cms.Core.Models;
using Umbraco.Commerce.Core.Api;
using Umbraco.Commerce.Core.Models;
namespace Umbraco15.Store.Demo.Core.Extensions
{
public class OrderReadOnlyExtensions(ICommerceApi commerceApi)
{
private readonly ICommerceApi _commerceApi = commerceApi;
public async Task<OrderReadOnly> AssignToCustomerAsync(OrderReadOnly orderReadOnly, IMember member, CancellationToken cancellationToken = default)
{
await _commerceApi.Uow.ExecuteAsync(async (uow) =>
{
var writableOrder = await orderReadOnly.AsWritableAsync(uow);
await writableOrder.AssignToCustomerAsync(member.Key.ToString());
orderReadOnly = writableOrder.AsReadOnly();
await uow.SaveChangesAsync();
uow.Complete();
}, cancellationToken);
return orderReadOnly;
}
}
}
If you have any feedback on it, it will be appreciated.