diff --git a/docs/getting-started/installing-solidus.mdx b/docs/getting-started/installing-solidus.mdx
index 8553b38..549db69 100644
--- a/docs/getting-started/installing-solidus.mdx
+++ b/docs/getting-started/installing-solidus.mdx
@@ -119,7 +119,7 @@ generated initializer in `config/initializers/spree.rb`.
The very first line on it will be very similar to this one:
```ruby
-Spree.load_defaults '4.1'
+Spree.load_defaults '4.2'
```
Some of the default values for Solidus preferences depend on the Solidus version. That line makes
diff --git a/docs/upgrading-solidus/v3.3.mdx b/docs/upgrading-solidus/v3.3.mdx
index 3b14c00..5c4ff7e 100644
--- a/docs/upgrading-solidus/v3.3.mdx
+++ b/docs/upgrading-solidus/v3.3.mdx
@@ -1,5 +1,5 @@
---
-sidebar_position: 4
+sidebar_position: -3.3
---
import PRLink from '@site/src/theme/PRLink';
diff --git a/docs/upgrading-solidus/v3.4.mdx b/docs/upgrading-solidus/v3.4.mdx
index 625e43a..afbd4f2 100644
--- a/docs/upgrading-solidus/v3.4.mdx
+++ b/docs/upgrading-solidus/v3.4.mdx
@@ -1,5 +1,5 @@
---
-sidebar_position: 3
+sidebar_position: -3.4
---
import PRLink from '@site/src/theme/PRLink';
diff --git a/docs/upgrading-solidus/v4.0.mdx b/docs/upgrading-solidus/v4.0.mdx
index a5f26b5..94f67a1 100644
--- a/docs/upgrading-solidus/v4.0.mdx
+++ b/docs/upgrading-solidus/v4.0.mdx
@@ -1,5 +1,5 @@
---
-sidebar_position: 2
+sidebar_position: -4.0
---
import PRLink from '@site/src/theme/PRLink';
diff --git a/docs/upgrading-solidus/v4.1.mdx b/docs/upgrading-solidus/v4.1.mdx
index 81cc4d1..48415b6 100644
--- a/docs/upgrading-solidus/v4.1.mdx
+++ b/docs/upgrading-solidus/v4.1.mdx
@@ -1,5 +1,5 @@
---
-sidebar_position: 1
+sidebar_position: -4.1
---
import PRLink from '@site/src/theme/PRLink';
diff --git a/docs/upgrading-solidus/v4.2.mdx b/docs/upgrading-solidus/v4.2.mdx
new file mode 100644
index 0000000..2a325c2
--- /dev/null
+++ b/docs/upgrading-solidus/v4.2.mdx
@@ -0,0 +1,69 @@
+---
+sidebar_position: -4.2
+---
+
+import PRLink from '@site/src/theme/PRLink';
+import MinimalRequirements from '@site/src/theme/MinimalRequirements';
+
+# Solidus v4.2 (2023-06-29)
+
+
+
+Solidus v4.2 is out! π
+
+This release brings the usual load of bug fixes and improvements, but also the new branding and some new features that we are happy to share with you.
+
+## Stop using partials and deface to customize the admin menu
+
+This change removes the need to use of partials and deface to customize the Admin menu. Instead, we now have a new `#children` attribute added to the `MenuItem` class in which second level menus are defined.
+
+Doing this brought a significant benefits in terms of simplicity and customization. Parent menu items can now ask the children if any of them is active without the need of duplicating the logic in the parent.
+
+Since the list of menu items is a simple array, it can be easily manipulated by other extensions, and in the host application. Finding a menu item is as simple as checking its `label`, e.g. `menu_items.find { _1.label == :products }`.
+
+```ruby
+Spree::Backend::Config.configure do |config|
+ # Let solidus know that we don't want to use the partials for second level
+ # menu items anymore. By enabling this whenver a menu item has both children
+ # and a partial, the partial will be ignored.
+ config.prefer_menu_item_partials = false
+
+ config.menu_items
+ .find { _1.label == :products }
+ .children << MenuItem.new(
+ label: :collections,
+ condition: -> { can? :admin, MyStore::ProductCollection },
+ url: '/product_collections'
+ )
+end
+```
+
+## Updated sidebar from the upcoming `SolidusAdmin`
+
+A new sidebar is also available that matches the style of the one in `SolidusAmin`, the new admin interface that is currently under development.
+
+The new sidebar is best experienced with the matching `solidus_admin` theme.
+
+```ruby
+Spree::Backend::Config.configure do |config|
+ config.theme = 'solidus_admin'
+ config.admin_updated_navbar = true
+end
+```
+
+We hope you like it!
+
+## Add support for the `SolidusAdmin` preview
+
+The new `SolidusAdmin` is still under development, but you can already preview it in your application.
+
+```shell
+bundle add solidus_admin
+bin/rails g solidus_admin:install
+```
+
+At the time of this writing it only covers product and order listing, and partial editing of products. We are working hard to bring more features to it, and we are looking forward to your feedback.
+
+Permission checks and authentication is shared with the existing admin interface, so you can use the same user to access both. A toggle is available at the bottom of the navigation menu to switch between the two interfaces.
+
+The new admin dashboard is mounted as an independent engine, and by default it can be disabled with a cookie, but that can be customized in the host application, e.g. to enable it only for a select number of users.
diff --git a/src/theme/SupportedVersions.js b/src/theme/SupportedVersions.js
index bdaab8a..7ea918b 100644
--- a/src/theme/SupportedVersions.js
+++ b/src/theme/SupportedVersions.js
@@ -2,6 +2,7 @@ import React from 'react';
export default function SupportedVersions() {
const versions = [
+ { number: 'v4.2', releaseDate: '2023-09-29', eolDate: '2025-06-29' },
{ number: 'v4.1', releaseDate: '2023-06-29', eolDate: '2024-12-29' },
{ number: 'v4.0', releaseDate: '2023-05-08', eolDate: '2024-11-08' },
{ number: 'v3.4', releaseDate: '2023-04-21', eolDate: '2024-10-21' },
diff --git a/versioned_docs/version-4.1/getting-started/installing-solidus.mdx b/versioned_docs/version-4.1/getting-started/installing-solidus.mdx
index 8553b38..549db69 100644
--- a/versioned_docs/version-4.1/getting-started/installing-solidus.mdx
+++ b/versioned_docs/version-4.1/getting-started/installing-solidus.mdx
@@ -119,7 +119,7 @@ generated initializer in `config/initializers/spree.rb`.
The very first line on it will be very similar to this one:
```ruby
-Spree.load_defaults '4.1'
+Spree.load_defaults '4.2'
```
Some of the default values for Solidus preferences depend on the Solidus version. That line makes
diff --git a/versioned_docs/version-4.2/advanced-solidus/_category_.json b/versioned_docs/version-4.2/advanced-solidus/_category_.json
new file mode 100644
index 0000000..ebd3371
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/_category_.json
@@ -0,0 +1,6 @@
+{
+ "label": "Advanced Solidus",
+ "position": 4,
+ "collapsible": true,
+ "collapsed": true
+}
diff --git a/versioned_docs/version-4.2/advanced-solidus/custom-authentication.mdx b/versioned_docs/version-4.2/advanced-solidus/custom-authentication.mdx
new file mode 100644
index 0000000..2cf085b
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/custom-authentication.mdx
@@ -0,0 +1,158 @@
+---
+sidebar_position: 1
+needs-diataxis-rewrite: true
+---
+
+# Custom authentication
+
+:::info
+You can use the official [`solidus_auth_devise`](https://github.com/solidusio/solidus_auth_devise) gem to provide
+a `Spree::User` model and basic authentication for Solidus. See its documentation for additional setup instructions.
+:::
+
+Solidus requires a `User` model in order to take advantage of all its features. This model can have any name, and
+Solidus can integrate with your application's existing authentication system.
+
+In this guide, we'll explore the steps required to create a `User` model from scratch, use an authentication solution
+like [Devise ](https://github.com/plataformatec/devise), or integrate your application's existing `User` model.
+
+## Basic requirements
+
+In order to use a custom user model, your model should have:
+
+* **An integer `id` column:** Solidus uses integers for all foreign keys, so you need to use integer IDs in your user
+ model. You may use other types of IDs by changing the types of the foreign key columns, but this is generally
+ discouraged.
+* **A `password` attribute:** This is needed if you use
+ `solidus_starter_frontend`, `solidus_frontend` or `solidus_backend`. You can
+ implement the attribute however you see fit.
+
+This is all you need for now. The rest of the requirements will be implemented in the next steps!
+
+## Preparing your user class
+
+### With the generator
+
+Solidus ships with a generator to prepare and configure your custom user class throughout the application. Just run the
+following:
+
+```bash
+$ rails g spree:custom_user AmazingStore::User
+```
+
+This will do the following:
+
+* Generate a migration to add some required columns to the custom model's table.
+* Set `Spree.user_class` to your custom model's class name, so that Solidus knows to use it in associations and
+ throughout the store.
+* Implement some authentication helpers required by `solidus_backend`,
+ `solidus_starter_frontend` and `solidus_frontend` in
+ `lib/spree/authentication_helpers.rb`.
+
+At this point, you'll need to migrate your database to add the new columns:
+
+```bash
+$ rails db:migrate
+```
+
+You may also want to customize the helpers in `lib/spree/authentication_helpers.rb`.
+
+### Without the generator
+
+#### Add the required columns
+
+The first step is to add the columns Solidus expects to the users table:
+
+* `spree_api_key`: a string containing the user's API key. This should be limited to 48 characters.
+* `bill_address_id`: an integer containing the ID of the `Spree::Address` that should be used as the user's billing
+ address.
+* `ship_address_id`: an integer containing the ID of the `Spree::Address` that should be used as the user's shipping
+ address.
+
+You can easily add these with the following migration:
+
+```bash
+$ rails g migration AddAuthColumnsToUsers \
+ spree_api_key:string{48} \
+ bill_address_id:integer \
+ ship_address_id:integer
+```
+
+Once the migration has been generated, you can migrate the database:
+
+```bash
+$ rails db:migrate
+```
+
+#### spree\_current\_user helper
+
+If you use `solidus_starter_frontend`, `solidus_frontend` or `solidus_backend`,
+you need to provide a `spree_current_user` helper method in your
+`ApplicationController`:
+
+```ruby title="app/controllers/application\_controller.rb"
+class ApplicationController < ActionController::Base
+ helper_method :spree_current_user
+
+ def spree_current_user
+ # If your gem already provides a current_user method,
+ # you may simply wrap it in spree_current_user. If not,
+ # you'll need some additional custom logic here.
+ current_user
+ end
+
+ # ...
+end
+```
+
+#### Add authentication helpers
+
+If you use `solidus_starter_frontend`, `solidus_frontend` or `solidus_backend`,
+you need to provide authentication helpers so that users can sign up, log in,
+and log out:
+
+```ruby title="app/controllers/application\_controller.rb"
+class ApplicationController < ActionController::Base
+ helper_method :spree_login_path
+ helper_method :spree_signup_path
+ helper_method :spree_logout_path
+
+ def spree_login_path
+ login_path
+ end
+
+ def spree_signup_path
+ signup_path
+ end
+
+ def spree_logout_path
+ logout_path
+ end
+
+ # ...
+end
+```
+
+## Adding Solidus user methods
+
+The [`Spree::UserMethods` module ](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/concerns/spree/user_methods.rb)
+provides extensive integration for a `User` model, creating associations and allowing it to interact with major models
+in Solidus like `Spree::Order`.
+
+To add user methods to your `User` model, include `Spree::UserMethods` :
+
+```ruby title="app/models/my\_store/user.rb"
+module MyStore
+ class User
+ include Spree::UserMethods
+
+ # ...
+ end
+end
+```
+
+## How-to guides
+
+- [How to sign in to the Solidus API using solidus_auth_devise][how-to-sign-in-to-the-solidus-api-using-solidus_auth_devise]
+
+[how-to-sign-in-to-the-solidus-api-using-solidus_auth_devise]: /how-tos/api/how-to-sign-in-to-the-solidus-api-using-solidus_auth_devise.mdx
diff --git a/versioned_docs/version-4.2/advanced-solidus/extension-development.mdx b/versioned_docs/version-4.2/advanced-solidus/extension-development.mdx
new file mode 100644
index 0000000..4bcae84
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/extension-development.mdx
@@ -0,0 +1,868 @@
+---
+sidebar_position: 2
+needs-diataxis-rewrite: true
+---
+
+# Extension development
+
+## Intro to extension development
+
+We've already talked about how to use extensions in [Using extensions][using-extensions]. As you may
+already know, extensions are Rails engines that augment your Solidus store with
+additional functionality such as payment gateways, WMS integrations, social login, and more. Using extensions when
+developing your store can be a huge time and money saver since it spares you the need
+to reimplement common (and often complicated) functionality.
+
+But how would you go about creating your own extension? Perhaps you found an unsolved problem in the
+ecosystem, or you need to implement a feature or integration that you think others could benefit
+from. Perhaps, you would like to create an open-source extension or, maybe, you want to keep the
+extension private and only allow your team to access it.
+
+Whatever the use case, this guide has got you covered. We'll see how to create an extension from
+scratch and release it as open-source on RubyGems or on a private gem server.
+
+## Creating your first extension
+
+In this example, we'll create an extension for integrating with Acme Fulfillment, a fulfillment
+provider β this is a very common use case in eCommerce. Our extension will do the following:
+
+1. When an order is finalized, it will call the fulfillment partner's API to send all information
+ about the order, so that it can be packaged and shipped to the customer.
+2. It will store the fulfillment partner's shipment ID in the Solidus DB, for easier inspection and
+ debugging.
+
+Let's dive right in!
+
+### Generating the skeleton
+
+When working with extensions and Rails engines in general, there's a lot of boilerplate involved.
+Over the years, we've developed tools that simplify most of the tasks around extension creation and
+maintenance.
+
+We've consolidated this tooling
+in [`solidus_dev_support`](https://github.com/solidusio/solidus\_dev\_support), a gem whose job is
+to make it a no-brainer to develop Solidus extensions. The gem provides static and runtime utilities
+that help you create a new extension, make it compatible with different Solidus versions, release it
+and maintain it over time.
+
+:::info
+
+`solidus_dev_support` provides much more functionality than we'd be able to cover in this guide, so
+you should definitely check out
+its [documentation](https://github.com/solidusio/solidus\_dev\_support) to get an idea of how much
+it does.
+
+:::
+
+The first step is to install the gem globally so that you can use it from your console:
+
+```bash
+$ gem install solidus_dev_support
+```
+
+You can now generate a new extension with the `solidus extension` command. Solidus extensions are
+generally named `solidus_`, and then the name of the feature or integration they provide, so we'll
+follow this convention for our Acme Fulfillment extension:
+
+```bash
+$ solidus extension solidus_acme_fulfillment
+```
+
+This will generate a `solidus_acme_fulfillment` directory in the current path. This directory will
+contain a lot of boilerplate that is required to make your extension play nice with Solidus and
+Rails conventions.
+
+`solidus_dev_support` tries to be as smart as possible and use some sensible defaults for your
+extension, but we'll still need to adjust some values before we can proceed.
+
+First of all, open `solidus_acme_fulfillment.gemspec` in your favorite text editor, and change the
+following lines to configure the required fields for your extension:
+
+```diff title="solidus_acme_fulfillment.gemspec"
+# ...
+- spec.authors = ['TODO: Write your name']
+- spec.email = 'TODO: Write your email address'
++ spec.authors = ['Alice']
++ spec.email = 'alice@mail.com'
+
+- spec.summary = 'TODO: Write a short summary, because RubyGems requires one.'
++ spec.summary = 'A Solidus extension to integrate with the Acme Fulfillment API.'
+- spec.description = 'TODO: Write a longer description or delete this line.'
+
+# ...
+```
+
+Now that our gemspec doesn't have any TODOs in it, we can install the extension's bundle, which
+contains some useful tooling for working with the extension:
+
+```
+$ bundle install
+```
+
+Now that the gemspec is configured and the bundle installed, we're ready to write some code!
+
+### Adding dependencies
+
+In order to make API calls, we'll need a library that makes it easier to perform HTTP requests. We
+could use Ruby's `HTTP` module, but its API is kind of cumbersome and not really fun to work with.
+Instead, we'll go with the popular [`httparty`](https://github.com/jnunemaker/httparty) gem. In
+order to do that, let's add the dependency to our gemspec:
+
+```diff title="solidus_acme_fulfillment.gemspec"
+ Gem::Specification.new do |spec|
+ # ...
+
+ spec.add_dependency 'solidus_core', ['>= 2.0.0', '< 4']
+ spec.add_dependency 'solidus_support', '~> 0.5'
++ spec.add_dependency 'httparty', '~> 0.20.0'
+
+ # ...
+ end
+```
+
+This will ensure `httparty` is also installed by any apps that install our extension. We can now
+reinstall the bundle to get the new gem:
+
+```bash
+$ bundle install
+```
+
+Finally, we'll need to require the `httparty` gem in our extension's main file, since gem
+dependencies are not autoloaded by Bundler when initializing the main app:
+
+```diff title="lib/solidus_acme_fulfillment.rb"
++require 'httparty'
++
+ require 'solidus_acme_fulfillment/configuration'
+ require 'solidus_acme_fulfillment/version'
+ require 'solidus_acme_fulfillment/engine'
+```
+
+We should now be able to access `httparty` everywhere in our extension's code!
+
+### Accepting configuration values
+
+A common pattern in extensions is to accept certain configuration values that the user can change in
+a Rails initializer. For this reason, the extension skeleton generated by `solidus_dev_support`
+ships with a sample configuration file where you can add any options that you want the user to be
+able to configure. The skeleton also contains an initializer which will be copied to the main app
+when the extension is installed, so that the user doesn't have to write the configuration code
+manually.
+
+In the case of our Acme Fulfillment extension, we want to let the user configure their API key. In
+order to do this, let's edit the `lib/solidus_acme_fulfillment/configuration.rb` file as follows:
+
+```diff title="lib/solidus_acme_fulfillment/configuration.rb"
+ module SolidusAcmeFulfillment
+ class Configuration
+- # Define here the settings for this extension, e.g.:
+- #
+- # attr_accessor :my_setting
++ attr_accessor :api_key
+ end
+
+ # ...
+ end
+```
+
+We will also edit the sample initializer to let the user know about the new configuration option:
+
+```diff title="lib/generators/solidus_acme_fulfillment/install/templates/initializer.rb"
+ SolidusAcmeFulfillment.configure do |config|
+- # TODO: Remember to change this with the actual preferences you have implemented!
+- # config.sample_preference = 'sample_value'
++ # Set your Acme Fulfillment API key here.
++ config.api_key = 'my-api-key'
+ end
+```
+
+When a user installs our extension, an initializer will be added to their main application
+under `config/initializers/solidus_acme_fulfillment.rb` which will contain our sample configuration.
+
+### Writing your first feature
+
+Customizing Solidus through an extension is very similar to customizing it in the main application,
+and the same rules and patterns apply: you can use extension hooks, the event bus, overrides, etc.
+
+:::caution
+
+One important aspect to keep in mind when working on extensions is that you can't predict what other
+extensions the user will install, so you need to make sure your customizations play nice with other
+extensions.
+
+For example, setting a configuration value in the Solidus configuration is usually discouraged in
+extensions, since other extensions may do the same and end up overriding your setting. Instead, you
+can change the value by altering the configuration in the main app through your installation
+initializer, or document that the value needs to be set in your readme, and let the user do it.
+
+:::
+
+When we described our requirements, we mentioned we want to save the shipment ID that we get back
+from the 3PL's API when we create the order, so that we can easily access the shipment later. The
+best place to store this information would be an additional column in the `spree_orders` table, so
+let's first write a migration to create it:
+
+```bash
+$ bin/rails g migration AddAcmeFulfillmentShipmentIdToSpreeOrders \
+ acme_fulfillment_shipment_id
+```
+
+:::info
+
+You may have noticed that we're not running `rails db:migrate` after generating the migration. This
+is because we're working in a Rails engine, not a Rails application. We don't have a database to
+work on.
+
+All database migrations that you generate in your extension will be automatically imported into the
+main application when the extension is installed via its initializer.
+
+:::
+
+Next, we need to implement the actual code to integrate with the fulfillment partner's API. An event
+subscriber seems like the best way to do this, so let's write one:
+
+```ruby title="app/subscribers/solidus_acme_fulfillment/order_subscriber.rb"
+module SolidusAcmeFulfillment
+ class OrderSubscriber
+ include Omnes::Subscriber
+
+ handle :order_finalized, with: :send_to_3pl
+
+ def send_to_3pl(event)
+ order = event.payload.fetch(:order)
+
+ response = HTTParty.post(
+ 'https://api.acmefulfillment.com/orders',
+ headers: {
+ 'Authorization' => "Bearer #{SolidusAcmeFulfillment.config.api_key}",
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json',
+ },
+ body: serialize_order(order).to_json,
+ )
+
+ order.update!(
+ acme_fulfillment_shipment_id: response.parsed_response['id'],
+ )
+ end
+
+ private
+
+ def serialize_order(order)
+ {
+ # ...
+ }
+ end
+ end
+end
+```
+
+Our event subscriber is pretty simple: it listens to the `:order_finalized`event and, when it's
+published, it calls the Acme Fulfillment API with the configured API key and the serialized order
+information. It then parses the API response and sets the `acme_fulfillment_shipment_id` column on
+the order to the ID returned by the fulfillment partner's API.
+
+:::caution
+
+In the real world, you'd want to move this block of code to a background job, so that it doesn't
+unnecessarily slow down your user's HTTP requests with API calls to your fulfillment partner.
+
+:::
+
+We still need to subscribe it to the Solidus event bus system. We can do it in our extension's
+engine file:
+
+```diff title="lib/solidus_acme_fullfilment/engine.rb"
+module SolidusAcmeFulfillment
+ class Engine < Rails::Engine
+ # ...
++
++ config.to_prepare do
++ SolidusAcmeFulfillment::OrderSubscriber.new.subscribe_to(Spree::Bus)
++ end
+ end
+ end
+```
+
+That's all we needed! The requirements have been satisfied, and it's now time to preview our work.
+In order to do that, we'll use the **sandbox app.**
+
+### Using the sandbox app
+
+Because extensions are Rails engines, they can't be previewed as easily as we'd do when customizing
+our main app: there's no underlying Rails/Solidus application to run the extension. You could
+install your extension in an existing Solidus app and preview it there, but this can be slow and
+cumbersome, especially when you're still actively working on the extension.
+
+Luckily, `solidus_dev_support` provides a Rake task we can run to generate a "sandbox app", i.e. a
+barebones Rails + Solidus application with our extension already installed and configured. The
+sandbox app is extremely useful in extension development, and it's important to learn to make the
+best of it.
+
+To generate the sandbox app, simply run the following command:
+
+```
+$ bin/sandbox
+```
+
+The generation might take a couple of minutes, so sit tight and relax! The process will also ask you
+a few times whether migrations should be run immediately or manually at a later stage β you want to
+run them immediately, which is also the default selection. This will save you a few seconds of work.
+
+Once the process has been completed, you'll find a new `sandbox` directory at the root of your
+extension. This contains your new shiny sandbox app. Your extension has already been installed and
+configured inside the app: try looking for the `config/initializers/solidus_acme_fulfillment.rb`
+initializer.
+
+:::caution
+
+The sandbox app is ephemeral and intended for development/test purposes only: the sandbox path is
+ignored by Git, so any changes you make here will be lost permanently if you remove the `sandbox`
+directory.
+
+:::
+
+`solidus_dev_support` also allows you to run commands in your sandbox app from the root of your
+extension, just as you would do with a regular Rails application. Try spinning up a Rails server:
+
+```
+$ bin/rails server
+```
+
+This should boot your sandbox app and serve it at [http://localhost:3000](http://localhost:3000), so
+that you can preview your extension as you work on it!
+
+:::info
+
+All `rails` commands will be delegated to the sandbox app.
+
+One exception is the `rails g` /`rails generate` command, which will be run in your extension (since
+that's usually the intended behavior). If you need to run a generator in the sandbox app, you'll
+have to first `cd` into the `sandbox` directory.
+
+As an alternative, you can use `bin/rails-engine` and `bin/rails-sandbox` to force a command to run
+in the engine or in the sandbox respectively.
+
+:::
+
+### Releasing the extension
+
+:::info
+
+This step is optional but recommended. You _could_ keep your extension in a private or public GitHub
+repository and download it directly from there, but you'd miss out on the benefits of properly
+versioning your extension, which makes it easier to maintain and upgrade it.
+
+:::
+
+Like all gems, Solidus extensions can be released to any public or private gem servers, such
+as [RubyGems](https://rubygems.org/) or [Gemfury](https://gemfury.com/). Releasing your extension
+allows you to package it in a convenient way and follow an established versioning scheme much more
+easily than simply pulling code from GitHub. In general, you should release the first version of
+your extension as soon as you start using it in production.
+
+By default, the extension skeleton generated by `solidus_dev_support` is configured to release your
+gem on RubyGems. If you're using a different gem server, they should provide instructions on how to
+properly configure your gemspec.
+
+Once you've configured your gem server you can release your extension with the following command:
+
+```
+$ gem bump -v 1.0.0 -t # bump the extension version to 1.0.0
+$ bin/rake changelog # generate the new release's changelog
+$ git commit -a --amend # update the version bump commit
+$ git push --follow-tags # push the version bump to GitHub
+$ gem release # release the extension on the gem server
+```
+
+:::danger
+
+The `bin/rake changelog` command will work as long as you're hosting your repository on GitHub.
+Otherwise, you need to manually create it or use another tool.
+
+You might also need to [generate a new GitHub token](https://github.com/settings/tokens/new) and
+provide it as an environment variable. E.g.: `CHANGELOG_GITHUB_TOKEN=my_token bin/rake changelog`\
+See [github-changelog-generator](https://github.com/github-changelog-generator/github-changelog-generator) (
+used under the hood) for details.
+
+:::
+
+:::info
+
+In the Solidus ecosystem, we follow [Semantic Versioning](https://semver.org/) for assigning version
+numbers to our releases. It is strongly recommended you do the same, in order not to break the
+expectations of experienced Solidus developers.
+
+:::
+
+### Installing the extension
+
+Once you've released your extension (or just pushed it to GitHub), you can install it in any Solidus
+store by following the [usual instructions][using-extensions]. First of all, add the
+extension to your Gemfile:
+
+```ruby title="Gemfile"
+# ...
+
+# Use this if you released your extension to a gem server:
+gem 'solidus_acme_fulfillment'
+
+# Use this if you simply host your extension on GitHub:
+gem 'solidus_acme_fulfillment', github: 'your-org/solidus_acme_fulfillment'
+```
+
+Then install the bundle:
+
+```
+$ bundle install
+```
+
+Finally, run your extension's install generator to copy all relevant files to your application:
+
+```
+$ bin/rails g solidus_acme_fulfillment:install
+```
+
+The generator will ask you whether to run migrations immediately. If you choose not to do it, you
+can always do it yourself with `bin/rails db:migrate`.
+
+As the last step, you may want to review and customize the files generated by your extension. In our
+example, you should set your API key in the `config/initializers/solidus_acme_fulfillment.rb` file.
+
+That's it! Your extension is now fully installed and running in your app.
+
+## Extension best practices
+
+The following section contains some advanced recommendations for extension design, development, and
+maintenance. By following these best practices, you'll make your extension future-proof and
+compatible with the vast majority of Solidus applications.
+
+### Don't override, extend
+
+The first and most important rule of good extension design is to
+avoid [overrides][customizing-core] at all costs. Overrides in extensions
+have the same problems as overrides in the main app: because you're directly altering third-party
+code, they are hard to maintain and hard to test.
+
+In extensions, overrides become even more of a problem, because multiple extensions may override the
+same pieces of Solidus! This can lead to a mess of tangled overrides that are in conflict with each
+other. Also, overrides don't play nice with IDE autocompletion, and they will make it more difficult
+for users of your extension to figure out your extension's API.
+
+Sometimes, overrides are inevitable, but you should always look for alternatives. Solidus allows
+users to customize the vast majority of [service objects][customizing-core] used by the
+framework. Whenever possible, you should leverage those configuration options instead of overriding
+the existing service objects. The [event bus][subscribing-to-events] is another
+good option which you should learn to rely on.
+
+Instead of altering the upstream version, try to find a way to provide the desired functionality
+with new code which extends or wraps the original implementation. This might mean writing a bit more
+code, but it will pay off greatly when you need to update your extension for compatibility with new
+Solidus versions, or when users need to understand how your extension interacts with the rest of
+their application.
+
+We've already seen a good example of it. We could have overridden
+the [`Spree::Order#finalize`](https://github.com/solidusio/solidus/blob/4cc1d5aeb0bd0ef9cc045be1c1083d1c702b5264/core/app/models/spree/order.rb#L737)
+method and added the API call to Acme Fulfillment's API there. However, that would have been
+brittle, as there's no guarantee the method won't change in subsequent Solidus releases. We can't
+either anticipate if stores or other extensions are messing with it. Having added the feature as an
+event handler frees us from all those problems and leaves our code completely scoped to our own
+domain.
+
+### Support internationalization
+
+Many Solidus stores are international, or plan to be at some point in the future. It's important
+that your extension is completely translatable, so that users can easily translate the UI and any
+other content it provides into the languages their store supports.
+
+There's really no magic when it comes to making Solidus extensions translatable, so we recommend
+checking out the [Rails Internalization guide](https://guides.rubyonrails.org/i18n.html).
+
+### Avoid storefront code
+
+We strongly discourage extensions from attempting to alter or extend the storefront in any way,
+since Solidus storefronts come in a lot of different shapes and forms: some storefronts are
+monolithic and rely on plain old ERB, SASS, and JS; others use React, Vue, or Stimulus + View
+Components; others still use Solidus as a headless solution, interacting with the framework through
+the REST or GraphQL API.
+
+As you can imagine, it would be impossible for an extension to provide all the possible variations
+of storefront integrations. Furthermore, customizing the storefront code to fit the specific
+storefront's needs is almost always more work than attempting to integrate the extension in the
+storefront from scratch.
+
+:::caution
+
+In the past, some extensions used to provide storefront code, either through Deface overrides or in
+the form of new views that the user could include in their main storefront. We have since moved away
+from this practice and such code should be considered deprecated.
+
+:::
+
+What we do recommend is documenting your extension thoroughly, so that other developers can easily
+understand it and use it in different ways.
+
+Sometimes, it's also useful to provide some examples of storefront integrations that users can
+copy-paste or use as inspiration when integrating their extension in their own store!
+
+### Design extension hooks
+
+When designing your extension, you should always be on the lookout for ways to make it more...
+extendable. Just like Solidus users customize the core to reach their goals, you should also expect
+that they will want to customize the behavior of your extension to fit their use case.
+
+Often, you can let them do this through plain old configuration switches, but sometimes you can't
+anticipate all the possible use cases and it's simpler and more flexible to let users provide their
+own implementation for certain pieces of your extension.
+
+In our [original example](#writing-your-first-feature), a good candidate for an extension hook would
+be the API serialization logic, which could be implemented as:
+
+```ruby title="app/subscribers/solidus_acme_fulfillment/order_subscriber.rb"
+module SolidusAcmeFulfillment
+ class OrderSubscriber
+ # ...
+
+ def serialize_order(order)
+ SolidusAcmeFulfillment::OrderSerializer.new(order).serialize
+ end
+ end
+end
+```
+
+Users may want to pass custom fields to the 3PL API, or override the ones you're setting. To
+accomplish this easily, you can allow them to provide their own API serializer class.
+
+The process is pretty simple. First of all, add a configuration option:
+
+```diff title="lib/solidus_acme_fulfillment/configuration.rb"
+ module SolidusAcmeFulfillment
+ class Configuration
+ # ...
++ attr_accessor :order_serializer_class
++
++ def initialize
++ # Set the default order serializer to our own implementation.
++ @order_serializer_class = 'SolidusAcmeFulfillment::OrderSerializer'
++ end
+ end
+
+ # ...
+ end
+```
+
+Also, make sure to add the new option to your initializer template:
+
+```diff title="lib/generators/solidus_acme_fulfillment/install/templates/initializer.rb"
+ SolidusAcmeFulfillment.configure do |config|
++ # ....
++
++ # This class is used to serializer orders sent to the 3PL API.
++ # You can override it with your own implementation.
++ config.order_serializer_class = 'SolidusAcmeFulfillment::OrderSerializer'
+ end
+```
+
+Next, implement the default serializer, by extracting it from the code you already have:
+
+```ruby title="app/serializers/solidus:acme:fulfillment/order:serializer.rb"
+module SolidusAcmeFulfillment
+ class OrderSerializer
+ def call(order)
+ {
+ # ...
+ }
+ end
+ end
+end
+```
+
+Finally, call the configured serializer from the subscriber you've implemented:
+
+```diff title="app/subscribers/solidus_acme_fulfillment/order_subscriber.rb"
+ module SolidusAcmeFulfillment
+ class OrderSubscriber
+ # ...
+
+ def serialize_order(order)
+- SolidusAcmeFulfillment::OrderSerializer.new(order).serialize
++ SolidusAcmeFulfillment
++ .config
++ .order_serializer_class
++ .constantize
++ .new
++ .call(order)
+ end
+ end
+ end
+```
+
+That's all you need! Users of your extension can now provide their own API serializer by
+implementing it in their app and setting the `order_serializer_class` configuration option.
+
+Another great option to provide extensibility is to publish your own events on the Solidus' event
+bus. When you do that, it's a good practice to prefix their name with your extension's name.
+
+For instance, we can do better at error handling and broadcast whenever we have a failure in our
+response:
+
+```diff title="app/subscribers/solidus_acme_fulfillment/order_subscriber.rb"
+- order.update!(
+- acme_fulfillment_shipment_id: response.parsed_response['id'],
+- )
++ if response.success?
++ order.update!(
++ acme_fulfillment_shipment_id: response.parsed_response['id'],
++ )
++ else
++ Spree::Bus.publish(:"solidus_acme_fulfillment.response_error", response: response)
++ end
+```
+
+Don't forget to register the new event name in the engine file:
+
+```diff
+config.to_prepare do
++ Spree::Bus.register(:"solidus_acme_fulfillment.response_error")
+ SolidusAcmeFulfillment::OrderSubscriber.new.subscribe_to(Spree::Bus)
+end
+```
+
+### Automate testing with CI
+
+:::tip
+
+[CircleCI](https://circleci.com/) is an extremely powerful platform, and an in-depth explanation of
+its architecture is out of the scope of this guide. The following paragraphs assume you are familiar
+with CircleCI and [CircleCI Orbs](https://circleci.com/docs/2.0/orb-intro/). If you are not, we
+recommend reading the relevant documentation first.
+
+:::
+
+The Solidus ecosystem is extremely large and varied. For lots of stores with extensive
+customizations, upgrading as soon as a new version of Solidus is released is simply not feasible, as
+it would take too much work and distract the engineering department from other priorities. To give
+Solidus users a smooth upgrade path, we commit to maintaining all Solidus
+versions [for 18 months after their release](https://solidus.io/security). This ensures stores have
+plenty of time to upgrade their Solidus version.
+
+Official extensions follow the same policy, while community-maintained extensions are expected to do
+the same. We aim to support all currently maintained Solidus versions so that users on older
+versions are not "left behind" as the ecosystem moves forward. This means that all extensions should
+be tested against all the currently supported Solidus versions so that no incompatible changes are
+inadvertently introduced in the extension's code.
+
+We know that this can be a burden for extension maintainers, so we've developed a set of tools to
+help with the process, like
+the [`@solidusio/extensions` CircleCI orb](https://circleci.com/developer/orbs/orb/solidusio/extensions)
+. The orb will automatically test your Solidus extension against the right Solidus versions, without
+the need for you to update the versions list manually. The orb will even periodically test your
+extension against the latest `master` branch of Solidus, so that you know whether your extension is
+compatible with the _upcoming_ version of Solidus!
+
+:::info
+
+If you have generated your extension with `solidus_dev_support`, your extension is already
+configured for testing via CircleCI, and you just need
+to [follow the project](https://circleci.com/docs/2.0/project-build/#adding-projects) on CircleCI!
+
+:::
+
+Here's a sample CircleCI configuration for a Solidus extension:
+
+```yaml
+version: 2.1
+
+orbs:
+ solidusio_extensions: solidusio/extensions@volatile
+
+jobs:
+ # Test with MySQL
+ run-specs-with-mysql:
+ executor: solidusio_extensions/mysql
+ steps:
+ - solidusio_extensions/run-tests
+ # Test with PostgreSQL
+ run-specs-with-postgres:
+ executor: solidusio_extensions/postgres
+ steps:
+ - solidusio_extensions/run-tests
+
+workflows:
+ # Test all commits against the supported Solidus versions
+ # and the latest master branch from Solidus
+ Run specs on supported Solidus versions:
+ jobs:
+ - run-specs-with-postgres
+ - run-specs-with-mysql
+ # Weekly test the extension's master branch against the
+ # supported Solidus versions and the latest master branch
+ # from Solidus
+ Weekly run specs against master:
+ jobs:
+ - run-specs-with-postgres
+ - run-specs-with-mysql
+ triggers:
+ - schedule:
+ cron: 0 0 * * 4
+ filters:
+ branches:
+ only:
+ - master
+```
+
+As you can read in the comments, the configuration above will:
+
+* Test every commit in `master` and in other branches against the currently supported Solidus
+ versions, as well as against the latest `master`, in order to ensure the correctness of any code
+ changes you push to the extension.
+* Test the current `master` weekly against the currently supported Solidus versions, as well as
+ against the latest `master`, in order to ensure your extension's code is compatible with the
+ upcoming Solidus release.
+
+The tests will be run both with MySQL and PostgreSQL since Solidus supports both.
+
+### Write engine-specific code
+
+As you probably already know, Solidus doesn't come as a monolithic piece of code. Instead, you can
+switch the default frontend, backend, and API components for your own. When developing an extension,
+you can't just assume that the whole default ecosystem is present. The only thing you can be certain
+of is that the `solidus_core` gem is there.
+
+However, you might want to extend the behavior of one of the Solidus sub-components.
+If `solidus_dev_support` is your friend when it comes to the development chores, another
+gem, [`solidus_support`](https://github.com/solidusio/solidus\_support), has your back covered when
+talking about your extension runtime and its compatibility with different Solidus installations and
+versions. By the way, if you created your extension with `solidus_dev_support`, you don't need to
+add `solidus_support` to your list of dependencies as it's already there.
+
+Say that you'd like to add an API endpoint to return the associated partner's fulfillment id for a
+given order. Thanks to `solidus_support`, you can place your controller in `lib/controllers/api/`
+and it'll be automatically picked up.
+
+```ruby title="lib/controllers/api/spree/solidus_acme_fulfillment/shipments_controller.rb"
+# frozen_string_literal: true
+
+class Spree::SolidusAcmeFulfillment::ShipmentsController < Spree::Api::BaseController
+ def by_order_id
+ id = params[:id]
+ order = Spree::Order.find(id)
+
+ respond_with({
+ id: id,
+ acme_fulfillment_shipment_id: order.acme_fulfillment_shipment_id
+ })
+ end
+end
+```
+
+:::info
+
+By inheriting from `Spree::Api::BaseController`, you have access to some of Solidus API's
+conventions out of the box. For instance, your endpoint is automatically under authentication. See
+the [file definition](https://github.com/solidusio/solidus/blob/master/api/app/controllers/spree/api/base\_controller.rb)
+for more details.
+
+:::
+
+All that is left is adding the corresponding route to your extension's routes file. However, we need
+to add it only when the API component is present:
+
+```diff title="config/routes.rb"
+ Spree::Core::Engine.routes.draw do
+- # Add your extension routes here
++ if SolidusSupport.api_available?
++ namespace :solidus_acme_fulfillment do
++ get '/shipments/by_order_id/:id', to: 'shipments#by_order_id'
++ end
++ end
+ end
+```
+
+You can now try your new route, served by the sandbox application. After restarting the server, you
+can use `curl` to access it. You'll need a user's `spree_api_key` to authenticate them:
+
+```
+curl http://localhost:3000/solidus_acme_fulfillment/shipments/by_order_id/1 \
+ --header "Authorization: Bearer 44abb4fda97e1a22da9a837d8705a3f492392453c37164c7" \
+ --header "Accept: application/json"
+```
+
+:::info
+
+Note that you can also use the following path structures:
+
+* `lib/views/{engine}` (e.g., `lib/views/api`)
+* `lib/controllers/{engine}` (e.g., `lib/controllers/backend`)
+
+`solidus_support` is smart enough to pick them up and only load the views/controllers when the
+corresponding Solidus engine has been loaded!
+
+:::
+
+### Write backward-compatible extensions
+
+One aspect to keep in mind when writing Solidus extensions is backwards compatibility: ideally, your
+extension should always be compatible with all the currently supported Solidus versions.
+
+In some cases, this can be done with some simple tricks. In more complicated
+scenarios, `solidus_support` provides you with all the tools you need. We encourage you to check
+out [`solidus_support`'s source code](https://github.com/solidusio/solidus\_support) to familiarize
+with the helpers it offers.
+
+#### Migrations
+
+By default, Rails generated the migration you created as being compatible with its latest version.
+However, Solidus also supports older releases. Your migrations should be compatible with the oldest
+Rails version still supported by Solidus:
+
+```diff title="db/migrate/20220602090213_add_acme_fulfillment_shipment_id_to_spree_orders.rb"
+-class AddAcmeFulfillmentShipmentIdToSpreeOrders < ActiveRecord::Migration[7.0]
++class AddAcmeFulfillmentShipmentIdToSpreeOrders < ActiveRecord::Migration[5.2]
+```
+
+#### Event bus
+
+`solidus_support` is also your friend when you want to support past Solidus versions which are still
+maintained. The event subscriber we created is only valid for Solidus versions greater than 3.2.
+Before that, [a more basic messaging system was available][subscribing-to-events].
+
+By including the `SolidusSupport::LegacyEventCompat::Subscriber` module, you'll be able to support
+both event subscribers:
+
+```diff title="app/subscribers/solidus_acme_fulfillment/order_subscriber.rb"
+ module SolidusAcmeFulfillment
+- class OrderSubscriber
+- include Omnes::Subscriber
++ module OrderSubscriber
++ include Spree::Event::Subscriber
++ include SolidusSupport::LegacyEventCompat::Subscriber
+
+- handle :order_finalized, with: :send_to_3pl
++ event_action :send_to_3pl, event_name: :order_finalized
+```
+
+Finally, you need to update your engine file to register your extension's events, but only when the
+new event subscriber is being used:
+
+```diff title="lib/solidus_acme_fulfillment/engine.rb"
+config.to_prepare do
+- Spree::Bus.register(:"solidus_acme_fulfillment.response_error")
+- SolidusAcmeFulfillment::OrderSubscriber.new.subscribe_to(Spree::Bus)
++ unless SolidusSupport::LegacyEventCompat.using_legacy?
++ Spree::Bus.register(:"solidus_acme_fulfillment.response_error")
++ SolidusAcmeFulfillment::OrderSubscriber.omnes_subscriber.subscribe_to(Spree::Bus)
++ end
+end
+```
+
+:::info
+
+`#omnes_subscriber` returns the legacy subscriber transformed in the new format, so that it works
+with the new event bus.
+
+:::
+
+[using-extensions]: /getting-started/using-extensions.mdx
+[customizing-core]: /customization/customizing-the-core.mdx
+[subscribing-to-events]: /customization/subscribing-to-events.mdx
diff --git a/versioned_docs/version-4.2/advanced-solidus/images-and-image-processing.mdx b/versioned_docs/version-4.2/advanced-solidus/images-and-image-processing.mdx
new file mode 100644
index 0000000..5456af7
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/images-and-image-processing.mdx
@@ -0,0 +1,173 @@
+---
+sidebar_position: 3
+needs-diataxis-rewrite: true
+---
+
+# Image processing
+
+## Architecture overview
+
+In a typical Solidus store, you can upload images for products and taxons.
+
+Under the hood, Solidus will use a different file processing library depending on the Rails
+version: [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html) is the
+default starting from Rails 6.1,
+while [Paperclip](https://github.com/thoughtbot/paperclip#paperclip) is used in earlier versions.
+
+:::caution
+
+Active Storage cannot be used in Rails 6.0 or earlier because it doesn't support public URLs, which
+Solidus needs to serve images to your users. If you're on Rails 6.0 or earlier, you _have_ to use
+Paperclip.
+
+:::
+
+While Paperclip is deprecated and will be removed in the near future, Solidus provides a
+compatibility layer that abstracts the differences between the two libraries, in order to offer an
+easier migration path to existing Paperclip users.
+
+## Customizing image sizes
+
+By default, Solidus uses the following sizes for product images:
+
+* `mini`: 48x48
+* `small`: 400x400
+* `product`: 680x680
+* `large`: 1200x1200
+
+and the following sizes for taxon icons:
+
+* `mini`: 32x32
+* `normal`: 128x128
+
+You can access the URL for a specific size by calling `Spree::Image#url`:
+
+```ruby
+image = Spree::Product.first.gallery.images.first
+image.url(:product)
+```
+
+If you're building a custom storefront, you may also want to change the sizes of
+the images in your store or add additional sizes. The default sizes can be changed
+using the `Spree::Config.product_image_styles` option.
+
+For example, we can set some new defaults and introduce a new `:jumbo` style
+like this:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+ config.product_image_styles = {
+ mini: '48x48>',
+ small: '100x100>',
+ product: '240x240>',
+ large: '600x600>',
+ jumbo: '1600x1600>'
+ }
+end
+```
+
+Similar to product styles, you can customize the taxon image styles using the
+`Spree::Config.taxon_image_styles` configuration option.
+
+:::caution
+
+Active Storage will automatically generate sizes upon initial request.
+If you change the default image sizes and are using Paperclip, you must
+regenerate the styles by running a Rake task:
+
+```bash
+bundle exec rake paperclip:refresh:thumbnails CLASS=Spree::Image
+```
+
+or if you are only adding new styles, you can run the following task:
+
+```bash
+bundle exec rake paperclip:refresh:missing_styles CLASS=Spree::Image
+```
+
+:::
+
+Now that you changed your sizes, try getting the URL for your new `jumbo` or `mini` sizes:
+
+```ruby
+image = Spree::Product.first.gallery.images.first
+image.url(:jumbo)
+
+icon = Spree::Taxon.first.icon
+icon.url(:mini)
+```
+
+You can also use your new styles in the `image` partial in the backend:
+
+```ruby
+<%= render 'spree/admin/shared/image', image: product.gallery.images.first, size: :jumbo %>
+```
+
+and if you are using `solidus_starter_frontend` for your storefront like this:
+
+```ruby
+<%= render(
+ ImageComponent.new(
+ image: product.gallery.images.first,
+ size: :jumbo,
+ itemprop: "image",
+ data: { js: 'product-main-image' }
+ )
+) %>
+```
+
+## Customizing the allowed MIME types
+
+By default, Solidus only accepts PNG, JPEG and GIF images. If you want to accept additional MIME
+types, e.g. WebP, you can do it via the `allowed_image_mime_types` configuration option:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.allowed_image_mime_types = %w(image/jpeg image/jpg image/png image/gif image/webp).freeze
+end
+```
+
+## Replacing an attachment module
+
+If the change you want to apply cannot be made through the existing configuration options, you can
+entirely replace the product image or taxon icon attachment module with your own.
+
+This can be useful, for example, if you need to customize your image styles or perform custom
+post-processing operations on your image such as watermarking, compression, etc.
+
+:::info
+
+When replacing an attachment module, we recommend copy-pasting the original module first and only
+changing what you need. The right starting point will depend on whether you're using ActiveStorage
+or Paperclip. You can find the modules
+for `Spree::Image` [here](https://github.com/solidusio/solidus/tree/v3.0/core/app/models/spree/image)
+and the modules
+for `Spree::Taxon` [here](https://github.com/solidusio/solidus/tree/v3.0/core/app/models/spree/taxon).
+
+:::
+
+Here's an example for the product image attachment:
+
+```ruby title="app/models/amazing\_store/image\_attachment.rb"
+module AmazingStore
+ module ImageAttachment
+ # ...
+ end
+end
+```
+
+Once you have your custom attachment module, you need to tell Solidus to use it:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+ config.image_attachment_module = 'AmazingStore::ImageAttachment'
+end
+```
+
+To replace the taxon attachment, follow the same process, but set the `taxon_attachment_module`
+configuration option instead.
+
diff --git a/versioned_docs/version-4.2/advanced-solidus/images/payment-methods.png b/versioned_docs/version-4.2/advanced-solidus/images/payment-methods.png
new file mode 100644
index 0000000..1f5c4b4
Binary files /dev/null and b/versioned_docs/version-4.2/advanced-solidus/images/payment-methods.png differ
diff --git a/versioned_docs/version-4.2/advanced-solidus/images/tax-rates.png b/versioned_docs/version-4.2/advanced-solidus/images/tax-rates.png
new file mode 100644
index 0000000..747aa30
Binary files /dev/null and b/versioned_docs/version-4.2/advanced-solidus/images/tax-rates.png differ
diff --git a/versioned_docs/version-4.2/advanced-solidus/model-preferences.mdx b/versioned_docs/version-4.2/advanced-solidus/model-preferences.mdx
new file mode 100644
index 0000000..0bfc46e
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/model-preferences.mdx
@@ -0,0 +1,334 @@
+---
+sidebar_position: 4
+needs-diataxis-rewrite: true
+---
+
+# Model preferences
+
+## Why are they needed?
+
+We've already
+covered [application-wide preferences](../getting-started/installing-solidus.mdx#first-time-configuration).
+Model preferences have a different scope, as they apply to a single ActiveRecord model and can take
+different values for each instance or record.
+
+You might think that's what regular ActiveRecord attributes are for. However, preferences are handy
+when
+using [Single Table Inheritance](https://api.rubyonrails.org/classes/ActiveRecord/Inheritance.html) (
+STI), i.e., when different models share the same database table. In those cases, you might need to
+add a piece of information only for one of them and avoid creating a new column with a `NULL` value
+for inapplicable models.
+
+:::danger
+
+Internally, preferences are handled as
+a [serializable ActiveRecord attribute](https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html)
+. Notice that serialized data is persisted as a raw string in the database engine. That means
+they're okay to read information from a loaded record, but they're not suited to perform SQL queries
+on a large dataset.
+
+:::
+
+## Preferable models
+
+:::caution
+
+Solidus preferences are the right option when youβre writing code that is tightly coupled to
+Solidus (e.g., it's a descendant of a preferable Solidus class, or it will be configurable via the
+Solidus admin panel).
+
+If you're writing code that doesn't belong to the Solidus domain (e.g., your own application's
+business logic), it's almost always better to forgo preferences and use regular ActiveRecord
+attributes instead.
+
+:::
+
+To make a model accept preferences (i.e., to make it "preferable"), you will first need to add
+a `preferences` column of type `text` to your model. Assuming you already have a model
+named `Greeter`, here's how to do it:
+
+```
+$ rails g migration AddPreferencesToGreeter preferences:text
+$ rails db:migrate
+```
+
+Then, you'll need to include the `Spree::Preferences::Persistable` module in your model:
+
+```ruby
+# frozen_string_literal: true
+
+require 'spree/preferences/persistable'
+
+module AmazingStore
+ class Greeter
+ include Spree::Preferences::Persistable
+ end
+end
+```
+
+And there you go! Your model is now preferable. Let's see how to use it.
+
+## Adding new preferences
+
+Adding a new preference is as simple as calling the `preference` method in your class:
+
+```diff
+# frozen_string_literal: true
+
+require 'spree/preferences/persistable'
+
+module AmazingStore
+ class Greeter
+ include Spree::Preferences::Persistable
+
++ preference :name, :string, default: "John"
+ end
+end
+```
+
+That's it! As you can see, you can also specify a default value with the `:default` option.
+
+:::info
+
+Besides `:string`, other types can be used for a preference.
+See [`Spree::Preferences::Preferable`](https://github.com/solidusio/solidus/blob/master/core/lib/spree/preferences/preferable.rb)
+for details.
+
+:::
+
+## Accessing preferences
+
+A few magic methods are generated for each preference in a model instance. For example, given
+a `:category` preference, the following methods will be generated:
+
+* `#preferred_category`: reader for the preference value.
+* `#preferred_category=(value)`: writer for the preference value (no persistence).
+* `#preferred_category_default`: default for the preference.
+* `#preferred_category_type`: type for the default preference.
+
+```ruby
+calculator = MyStore::NewTaxCalculator
+calculator.preferred_category = "B"
+calculator.preferred_category # => "B"
+calculator.preferred_category_default # => "A"
+calculator.preferred_category_type # => :string
+```
+
+Solidus also provides some generic methods:
+
+* `#get_preference(name)`: Returns the value for the given preference.
+* `#set_preference(name, value)`: Sets the value for the given preference (no persistence).
+* `#preference_default(name)`: Gets the default for the given preference.
+* `#preference_type(name)`: Gets the type for the given preference.
+* `#has_preference?(name)`: Predicate to check whether a preference is defined.
+
+```ruby
+calculator.set_preference(:category, "B")
+calculator.get_preference(:category) # => "B"
+calculator.preference_default(:category) # => "A"
+calculator.preference_type(:category) # => :string
+calculator.has_preference?(:category) # => true
+calculator.has_preference?(:other) # => false
+```
+
+Finally, some methods allow inspecting the entire collection of preferences:
+
+* `#preferences`: Hash of preferences.
+* `#default_preferences`: Hash of default preferences.
+
+```ruby
+calculator.preferences # => { :category=>"B" }
+calculator.default_preferences # => { :category=>"A" }
+```
+
+To bring it all together, let's use our `name` preference in the `Greeter` model to do something:
+
+```ruby
+# frozen_string_literal: true
+
+require 'spree/preferences/persistable'
+
+module AmazingStore
+ class Greeter
+ include Spree::Preferences::Persistable
+
+ preference :name, :string, default: "John"
+
+ def call
+ "Hello, #{name}!"
+ end
+ end
+end
+```
+
+Let's test it:
+
+```ruby
+AmazingStore::Greeter.new.call # => "Hello, John!"
+AmazingStore::Greeter.new(preferred_name: "Jane").call # => "Hello, Jane!"
+```
+
+Neat!
+
+## Static preferences
+
+Sometimes, you might not want to store preferences in your database. This is the case, for example,
+with API credentials or other sensitive information. [The Twelve-Factor App](https://12factor.net/)
+recommends storing this data in an environment variable.
+
+Solidus supports this mechanism, which is called "static preferences", out of the box.Let's
+repurpose our `Greeter` model to use them!
+
+First of all, we need to add a `preference_source` column (we'll see why in a second):
+
+```
+$ rails g migration AddPreferenceSourceToGreeter preference_source
+$ rails db:migrate
+```
+
+Then, we need to include the `Spree::Preferences::StaticallyConfigurable` module:
+
+```diff
+# frozen_string_literal: true
+
+require 'spree/preferences/persistable'
++ require 'spree/preferences/statically_configurable'
+
+module AmazingStore
+ class Greeter
+ include Spree::Preferences::Persistable
++ include Spree::Preferences::StaticallyConfigurable
+
+ preference :name, :string
+ end
+end
+```
+
+Finally, we'll need to configure one or more preference sources, i.e. the actual collection of
+preference values that can be used for instances of our `AmazingStore::Greeter` model.
+
+This can be done in any initializer, including `config/initializers/spree.rb`:
+
+```ruby title="config/initializers/spree.rb"
+Spree::Config.static_model_preferences.add(
+ 'AmazingStore::Greeter',
+ 'greeter_preferences',
+ name: ENV["GREETER_NAME"],
+)
+```
+
+Let's test it again!
+
+```ruby
+ENV["GREETER_NAME"] = "Jane" #Β To simulate an environment variable
+
+AmazingStore::Greeter.new(
+ preference_source: "greeter_preferences",
+).call # => "Hello, Jane!"
+```
+
+In our example, we are using environment variables to provide the preference values, but you can use
+a secret store such as [Vault](https://www.vaultproject.io/) or anything else!
+
+## Built-in preferable models
+
+Solidus comes with a few preferable models out of the box,
+like [`Spree::PaymentMethod`](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/payment\_method.rb)
+and [`Spree::Calculator`](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator.rb)
+. In most cases, you will just inherit from these classes, which means you won't need any of the
+boilerplate setup code. Instead, you will just add new preferences to your custom descendant.
+
+Let's see a couple of examples!
+
+### Calculators
+
+Say that you want to create a [calculator](tax-calculation.mdx#customizing-tax-calculation) for a new
+tax whose amount depends on a categorization of some type. Calculators
+are [already configurable](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator.rb#L7)
+, so you can create a new class that inherits from `Spree::Calculator` and add a `category`
+preference to it:
+
+```ruby title="app/models/amazing_store/new_tax_calculator.rb"
+# frozen_string_literal: true
+
+module AmazingStore
+ class NewTaxCalculator < Spree::Calculator
+ preference :category, :string, default: "A"
+
+ AMOUNT_PER_CATEGORY = {
+ "A" => 10,
+ "B" => 20,
+ "C" => 30
+ }
+
+ def initialize(order)
+ @order = order
+ end
+
+ def calculate
+ Spree::Tax::OrderTax.new(
+ order_id: order.id,
+ line_item_taxes: order.amount + AMOUNT_PER_CATEGORY[preferred_category],
+ shipment_taxes: 0
+ )
+ end
+ end
+end
+```
+
+:::info
+
+You'll notice we don't have to include any files or module in our payment method. That's
+because `Spree::PaymentMethod` [already does it for us!](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/payment\_method.rb#L15)
+
+:::
+
+That's it! You can now use your `AmazingStore::NewTaxCalculator`.
+
+### Payment methods
+
+Payment methods are another example of a configurable class in Solidus. Furthermore, they're
+statically configurable, so that you can store your payment provider credentials outside of the DB.
+
+Say, for example, that you've followed the guide
+to [create a new payment method](payments-and-refunds.mdx#building-a-custom-payment-source), and all
+that's left to do is to add a configurable API key to your custom payment method. You can do it like
+this:
+
+```ruby title="app/models/amazing_store/amazing_payment_method.rb"
+# frozen_string_literal: true
+
+module AmazingStore
+ class AmazingPaymentMethod < Spree::PaymentMethod
+ preference :api_key, :string
+ end
+end
+```
+
+As you see, we've added an `api_key` preference for it, but we don't want its value to be rendered
+in plain sight in the backend, and we don't want it to be stored in the database. Let's, therefore,
+add a source from where the new payment method can read its preferences:
+
+```ruby title="config/initializers/spree.rb"
+# ...
+Spree::Config.static_model_preferences.add(
+ 'AmazingStore::AmazingPaymentMethod',
+ 'amazing_payment_method_credentials',
+ api_key: ENV['AMAZING_PAYMENT_METHOD_API_KEY'],
+ server: Rails.env.production? ? 'production' : 'test',
+ test_mode: !Rails.env.production?
+)
+```
+
+:::info
+
+If you're wondering where those `server` and `test_mode` settings come from, they're common
+preferences inherited from [`Spree::PaymentMethod`](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/payment\_method.rb).
+
+:::
+
+You can now to _Settings -> Payments -> New Payment Method_, create a new instance of
+your `AmazingPaymentMethod`, and pick `amazing_payment_method_credentials` as the preference source
+to read the API key from the environment!
+
+###
diff --git a/versioned_docs/version-4.2/advanced-solidus/payments-and-refunds.mdx b/versioned_docs/version-4.2/advanced-solidus/payments-and-refunds.mdx
new file mode 100644
index 0000000..90fb934
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/payments-and-refunds.mdx
@@ -0,0 +1,1110 @@
+---
+sidebar_position: 5
+needs-diataxis-rewrite: true
+---
+
+# Payments and refunds
+
+:::info
+
+This guide covers the architecture and functionality of payments and refunds in Solidus, which is
+useful when you need to customize the payment flow or integrate with a custom payment gateway.
+However, Solidus comes with integrations for the major payment service providers. If that's what
+you're looking for, check out
+our [Payments](https://solidus.io/extensions/#extensions-group-payments) extensions.
+
+:::
+
+## Domain concepts
+
+Solidus comes with a modular, powerful system for managing payments and refunds.
+
+The system is designed in layers, with each layer wrapping the one below to perform an additional
+level of abstraction. This allows for more granular customization, and makes it flexible enough that
+it can be adapted to any kind of payment service provider and payment/refunds flow.
+
+In the next paragraphs, we'll go through the main pieces that make up Solidus' payment system, and
+give an overview of how they work with each other.
+
+### Payment gateways
+
+Payment gateways are the smallest unit of the payment system: each payment gateway provides the
+logic for integrating with the API of a specific PSP (Payment Service Provider).
+
+Most payment gateways are built on top
+of [ActiveMerchant](https://github.com/activemerchant/active\_merchant), a popular Ruby library for
+integrating with PSPs. You can learn more about the architecture on
+the [ActiveMerchant documentation](https://github.com/activemerchant/active\_merchant/wiki).
+
+### Payment sources
+
+:::info
+
+Credit cards are the most common example of payment sources, and Solidus provide a
+good [out-of-the-box implementation](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/credit\_card.rb)
+for credit-card-based payment sources.
+
+:::
+
+[Payment sources](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment\_source.rb)
+, as the name suggests, are models whose purpose is to store information about the different ways a
+user can pay.
+
+Each payment source is backed by its own DB table and exposes different information to Solidus about
+the actions that can be taken on it and whether it is reusable for future payments.
+
+Reusable payment sources can be added to a
+user's [wallet](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/wallet.rb), in
+which case they can be picked by the user for future checkouts as well.
+
+### Payment methods
+
+:::info
+
+Solidus provides out-of-the-box implementations
+for [check](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment\_method/check.rb)
+and [store credit](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment\_method/store\_credit.rb)
+payment methods, and a base implementation
+for [credit card](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment\_method/credit\_card.rb)
+payment methods.
+
+:::
+
+Payment methods represent different ways customers can pay in your store. For example, you may have
+different payment methods for PayPal and Stripe.
+
+Different payment methods support different features, depending on the underlying PSP:
+
+* **Payment sources.** Some payment methods, like check payments or bank transfers, don't need a
+ payment source to "draw" money from, e.g. because the payment is done off-site.
+* **Payment profiles.** Some payment methods support vaulting, i.e., the ability to store a payment
+ source so that it can be charged later. When that's the case, Solidus will attempt to vault the
+ payment source after creating a payment for it.
+
+:::info
+
+Not all payment methods are tied to a PSP. For example, the check and store credit payment methods
+that Solidus ships with don't need to interact with a PSP to process payments. You can think of
+these as "virtual" payment methods.
+
+:::
+
+Solidus doesn't know much about payment gateways as such: instead, payment gateways are just "helper
+classes." When processing payments and refunds, Solidus interacts with the payment method, not the
+payment gatewayβthe payment method can then delegate the calls to the payment gateway with no
+modifications ([which is the default](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment\_method.rb#L40))
+, or provide its own implementation that wraps the payment gateway's behavior and enriches it with
+additional logic.
+
+This abstraction provides several advantages:
+
+* It allows you to encapsulate the API interaction logic for your PSP and test it in isolation,
+ whereas the payment method's implementation can deal with Solidus-specific details.
+* It allows you to easily implement payment methods that don't use a PSP (e.g. cash on delivery),
+ without Solidus having to know about this nuance.
+* It allows you to use the store's configuration to determine how to structure the API calls to the
+ PSP, and to enrich the API payload with store-specific information (e.g., to add your store's name
+ to your customer's credit card statement).
+
+### Payments
+
+The [`Spree::Payment`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment.rb)
+model act as a connector between user input and the rest of the payment system. Payments are
+associated to an order, a payment source and a payment method.
+
+Payments have
+a [state machine](https://github.com/solidusio/solidus/blob/v3.0/core/lib/spree/core/state\_machines/payment.rb)
+that tracks the status of the payment as it goes through the regular payment processing flow.
+Through
+the [`Spree::Payment::Processing`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/processing.rb)
+module, payments expose a simplified API that protects the rest of the system from PSP connection
+errors and data integrity issues, and integrates PSP operations with the payments state machine.
+
+### Refunds
+
+Refunds are a critical part of an eCommerce business and they are issued on a regular basis to
+customers for a variety of reasons.
+
+In Solidus, refunds are represented by
+the [`Spree::Refund`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/refund.rb)
+model, and they are associated to their respective payment. Modeling refunds as a separate concept
+from payments makes them very flexible and allows to use them for a broad range of use cases.
+
+Refunds have a very simple API and they only expose
+a [`#perform!`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/refund.rb#L42)
+method.
+
+## Flow walkthroughs
+
+### Payment processing
+
+:::caution
+
+Payment processing is tightly coupled to the payments state machine API. If you customize or replace
+the payments state machine, you will need to make sure that your customizations play well with the
+payment processing flow!
+
+:::
+
+The flow for payment processing, in a standard Solidus store, is the following:
+
+* A payment is created, either by the customer or by a store admin.
+* Before the order is
+ completed, [`process_payments_before_complete`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/order.rb#L726)
+ method is called on the order. This
+ calls [`process_payments!`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/order/payments.rb#L22)
+ which, in turn,
+ calls [`process!`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/processing.rb#L25)
+ on all unprocessed payments.
+* Depending on whether the payment method uses auto-capture, `process!` either authorizes or
+ authorizes and captures the payment.
+*
+
+The [`authorize!`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/processing.rb#L37)
+or [`purchase!`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/processing.rb#L42)
+call performs
+the [corresponding action on the payment method](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/processing.rb#L176)
+and [updates the payment's state](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/processing.rb#L185)
+accordingly.
+
+At this point, all payments on the order have been processed and they're either authorized or
+captured. If they're just authorized, an admin will need to capture the payment manually at a later
+stage (e.g., when the order is shipped), which will call
+the [`capture!`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/processing.rb#L49)
+method.
+
+### Payment cancellation
+
+:::info
+
+The [`Spree::Payment::Cancellation`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/cancellation.rb)
+class is responsible for cancelling payments in a stock Solidus installation, but you can easily
+replace it with your own.
+
+:::
+
+If an order is cancelled at any point, Solidus will also "cancel" any payments on the order. The
+flow for payment cancellation is quite straightforward:
+
+1. When an order is cancelled,
+ the [state machine](https://github.com/solidusio/solidus/blob/v3.0/core/lib/spree/core/state\_machines/order.rb#L124)
+ calls
+ the [`#after_cancel`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/order.rb#L800)
+ method on the order.
+2. `#after_cancel` loops through all payments, discards the ones that have been fully refunded or
+ are not in a cancellable state, and
+ calls [`#cancel!`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/processing.rb#L87)
+ on the remaining ones.
+3. `#cancel!` [instantiates and run a payment canceller](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/processing.rb#L87)
+ for the payment.
+4.
+
+The [default payment canceller](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/cancellation.rb)
+has different logic depending on the state of the payment:
+
+1. If the payment has been captured, it refunds the customer (
+ see [Refund processing](payments-and-refunds.mdx#refund-processing)).
+2. If the payment has only been authorized, it voids the authorization.
+
+### Refund processing
+
+In a stock Solidus store, refunds and they can be created in different ways:
+
+* manually via the admin UI;
+* automatically, when [cancelling a payment](payments-and-refunds.mdx#payment-cancellation);
+* automatically, when processing a reimbursement.
+
+Some stores also have other ways for a refund to be created, e.g. through a return flow that can be
+initiated by the customer via the storefront or a third-party tool.
+
+When a refund is created through the UI, via a payment cancellation or a reimbursement, Solidus will
+also immediately call `#perform!` on the refund. This processes the refund through the original
+payment's payment method and updates the payment and order accordingly.
+
+## Customizing the payment system
+
+In the next paragraphs, we'll see how to customize different aspects of Solidus' payment system.
+
+Note that this is an advanced use case and is only required in very specific scenarios. In most
+cases, you'll be better off using one of the existing payment integrations.
+
+### Custom payment gateways
+
+:::info
+
+Most payment integrations in Solidus don't ship with a custom payment gateway. Instead, they rely on
+one of the payment gateways provided
+by [ActiveMerchant](https://github.com/activemerchant/active\_merchant).
+
+If you need to integrate with a PSP that's not supported by Solidus, you should first look and see
+whether ActiveMerchant already provides the payment gateway you need: if that's the case, you will
+only need to implement a custom payment method and source.
+
+:::
+
+Implementing a custom payment gateway can be useful if you're integrating with a lesser-known PSP (
+e.g., a local PSP in your country), or if you need to deeply customize an existing PSP integration.
+
+#### The payment gateway API
+
+Payment gateways expose the following API:
+
+* `#initialize(options)`: initializes the gateway with the provided options. By default, Solidus
+ will pass
+ the [payment method's preferences](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment\_method.rb#L78)
+ in here.
+* `#authorize(money, source, options = {})`: authorizes a certain amount on the provided payment
+ source.
+* `#capture(money, transaction_id, options = {})`: captures a certain amount from a previously
+ authorized transaction.
+* `#purchase(money, source, options = {})`: authorizes and captures a certain amount on the provided
+ payment source.
+* `#void(transaction_id, [source,] options = {})`: voids a previously authorized transaction,
+ releasing the funds that are on hold. The `source` parameter is only needed for payment gateways
+ that support payment profiles.
+* `#credit(money, [source,] transaction_id, options = {})`: refunds the provided amount on a
+ previously captured transaction. The `source` parameter is only needed for payment gateways that
+ support payment profiles.
+
+All of these methods are expected to return
+an [`ActiveMerchant::Billing::Response`](https://github.com/activemerchant/active\_merchant/blob/v3.0/lib/active\_merchant/billing/response.rb)
+object containing the result and details of the operation. Payment gateways never raise exceptions
+when things go wrong: they only return response objects that represent successes or failures, and
+Solidus handles control flow accordingly.
+
+:::caution
+
+For historical and technical reasons, the Solidus API for payment gateways deviates from the
+ActiveMerchant API in a few ways:
+
+* The `source` parameter that is passed to a gateway will be an instance of `Spree::PaymentSource`,
+ while ActiveMerchant gateways expect their own models.
+* The `#void` and `#credit` method in ActiveMerchant never accepts a payment source. Solidus will
+ pass the payment source to `#void` and `#credit` if the payment method supports payment profiles.
+* The `#credit` method in ActiveMerchant gateways does not accept a transaction ID but a payment
+ method token (e.g., credit card token), since it can be used to credit funds to a payment source
+ even in the absence of a previous charge. What Solidus calls `#credit` is actually
+ called `#refund` in ActiveMerchant.
+
+For custom payment gateways, these discrepancies are not a problem, since the gateways can be
+implemented to respond to the API expected by Solidus.
+
+For ActiveMerchant gateways, the payment method will wrap these methods instead of delegating them
+to the gateway directly, and will transform the method calls and their arguments to comply with
+ActiveMerchant's interface.
+
+:::
+
+#### Building your gateway
+
+To build your gateway, you just need to create a new class that responds to the Gateway API. In this
+example, we'll build a new payment gateway for our beloved SolidusPay which provides a nice RESTful
+API for managing payments and refunds.
+
+The first step would be to build the skeleton of our gateway. For the time being, we'll just make
+sure we store the API key that's passed when initializing the gateway, and we'll write a small
+helper on top of the HTTParty gem for interacting with the PSP's API:
+
+```ruby title="app/models/solidus:pay/gateway.rb"
+module SolidusPay
+ class Gateway
+ API_URL = 'https://soliduspay.com/api/v1'
+
+ attr_reader :api_key
+
+ def initialize(options)
+ @api_key = options.fetch(:api_key)
+ end
+
+ private
+
+ def request(method, uri, body = {})
+ HTTParty.send(
+ method,
+ "#{API_URL}#{uri}",
+ headers: {
+ "Authorization" => "Bearer #{api_key}",
+ "Content-Type" => "application/json",
+ "Accept" => "application/json",
+ },
+ body: body.to_json,
+ )
+ end
+ end
+end
+```
+
+Now that we have everything we need to interact with the API, we can start writing the actual
+integration. The first step in processing a payment is usually authorizing it, so we'll start with
+that:
+
+```ruby title="app/models/solidus:pay/gateway.rb"
+module SolidusPay
+ class Gateway
+ # ...
+
+ def authorize(money, auth_token, options = {})
+ response = request(
+ :post,
+ "/charges",
+ payload_for_charge(money, auth_token, options).merge(capture: false),
+ )
+
+ if response.success?
+ ActiveMerchant::Billing::Response.new(
+ true,
+ "Transaction Authorized",
+ {},
+ authorization: response.parsed_response['id'],
+ )
+ else
+ ActiveMerchant::Billing::Response.new(
+ false,
+ response.parsed_response['error'],
+ )
+ end
+ end
+
+ private
+
+ # ...
+
+ def payload_for_charge(money, auth_token, options = {})
+ {
+ auth_token: auth_token,
+ amount: money,
+ currency: options[:currency],
+ description: "Payment #{options[:order_id]}",
+ billing_address: options[:billing_address],
+ }
+ end
+ end
+end
+```
+
+The logic for the `#authorize` method is fairly straightforward: it makes a POST request to
+the `/api/v1/charges` endpoint of the PSP's API. The body of the request includes information about:
+
+* the amount we want to charge, which is passed in the `amount` parameter as a number of cents (
+ e.g., `1000` represents 10.00, `1050` represents 10.50),
+* the payment source we want to charge, which is passed in the `auth_token` parameter,
+* [metadata about the order and the payment](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment/processing.rb#L91)
+ , which is passed in the `options` parameter.
+
+The method then returns an `ActiveMerchant::Billing::Response` object that represents the success or
+failure of the operation. In the successful response, the `:authorization` option will also be
+included. Solidus will store the value of this option on the `response_code` attribute on
+the `Spree::Payment` record, so that we can reference the transaction ID when
+capturing/voiding/refunding it later.
+
+You'll notice that the payload of the request is generated in a helper method, `#payload_for_charge`
+. This is because the payloads for authorizing and purchasing (i.e., authorizing and capturing in
+one go) is the same, minus the `:capture` option, which we'll set to `true` when we also want to
+capture the amount:
+
+```ruby title="app/models/solidus:pay/gateway.rb"
+module SolidusPay
+ class Gateway
+ # ...
+
+ def purchase(money, auth_token, options = {})
+ response = request(
+ :post,
+ "/charges",
+ payload_for_charge(money, auth_token, options).merge(capture: true),
+ )
+
+ if response.success?
+ ActiveMerchant::Billing::Response.new(
+ true,
+ "Transaction Purchased",
+ {},
+ authorization: response.parsed_response['id'],
+ )
+ else
+ ActiveMerchant::Billing::Response.new(
+ false,
+ response.parsed_response['error'],
+ )
+ end
+ end
+
+ # ...
+ end
+end
+```
+
+:::info
+
+Some PSPs, such as Stripe, provide a single endpoint for authorizing and capturing a payment in one
+request. Others will require you to perform two different requests, in which case your `#purchase`
+method may simply call `#authorize` and `#capture` in succession.
+
+:::
+
+The rest of our gateway (`#capture`, `#void` and `#credit`) is trivial and pretty similar to our
+existing methods. We just call our PSP to perform a certain operation on an existing transaction:
+
+```ruby title="app/models/solidus:pay/gateway.rb"
+module SolidusPay
+ class Gateway
+ # ...
+
+ def capture(money, transaction_id, options = {})
+ response = request(
+ :post,
+ "/charges/#{transaction_id}/capture",
+ { amount: money },
+ )
+
+ if response.success?
+ ActiveMerchant::Billing::Response.new(true, "Transaction Captured")
+ else
+ ActiveMerchant::Billing::Response.new(
+ false,
+ response.parsed_response['error'],
+ )
+ end
+ end
+
+ def void(transaction_id, options = {})
+ response = request(:post, "/charges/#{transaction_id}/refunds")
+
+ if response.success?
+ ActiveMerchant::Billing::Response.new(true, "Transaction Voided")
+ else
+ ActiveMerchant::Billing::Response.new(
+ false,
+ response.parsed_response['error'],
+ )
+ end
+ end
+
+ def credit(money, transaction_id, options = {})
+ response = request(
+ :post,
+ "/charges/#{transaction_id}/credit",
+ { amount: money },
+ )
+
+ if response.success?
+ ActiveMerchant::Billing::Response.new(true, "Transaction Credited")
+ else
+ ActiveMerchant::Billing::Response.new(
+ false,
+ response.parsed_response['error'],
+ )
+ end
+ end
+
+ # ...
+ end
+end
+```
+
+At this point, we have our custom payment gateway which encapsulates all API interaction logic with
+the PSP, and we can use it as part of a custom payment method.
+
+### Custom payment sources
+
+:::info
+
+Not all payment methods require a custom payment source. Instead, you may want to simply rely on the
+existing [`Spree::CreditCard`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/credit\_card.rb)
+payment source, which provides some useful logic for working with "credit card" types of payment
+sources.
+
+:::
+
+Creating a custom payment source may be needed if you are integrating with a new PSP that is not
+credit-card based. This will be the case, for instance, when a customer pays through their PSP
+account's balance rather than via a specific credit card (e.g., "Pay with PayPal"). It's also a
+common setup with financing PSPs such as Affirm or AfterPay: in this case, the PSP itself is the "
+source" of money.
+
+#### The payment source API
+
+Payment sources respond to a very simple API which tells Solidus what operations can or cannot be
+performed on the payment source. Solidus will use this information to display/hide certain actions
+on the backend, or to control the order processing flow:
+
+* `#actions`: returns an array of actions that can _generally_ be performed on payments with this
+ payment source. `capture`, `void` and `credit` are standard supported actions, but you can also
+ have custom actions here, as long as `Spree::Payment` responds to them.
+* `#can_?`: for each of the actions returned by `#actions`, Solidus will attempt to call
+ this method to verify whether that action can be taken on a payment, which will be passed to the
+ method as the only
+ argument. [Default implementations](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment\_source.rb#L21)
+ are provided for `capture`, `void` and `credit` which check the payment's state.
+* `#reusable?`: whether this payment source is reusable (i.e., whether it can be used on future
+ orders as well). Solidus will use this to determine whether to add the payment source to the
+ user's wallet after the order is placed, and to determine which sources to show from the user's
+ wallet during the checkout flow.
+
+Next, let's see how exactly to implement these methods in a brand new payment source.
+
+#### Building a custom payment source
+
+For example, let's say we're integrating with a brand new PSP called SolidusPay which allows
+customers to pay with their SolidusPay account's balance, similar to what happens with PayPal. In
+order to model this behavior, we'll need a custom payment source model, which will be much simpler
+than the default credit card payment source.
+
+The first step is to generate a new model, which we'll call `SolidusPay::Transaction`.
+
+Our model will just have a `payment_method_id` column, which will be used to associate the payment
+source to the payment method that generated it, and an `auth_token` column, which we'll use to store
+the ID of the SolidusPay auth token that we will later charge:
+
+```bash
+$ rails g model SolidusPay::Transaction payment_method_id:integer auth_token:string
+$ rails db:migrate
+```
+
+By default, Rails will generate a model that inherits from `ApplicationRecord`. Instead, we want our
+model to inherit from `Spree::PaymentSource`, so that we can benefit from some sensible defaults
+provided by Solidus for payment sources:
+
+```diff title="app/models/solidus:pay/transaction.rb"
+- class SolidusPay::Transaction < ApplicationRecord
++ class SolidusPay::Transaction < Spree::PaymentSource
++ self.table_name = "solidus_pay_transactions"
+ end
+```
+
+At this point, we have our new payment source model ready. Now, let's implement the payment source
+API, so that Solidus knows how to use our payment source during order processing (note that this
+logic is taken verbatim from `Spree::PaymentSource`, other than the `#reusable?` method which would
+normally return `false`):
+
+```ruby title="app/models/solidus:pay/transaction.rb"
+class SolidusPay::Transaction < Spree::PaymentSource
+ # ...
+
+ # SolidusPay payments can be captured, voided and refunded.
+ def actions
+ %w(capture void credit)
+ end
+
+ # A SolidusPay payment can be captured as long as it's in the checkout or pending state.
+ def can_capture?(payment)
+ payment.pending? || payment.checkout?
+ end
+
+ # We rely on the payment state machine to determine when a SolidusPay payment can be voided.
+ def can_void?(payment)
+ payment.can_void?
+ end
+
+ # A SolidusPay payment can be refunded if it's been captured and if the
+ # un-refunded amount is greater than 0.
+ def can_credit?(payment)
+ payment.completed? && payment.credit_allowed > 0
+ end
+
+ # SolidusPay accounts can be used to pay on future orders as well.
+ def reusable?
+ true
+ end
+end
+```
+
+At this point, we have a new payment source and Solidus knows how to use it internally.
+
+### Custom payment methods
+
+Payment methods are what Solidus interacts with when processing payments and refunds. In order to
+take advantage of a custom payment gateway, we'll also need a custom payment method.
+
+#### The payment method API
+
+By
+default, `Spree::PaymentMethod` [delegates](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment\_method.rb#L40)
+all gateway-specific method calls to the gateway itself. You can choose to maintain this behavior,
+or to wrap the calls and enrich them with your custom logic by re-defining the relevant methods (
+e.g., `#authorize`, `#capture`, `#purchase` etc.).
+
+A common setup is to code your payment gateway so that it doesn't have to know about payment sources
+and can be used independently, while the payment method acts as a bridge between payment sources and
+the calls to the payment gateway.
+
+Payment methods also expose additional methods which are Solidus-specific and not part of the
+standard gateway API. We'll see which in the next paragraphs.
+
+#### Building a custom payment method
+
+The first step for building a custom payment method is to create a new model that inherits
+from `Spree::PaymentMethod`. Unlike for payment sources, payment methods are all stored in one
+table, so there's no need to use the model generator. Instead, we'll create the model class
+directly, with the bare minimum that's needed to make it work:
+
+```ruby title="app/models/solidus:pay/payment:method.rb"
+class SolidusPay::PaymentMethod < Spree::PaymentMethod
+end
+```
+
+After creating the model, it's a good idea to configure the translation for its name, so that
+Solidus knows how to display the payment method's name properly in the backend:
+
+```yaml title="config/locales/en.yml"
+en:
+ # ...
+
+ activerecord:
+ models:
+ solidus_pay/payment_method: Solidus Pay
+```
+
+Now that we have our new payment method, we need to tell Solidus about its existence, so that
+SolidusPay shows up when an admin attempts to configure a new payment method:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.environment.payment_methods << 'SolidusPay::PaymentMethod'
+end
+```
+
+At this point, if you open your Solidus backend and navigate to Settings β Payments β New Payment
+Method, you should see "Solidus pay" in the list of available payment methods:
+
+
+
+Fill in a name for your payment method ("Solidus Pay" will do just fine) and hit Create to create a
+new instance of the payment method in your Solidus store.
+
+:::info
+
+It's common to need to store some credentials or other preferences associated with a payment method.
+Please, see
+the [section about statically configured models for details](model-preferences.mdx#static-preferences).
+
+:::
+
+At this point, however, the payment method is just an empty shell: it doesn't know anything about
+the custom source or the custom gateway we have implemented. We still need to make some adjustments
+in order to fully integrate it with our PSP.
+
+#### Integrating a custom payment source
+
+The first step to a complete integration is to make our payment method aware of the custom payment
+source model we have created earlier. This way, the payment method will know which sources to
+retrieve and create during the checkout flow.
+
+To integrate the payment method with our payment source, we'll implement the following methods:
+
+* `#payment_source_class`: returns the payment source class the payment method works with.
+* `#reusable_sources(order)`: given a payment source, returns whether the payment method can work
+ with it.
+* `#supports?(source)`: \_\*\*\_given an order, returns the list of reusable sources on the order
+ for the payment method.
+
+The implementation for these is pretty simple and self-explanatory:
+
+```ruby
+class SolidusPay::PaymentMethod < Spree::PaymentMethod
+ # ...
+
+ def payment_source_class
+ SolidusPay::Transaction
+ end
+
+ def supports?(source)
+ source.is_a?(payment_source_class)
+ end
+
+ def reusable_sources(order)
+ return [] unless order.user
+
+ order.user.wallet_payment_sources.map(&:payment_source).select do |source|
+ supports?(source) && source.reusable?
+ end
+ end
+end
+```
+
+#### Integrating a custom payment gateway
+
+In order to start using our custom payment gateway, we'll need to tell the payment method which
+payment gateway class to initialize. We can do this by defining a `gateway_class` method on the
+payment method:
+
+```ruby
+class SolidusPay::PaymentMethod < Spree::PaymentMethod
+ # ...
+
+ preference :api_key, :string
+
+ def gateway_class
+ SolidusPay::Gateway
+ end
+end
+```
+
+You will notice that we also added an `api_key` preference. This preference will be automatically
+configurable via the payment method UI in the Solidus admin, and will be passed to the payment
+gateway's `#initialize` method. By default, the payment method will delegate all `#authorize`
+, `#capture`, `#purchase`, `#void` and `#credit` calls to the gateway.
+
+However, there's one caveat: Solidus will pass instances of `SolidusPay::Source` to our payment
+method when calling `#authorize` and `#capture`, but the gateway doesn't know anything about payment
+sources and works directly with auth tokens instead. This makes the gateway independent of Solidus,
+but it also means Solidus will pass the wrong type of argument to it.
+
+To accommodate this discrepancy, we'll adjust the `#authorize` and `#capture` methods on the payment
+method slightly, in order to transform payment sources into auth tokens:
+
+```ruby
+class SolidusPay::PaymentMethod < Spree::PaymentMethod
+ # ...
+
+ def authorize(money, source, options = {})
+ gateway.authorize(money, source.auth_token, options)
+ end
+
+ def purchase(money, source, options = {})
+ gateway.purchase(money, source.auth_token, options)
+ end
+end
+```
+
+Finally, we need to implement the `#try_void` method. This method is supposed to attempt to void a
+payment or return `false` if the payment cannot be voided anymore (in which case, Solidus will issue
+a refund instead):
+
+```ruby
+class SolidusPay::PaymentMethod < Spree::PaymentMethod
+ # ...
+
+ def try_void(payment)
+ return false if payment.completed?
+
+ void(payment.source.transaction_id)
+ end
+end
+```
+
+Our `try_void` implementation is pretty simple: if the payment has already been completed (i.e.,
+it's been captured), we return `false` and let Solidus issue a refund. If the payment hasn't been
+captured, we void the existing authorization instead.
+
+#### Providing payment method partials
+
+:::caution
+
+These partials assume that you haven't customized the backend, storefront or API in significant ways
+which would prevent them from working/displaying properly. If you have applied extensive
+customizations to either of these engines, make sure to adjust the partials accordingly!
+
+:::
+
+```ruby
+class SolidusPay::PaymentMethod < Spree::PaymentMethod
+ # ...
+
+ def partial_name
+ 'solidus_pay'
+ end
+end
+```
+
+When you create a new payment method, Solidus has no idea how to actually display it in the
+storefront, backend or API. You will need to provide payment method partials so that Solidus can use
+them when displaying the SolidusPay payment method.
+
+The first partial we'll implement is used by Solidus when displaying the SolidusPay payment form in
+the checkout flow. We will just ask users for their SolidusPay auth token:
+
+```markup title="app/views/spree/checkout/payment/:solidus:pay.html.erb"
+<% param_prefix = "payment_source[#{payment_method.id}]" %>
+
+
+```
+
+We'll also need to add `auth_token` as a permitted source attribute in order for
+it to get accepted by `Spree::CheckoutController`:
+
+```ruby title="config/initializers/spree.rb
+Spree::PermittedAttributes.source_attributes << :auth_token
+```
+
+When users fill this form during checkout, Solidus will create a new `SolidusPayAccount` payment
+source with the auth token provided by the user. In general, Solidus will copy all
+the `#{param_prefix}[...]` attributes to the payment source, so you can add more columns to the
+payment source model and Solidus will set them all as long as your field names are structured
+properly.
+
+:::caution
+
+For the sake of simplicity, we are assuming paying via SolidusPay is as simple as providing your
+auth token in clear text. In reality, things are usually slightly more complicated and require
+integrating with a JS SDK or redirecting the user to an off-site page in order to get a payment
+token. It doesn't matter how complex your payment scenario: as long as it results in a payment
+source being created with the right information, Solidus can integrate with it.
+
+:::
+
+But what if our user already has a SolidusPay account in their wallet, and they want to use that
+instead? That requires another partial:
+
+```markup title="app/views/spree/checkout/existing:payment/:solidus:pay.html.erb"
+
+```
+
+Here, we are simply showing a radio button which Solidus will render to the user for all SolidusPay
+accounts. The user can check one of the radio boxes to pay with an existing payment source.
+
+At this point, it's possible to pay with a new or existing SolidusPay account via the storefront.
+Let's make sure the same can be done in the backend, for orders placed manually by an admin.
+
+This is what the partial for the backend looks like:
+
+```markup title="app/views/spree/admin/payments/source:forms/:solidus:pay.html.erb"
+
+```
+
+As you can see, this partial covers both new and existing payment sources. The admin can either
+select one of the existing payment sources, or they can enter the customer's SolidusPay auth token
+to create a new payment source.
+
+:::caution
+
+For many payment sources, it won't be possible to create a new source from the backend (e.g., it
+wouldn't make sense to let admins link a customer's PayPal account via the backend, since they
+wouldn't have the customer's PayPal credentials). In this case, it's perfectly fine not to display
+the form at all, and only let admins choose from existing payment sources.
+
+:::
+
+We need one more partial for the backend, which will be used by Solidus when displaying a payment
+source's information to admins:
+
+```markup title="app/views/spree/admin/payments/source:views/:solidus:pay.html.erb"
+
+```
+
+Here, we are just displaying the SolidusPay auth token, so that admins can easily understand which
+payment source was used on a particular payment.
+
+Finally, the last partial is needed to display a payment source's information via the API. Again,
+we'll just include the payment source's ID and the SolidusPay auth token, so that the payment source
+can be properly rendered by e.g. a mobile/JS application that uses the Solidus API:
+
+```ruby title="app/views/spree/api/payments/source:views/:solidus:pay.json.jbuilder"
+json.call(payment_source, :id, :auth_token)
+```
+
+#### Adding support for payment profiles
+
+Payment methods in Solidus can support **payment profiles**. Payment profiles provide the ability to
+vault a payment source, i.e. store its details permanently in the PSP so that it can be charged at a
+later stage. If you've ever used Stripe, this would be the equivalent of creating a Stripe customer
+associated to the credit card you're trying to charge (in fact, that's exactly what the Stripe
+payment method in Solidus does!).
+
+When a payment method supports payment profiles, Solidus will alter the usual payment processing
+flow slightly to accommodate that:
+
+* Right after creating a payment, Solidus
+ will [call](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/payment.rb#L28)
+ the `#create_payment_profile(payment)` method on the payment method. This method is supposed to
+ create a payment profile for the payment source on the payment, and save its details on the
+ payment source.
+*
+
+When [voiding](https://github.com/solidusio/solidus/blob/e878076f2ed670d07654ab6293a16588743f2fa6/core/app/models/spree/payment/processing.rb#L74)
+or [refunding](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/refund.rb#L60) a
+transaction, Solidus will pass the payment source to the `#void` or `#refund` call, so that the
+payment gateway can include the payment profile ID in the PSP API call, if required by the PSP.
+
+The first step for adding support for payment profiles is defining
+the `#payment_profiles_supported?` method in our payment method:
+
+```ruby
+class SolidusPay::PaymentMethod < Spree::PaymentMethod
+ # ...
+
+ def payment_profiles_supported?
+ true
+ end
+end
+```
+
+Next, we'll implement a `create_customer`method in our gateway, which accepts a SolidusPay auth
+token and creates a new SolidusPay customer from it:
+
+```ruby
+class SolidusPay::Gateway
+ # ...
+
+ def create_customer(auth_token)
+ request(
+ :post,
+ "/customers",
+ auth_token: auth_token,
+ ).parsed_response
+ end
+end
+```
+
+This method will return the customer's profile, and we'll assume that the customer ID we want to
+store will be in the `id` key.
+
+At this point, we'll need to add a column to our `SolidusPay::Transaction` model for storing the
+SolidusPay customer ID:
+
+```bash
+$ rails g migration AddCustomerIdToSolidusPayTransactions customer_id:string
+$ rails db:migrate
+```
+
+:::caution
+
+At the moment, Solidus doesn't offer native data structures for associating multiple payment sources
+to the same payment profile (e.g., associating the same Stripe customer to multiple credit cards).
+However, this could easily be implemented as a custom functionality (e.g., by storing the Stripe
+customer ID on the user rather than the payment source).
+
+:::
+
+Finally, we'll implement the `create_profile` method on the SolidusPay payment method. This method
+will be called right after a new SolidusPay payment is created, and it should create a new
+SolidusPay customer with the payment source associated to the payment method, and store its ID on
+the corresponding transaction:
+
+```ruby
+class SolidusPay::PaymentMethod < Spree::PaymentMethod
+ # ...
+
+ def create_profile(payment)
+ customer = gateway.create_customer(payment.auth_token)
+
+ payment.source.update(customer_id: customer['id'])
+ end
+end
+```
+
+Solidus should now create a new customer profile in SolidusPay whenever we create a SolidusPay
+payment, and the customer ID will be correctly associated to the payment source.
+
+The last step is updating the payment gateway and payment method implementation: now that our
+payment method supports payment profiles, Solidus will start passing the payment source as an
+additional parameter to `#void` and `#credit`:
+
+```diff
+ class SolidusPay::Gateway
+ # ...
+
+- def void(transaction_id, options = {})
++ def void(transaction_id, source, options = {})
+ # ...
+ end
+
+- def credit(money, source, transaction_id, options = {})
++ def credit(money, source, transaction_id, options = {})
+ # ...
+ end
+ end
+```
+
+In our example, we are not doing anything with the `source` parameter and simply accept it so that
+Solidus' method calls don't fail. In the real world, we may want to pass the customer ID to the
+gateway along with the transaction ID, so that it can be included in the API payload.
+
+### Custom payment canceller
+
+If you need to customize the logic that is used for payment cancellation, you can easily do it via a
+configuration hook.
+
+For this example, we are going to assume that, instead of refunding customers on the original
+payment method, you want to offer them store credit instead.
+
+In order to accomplish this, you will first need to create a custom payment canceller:
+
+```ruby title="app/models/awesome:store/payment:canceller.rb"
+module AwesomeStore
+ class PaymentCanceller
+ def cancel(payment)
+ # Capture the payment. If the payment has already been captured,
+ # this will be a no-op.
+ payment.capture!
+
+ # Find or create an "Order cancellation" store credit category.
+ category = Spree::StoreCreditCategory.find_or_create_by(
+ name: 'Order cancellation',
+ )
+
+ # Create a store credit for the payment amount.
+ Spree::StoreCredit.create!(
+ user: payment.order.user,
+ amount: payment.credit_allowed,
+ category: category,
+ currency: payment.currency,
+ created_by: payment.order.canceler,
+ )
+ end
+ end
+end
+```
+
+The cancellation logic is pretty simple: first of all, we capture the payment, unless it's already
+been captured. Then we generate a store credit for the payment amount. Customers will then be able
+to spend the store credit on their next order.
+
+In order for Solidus to start using our canceller, we need to specify it in our initializer:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.payment_canceller = AwesomeStore::PaymentCanceller.new
+end
+```
+
+You can test your new canceller by placing and cancelling an order from the Solidus backend: you'll
+see that, instead of the payment being refunded, a store credit will be generated instead.
diff --git a/versioned_docs/version-4.2/advanced-solidus/permission-management.mdx b/versioned_docs/version-4.2/advanced-solidus/permission-management.mdx
new file mode 100644
index 0000000..9d50ac4
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/permission-management.mdx
@@ -0,0 +1,142 @@
+---
+sidebar_position: 6
+needs-diataxis-rewrite: true
+---
+
+# User permissions
+
+## Architecture overview
+
+For authorization management, Solidus uses [CanCanCan](https://github.com/CanCanCommunity/cancancan)
+\(a fork of the popular, now unmaintained CanCan library\), a Ruby/Rails authorization library that
+allows you to define granular permissions on collection and individual resources. This is combined
+with an in-house user role system that allows you to assign certain permissions to certain groups of
+users.
+
+At a high level, here are the main concepts in the authorization system:
+
+* [**The ability**](https://github.com/CanCanCommunity/cancancan#define-abilities) is a CanCan class
+ that stores information about the permissions a user has on certain collections or resources, and
+ the conditions under which such permissions must be granted. Solidus
+ has [its own ability class](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/ability.rb)
+ which has some custom logic to support permission sets.
+* [**Permission
+ sets**](https://github.com/solidusio/solidus/tree/master/core/lib/spree/permission_sets) are
+ Solidus classes that activate certain sets of permissions on the user's ability. For instance,
+ the [`StockManagement`](https://github.com/solidusio/solidus/blob/v3.0/core/lib/spree/permission_sets/stock_management.rb)
+ permission set allows the user to manage stock items and locations. They are a neat way to
+ encapsulate groups of related permissions.
+* [**The role
+ configuration**](https://github.com/solidusio/solidus/blob/v3.0/core/lib/spree/core/role_configuration.rb)
+ is a configuration class that associates user roles \(stored through
+ the [`Spree::RoleUser`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/role_user.rb)
+ model\) with a collection of permission sets. By default, a Solidus store has two
+ roles: [`default` and `admin`](https://github.com/solidusio/solidus/blob/v3.0/core/lib/spree/app_configuration.rb#L492)
+ , which have customer and super-admin permissions respectively.
+
+:::info
+
+Solidus only uses
+the [`DefaultCustomer`](https://github.com/solidusio/solidus/blob/v3.0/core/lib/spree/permission_sets/default_customer.rb)
+and [`SuperUser`](https://github.com/solidusio/solidus/blob/v3.0/core/lib/spree/permission_sets/super_user.rb)
+permission sets
+internally: [all the others](https://github.com/solidusio/solidus/tree/master/core/lib/spree/permission_sets)
+are provided for your own convenience, in case you want to define custom roles with more granular
+permissions.
+
+:::
+
+The process Solidus follows to determine the current user's permissions is pretty simple:
+
+1. If the user is authenticated, it retrieves the current user's roles. If the user isn't
+ authenticated, it assumes the user only has the `default` role.
+2. It collects all the permission sets for the user's roles.
+3. It applies all the permission sets to the current ability.
+
+This simple but flexible system allows you to create custom permissions and roles, both in
+extensions and in your main application, as well as do things such as store permission sets in the
+database rather than in Ruby code, allowing the administrator to change them via the UI.
+
+Let's see how we can leverage some of that flexibility.
+
+## Custom user roles
+
+Let's start with something simple: we'll define a new `customer_service` role that has limited
+access to the Solidus backend. Customer service representatives will only be able to display users,
+products and stock locations.
+
+The first step is to create the `Spree::Role` record that we'll use to store our role. In order to
+do that, you can simply add the following to your seeds:
+
+```ruby
+# ...
+Spree::Role.where(name: 'customer_service').first_or_create!
+```
+
+Then, run the seeds:
+
+```bash
+$ rails db:seeds
+```
+
+Now that you have defined your role, you just need to associate it with the desired permission sets
+in your Solidus configuration. You can do this in the Solidus initializer:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+ config.roles.assign_permissions :customer_service, [
+ Spree::PermissionSets::OrderDisplay,
+ Spree::PermissionSets::UserDisplay,
+ Spree::PermissionSets::ProductDisplay,
+ ]
+end
+```
+
+Now, if you assign the `customer_service` role to a user and sign into the backend with their
+credentials, you'll see that you only have limited access to user profiles, orders and products.
+
+## Custom permission sets
+
+Let's take it one step further and assume that we also want customer service representatives to be
+able to update user profiles, perhaps in case a customer forgets their password and wants a reset.
+
+Since there isn't a permission set that does what we need, we'll need to create our own:
+
+```ruby title="app/models/amazing\_store/permission\_sets/user\_update.rb"
+module AmazingStore
+ module PermissionSets
+ class UserUpdate < Spree::PermissionSets::Base
+ def activate!
+ can :update, Spree::User
+ end
+ end
+ end
+end
+```
+
+Now that we have our permission set, the last step is to associate it to the `customer_service` role
+by updating the role definition we created:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+ config.roles.assign_permissions :customer_service, [
+ Spree::PermissionSets::OrderDisplay,
+ Spree::PermissionSets::UserDisplay,
+ Spree::PermissionSets::ProductDisplay,
+ AmazingStore::PermissionSets::UserUpdate,
+ ]
+end
+```
+
+That's it! Now, customer service representatives can also update user profiles.
+
+:::info
+
+Permission sets delegate a lot of their implementation to CanCanCan, which provides a very powerful
+API for defining which actions a user can and cannot perform. For a complete overview of what you
+can accomplish, refer to the [CanCanCan documentation](https://github.com/CanCanCommunity/cancancan).
+
+:::
+
diff --git a/versioned_docs/version-4.2/advanced-solidus/promotions-system.mdx b/versioned_docs/version-4.2/advanced-solidus/promotions-system.mdx
new file mode 100644
index 0000000..0ae87b0
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/promotions-system.mdx
@@ -0,0 +1,353 @@
+---
+sidebar_position: 7
+needs-diataxis-rewrite: true
+---
+
+# Promotions system
+
+## Architecture overview
+
+Solidus ships with a powerful rule-based promotions system that allows you to grant flexible
+discounts to your customers in many different scenarios. You can apply discounts to the entire
+order, to a single line item or a set of line items, or to the shipping fees.
+
+In order to achieve this level of flexibility, the promotions system is composed of four concepts:
+
+* **Promotion handlers** are responsible for activating a promotion at the right step of the
+ customer experience.
+* **Promotion rules** are responsible for checking whether an order is eligible for a promotion.
+* **Promotion actions** are responsible for defining the discount(s) to be applied to eligible
+ orders.
+* **Adjustments** are responsible for storing discount information. Promotion adjustments are
+ recalculated every time the order is updated, to check if their eligibility persists when the
+ state of the order changes. It is possible to
+ [customize how this recalculation behaves][how-to-use-a-custom-promotion-adjuster].
+
+:::info
+
+Adjustments go beyond promotions and apply to other concepts that modify the order amount. Taxes are
+another good example.
+
+:::
+
+Let's take the example of the following promotion:
+
+> Apply free shipping on any orders whose total is $100 USD or greater.
+
+Here's the flow Solidus follows to apply such a promotion:
+
+1. When the customer enters their shipping information,
+ the [`Shipping`](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion\_handler/shipping.rb)
+ promotion handler activates the promotion on the order.
+2. When activated, the promotion will perform
+ some [basic eligibility checks](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion.rb#L149) (
+ e.g. usage limit, validity dates) and
+ then [ensure the defined promotion rules are met.](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion.rb#L149)
+3. When called,
+ the [`ItemTotal`](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion/rules/item\_total.rb)
+ promotion rule will ensure the order's total is $100 USD or greater.
+4. Since the order is eligible for the promotion,
+ the [`FreeShipping`](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion/actions/free\_shipping.rb)
+ action is applied to the order's shipment. The action creates an adjustment that cancels the cost
+ of the shipment.
+5. The customer gets free shipping!
+
+This is the architecture at a glance. As you can see, Solidus already ships with some useful
+handlers, rules, and actions out of the box.
+
+However, you're not limited to using the stock functionality. In fact, the promotions system shows
+its full potential when you use it to implement your own logic. In the rest of the guide, we'll use
+the promotions system to implement the following requirements:
+
+> We want to uphold a partnership with a new payment platform by offering a 50% shipping discount
+> when customers pay with it during the checkout.
+
+In order to do this, we'll have to implement our own handler, rule, and action. Let's get to work!
+
+## Implementing a new handler
+
+There's nothing special about promotion handlers: technically, they're just plain old Ruby objects
+that are created and called in the right places during the checkout flow.
+
+There is no unified API for promotion handlers, but we can take inspiration from
+the [existing ones](https://github.com/solidusio/solidus/tree/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion\_handler)
+and use a similar format:
+
+```ruby title="app/models/amazing_store/promotion_handler/payment.rb"
+# frozen_string_literal: true
+
+module AmazingStore
+ module PromotionHandler
+ class Payment
+ RULES_TYPE = 'AmazingStore::Promotion::Rules::Payment'
+
+ attr_reader :order
+
+ def initialize(order)
+ @order = order
+ end
+
+ def activate
+ promotions.each do |promotion|
+ promotion.activate(order: order) if promotion.eligible?(order)
+ end
+ end
+
+ private
+
+ def promotions
+ ::Spree::Promotion.
+ active.
+ joins(:promotion_rules).
+ where('promotion_rules.type' => RULES_TYPE)
+ end
+ end
+ end
+end
+```
+
+Our promotion handler selects a subset of promotions with a specific rule type that we haven't yet
+created. Then, it activates the eligible ones, i.e., those who obey its rules.
+
+Remember that promotion handlers simply apply active promotions to the current order at the correct
+stage of the order workflow. While other handlers might pick up our promotions, they won't be able
+to activate it if they run before the payment step. With the new handler, we want to ensure that
+promotions can be activated after a payment method has been selected for the order.
+
+Let's call our handler as a callback after the checkout flow has transitioned from the `:payment`
+state (see
+the [section on how to customize state machines](state-machines.mdx#customizing-core-behavior)):
+
+```ruby title="app/overrides/amazing_store/load_payment_promotion_handler.rb"
+# frozen_string_literal: true
+
+module AmazingStore
+ module LoadPaymentPromotionHandler
+ def self.prepended(base)
+ base.state_machine.after_transition(from: :payment) do |order|
+ AmazingStore::PromotionHandler::Payment.new(order).activate
+ end
+ end
+
+ ::Spree::Order.prepend(self)
+ end
+end
+```
+
+## Implementing a new rule
+
+Now that we have our handler, let's move on and implement the promotion rule that checks whether the
+customer is using the promoted payment method.
+
+We'll allow store admins to edit which payment method carries the discount. The best way to do that
+is to create a preference for the promotion rule itself:
+
+```ruby title="app/models/amazing_store/promotion/rules/payment.rb"
+# frozen_string_literal: true
+
+module AmazingStore
+ module Promotion
+ module Rules
+ class Payment < ::Spree::PromotionRule
+ DEFAULT_PREFERRED_PAYMENT_TYPE = 'AmazingStore::AmazingPaymentPlatform'
+
+ ALLOWED_PAYMENT_TYPES = [
+ DEFAULT_PREFERRED_PAYMENT_TYPE,
+ 'Spree::PaymentMethod::Check',
+ 'Spree::PaymentMethod::CreditCard'
+ ].freeze
+
+ preference :payment_type, :string, default: DEFAULT_PREFERRED_PAYMENT_TYPE
+
+ validates :preferred_payment_type, inclusion: {
+ in: ALLOWED_PAYMENT_TYPES,
+ allow_blank: true
+ }, on: :update
+
+ def applicable?(promotable)
+ promotable.is_a?(::Spree::Order)
+ end
+
+ def eligible?(order, _options = {})
+ order.payments.any? do |payment|
+ payment.payment_method.type == preferred_payment_type
+ end
+ end
+ end
+ end
+ end
+end
+```
+
+:::caution
+
+You may have noticed that we allow the payment type to be blank on creation. This is because
+promotion rules are initially created without any of their preferences, so that the correct form can
+be presented to the admin when configuring the rule. If we enforced the presence of a payment type
+since the very beginning, Solidus wouldn't be able to create the promotion rule and admins would get
+an error.
+
+:::
+
+Now that we have the implementation of our promotion rule, we also need to give admins a nice UI
+where they can manage the rule and enter the promoted payment type. We just need to create the right
+partial, where we'll have a local variable `promotion_rule` available to access the current
+promotion rule instance:
+
+```markup title="app/views/spree/admin/promotions/rules/_payment.html.erb"
+
+```
+
+The last step is to register our new promotion rule in an initializer:
+
+```ruby title="config/initializers/promotions.rb"
+# ...
+Rails.application.config.spree.promotions.rules << 'AmazingStore::Promotion::Rules::Payment'
+```
+
+When you create a new promotion in the backend, we should now see the _Payment_ promotion rule. For
+a better experience, we can associate a description so that it's rendered along its form:
+
+```yaml title="config/locales/en.yml"
+en:
+ # ...
+ activerecord:
+ attributes:
+ amazing_store/promotion/rules/payment:
+ description: Must use the specified payment method
+```
+
+## Implementing a new action
+
+Finally, let's implement the promotion action that will grant customers a 50% shipping discount. In
+order to do that, we can take inspiration from the
+existing [`FreeShipping`](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/promotion/actions/free\_shipping.rb)
+action:
+
+```ruby title="app/models/amazing_store/promotion/actions/half_shipping.rb"
+# frozen_string_literal: true
+
+module AmazingStore
+ module Promotion
+ module Actions
+ class HalfShipping < ::Spree::PromotionAction
+ # The `perform` method is called when an action is applied to an order or line
+ # item. The payload contains a lot of useful context:
+ # https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion.rb#L129
+ def perform(payload = {})
+ order = payload[:order]
+ promotion_code = payload[:promotion_code]
+
+ results = order.shipments.map do |shipment|
+ # If the shipment has already been discounted by this promotion action,
+ # we skip it.
+ next false if shipment.adjustments.where(source: self).exists?
+
+ # If not, we create an adjustment to apply a 50% discount on the shipment.
+ shipment.adjustments.create!(
+ order: shipment.order,
+ amount: compute_amount(shipment),
+ source: self,
+ promotion_code: promotion_code,
+ label: promotion.name,
+ )
+
+ # We return true here to mark that the shipment has been discounted.
+ true
+ end
+
+ # `perform` needs to return true if any adjustments have been applied by
+ # the promotion action. Otherwise, it should return false.
+ results.any? { |result| result == true }
+ end
+
+ def compute_amount(shipment)
+ shipment.cost * -0.5
+ end
+
+ # The `remove_from` method should undo any actions done by `perform`. It is
+ # used when an order becomes ineligible for a given promotion and the promotion
+ # needs to be removed.
+ def remove_from(order)
+ order.shipments.each do |shipment|
+ shipment.adjustments.each do |adjustment|
+ if adjustment.source == self
+ # Here, we simply remove any adjustments on the order's shipments
+ # created by this promotion action.
+ shipment.adjustments.destroy!(adjustment)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+```
+
+As you can see, there's quite a bit going on here, but hopefully, the comments help you with the
+flow of the action and the purpose of the methods we implemented.
+
+Just like rules, promotion actions can also have preferences and allow admin to define them via the
+UI. However, in this case, we don't need any of that. Still, Solidus will expect a partial for the
+action, so we should create an empty ERB file.
+
+```erb title="app/views/spree/admin/promotions/actions/_half_shipping.html.erb"
+
+```
+
+:::info
+
+You can look at
+the [`CreateQuantityAdjustments`](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion/actions/create\_quantity\_adjustments.rb)
+action and
+the [corresponding view](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/backend/app/views/spree/admin/promotions/actions/\_create\_quantity\_adjustments.html.erb)
+for an example of actions with preferences.
+
+:::
+
+Finally, we need to register our action by adding the following to an initializer:
+
+```ruby title="config/initializers/promotions.rb"
+# ...
+Rails.application.config.spree.promotions.actions << 'AmazingStore::Promotion::Actions::HalfShipping'
+```
+
+Like before, let's add a human-friendly description:
+
+```yaml title="config/locales/en.yml"
+en:
+ # ...
+ activerecord:
+ attributes:
+ amazing_store/promotion/actions/half_shipping:
+ description: Applies 50% discount in shipping
+```
+
+Restart the server and you should now see your new promotion action!
+
+Let's try it out!
+
+First of all, go to the _Promotions_ section on the backend and click _New Promotion_. In this case,
+it makes sense to check the _Apply to all orders_ option, as our promotion doesn't need a code. Once
+the promotion has been created, add the _Payment_ rule and the _Half shipping_ action.
+
+You can now go to the frontend and see how the shipment price is dropped by 50% if you select the
+configured payment method.
+
+## How-to guides
+
+- [How to use a custom promotion adjuster][how-to-use-a-custom-promotion-adjuster]
+
+[how-to-use-a-custom-promotion-adjuster]: ../how-tos/how-to-use-a-custom-promotion-adjuster
diff --git a/versioned_docs/version-4.2/advanced-solidus/returns.mdx b/versioned_docs/version-4.2/advanced-solidus/returns.mdx
new file mode 100644
index 0000000..54b9ede
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/returns.mdx
@@ -0,0 +1,108 @@
+---
+sidebar_position: 8
+---
+
+# Returns
+
+This guide will go through what Solidus offers to manage the return of
+purchased items: the compensation of their cost in different ways or the
+exchange with other products.
+
+Sometimes things don't go as expected during an online transaction. Items can
+get damaged in their way, or the wrong product is shipped by error. Other
+times, companies have flexible return policies to remove friction in the
+payment step. In any case, e-commerce stores need a process to monitor returns
+and reliably support customers and administrators.
+
+## RMA: return authorizations
+
+The journey of a return usually begins with the buyer asking for it, whether
+via form or email. At that moment, the customer service can create an RMA
+(Return Merchandise Authorization) with a unique number that can be used as a
+reference for any further communication.
+
+In Solidus, an RMA is represented by the
+[`Spree::ReturnAuthorization`][return-authorization] model. It contains a
+number and other information like the stock location where items should be
+delivered or a memo for a free-text description.
+
+A `Spree::ReturnAuthorization` has an authorized state on creation, but it can
+be canceled if desired. We must note that, at this point, we're still dealing
+with user requests, not actual returns. A store can authorize a request and
+keep processing it, regardless of its final result (e.g., not ending up
+accepting a return), or it can cancel it for being invalid and not deserving
+further consideration.
+
+## Return items
+
+An RMA must reference one or more of the products a customer purchased. For
+instance, a user bought five cups, but one of them arrived damaged. Depending
+on the store policy, they might ask for a reimbursement or an exchange for
+another cup. At a later stage, the request will be approved, edited or denied.
+
+In Solidus, each return item is represented through a
+[`Spree::ReturnItem`][return-item] record. Notice that a `Spree::ReturnItem`
+instance is associated with a [`Spree::InventoryUnit`][inventory-unit]. That's
+the most concrete level of product identification in orders; i.e., orders can
+have several line items for different variants, and each line item may contain
+one or more. The inventory unit maps one to one to a physical (or digital, for
+what it's worth) object, such as a broken cup.
+
+A `Spree::ReturnItem` contains information about the requested reimbursement
+amount and how it should be processed (e.g., refund or store credit) or another
+unit with which the store should replace it.
+
+Finally, two status fields govern the lifecycle of an item to return:
+
+- The **acceptance status** controls the eligibility of the return per-se. Initially
+_pending_, it should end up _accepted_ in case of going forward.
+- The **reception status** marks whether the store has already received the
+returned package. In a standard scenario, it should transition from _awaiting_
+to _received_.
+
+## Customer returns
+
+Finally, after the return authorization has moved forward, it'll materialize in
+a customer return. At that point, a store will already have the returned items
+back. It might be that the customer support department rejected some of the
+requests or accepted all of them.
+
+In Solidus, that's represented by a [`Spree::CustomerReturn`][customer-return]
+record. Similar to RMAs, it's a simple model. It only contains an
+identification number and a stock location that is a final override of the
+RMA's proposal. Customer returns are flexible and not tied to a single return
+authorization. Because of that, `Spree::ReturnItem` belongs not only to
+`Spree::ReturnAuthorization` but also to `Spree::CustomerReturn`.
+
+## Reimbursements
+
+The final step is fulfilling the store obligations and performing the agreed
+reimbursement or exchange. The store will need another shipment with the new
+items in the latter case.
+
+[`Spree::Reimbursement`][reimbursement] takes care of wrapping everything up.
+It's associated with a `Spree::CustomerReturn`, but the system is flexible
+enough so that more than one can be created until all returned items have been
+processed.
+
+Typically, administrators can edit the returned item details, like the amount
+to refund, the article to use as a substitute, or if it should be marked as
+resellable. The system will also take care of creating a new shipment if
+needed. In the end, a new reimbursement record will be created with a state of
+either reimbursed or errored.
+
+## How-to guides
+
+- [How to customize the default elibility: skipping RMAs][how-to-customize-default-eligibility]
+- [How to modify valid exchange items][how-to-modify-valid-exchange-items]
+- [How to use custom logic to calculate refunds][how-to-use-custom-logic-to-calculate-refunds]
+
+[return-authorization]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/return_authorization.rb
+[return-item]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/return_item.rb
+[inventory-unit]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/inventory_unit.rb
+[customer-return]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/customer_return.rb
+[reimbursement]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/reimbursement.rb
+
+[how-to-customize-default-eligibility]: /how-tos/rma_and_returns/how-to-customize-return-eligibility-rules-skipping-rmas.mdx
+[how-to-modify-valid-exchange-items]: /how-tos/rma_and_returns/how-to-modify-valid-exchange-items-in-returns.mdx
+[how-to-use-custom-logic-to-calculate-refunds]: /how-tos/rma_and_returns/how-to-use-custom-logic-to-calculate-return-refunds.mdx
diff --git a/versioned_docs/version-4.2/advanced-solidus/state-machines.mdx b/versioned_docs/version-4.2/advanced-solidus/state-machines.mdx
new file mode 100644
index 0000000..5a335f4
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/state-machines.mdx
@@ -0,0 +1,103 @@
+---
+sidebar_position: 9
+---
+
+# State machines
+
+In this guide, we'll see what state machines are and how they are part of the
+Solidus toolbelt for dealing with business flows.
+
+As a programmer, state is one of the most challenging parts to model in a system. For instance, if
+you ask a semaphore which light is turned on, it might answer red, green, or amber, depending on
+when you ask it and what happened before the request: the _state_ of the semaphore depends on
+user-initiated and external events.
+
+A [finite state machine](https://en.wikipedia.org/wiki/Finite-state\_machine)
+(FSM) is a design pattern where a system, or one of its components, can be in
+one and only one of a limited number of states at a given time. Transitions are
+allowed between states under well-known events. As long as the initial state
+and the list of past events are known, you can recreate a state machine at any
+point.
+
+For instance, a semaphore can be in a red, green, or amber state. When a
+pedestrian presses a button, it transitions from green to amber. When on amber,
+it goes to red if 5 seconds pass. If red, it turns green after 180 seconds. If
+you know a semaphore was green at time zero, and the list of events has been
+button-press, 500 seconds, button-press, 3 seconds, you know it's amber now.
+
+Several parts of an e-commerce system fit this paradigm well. An order can be
+in progress, waiting
+for payment, or completed. It goes to completed when the payment is made. At that point,
+reimbursement can still be processed or completed. A payment... well, you get the idea.
+
+## State machines in Solidus
+
+Solidus defines [a few state
+machines](https://github.com/solidusio/solidus/tree/master/core/lib/spree/core/state\_machines)
+on top of some models.
+
+Each state machine describes its valid states and the allowed transitions. It
+also defines event methods that can be called from the outside to trigger
+internal changes. Finally, they can also declare some hooks that run when
+specific transitions happen.
+
+Internally, Solidus' state machines use the
+[`states_machine`](https://github.com/state-machines/state\_machines) gem (more
+precisely,
+[`states_machine-activerecord`](https://github.com/state-machines/state\_machines-activerecord))
+. Take a look at [`states_machine`'s
+README](https://github.com/state-machines/state\_machines/blob/master/README.md)
+for more details on its usage and API.
+
+## Customizing state machines
+
+State machines modules are included in the corresponding model, so all the
+strategies described in the [core customization
+section](../customization/customizing-the-core.mdx) are valid.
+
+When customizing state machines, you should differentiate two different use cases:
+
+1. You need to modify the domain where the state machine flow belongs. For
+instance, you're working on the order checkout and want to prevent it from
+completing if some requirement is not met.
+2. You need to add orthogonal behavior to the flow, like sending an email or
+updating an external service when a payment is received.
+
+It would be best if you kept in mind that state machine transitions (including
+their `after_` hooks) are wrapped within a database transaction. On the first
+use case, adding new transition hooks is okay. However, if your requirement is
+tangential to the main flow, it's better to override the whole event method so
+that you can do your work when the primary database transaction is over.
+
+A good example of adding orthogonal behavior is event subscribers. There's no
+point in blocking database access until your subscribers have finished running.
+In fact, it's a bad practice: a failed subscriber could roll back the whole DB
+transaction and potentially leave your system in an inconsistent state.
+
+The customizations explained above allow surgical-precision changes to the
+state machine's flows. Still, sometimes, you may need to change extensive parts
+of how they act for more deep behavioral modifications. In that case, Solidus
+allows replacing the entire state machine with something custom. As always,
+with great power comes great responsibility. Replacing the whole state machine
+should be the outcome of an informed decision. Solidus relies on well-known
+state machine states and events in many areas of the core, so be prepared to
+adjust other parts of Solidus to work with your custom implementation.
+
+:::danger
+
+It's better to be conservative when customizing state machines. Try to apply
+the smallest possible set of changes, and if possible, avoid changing the
+defined states. You're dealing with the core of the domain model: large changes
+could branch out in unanticipated ways!
+
+:::
+
+## How-to guides
+
+- [How to customize existing state machines][how-to-customize-existing-state-machines]
+- [How to add orthogonal behavior: publishing events][how-to-add-orthogonal-behavior]
+- [How to replace an existing state machine][how-to-replace-an-existing-state-machine]
+
+[how-to-customize-existing-state-machines]: /how-tos/state_machine/how-to-customize-existing-state-machines.mdx
+[how-to-add-orthogonal-behavior]: /how-tos/state_machine/how-to-add-orthogonal-behavior-to-state-machines-publishing-events.mdx
+[how-to-replace-an-existing-state-machine]: /how-tos/state_machine/how-to-replace-an-existing-state-machine.mdx
diff --git a/versioned_docs/version-4.2/advanced-solidus/stock-and-fulfillment.mdx b/versioned_docs/version-4.2/advanced-solidus/stock-and-fulfillment.mdx
new file mode 100644
index 0000000..8ea684e
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/stock-and-fulfillment.mdx
@@ -0,0 +1,739 @@
+---
+sidebar_position: 10
+needs-diataxis-rewrite: true
+---
+
+# Stock management
+
+## Architecture overview
+
+The stock system in Solidus is possibly one of the most complicated and powerful provided by the
+platform. It consists of several pieces, all working in unison.
+
+It all begins when an order is about to transition to the `delivery` state. When that happens, the
+order state
+machine [calls the `create_proposed_shipments`](https://github.com/solidusio/solidus/blob/v3.0/core/lib/spree/core/state\_machines/order.rb#L103)
+method on the order, which in turn [uses the configured **stock
+coordinator**](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/order.rb#L493)
+to re-create the order's shipments.
+
+:::caution
+
+If you remove the `delivery` state from the order state machine, or override the state machine with
+your own, the stock coordinator won't be called automatically anymore, and it will be up to you to
+call it at the right time.
+
+:::
+
+The **stock coordinator** is the main actor in the stock system and coordinates all the other
+components. It takes an order as input and builds a list of proposed shipments for that order, along
+with their available shipping rates (e.g., FedEx Home Delivery for $10, FedEx Overnight for $20,
+etc.).
+
+The default implementation of the stock coordinator
+is [`Spree::Stock::SimpleCoordinator`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/stock/simple\_coordinator.rb)
+, but you can use a different coordinator if you want. In the rest of this guide, we'll assume you'
+re using the default `SimpleCoordinator` and we'll explain its inner workings.
+
+:::caution
+
+The default `SimpleCoordinator` class contains stock coordination logic that is the result of years
+of eCommerce experience and community contributions. We strongly recommend going with the default
+implementation and only overriding its subcomponents, unless you _really_ know what you're doing.
+
+:::
+
+The work done by the stock estimator can be split in two logical chunks:
+
+1. First, the estimator **creates the packages** for the
+ order. [Packages](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/stock/package.rb)
+ are a simplified version of shipments, meant to hold information about which stock is leaving
+ from which stock location.
+2. It then converts the packages into shipments and **estimates the shipping rates** for those
+ shipments, depending on the shipping methods available in the store.
+
+Let's see which other service objects are involved in these two processes!
+
+### Package creation
+
+The following actors are involved in the package creation phase:
+
+* **Stock location filter:** this class is responsible for filtering the stock locations created in
+ the backend and only returning the ones that should be used for the current order (e.g., you may
+ want to only use stock locations in the customer's country).
+* **Stock location sorter:** this class is responsible for sorting the list of filtered stock
+ locations in order of priority (e.g., you may want to pick inventory from the stock location
+ closest to the customer).
+* **Stock allocator:** this class is responsible for allocating stock from the selected stock
+ locations (e.g., you may want to allocate on-hand inventory before backordering).
+* **Stock splitters:** this class is responsible for splitting the allocated inventory units into
+ different packages (e.g., you may want to keep all packages below a certain weight, and ship
+ multiple packages if needed).
+
+The process for package creation is fairly straightforward:
+
+1. First, the coordinator uses the **configured stock location** filter to get the list of stock
+ locations to use for the current order.
+2. Then, the filtered list is sorted with the **configured stock location sorter**.
+3. Then, the filtered and sorted stock locations, along with the inventory units to allocate, are
+ passed to the **configured stock allocator**, which maps each stock location to a list of on-hand
+ inventory units to ship and backorderable inventory units to backorder. (At this point, an "
+ insufficient stock" error is raised if there's leftover inventory that couldn't be allocated from
+ any stock location.)
+4. Then, the list of on-hand and backorderable inventory units is converted into **packages**, one
+ package per stock location.
+5. Finally, the list of packages is passed to the **configured stock splitters**, which may further
+ split up the original packages.
+
+At this point, we have our final list of packages. It's now time to convert them into real shipments
+and estimate their shipping rates.
+
+### Rate estimation
+
+The rate estimation process follows a similar pattern:
+
+1. First, the coordinator converts the packages into shipments.
+2. Then, it calls the **configured estimator** to calculate the shipping rates for each package.
+3. Finally, it links the shipping rates for each package to the corresponding shipment.
+
+Because the estimator is configurable, you can override the estimation logic however you want.
+
+However, for the purpose of this guide, we'll assume you're using the
+default [`Spree::Stock::Estimator`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/stock/estimator.rb)
+, and we'll explain its process too:
+
+1. First, the estimator retrieves the list of shipping methods available for the package being
+ estimated. This determination takes into account the current store, the order's shipping address
+ and the currency on the shipping method's calculator.
+2. Then, it calculates the rate for each available shipping method, by using the calculator
+ configured on the shipping method.
+3. Then, it filters out any rates that belong to backend-only shipping methods, in case the
+ calculation is being performed from the storefront.
+4. Then, it selects the default shipping rate by using the configured **shipping rate selector**.
+5. Finally, it sorts the shipping rates by using the configured **shipping rate sorter**.
+
+The result of this process is a sorted list of shipping rates for the original package, with a
+default shipping rate already pre-selected for the user.
+
+### Inventory unit creation
+
+When the stock coordinator needs to construct shipments for an order, it needs to generate inventory
+units for that shipment. It delegates this task to
+the [configured inventory unit builder](https://github.com/solidusio/solidus/blob/3ce8dc4bf8fc9b85418cdaff4c53748bb807996c/core/app/models/spree/stock/simple\_coordinator.rb#L27-L28)
+. This configurable class is responsible for building (but not saving) the inventory units that make
+up the order.
+
+## Customizing package creation
+
+There are several pieces you can customize in the package creation process:
+
+* the **stock location filter,** to customize which stock locations Solidus picks inventory from;
+* the **location sorter,** to customize how Solidus prioritizes stock locations to pick inventory
+ from;
+* the **allocator,** to customize how Solidus prioritizes inventory units to allocate from the
+ filtered and sorted stock locations;
+* the **splitters,** to customize how Solidus splits the allocated inventory units in packages.
+
+In the next paragraphs, we'll see a brief example for each of these customizations!
+
+### Stock location filter
+
+:::info
+
+The [default stock location filter](https://github.com/solidusio/solidus/blob/6c0da5d618a6d04d13ef50ec01ae17c3b06f6259/core/app/models/spree/stock/location\_filter/active.rb)
+simply filters out the inactive stock locations.
+
+:::
+
+Let's say you are a giant brand with warehouses all over the US, and you only ever want to ship from
+the stock locations in the customer's state.
+
+You can do that by writing a custom stock location filter that looks like this:
+
+```ruby title="app/models/awesome:store/stock/location:filter/order:state.rb"
+module AwesomeStore
+ module Stock
+ module LocationFilter
+ class OrderState < Spree::Stock::LocationFilter::Base
+ def filter
+ stock_locations.active.where(state: order.ship_address.state)
+ end
+ end
+ end
+ end
+end
+```
+
+As you can see, the logic is pretty simple: we take an initial list of stock locations (the default
+stock coordinator will simply pass all stock locations here) and then we only pick the ones that are
+active and where the state matches the state on the order's shipping address.
+
+In order to start using our new stock location filter, you just need to configure it:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.stock.location_filter_class = 'AwesomeStore::Stock::LocationFilter::OrderState'
+end
+```
+
+### Stock location sorter
+
+:::info
+
+[By default,](https://github.com/solidusio/solidus/blob/6c0da5d618a6d04d13ef50ec01ae17c3b06f6259/core/app/models/spree/stock/location\_sorter/unsorted.rb)
+stock locations are unsorted, but Solidus provides a
+built-in [`DefaultFirst`](https://github.com/solidusio/solidus/blob/6c0da5d618a6d04d13ef50ec01ae17c3b06f6259/core/app/models/spree/stock/location\_sorter/default\_first.rb)
+sorter that will put the default stock location first.
+
+:::
+
+Let's say that you ship from a mix of your own warehouses and third-party warehouses, and you want
+to ship from your own warehouses first in order to minimize fulfillment cost.
+
+You could do this with a custom stock location sorter:
+
+```ruby title="app/models/awesome:store/stock/location:sorter/self:owned:first.rb"
+module AwesomeStore
+ module Stock
+ module LocationSorter
+ class SelfOwnedFirst < Spree::Stock::LocationSorter::Base
+ def sort
+ # We're assuming the `self_owned` column is `true` when the warehouse
+ # is self-owned, and `false` when it's ownerd by a third-party.
+ stock_locations.order(self_owned: :desc)
+ end
+ end
+ end
+ end
+end
+```
+
+The implementation is pretty similar to that of the stock location filter: you take an initial list
+of sorted stock locations and you return a sorted list.
+
+Now that you have implemented your sorter, you need to enable it:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.stock.location_sorter_class = 'AwesomeStore::Stock::LocationSorter::SelfOwnedFirst'
+end
+```
+
+### Stock allocator
+
+:::info
+
+The [default stock allocator](https://github.com/solidusio/solidus/blob/6c0da5d618a6d04d13ef50ec01ae17c3b06f6259/core/app/models/spree/stock/allocator/on\_hand\_first.rb)
+picks on hand inventory units before backordered inventory units.
+
+:::
+
+Let's say you're a drop-shipping business, but you also hold a tiny amount of inventory on-hand for
+VIP customers or other special cases. In this case, you want to make sure you backorder all items
+and never touch your on-hand inventory unless absolutely needed (e.g.., if the customer ordered an
+item that's not being produced anymore and cannot be backordered).
+
+You could accomplish this with a custom stock allocator such as the following:
+
+```ruby title="app/models/awesome:store/stock/allocator/backordered:first.rb"
+module AwesomeStore
+ module Stock
+ module Allocator
+ class BackorderedFirst < Spree::Stock::Allocator::Base
+ def allocate_inventory(desired)
+ # Allocate backordered inventory first
+ backordered = allocate_backordered(desired)
+ desired -= backordered.values.sum if backordered.present?
+
+ # Allocate any non-backorderable inventory from on-hand inventory
+ on_hand = allocate_on_hand(desired)
+ desired -= on_hand.values.sum if on_hand.present?
+
+ # `desired` at this point should be empty if we managed to
+ # allocate all required inventory
+ [on_hand, backordered, desired]
+ end
+
+ protected
+
+ # In these two methods, `availability` is a `Spree::Stock::Availability`
+ # instance, which maps a list of variants to their availability in the
+ # filtered stock locations
+
+ def allocate_backordered(desired)
+ allocate(availability.backorderable_by_stock_location_id, desired)
+ end
+
+ def allocate_on_hand(desired)
+ allocate(availability.on_hand_by_stock_location_id, desired)
+ end
+
+ def allocate(availability_by_location, desired)
+ # `availability_by_location` is a `Spree::StockQuantities` instance
+ # that makes it easier to perform operations on inventory units
+ availability_by_location.transform_values do |available|
+ # Find the desired inventory which is available at this location
+ packaged = available & desired
+ # Remove found inventory from desired
+ desired -= packaged
+ packaged
+ end
+ end
+ end
+ end
+ end
+end
+```
+
+This allocator is extremely similar to Solidus' default stock allocator, but it works backwards: it
+allocates backordered inventory units before starting to pick on-hand inventory units.
+
+:::info
+
+Because operations on inventory units can be a bit complicated for a developer to perform manually,
+Solidus provides two helper
+classes, [`Spree::Stock::Availability`](https://github.com/solidusio/solidus/blob/6c0da5d618a6d04d13ef50ec01ae17c3b06f6259/core/app/models/spree/stock/availability.rb)
+and [`Spree::StockQuantities`](https://github.com/solidusio/solidus/blob/6c0da5d618a6d04d13ef50ec01ae17c3b06f6259/core/app/models/spree/stock\_quantities.rb)
+, which make it easier to reason about and perform algebraic operations on inventory units. Feel
+free to take a look at their source code to understand how they work in detail.
+
+:::
+
+### Stock splitters
+
+:::info
+
+The default splitter chain will split packages
+by [shipping category](https://github.com/solidusio/solidus/blob/6c0da5d618a6d04d13ef50ec01ae17c3b06f6259/core/app/models/spree/stock/splitter/shipping\_category.rb)
+and then
+by [availability](https://github.com/solidusio/solidus/blob/6c0da5d618a6d04d13ef50ec01ae17c3b06f6259/core/app/models/spree/stock/splitter/backordered.rb) (
+i.e., by separating on hand and backordered items in different packages).
+
+There's also a `Weight` splitter that is not enabled by default, which will split packages so that
+they are all below a certain weight threshold.
+
+:::
+
+An important aspect to understand about stock splitters is that, unlike all the other components of
+the stock system, you can have multiple stock splitters configured at the same time to form a **
+splitter chain**.
+
+When the packages are ready to be split, Solidus will pass the initial list of packages to the first
+splitter in the chain, and each splitter is responsible for running its logic and passing the result
+to the next splitter in the chain, until the end of the chain is reached.
+
+As an example, let's say you ship some frozen products that are packaged in dry ice. You want to
+split frozen products and regular products in separate packages.
+
+You could accomplish this with a custom stock splitter such as the following:
+
+```ruby
+module AwesomeStore
+ module Stock
+ module Splitter
+ class FrozenItems < Spree::Stock::Splitter::Base
+ def split(packages)
+ split_packages = []
+
+ packages.each do |package|
+ # Split each package in frozen and non-frozen items
+ split_packages += split_package(package)
+ end
+
+ # `return_next` is a helper that will pass the split
+ # packages to the next splitter in the chain
+ return_next split_packages
+ end
+
+ private
+
+ def split_package(package)
+ frozen_items = []
+ non_frozen_items = []
+
+ package.contents.each do |item|
+ # We are assuming that `Spree::Variant` responds to `#frozen?`
+ if item.variant.frozen?
+ frozen_items << item
+ else
+ non_frozen_items << item
+ end
+ end
+
+ # The `build_package` method is a helper that takes a
+ # list of items and builds a package with them.
+ [
+ # Build the package for frozen items
+ build_package(frozen_items),
+ # Build the package for non-frozen items
+ build_package(non_frozen_items),
+ ]
+ end
+ end
+ end
+ end
+end
+```
+
+The implementation here is slightly more complicated than usual, so let's walk through it:
+
+1. First, we loop through each package that is passed to the splitter.
+2. Then, for each package, we separate the frozen and the non-frozen items in two separate packages.
+3. Then, we pass the final list of split packages to the next stock location splitter.
+
+:::caution
+
+As you may imagine, the order of stock splitters is important to determine the final result of the
+splitter chain. When you implement a custom stock splitter, make sure to add it in the right place!
+If you want full control over the splitter chain, you can override the `stock_splitters` array
+completely rather than appending to it.
+
+:::
+
+Now that we have our new splitter, we need to add it to the splitter chain:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.environment.stock_splitters << 'AwesomeStore::Stock::Splitter::FrozenItems'
+end
+```
+
+## Customizing rate estimation
+
+As far as the rate estimation is concerned, there are two pieces you can customize:
+
+* the **estimator,** to customize how Solidus calculates shipping rates for a shipment;
+* the **shipping rate selector**, to customize how Solidus selects a default shipping rate;
+* the **shipping rate sorter**, to customize how Solidus sorts shipping rates.
+
+In the next paragraphs, we'll see a brief example for each of these customizations!
+
+### **Shipping rate estimator**
+
+:::info
+
+The [default shipping rate estimator](https://github.com/solidusio/solidus/blob/6c0da5d618a6d04d13ef50ec01ae17c3b06f6259/core/app/models/spree/stock/estimator.rb)
+simply uses the shipping methods you have configured on your store, along with the respective
+calculators, to calculate the right shipping rate for each shipment/shipping method combination.
+
+:::
+
+For large/complex stores, using fixed shipping rates, or attempting to re-create the shipping rate
+calculation logic used by carriers, is simply not feasible. When that's the case, you can override
+Solidus' shipping rate estimator to bypass the configured shipping methods completely and use an
+external data source such as an API (e.g., [EasyPost](https://www.easypost.com/)).
+
+Let's see an example with EasyPost:
+
+```ruby
+module AwesomeStore
+ module Stock
+ class EasypostEstimator
+ def shipping_rates(package, _frontend_only = true)
+ # Create a new shipment with the EasyPost API and
+ easypost_rates = get_rates_from_easypost(package)
+
+ # Retrieve the rates for the EasyPost shipment
+ shipping_rates = easypost_rates.map do |easypost_rate|
+ # Turn the EasyPost rate into a Solidus shipping rate
+ build_shipping_rate(easypost_rate)
+ end
+
+ # Choose the default shipping rate through the configured shipping rate selector
+ unless shipping_rates.empty?
+ default_shipping_rate = Spree::Config.shipping_rate_selector_class.new(shipping_rates).find_default
+ default_shipping_rate.selected = true
+ end
+
+ # Sort the shipping rates through the configured shipping rate sorter
+ Spree::Config.shipping_rate_sorter_class.new(shipping_rates).sort
+ end
+
+ private
+
+ def get_rates_from_easypost(package)
+ # API integration logic here...
+ end
+
+ def build_shipping_rate(easypost_rate)
+ # Find or create a new shipping method in Solidus
+ # for this EasyPost rate
+ shipping_method = Spree::ShippingMethod.find_or_create_by(
+ carrier: easypost_rate.carrier,
+ service_level: easypost_rate.service,
+ ) do |shipping_method|
+ shipping_method.name = "#{easypost_rate.carrier} #{easypost_rate.service}"
+ shipping_method.calculator = Spree::Calculator::Shipping::FlatRate.create
+ shipping_method.shipping_categories = Spree::ShippingCategory.all
+ shipping_method.available_to_users = true
+ end
+
+ # Build a Solidus shipping rate for this EasyPost rate
+ Spree::ShippingRate.new(
+ shipping_method: shipping_method,
+ name: "#{easypost_rate.carrier} #{easypost_rate.service}",
+ cost: easypost_rate.rate,
+ )
+ end
+ end
+ end
+end
+```
+
+The API integration logic has been left out on purpose, so let's walk through the rest of the
+implementation:
+
+1. First, we call the EasyPost API to create a shipment and retrieve the proposed rates.
+2. Then, we transform the EasyPost rates into `Spree::ShippingRate` instances.
+3. Then, we choose the default rate through the configured shipping rate selector.
+4. Then, we sort the rates through the configured shipping rate sorter.
+5. Finally, we return the sorted rates.
+
+:::info
+
+Notice how, in the `build_shipping_rate` method, we are finding or creating the shipping method for
+each EasyPost rate, since we're not relying on the shipping methods stored in the DB but reading
+them directly from EasyPost. An alternative would be to only generate EasyPost rates for shipping
+methods that already exist in the Solidus DB (e.g., to give admins more granular control over which
+shipping methods to enable).
+
+:::
+
+Now that our estimator is ready, we just need to configure it:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.stock.estimator_class = 'AwesomeStore::Stock::EasypostEstimator'
+end
+```
+
+### Shipping rate selector
+
+:::info
+
+The [default shipping rate selector](https://github.com/solidusio/solidus/blob/6c0da5d618a6d04d13ef50ec01ae17c3b06f6259/core/app/models/spree/stock/shipping\_rate\_selector.rb)
+pre-selects the lowest-priced shipping rate for the user.
+
+:::
+
+What if we wanted to pre-select the shipping rate with the quickest delivery time? We could easily
+accomplish this through a custom shipping rate selector:
+
+```ruby
+module AwesomeStore
+ module Stock
+ class ShippingRateSelector
+ attr_reader :shipping_rates
+
+ def initialize(shipping_rates)
+ @shipping_rates = shipping_rates
+ end
+
+ def find_default
+ # This assumes `Spree::ShippingRate` responds to `#delivery_time`
+ # with the number of days it will take to deliver the package
+ shipping_rates.min_by(&:delivery_time)
+ end
+ end
+ end
+end
+```
+
+The logic here is extremely simple: we accept a list of shipping rates as input, and we simply
+return the shipping rate that needs to be selected as the default, by looking for the shipping rate
+with the lowest delivery time.
+
+As usual, we now need to tell Solidus to use our shipping rate selector:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.stock.shipping_rate_selector_class = 'AwesomeStore::Stock::ShippingRateSelector'
+end
+```
+
+### Shipping rate sorter
+
+:::info
+
+The [default shipping rate sorter](https://github.com/solidusio/solidus/blob/6c0da5d618a6d04d13ef50ec01ae17c3b06f6259/core/app/models/spree/stock/shipping\_rate\_sorter.rb)
+sorts the shipping rates by price, from lowest to highest.
+
+:::
+
+Let's say we also want to sort the shipping rates by delivery time, so that the customer can more
+easily make an informed decision.
+
+All we need to do is create a custom shipping rate sorter:
+
+```ruby
+module AwesomeStore
+ module Stock
+ class ShippingRateSorter
+ attr_reader :shipping_rates
+
+ def initialize(shipping_rates)
+ @shipping_rates = shipping_rates
+ end
+
+ def sort
+ # This assumes `Spree::ShippingRate` responds to `#delivery_time`
+ # with the number of days it will take to deliver the package
+ shipping_rates.sort_by(&:delivery_time)
+ end
+ end
+ end
+end
+```
+
+As you can see, shipping rate sorters are very simple: they accept a list of shipping rates and
+return it sorted. In our case, we are sorting the shipping rates by a custom `delivery_time`
+attribute.
+
+Then, we just tell Solidus to use our custom shipping rate sorter:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.stock.shipping_rate_sorter_class = 'AwesomeStore::Stock::ShippingRateSorter'
+end
+```
+
+## Customizing inventory unit creation
+
+Some stores may need to customize inventory unit creation. This is the case when stores have line
+items that don't map normally to the actual fulfilled items. For example, you might sell a "bundle"
+product that needs to be expanded into the composite items of the bundle in the shipment.
+
+There are three important classes that need to be overridden to accomplish customizations around
+this: the inventory unit builder, the availability validator, and the inventory validator.
+
+### Inventory unit builder
+
+The inventory unit builder is the class responsible for actually looping over the line items in the
+order and constructing the inventory units from them. This class is where you would implement your
+logic that transforms your "bundle" line items into their composite parts.
+
+You would implement your custom builder:
+
+```ruby
+module AwesomeStore
+ module Stock
+ class InventoryUnitBuilder
+ def initialize(order)
+ @order = order
+ end
+
+ # This method must return unsaved inventory units for all items in
+ # the given order.
+ def units
+ @order.line_items.flat_map do |line_item|
+ # Put your custom logic here.
+ end
+ end
+
+ # This method must return unsaved inventory units that that should
+ # exist for this line item, but currently do not
+ def missing_units_for_line_item(line_item)
+ # Put your custom logic here.
+ end
+ end
+ end
+end
+
+```
+
+Then, you would tell Solidus to use it:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.stock.inventory_unit_builder_class = 'AwesomeStore::Stock::InventoryUnitBuilder'
+end
+```
+
+### Availability validator
+
+The availability validator is responsible for validating that there is inventory available for a
+given line item. If you're customizing the mapping of line items to inventory units, you'll need to
+reflect your new behaviour here.
+
+You can implement a custom availability validator:
+
+```ruby
+module AwesomeStore
+ module Stock
+ class AvailabilityValidator < ActiveModel::Validator
+ def validate(line_item)
+ # This method takes a line item and returns a boolean indicating whether
+ # inventory is available for it. It also needs to attach a validation
+ # error to the quantity field of the line item.
+ end
+ end
+ end
+end
+```
+
+With the custom class in place, you can then tell Solidus to use it:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.stock.availability_validator_class = 'AwesomeStore::Stock::AvailabilityValidator'
+end
+```
+
+### Inventory validator
+
+The final class you'll need to customize to handle inventory unit creation customizations is the
+inventory validator. It is responsible for validating that the inventory units associated with a
+line item match what the line item requires as part of the checkout process.
+
+By default, this class simply checks that the number of inventory units matches the quantity of the
+line item.
+
+You can define a custom inventory validator:
+
+```ruby
+module AwesomeStore
+ module Stock
+ class InventoryValidator < ActiveModel::Validator
+ def validate(line_item)
+ # If the line item's inventory units do not match up with what it requires
+ # then this method should attach an error to the :inventory field of the
+ # line item and return that error.
+ #
+ # The stock logic looks like this:
+ if line_item.inventory_units.count != line_item.quantity
+ line_item.errors.add(:inventory, I18n.t(
+ 'spree.inventory_not_available',
+ item: line_item.variant.name
+ ))
+ end
+ end
+ end
+ end
+end
+```
+
+With your custom inventory validator defined, you can tell Solidus to use it:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.stock.inventory_validator_class = 'AwesomeStore::Stock::InventoryValidator'
+end
+```
diff --git a/versioned_docs/version-4.2/advanced-solidus/tax-calculation.mdx b/versioned_docs/version-4.2/advanced-solidus/tax-calculation.mdx
new file mode 100644
index 0000000..c13129f
--- /dev/null
+++ b/versioned_docs/version-4.2/advanced-solidus/tax-calculation.mdx
@@ -0,0 +1,349 @@
+---
+sidebar_position: 11
+needs-diataxis-rewrite: true
+---
+
+# Tax calculation
+
+## Architecture overview
+
+:::caution
+In the following paragraphs, we use the terms **tax calculator** and **rate calculator**. While they sound similar,
+they are different things: the taxation system in Solidus primarily relies on tax calculators, not rate calculators.
+
+While
+the [default tax calculators](https://github.com/solidusio/solidus/tree/v3.0/core/app/models/spree/tax\_calculator)
+delegate the actual taxation math to rate calculators, it's perfectly possible to write a custom tax
+calculator that doesn't use rate calculators and instead relies on its own logic, e.g. by querying
+an external API.
+
+:::
+
+Solidus's taxation system revolves around the concept of **tax calculators**. These are classes that
+implement the logic required for calculating taxes
+on [orders](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/tax\_calculator/default.rb)
+and [shipping rates](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/tax\_calculator/shipping\_rate.rb).
+
+The default tax calculators rely on **tax categories,** **tax rates** and **rate calculators**.
+These are concepts that help configure item taxation through the Solidus backend:
+
+* [**Tax categories**](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/tax\_category.rb)
+ are used to group tax rates togetherβall products and shipping methods are assigned a tax
+ category.
+* [**Tax rates**](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/tax\_rate.rb)
+ associate a tax category with a geographic zone and a rate calculator (and also determine whether
+ the tax is included in the item's original amount or not).
+* [**Rate calculators**](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/calculator/default\_tax.rb)
+ implement the actual math for converting a tax rate (e.g., 3% included in price) into a final
+ amount (e.g., $3.00, when computed on a $100.00 amount).
+
+This system is very flexible and allows you to insert your custom logic at different abstraction
+levels. But before we dive into how to customize it, let's take a look at the flow Solidus follows
+for calculating taxes on orders and shipping rates.
+
+### Order taxation
+
+:::info
+Note that promotions are applied to orders before taxes are calculated. This is to comply with tax
+regulations for value-added
+taxation [as outlined by the Government of the United Kingdom](https://www.gov.uk/vat-businesses/discounts-and-free-gifts#1)
+and for sales
+tax [as outlined by the California State Board of Equalization](http://www.boe.ca.gov/formspubs/pub113/).
+:::
+
+The flow for order taxation is the following:
+
+1. Whenever an order is updated, Solidus calls the `OrderUpdater` service. This service is
+ responsible for recalculating all the amounts on the order, including tax amounts. This is done
+ in the [`update_taxes` method](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/order\_updater.rb#L219),
+ which in turn calls the configured order adjuster.
+2. The [default order adjuster](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/tax/order\_adjuster.rb)
+ uses the configured order tax calculator to [determine](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/tax/order\_adjuster.rb#L17)
+ which taxes should be applied to the order, then builds an `OrderTaxation` object and uses it to
+ [apply them](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/tax/order\_adjuster.rb#L18).
+3. The [`Spree::OrderTaxation` class](https://github.com/solidusio/solidus/blob/63c937472de529cce99bf3ea8dd9f2a8cbc0e431/core/app/models/spree/order\_taxation.rb#L26)
+ applies the taxes on the order by _upserting_ the corresponding adjustments on the taxed items (i.e., line items and shipments).
+4. The order's `included_tax_total` or `additional_tax_total`
+ are [updated](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/order\_updater.rb#L219)
+ according to the adjustments created in the previous step.
+
+### Shipping rate taxation
+
+:::info
+For more information on when and how shipments and shipping rates are built, you can refer to
+the [Stock management](stock-and-fulfillment.mdx) guide.
+:::
+
+In addition to calculating taxes on orders Solidus also calculates taxes on shipping rates. The flow
+here is slightly different, and is kicked off by
+the [default stock estimator](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/stock/estimator.rb):
+
+1. Right after building the shipping rate for a shipment,
+ Solidus [calls the configured shipping rate tax calculator](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/stock/estimator.rb#L45)
+ to calculate the tax for each shipping rate.
+2. Shipping rates don't have adjustments, so the resulting taxes are stored in a
+ dedicated [`ShippingRateTax`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/shipping\_rate\_tax.rb)
+ model instead.
+
+:::caution
+
+Note that, while these tax amounts will be included in the shipping rates that are displayed to your
+user, Solidus will still re-calculate taxes on your shipment cost, and the final amount the user is
+charged depends on the shipment's cost rather than the shipping rate's cost.
+
+This is because you may have additional adjustments on your shipment, e.g. you're offering a "free
+shipping" promotion and want to completely discount shipping for the user. In this case, the
+shipping rate might be $10.0 + a $2.0 tax, but your shipment total will still be $0.0.
+
+You should treat tax calculation for shipping rates as a UI-only matter. The standard order tax
+calculation flow determines the price your user will pay.
+
+:::
+
+## Customizing tax calculation
+
+If you want to customize the tax calculation logic, you may do it at two different levels:
+
+* **Write a custom rate calculator:** with this approach, admins will create a tax rate that uses
+ your own rate calculator and tell Solidus to use that tax rate for your products and shipping
+ methods. The default tax calculator will call the configured tax rate, which in turn will delegate
+ the amount computation to your custom rate calculator.
+* **Replace the tax calculator (recommended):** this way, Solidus will not use the rate calculators
+ at all. This approach affords you maximum flexibility, since you'll be calculating taxes on the
+ entire order at the same time rather than on a per-item basis.
+
+### With a custom tax calculator
+
+The public interface for a tax calculator is pretty simple: it takes an order during initialization
+and exposes a `#calculate` method that returns
+a [`Spree::Tax::OrderTax`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/tax/order\_tax.rb)
+instance. This is an object that contains information about all taxes to apply to the item.
+
+
+#### Orders
+
+Here's a dead-simple custom order tax calculator that simply applies a 1% tax on all line items and
+a 2% tax on all shipments:
+
+```ruby title="app/models/awesome:store/tax:calculator/default.rb"
+module AwesomeStore
+ module TaxCalculator
+ class Default
+ def initialize(order)
+ @order = order
+ end
+
+ def calculate
+ Spree::Tax::OrderTax.new(
+ order_id: order.id,
+ line_item_taxes: line_item_rates,
+ shipment_taxes: shipment_rates
+ )
+ end
+
+ private
+
+ def line_item_rates
+ order.line_items.flat_map do |line_item|
+ calculate_rates(line_item)
+ end
+ end
+
+ def shipment_rates
+ order.shipments.flat_map do |shipment|
+ calculate_rates(shipment)
+ end
+ end
+
+ def calculate_rates(item)
+ amount = if item.is_a?(Spree::LineItem)
+ item.amount * 0.01
+ elsif item.is_a?(Spree::Shipment)
+ item.amount * 0.02
+ end
+
+ [
+ Spree::Tax::ItemTax.new(
+ item_id: item.id,
+ label: 'Custom Tax',
+ # NOTE: You still need to tie the item tax to a tax rate, otherwise
+ # Solidus will not be able to compare tax adjustments to each other
+ tax_rate: Spree::TaxRate.find_by(name: 'Custom Tax Rate'),
+ amount: amount,
+ included_in_price: false,
+ )
+ ]
+ end
+ end
+ end
+end
+```
+
+Once you have implemented your calculator, you need to tell Solidus to use it:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+
+ config.tax_calculator_class = 'AwesomeStore::TaxCalculator::Default'
+end
+```
+
+#### Shipping rates
+
+Here's a sample shipping rate tax calculator that applies a 3% tax to all shipping rates:
+
+```ruby title="app/models/awesome:store/tax:calculator/shipping:rate.rb"
+module AwesomeStore
+ module TaxCalculator
+ class ShippingRate
+ def initialize(order)
+ @order = order
+ end
+
+ def calculate(shipping_rate)
+ # Run your custom logic here and return an array
+ # of `Spree::Tax::ItemTax` objects. For example:
+
+ [
+ Spree::Tax::ItemTax.new(
+ item_id: shipping_rate.id,
+ label: 'Custom tax',
+ tax_rate: 0.03,
+ amount: shipping_rate.amount * 0.03,
+ included_in_price: false,
+ )
+ ]
+ end
+ end
+ end
+end
+```
+
+Once you have created the tax calculator, you need to tell Solidus to use your custom implementation
+instead of the default:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ config.shipping_rate_tax_calculator_class = 'AwesomeStore::TaxCalculator:ShippingRate'
+end
+```
+
+Reboot your server, and Solidus should start using your custom tax calculator!
+
+### With a custom rate calculator
+
+With a custom rate calculator, store administrators configure tax rates as usual in the Solidus
+backend, but select your custom rate calculator instead of the default one. When a tax rate is
+applied to an item, the custom tax calculator will be called and your logic will be triggered.
+
+A custom rate calculator is pretty simple, and it looks like the following:
+
+```ruby title="app/models/awesome:store/calculator/default:tax.rb"
+module AwesomeStore
+ module Calculator
+ class DefaultTax < Spree::Calculator::DefaultTax
+ class << self
+ def description
+ 'My Custom Calculator'
+ end
+ end
+
+ def compute_line_item(line_item)
+ calculate(line_item.total_before_tax)
+ end
+
+ def compute_shipping_rate(shipping_rate)
+ calculate(shipping_rate.total_before_tax)
+ end
+
+ def compute_shipment(shipment)
+ calculate(shipment.total_before_tax)
+ end
+
+ private
+
+ def calculate(amount)
+ # Skip the calculation if this tax rate is not active.
+ return 0 unless calculable.active?
+
+ # e.g. do some API call here and return the tax amount
+ # ...
+ end
+ end
+ end
+end
+```
+
+As you can see, you can specify different logic for calculating taxes on line items, shipping rates
+and shipments, if you need to (e.g., if you're not charging tax on shipments). If you're using the
+same logic for all objects, you may further simplify the implementation:
+
+```ruby title="app/models/awesome:store/calculator/default:tax.rb"
+module AwesomeStore
+ module Calculator
+ class DefaultTax < Spree::Calculator::DefaultTax
+ class << self
+ def description
+ 'My Custom Calculator'
+ end
+ end
+
+ def compute_item(item)
+ # Skip the calculation if the tax rate is not active.
+ return 0 unless calculable.active?
+
+ amount = item.total_before_tax
+
+ # e.g. do some API call here and return the tax amount
+ # ...
+ end
+
+ alias_method :compute_shipment, :compute_item
+ alias_method :compute_line_item, :compute_item
+ alias_method :compute_shipping_rate, :compute_item
+ end
+ end
+end
+```
+
+This is how the default tax calculator is implemented, for instance!
+
+Once you have implemented your custom rate calculator, you need to register it by adding the
+following to an initializer:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+ config.environment.calculators.tax_rates << 'AwesomeStore::Calculator::DefaultTax'
+end
+```
+
+At this point, you can create a new tax rate in the admin panel and select your custom rate
+calculator. In the admin panel, go to **Settings -> Taxes -> Tax Rates** and click on **New Tax
+Rate**, then configure the new tax rate like this (you may want to change the validity period, zone
+and tax categories):
+
+
+
+You can now save your tax rate, and your custom rate calculator will start being called for all
+items in one of the tax rate's tax categories, as long as they belong to the tax rate's zone!
+
+:::info
+
+You'll notice that we entered a **Rate** of 0.0 in the configuration above, and that we disabled
+the **Show rate in label** option.
+
+This is because, in our custom rate calculator, the user-provided tax rate is not being used at all:
+instead, we are calling an external API to return the correct tax rate for us.
+
+This kind of inconsistency is one of the reasons you should almost always use a custom tax
+calculator instead of a custom rate calculator.
+
+:::
+
+## How-to guides
+
+- [How to setup Colorado Delivery Fee][how-to-setup-colorado-delivery-fee]
+
+[how-to-setup-colorado-delivery-fee]: ../how-tos/how-to-setup-colorado-delivery-fee
diff --git a/versioned_docs/version-4.2/cookbook/_category_.json b/versioned_docs/version-4.2/cookbook/_category_.json
new file mode 100644
index 0000000..721a30a
--- /dev/null
+++ b/versioned_docs/version-4.2/cookbook/_category_.json
@@ -0,0 +1,6 @@
+{
+ "label": "Cookbook",
+ "position": 5,
+ "collapsible": true,
+ "collapsed": true
+}
diff --git a/versioned_docs/version-4.2/cookbook/redefining-checkout-steps.mdx b/versioned_docs/version-4.2/cookbook/redefining-checkout-steps.mdx
new file mode 100644
index 0000000..aefb0a2
--- /dev/null
+++ b/versioned_docs/version-4.2/cookbook/redefining-checkout-steps.mdx
@@ -0,0 +1,216 @@
+---
+sidebar_position: 2
+needs-diataxis-rewrite: true
+---
+
+# Redefining checkout steps
+
+Solidus comes bundled with a robust checkout system that caters to the needs of the majority of
+eCommerce stores. However, this system doesn't fit the needs for every business.
+
+In this guide, we'll cover the steps you need to take to customize the checkout flow in your Solidus
+store.
+
+## The default checkout flow
+
+The default Solidus checkout flow follows these steps, from start to finish:
+
+1. Cart
+2. Address
+3. Delivery
+4. Payment (if needed)
+5. Confirm
+6. Complete
+
+## Removing checkout steps
+
+You'll need to override the order model to add or remove checkout steps. Inside the override, you
+just need to add `remove_checkout_step` and pass in the name of the step you want to remove. For
+example, if you wanted to remove the address step, your override might look like this:
+
+```ruby title="app/overrides/my_app/spree/order/remove_checkout_step.rb"
+# frozen_string_literal: true
+
+module MyApp
+ module Spree
+ module Order
+ module RemoveCheckoutStep
+ def self.prepended(base)
+ base.remove_checkout_step :address
+ end
+
+ ::Spree::Order.prepend self
+ end
+ end
+ end
+end
+```
+
+Please keep in mind the following caveats for removing checkout steps:
+
+* If you remove the address step but keep the delivery step, the delivery step will break, as it
+expects the order to have an address.
+* If you remove the payment step but the order has a positive balance, the order will not be able to
+complete.
+
+## Adding checkout steps
+
+If you want to add a custom checkout step, you will need to call `add_checkout_step` in the order
+override, and pass in your custom step name, as well as a `before` or `after` attribute, so that
+the order state machine knows where to put this custom step in the checkout flow.
+
+```ruby title="app/overrides/my_app/spree/order/add_checkout_step.rb"
+# frozen_string_literal: true
+
+module MyApp
+ module Spree
+ module Order
+ module AddCheckoutStep
+ def self.prepended(base)
+ base.insert_checkout_step :my_custom_step, { before: :confirm }
+ end
+
+ ::Spree::Order.prepend self
+ end
+ end
+ end
+end
+```
+
+You will also need to add a view for this custom step:
+
+```erb title="app/views/spree/checkout/_my_custom_step.html.erb"
+
+ The customer will see this partial when they are on your checkout step.
+ Be sure to add a continue button!
+
+```
+
+:::warning
+
+The path of overrides is extremely important: the directory where you put the override **must**
+match the path of the view you want to customize (minus the extension), and the override **must**
+have the `.html.erb.deface` extension for Deface to apply it correctly. If an override is not
+getting applied, the first thing you look at should be the path.
+
+:::
+
+:::info
+
+Deface provides a lot of selectors, actions and tools for debugging your overrides β taking the time
+to understand how to use them correctly will help you a lot when overriding different parts of the
+backend. You can look at the [Deface documentation](https://github.com/spree/deface) and even at
+Solidus extensions if you need some inspiration with a tricky override.
+
+:::
+
+The last thing we need to do for our button to appear properly is add
+the `spree.remove_from_rejected` translation key to our application. We just need to add the key to
+the `config/locales/en.yml` file in our application, like this:
+
+```yaml title="config/locales/en.yml"
+en:
+ spree:
+ remove_from_rejected: Remove from rejected
+```
+
+:::info
+
+While it's also possible to hardcode the string in your views/controllers, using Rails' native
+internationalization features will allow you to write code that is easier to maintain and will make
+it easier to go global, should you ever need it.
+
+:::
+
+:::info
+
+You can override default Spree translations in the exact same way, if you want to change the default
+labels or messages in the backend.
+
+:::
+
+## Adding new search form fields
+
+The last point of our feature requires that users can list all the orders that are rejected. The
+most straightforward solution is adding a field to the orders' search form for our new `:rejected`
+attribute.
+
+:::info
+
+Search forms in Solidus use the [ransack](https://github.com/activerecord-hackery/ransack) gem under
+the hood. Please, see its documentation for a complete description of everything that is supported.
+
+:::
+
+The orders' search form is visible from the "Orders" menu item. We've already
+seen [how to override views with Deface](customizing-the-backend.mdx#defacing-admin-views). This time
+we need to override the index template for orders:
+
+```erb title="app/overrides/spree/admin/orders/index/add_rejected_filter.html.erb.deface"
+
+
+```
+
+The new field is already visible. However, for security reasons, you're still required to explicitly
+include the new attribute to the list of allowed queryable columns:
+
+```ruby title="config/initializers/spree.rb"
+# ...
+Rails.application.config.to_prepare do
+ Spree::Order.whitelisted_ransackable_attributes |= ['rejected']
+end
+```
+
+After restarting the server, you can try it out and confirm it's working as expected!
+
+## Adding new menu items
+
+If you wanted to give maximum prominence to the problem with rejected orders, you could consider
+adding a new item to the main admin menu. You can do that by creating a
+new [`Spree::BackendConfiguration::MenuItem`](https://github.com/solidusio/solidus/blob/df51df62fa9b829216958b21b20514b7a3d87b30/backend/lib/spree/backend\_configuration.rb#L37)
+instance:
+
+```ruby title="config/initializers/spree.rb"
+# ...
+Spree::Backend::Config.configure do |config|
+ # ...
+ config.menu_items << config.class::MenuItem.new(
+ [:rejected_orders],
+ 'ban',
+ url: '/admin/orders?q[rejected_eq]=true',
+ position: 0
+ )
+end
+```
+
+Our new `:rejected_orders` menu item points to the same URL generated when only
+the [previously introduced filter](customizing-the-backend.mdx#adding-new-search-form-fields) is
+selected in the search form. We use [`ban`](https://fontawesome.com/search?q=ban\&s=solid%2Cbrands)
+as its [Font Awesome](https://fontawesome.com) icon and want its position to be the first one.
+
+:::info
+
+The `position` argument is always considered after the "Order" menu item, which will always be on
+the very top of the sidebar. Therefore, the `:rejected_orders` item will be second in our example.
+
+:::
+
+Finally, we need to add the translation so its label is rendered:
+
+```yaml title="config/locales/en.yml"
+# ...
+en:
+ spree:
+ admin:
+ tab:
+ rejected_orders: Rejected orders
+```
+
+After restarting again your server, you can see how everything is in place.
+
+There're other interesting options that you can give on the initialization of a menu item:
+
+* `condition:` can contain a `Proc` for when the menu item should be displayed. For instance, if we
+only wanted our example to be rendered when there's at least one rejected order, we could
+pass `condition: -> { Spree::Order.where(rejected: true).any? }`.
+* `match_path:` allows more flexibility to match the current URL and render the custom item as being
+active in the menu. For instance, we might want to have our example highlighted whenever the
+filter has been selected, regardless of other filters being
+applied: `match_path: %r{[rejected_eq]=true}`.
+* `label:` allows changing the key under `{lang}.spree.admin.tab` where the label translation can be
+found in the locale file.
+* `partial:` can be used in complex scenarios when you want a partial to be rendered as content
+within your menu item. For instance, `partial: 'spree/admin/orders/rejected_orders'`.
+
+## Customizing assets
+
+Solidus leverages the [Rails asset
+pipeline](https://guides.rubyonrails.org/asset_pipeline.html) to allow for
+customization and overriding of your backend assets. We recommend that you
+familiarize yourself with the Rails asset pipeline before going any further.
+
+Some aspects of the style in the backend have been extracted into SCSS
+variables. You can look at them in the [`spree/backend/globals/_variables.scss`
+file](https://github.com/solidusio/solidus/blob/master/backend/app/assets/stylesheets/spree/backend/globals/_variables.scss).
+If you want to redefine some of them, you can create a
+`spree/backend/globals/_variables_override.scss` file under your application
+stylesheets directory. For instance, if you wanted to make the header black:
+
+```scss title="app/assets/stylesheets/spree/backend/globals/_variables_override.scss"
+$color-header-bg: #000;
+```
+
+For more elaborated changes, take into account that, when you install Solidus,
+the two following manifest files are created:
+
+* `vendor/assets/stylesheets/spree/backend/all.css`, for your CSS assets.
+* `vendor/assets/javascripts/spree/backend/all.js`, for your Javascript assets.
+
+If you glance at them, you'll see that they both include a `spree/backend`
+file, which is located in the Solidus' backend Rails engine, and, after that,
+they also require all the files recursively under their directories (the
+`require_tree .` directive). Any file you put under
+`vendor/assets/{stylesheets,javascripts}/spree/backend` directories will be
+loaded after Solidus' default assets, allowing you not only to customize but to
+override CSS and Javascript code.
+
+:::warning
+
+If you can, always go for adding instead of overriding default assets. There's
+no guarantee that a Solidus style won't change in the future and make your
+override useless or behave in unexpected ways.
+
+:::
+
+Nonetheless, you might want to put the assets you control under `app/assets`,
+just like Sprockets recommends. For that, you need to add another directive to
+the generated manifest, as we'll demonstrate below.
+
+:::danger
+
+When dropping a file under `app/assets`, you must be careful. By default, the
+generated manifests under
+`app/assets/{stylesheets,javascript}/application.{css,js}` in a new Rails
+application contain directives to load any file below their hierarchy
+recursively. I.e., a style meant only to be used in the backend could interfere
+with your storefront. Make sure you update the generated manifests accordingly,
+including the removal of the `require_tree .` directive.
+
+:::
+
+As a simple example, let's make the delete button a bit more prominent by
+uppercasing its label. We'll require another file from the Solidus CSS
+manifest at `vendor/stylesheets/spree/backend/all.css`
+
+```diff
+ *= require spree/backend
+ *= require_self
+ *= require_tree .
++ *= require spree/backend/custom
+ */
+```
+
+We'll place it within the `app/assets/stylesheets` directory:
+
+```css title="app/assets/stylesheets/spree/backend/custom.css"
+.btn-remove_order_from_rejected {
+ text-transform: uppercase;
+}
+```
+
+Pretty much the same when it comes to Javascript. In this case, we'd need to
+modify the `vendor/assets/javascripts/spree/backend/all.js` manifest.
+
+```diff
+ //= require rails-ujs
+ //= require spree/backend
+ //= require_tree .
++//= require spree/backend/custom
+```
+
+We could then put any new behavior in the expected file:
+
+```javascript title="app/assets/javascripts/spree/backend/custom.js"
+// Javascript code
+```
+
+## Taking it from here
+
+Congratulations! You have implemented your first custom feature for the Solidus backend.
+
+Of course, we have just scratched the surface of what's possible: the backend provides a lot of UI
+components and capabilities you may leverage. We suggest spending some time in the backend's
+codebase to get accustomed with all the different tools at your disposal, and doing some
+planning/research before every custom feature.
+
+By using a combination of custom controller actions, view overrides and automated tests, you'll be
+able to write custom admin features that are fully integrated with the Solidus experience, and yet
+are a joy to maintain and evolve over time.
diff --git a/versioned_docs/version-4.2/customization/customizing-the-core.mdx b/versioned_docs/version-4.2/customization/customizing-the-core.mdx
new file mode 100644
index 0000000..40b64d5
--- /dev/null
+++ b/versioned_docs/version-4.2/customization/customizing-the-core.mdx
@@ -0,0 +1,231 @@
+---
+sidebar_position: 2
+needs-diataxis-rewrite: true
+---
+
+# Customizing the core
+
+This guide will teach you how to customize different aspects of Solidus' business logic, as well
+as the different techniques for doing so.
+
+## Customization strategies
+
+Most Solidus stores don't stop at customizing the storefront: in fact, if all you need is a custom
+storefront, perhaps Solidus is not a good choice in the first place. Solidus users want to be able
+to customize every single aspect of their store, not just how the store appears to customers but
+also what happens under the hood when customers browse it and place an order.
+
+Because Solidus is built on Ruby and Ruby on Rails, there are no limits to what can be customized.
+You can literally change every single aspect of the framework's business logic however you see fit,
+either through built-in customization hooks or Ruby's powerful meta-programming features.
+
+How you customize Solidus will have implications for the stability and maintainability of your
+store, so it's good to know the set of tools at your disposal and make an informed decision
+depending on your specific use case. In most cases, you'll want to adopt a mix of these approaches.
+
+### Using built-in hooks
+
+Solidus provides a rich and powerful API for customizing different aspects of your store's business
+logic. By defining which classes get called to perform certain tasks in your store, you have the
+option to either enrich or completely replace Solidus' default functionality.
+
+The [`Spree::AppConfiguration`](https://github.com/solidusio/solidus/blob/v3.0/core/lib/spree/app\_configuration.rb)
+class has a list of all the service object classes that can be natively customized. Look through
+the source code of that class and see if there's an option that resembles what you need to do. If
+yes, bingo!
+
+:::info
+
+`Spree::AppConfiguration` is not the only configuration class that contains service objects. Before
+resorting to other customization methods, search through Solidus' source code and see if there are
+any other options that allow you to replace the class you need.
+
+:::
+
+For instance, Solidus merges a user's "guest" cart with the cart associated to their user account
+when they sign in. Let's suppose you don't like this default behavior, and would like to keep the
+two carts separate at all times instead, to avoid confusion for the user. You can see there
+is [an option](https://github.com/solidusio/solidus/blob/v3.0/core/lib/spree/app\_configuration.rb#L382)
+in the configuration that allows us to control precisely this behavior:
+
+```ruby title="solidus/core/lib/spree/app_configuration.rb"
+# Allows providing your own class for merging two orders.
+#
+# @!attribute [rw] order_merger_class
+# @return [Class] a class with the same public interfaces
+# as Spree::OrderMerger.
+class_name_attribute :order_merger_class, default: 'Spree::OrderMerger'
+```
+
+Let's also take a look at
+the [default `Spree::OrderMerger` class](https://github.com/solidusio/solidus/blob/475d9db5d0291dd4aeddc58ec919988c336729bb/core/app/models/spree/order\_merger.rb)
+to understand what public API Solidus expects us to implement in our custom version:
+
+```ruby title="solidus/core/app/models/spree/order_merger.rb"
+module Spree
+ class OrderMerger
+ # ...
+
+ def initialize(order)
+ # ...
+ end
+
+ def merge!(other_order, user = nil)
+ # ...
+ end
+
+ # ...
+ end
+end
+```
+
+As you can see, the order merger exposes two public methods:
+
+* `#initialize`, which accepts an order.
+* `#merge!`, which accepts another order to merge with the first one and (optionally) the current
+user.
+
+Equipped with this information, we can now write our "nil" order merger:
+
+```ruby title="app/models/amazing_store/nil_order_merger.rb"
+module AmazingStore
+ class NilOrderMerger
+ attr_reader :order
+
+ def initialize(order)
+ @order = order
+ end
+
+ def merge!(other_order, user = nil)
+ order.associate_user!(user) if user
+ end
+ end
+end
+```
+
+Finally, now that we have the new merger, we need to tell Solidus to use it:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+ config.order_merger_class = 'AmazingStore::NilOrderMerger'
+end
+```
+
+Restart your application server, and Solidus should start using your shiny new order merger!
+
+:::warning
+
+When overriding critical functionality, you may also want to make sure that your feature is working
+correctly in integration. The unit test we have written, for instance, doesn't guarantee in any way
+that our custom order merger responds to the expected public API and will not break as soon as
+Solidus tries to call it.
+
+:::
+
+### Using overrides
+
+Solidus is a large and complex platform and, while new built-in customization hooks and events are
+introduced all the time to make the platform easier to extend, there may be situations where Solidus
+doesn't provide an official API to customize what you need. When that's the case, Ruby's
+meta-programming features come to the rescue, allowing you to extend and/or override whatever you
+want.
+
+As an example, suppose you want to introduce an environment variable that allows you to temporarily
+make all products unavailable in the storefront.
+
+When you look at Solidus' source code, you will notice that `Spree::Product` already has
+an [`#available?`](https://github.com/solidusio/solidus/blob/v3.0/core/app/models/spree/product.rb#L171)
+method to control a product's visibility. This is the original implementation:
+
+```ruby
+# Determines if product is available. A product is available if it has not
+# been deleted and the available_on date is in the past.
+#
+# @return [Boolean] true if this product is available
+def available?
+ !deleted? && available_on&.past? && !discontinued?
+end
+```
+
+As you can see, there is no "clean" way we can extend this method: no built-in customization hooks
+or events that can help us.
+
+However, we can still use plain old Ruby and the power
+of [`Module#prepend`](https://ruby-doc.org/core-2.6.1/Module.html#method-i-prepend). If you're not
+familiar with it, `#prepend` is a method that allows us to insert a module at the beginning of
+another module's ancestors chain. Think of it as taking a module A and placing it "in front" of
+another module B: when you call a method on module B, Ruby will first hit module A and then continue
+down the chain of ancestors.
+
+In the Solidus ecosystem, we call **overrides** to the modules that are prepended**.** Overrides are
+usually named in a descriptive way that expresses how the override extends the original class.
+
+:::info
+
+The Solidus ecosystem used to rely heavily on `#class_eval` for overrides, but `#prepend` is a
+cleaner and more easily maintainable approach. You may see old guides, tutorials and extensions
+still using `#class_eval`, but you should know this is a deprecated pattern.
+
+:::
+
+:::warning
+
+If you're not yet in Ruby 3 and you're prepending a module, take note that if Rails includes the
+module before the `prepend` is called, then Rails might not be able to include the prepended
+behavior. This might happen if you're prepending a views helper or an ActiveSupport concern. For
+these cases, you might have no choice but to use `#class_eval` to override the module. For more
+information, please
+see [Module.prepend does not work nicely with included modules](https://github.com/solidusio/solidus/issues/3371).
+
+:::
+
+To begin with, you need to set up the directory where you'll place your overrides so that Rails can
+pick them up:
+
+```ruby title="config/application.rb"
+overrides = "#{Rails.root}/app/overrides"
+Rails.autoloaders.main.ignore(overrides)
+config.to_prepare do
+ Dir.glob("#{overrides}/**/*.rb").each do |override|
+ load override
+ end
+end
+```
+
+In our example, we can customize the `Spree::Product#available?` method by writing a module that
+will be prepended to `Spree::Product`. Here's our `AddGlobalHiddenFlag` override:
+
+```ruby title="app/overrides/amazing_store/spree/product/add_global_hidden_flag.rb"
+module AmazingStore
+ module Spree
+ module Product
+ module AddGlobalHiddenFlag
+ def available?
+ ENV['MAKE_PRODUCTS_UNAVAILABLE'] == false && super
+ end
+
+ ::Spree::Product.prepend self
+ end
+ end
+ end
+end
+```
+
+As you can see, we are not only able to override the default `#available?` implementation, but we
+can also call the original implementation with `super`. This allows you to decide whether you want
+to extend the original method or completely override it.
+
+:::warning
+
+You should always prefer customizing Solidus via public, standardized APIs such as the built-in
+customization hooks and the event bus, whenever possible. When you use a supported API, it's much
+less likely your customization will be broken by a future upgrade that changes the part of the code
+you are overriding.
+
+:::
+
+### Using the event bus
+
+Please, take a look at the [Subscribing to events ](subscribing-to-events.mdx)chapter for a complete
+description of the Event Bus on Solidus.
diff --git a/versioned_docs/version-4.2/customization/customizing-your-storefront.mdx b/versioned_docs/version-4.2/customization/customizing-your-storefront.mdx
new file mode 100644
index 0000000..1946036
--- /dev/null
+++ b/versioned_docs/version-4.2/customization/customizing-your-storefront.mdx
@@ -0,0 +1,196 @@
+---
+sidebar_position: 1
+needs-diataxis-rewrite: true
+---
+
+# Customizing your storefront
+
+In this guide, we're going to show you an example of how to customize your storefront. In
+particular, we're going to add a hero image to the home page:
+
+
+
+For the rest of the guide, we'll assume that you have a running Solidus application with
+SolidusStarterFrontend as your storefront.
+
+:::info
+
+With Solidus 3.2, installing Solidus would automatically install [SolidusStarterFrontend][], which
+is the new frontend solution for Solidus. As a Rails application template, it copies all the
+Solidus frontend code to your Rails app. This makes the frontend code directly accessible to your
+application. If you're familiar with customizing views and assets on a regular Rails app, you
+already have the knowledge to customize the frontend of a Solidus application.
+
+Before Solidus 3.2, we used to package the storefront as a Rails engine, called
+[SolidusFrontend][]. In theory, this would allow users to upgrade their
+storefront whenever there is a new update. However, we found that users rarely
+did this, since they tend to heavily customize their storefronts.
+Furthermore, packaging the storefront as a Rails engine made frontend
+customization more difficult than necessary.
+
+If you're interested to learn more about the transition from SolidusFrontend to
+SolidusStarterFrontend, please check out the
+[Getting Started with Solidus Starter Frontend][Getting Started with SSF] post.
+
+:::
+
+## Editing a view
+
+If we inspect the home page, we'll see that the hero container has the following HTML code:
+
+```html
+
+
+
+ The only eCommerce platform youβll ever need.
+
+
+
+ Build, customize and scale your store with no limits or license fees.
+ Solidus is the free, open-source eCommerce framework for
+ digitally-native brands, fast-growing online businesses and pragmatic
+ developers.
+
+
+
+```
+
+If we search for views with the CSS class `"hero__container"` in your Solidus app, we find
+`app/views/spree/home/_hero.html.erb`. This partial was copied to your app during the installation
+of SolidusStarterFrontend.
+
+Let's use the [`image_tag`][image_tag] helper to try and add a placeholder for the hero image in
+that partial:
+
+```diff
+
++ <%= image_tag('hero-image') %>
+
+
+ The only eCommerce platform youβll ever need.
+
+
+
+ Build, customize and scale your store with no limits or license fees.
+ Solidus is the free, open-source eCommerce framework for
+ digitally-native brands, fast-growing online businesses and pragmatic
+ developers.
+
+
+
+```
+
+If we test it out, we'll find that we need to add the hero image to the asset pipeline:
+
+
+
+## Running the frontend specs
+
+If you try and run the [RSpec][] test suite at this point, you might also see some spec failures
+because of the asset pipeline error:
+
+```
+$ bundle exec rspec spec/requests/spree/home_spec.rb
+
+...
+
+Failures:
+
+ 1) Home layout provides current user to the searcher class
+ Failure/Error: <%= image_tag('hero-image') %>
+
+ ActionView::Template::Error:
+ The asset "hero-image" is not present in the asset pipeline.
+ # ./app/views/spree/home/_hero.html.erb:3:in `_app_views_spree_home__hero_html_erb___8794442347239906_218740'
+ # ./app/views/spree/home/index.html.erb:1:in `_app_views_spree_home_index_html_erb__1597336803422850061_218720'
+ # ./spec/requests/spree/home_spec.rb:18:in `block (2 levels) in '
+ # ------------------
+ # --- Caused by: ---
+ # Sprockets::Rails::Helper::AssetNotFound:
+ # The asset "hero-image" is not present in the asset pipeline.
+ # ./app/views/spree/home/_hero.html.erb:3:in `_app_views_spree_home__hero_html_erb___8794442347239906_218740'
+
+...
+
+```
+
+When you installed SolidusStarterFrontend to your app, you also received some specs for testing
+the frontend. These specs are part of the SolidusStarterFrontend test suite itself, that is, they
+are used in testing the SolidusStarterFrontend project. By copying these specs to your app, you get
+a good starting point for testing your application.
+
+## Adding an image
+
+We'll be using this image as the hero image for the home page:
+
+
+
+Following the [Rails Assets Pipeline guide][], we're supposed to save this image in the
+`app/assets/images` directory. Once we save the image there, the home page now looks like this:
+
+
+
+As you can see, we'll need to update the stylesheets in order to move the image above the hero
+message.
+
+## Customizing a stylesheet
+
+If we search for the word "hero" in the stylesheets of the app, we would find
+`app/assets/stylesheets/spree/frontend/components/presentation/_hero.scss`:
+
+```scss
+.hero {
+ background: $color-background-dark;
+ color: $color-copy-bright;
+ padding: 10rem 0;
+
+ &__container {
+ @extend %section-spacings;
+ display: flex;
+ }
+}
+```
+
+Like the hero partial from earlier, this [Sass][] stylesheet was copied to your Rails app when
+SolidusStarterFrontend was installed on the app.
+
+Let's update the stylesheet to
+
+1. change the flex direction of the container to `column`, and
+2. add a bottom margin to the hero image
+
+```diff
+ .hero {
+ background: $color-background-dark;
+ color: $color-copy-bright;
+ padding: 10rem 0;
+
+ &__container {
+ @extend %section-spacings;
+ display: flex;
++ flex-direction: column;
++
++ img {
++ margin-bottom: 3rem;
++ }
+ }
+ }
+```
+
+With those changes, the hero section now looks much better:
+
+
+
+## Summary
+
+Since Solidus copies all of the starter frontend code to your app, modifying frontend views and
+assets is similar to any other Rails application. You're also provided an initial test suite to
+test drive your frontend modifications.
+
+[Getting Started with SSF]: https://solidus.io/blog/2022/06/27/getting-started-with-solidus-starter-frontend
+[image_tag]: https://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html#method-i-image_tag
+[Rails Assets Pipeline guide]: https://guides.rubyonrails.org/asset_pipeline.html#coding-links-to-assets
+[RSpec]: https://rspec.info
+[Sass]: https://sass-lang.com
+[SolidusFrontend]: https://github.com/solidusio/solidus_frontend
+[SolidusStarterFrontend]: https://github.com/solidusio/solidus_starter_frontend
diff --git a/versioned_docs/version-4.2/customization/images/deface-example.png b/versioned_docs/version-4.2/customization/images/deface-example.png
new file mode 100644
index 0000000..2c55536
Binary files /dev/null and b/versioned_docs/version-4.2/customization/images/deface-example.png differ
diff --git a/versioned_docs/version-4.2/customization/subscribing-to-events.mdx b/versioned_docs/version-4.2/customization/subscribing-to-events.mdx
new file mode 100644
index 0000000..d35499b
--- /dev/null
+++ b/versioned_docs/version-4.2/customization/subscribing-to-events.mdx
@@ -0,0 +1,328 @@
+---
+sidebar_position: 3
+needs-diataxis-rewrite: true
+---
+
+# Subscribing to events
+
+Solidus comes with a publish-subscribe system courtesy
+of [Omnes](https://github.com/nebulab/omnes) (a pub/sub library for Ruby). It allows applications to
+hook into Solidus events (like completing an order) and extend the associated behavior.
+
+:::info
+
+You can run `Spree::Bus.registry.event_names`to get the list of available events.
+
+:::
+
+### Use cases
+
+The Event Bus should not be seen as a way to add behavior to the domain model that published the
+event. With that, we mean that subscribers' logic should be independent of the business flow that
+triggered them.
+
+For instance, take the `:order_finalized` event as an example. Collecting stats or sending a
+confirmation SMS to the user would be good candidates for subscribers. However, if you need to
+perform some logic that could prevent an order from being marked as completed, like checking some
+conditions about the user, that should not be a subscriber. Instead, you should look at other
+options to [customize the Solidus core](customizing-the-core.mdx).
+
+The reason is a direct consequence of the decoupling the Event Bus provides. That's something good,
+as it makes the upstream publisher independent of its subscribers' interface. However, it also
+introduces indirection, and you don't want to jump from subscriber to subscriber to know why
+something in the core transaction didn't work as expected. Think of what happens if your subscribers
+spawn an async process; you don't want the main flow to wait for them before completing or fail if
+they do.
+
+### Subscription to events
+
+Imagine you want to send an SMS whenever an order is completed. Luckily, Solidus emits an event
+called `:order_finalized` when that happens.
+
+To hook into that event, you can create an Omnes subscriber:
+
+```ruby title="app/subscribers/my_store/sms_subscriber.rb"
+module MyStore
+ class SmsSubscriber
+ include Omnes::Subscriber
+
+ handle :order_finalized,
+ with: :notify_order_completed,
+ id: :sms_notify_order_completed
+
+ def notify_order_completed(event)
+ order = event.payload[:order]
+ SmsService.new.notify_order_completed(order)
+ end
+ end
+end
+```
+
+There're other possible ways to create subscriptions with Omnes. You can, for instance, subscribe to
+all events or run subscriptions asynchronously. Check its [README](https://github.com/nebulab/omnes)
+for details.
+
+You still need to subscribe to the Solidus event bus, globally accessible as the `Spree::Bus`
+constant. To activate subscriptions on load and refresh them on reload, wrap the code within
+a [`#to_prepare`](https://api.rubyonrails.org/classes/ActiveSupport/Reloader.html#method-c-to\_prepare)
+call in a Rails initializer:
+
+```ruby title="config/initializers/omnes.rb"
+# frozen_string_literal: true
+
+Rails.application.config.to_prepare do
+ MyStore::SmsSubscriber.new.subscribe_to(Spree::Bus)
+end
+```
+
+:::warning
+
+Unlike in other Omnes buses you might want to create, be sure not to call `Spree::Bus.clear` on
+the `#to_prepare` block. `Spree::Bus` is owned by the Solidus engine, which is already taking care
+of cleaning it before code reload. If you clean it again, you'll lose Solidus defined events and
+subscriptions.
+
+:::
+
+### Custom events
+
+You're free to register your custom events into `Spree::Bus`. However, it's a good practice if you
+namespace them so that you won't conflict with new events added in the future to core Solidus:
+
+```ruby
+Spree::Bus.register(:my_app_custom_event)
+```
+
+### Testing events
+
+The Event Bus on Solidus is a global bus. That means you might need a way to temporarily disable it
+except for the subscriber you want to test. The `.performing_only` method can be used to only listen
+to a given subscription for the duration of a block.
+
+```ruby title="spec/subscribers/my_store/sms_notification_subscriber_spec.rb"
+require 'rails_helper'
+
+RSpec.describe MyStore::SmsNotificationSubscriber do
+ let(:sms_queue) { SmsService.test_queue }
+ let(:subscription) { Spree::Bus.subscription(:sms_notify_order_completed) }
+
+ it 'sends an SMS when an order is finalized' do
+ order = create(:order)
+
+ Spree::Bus.performing_only(subscription) do
+ Spree::Bus.publish(:order_finalized, order: order)
+ end
+
+ expect(sms_queue.count).to be(1)
+ end
+end
+```
+
+#### Stubbing events
+
+Solidus also comes with stub helpers to make it straightforward to test that an event has been
+fired.
+
+To begin with, you need to include the `Spree::TestingSupport::BusHelpers` in your test file. After
+that, you need to call the `stub_spree_bus` method before asserting that a given event was
+published.
+
+```ruby title="spec/services/my_store/custom_service_spec.rb"
+require 'rails_helper'
+require 'spree/testing_support/bus_helpers'
+
+RSpec.describe MyStore::CustomService do
+ include Spree::TestingSupport::BusHelpers
+
+ describe 'call' do
+ it 'fires custom event' do
+ stub_spree_bus
+ order = create(:order)
+
+ described_class.new.call(order)
+
+ expect(:custom_event).to have_been_published
+ end
+ end
+end
+```
+
+You can also assert the published payload using the `with` modifier.
+
+```ruby title="spec/services/my_store/custom_service_spec.rb"
+# ...
+expect(:custom_event).to have_been_published.with(
+ a_hash_including(order: order)
+)
+# ...
+```
+
+### Observing events
+
+One of the tricky parts of event-driven design is that it's sometimes challenging to inspect the
+flow of the program. Think of debugging, logging, and so on. Omnes comes with a lot of facilities
+for observability.
+
+Besides the published event, subscriptions can take a second argument to access the line of code
+that published the event and when that happened.
+
+```ruby
+Spree::Bus.subscribe(:order_finalized) do |_event, context|
+ puts context.time
+ puts context.caller_location
+end
+# 2022-01-01 00:00:00 UTC
+# /path/to/file/that/published/the/event:99:in `'
+```
+
+There's much more to that. Check [Omnes' README](https://github.com/nebulab/omnes) for details.
+
+### Upgrading from the legacy event system
+
+[Omnes](https://github.com/nebulab/omnes) is the default way to go for event-driven behavior since
+Solidus v3.2. However, before that, a custom event system based
+on [ActiveSupport::Notifications](https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html)
+was in place. You might need to update your code if you're upgrading from Soldius v3.1 or before.
+
+Once you run the [update generator](/upgrading-solidus/index.mdx#updating-preferences),
+you'll have an option `config.use_legacy_events` commented out
+in `config/initializers/new_solidus_defaults.rb`. Don't activate it until you've gone through all
+the following points. However, you're good to go if your application is not using events for
+anything (not subscribing to Solidus events or using custom ones). In that case, you can disable
+legacy events straight away and stop reading now.
+
+#### Subscriber modules
+
+* Switch from a subscriber module to an Omnes subscriber. Be sure that event names are given as
+a `Symbol`:
+
+```ruby title="app/subscribers/my_subscriber.rb"
+# Instead of
+module MySubscriber
+ event_action :do_something, event_name: :order_finalized
+ event_action :order_recalculated
+
+ def do_something(event)
+ # ...
+ end
+
+ def order_recalculated(event)
+ # ...
+ end
+end
+# do
+class MySubscriber
+ include Omnes::Subscriber
+
+ handle :order_finalized, with: :do_something
+ handle :order_recalculated, with: :order_recalculated
+
+ def do_something(event)
+ # ...
+ end
+
+ def order_recalculated(event)
+ # ...
+ end
+end
+```
+
+* Subscribe Omnes subscribers to `Spree::Bus` in an initializer:
+
+```ruby title="config/initializers/omnes.rb"
+Rails.application.config.to_prepare do
+ MySubscriber.new.subscribe_to(Spree::Bus)
+end
+```
+
+#### Block subscriptions
+
+* Rename references from `Spree::Event` to `Spree::Bus`.
+* Make sure that you subscribe to the event name as a `Symbol`.
+
+```ruby
+# Instead of
+Spree::Event.subscribe('order_finalized') {}
+# do
+Spree::Bus.subscribe(:order_finalized) {}
+```
+
+#### Regular expression subscriptions
+
+Regular expression subscriptions are not supported on Omnes by default. They're considered an
+anti-pattern, as you could unintentionally subscribe to new events added in the future if their
+names match your pattern (e.g., you subscribe to `/order/` and a new event `stock_backordered` is
+added).
+
+The chances are that if you're using regular expression subscriptions, your use case is subscribing
+to all events regardless of their nature (probably for logging purposes). In that case, you can lean
+on Omnes's `#subscribe_to_all` method (or `handle_all` for subscriber classes):
+
+```ruby
+# Instead of
+Spree::Event.subscribe /.*\.spree$/ do |event|
+ # do_something
+end
+# do
+Spree::Bus.subscribe_to_all do |event|
+ # do_something
+end
+```
+
+If you do want to use a regular expression for subscriptions, you can still use a custom Omnes
+matcher with `#subscribe_with_matcher` (or `handle_with_matcher` in a subscriber class):
+
+```ruby
+# Instead of
+Spree::Event.subscribe /order_/ do |event|
+ # do_something
+end
+# do
+ORDER_EVENTS_MATCHER = ->(event) { event.omnes_event_name.match?(/order_/) }
+Spree::Bus.subscribe_with_matcher(ORDER_EVENTS_MATCHER) do |event|
+ # do_something
+end
+```
+
+#### Publishing events
+
+* Rename `Spree::Event.fire` to `Spree::Bus.publish`.
+* Giving a block at publication time is no longer supported. It provided no value, plus its
+execution time (before vs. after subscriptions) was confusing:
+
+```ruby
+# Instead of
+Spree::Event.fire(:my_store_custom_event, order_id: order.id) do
+ do_something
+end
+# do
+do_something
+Spree::Bus.publish(:my_store_custom_event, order_id: order.id)
+```
+
+* Make sure that the published event name is a `Symbol`.
+* Register that event in an initializer within
+a [#to\_prepare](https://api.rubyonrails.org/classes/ActiveSupport/Reloader.html#method-c-to\_prepare)
+block:
+
+```ruby title="config/initializer/omnes.rb"
+Rails.application.config.to_prepare do
+ Spree::Bus.register(:my_store_custom_event)
+end
+```
+
+#### Updating extensions
+
+If you're maintaining an extension that needs to support both the legacy and the new event bus
+systems, you can leverage
+the [compatibility layer shipped with solidus\_support v0.9](https://github.com/solidusio/solidus\_support/blob/ba3c7d00f2da26f5bf5df54201697fa30c1ac60d/lib/solidus\_support/legacy\_event\_compat.rb)
+. Please, check it for details.
+
+:::success
+
+There're many more features supported by Omnes that were not possible with the legacy system, like
+async subscriptions, event instances, or the autodiscovery of event handlers from subscribers. This
+guide covered the bare minimum to update your store. Please, check
+its [README](https://github.com/nebulab/omnes) to extract all the potential that Omnes brings.
+
+:::
diff --git a/versioned_docs/version-4.2/getting-started/_category_.json b/versioned_docs/version-4.2/getting-started/_category_.json
new file mode 100644
index 0000000..6f99e46
--- /dev/null
+++ b/versioned_docs/version-4.2/getting-started/_category_.json
@@ -0,0 +1,6 @@
+{
+ "label": "Getting Started",
+ "position": 2,
+ "collapsible": true,
+ "collapsed": true
+}
diff --git a/versioned_docs/version-4.2/getting-started/deploying-your-store.mdx b/versioned_docs/version-4.2/getting-started/deploying-your-store.mdx
new file mode 100644
index 0000000..6e6f201
--- /dev/null
+++ b/versioned_docs/version-4.2/getting-started/deploying-your-store.mdx
@@ -0,0 +1,214 @@
+---
+sidebar_position: 5
+needs-diataxis-rewrite: true
+---
+
+# Deploying your store
+
+This guide will teach you how to deploy your Solidus store to different cloud infrastructure
+providers.
+
+## Choosing a cloud provider
+
+Deploying a Solidus application to production is no different from deploying any other Rails
+application. You can pick from a number of infrastructure providers. Our favorites are:
+
+* [Heroku](https://heroku.com/): Infrastructure-as-a-Service provider built on top of AWS.
+Incredibly easy to set up and work with, and arguably the most popular option for deploying Rails
+applications.
+* [AWS ECS](https://aws.amazon.com/ecs/): container orchestration service provided by AWS.
+Recommended if you need an advanced setup, or expect to scale aggressively very quickly after
+launch.
+* [DigitalOcean](https://digitalocean.com): developer cloud solutions provider. You can buy plain
+old VPS, or Kubernetes clusters.
+
+For further instructions, we recommend referring to the official documentation for these services.
+
+## External dependencies
+
+When deploying a Solidus store, there are also a few external dependencies that you need to provide
+in order for your store to work properly.
+
+### File storage
+
+When you run Solidus locally or on a single node, any files you upload \(product images, taxon icons
+etc.\) are stored on the filesystem. While this works great in development, it's not a viable option
+when deploying to cloud platforms, where clustering may cause files in one node not to be accessible
+by all other nodes. You may also find that files disappear when a node reboots because
+of [ephemeral filesystems](https://devcenter.heroku.com/articles/dynos#ephemeral-filesystem).
+
+When running your store in production, you will have to rely on a file storage service such
+as [Amazon S3](https://aws.amazon.com/s3/)
+or [Microsoft Azure Storage Service](https://azure.microsoft.com/en-us/services/storage/). Files
+will be uploaded to the storage service, which will also handle concerns such as high availability,
+security and distribution.
+
+#### Active Storage
+
+Solidus supports a multitude storage services out of the box through
+Rails' [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html) framework.
+
+To configure Active Storage, change `config/storage.yml` by uncommenting your preferred storage
+services and setting its credentials. In the following example, we store our files in an S3 bucket:
+
+```ruby title="config/storage.yml"
+amazon:
+ service: S3
+ access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
+ secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
+ region: <%= ENV['AWS_REGION'] %>
+ bucket: <%= ENV['AWS_BUCKET'] %>
+```
+
+Next, you'll need to configure Rails to use your new Active Storage service in production:
+
+```ruby title="config/environment/production.rb"
+Rails.application.configure do
+ # ...
+
+ config.active_storage.service = :amazon
+end
+```
+
+Finally, store your S3 credentials in the environment variables used in `config/storage.yml`.
+
+#### Paperclip
+
+:::caution
+
+Paperclip [has been deprecated](https://github.com/thoughtbot/paperclip#deprecated) in favor
+of [Active Storage](https://guides.rubyonrails.org/active_storage_overview.html). You should only
+use Paperclip with Rails 6.0 or earlier, where Active Storage does not support public URLs and
+cannot be used with Solidus applications.
+
+:::
+
+Solidus also supports the popular [Paperclip](https://github.com/thoughtbot/paperclip) gem. In order
+to configure Paperclip, just create an initializer like the following:
+
+```ruby title="config/initializers/paperclip.rb"
+if Rails.env.production?
+ Paperclip::Attachment.default_options.merge!(
+ storage: :s3,
+ bucket: ENV.fetch('S3_BUCKET'),
+ s3_host_name: ENV.fetch('S3_HOST_NAME'),
+ s3_credentials: {
+ access_key_id: ENV.fetch('S3_ACCESS_KEY_ID'),
+ secret_access_key: ENV.fetch('S3_SECRET_ACCESS_KEY'),
+ s3_region: ENV.fetch('S3_REGION'),
+ }
+ )
+end
+```
+
+Finally, put your S3 credentials in the environment variables used in the initializer.
+
+### Cache store
+
+Solidus
+employs [fragment caching](https://guides.rubyonrails.org/caching_with_rails.html#fragment-caching)
+and [low-level caching](https://guides.rubyonrails.org/caching_with_rails.html#low-level-caching)
+extensively throughout the storefront and API views. By default, Rails uses an in-memory cache
+adapter in production. This essentially makes all caching useless if you are running Solidus across
+multiple nodes, since the cache is not shared across instances.
+
+Therefore, instead of the default adapter you should instead rely on an actual caching system.
+Popular options in the Rails ecosystem are [memcached](https://memcached.org/)
+and [Redis](https://redis.io/).
+
+The procedure for configuring your cache store with Solidus is no different from doing it in a
+regular Rails application. Refer to
+the [Rails caching guide](https://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-memcachestore)
+for more details and recommendations on how to properly set up your caching server.
+
+### Async operations
+
+Solidus schedules certain time-intensive operations in the background. This provides faster feedback
+to the user and avoids blocking the Web process for too long. The most common examples are
+transactional emails. When an email needs to be delivered to the user, Solidus will enqueue the
+operation rather than executing it immediately. This operation will then be run in the background
+by [ActiveJob](https://guides.rubyonrails.org/active_job_basics.html).
+
+The default ActiveJob adapter
+is [Async](https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html), which
+uses an in-process thread pool to schedule jobs. While Async is a good choice for local development
+and testing, it is a poor option for production deployments, as any pending jobs are dropped when
+the process restarts
+\([Heroku restarts dynos automatically every 24 hours](https://devcenter.heroku.com/articles/dynos#automatic-dyno-restarts)
+, for instance\).
+
+Instead, you should use a production-grade queue such
+as [Sidekiq](https://github.com/mperham/sidekiq), which uses Redis for storing and retrieving your
+application's jobs under the hood. Using Sidekiq with ActiveJob is simple.
+
+First of all, install Sidekiq by adding it to your `Gemfile`:
+
+```bash
+bundle add 'sidekiq'
+```
+
+Next, tell ActiveJob to use Sidekiq for queueing and running jobs:
+
+```ruby title="config/application.rb"
+module AmazingStore
+ class Application < Rails::Application
+ # ...
+ config.active_job.queue_adapter = :sidekiq
+ end
+end
+```
+
+That's it! Solidus will now use Sidekiq and Redis for all asynchronous processing. You may refer to
+the [Sidekiq documentation](https://github.com/mperham/sidekiq)
+and [ActiveJob documentation](https://guides.rubyonrails.org/active_job_basics.html) for advanced
+configuration.
+
+### Content delivery network
+
+It is strongly recommended to serve static assets via
+a [Content Delivery Network \(CDN\)](https://it.wikipedia.org/wiki/Content_Delivery_Network) rather
+than your own application. CDNs are a relatively simple and efficient way to instantaneously boost
+the performance of your application, and are widely used in Web development.
+
+As with many other tasks, configuring a CDN for Solidus is the same as configuring it for a regular
+Rails application, so you can refer to the Rails guides
+on [configuring a CDN](https://guides.rubyonrails.org/asset_pipeline.html#cdns).
+
+There are many reliable CDNs, with the most popular
+being [Amazon CloudFront](https://aws.amazon.com/cloudfront/).
+
+### Email delivery
+
+In order to send emails, Solidus needs a valid SMTP server. While you could use your domain
+registrar's mail server, it is usually recommended to use a more robust and feature-complete
+solution that will also provide useful insights and business metrics like deliverability, open, and
+click-through rates.
+
+[SendGrid](https://sendgrid.com/), [Mailgun](https://www.mailgun.com/)
+and [Mailchimp](https://mailchimp.com/features/transactional-email/) are all very good,
+battle-tested solutions for delivering transactional emails to your customers, but you are free to
+use any other service you wish.
+
+Most of these services provide a regular SMTP server you can use to deliver emails, which you can
+configure in Rails. Here's an example configuration for SendGrid:
+
+```ruby title="config/application.rb"
+module AmazingStore
+ class Application < Rails::Application
+ # ...
+ config.action_mailer.smtp_settings = {
+ user_name: ENV.fetch('SENDGRID_USERNAME'),
+ password: ENV.fetch('SENDGRID_PASSWORD'),
+ domain: ENV.fetch('SENDGRID_DOMAIN'),
+ address: 'smtp.sendgrid.net',
+ port: 465,
+ authentication: :plain,
+ enable_starttls_auto: true,
+ }
+ end
+end
+```
+
+You should then configure the `SENDGRID_USERNAME`, `SENDGRID_PASSWORD` and `SENDGRID_DOMAIN`
+environment variables with your SendGrid credentials.
+
diff --git a/versioned_docs/version-4.2/getting-started/installing-solidus.mdx b/versioned_docs/version-4.2/getting-started/installing-solidus.mdx
new file mode 100644
index 0000000..549db69
--- /dev/null
+++ b/versioned_docs/version-4.2/getting-started/installing-solidus.mdx
@@ -0,0 +1,169 @@
+---
+sidebar_position: 1
+needs-diataxis-rewrite: true
+---
+
+# Installing Solidus
+
+This guide will teach what each you how to install Solidus and configure Solidus in a new or
+existing Rails application.
+
+## System requirements
+
+Solidus requires the following software on the host to run properly:
+
+* [Ruby](https://www.ruby-lang.org) interpreter: Solidus always supports the oldest maintained Ruby
+branch.
+* [Rails](https://www.rubyonrails.org): Solidus always supports the oldest maintained Rails version.
+* Relational database: the core and the extensions are always tested
+with [MySQL](https://www.mysql.com) and [PostgreSQL](https://www.postgresql.org), but other
+relational databases should work as well.
+* [libvips](https://github.com/libvips/libvips) or
+[ImageMagick](http://imagemagick.org/script/download.php): this is needed for
+manipulating product images and other assets. You need one or the other,
+depending on the [configured variant
+processor](https://guides.rubyonrails.org/configuring.html#config-active-storage-variant-processor).
+
+## Library architecture
+
+Solidus has been designed as an ecosystem of independent libraries (_gems_, in the Ruby world) that
+work well in isolation, but collaborate to give you an amazing eCommerce experience when used
+together. A standard Solidus installation is composed of the following gems:
+
+* [solidus\_core](https://github.com/solidusio/solidus/tree/master/core): provides the core data
+models and eCommerce business logic. This is the bare minimum for a Solidus install.
+* [solidus\_backend](https://github.com/solidusio/solidus/tree/master/backend): provides the
+standard Solidus backend, a powerful administrative UI you can use to manage your Solidus store.
+* [solidus\_api](https://github.com/solidusio/solidus/tree/master/api): provides the Solidus REST
+API. The API is required by the backend, but you may also use it for your own purposes (e.g. for
+JS interactions in the storefront).
+
+Besides, Solidus aims to be agnostic of the storefront. We provide
+[solidus\_starter\_frontend](https://github.com/solidusio/solidus_starter_frontend)
+as a bootstrap solution from which a tailored store can be built. Otherwise,
+you can create your storefront from scratch using whatever technology you
+prefer.
+
+For maximum flexibility, you can decide you just want to install specific gems and build the rest of
+the functionality yourself. Or, if you want the full-fledged Solidus experience, you can install
+the [solidus](https://github.com/solidusio/solidus) gem, which ties them all together and will give
+you a complete store. This is the approach we'll be following in this guide.
+
+## Installation steps
+
+### In a new app
+
+If you don't have an existing Ruby on Rails application yet, simply create one with:
+
+```bash
+$ rails new -T amazing_store
+```
+
+:::info
+
+This command will generate a new Rails application in the `amazing_store` directory. This application
+will be using Sqlite3 as the database adapter by default and will skip generating default test files
+(via the `-T` option). If you want to see all the installation options offered by Rails, run `rails new --help`.
+
+:::
+
+Once you have generated your new Rails application, you can proceed as if you were installing
+Solidus [in an existing app](#in-an-existing-app).
+
+### In an existing app
+
+If you have an existing Ruby on Rails application, installing Solidus is fairly simple. In your CLI:
+
+```bash
+$ bundle add solidus
+$ bin/rails generate solidus:install
+```
+
+:::info
+
+If you want to see all the installation options offered by Solidus, run `bin/rails generate solidus:install --help`.
+
+:::
+
+The installer will prompt you on a few questions before completing the
+installation:
+
+- Which storefront you want to install. We
+provide a ready-to-use template called
+[solidus\_starter\_frontend](https://github.com/solidusio/solidus_starter_frontend)
+to quickly get up and running. There's also the option to choose
+no storefront and build your own.
+- What payment service you would like to install with Solidus. Currently,
+it comes packaged with
+[Paypal](https://github.com/solidusio/solidus_paypal_commerce_platform/),
+[Stripe](https://github.com/solidusio/solidus_stripe/) and
+[Braintree](https://github.com/solidusio/solidus_braintree) but you can also
+choose to start without a payment service via the "none" option and add one later.
+If you choose one, remember to check their READMEs for additional configuration steps.
+
+Once the installation has completed, you can now start your Rails server:
+
+```bash
+$ bin/rails server
+```
+
+You should now be able to access your storefront at [http://localhost:3000](http://localhost:3000).
+You can also access Solidus' admin UI at [http://localhost:3000/admin](http://localhost:3000/admin)
+with the default credentials (email: `admin@example.com`, password: `test123`).
+
+## First-time configuration
+
+Once Solidus has been installed, you can get a feel of its configuration options by looking at the
+generated initializer in `config/initializers/spree.rb`.
+
+The very first line on it will be very similar to this one:
+
+```ruby
+Spree.load_defaults '4.2'
+```
+
+Some of the default values for Solidus preferences depend on the Solidus version. That line makes
+sure you use the defaults for the version you have just installed.
+
+:::info
+
+You'll have to bump the loaded version when
+you [upgrade to a new Solidus version](/upgrading-solidus/index.mdx#updating-preferences).
+
+:::
+
+Right after that, you'll see a block for the preferences that apply to the core component:
+
+```ruby
+Spree.config do |config|
+ config.currency = "USD"
+ # ...
+end
+```
+
+Take a look at
+the [`Spree::AppConfiguration`](https://github.com/solidusio/solidus/blob/master/core/lib/spree/app\_configuration.rb)
+class for all the available settings. You can adjust some of them now, but you will probably want to
+revisit them as you develop your store and customize Solidus to fit your needs.
+
+Finally, the initializer contains a block for all the other components you have installed. For
+instance:
+
+```ruby
+Spree::Api::Config.configure do |config|
+ config.requires_authentication = true
+end
+```
+
+Just like for the core,
+see [`Spree::ApiConfiguration`](https://github.com/solidusio/solidus/blob/master/api/lib/spree/api\_configuration.rb)
+and [`Spree::BackendConfiguration`](https://github.com/solidusio/solidus/blob/master/backend/lib/spree/backend\_configuration.rb)
+for the complete list of settings.
+
+:::info
+
+You can inspect the current values for the settings running `Spree::Config` for core
+and `Spree::Backend::Config` or `Spree::Api::Config` for the backend and API components,
+respectively, in the console.
+
+:::
diff --git a/versioned_docs/version-4.2/getting-started/testing-solidus.mdx b/versioned_docs/version-4.2/getting-started/testing-solidus.mdx
new file mode 100644
index 0000000..0ad7be3
--- /dev/null
+++ b/versioned_docs/version-4.2/getting-started/testing-solidus.mdx
@@ -0,0 +1,648 @@
+---
+sidebar_position: 4
+needs-diataxis-rewrite: true
+---
+
+# Testing Solidus
+
+This guide will teach you how to test different aspects of your Solidus application by leveraging
+the best practices and tooling provided by Solidus.
+
+## Introduction to testing
+
+For most of the examples in this guide, we have also provided automated tests. While this is a bit
+unusual in product documentation, we wanted to give you something you can use as inspiration when
+testing your own customizations. Feel free to adapt our tests to fit your own style!
+
+We can't emphasize enough the importance of writing tests for your Solidus app: Solidus is a large,
+complex framework, and you are bound to miss gotchas and edge cases when customizing it, no matter
+how much QA you do. **Skipping on automated tests means asking for trouble,** especially in
+eCommerce, where downtime translates directly into financial loss.
+
+While writing tests may seem like a useless distraction in the short term, it will make you more
+productive in the long term, by allowing you to change code faster, with more confidence and with
+less manual work. Having good test coverage will also help you tremendously when upgrading Solidus.
+
+## Our testing philosophy
+
+In this paragraph, you'll find some opinionated advice about how to test your Solidus application.
+This advice is the result of years spent evolving and maintaining large-scale eCommerce apps, and
+will almost certainly be a good starting point for anyone starting to work with Solidus.
+
+### Unit vs. system tests
+
+In general, there's no hard rule on whether something should be tested with a unit test or a system
+test. Each developer and development team has its own style and philosophy, and you should find
+your own and adapt it over time, as your application grows and your needs evolve.
+
+Our recommendation is to have a balanced test diet: write unit tests for all possible scenarios
+\(happy paths, failure paths, edge cases, etc.\), and write system tests for at least the happy path
+of all customer- or admin-facing features. This will help you ensure your application is working
+well in the real world, and not just when testing each component in isolation.
+
+:::info
+
+We're using "unit tests" in this guide in a loose way to refer to a test that primarily tests a
+single module/class rather than its interactions with the rest of the codebase.
+
+:::
+
+In certain cases, you may also want to write lower-level integration tests which don't exercise the
+UI, but call multiple components without attempting to isolate them. A good use case would be
+testing that a given set of promo rules and actions works as expected when the promotion is applied
+to a real order.
+
+### Test coverage
+
+**~80% or higher** is a good test coverage to aim for, but take it with a grain of salt.
+
+In general, coverage metrics are not an optimal measure of test quality, as they don't tell you
+anything about where the code is being exercised and how its outputs and side effects are being
+measured: you can have very high test coverage and still have tons of blind spots in your
+application, because you're calling your code but not verifying its behavior.
+
+Rather than obsessing over test coverage, create guidelines around how to write meaningful,
+effective tests. High test coverage will come as a natural byproduct.
+
+
+## Testing Solidus with Solidus Starter Frontend installed
+
+In case you installed Solidus selecting the Starter Frontend as storefront, you will notice that
+you have some specs already in place. You can find them in the `spec` folder of your application.
+
+Most of the steps covered in the rest of the guide are already done for you, but it's still a good
+idea to read through the guide to understand how your test suite is configured and the reasons behind
+the choices we made.
+
+Please note that the main entry point for the configuration provided by the Starter Frontend is placed
+at `spec/solidus_starter_frontend_spec_helper.rb` instead of `spec/spec_helper.rb` and `spec/rails_helper.rb`
+as a regular Rails application would do. That's because we wanted to still let the freedom to build your
+own test suite, independent from the one we provide.
+
+## Configuring your test environment from scratch
+
+If you didn't use the Starter Frontend and want to start from scratch, here's the bare minimum you'll need
+to get started with testing your Solidus app:
+
+```ruby title="Gemfile"
+group :development, :test do
+ gem 'rspec-rails'
+ gem 'factory_bot_rails', '~> 4.8'
+end
+
+group :test do
+ gem 'capybara', '>= 3.26'
+end
+```
+
+Let's go over the reason we're recommending these tools and how to set them up.
+
+### Test framework: RSpec
+
+RSpec is the preferred testing framework in the Solidus world. While it's certainly possible to test
+a Solidus application with other frameworks \(e.g., MiniTest\), all of our test helpers have been
+written to support RSpec, so we strongly recommend using it.
+
+To properly configure RSpec, run the following command after installing the `rspec-rails` gem:
+
+```bash
+$ rails g rspec:install
+```
+
+After installing RSpec, take a look at `spec/spec_helper.rb` and `spec/rails_helper.rb`, as they
+contain some default configurations which you may want to uncomment. At the very least, make sure
+you uncomment these lines:
+
+```ruby title="spec/rails\_helper.rb"
+Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
+```
+
+It will automatically load each file in `spec/support` before starting your test suite. This allows
+you to import test helpers and configurations from other gems without polluting your main RSpec
+configuration.
+
+:::info
+
+Throughout the rest of this guide, we'll assume you are loading files in `spec/support`.
+
+:::
+
+For more information on RSpec and its usage, please see
+the [official documentation](https://relishapp.com/rspec/rspec-rails/docs).
+
+### Factories: FactoryBot
+
+Very often, you'll want to generate an instance of a user, order, product or any other type of
+Solidus model in a test. Instead of forcing you to generate the data manually every time, we provide
+a set of convenience factories you can import in your app:
+
+```ruby title="config/application.rb"
+module AmazingStore
+ class Application < Rails::Application
+ # ...
+
+ if defined?(FactoryBotRails)
+ initializer after: "factory_bot.set_factory_paths" do
+ require 'spree/testing_support/factory_bot'
+
+ # The paths for Solidus' core factories.
+ solidus_paths = Spree::TestingSupport::FactoryBot.definition_file_paths
+
+ # Optional: Any factories you want to require from extensions.
+ extension_paths = [
+ # MySolidusExtension::Engine.root.join("lib/my_solidus_extension/testing_support/factories"),
+ # or individually:
+ # MySolidusExtension::Engine.root.join("lib/my_solidus_extension/testing_support/factories/resource.rb"),
+ ]
+
+ # Your application's own factories.
+ app_paths = [
+ Rails.root.join('spec/factories'),
+ ]
+
+ FactoryBot.definition_file_paths = solidus_paths + extension_paths + app_paths
+ end
+ end
+ end
+end
+```
+
+Finally, you'll want to import the FactoryBot DSL methods. This allows you to call `create`, `build`
+, `build_stubbed` and `attributes_for` in your tests without prefixing them with `FactoryBot`:
+
+```ruby title="spec/support/factory\_bot.rb"
+RSpec.configure do |config|
+ config.include FactoryBot::Syntax::Methods
+end
+```
+
+For more information on FactoryBot and its usage, please see
+the [official documentation](https://github.com/thoughtbot/factory_bot).
+
+### System tests: Capybara
+
+[Capybara](https://github.com/teamcapybara/capybara) is an acceptance test framework that simulates
+how a real user would interact with your app. Rails uses Capybara to
+implement [system tests](https://guides.rubyonrails.org/testing.html#system-testing), which are
+tests where you interact with the UI of your application rather than directly calling individual
+modules.
+
+When configured properly, system tests can also execute JavaScript code, just like a real browser
+would do. In order for JavaScript to be executed, you'll need to tell Capybara to switch to a
+JavaScript-capable browser for JS tests:
+
+```ruby title="spec/support/capybara.rb"
+RSpec.configure do |config|
+ config.before(:each, type: :system) do |example|
+ if example.metadata[:js]
+ driven_by :selenium_chrome_headless
+ else
+ driven_by :rack_test
+ end
+ end
+end
+```
+
+The configuration above tells Capybara to use the default `Rack::Test` browser for non-JS tests, and
+Chrome for JS tests. This enables you to do the following:
+
+```ruby title="spec/system/product\_page\_spec.rb"
+RSpec.describe "Product page", type: :system do
+ it "shows the product's description" do
+ visit "/products/solidus-shirt"
+
+ expect(page).to have_text("Solidus-branded T-shirt")
+ end
+
+ it "allows me to add a product to my cart", :js do
+ visit "/products/solidus-shirt"
+
+ click_button "Add to Cart"
+
+ expect(page).to have_text("Product was added to the cart!")
+ end
+end
+```
+
+In the example above, the first test, which doesn't require JavaScript, will be run
+with `Rack::Test`, which is faster. The second test will be run through a headless Chrome instance
+with JS capabilities.
+
+For more information on system tests and Capybara, you can refer to
+the [RSpec guides](https://relishapp.com/rspec/rspec-rails/v/5-0/docs/system-specs/system-spec)
+and [Capybara's official documentation](https://github.com/teamcapybara/capybara).
+
+## Using the built-in helpers
+
+Solidus comes with a set of useful helpers you can use in your tests. You can find all of them under
+the [`spree/testing_support`](https://github.com/solidusio/solidus/tree/master/core/lib/spree/testing_support)
+path. We suggest including the ones you need in your RSpec configuration to save some time when
+writing tests.
+
+:::info
+
+You will notice Solidus has more helpers than we are documenting here. This is because some of the
+helpers in `spree/testing_support` are mostly meant for internal use \(i.e., for testing the Solidus
+codebase itself\), and wouldn't be very useful in another test suite.
+
+You may still use the undocumented helpers if you find them useful, but keep in mind they may change
+over time.
+
+:::
+
+### Authorization helpers
+
+These helpers allow you to bypass
+Solidus' [authorization system](../advanced-solidus/permission-management.mdx). This makes testing
+easier, since you don't have to stub the current user or the current ability.
+
+In order to use the helpers, first include them in your RSpec configuration:
+
+```ruby title="spec/support/solidus.rb"
+# ...
+
+require 'spree/testing_support/authorization_helpers'
+```
+
+Once you've included them, you can use them both in controller specs and system specs.
+
+:::caution
+
+Stubbing or customizing the authorization system during testing can lead to unexpected bugs in
+production. Instead of stubbing the authorization system, just use Devise's helpers to sign in as a
+user with the right permissions.
+
+:::
+
+#### Stubbing the authorization system
+
+The `stub_authorization!` method bypasses the authentication and authorization systems completely by
+stubbing the current user and allowing you to perform any action on any resource:
+
+```ruby
+RSpec.describe 'The product admin' do
+ stub_authorization!
+
+ it 'allows me to view the products' do
+ product = create(:product)
+
+ visit spree.admin_path
+ click_link 'Products'
+
+ expect(page).to have_content(product.name)
+ end
+end
+```
+
+#### Defining a custom authorization block
+
+The `custom_authorization!` method, on the other hand, allows you to define a custom authorization
+block. You'll still need to authenticate the current user, if you use it:
+
+```ruby
+RSpec.describe 'The product admin' do
+ custom_authorization! do
+ can :read, Spree::Product
+ end
+
+ it 'allows me to view the products' do
+ sign_in create(:user)
+ product = create(:product)
+
+ visit spree.admin_path
+ click_link 'Products'
+
+ expect(page).to have_content(product.name)
+ end
+
+ it 'does not allow me to edit products' do
+ sign_in create(:user)
+ product = create(:product)
+
+ visit spree.edit_admin_product_path(product)
+
+ expect(page).to have_content('Access denied')
+ end
+end
+```
+
+\(Note that the [`sign_in` helper](https://github.com/heartcombo/devise#test-helpers) is provided by
+Devise, not Solidus.\)
+
+### Capybara helpers
+
+These helpers make it easier to interact with the UI in system specs, especially when testing the
+Solidus backend. They can come in really useful if you've customized the backend and want to test
+some piece of functionality. They also suppress annoying Puma logs in the test output.
+
+As always, the first step is to include them in your app:
+
+```ruby title="spec/support/solidus.rb"
+# ...
+
+require 'spree/testing_support/capybara_ext'
+```
+
+#### Interacting with icons
+
+You can use the `click_icon` helper to find and click on a specific FontAwesome icon:
+
+```ruby
+RSpec.describe 'The option types admin' do
+ it 'allows me to delete the option types' do
+ sign_in create(:admin_user)
+ option_type = create(:option_type)
+
+ visit spree.admin_path
+ click_link 'Products'
+ click_link 'Option Types'
+ click_icon 'trash'
+
+ expect(page).to have_content('has been successfully removed')
+ end
+end
+```
+
+#### Interacting with tables
+
+The `within_row` helper can be used to scope the Capybara context to a specific row within an index
+table in the backend:
+
+```ruby
+RSpec.describe 'The product admin' do
+ it 'allows me to edit a product' do
+ sign_in create(:admin_user)
+ product1 = create(:product)
+ product2 = create(:product)
+
+ visit spree.admin_products_path
+ # Clicks the edit icon for the first product
+ within_row(1) { click_icon 'edit' }
+
+ # ...
+ end
+end
+```
+
+The `column_text` helper can be used to retrieve the text from a specific column in a table row:
+
+```ruby
+RSpec.describe 'The product admin' do
+ it 'displays the product SKU' do
+ sign_in create(:admin_user)
+ product1 = create(:product)
+ product2 = create(:product)
+
+ visit spree.admin_products_path
+
+ within_row(1) do
+ expect(column_text(1)).to eq(product1.sku)
+ end
+ end
+end
+```
+
+#### Interacting with select2 inputs
+
+The Solidus backend uses the [Select2](https://select2.org/) jQuery plugin for nicer-looking,
+Ajax-enabled select boxes. Because Select2 inputs are not regular inputs, some additional code is
+required when interacting with them.
+
+You can use the `select2_search` helper to search and select a specific option from a Select2 input
+\(this would be equivalent to typing the option's text in the search field, then selecting the
+result\):
+
+```ruby
+RSpec.describe 'The orders admin' do
+ it 'allows me to filter by variant' do
+ sign_in create(:admin_user)
+ product = create(:product)
+ order = create(:order) do |o|
+ create(:line_item, order: o, variant: product.master)
+ end
+
+ visit spree.admin_orders_path
+ select2_search product.sku, from: 'Variant'
+ click_button 'Filter results'
+
+ expect(page).to have_content(order.number)
+ end
+end
+```
+
+#### Testing meta tags
+
+You can use the `have_meta` helper to expect the current page to have a specific meta tag:
+
+```ruby
+RSpec.describe 'The product page' do
+ it 'uses the product description in the meta description' do
+ product = create(:product)
+
+ visit spree.product_path(product)
+
+ expect(page).to have_meta(:description, product.description)
+ end
+end
+```
+
+### Order helpers
+
+Sometimes, you need to generate an order in a given state. Solidus ships with a set
+of [order factories](https://github.com/solidusio/solidus/blob/v4.1/core/lib/spree/testing_support/factories/order_factory.rb)
+you can use to generate different types of orders, and these should be your first choice.
+
+However, you will sometimes need a bit more granularity than what the factories provide \(e.g., when
+testing the checkout flow\). A common use case is wanting to generate an order in a certain state,
+only with the information the user would have provided up until that state \(e.g., generate an order
+in the delivery state, only with address information\). That's exactly what the `OrderWalkthrough`
+helper is for.
+
+:::caution
+
+The `OrderWaltkhrough` helper is extremely opinionated: it assumes you haven't modified the order
+state machine in significant ways, and that your checkout flow resembles the standard, multi-page
+checkout flow from the starter frontend.
+
+If you have altered the order state machine or checkout flow significantly, you may want to use the
+order factories instead, or write your own helper, in order for your tests to better resemble
+real-world usage.
+
+:::
+
+First of all, include the helper in your test suite:
+
+```ruby title="spec/support/solidus.rb"
+# ...
+
+require 'spree/testing_support/order_walkthrough'
+```
+
+You can now use the helper whenever you want:
+
+```ruby
+RSpec.describe 'The checkout flow' do
+ it 'renders the delivery step' do
+ user = create(:user)
+ sign_in user
+ # Generate an order in the `delivery` state
+ order = Spree::TestingSupport::OrderWalkthrough.up_to(:address)
+ order.update!(user: user)
+
+ visit spree.checkout_path
+
+ expect(page).to have_current_path('/checkout/delivery')
+ end
+end
+```
+
+The `OrderWalkthrough.up_to` call will create a new order and it will simulate what a user would do
+if they went to the checkout flow and only completed the `address` state. The order will have line
+items and an address on it, and it will be in the `delivery` state, ready for shipping method
+selection.
+
+### Preference helpers
+
+Sometimes, it can be useful to stub the Solidus configuration, to test how your store would behave
+with certain configuration options. This is usually the case for payment methods and other resources
+which can be configured by store operators without developer intervention: when that's the case, you
+want to make sure operators can't take any action that could cause a store malfunction, which
+requires testing under different configurations.
+
+If you need to stub the configuration, first of all require the relevant helper:
+
+```ruby title="spec/support/solidus.rb"
+# ...
+
+require 'spree/testing_support/preferences'
+```
+
+You can now use the `stub_spree_preferences` helper anywhere in your code. The helper accepts a hash
+of preferences, in which case the preferences will be stubbed on the global configuration:
+
+```ruby
+RSpec.describe 'The product page' do
+ it 'renders the price in EUR' do
+ # Stub the global `currency` setting of the store
+ stub_spree_preferences(currency: 'EUR')
+ product = create(:product)
+
+ visit spree.product_path(product)
+
+ expect(page).to have_content('β¬100,00')
+ end
+
+ it 'renders the price in USD' do
+ # Stub the global `currency` setting of the store
+ stub_spree_preferences(currency: 'USD')
+ product = create(:product)
+
+ visit spree.product_path(product)
+
+ expect(page).to have_content('$100.00')
+ end
+end
+```
+
+The helper can also accept a configuration class and a hash of preferences, in which case the
+preferences will be stubbed on the provided configuration class:
+
+```ruby
+RSpec.describe 'The backend' do
+ it 'is available in English' do
+ # Stub the `locale` setting of the backend
+ stub_spree_preferences(Spree::Backend::Config, locale: 'en')
+
+ visit spree.admin_path
+
+ expect(page).to have_content('Email')
+ end
+
+ it 'is available in French' do
+ # Stub the `locale` setting of the backend
+ stub_spree_preferences(Spree::Backend::Config, locale: 'fr')
+
+ visit spree.admin_path
+
+ expect(page).to have_content('Courriel')
+ end
+end
+```
+
+### URL helpers
+
+By default, your tests will not have access to the Solidus routes, because they're part of a Rails
+engine and not of your main application. You could access them
+with `Spree::Core::Engine.routes.url_helpers`, but you can include the URL helpers if you want to
+save a few characters:
+
+```ruby title="spec/support/solidus.rb"
+# ...
+
+require 'spree/testing_support/url_helpers'
+
+RSpec.configure do |config|
+ config.include Spree::TestingSupport::UrlHelpers
+end
+```
+
+You can now access the helpers via the `spree.` shortcut:
+
+```ruby
+RSpec.describe 'The product page' do
+ it 'is accessible' do
+ product = create(:product)
+
+ visit spree.product_path(product)
+
+ expect(page).to have_content(product.name)
+ end
+end
+```
+
+## Testing your Solidus app
+
+Since Solidus applications are just regular Rails applications, there's nothing special you need to
+do to test them: just write tests as you'd usually do and you'll be fine! With that said, there are
+some aspects to keep in mind when testing certain parts of your app, especially if you want to have
+an easy time upgrading Solidus.
+
+### Testing service objects
+
+If you have implemented a service object to replace one of Solidus' default implementations, make
+sure to test it just as you would do with any other service object.
+
+If you're inheriting from Solidus' default implementation, you should also test any behavior that's
+inherited from the original class, to make sure you haven't altered functionality in undesired ways.
+This will usually not be needed, because the customizable service objects in Solidus have small,
+well-defined interfaces, but keep it in mind!
+
+### Testing overrides
+
+Testing an override is similar to testing a service objects that inherits from the original
+implementation: for full test coverage, you'll have to test both your customization and any original
+functionality that comes from the default implementation. This will ensure you haven't broken the
+original functionality in any way, and it will make it easier to upgrade Solidus.
+
+This kind of gotcha is why you should
+prefer [customizing Solidus](../customization/customizing-the-core.mdx) via the extension hooks or
+the event bus instead of relying on overrides.
+
+### Testing the storefront
+
+There's nothing special you need to do here: when testing your storefront, do it with system specs,
+as you would do for any regular Rails app. Focus on user-facing functionality and integration
+testing \(i.e., don't write controller tests unless you have a very good reason to\).
+
+### Testing the backend
+
+Just like for the storefront, you should use system specs for testing your backend customizations.
+You may also want to leverage some of the [built-in helpers](testing-solidus.mdx#capybara-helpers)
+Solidus provides for testing the backend UI.
+
+### Testing event subscribers
+
+Event-driven behavior is an awesome way to decouple orthogonal logic, but it
+can be tricky to test. Take a look at the section about [how to test event
+subscribers](../customization/subscribing-to-events.mdx#testing-events) to see
+how to do it in Solidus.
diff --git a/versioned_docs/version-4.2/getting-started/using-extensions.mdx b/versioned_docs/version-4.2/getting-started/using-extensions.mdx
new file mode 100644
index 0000000..d384379
--- /dev/null
+++ b/versioned_docs/version-4.2/getting-started/using-extensions.mdx
@@ -0,0 +1,160 @@
+---
+sidebar_position: 3
+needs-diataxis-rewrite: true
+---
+
+# Using extensions
+
+This guide will teach you when, why and how to use Solidus extensions, and provide you with some
+best practices for doing so.
+
+## When to use extensions
+
+When you first install Solidus, you will notice that the platform is complete but quite lean. There
+aren't any toggles to enable shiny additional features. You get what you need to start an online
+store, and nothing more. This is by design. We know that each brand's business domain and USP are
+unique, and that they require a unique implementation approach. With that said, we also know that it
+isn't always smart to reinvent the wheel, especially when you're dealing with a common problem that'
+s already been solved.
+
+The easiest and quickest way to augment the functionality of your store is through _extensions_.
+These are gems or full-fledged Rails engines that provide additional functionality for Solidus by
+extending and overriding parts of the core. The Solidus ecosystem has a lot of extensions for many
+disparate tasks. There are extensions that add support for new payment providers or 3PL
+integrations, extensions that enhance your store with social features and many, many more to choose
+from.
+
+In general, if there's already an extension that solves your problem, you should evaluate it before
+attempting to roll your own solution. Reusing the work of the community will not only save you the
+effort of a custom implementation, but will also make the community stronger.
+
+With that said, you're strongly encouraged to customize Solidus on your own if none of the available
+solutions work for you, and this is exactly where the platform shines. You may then decide whether
+to keep your implementation private or, if you think the world could benefit from your work, to give
+back to the community and open-source it.
+
+:::info
+
+There's also a third option: if an extension already exists and takes you 90% of the way to solving
+your problem, consider submitting a patch to improve it and/or make it more flexible. This way, you
+benefit from the community's efforts while also ending up with a solution that fits your own use
+case perfectly.
+
+:::
+
+## Extension governance
+
+The Solidus community has a strict governance model around extension management and maintenance.
+Extensions can be split into three big groups:
+
+* **Official extensions:** these extensions are hosted in
+the [solidusio](https://github.com/solidusio) GitHub organization. They provide critical pieces of
+functionality that need to be working at all times \(e.g. payment providers, sales tax calculation
+and a few other categories\). They are maintained by the core team and any contribution is
+reviewed by the core team for correctness and completeness.
+* **Community extensions:** these extensions are hosted in
+the [solidusio-contrib](https://github.com/solidusio-contrib) GitHub organization. They provide
+common pieces of functionality that, although useful, are usually not mission-critical. They are
+maintained by the entire Solidus community, with each extension usually having its own
+maintainer\(s\). The core team works on tools and automations that make their maintenance easier,
+but doesn't commit to spending their time actively working on these extensions.
+* **Third-party extensions:** these are extensions that live "in the wild", and are not hosted in
+any of Solidus' GitHub organizations. They may be written by organizations or individuals that
+were interested in Solidus and decided to open-source their efforts. As you may imagine, the
+Solidus community has no control over the quality of these extensions, so you're encouraged to
+review them on your own before installing them in your store.
+
+:::info
+
+Extensions may be "promoted" from one group to the other by popular demand \(e.g. a third-party
+extension becoming a community extension because it's widely used, or a community extension becoming
+an official extension because it's become critical for multiple stores\). If you want to propose a
+promotion, ping someone from the core team in Slack.
+
+:::
+
+## Finding the right extension
+
+You can find many useful extensions on the [Extensions](https://solidus.io/extensions) page of the
+Solidus website, along with their CI status and supported Solidus versions. You can also filter by
+extension type \(official, community or third-party\), which is useful if you want your extensions
+to come with certain guarantees.
+
+If you haven't found what you're looking for, you should still search on Google, GitHub
+and [The Ruby Toolbox](https://www.ruby-toolbox.com/): new extensions are born every day and the one
+you need may be out there!
+
+## Installing an extension
+
+:::caution
+
+As a general rule, you should ALWAYS review any code that goes into your application. While Solidus
+has strict policies around code reviews, you should go the extra mile and ensure any extensions
+you're installing are safe and production-ready.
+
+:::
+
+The process for installing a Solidus extension is usually pretty simple and similar to the
+installation of the platform itself \(after all, extensions are also Rails engines\). The first step
+is always adding the extension to your `Gemfile`:
+
+```ruby title="Gemfile"
+gem 'solidus'
+gem 'solidus_auth_devise'
+# ...
+gem 'solidus_amazing_extension'
+```
+
+Note that some extensions may have an outdated release on RubyGems or may not have been released
+yet, in which case you'll want to pull the extensions directly from GitHub:
+
+```ruby
+gem 'solidus'
+gem 'solidus_auth_devise'
+# ...
+gem 'solidus_amazing_extension', github: 'solidusio/solidus_amazing_extension'
+```
+
+Once you have added the gem, install your bundle:
+
+```bash
+$ bundle install
+```
+
+Once the extension has been installed, simply follow the instructions in the extension's
+documentation. You may have to run an installation generator and run database migrations if the
+extension alters the database's schema.
+
+:::info
+
+The order gems appear in your `Gemfile` is important in the case of Solidus extensions. Make sure
+extensions are always added after the `solidus` and `solidus_auth_devise` gems, so that any
+overrides in the extension are applied correctly.
+
+:::
+
+## Staying up-to-date
+
+Just like the core, extensions are constantly updated to add new features, enhance existing
+functionality, fix bugs and add compatibility with new Solidus versions. It's extremely important to
+keep your extensions up to date with the same diligence you reserve for the rest of your
+application. You should have a clear process in place for continuously updating extensions. The
+easier it is to update extensions, the likelier it is you will be doing it early and often.
+
+The recommended approach is to write automated tests for any functionality added by extensions to
+your store. While extensions may have their own test suites, you cannot always predict how an
+extension will interact with your store's customizations or with other extensions. Furthermore, it's
+always best to test functionality in integration and in the context of your own app.
+
+It's also recommended NOT to add any version constraints to extensions in your `Gemfile`. Instead,
+rely on your tests to ensure nothing's broken after an update. Ideally, the update process should be
+as simple as running the following commands:
+
+```bash
+$ bundle update
+$ bundle exec rspec
+```
+
+Of course, this is not always possible and there may be situations where you need to lock an
+extension, but we recommend keeping these to a minimum.
+
diff --git a/versioned_docs/version-4.2/how-tos/_category_.json b/versioned_docs/version-4.2/how-tos/_category_.json
new file mode 100644
index 0000000..873a5c0
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/_category_.json
@@ -0,0 +1,6 @@
+{
+ "label": "How-tos",
+ "position": 5,
+ "collapsible": true,
+ "collapsed": true
+}
diff --git a/versioned_docs/version-4.2/how-tos/api/_category_.json b/versioned_docs/version-4.2/how-tos/api/_category_.json
new file mode 100644
index 0000000..53c7ebd
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/api/_category_.json
@@ -0,0 +1,6 @@
+{
+ "label": "API",
+ "position": 1,
+ "collapsible": true,
+ "collapsed": true
+}
diff --git a/versioned_docs/version-4.2/how-tos/api/add-attributes-to-existing-resources.mdx b/versioned_docs/version-4.2/how-tos/api/add-attributes-to-existing-resources.mdx
new file mode 100644
index 0000000..ad963c6
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/api/add-attributes-to-existing-resources.mdx
@@ -0,0 +1,111 @@
+---
+sidebar_position: 10
+---
+
+# Add attributes to existing resources
+
+This guide will show you how to add attributes to existing resources in order
+to change and retrieve your custom attributes in API endpoints.
+
+For this how-to, we are going to add a new boolean attribute called `accept_reviews` to the product resource.
+This attribute will be part of the product response in the API endpoints.
+
+## Add the new attribute to existing resources
+
+To add new attributes to existing resources, you need to create a migration file
+and run the migration.
+
+### Create a migration file
+
+To create a migration file, run the following command:
+
+```bash
+bin/rails g migration add_accept_reviews_to_spree_products accept_reviews:boolean
+```
+
+This will create a migration file in `db/migrate` with similar content:
+
+```ruby
+class AddAcceptReviewsToSpreeProducts < ActiveRecord::Migration[7.0]
+ def change
+ add_column :spree_products, :accept_reviews, :boolean
+ end
+end
+```
+
+### Run the migration
+
+To run the migration, run the following command:
+
+```bash
+bin/rails db:migrate
+```
+
+## Add the new attribute to the serializer
+
+In order to expose this field to API clients, we'll need to add a JSON field to the products API. We
+could just copy-paste the `product.json.jbuilder` view from Solidus and add the field there, but
+then we would need to remember to update our custom view every time the original view is changed.
+
+Instead, Solidus provides a more manageable way to add attributes to API resources via
+the `Spree::Api::Config` module. Let's see how we can do it and test it:
+
+```ruby title="config/initializers/spree.rb"
+# ...
+Spree::Api::Config.product_attributes << :accept_reviews
+```
+
+:::info
+
+Extending the attributes, as shown above, will work for all API resources, not just products. You can
+find the list of the default available attributes in the [Spree::ApiConfiguration](https://github.com/solidusio/solidus/blob/main/api/lib/spree/api_configuration.rb)
+class.
+
+:::
+
+From now on, the `accept_reviews` attribute will be included in the response of the products API.
+
+
+## Allow the new attribute to be updated
+
+In order to allow the new attribute to be updated, we need to add it to the list of permitted attributes
+in the `Spree::PermittedAttributes` module. Let's see how we can do it and test it:
+
+```ruby title="config/initializers/spree.rb"
+# ...
+Spree::PermittedAttributes.product_attributes << :accept_reviews
+```
+
+:::info
+
+Extending the permitted attributes, as shown above, will work for all API resources, not just products. You can
+find the list of the default available attributes in the [Spree::PermittedAttributes](https://github.com/solidusio/solidus/blob/main/core/lib/spree/permitted_attributes.rb)
+and more details about how they are used across the project in [Spree::Core::ControllerHelpers::StrongParameters](https://github.com/solidusio/solidus/blob/main/core/lib/spree/core/controller_helpers/strong_parameters.rb#L11).
+
+:::
+
+From now on, the `accept_reviews` attribute will be allowed to be updated in the products API.
+
+### A special case for Line Items
+
+If you are trying to update a new attribute to the line item resource, you will need an extra step. In fact,
+for security reasons, the line item attributes do not follow the same pattern as the other resources and you'll
+need to bypass the restriction by adding the following line to your `Spree::PermittedAttributes` initializer:
+
+```ruby title="config/initializers/spree.rb"
+
+Spree::PermittedAttributes.line_item_attributes << { options: [:some_option] }
+
+```
+
+Now, you can create line items with the `some_option` attribute with a similar payload:
+
+```json
+{
+ line_item: {
+ variant_id: 123,
+ quantity: 1,
+ options: { some_option: "foobar" }
+ }
+}
+```
diff --git a/versioned_docs/version-4.2/how-tos/api/how-to-sign-in-to-the-solidus-api-using-solidus_auth_devise.mdx b/versioned_docs/version-4.2/how-tos/api/how-to-sign-in-to-the-solidus-api-using-solidus_auth_devise.mdx
new file mode 100644
index 0000000..8e1bf7f
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/api/how-to-sign-in-to-the-solidus-api-using-solidus_auth_devise.mdx
@@ -0,0 +1,160 @@
+---
+sidebar_position: 30
+---
+
+# How to sign in to the Solidus API using solidus_auth_devise
+
+We'll learn how to leverage a common dependency on Solidus,
+[solidus_auth_devise][solidus_auth_devise], to also provide the initial
+email/password authentication for the Solidus API.
+
+We need to create the controller to handle the sign in, inheriting from the
+standard [`Devise::SessionsController`][devise_sessions_controller]:
+
+```ruby title="app/controllers/spree/api/user_sessions_controller.rb"
+# frozen_string_literal: true
+
+class Spree::Api::UserSessionsController < Devise::SessionsController
+ skip_before_action :verify_authenticity_token
+
+ clear_respond_to && respond_to(:json)
+
+ def after_sign_in_path_for(_resource)
+ nil
+ end
+end
+```
+
+There are a few important things to notice here:
+
+* We're skipping the `verify_authenticity_token` as with any other API
+requests.
+* We're configuring the controller to only respond to JSON requests. First, we
+need to clear the previous configuration inherited from solidus_auth_devise.
+The `clear_respond_to` && `respond_to` methods come from the [`responders`
+gem][responders], which is a dependency used by devise.
+* We're overriding the `after_sign_in_path_for` method to return `nil` in order
+to avoid any redirection attempt after sign in. That's a hook used by devise.
+
+Let's now configure devise in the routes:
+
+```ruby title="config/routes.rb"
+# ...
+ namespace :api do
+ devise_scope :spree_user do
+ post '/sign_in', to: '/spree/api/user_sessions#create', format: false, defaults: { format: :json }
+ end
+ end
+# ...
+```
+
+The `devise_scope` method is used to tell devise which user scope needs to be
+handled in the route defined within. For the declared route, we ensure that
+`:json` is the default format and that no others can be requested.
+
+We can now test the sign in endpoint. After restarting the server, we can run:
+
+```bash
+$ curl -X POST -v -H 'Content-Type: application/json' http://localhost:3000/api/sign_in -d '{"spree_user": {"email": "admin@example.com", "password": "test123" }}'
+```
+
+Given that the user exists, we should get a response similar to:
+
+```bash
+{"id":1,"email":"admin@example.com","persistence_token":null,"perishable_token":null,"last_request_at":null,"login":"admin@example.com","ship_address_id":null,"bill_address_id":null,"created_at":"2023-02-02T04:54:14.867Z","updated_at":"2023-02-03T15:24:09.962Z","spree_api_key":"fbfd90eb1b323fbcdebf59fe9280917b4e2c80569e2d4aed","authentication_token":null,"deleted_at":null}
+```
+
+For Rails 7 onward, we still need to do an adjustment to work with the unhappy
+path (see [issue on the Devise repository][issue_devise]):
+
+```ruby title="config/initializers/devise.rb"
+# ...
+Devise.setup do |config|
+ # ...
+ config.navigational_formats = ['*/*', :html, :turbo_stream]
+end
+```
+
+After restarting once more the server, let's try to sign in with invalid credentials:
+
+```bash
+$ curl -X POST -v -H 'Content-Type: application/json' http://localhost:3000/api/sign_in -d '{"spree_user": {"email": "admin@example.com", "password": "invalid" }}'
+```
+
+We should now get:
+
+```bash
+{"error":"Invalid email or password."}
+```
+
+## Customizing the success response
+
+The default response for a successful sign in is the user instance serialized
+as JSON. However, we can create our own view to change it:
+
+:::info
+We're using here [jbuilder][jbuilder], which is already a dependency on
+solidus-api, but you can use any other templating engine.
+:::
+
+```ruby title="app/views/spree/api/user_sessions/create.json.jbuilder"
+json.attributes([:email, :spree_api_key])
+```
+
+Let's try again:
+
+```bash
+$ curl -X POST -v -H 'Content-Type: application/json' http://localhost:3000/api/sign_in -d '{"spree_user": {"email": "admin@example.com", "password": "test123" }}'
+{"attributes":["email","spree_api_key"]}
+```
+
+## Customizing the failure response
+
+We can also customize the response for a failed sign in. For that, we need to
+configure the so-called [failure application][failure_application] that
+[warden][warden], the engine underlining devise, uses. We can override the
+default behavior via inheritance:
+
+```ruby title="lib/auth_failure_app.rb"
+# frozen_string_literal: true
+
+class AuthFailureApp < Devise::FailureApp
+ def http_auth_body
+ return super unless request_format == :json
+
+ {
+ success: false,
+ message: i18n_message
+ }.to_json
+ end
+end
+```
+
+Let's inform warden to use it from the devise initializer:
+
+```ruby title="config/initializers/devise.rb"
+# frozen_string_literal: true
+
+require 'auth_failure_app'
+
+# ...
+ config.warden do |manager|
+ manager.failure_app = AuthFailureApp
+ end
+# ...
+```
+
+After restarting the server, we can now confirm that the response on failure has changed:
+
+```bash
+$ curl -X POST -v -H 'Content-Type: application/json' http://localhost:3000/api/sign_in -d '{"spree_user": {"email": "admin@example.com", "password": "invalid" }}'
+{"success":false,"message":"Invalid email or password."}
+```
+
+[solidus_auth_devise]: https://github.com/solidusio/solidus_auth_devise
+[devise_sessions_controller]: https://github.com/heartcombo/devise/blob/main/app/controllers/devise/sessions_controller.rb
+[responders]: https://github.com/heartcombo/responders
+[issue_devise]: https://github.com/heartcombo/devise/issues/5439
+[jbuilder]: https://github.com/rails/jbuilder
+[failure_application]: https://github.com/wardencommunity/warden/wiki/Failures
+[warden]: https://github.com/wardencommunity/warden
diff --git a/versioned_docs/version-4.2/how-tos/how-to-setup-colorado-delivery-fee.mdx b/versioned_docs/version-4.2/how-tos/how-to-setup-colorado-delivery-fee.mdx
new file mode 100644
index 0000000..0a343c2
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/how-to-setup-colorado-delivery-fee.mdx
@@ -0,0 +1,32 @@
+---
+sidebar_position: 20
+---
+
+# How to setup Colorado Delivery Fee
+
+Solidus taxation system supports the [Colorado retail delivery fee][colorado-delivery-fee]
+out of the box. In this how-to, we will see the steps required to configure your store to enable this tax.
+
+## Via Admin Panel
+
+1. Create a new Zone for the state of Colorado.
+2. Create a new TaxRate within that zone, with:
+ - The new Colorado Zone as its zone.
+ - A rate of `0.27`.
+ - `Order Level` as `Tax Rate Level`.
+ - `FlatFee` as its base calculator.
+
+
+
+
+## Via rake task
+
+Solidus also provides a [rake task][rake-task] to help with the process. It will add the
+new Tax Rate to the default Tax Category if it exists. To execute it, please run:
+
+```bash
+bin/rails taxes:colorado_delivery_fee
+```
+
+[colorado-delivery-fee]: https://tax.colorado.gov/retail-delivery-fee
+[rake-task]: https://github.com/solidusio/solidus/blob/v3.3.0/core/lib/tasks/colorado_delivery_fee.rake
diff --git a/versioned_docs/version-4.2/how-tos/how-to-use-a-custom-promotion-adjuster.mdx b/versioned_docs/version-4.2/how-tos/how-to-use-a-custom-promotion-adjuster.mdx
new file mode 100644
index 0000000..28ba687
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/how-to-use-a-custom-promotion-adjuster.mdx
@@ -0,0 +1,71 @@
+---
+sidebar_position: 10
+---
+
+# How to use a custom promotion adjuster
+
+Every time an order is updated, its eligible promotions might
+not be valid anymore; for example, this could happen if a promotion is
+tight to a specific order total threshold, and this value changes within
+the update. In the same way, changing the order could make it eligible
+for new promotions.
+
+In this guide, we'll see how to use our custom logic to recalculate adjustments every time an
+order is updated.
+
+In Solidus, this behavior is encapsulated in the
+[`Spree::Promotion::OrderAdjustmentsRecalculator`][order_adjustments_recalculator],
+class, which is called every time the order is updated. This class iterates over all
+existing promotion adjustments and recalculates their amount and eligibility.
+
+:::caution
+
+Please, keep in mind that promotion adjustments are handled before taxes are
+calculated, such that taxes always respect promotions.
+
+:::
+
+We'll now see how to use our own class instead of the one provided
+by Solidus.
+
+First of all, let's declare which promotion adjuster class we want to
+use, with the corresponding configuration:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+ config.promotion_adjuster_class = 'MyStore::PromotionAdjuster'
+end
+```
+
+Lastly, let's create our custom class, which implements the same interface
+of the original promotion adjuster class provided by Solidus:
+
+```ruby title="app/models/my_store/promotion_adjuster.rb"
+module MyStore
+ class PromotionAdjuster
+ def initialize(order)
+ @order = order
+ end
+
+ def call
+ # Your custom logic here.
+ end
+ end
+end
+```
+
+:::info
+
+Keep in mind that if you define your class as:
+
+```ruby
+class PromotionAdjuster < ::Spree::Promotion::OrderAdjustmentsRecalculator
+```
+
+you will be able to use `super` in its methods to execute parts of the
+original class provided by Solidus.
+
+:::
+
+[order_adjustments_recalculator]: https://github.com/solidusio/solidus/blob/v3.3/core/app/models/spree/promotion/order_adjustments_recalculator.rb
diff --git a/versioned_docs/version-4.2/how-tos/how-to-use-searchkick-for-search-autocomplete.mdx b/versioned_docs/version-4.2/how-tos/how-to-use-searchkick-for-search-autocomplete.mdx
new file mode 100644
index 0000000..72cd21b
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/how-to-use-searchkick-for-search-autocomplete.mdx
@@ -0,0 +1,363 @@
+---
+sidebar_position: 40
+---
+
+# How to use Searchkick for search autocomplete
+
+In this guide, we'll see how to improve the autocomplete capabilities offered
+by default in [Solidus Starter Frontend][ssf], providing support for large
+dataset and advanced features like full-text search, typo tolerance, etc.
+
+This how-to will use a gem called [Searchkick][searchkick-github], which
+is one of the most popular choices in the Rails ecosystem.
+
+## Installing Searchkick
+
+Let's get started by adding the necessary gems to our Gemfile:
+
+```
+bundle add searchkick elasticsearch
+```
+
+:::info
+
+Searchkick supports both Elasticsearch and OpenSearch. This guide
+section covers the installation with Elasticsearch. Please, refer to
+[Searchkick README][searchkick-github] for more installation options.
+
+:::
+
+## Indexing Configuration
+
+We can now customize our Product model to tell it how its information
+should be indexed. We can [customize][override-core] the `Spree::Product`
+core model:
+
+```ruby title="app/overrides/my_store/spree/product/add_searchkick.rb"
+module AmazingStore
+ module Spree
+ module Product
+ module AddSearchkick
+ def self.prepended(base)
+ unless base.respond_to?(:searchkick_index)
+ base.searchkick word_start: [:name]
+ end
+ end
+
+ def search_data
+ { name: name }
+ end
+
+ def should_index?
+ kept?
+ end
+
+ ::Spree::Product.prepend self
+ end
+ end
+ end
+end
+```
+
+A brief explanation about what we just added to the
+`Spree::Product` model:
+
+- `searchkick word_start: [:name]` enables Searchkick for this
+ model. `word_start: [:name]` will index the name of the product
+ in a special way that allows a partial match with the initial
+ part of the string. By default, it matches the entire word.
+ We will see later why we need this.
+- `unless base.respond_to?(:searchkick_index)` conditional surrounds
+ the statement above to avoid Searchkick to try to load itself multiple
+ times in development, where the code is reloaded at each new request.
+- `search_data` tells Searchkick which fields we want to index.
+ For this example, `name` is enough but you could consider adding
+ more fields depending on your needs.
+- `should_index?` determines which records will be indexed. In this
+ case, to keep it simple we only index products that are not
+ soft-deleted.
+
+Now that we've got the basis, it's time for our first real indexing.
+Opening a Rails console, we can run:
+
+```ruby title="bin/rails c"
+Spree::Product.reindex
+```
+
+:::info
+
+Be sure you have Elasticsearch up and running. Please, follow the
+official [installation instructions][elasticsearch-install-guide],
+or if you are a MacOS and Homebrew user, just run:
+
+```sh
+brew install elastic/tap/elasticsearch-full
+brew services start elasticsearch-full
+```
+
+:::
+
+At this point, we can verify that a basic search is working.
+Assuming you have some products which include the `ruby`
+word in one of the indexed fields, you can run:
+
+```ruby title="bin/rails c"
+Spree::Product.search("ruby").map(&:name)
+# => ["Ruby Hoodie", "Ruby Polo", "Ruby Mug", "Ruby Tote", "Ruby Hoodie Zip"]
+```
+
+Yey, it works! π
+
+Time to index our categories as well:
+
+```ruby title="app/overrides/my_store/spree/taxon/add_searchkick.rb"
+module AmazingStore
+ module Spree
+ module Taxon
+ module AddSearchkick
+ def self.prepended(base)
+ unless base.respond_to?(:searchkick_index)
+ base.searchkick word_start: [:name]
+ end
+ end
+
+ def search_data
+ { name: name }
+ end
+
+ ::Spree::Taxon.prepend self
+ end
+ end
+ end
+end
+```
+
+We can now run a reindex for `Spree::Taxon` and verify it's
+working:
+
+```ruby title="bin/rails c"
+Spree::Taxon.reindex
+Spree::Taxon.search("ruby").map(&:name)
+# => ["Ruby"]
+```
+
+## Enhancing search autocomplete
+
+[Solidus Starter Frontend][ssf] comes with a simple search autocomplete out of
+the box. In this section of the guide, we will change its behavior to use the
+newly added advanced search functionalities.
+
+:::info
+
+The search autocomplete feature has been added to the Starter Frontend
+since Solidus 3.4.0. If you don't have it, please refer to
+[Autocomplete main search with products and taxons][autocomplete-pr] to
+backport that feature in your storefront.
+
+:::
+
+To let the autocomplete controller use Searchkick, we need to change the
+code of the `AutocompleteResultsController` provided like this:
+
+```diff title="app/controllers/autocomplete_results_controller.rb"
+
+ def autocomplete_products
+ if params[:keywords].present?
+- searcher = build_searcher(params.merge(per_page: 5))
+- searcher.retrieve_products
++ Spree::Product.search(params[:keywords], fields: [{name: :word_start}], limit: 5)
+ else
+ Spree::Product.none
+ end
+ end
+
+ def autocomplete_taxons
+ if params[:keywords].present?
+- Spree::Taxon
+- .where(Spree::Taxon.arel_table[:name].matches("%#{params[:keywords]}%"))
+- .limit(5)
++ Spree::Taxon.search(params[:keywords], fields: [{name: :word_start}], limit: 5)
+ else
+ Spree::Taxon.none
+ end
+ end
+```
+
+- `fields: [{name: :word_start}]` part is required to tell Searchkick that we
+ want to use the special index for partial matching, previously defined in
+ the models.
+
+
+With just these small changes, the autocomplete will finally use Searchkick
+under the hood.
+
+
+## Next steps
+
+We just scratched the surface of what's possible with Searchkick, and
+it's left to each store to implement the configuration that better suits
+its needs. Still, there are some big things worth considering and in
+this section, we'll see the most relevant for a typical Solidus store.
+
+:::info
+
+[Searchkick README][searchkick-github] is really well done and contains
+more details about the information reported in this guide. Please, always
+refer to that documentation to get extended and updated information.
+
+:::
+
+
+### Reindex Strategies
+
+By default, every time you change a record if Searchkick is enabled
+on that model, a reindex will be triggered for that record only.
+While this is very useful, it can hurt stores' performances.
+
+To mitigate this problem, it is suggested to use the Asynchronous
+Reindex Strategy, which will push the update to a background job.
+
+:::info
+
+Please refer to Rails' [Active Job][active-job] guide to setup your
+system for background jobs support.
+
+:::
+
+To enable this strategy, you can edit the directive that enabled
+Searchkick on the models, like this:
+
+```diff title="app/overrides/my_store/spree/product/add_searchkick.rb"
+- base.searchkick word_start: [:name]
++ base.searchkick word_start: [:name], callbacks: :async
+```
+
+### Reindex Associations
+
+Another important aspect of automatic reindexing is how
+associations are reindexed. In fact, data is not automatically
+synced when an association is updated. This is useful if the
+information indexed for a given record depends on the state of
+another resource of your application, associated with that record.
+
+For example, imagine you only want to index products that have
+stock in your warehouses. You can easily add this behavior using
+the `should_index?` method as we saw before:
+
+
+```diff title="app/overrides/my_store/spree/product/add_searchkick.rb"
+ def should_index?
+- kept?
++ kept? && total_on_hand > 0
+ end
+```
+
+:::info
+
+`total_on_hand` checks all the product variants' stock levels via a
+model called Spree::StockItem. This model contains all the information
+about the remaining stock for a given variant in a given warehouse.
+
+:::
+
+Nice and clean, but what if someone purchases that product and its stock
+goes to 0?
+
+We need to also tell Solidus to reindex products associated with the
+corresponding StockItem, every time they get an update.
+
+```ruby title="app/overrides/my_store/spree/stock_item/reindex_product_on_change.rb"
+module AmazingStore
+ module Spree
+ module StockItem
+ module ReindexProductOnChange
+ def self.prepended(base)
+ base.after_commit :reindex_product
+ end
+
+ private
+
+ def reindex_product
+ variant.product.reindex
+ end
+
+ ::Spree::StockItem.prepend self
+ end
+ end
+ end
+end
+```
+
+Now, every time a stock level changes, the system will reindex the
+corresponding product to determine if it's still eligible to be part of the
+autocomplete search results.
+
+### Completely replace Solidus Search with Searchkick.
+
+You might be wondering why we are using these powerful Searchkick capabilities
+for autocomplete results only instead of replacing the whole Solidus searcher
+to use this new approach. That's a good point, actually!
+
+Unfortunately, replacing the whole searcher with Searchkick properly requires us
+to support a lot of other features, like filtering products per taxons, accepting
+scopes already defined on `Spree::Products` and many other things that would have
+made this how-to way more complex. We'll leave here an initial implementation idea,
+which only replaces the matching keyword logic from ActiveRecord to Searchkick.
+
+First of all, let's restore using the searcher in the autocomplete results controller
+for products:
+
+```diff title="app/controllers/autocomplete_results_controller.rb"
+ def autocomplete_products
+ if params[:keywords].present?
+- Spree::Product.search(params[:keywords], fields: [{name: :word_start}], limit: 5)
++ searcher = build_searcher(params.merge(per_page: 5))
++ searcher.retrieve_products
+ else
+ Spree::Product.none
+ end
+ end
+```
+
+Now, we can create our own custom searcher based on Searchkick:
+
+```ruby title="app/models/my_store/searchkick_searcher.rb"
+module MyStore
+ class SearchkickSearcher < Spree::Core::Search::Base
+ protected
+
+ def get_products_conditions_for(base_scope, query)
+ unless query.blank?
+ searchkick_products = Spree::Product.search(query, fields: [{name: :word_start}])
+ base_scope.where(ids: searchkick_products.pluck(:id))
+ end
+ base_scope
+ end
+ end
+end
+```
+
+As you can see, this class inherits from [the core searcher][base-searcher],
+and replaces just the method responsible for filtering results by the keyword
+passed to the searcher.
+
+As the final step, we can tell Solidus to use this class with the proper configuration:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ config.searcher_class = 'MyStore::SearchkickSearcher'
+ # ...
+end
+```
+
+That's it. Now, if you want, you can iterate and replace more parts of the searcher
+using Searchkick.
+
+
+[ssf]: https://github.com/solidusio/solidus_starter_frontend
+[searchkick-github]: https://github.com/ankane/searchkick
+[elasticsearch-install-guide]: https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html
+[override-core]: /customization/customizing-the-core#using-overrides
+[autocomplete-pr]: https://github.com/solidusio/solidus_starter_frontend/pull/300
+[active-job]: https://guides.rubyonrails.org/active_job_basics.html
+[base-searcher]: https://github.com/solidusio/solidus/blob/019ba27e24fcbc255db7fd3efdb5820efe0662c7/core/lib/spree/core/search/base.rb
diff --git a/versioned_docs/version-4.2/how-tos/images/colorado-delivery-fee-setup.gif b/versioned_docs/version-4.2/how-tos/images/colorado-delivery-fee-setup.gif
new file mode 100644
index 0000000..6147e8d
Binary files /dev/null and b/versioned_docs/version-4.2/how-tos/images/colorado-delivery-fee-setup.gif differ
diff --git a/versioned_docs/version-4.2/how-tos/rma_and_returns/_category_.json b/versioned_docs/version-4.2/how-tos/rma_and_returns/_category_.json
new file mode 100644
index 0000000..760926e
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/rma_and_returns/_category_.json
@@ -0,0 +1,6 @@
+{
+ "label": "RMA and Returns",
+ "position": 3,
+ "collapsible": true,
+ "collapsed": true
+}
diff --git a/versioned_docs/version-4.2/how-tos/rma_and_returns/how-to-customize-return-eligibility-rules-skipping-rmas.mdx b/versioned_docs/version-4.2/how-tos/rma_and_returns/how-to-customize-return-eligibility-rules-skipping-rmas.mdx
new file mode 100644
index 0000000..ca4d454
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/rma_and_returns/how-to-customize-return-eligibility-rules-skipping-rmas.mdx
@@ -0,0 +1,64 @@
+---
+sidebar_position: 2
+---
+
+# How to customize return eligibility rules: skipping RMAs
+
+Solidus' [built-in process for returns][returns] is mighty and flexible, and
+even allows you to disable RMAs as a prerequisite to creating a customer
+return.
+
+Go ahead and create an order from the storefront. You must go through
+the entire checkout process, from adding items to the cart to confirming the
+payment.
+
+Next, you need to capture the payments in the system and perform the shipments.
+You can do it either from the admin panel or through the console. E.g.:
+
+```ruby
+order = Spree::Order.find_by_number('R723438584')
+order.payments.map(&:capture!)
+order.shipments.map(&:ship!)
+```
+
+Let us go to the heart of the matter. When you add return items to a customer's
+return, Solidus checks whether they are eligible for being returned. By
+default, it uses
+[`Spree::ReturnItem::EligibilityValidator::Default`][eligibility-validator-default].
+Even if you can [replace it altogether][configure-eligibility-validator], the
+default validator is flexible enough to allow us to skip only the requirement
+for an RMA.
+
+Take a look at the stack of steps that `EligibilityValidator::Default` calls:
+
+```ruby title="core/app/models/spree/return_item/eligibility_validator/default.rb"
+# ...
+self.permitted_eligibility_validators = [
+ ReturnItem::EligibilityValidator::OrderCompleted,
+ ReturnItem::EligibilityValidator::TimeSincePurchase,
+ ReturnItem::EligibilityValidator::RMARequired,
+ ReturnItem::EligibilityValidator::InventoryShipped,
+ ReturnItem::EligibilityValidator::NoReimbursements
+]
+# ...
+```
+
+Let's use the Solidus initializer to remove the unwanted one:
+
+```ruby title="config/initializers/spree.rb"
+# ...
+Rails.application.config.to_prepare do
+ ::Spree::ReturnItem::EligibilityValidator::Default.permitted_eligibility_validators.delete(
+ ::Spree::ReturnItem::EligibilityValidator::RMARequired
+ )
+end
+# ...
+```
+
+And that's it! You can now simplify the return flow by creating customer
+returns without going through a previous authorization. You can go ahead and
+check it out in the admin panel.
+
+[returns]: /advanced-solidus/returns.mdx
+[eligibility-validator-default]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/return_item/eligibility_validator/default.rb
+[configure-eligibility-validator]: https://github.com/solidusio/solidus/blob/008192cd82dc9e8c270b47782624172ac02b3552/core/app/models/spree/return_item.rb#L14
diff --git a/versioned_docs/version-4.2/how-tos/rma_and_returns/how-to-modify-valid-exchange-items-in-returns.mdx b/versioned_docs/version-4.2/how-tos/rma_and_returns/how-to-modify-valid-exchange-items-in-returns.mdx
new file mode 100644
index 0000000..abbefe2
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/rma_and_returns/how-to-modify-valid-exchange-items-in-returns.mdx
@@ -0,0 +1,66 @@
+---
+sidebar_position: 1
+---
+
+# How to modify valid exchange items in returns
+
+When customers perform a [return][returns], they can be presented with options
+to exchange the corresponding item. We'll see how we can modify which variants are
+prompted as eligible.
+
+To begin with, you can go through the checkout process in the storefront and
+create a completed order. As we need to perform a return, we must capture its
+payments in the system and mark the shipments as delivered. You can do it in
+the backend or through the console:
+
+```ruby
+order = Spree::Order.find_by_number('R723438584')
+order.payments.map(&:capture!)
+order.shipments.map(&:ship!)
+```
+
+If you go now to the backend and try to create a new RMA for that order, you'll
+see that variants for the same product are shown to the user as valid
+exchanges. That's because, by default,
+[`Spree::ReturnItem::ExchangeVariantEligibility::SameProduct`][same-product] is
+[used as the engine][configure-exchange-variant-engine].
+
+Let's be more strict and leverage the default engine to restrict exchanges to
+those variants of the same product that are not heavier than the original. We
+need to follow the same API used by the built-in engines.
+
+```ruby title="app/services/amazing_store/return_item/exchange_variant_eligibility/same_product_not_heavier.rb"
+# frozen_string_literal: true
+
+module AmazingStore
+ module ReturnItem
+ module ExchangeVariantEligibility
+ module SameProductNotHeavier
+ def self.eligible_variants(variant, stock_locations: nil)
+ Spree::ReturnItem::ExchangeVariantEligibility::SameProduct.
+ eligible_variants(variant, stock_locations: stock_locations).
+ where(weight: ..variant.weight)
+ end
+ end
+ end
+ end
+end
+```
+
+Lastly, we need to tell Solidus to use our module by default. We can configure
+it in the Solidus initializer:
+
+```ruby title="config/initializers/spree.rb"
+# ...
+Rails.application.config.to_prepare do
+ ::Spree::ReturnItem.exchange_variant_engine = AmazingStore::ReturnItem::ExchangeVariantEligibility::SameProductNotHeavier
+end
+# ...
+```
+
+Restart the server and check it out! If you go back to the admin panel, you'll
+see that only lighter variants are now presented to the user.
+
+[returns]: /advanced-solidus/returns.mdx
+[configure-exchange-variant-engine]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/return_item.rb#L22
+[same-product]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/return_item/exchange_variant_eligibility/same_product.rb
diff --git a/versioned_docs/version-4.2/how-tos/rma_and_returns/how-to-use-custom-logic-to-calculate-return-refunds.mdx b/versioned_docs/version-4.2/how-tos/rma_and_returns/how-to-use-custom-logic-to-calculate-return-refunds.mdx
new file mode 100644
index 0000000..9a08d68
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/rma_and_returns/how-to-use-custom-logic-to-calculate-return-refunds.mdx
@@ -0,0 +1,81 @@
+---
+sidebar_position: 3
+---
+
+# How to use custom logic to calculate return refunds
+
+We'll see how to customize the refund amount customers get when they perform a
+[return][returns]. Solidus performs full-amount refunds by default, but we can
+change it easily.
+
+We need a completed order, with payments captured and shipments delivered. Go
+through the checkout process and fulfill those conditions in the admin or
+console:
+
+```ruby
+order = Spree::Order.find_by_number('R723438584')
+order.payments.map(&:capture!)
+order.shipments.map(&:ship!)
+```
+
+You can go to the admin panel and check how the total amount for each inventory
+unit is presented by default when creating a new RMA for that order. That's the
+logic done by the [default return calculator][refund-calculator-default], which
+is [configured][configure-refund-calculator] in the return item model.
+
+Sometimes, though, stores need a stricter policy. Imagine that the full refund
+is only granted during the first week after placing the order. During the
+second week, there's a 25% penalty; beyond that, only 50% of the item's price
+is refunded.
+
+We can create our custom refund calculator accounting for those requirements
+while we can still lean on the default implementation:
+
+```ruby title="app/services/amazing_store/calculator/returns/with_penalty.rb"
+# frozen_string_literal: true
+
+module AmazingStore
+ module Calculator
+ module Returns
+ class WithPenalty < Spree::Calculator::Returns::DefaultRefundAmount
+ def compute(return_item)
+ default = super
+ case days_since_order(return_item)
+ when 0..7
+ default
+ when 8..14
+ default * 0.75
+ else
+ default * 0.5
+ end
+ end
+
+ private
+
+ def days_since_order(return_item)
+ (Date.today - return_item.inventory_unit.order.created_at.to_date).to_i
+ end
+ end
+ end
+ end
+end
+```
+
+We need to configure the returns item model to enable our calculator. Let's
+leverage the Solidus' initializer for that:
+
+```ruby title="config/initializers/spree.rb"
+# ...
+Rails.application.config.to_prepare do
+ ::Spree::ReturnItem.refund_amount_calculator = AmazingStore::Calculator::Returns::WithPenalty
+end
+# ...
+```
+
+That's it! Restart the server and check it out yourself. You might want to
+manually update the order's `:created_at` field to test the different possible
+situations.
+
+[returns]: /advanced-solidus/returns.mdx
+[refund-calculator-default]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/returns/default_refund_amount.rb
+[configure-refund-calculator]: https://github.com/solidusio/solidus/blob/31187cec36ccefca33406d5ffb5914db3eca78f2/core/app/models/spree/return_item.rb#L30
diff --git a/versioned_docs/version-4.2/how-tos/state_machine/_category_.json b/versioned_docs/version-4.2/how-tos/state_machine/_category_.json
new file mode 100644
index 0000000..161e265
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/state_machine/_category_.json
@@ -0,0 +1,6 @@
+{
+ "label": "State Machine",
+ "position": 2,
+ "collapsible": true,
+ "collapsed": true
+}
diff --git a/versioned_docs/version-4.2/how-tos/state_machine/how-to-add-orthogonal-behavior-to-state-machines-publishing-events.mdx b/versioned_docs/version-4.2/how-tos/state_machine/how-to-add-orthogonal-behavior-to-state-machines-publishing-events.mdx
new file mode 100644
index 0000000..ebb8833
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/state_machine/how-to-add-orthogonal-behavior-to-state-machines-publishing-events.mdx
@@ -0,0 +1,92 @@
+---
+sidebar_position: 1
+---
+
+# How to add orthogonal behavior to state machines: publishing events
+
+In this guide, we'll see how we can hook into the business flow transitions
+orchestrated by a state machine to add behavior that fits into another area
+domain.
+
+As noted in the [event bus][event-bus] guide, you can leverage the event bus to
+hook into core events. That's helpful when you need to perform something in
+response to a change in the system, but your logic is orthogonal (i.e.,
+decoupled) to the main flow. Transitions between state machine states are good
+candidates to become hotspots where tangential logic is triggered.
+
+:::caution
+
+Don't be confused about state machine events vs. bus events. State machine
+events are conditions that can produce a transition between valid states.
+They're local to the state machine component. On the other hand, bus events can
+be published and consumed anywhere within the system and, per se, have nothing
+to do with the state machines.
+
+:::
+
+For instance, you might want to update your ERP or send an SMS when a payment
+is marked as completed. The cleaner way to do that is to publish an event when
+that happens and then subscribe to it.
+
+First, you need to override the `#complete` state machine event on
+`Spree::Payment` (see the [overrides section][customize-core] for the required
+setup code):
+
+```ruby title="app/overrides/my_store/publish_payment_completed.rb"
+# frozen_string_literal: true
+
+module MyStore
+ module PublishPaymentCompleted
+ def complete
+ super.tap do |result|
+ Spree::Bus.publish(:payment_completed, payment: self) if result
+ end
+ end
+
+ ::Spree::Payment.prepend self
+ end
+end
+```
+
+The following is an example of a subscriber to the new event:
+
+```ruby title="app/subscribers/my_store/payments_subscriber.rb"
+module MyStore
+ class PaymentsSubscriber
+ include Omnes::Subscriber
+
+ handle :payment_completed,
+ with: :notify_payment_completed
+
+ def notify_payment_completed(event)
+ payment = event.payload[:payment]
+ Notifier.new.notify_payment_completed(payment)
+ end
+ end
+end
+```
+
+Don't forget to register the new event and subscribe to it:
+
+```ruby title="config/initializer/events.rb"
+Rails.application.config.to_prepare do
+ Spree::Bus.register(:payment_completed)
+ MyStore::PaymentsSubriber.new.subscribe_to(Spree::Bus)
+end
+```
+
+Done. Once the server is restarted, the `:payment_completed` event will be
+published every time a payment is completed. That will let its subscriber know
+it's time to do their job.
+
+:::info
+
+Ideally, Solidus would publish events for every state machine transition out of the box. Our event
+bus is fairly new and we're still working on it, but we'll get there eventually! In the meantime,
+you can check`Spree::Bus.registered_events` for the complete list of events that are already
+published.
+
+:::
+
+[event-bus]: /customization/subscribing-to-events.mdx
+[customize-core]: /customization/customizing-the-core.mdx#using-overrides
diff --git a/versioned_docs/version-4.2/how-tos/state_machine/how-to-customize-existing-state-machines.mdx b/versioned_docs/version-4.2/how-tos/state_machine/how-to-customize-existing-state-machines.mdx
new file mode 100644
index 0000000..0b5a121
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/state_machine/how-to-customize-existing-state-machines.mdx
@@ -0,0 +1,52 @@
+---
+sidebar_position: 2
+---
+
+# How to customize existing state machines
+
+Sometimes you might need to tweak Solidus' core model to fit your business
+needs. In that case, you might want to tweak a state machine to obey your
+extended domain.
+
+Say that you must store the time when a payment has been marked as completed.
+First, you need to add a new `#completed_at` field to the payments table:
+
+```bash
+bin/rails g migration AddCompletedAtToSpreePayments completed_at:time
+bin/rails db:migrate
+```
+
+Next, you can leverage the payment state machine to fill it. You can add an
+`after_transition hook` using an [override][customize-core] (don't forget the setup code in `config/application.rb`):
+
+```ruby title="app/overrides/my_store/payment_set_completed_at.rb"
+# frozen_string_literal: true
+
+module MyStore
+ module PaymentSetCompletedAt
+ def self.prepended(base)
+ base.state_machine.after_transition(to: :completed) do
+ self.completed_at = Time.zone.now
+ end
+ end
+
+ ::Spree::Payment.prepend self
+ end
+end
+```
+
+That's all you need to do. From this moment, the state machine will be
+responsible for storing when a payment is completed.
+
+:::danger
+
+Be aware of not overusing transition hooks in the state machines. When the involved logic requires
+reaching external services or, more generally, is decoupled from the main flow, you're better
+off [leveraging the event bus][how-to-add-orthogonal-behavior]. Otherwise, you
+will eventually run into the typical gotchas and downsides of abusing
+`ActiveRecord` callbacks.
+
+:::
+
+[customize-core]: /customization/customizing-the-core.mdx#using-overrides
+[how-to-add-orthogonal-behavior]: /how-tos/state_machine/how-to-add-orthogonal-behavior-to-state-machines-publishing-events.mdx
diff --git a/versioned_docs/version-4.2/how-tos/state_machine/how-to-replace-an-existing-state-machine.mdx b/versioned_docs/version-4.2/how-tos/state_machine/how-to-replace-an-existing-state-machine.mdx
new file mode 100644
index 0000000..2da9503
--- /dev/null
+++ b/versioned_docs/version-4.2/how-tos/state_machine/how-to-replace-an-existing-state-machine.mdx
@@ -0,0 +1,46 @@
+---
+sidebar_position: 3
+---
+
+# How to replace an existing state machine
+
+We'll see how you can flat-out replace state machines with your custom
+implementation. Not something you'll need every day, as [it comes with its
+drawbacks][state-machines-customization], but it can bring you a lot of
+flexibility if needed.
+
+A custom state machine can be specified through the `state_machines` option in
+`config/initializers/spree.rb`.
+
+For instance, if you wanted to replace the payment state machine, you could
+create your own one like this:
+
+```ruby title="lib/my_store/state_machines/payment.rb"
+# frozen_string_literal: true
+
+module MyStore
+ class StateMachines
+ module Payment
+ extend ActiveSupport::Concern
+
+ included do
+ state_machine initial: :custom_state do
+ # Event, transition & hook definitions
+ end
+ end
+ end
+ end
+end
+```
+
+And then you'd need to tell Solidus to use it:
+
+```ruby title="config/initializers/spree.rb"
+# ...
+Spree.config do |config|
+ config.state_machines.payment = 'MyStore::StateMachines::Payment'
+ # ...
+end
+```
+
+[state-machines-customization]: /advanced-solidus/state-machines.mdx#customizing-state-machines
diff --git a/versioned_docs/version-4.2/index.mdx b/versioned_docs/version-4.2/index.mdx
new file mode 100644
index 0000000..b22ec4b
--- /dev/null
+++ b/versioned_docs/version-4.2/index.mdx
@@ -0,0 +1,98 @@
+---
+sidebar_position: 1
+---
+
+# Introduction
+
+Solidus is a free, open-source eCommerce platform based on the Ruby on Rails framework. Built as a
+Rails engine, Solidus is designed to be incredibly flexible: you start from a strong foundation and
+a fully functional backend, but you can customize every single aspect of the platform, either
+through built-in configuration hooks and extensions or the Ruby's and Rails' native override
+mechanisms.
+
+By providing the building blocks for creating production-grade online stores, Solidus saves you a
+lot of time and money, while still giving you all the flexibility you're going to need.
+
+Today, Solidus is used in production by a multitude of popular stores such as FLOYD, Maisonette,
+MeUndies, Away, Casper, Bonobos, Ace & Tate and quite a few others.
+
+## The history of Solidus
+
+:::info
+
+You may safely skip this section if you're not interested in the history of the project.
+
+:::
+
+In the beginning there was Spree Commerce, a still very popular eCommerce platform for Rails. Spree
+was an incredibly active, community-maintained project that saw a lot of activity. In 2015 however,
+Stembolt, an eCommerce consulting firm, didn't like the direction the project was taking and created
+a fork they called Solidus, which put the focus on stability, ease of upgrade and
+backwards-compatibility. Shortly after, the Spree project was acquired by First Data, a payment
+services provider, which completely shut down the development of the project.
+
+At this point, Solidus started gaining a lot of traction. Much of the Spree community migrated to
+Solidus and started contributing to the platform. Stembolt did an amazing job in giving the project
+a strong direction and creating a passionate community around it. In July 2018, however, Stembolt
+was also acquired by one of their clients, JUUL Labs, and stopped actively working on Solidus.
+
+Luckily, Solidus was built to survive the hit: the community, led by Nebulab, the largest
+contributor to the platform, quickly reorganized under a new model of decentralized ownership that
+would guarantee the long-term success of the platform.
+
+Today, the Solidus community is strong and healthy, with technical and financial contributions from
+several industries. Retailers, agencies and passionate developers have come together to work on a
+project they love and use on a day-to-day basis. The result is an eCommerce platform that is built
+by the same people who use it every day, and incorporates insights and feedback from companies that
+come from different business domains and scales.
+
+## Solidus vs the competition
+
+eCommerce is quite an old problem, and it has been solved in many different ways throughout the
+years. In this section, we'll attempt to compare Solidus to some of the most popular approaches and
+specific products in the industry. Bear in mind that each product was built to solve a very specific
+problem, just like Solidus, and may or may not be a good fit for your needs.
+
+### SaaS platforms
+
+SaaS \(Software as a Service\) platforms are managed solutions that are hosted by the company that
+created them, in exchange for a monthly or yearly fee. Popular examples are Shopify and BigCommerce.
+These products usually expose a UI that allows you to control different aspects of your store and
+provide some kind of extension or app marketplace where you can install additional functionality,
+either for free or by paying an additional fee.
+
+While they are great to quickly get your store up and running, SaaS platforms are, by their very
+nature, quite limited in how much they can be customized. Because you don't have direct access to
+the code that is running on your store, you can only customize what the software vendor allows you
+to customize. Furthermore, any customizations you create are "isolated" and cannot easily interact
+with other components of the system. This makes it much more difficult to create fully integrated
+experiences.
+
+Solidus takes a very different approach: by giving you full access to the underlying platform's
+code, it allows you to completely customize any aspect of your store. When you decide to base your
+store on Solidus, the sky is the limit, quite literally. Of course, this comes at a cost: you need
+to make sure you have the technical expertise and resources you need to maintain your store.
+
+### Open-source platforms
+
+Solidus' approach is not new in the eCommerce world. Magento and Spree are among the most popular
+open-source eCommerce platforms, although there are definitely others. While the general approach of
+these platforms is the same as Solidus, each product is tailored for a slightly different type of
+store and user base, and understanding these differences is key to making the right decision.
+
+Magento, for example, is a very large and bulky piece of software. This makes it a good option for
+stores that need a ton of standard features out of the box and are not looking to do a lot of
+customization. That same strength, however, can become a problem for smaller stores that want a
+flexible platform that can grow with them, or even for larger stores that want to be able to
+customize every nook and cranny of their customer \(and admin\) experience.
+
+Spree, the project Solidus was forked from, still provides a lot of customization freedom, although
+the community has been working to appeal to a larger user base by creating UI-based configuration
+flows and out-of-the-box storefront themes. These features suffer from the same limitations as SaaS
+platforms: at best, they will sit unused in your codebase as you ignore them and do your own thing;
+at worst, they will actively slow you down as you try to make them work for your unique use case.
+
+Solidus took a different path: it's a complete, yet nimble platform that puts you in total control
+of the shopping experience you want to build. While this can feel scary at first, it's a really
+liberating experience once you get used to it.
+
diff --git a/versioned_docs/version-4.2/upgrading-solidus/_category_.json b/versioned_docs/version-4.2/upgrading-solidus/_category_.json
new file mode 100644
index 0000000..8c2c6dd
--- /dev/null
+++ b/versioned_docs/version-4.2/upgrading-solidus/_category_.json
@@ -0,0 +1,6 @@
+{
+ "label": "Upgrading Solidus",
+ "position": 7,
+ "collapsible": true,
+ "collapsed": true
+}
diff --git a/versioned_docs/version-4.2/upgrading-solidus/index.mdx b/versioned_docs/version-4.2/upgrading-solidus/index.mdx
new file mode 100644
index 0000000..7c81dcb
--- /dev/null
+++ b/versioned_docs/version-4.2/upgrading-solidus/index.mdx
@@ -0,0 +1,204 @@
+---
+sidebar_position: 2
+needs-diataxis-rewrite: true
+---
+
+# Upgrading Solidus
+
+With Solidus' maintenance policy, a release will receive security patches and other critical
+bug-fixes for 18 months after it's released to the public. This should give you plenty of time to
+upgrade to new versions of Solidus before your release reaches its EOL. You can find a list of the
+currently supported Solidus versions on the [Security](https://solidus.io/security/) page of our
+website.
+
+Because of the project's focus on stability and backwards compatibility, upgrading Solidus is
+usually a painless process: minor releases NEVER break public APIs, although they may deprecate APIs
+that will then be removed in the next major.
+
+When upgrading, look at the [changelog](https://github.com/solidusio/solidus/blob/v3.0/CHANGELOG.md)
+and make a note of any large refactoring or public API changes, then update your app accordingly.
+You should also make sure to [update any extensions](/getting-started/using-extensions.mdx#staying-up-to-date) you have
+installed, since new releases may have come out to support the new Solidus version or take advantage
+of new functionality it introduces.
+
+Solidus contains a task to help you with the upgrading process. Remember to run
+it when you get hands-on:
+
+```bash
+bin/rails g solidus:update
+```
+
+It will:
+
+- Copy all new [Solidus database migrations](#updating-the-database) to your host application.
+- Generate an initializer to help you [update to the new
+ defaults](#updating-preferences).
+
+## Ruby and Rails upgrades
+
+Solidus' approach on Ruby and Ruby on Rails support is fairly simple: each
+minor release supports up to the oldest Ruby and Rails versions that are still
+maintained. For major releases, we could remove support for old but maintained
+versions if that brings some benefit, but still allowing you to update to a
+supported Ruby or Rails version before upgrading Solidus.
+
+Solidus 2.10, for instance, introduced support for Rails 6.0, but it also works with Rails 5.2. As
+for Ruby, Solidus works with Ruby 2.4 and later, because 2.4 was the oldest maintained version at
+the time of release. However, you cannot use Rails 6 with Ruby 2.4, which means
+you will have to upgrade to at least Ruby 2.5 if you want to use Solidus with
+Rails 6. Another example, Solidus 4.0 removed support for Rails 6.0 & Rails
+6.1, but, as Solidus 3.4 supports Rails 7, you can first update your store to
+Rails 7 and then go for Solidus 4.0.
+
+When you upgrade Solidus, you should also make sure to upgrade your Ruby and Rails versions to the
+newest possible versions. Ruby upgrades are usually pretty smooth, while Rails provides
+amazing [upgrade guides](https://guides.rubyonrails.org/upgrading_ruby_on_rails.html) you can
+follow.
+
+## Upgrading dependencies
+
+Solidus is just a Rails engine that runs as part of your application, so you should still take care
+to regularly upgrade any other dependencies in addition to Solidus. There are tools that can help
+you stay on top of version updates, such as [Dependabot](https://dependabot.com/), but in general
+the best tool you can employ is a solid suite of automated unit and integration tests that verify
+the behavior of your application after an upgrade.
+
+## Dealing with deprecations
+
+:::caution
+
+While it can be tempting to leave calls to deprecated APIs in place and wait for their removal
+before fixing them, this approach will come back to haunt you when you upgrade to a new major
+version and find that you need to update dozens of method calls that don't work anymore.
+
+:::
+
+It's particularly important to understand how to handle deprecations correctly. The recommended
+approach is to fix deprecation warnings as soon as they arise. When you upgrade Solidus, run your
+entire test suite and copy all deprecation warnings to a separate file. In Bash, you can easily do
+it by running this command from your app's root:
+
+```bash
+$ bundle exec rspec 2>deprecations.txt
+```
+
+This will save all Solidus deprecations to the `deprecations.txt` file. You will find that this file
+contains a lot of duplicates, but you may remove them with another command:
+
+```bash
+$ cat deprecations.txt | sort | uniq
+```
+
+This will output a de-duplicated list of deprecations in your code. Once you have this, just go
+through the deprecations one by one and fix them. Then run your test suite again to ensure your app
+doesn't contain any deprecated code.
+
+:::info
+
+In some cases, deprecated code may come from Solidus extensions and not your own app, meaning you
+can't fix the deprecation yourself. When this happens, you can open an issue in the extension's
+repository to let the maintainer know that they need to update their extension.
+
+:::
+
+## Updating the database
+
+Especially during major version upgrades, when you run the `bin/rails g
+solidus:update` task, new database migrations updating the Solidus database
+schema might be copied to your host application.
+
+It would be best if you never ran them blindly. Instead, look into them in
+detail, understand them, check if they could cause an issue with your production
+data, and, if needed, modify them to match your specific situation. Depending on
+your data volume, usage, and schema modifications on your side, you might prefer
+to ditch them or perform a longer but safer update journey. The documentation
+for the [strong_migrations](https://github.com/ankane/strong_migrations) gem
+contains helpful tips you can check.
+
+## Updating preferences
+
+There're some aspects of Solidus behavior that you can tweak on your application. For instance,
+guest checkouts are allowed by default, but you can change it on the `spree.rb` initializer:
+
+```ruby
+Spree.config do |config|
+ config.allow_guest_checkout = false
+end
+```
+
+Some defaults may change between Solidus versions. Those changes might break the expectations of
+your application behavior. For that reason, Solidus comes with the `Spree.load_defaults(version)`
+method to ensure that the implicit defaults \(those you're not overriding on `spree.rb`\) stay the
+same after an upgrade. This method is called on top of the `spree.rb` file. E.g.:
+
+```ruby
+Spree.load_defaults('3.0')
+Spree.config do |config|
+ # ...
+end
+```
+
+> If you upgrade from Solidus 3.0 to Solidus 3.1, you won't have the `Spree.load_defaults` call in
+> your initializer yet. Please, ensure that you add it before performing the upgrade!
+
+After an upgrade, you should update the version passed to the `Spree.load_defaults` method. However,
+before that, you need to check which defaults have changed and decide what to do with them. You have
+a couple of options:
+
+* Adapt your application code to integrate the new default.
+* Explicitly use the old default in `spree.rb` if you're sure you still want to use it.
+* Do nothing if the new default doesn't entail a change from your side.
+
+Recall that Solidus comes with the `solidus:update` generator to help you with
+the process:
+
+```bash
+bin/rails g solidus:update
+```
+
+This command will generate a new initializer called `new_solidus_defaults.rb`. All the defaults that
+have changed in the latest version are displayed, each in a commented out line. That allows you to
+act on each individual new preference performing one of the options given above. If you want to
+integrate a new preference, you only need to uncomment its line. When you're done with all the
+defaults, you can flip the version used in `spree.rb`'s `Spree.load_defaults` call and remove
+the `new_solidus_defaults.rb` file altogether.
+
+Internally, `Spree.load_defaults` is a shortcut that forwards the same method to the configuration
+object for every available Solidus component: core, backend & API. You'll see
+that the `#load_defaults` call, with the previous version as an argument, is
+disassembled into the individual components in `new_solidus_defaults.rb`. It
+gives you more fine-grained control as you can flag as done an individual
+component by updating its `#load_defaults` version when you're done with it.
+
+For instance, say that you're upgrading from Solidus 3.0 to 3.1, and three made-up defaults have
+changed:
+
+* Core's `:core_pref_one` was `false` and now is `true`.
+* Core's `:core_pref_two` was `false` and now is `true`.
+* Backend's `:backend_pref` was `'this'` and now is `'that'`.
+
+The generated `new_solidus_defaults.rb` file would look something like the following:
+
+```ruby
+Spree.config do |config|
+ config.load_defaults('3.0')
+ # config.core_pref_one = true
+ # config.core_pref_two = true
+end
+Spree::Backend::Config do |config|
+ config.load_defaults('3.0')
+ # config.backend_pref = 'that'
+end
+```
+
+A sensible approach you could take to leave your application up to date could be:
+
+* Adapt your app to take into account that `core_pref_one` is `true` and uncomment its line in the
+initializer.
+* Adapt your app to take into account that `core_pref_two` is `true`, remove lines
+for `core_pref_one` & `core_pref_two`, and update the `config.load_default` call within
+the `Spree.config` block to take `'3.1'` as parameter.
+* Adapt your app to take into account that `backend_pref` is `'that'`. Remove
+the `new_solidus_defaults.rb` initializer and bump the `Spree.load_defaults` call in `spree.rb`
+to `'3.1'`.
+
diff --git a/versioned_docs/version-4.2/upgrading-solidus/v3.3.mdx b/versioned_docs/version-4.2/upgrading-solidus/v3.3.mdx
new file mode 100644
index 0000000..5c4ff7e
--- /dev/null
+++ b/versioned_docs/version-4.2/upgrading-solidus/v3.3.mdx
@@ -0,0 +1,157 @@
+---
+sidebar_position: -3.3
+---
+
+import PRLink from '@site/src/theme/PRLink';
+import MinimalRequirements from '@site/src/theme/MinimalRequirements';
+
+# Solidus v3.3 (2022-01-24)
+
+
+
+New year, new Solidus release! This time, we acknowledge our [new policy around Ruby & Rails support](https://solidus.io/release_policy/). It also comes with new features, bug fixes, as well as some deprecations that will be removed in the next major release. Take the time to upgrade. We've committed with the community to release more often, so keeping up to date will help you when Solidus v4.0 is out, which will happen sooner than later.
+
+Before getting hands-on, please review our generic [upgrade guides](/upgrading-solidus/index.mdx). You can also check the complete [Changelog](https://github.com/solidusio/solidus/blob/v3.3/CHANGELOG.md) in our repository.
+
+## Added support for Ruby v3.2
+
+Solidus v3.3 comes with support for the recently released v3.2 of Ruby. You can safely upgrade to it without Solidus getting in your way.
+
+## Removed support for Ruby v2.5
+
+With v3.3, Solidus performs a long-due cleanup of its codebase, removing support for EOL's Ruby versions v2.5 & v2.6. If you're on Solidus v3.2 and you want to upgrade to v3.3:
+
+- Update your Solidus v3.2 store to Ruby v2.6.
+- Update your Solidus v3.2 store to Ruby v2.7.
+- Update your Solidus v3.2 store to v3.3.
+
+## Removed support for Ruby v2.6
+
+Solidus v3.3 no longer supports Ruby v2.6, which reached the EOL status. You'll need to:
+
+- Update your Solidus v3.2 store to Ruby v2.7.
+- Update your Solidus v3.2 store to v3.3.
+
+## Removed support for Rails v5.2
+
+Rails v5.2 (EOL) support has been removed from Solidus v3.3:
+
+- Update your Solidus v3.2 store to Rails v6.0.
+- Update your Solidus v3.2 store to v3.3.
+
+## Support for the new Colorado delivery fee
+
+We've updated our taxation system to support the new [Colorado retail delivery fee](https://tax.colorado.gov/retail-delivery-fee).
+Please, check [the corresponding guide page][how-to-setup-colorado-delivery-fee] to setup
+this new tax on your store.
+
+[how-to-setup-colorado-delivery-fee]: ../how-tos/how-to-setup-colorado-delivery-fee
+
+## Deprecate discriminatory wording for Ransack methods
+
+We're deprecating `.whitelisted_ransackable_attributes` & `.whitelisted_ransackable_associations` in favor of `.allowed_ransackable_attributes` & `.allowed_ransackable_associations`, respectively. The old terms won't be available on the next major.
+
+## Version upgraded for packaged underscore.js
+
+We've upgraded the packaged version of [underscore.js](https://underscorejs.org/) from v1.8.3 to v1.13.6. In case you were using it, check compatibility.
+
+## Deprecated #mails_from preference
+
+We've deprecated the `#mails_from` preference, as it wasn't used in the Solidus codebase anymore. Make sure you aren't using it for anything else. Otherwise, the expected way to handle the default `From:` field for transactional emails is the `Spree::Store#mail_from_address` attribute.
+
+## Store static preferences using string class names
+
+It's no longer necessary to wrap the definition of static preferences within a `.to_prepare` block to avoid [referencing a constant on an initializer](https://guides.rubyonrails.org/v6.1.0/autoloading_and_reloading_constants.html#autoloading-when-the-application-boots).
+
+You can change code like:
+
+```ruby title="config/initializers/spree.rb"
+Rails.application.config.to_prepare do
+ Spree::Config.static_model_preferences.add(
+ AmazingStore::Greeter,
+ 'greeter_preferences',
+ name: ENV["GREETER_NAME"],
+ )
+end
+```
+
+with:
+
+
+```ruby title="config/initializers/spree.rb"
+Spree::Config.static_model_preferences.add(
+ 'AmazingStore::Greeter',
+ 'greeter_preferences',
+ name: ENV["GREETER_NAME"],
+)
+```
+
+## Configurable promotion adjuster
+
+You can now change what the default promotion adjuster does. Configure the `Spree::Config.promotion_adjuster_class`
+preference with a class that takes the order on initialization and responds to the `#call` method.
+
+Read [the corresponding how-to][how-to-use-a-custom-promotion-adjuster] for more information.
+
+[how-to-use-a-custom-promotion-adjuster]: ../how-tos/how-to-use-a-custom-promotion-adjuster
+
+## Configurable algorithm to prioritize store credits
+
+Before v3.3, store credits were always sorted by the priority of their type. Now you can change
+that behavior by configuring the `Spree::Config.store_credit_prioritizer_class` preference with
+a class that takes the store credits and the order on initialization and responds to the `#call`
+method.
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+ config.store_credit_prioritizer_class = 'MyStore::StoreCreditPrioritizer'
+end
+```
+
+```ruby title="app/models/my_store/store_credit_prioritizer.rb"
+module MyStore
+ class StoreCreditPrioritizer
+ def initialize(store_credits, order)
+ @store_credits = store_credits
+ @order = order
+ end
+
+ def call
+ # ...
+ end
+ end
+end
+```
+
+## Allow shipping category on variants
+
+It was already possible for a variant to have a different tax category than its product. Now, that's also true for the shipping category.
+
+## Default implementation for PaymentMethod#try_void
+
+When adding a new payment integration, it was required to implement the `PaymentMethod#try_void` method. However, it turned out that the logic was always very similar. We now provide a default implementation that would be enough in most situations.
+
+## Improved bogus credit card voiding
+
+Before this change, all attempts to void a payment with this testing payment method in the admin panel were successful. It's now possible to simulate a failure by trying to cancel an order with a captured payment.
+
+## Variant and product autocomplete functions flexibility with Ransack
+
+It's now possible to customize the autocompletion for variants and products on the backend via a callback returning a Ransack query. E.x.:
+
+```javascript
+$('#product-dropdown').productAutocomplete({
+ multiple: false,
+ searchCallback: (searchString) => ({
+ q: {
+ name_cont: searchString,
+ available_on_not_null: true
+ })
+ }
+})
+```
+
+## Add available in product's Ransack scopes
+
+`.available` is now available to be used as a [Ransack scope](https://activerecord-hackery.github.io/ransack/going-further/other-notes/#using-scopesclass-methods) in `Spree::Product`
diff --git a/versioned_docs/version-4.2/upgrading-solidus/v3.4.mdx b/versioned_docs/version-4.2/upgrading-solidus/v3.4.mdx
new file mode 100644
index 0000000..afbd4f2
--- /dev/null
+++ b/versioned_docs/version-4.2/upgrading-solidus/v3.4.mdx
@@ -0,0 +1,106 @@
+---
+sidebar_position: -3.4
+---
+
+import PRLink from '@site/src/theme/PRLink';
+import MinimalRequirements from '@site/src/theme/MinimalRequirements';
+
+# Solidus v3.4 (2023-04-21)
+
+
+
+This is a slimmer release, but a very important one as it'll be the last on the 3.x series. [Following our policy](https://solidus.io/release_policy/#last_minor_before_major), there won't be other changes except removing deprecated code in release v4. That means if you adapt your store to be free of warnings, you should be ready to go when v4 is out (and it's just around the corner!).
+
+Please, remember to review our generic [upgrade guides](/upgrading-solidus/index.mdx) and run the `bin/rails g solidus:update` task. You can also check the complete [Changelog](https://github.com/solidusio/solidus/blob/v3.4/CHANGELOG.md) in our repository.
+
+## New taxon and taxonomy validations
+
+New validations were added to the `Taxon` and `Taxonomy` models but are behind the new temporary `extra_taxon_validations` and `extra_taxonomy_validations` preferences.
+
+- Taxonomy must have a unique name.
+- Taxon must have a unique name under the same parent Taxon (or at root level).
+- A validation was added in Taxon to ensure Taxonomies can only have one root Taxon.
+
+Before using the new preferences, ensure you don't have any now invalid Taxons or Taxonomies on production:
+
+1. Pull the production Taxons and Taxonomies to a development or staging environment.
+2. Set the new preferences to `true`.
+3. Run `Spree::Taxon.all.select(&:invalid?)` and `Spree::Taxonomy.all.select(&:invalid?)`.
+4. If you don't have empty arrays, you'll need to manually fix these records on production (warning you may have products attached to these Taxons/Taxonomies).
+
+If you had any invalid records, take caution in case you have any code that creates Taxons or Taxonomies.
+
+On top of that, some of your tests may break if you have incorrectly built Taxons. We've also added some help to fix those factories:
+
+- Now, if you pass just a `parent` Taxon, the Taxonomy will be inferred from the `parent`.
+- As before, if you pass no `taxonomy`, a Taxonomy and its root Taxon will be made.
+- Now, the `parent` will be inferred from the given `taxonomy` or default created Taxonomy. This means, the created Taxon will always be nested (if you want a root Taxon, create a Taxonomy and get its root).
+
+## Configurable order update attributes class
+
+`Spree::OrderUpdateAttributes` class is no longer hard-coded. That will grant you flexibility if you need to store additional attributes during checkout.
+
+You need to configure your class in the new `order_update_attributes_class`. Make sure it follows the same signature as the default one:
+
+```ruby title="config/initializers/spree.rb"
+Spree.config do |config|
+ # ...
+ config.order_update_attributes_class = 'MyStore::OrderUpdateAttributes'
+end
+```
+
+```ruby title="app/models/my_store/order_update_attributes.rb"
+module MyStore
+ class OrderUpdateAttributes
+ def initialize(order, update_params, request_env:)
+ @order = order
+ @update_params = update_params
+ @request_env = request_env
+ end
+
+ def call
+ # true || false
+ end
+ end
+end
+```
+
+## Configure allowed ransackable scopes
+
+We already had allowed ransackable attributes and associations to configure [authorization on Ransack](https://activerecord-hackery.github.io/ransack/going-further/other-notes/#authorization-allowlistingdenylisting). We added the missing `allowed_ransackable_scopes` piece, allowing you to modify the defaults without overriding the `.ransack_scopes` method:
+
+```ruby
+Spree::Product.allowed_ransackable_scopes.concat([:new_scope])
+```
+
+## Improvements for the risk analysis report
+
+The payment risk analysis summary rendered for orders considered dangerous now contains information for all the associated payments instead of only considering the last one.
+
+## Easily change from frontend to admin view for an order
+
+That's a small quality of life improvement. If you're checking out an order in the storefront, you can now prepend `/admin` to the route and land in the admin panel counterpart. Very helpful during development and testing.
+
+## All-encompassing update task
+
+Before, running `bin/rails solidus:update` would only generate the initializer to [update default Solidus preferences](/upgrading-solidus/index.mdx#updating-preferences]). Now, it'll also copy the new migration files to your host application. Remember, you'll still need to check and run them by yourself.
+
+## Deprecate promotions with an `any` policy
+
+Actually, promotions with a `match_policy` of `any` were deprecated on v3.2. We forgot to add a deprecation warning, so you may only realize it now.
+
+As a reminder, if you have promotions with such a match policy, try running the following rake task:
+
+```bash
+bin/rake solidus:split_promotions_with_any_match_policy
+```
+
+That will create separate promotions for each of the rules of your promotions with `any` match policy, which should have the same outcome for customers.
+
+## Deprecation of payment offsets
+
+That's just some dangling code removal, as payment offsets (the old refund system) have not been used on Solidus for a long time. You must move entirely to the well-established refund system if you're still using them.
+
+## Deprecated `#line_item_shipment_price`
+
+The `Admin::OrdersHelper#line_item_shipment_price` method has been deprecated. You can use the equivalent `Spree::LineItem#display_amount` instead.
diff --git a/versioned_docs/version-4.2/upgrading-solidus/v4.0.mdx b/versioned_docs/version-4.2/upgrading-solidus/v4.0.mdx
new file mode 100644
index 0000000..94f67a1
--- /dev/null
+++ b/versioned_docs/version-4.2/upgrading-solidus/v4.0.mdx
@@ -0,0 +1,106 @@
+---
+sidebar_position: -4.0
+---
+
+import PRLink from '@site/src/theme/PRLink';
+import MinimalRequirements from '@site/src/theme/MinimalRequirements';
+
+# Solidus v4.0 (2023-05-08)
+
+
+
+Finally, we released Solidus 4.0! π
+
+This release marks an important milestone for Solidus. New installations are now fully supporting the starter frontend,
+which works out of the box with the most common payment gateway integrations: PayPal, Stripe and Braintree.
+
+:::info
+
+Please, when upgrading, remember to review our generic [upgrade guides](/upgrading-solidus/index.mdx) and run:
+
+```
+bin/rails g solidus:update
+```
+
+You can also check the complete [Changelog](https://github.com/solidusio/solidus/blob/v4.0/CHANGELOG.md) in our repository.
+
+:::
+
+Following our release policy, we try to make any major release as similar as possible to the last version of the previous series (v3.4 in this case).
+
+As with any major release though, there are some breaking changes, which we are going to cover here.
+The majority of them have been part of a deprecation cycle, but there are things you should check and possibly fix
+before or during the upgrade.
+
+Here's a list of things you should take care of.
+
+## Changes/Removals without deprecation
+
+## Removed support for Rails < 7 & Ruby < 3
+
+To have Solidus ready for what comes next, we need everyone to use at least Rails 7. That's because we have a plan to
+switch the Admin Panel to the Hotwire stack, which is now the default in the Rails world.
+
+**Before upgrading to Solidus v4.0, please be sure you are on Rails 7.0.** Here's a link to the Rails documentation about [upgrading from one version to the other][upgrading-rails].
+
+Please, keep in mind that Solidus v3.4 support both Rails 6.0, 6.1 and 7.0, which gives everyone the possibility to upgrade
+Rails iteratively before the Solidus major upgrade.
+
+[upgrading-rails]: https://guides.rubyonrails.org/upgrading_ruby_on_rails.html
+
+Along with that, we also removed support for older Ruby versions: **you need at least Ruby 3.0 to use Solidus v4.0**.
+
+## Make option value to variant association unique
+
+The same option value cannot be associated anymore with a given variant multiple times. It's very likely
+that you are not doing this, but this change introduces a new unique index on the `spree_option_values_variants` database table, which is going to fail if you have duplicate records.
+
+Please, double-check your production database before this new migration is executed. Here's an example script you can run to test if you have duplicates:
+
+```ruby
+Spree::OptionValuesVariant.select(:variant_id,:option_value_id).group(:variant_id,:option_value_id).having("count(*) > 1").size
+=> {[49, 3]=>3} # means that there are 3 Spree::OptionValuesVariant with variant_id: 49 and option_value_id: 3.
+```
+
+If the script above returns anything different from `{}`, you need to remove those duplicates.
+
+## Remove deprecated_address_id column from shipments
+
+We have not used this column since we removed all references to it in 2016. Be sure you are also
+never using it.
+
+## Remove position column from spree_taxons
+
+Apparently, taxons used to be a list in very early versions of Spree, and have then been refactored to be a nested set. Nested sets are strict supersets of lists,
+so the `position` column is entirely unnecessary.
+
+Please, be sure you are never using it, and if you do, just re-add it with a new migration.
+
+## Remove unused columns from spree_promotion_rules
+
+We are cleaning up the `spree_promotion_rules` table from useless columns:
+
+- `product_group_id` refers to something that was removed from Spree in version 1.1 (see https://github.com/spree-contrib/spree_product_groups).
+- `code` refers to nothing.
+
+## Drop unused table promotion_action_line_items
+
+There's another table with identical columns that is being referenced in the codebase, `spree_line_item_actions`.
+
+You shouldn't, but please, be sure you are not using `promotion_action_line_items`.
+
+## Changes/Removals with deprecation
+
+Most of the v4.0 removals have been deprecated first, which means that if you are on v3.4, and you addressed all the deprecation warnings you see in your logs, you should be safe.
+
+Here's a list of the PRs that remove this deprecated code. For additional details, please read them commit by commit:
+
+- [Remove solidus_frontend from the meta gem](https://github.com/solidusio/solidus/pull/5026)
+- [Remove deprecated code from Core component](https://github.com/solidusio/solidus/pull/4989)
+- [Remove deprecated code from API component](https://github.com/solidusio/solidus/pull/5020)
+- [Remove deprecated code from Backend component](https://github.com/solidusio/solidus/pull/5021)
+- [Remove deprecated Preferences](https://github.com/solidusio/solidus/pull/5022)
+- [Remove deprecated factories usage](https://github.com/solidusio/solidus/pull/5023) + [Remove deprecated spree/testing_support file](https://github.com/solidusio/solidus/pull/5063)
+- [Remove deprecated order updater promotions code](https://github.com/solidusio/solidus/pull/4890)
+- [Remove support for deprecated promo rules matching policy](https://github.com/solidusio/solidus/pull/5019)
+- [Remove support for legacy event system](https://github.com/solidusio/solidus/pull/5024)
diff --git a/versioned_docs/version-4.2/upgrading-solidus/v4.1.mdx b/versioned_docs/version-4.2/upgrading-solidus/v4.1.mdx
new file mode 100644
index 0000000..48415b6
--- /dev/null
+++ b/versioned_docs/version-4.2/upgrading-solidus/v4.1.mdx
@@ -0,0 +1,94 @@
+---
+sidebar_position: -4.1
+---
+
+import PRLink from '@site/src/theme/PRLink';
+import MinimalRequirements from '@site/src/theme/MinimalRequirements';
+
+# Solidus v4.1 (2023-06-29)
+
+
+
+Solidus v4.1 is out! π
+
+This release is relatively tiny and should be quite easy to upgrade. It starts to lay down the foundational
+work for the new Admin, which will be delivered gradually during the rest of the v4.x releases.
+
+In fact, with v4.1, stores can switch their Admin to the new color scheme, and allow operators to start
+getting used to it, reducing the impact of future changes.
+
+Let's see in detail the main things to be aware of when upgrading.
+
+:::info
+
+Please, when upgrading, remember to review our generic [upgrade guides](/upgrading-solidus/index.mdx) and run:
+
+```
+bin/rails g solidus:update
+```
+
+You can also check the complete [Changelog](https://github.com/solidusio/solidus/blob/v4.1/CHANGELOG.md) in our repository.
+
+:::
+
+## Add a new admin theme
+
+https://github.com/solidusio/solidus/pull/5092 is the first step towards the new Admin. It introduces a new
+theme, which is a set of colors and styles that can be applied to the Admin.
+
+To switch to the new theme, you can set the following preference:
+
+```
+Spree::Backend::Config.theme = 'solidus_admin'
+```
+
+## Allow changing the order recalculator
+
+The `order.recalculate` and `order.recalculator` are introduced as aliases to the now deprecated
+`order.update` and `order.updater` in order to clarify the intention of these methods.
+
+This change also adds a preference to change the order recalculator (also known as order updater).
+If your store has any customization of the order recalculator, you can evaluate creating
+your own class that inherits from the current OrderUpdater class and set the new preference as:
+
+```
+Spree::Config.order_recalculator_class = 'YourCustomOrderRecalucator'
+```
+
+## Remove respond_to :html from Spree::BaseController
+
+When we removed the gem `responders` as a core dependency (it still is for backend and api),
+we forgot to move the `respond_to :html` statement from `Spree::BaseController`, which is part
+of core.
+
+This change cleans up this discrepancy, but when updating please be aware that your custom code
+could be broken if these statements are true together:
+
+- You have a controller inheriting from Spree::BaseController.
+- You are making use of responders in that controller.
+- You are also using the `:html` format.
+- And you are not explicitly adding the `:html` format.
+
+If that's the case, you can just add `respond_to :html` in that controller to fix it.
+
+## Allow lambda in menu item :match_path option and URL
+
+This change allows setting a path for a menu item via a lambda executed at runtime. If engines other than Solidus
+would like their route in the main menu, this is how they can do it dynamically without having to hard-code the path.
+Similarly, we allow the `match_path` option for Menu Items to be a callable taking a request.
+
+You can now do:
+
+```ruby
+Spree::Backend::Config.configure do |config|
+ config.menu_items << config.class::MenuItem.new(
+ [:admin, :my_custom_controller],
+ 'my_custom_controller',
+ url: ->(_request) { # do something to generate the menu item's URL },
+ match_path: ->(_request) { # do something to match the path and highlight the menu item }
+ )
+end
+```
+
+If you have any customization for setting complex `path`s and `match_path`s in your Admin menu items,
+it's time to remove them and switch to this method.
diff --git a/versioned_docs/version-4.2/upgrading-solidus/v4.2.mdx b/versioned_docs/version-4.2/upgrading-solidus/v4.2.mdx
new file mode 100644
index 0000000..2a325c2
--- /dev/null
+++ b/versioned_docs/version-4.2/upgrading-solidus/v4.2.mdx
@@ -0,0 +1,69 @@
+---
+sidebar_position: -4.2
+---
+
+import PRLink from '@site/src/theme/PRLink';
+import MinimalRequirements from '@site/src/theme/MinimalRequirements';
+
+# Solidus v4.2 (2023-06-29)
+
+
+
+Solidus v4.2 is out! π
+
+This release brings the usual load of bug fixes and improvements, but also the new branding and some new features that we are happy to share with you.
+
+## Stop using partials and deface to customize the admin menu
+
+This change removes the need to use of partials and deface to customize the Admin menu. Instead, we now have a new `#children` attribute added to the `MenuItem` class in which second level menus are defined.
+
+Doing this brought a significant benefits in terms of simplicity and customization. Parent menu items can now ask the children if any of them is active without the need of duplicating the logic in the parent.
+
+Since the list of menu items is a simple array, it can be easily manipulated by other extensions, and in the host application. Finding a menu item is as simple as checking its `label`, e.g. `menu_items.find { _1.label == :products }`.
+
+```ruby
+Spree::Backend::Config.configure do |config|
+ # Let solidus know that we don't want to use the partials for second level
+ # menu items anymore. By enabling this whenver a menu item has both children
+ # and a partial, the partial will be ignored.
+ config.prefer_menu_item_partials = false
+
+ config.menu_items
+ .find { _1.label == :products }
+ .children << MenuItem.new(
+ label: :collections,
+ condition: -> { can? :admin, MyStore::ProductCollection },
+ url: '/product_collections'
+ )
+end
+```
+
+## Updated sidebar from the upcoming `SolidusAdmin`
+
+A new sidebar is also available that matches the style of the one in `SolidusAmin`, the new admin interface that is currently under development.
+
+The new sidebar is best experienced with the matching `solidus_admin` theme.
+
+```ruby
+Spree::Backend::Config.configure do |config|
+ config.theme = 'solidus_admin'
+ config.admin_updated_navbar = true
+end
+```
+
+We hope you like it!
+
+## Add support for the `SolidusAdmin` preview
+
+The new `SolidusAdmin` is still under development, but you can already preview it in your application.
+
+```shell
+bundle add solidus_admin
+bin/rails g solidus_admin:install
+```
+
+At the time of this writing it only covers product and order listing, and partial editing of products. We are working hard to bring more features to it, and we are looking forward to your feedback.
+
+Permission checks and authentication is shared with the existing admin interface, so you can use the same user to access both. A toggle is available at the bottom of the navigation menu to switch between the two interfaces.
+
+The new admin dashboard is mounted as an independent engine, and by default it can be disabled with a cookie, but that can be customized in the host application, e.g. to enable it only for a select number of users.
diff --git a/versioned_sidebars/version-4.2-sidebars.json b/versioned_sidebars/version-4.2-sidebars.json
new file mode 100644
index 0000000..2782dc0
--- /dev/null
+++ b/versioned_sidebars/version-4.2-sidebars.json
@@ -0,0 +1,8 @@
+{
+ "sidebar": [
+ {
+ "type": "autogenerated",
+ "dirName": "."
+ }
+ ]
+}
diff --git a/versions.json b/versions.json
index 0e4392d..b5beed0 100644
--- a/versions.json
+++ b/versions.json
@@ -1,4 +1,5 @@
[
+ "4.2",
"4.1",
"4.0",
"3.4",