Skip to content

Create a new state-driven checkout page#8118

Open
mt3d wants to merge 1 commit intonopSolutions:developfrom
mt3d:issue-4849-new-one-page-checkout
Open

Create a new state-driven checkout page#8118
mt3d wants to merge 1 commit intonopSolutions:developfrom
mt3d:issue-4849-new-one-page-checkout

Conversation

@mt3d
Copy link

@mt3d mt3d commented Feb 20, 2026

This PR fixes #4849.

After over 50 commits (now squashed into a single commit for an easy review), this PR introduces a new checkout page where all the information could be entered in a single view. The goal of this work was to design a simplified and cohesive checkout experience could be achieved without altering the underlying checkout semantics of Nop. While the UI can certainly be further refined and some implementation details may still be open to change, the design has reached a stage where feedback is necessary before proceeding further.

Video previews of the new flow are available here and here.

From a UI perspective, the page is divided into 6 sections: Billing Address, Shipping Address, Shipping Method, Payment Method, Payment Info, and Order Summary. Each section can be updated independently. When the user sets the billing address, the payment method and payment info sections are updated. Similarly, when the user sets the shipping address, the shipping method section is recalculated. Finally, any changes to the shipping and payment methods will be reflected in the order summary section.

How does the page work internally? To understand this, we have to ask what checkout in Nop actually is. This is very important, because the new flow never attempts to change or modify the meaning of checkout in Nop, so that nothing is ever broken in other parts of the system. If we look closely at the current implementation of OPC, we will see that each step aims to to set a single value:

  • The billing address step aims to set customer.BillingAddressId
  • The shipping address step aims to set customer.ShippingAddressId (or a generic attribute for pickup in store)
  • The shipping method step aims to set a generic attribute with the name of the shipping method
  • The payment method step aims to set a generic attribute with the name of the payment method
  • The payment method step starts a new ProcessPaymentRequest with the available payment info

This PR maintains the exact same model. To support a unified page, I've created endpoints for setting each one of the above values, along with a state snapshot endpoint that returns the current aggregate checkout state.. Each section has simply to call the corresponding endpoint to update the state. Additionally, separate rendering endpoints return HTML fragments for each section. This separation of state modification and rendering allows the checkout process to remain identical to OPC while enabling a flexible client experience. These APIs are also structured in a way that could be reused by SPAs or mobile clients in the future.

On the client side, there is a checkout manager that maintains a local state, and whose job is to keep it synced with server. After each update to the state, a dependency graph is consulted to see what sections need to be updated. This ensures that updates are minimal and deterministic rather than full page reloads. Rendering functions are explicitly side effect free when it comes to the state, meaning that UI updates are always derived from server confirmed data. During initialization, the client hydrates the page from the main checkout aggregate endpoint before any interaction occurs. In this design, the server remains the single source of truth for addresses, shipping and payment selections, and requirement evaluation, while the client is responsible only for visibility rules and incremental rendering.

One major change compared to the previous OPC flow is how addresses are handled. Trying to achieve real time updates using the old forms is exceptionally hard (this is what I tried at first). Instead, address creation and editing are performed within isolated modal dialogs. This removes the need for partial live validation and incremental field synchronization, which simplifies both the client and server interaction while while still being fully compatible with the existing checkout model.

Scope and compatibility: this PR does not modify the database schema, existing public APIs, or plugin contracts. No breaking changes are introduced to the customer, shopping cart, or address entities. The implementation is additive and isolated to a new controller (SpCheckoutController). The existing OPC and standard checkout flows remain untouched, and this new checkout can coexist as an alternative implementation.

About the UI: The current UI reuses the existing section markup and styling to isolate architectural changes from visual redesign. The primary goal of this PR is to build a unified checkout and state synchronization model rather than introduce a complete UI overhaul. A more modern layout could further improve the experience, for example by using more compact sections and placing the order summary in a sticky right column for better visibility. Such refinements can be explored in the future once this new design is validated.

I would really appreciate feedback on the architectural direction, particularly regarding the endpoint design and state synchronization strategy, before continuing refinement of the UI layer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New real one-page checkout

2 participants