Announcing Vendure v0.18.0
June 07, 2023
Video sections:
Tax handling overhaul
Taxes are complicated. Designing a system which can accommodate the various tax systems of the world, and their interaction with all possible product types and discount combinations gets really complex.
In GitHub issue #573 I set out to solve an incorrect tax calculation when applying a promotion discount. This turned out to be the entrance to a surprisingly deep rabbit hole. The more I researched, the more shortcomings I found with the existing approach. I also better understood the full range of cases that a correct tax system must be able to handle. Furthermore, I discovered that many of the well-known e-commerce frameworks don't get this right!
I decided to do a deep-dive into the way Vendure handles taxes and attempt to get the foundations correct. This effort touched on many parts of the framework and unfortunately means that there are more breaking changes than usual. But the tradeoff is that now I'm quite confident that we have a very solid basis to work with, allowing us to correctly handle a wide range of tax requirements.
Standardized API
The GraphQL API had inconsistent naming of prices - on the one hand we had shipping, shippingWithTax
yet also subTotalBeforeTax, subTotal
. This release now standardizes the naming of all taxable money fields to the format <price>, <price>WithTax
.
For example:
query {
activeOrder {
...on Order {
lines {
linePrice
linePriceWithTax
}
subTotal
subTotalWithTax
shipping
shippingWithTax
total
totalWithTax
}
}
}
Correct handling of order-level discounts
Previously, Order-level discounts (e.g. "10% off order") were handled by adding an adjustment to the Order which reduced the total by the desired amount. The problem with this approach is that it is incorrect from a taxation point-of-view. Taxes apply to the goods ordered (the OrderItems), so reducing the Order total should also reduce the amount of tax being applied.
The problem can be illustrated with an example: A customer places an Order with 2 items - T-Shirt ($10) and Shoes ($40). They apply a coupon code which gives a 50% discount on the whole Order. How much tax should be paid on the items now? Furthermore, if the customer later returns the T-Shirt, how much do we refund?
The way this should be done is to distribute the discount across the entire Order by proportionally reducing the taxable price of each OrderItem in the Order. This is known as "pro-rating" and is how the problem is handled in frameworks such as Magento or Sylius. This approach has now been implemented in Vendure, meaning that in the example above, the pro-rated price of the T-Shirt is now $5 (the $25 discount has been distributed across the 2 items, with the T-Shirt making up 1/5 of the overall Order total, thus receiving 1/5 of the total discount).
Support for external tax APIs
Since tax can be so complex, many companies choose to use a service such as Avalara or TaxJar to handle the calculation of taxes on orders. Previously there was no easy way to integrate such services with Vendure, since all tax calculations assumed the use of the built-in TaxRate system.
With this release you can now make asynchronous lookups for tax rates via the new [TaxLineCalculationStrategy]({{< relref "tax-line-calculation-strategy" >}}). You can read more about the development of this feature in GitHub issue #307
Multiple taxes per OrderItem
In some situations, more than one tax may be applicable to a given item. For example, in the USA, an item may be subject to both state-level and city-level sales tax. Such scenarios are now supported since the TaxLineCalculationStrategy returns an array of TaxLines.
Read the new Taxes guide
Dashboard widgets
The Admin UI dashboard now supports "widgets" - self-contained components exposing useful information such as recent orders, sales totals, charts, etc.
Currently, we're just shipping a few simple widgets, but you can start building your own now by following the new Dashboard Widgets guide.
Widgets are lazily-loaded, which means that any required JavaScript (such as a 3rd-party charting library) is only loaded if the widgets is displayed. Individual administrators can also customize the layout of their dashboards, which will be persisted to the browser's localStorage. Widgets also support permissions, which means they can be restricted only to Administrators who have the specified permissions.
Automated Fulfillment creation
Previously, when creating a Fulfillment for an Order, the details (method, tracking code) had to be filled out manually by the administrator. This prevented automation such as the use of an external shipping API to generate tracking codes and even things like shipping labels.
With this release we introduce the concept of FulfillmentHandlers, which contain custom code that gets invoked whenever a Fulfillment is created. This code can perform tasks such as calling external APIs, and then return the data required to generate the Fulfillment.
ShippingMethods now have an associated fulfillmentHandlerCode
property, which is used by the Admin UI to decide which FulfillmentHandler to use when creating a Fulfillment.
By default, we a manual handler which reproduces the existing behaviour.
To read more about the development of this feature, see GitHub issue #529.
Order modification
Administrators are now able to modify existing orders. This allows them to add new items, alter quantities, add surcharges and edit the shipping and billing addresses.
Modifying an Order is done by transitioning to the new Modifying
state, at which point the new [modifyOrder
mutation]({{< relref "/docs/graphql-api/admin/mutations" >}}#modifyorder) may be used.
Modifications will often change the price of the Order, meaning that either an additional Payment is required (if the price increases) or a Refund (if the price reduces). Currently both of these must be handled manually (i.e. Vendure will create a Payment or a Refund, but the actual transaction must be performed externally, such as in your payment provider's dashboard). In future we'll be able to integrate these operations more seamlessly. To read more about the development of this feature, see GitHub issue #314.
Note:
Order modification turned out to be an extremely complex feature to implement (over 50 hours' work!), and there are sure to be edge-cases which may not behave as expected, particularly to do with how Promotions behave on modified Orders. We advise you to use this feature with caution for now until these edge-cases have been ironed out.
Other notable improvements
ChannelAware ProductVariants: The
ChannelAware
interface is used to associate entities with one or more Channels. We're had ChannelAware Products for a while, but this release allows individual ProductVariants to be assigned to Channels too. This enables things like excluding particular variants from specific channels. Thanks to Hendrik Depauw for putting in the considerable work to implement this!Order surcharges: Order can now have surcharges added to them. A surcharge is intended to represent a non-SKU modification to the Order total. For example, some shops apply a percentage surcharge when using certain payment methods. Currently, there is no GraphQL API for adding surcharges, so they must be added via custom code in plugins (e.g. as part of a CustomOrderProcess). Read more about the development of this feature in GitHub issue #583.
Override Admin UI nav: Items in the Admin UI main navigation bar can now be overridden. This allows you to replace entire routes with your own custom implementations.
Set Admin UI baseHref: When building a custom version of the Admin UI using the
@vendure/ui-devkit
package, you can now specify abaseHref
value to allow the Admin UI to be accessible via a path other than/admin/
.Shipping promotions: It is now possible to define PromotionActions which apply specifically to the Order's shipping. The typical example is a "free shipping" promotion (which we now include by default).
Fixed order discounts: The default PromotionActions now include a "Discount order by fixed amount" action, enabling Promotions like "$5 off order".
See all changes in the v0.18.0 Changelog
BREAKING CHANGES / Migration Guide
🚧 Read this section carefully!
If you have any questions or issues with the migration, please post them to the v0.18.0 Migration thread on our GitHub Discussions forum.
For general instructions on upgrading, please see the new Updating Vendure guide.
Database migration
This release includes more database schema changes than usual. If your project is still in development and you don't need to preserve any data, it may be preferable to simply drop all tables and start again using the synchronize
option.
If you do need to preserve real data (i.e. you have Vendure in production), follow this guide carefully. There is also a visual guide at the end of the announcement video.
Create a complete backup of your database schema and data.
Generate a migration script as described in the Migrations guide.
You must now modify the generated migration script in accordance with the examples given in this Vendure 0.18.0 Migration gist. This will involve moving around the generated queries, adding some queries, and adding calls to the provided utility functions which will deal with the more complex data migrations.
Migration utility functions (copy the contents of this file into the parent directory of the
/migrations
folder)
IMPORTANT test the migration first on data you are prepared to lose to ensure that it works as expected. Do not run on production data without testing. For production data, make a full backup first!
GraphQL API changes
The following changes to the GraphQL Shop API may necessitate changes to your storefront application:
The mutations
setOrderShippingAddress
,setOrderBillingAddress
setOrderCustomFields
now return a union type which includes a newNoActiveOrderError
. Code which refers to these mutations will need to be updated to account for the union with the fragment spread syntax...on Order {...}
.Taxes on OrderItems are no longer listed under
adjustments
, they now have their own fieldtaxLines
.The
Order.shippingMethod
field has been replaced byOrder.shippingLines.shippingMethod
The
Order.subTotal
andOrder.total
fields previously referred to the tax-inclusive price, but now refer to the price without tax. The tax-inclusive prices are nowsubTotalWithTax
andtotalWithTax
respectively.The
ShippingMethod.description
field now holds the optional long-form description, and the data it previously pointed to is now in thename
field.The
Order.adjustments
andOrder.lines.adjustments
fields have been replaced by thediscounts
field.The
OrderLine.totalPrice
field has been deprecated and replaced by thelinePriceWithTax
field.
TypeScript API Changes
The
PaymentMethodHandler.createPayment()
method now takes a newamount
argument. Update any custom PaymentMethodHandlers to use account for this new parameter and use it instead oforder.total
when creating a new payment.// before createPayment: async (ctx, order, args, metadata) { const transactionAmount = order.total; // ... } // after createPayment: async (ctx, order, amount, args, metadata) { const transactionAmount = amount; // ... }
The
PriceCalculationStrategy
has been renamed toOrderItemPriceCalculationStrategy
.
Create your first commerce experience with Vendure in less than 2 minutes
Vendure is a registered trademark. Our trademark policy ensures that our brand and products are protected. Feel free to reach out if you have any questions about our trademarks.
Documentation
Newsletter
Get the latest product news and announcements delivered directly to your inbox.