Tie commerce order to member signed-in

We have restricted the checkout page to be accessible only by signed-in members, but the customer info page asks for an email address that could differ from the signed-in member’s. When the email address differs, the order doesn’t appear in the member’s order history.

Is there a way of linking the email for the contact information to the signed-in member’s email and not asking for an email during checkout?
Or will it be directly in the source code for Checkout?


If I’ve understood what you’re attempting to do then could you not unpublish the customer information page which then should redirect you to the next page in the checkout process (assuming you’re using the Umbraco Commerce Checkout)?

Yes, it’s using Commerce Checkout, and I wondered if there was a configuration somewhere that could link orders to signed-in members.

I believe I will have to change the Razor files manually.

You can assign an order to a customer by calling order.AssignToCustomerAsync(reference) where the reference can be anything, but by default this should be the logged in member key.

Umbraco Commerce should attempt to assign this automatically at two points:

  1. If you are logged in and a new order is created
  2. When calling BeginPaymentFormAsync which is usually the last moment before redirecting to the payment gateway.

As mentioned above though, you can assign the order explicitly yourself.

NB This won’t auto populate the email address on the checkout flow, but you could check to see if the order.CustomerInfo.CustomerReference is populated and if it is, lookup the member and enter their email address into the email input field (if not already populated)

When looking up orders for a user, you can use the orderService.GetFinalizedOrdersForCustomerAsync(storeId, customerReferenceOrEmail) and passing the customer reference would fetch all orders with the same customer reference.

Hey Matt,

'OrderReadOnly' does not contain a definition for 'AssignToCustomerAsync' and the best extension method overload 'OrderExtensions.AssignToCustomerAsync(Task<Order>, string)' requires a receiver of type 'System.Threading.Tasks.Task<Umbraco.Commerce.Core.Models.Order>'

The Checkout Customer Information page has been updated to:

@using Newtonsoft.Json;
@using Umbraco.Commerce.Checkout.Web
@using Microsoft.AspNetCore.Http
@inherits UmbracoViewPage
@inject IHttpContextAccessor context;
@inject IMemberService memberService;
@{
    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;

    await currentOrder.AssignToCustomerAsync(member!.Key);

    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>
    <strong>@(currentOrder.CustomerInfo.Email)</strong>
    <input name="email" type="hidden" value="@(currentOrder.CustomerInfo.Email)" 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")
}

Yea, any modification to an entity requires converting it to its writable version first (all entities are read only by default).

See the docs here for info on converting a read only entity to a writable one

This will need to be wrapped in a unit of work so you’ll want to read the docs here too

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.

Looks good. I’d maybe just simplify your AssignToCusomterAsync slightly with:

public async Task<OrderReadOnly> AssignToCustomerAsync(OrderReadOnly orderReadOnly, IMember member, CancellationToken cancellationToken = default)
{
    return await _commerceApi.Uow.ExecuteAsync<OrderReadOnly>(async (uow) =>
    {
        var writableOrder = await orderReadOnly.AsWritableAsync(uow)
            .AssignToCustomerAsync(member.Key.ToString());
        await uow.SaveChangesAsync();
        return uow.Complete(writableOrder.AsReadOnly());
    }, cancellationToken);
}

Just make sure you have a using for Umbraco.Commerce.Extensions defined.

1 Like

Actually, maybe also do a check first to make sure it’s not already assigned as there is no point wasting the DB write if it’s not necessary.

1 Like

Good point!

        public async Task<OrderReadOnly> AssignToCustomerAsync(OrderReadOnly orderReadOnly, IMember member, CancellationToken cancellationToken = default)
        {
            if (orderReadOnly.CustomerInfo.CustomerReference == member.Key.ToString()) return orderReadOnly;

            return await _commerceApi.Uow.ExecuteAsync<OrderReadOnly>(async (uow) =>
            {
                var writableOrder = await orderReadOnly.AsWritableAsync(uow)
                    .AssignToCustomerAsync(member.Key.ToString());
                await uow.SaveChangesAsync();
                return uow.Complete(writableOrder.AsReadOnly());
            }, cancellationToken);
        }

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.