Developer Advisory: Changes in order coupons line item storage

Back in October, we created a GitHub discussion regarding an issue with the storage of metadata concerning coupons applied to orders. The short version is that once a coupon is applied to an order, a coupon_data metadata item is stored for the corresponding discount order line item (needed to apply the coupon again when doing recalculations on the order when the coupon no longer exists) in the wp_woocommerce_order_itemmeta table; but that metadata item is an entire copy of the coupon object itself, which is overkill and is causing issues in sites with heavy coupon usage (the database size is growing up to unacceptable levels).

We’re glad to announce that a fix for this has been implemented and will land in WooCommerce 8.7. In this blog post, we’ll explain the changes made.

Changes to metadata storage

From now on, when a coupon is applied to an order, the corresponding line item won’t get a coupon_data metadata item. Instead, it will get a coupon_info item whose value is a string representing a JSON array with the following information:

  • Coupon id.
  • Coupon code.
  • Discount type. The default value fixed_cart will be stored as null.
  • Nominal amount of the coupon, it’s a number that represents either a fixed amount or a percent, depending on the coupon type.
  • Whether the coupon grants free shipping or not. This value is stored only if it has a value of true.

For example, a coupon with id 1234 and code 20off granting a 20% discount and free shipping will get a metadata entry with this value:

[1234,"20off","percent",20,true]

while for a coupon with the same id and code, 10bucks, granting a fixed discount of 10$/€/… and no free shipping the value will be:

[1234,"10bucks",null,10]

Migrating metadata on existing orders

Coupon metadata for existing orders won’t get automatically migrated to the new format (and meta key name). If additional coupons are added to the order, these will get coupon_info metadata entries, but existing coupon_data for other coupons previously applied to the order won’t be touched.

If the database space taken up by existing coupon_data entries is an issue in your site, you can use the new metadata migration tool available in the WooCommerce tools page (WooCommerce – Status – Tools):

Click the Start converting button and a background process that converts existing coupon_data entries to simplified coupon_info entries will be initiated. Once the process has started, the button text turns into Stop converting and, you guessed it, pressing it will stop the process (you can restart it at any time). 1000 metadata entries are converted in each iteration.

Changes in REST API

The order details REST API endpoint (/wp-json/wc/v3/orders/<order id>) also gets changes, more precisely in the objects returned inside the array keyed as coupon_lines:

  • Since the raw existing metadata for coupon line items is returned inside the meta_data key, this means that you’ll get coupon_info entries (instead of coupon_data entries) for new orders and for old orders that have got their metadata entries migrated. One of the two (but not both) will always exist for any given coupon line item.
  • Three new keys are added to the coupon line item object (outside of meta_data): discount_type (string), nominal_amount (float) and grants_free_shipping (boolean). In this way, you get basic coupon information without having to parse the metadata, be it the old coupon_data or the new coupon_info.

Here’s an example of the information returned for an order having one coupon applied and a coupon_data metadata entry in the corresponding coupon line item:

{
	"coupon_lines": [
        {
            "id": 100,
            "code": "20off",
            "discount": "80.65",
            "discount_tax": "19.36",
            "meta_data": [
                {
                    "id": 200,
                    "key": "coupon_data",
                    "value": {
                        "id": 300,
                        "code": "20off",
                        "amount": "20",
                        "status": "publish",
                        "discount_type": "percent",
                        "date_created": {
                            "date": "2021-03-29 10:54:30.000000",
                            "timezone_type": 1,
                            "timezone": "+00:00"
                        },
                        //...and a lot more!
}

And this is the same example, but with the line item having being created with (or having been migrated to) a coupon_info entry:

"coupon_lines": [
        {
            "id": 100,
            "code": "20off",
            "discount": "80.65",
            "discount_tax": "19.36",
            "discount_type": "percent",
            "nominal_amount": 20,
            "free_shipping": true
            "meta_data": [
                {
                    "id": 200,
                    "key": "coupon_info",
                    "value": "[300,\"20off\",\"percent\",20,true]",
                    "display_key": "coupon_info",
                    "display_value": "[300,\"20off\",\"percent\",20,true]"
                }
            ]
        }
    ]

Please notice the difference betwee the nominal_amount field (this tells the discount amount as defined in the coupon itself, and can be either a fixed monetary value or a percent) and the discount field (this is the actual value discounted from the order, and is always a fixed monetary value).

Also note (this can be confusing) that three id values are in play here: the id of the order line corresponding to the coupon usage (100 in the example), the id of the metadata entry created for the order line (200) and the id of the coupon itself (300).

Finally, keep in mind that as in the case of coupon_data entries, the referenced coupon could no longer exist (a site administrator might have deleted it after it was applied to orders).

How can I tell if this affects me?

This change affects you if you have custom code that makes direct use of the old coupon_data metadata entries, either by directly retrieving it from the database (wp_woocommerce_order_itemmeta table) or from the order details REST API endpoint. Your code should be changed so that it’s prepared for coupon line items that have a coupon_info entry instead of a coupon_data entry (one of the two will always be available for any given coupon line item).

2 responses to “Developer Advisory: Changes in order coupons line item storage”

  1. Pankaj Nikam Avatar
    Pankaj Nikam

    Hi @konamiman, while I welcome the new format, I still would like to see some backward compatibilty since it was a breaking change for existing APIs which depeneded on the old functionality. It broke our production workflow and it would be nice if such breaking changes are versioned so that the old calls do not break.
    Thank you.

    1. konamiman Avatar

      I’m sorry to read that. In this case providing the old functionality via versioning wouldn’t be possible because the information that was previously stored in the order meta (the full coupon object) won’t be available; it might be regenerated from the coupon data, but the coupon itself might no longer exist.

      As a rule of thumb, though, and while we try to keep these breaking changes to the minimum, we recommend not to rely on “free form” (not explicitly documented) metadata received from the WooCommerce code or REST APIs, or at least to have a backup plan in case the expected information is missing or has a different format.

Leave a Reply

Your email address will not be published. Required fields are marked *