Implement the commerce_cart_expiration (for Commerce 1.x) module solution into Commerce 2.x core

Bojanz suggestion:

it's basically an order type setting + a hook_cron() that does an entity query and adds order IDs to the queue, and a queue processor to delete the orders

Code History

Code history and changes can be found here: https://github.com/drupalcommerce/commerce/pull/639.

CommentFileSizeAuthor
#79 2853527-79-cart-expiration.patch20.86 KBbojanz
#71 2853527-71.patch18.55 KBflocondetoile
#69 interdiff-67-69.txt2.53 KBflocondetoile
#69 2853527-69.patch18.6 KBflocondetoile
#67 2853527-67.patch18.35 KBflocondetoile
#59 interdiff-2853527-58-59.txt1.43 KBflocondetoile
#59 2853527-59.patch18.35 KBflocondetoile
#58 interdiff-2853527-54-58.txt752 bytesflocondetoile
#58 2853527-58.patch18.3 KBflocondetoile
#55 interdiff-2853527-52-54.txt4.5 KBflocondetoile
#55 2853527-54.patch18.36 KBflocondetoile
#52 2853527-52.patch14.64 KBflocondetoile
#48 2853527-48.patch14.53 KBjacobbell84
#47 interdiff-2853527-44-47.txt1.2 KBtuutti
#47 2853527-47.patch14.5 KBtuutti
#44 2853527-44.patch15.15 KBmglaman
#42 2853527-42.patch15.18 KBmglaman
#38 2853527-38-cart-expiration.patch15.18 KBbojanz
#38 cart-expiration.png64.83 KBbojanz
#36 interdiff-2853527-36-34.txt2.29 KBmglaman
#36 2853527-36.patch15.4 KBmglaman
#34 2853527-34.patch15.44 KBmglaman
#34 interdiff-2853527-34-29.txt4.43 KBmglaman
#29 Edit_Physical___Drush_Site-Install.png17.22 KBmglaman
#29 interdiff-2853527-28-27.txt8.54 KBmglaman
#29 2853527-28.patch15.7 KBmglaman
#27 2853527-27.patch15.1 KBmglaman
#27 interdiff-2853527-27-25.txt3.05 KBmglaman
#25 2853527-25.patch15.06 KBmglaman
#25 interdiff-2853527-25-21.txt5.95 KBmglaman
#21 commerce-implement-cart-expiration-2853527-21.patch14.18 KBcarstenG
#15 2853527-interdiff-10-15.txt2.21 KBflocondetoile
#15 2853527-15.patch14.13 KBflocondetoile
#10 2853527-9.patch13.69 KBflocondetoile
#6 interdiff-639-hours.txt6 KBdrugan
#6 interdiff-639.txt2.35 KBdrugan
#4 cart_37_days_old.png180.53 KBdrugan
#4 setting_is _not_saved.png166.68 KBdrugan
#3 cart_expiration.diff3.94 KBrthornton
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

vasike created an issue. See original summary.

rthornton’s picture

Assigned: vasike » rthornton
rthornton’s picture

drugan’s picture

Status: Active » Needs work
FileSize
166.68 KB
180.53 KB

Good idea but it needs further work.

I have a cart 37 days old and assigned 2 days as cart expiration term but the cart was not deleted after running cron manually. Not explored this deeply but I think the reason is that the cart expiry setting is not actually saved (I expect to see 2 days here now) and might be not visible in modules/cart/src/Plugin/QueueWorker/CartExpire.php

Also, it would be useful to expire cart not only per day basis but also per hour too.

mglaman’s picture

Status: Needs work » Needs review

There's been many adjustments, here is the PR: https://github.com/drupalcommerce/commerce/pull/639. I also have added a test.

Care to give the new patch a try https://patch-diff.githubusercontent.com/raw/drupalcommerce/commerce/pul...

drugan’s picture

The https://patch-diff.githubusercontent.com/raw/drupalcommerce/commerce/pul... still didn't work for me.

After applying interdiff-639.txt it works. Also, would be it more useful to set hours instead of days for cart to expire. Please, consider interdiff-639-hours.txt because it allows to set more precise expiry setting.

What I've found additionally is that if the cart was viewed on the admin/commerce/orders/{commerce_order} or admin/commerce/orders/{commerce_order}/edit or admin/commerce/orders/carts and the interval between the last cart refresh and current time is more than order type refresh frequency setting then the cart will be refreshed and therefore not removed. The same had happened when trying to get $order->cart->value before getting $order->getChangedTime() in modules/cart/src/Plugin/QueueWorker/CartExpire.php. Actually it had blocked any cart removing.

We need to keep this mind because there might come questions like: Why my carts are not removed in the time specified? Do not touch them and they will be removed.

shabana.navas’s picture

Issue summary: View changes
mglaman’s picture

Status: Needs review » Needs work

That PR should get added as a patch here and rerolled. We also now have \Drupal\commerce\Interval that could be used to help determine the run cycle.

Issue needs triage in general.

flocondetoile’s picture

Assigned: rthornton » flocondetoile

Working on this

flocondetoile’s picture

Assigned: flocondetoile » Unassigned
Status: Needs work » Needs review
FileSize
13.69 KB

Here the patch rerolled and improved :

- Use Drupal\Commerce\Interval
- We can select now an unit for the expiration time
- #6 is fixed. This was because using orderStorage->loadMultiple() trigger the OrderRefresh and then change the order changed time to now.

mglaman’s picture

Awesome!! Quick review notes. Will test locally as well.

  1. +++ b/modules/cart/commerce_cart.module
    @@ -284,3 +335,36 @@ function commerce_cart_views_data_alter(array &$data) {
    +    if ($cart_expiration['enable'] && !empty($cart_expiration['unit']) && !empty($cart_expiration['number'])) {
    

    Should we do a !empty() on checking for enable as well?

  2. +++ b/modules/cart/commerce_cart.module
    @@ -284,3 +335,36 @@ function commerce_cart_views_data_alter(array &$data) {
    +      $current_date = new DrupalDateTime('now');
    

    It defaults to 'now'. But maybe we can keep to be explicit

  3. +++ b/modules/cart/src/Plugin/QueueWorker/CartExpire.php
    @@ -0,0 +1,94 @@
    +      // We skip the OrderRefresh process to not update the order and so
    +      // its changed time to now.
    +      $order = $this->orderStorage->loadUnchanged($id);
    

    Good catch!

flocondetoile’s picture

1. Why not. But as the enable option is 0 or 1, this condition is sufficient. I wanted to be more "explicit" with other parameters.

2. To be honest, I'm not really comfortable when working on date :-). My first idea was to provide a DrupalDatetime with a DateTimeZone based on UTC (new DrupalDateTime('now', new DatetimeZone('UTC'))). But instead, for simplicity, I removed the DateTimeZone('UTC'), and then we use the default system timezone. And I let the 'now' parameter to stay explicit. But I have some doubt if we have to use (or not) the UTC TimeZone.

mglaman’s picture

Cool, I'm fine keeping the 'now', just update to empty check, just in case.

mglaman’s picture

Issue tags: +Needs manual testing

To address #6

I do not know how often store managers go to view a cart or edit a cart. If they do so, I think that is valid that it persists longer (resets the countdown to deletion.)

The same had happened when trying to get $order->cart->value before getting $order->getChangedTime() in modules/cart/src/Plugin/QueueWorker/CartExpire.php. Actually it had blocked any cart removing.

This was fixed by using loadUnchanged.

Carts may not expire, however, if the refresh was changed to REFRESH_ALWAYS. Anytime admin/commerce/order/carts is loaded the first page will have their refresh run. But, these are edge cases I can live with. Orders should only refresh when the current user is the owner of the order while in a draft state.

See \Drupal\commerce_order\OrderRefresh::shouldRefresh.

I'm testing this manually locally.

flocondetoile’s picture

  • Patch updated for #11.1
  • Also, added a $cart_expiration_default default value to the $order_type->getThirdPartySetting() method (in hook_cron and CartExpire), otherwise the new Interval will throw an exception if the order type has not been saved to set this new third party setting.
  • Added a control on the QueueWorker if the cart expiration is still enabled when the queue worker run.
mglaman’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs manual testing

Woo! It worked. Putting to RTBC for bojanz eyes.

The last submitted patch, 3: cart_expiration.diff, failed testing. View results

The last submitted patch, 3: cart_expiration.diff, failed testing. View results

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 15: 2853527-15.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mglaman’s picture

Status: Needs work » Reviewed & tested by the community

So, DrupalCI had some issues. Tests are fine. Back to RTBC.

carstenG’s picture

mglaman’s picture

Has anyone been using this on their site to provide feedback?

mglaman’s picture

  1. +++ b/modules/cart/config/schema/commerce_cart.schema.yml
    @@ -8,6 +8,19 @@ commerce_order.commerce_order_type.*.third_party.commerce_cart:
    +        enable:
    +          type: integer
    +          label: 'Cart expiration enabled'
    

    This should be changed to be a boolean, I feel like.

  2. +++ b/modules/cart/config/schema/commerce_cart.schema.yml
    @@ -8,6 +8,19 @@ commerce_order.commerce_order_type.*.third_party.commerce_cart:
    +        number:
    +          type: integer
    +          label: 'Cart expiration time'
    

    Wondering if this should be something different

    EDIT: that's what we call it in Interval.

mglaman’s picture

Status: Reviewed & tested by the community » Needs review
mglaman’s picture

Here is an updated patch. enable is now boolean in the schema and updated the test.

mglaman’s picture

Status: Needs review » Needs work
  1. +++ b/modules/cart/commerce_cart.module
    @@ -255,3 +306,41 @@ function commerce_cart_views_data_alter(array &$data) {
    +  $queue = \Drupal::queue('cart_expirations');
    
    +++ b/modules/cart/src/Plugin/QueueWorker/CartExpire.php
    @@ -0,0 +1,104 @@
    + *  id = "cart_expirations",
    

    "commerce_cart_expiration"

  2. +++ b/modules/cart/src/Plugin/QueueWorker/CartExpire.php
    @@ -0,0 +1,104 @@
    + * Removes an expired Cart.
    

    Rework, lower case C.

There's other fixes to go as well.

mglaman’s picture

New patch. The following remains.

  1. +++ b/modules/cart/commerce_cart.module
    @@ -210,6 +216,45 @@ function commerce_cart_form_commerce_order_type_form_alter(array &$form, FormSta
    +  $form['commerce_cart']['cart_expiration_unit'] = [
    ...
    +  $form['commerce_cart']['cart_expiration_number'] = [
    

    Per recurring, the interval UX is to have number first and then the unit. And number should just be "interval"

    See https://cgit.drupalcode.org/commerce_recurring/tree/src/Plugin/Commerce/...

  2. +++ b/modules/cart/commerce_cart.module
    @@ -210,6 +216,45 @@ function commerce_cart_form_commerce_order_type_form_alter(array &$form, FormSta
    +    '#title' => t('Number of time unit until cart expires'),
    

    Fix wording

  3. +++ b/modules/cart/config/schema/commerce_cart.schema.yml
    @@ -8,6 +8,19 @@ commerce_order.commerce_order_type.*.third_party.commerce_cart:
    +        enable:
    +          type: boolean
    +          label: 'Cart expiration enabled'
    

    Remove enable. If it's enabled we save a valid number value. If it is not enabled, we save 0 as the number value.

flocondetoile’s picture

Just deploy patch #27 on production. Everything is working fine. I will report in a few day once the big queue of abandoned carts will be purged.

mglaman’s picture

Status: Needs work » Needs review
FileSize
15.7 KB
8.54 KB
17.22 KB

Fixes the UI items and removes enabled.

bojanz’s picture

Status: Needs review » Needs work
+  $cart_expiration_default = ['unit' => 'day', 'number' => 0];

Why don't we provide a reasonable default, such as 30 days?

+  $form['commerce_cart']['cart_expiration_interval']['cart_expiration_number'] = [
+    '#type' => 'number',
+    '#title' => t('Interval'),
+    '#default_value' => $cart_expiration['number'],
+    '#required' => TRUE,
+    '#min' => 0,
+  ];

Why would we allow a minimum of zero? Shouldn't #min be 1?

+    '#options' => [
+      'hour' => 'Hour',
+      'day' => 'Day',
+      'month' => 'Month',
+    ],

Missing t().

+    $cart_expiration = $order_type->getThirdPartySetting('commerce_cart', 'cart_expiration', [
+      'unit' => 'day',
+      'number' => 0,
+    ]);
+
+    if (!empty($cart_expiration['unit']) && !empty($cart_expiration['number'])) {

Wouldn't it be shorter to provide no default and then do if (!empty($cart_expiration))
Same with the default in CartExpiration.

+      $ids = $order_storage->getQuery()
+        ->condition('type', $order_type->id())
+        ->condition('cart', TRUE)
+        ->condition('changed', $expiration, '<')
+        ->range(0, 50)
+        ->execute();

Let's use "<="

mglaman’s picture

Why don't we provide a reasonable default, such as 30 days?

What if someone wants to disable cart expiration? By removing enabled we would always have it be configured.

Why would we allow a minimum of zero? Shouldn't #min be 1?

Makes sense -- we can save it as 0 if disabled and if empty/0 show 30.

Wouldn't it be shorter to provide no default and then do if (!empty($cart_expiration))
Same with the default in CartExpiration.

Yeah.

----

Also, I wonder if this should exclude orders attached to users. Right now it purges anonymous and authenticated carts. If we turn it on by default, then I definitely think we should. It's probably a good idea anyways to preserve authenticated user carts. Amazon does.

flocondetoile’s picture

And what about a checkbox "Delete only anonymous cart" checked by default ?

mglaman’s picture

Actually, I don't know if we need to worry about authenticated users or not. If the expiration threshold is high enough, their cart will be refreshed whenever they come back (a day, a week, etc) later. This prevents it from being purged.

mglaman’s picture

Status: Needs work » Needs review
FileSize
4.43 KB
15.44 KB

Here is an updated patch per bojanz review. Didn't concern about authenticated users, given thoughts in #33

mglaman’s picture

Title: Implement Commerce Cart Expiration » Implement the ability to expire and delete old carts
Category: Task » Feature request

While we're at it: this isn't a task, it's a feature request.

mglaman’s picture

Clean up default values in the form.

bojanz’s picture

Assigned: Unassigned » bojanz
+    $cart_expiration = [
+      'unit' => $settings['cart_expiration_interval']['cart_expiration_unit'],
+      'number' => $settings['cart_expiration_interval']['cart_expiration_number'],
+    ];

This shows clearly that the form structure is odd, cart_expiration_interval instead of cart_expiration, and the prefixes on unit and number.

The file for CartExpirationTest is called CartExpirationsTest.

I'll clean that up on commit.

bojanz’s picture

Status: Needs review » Needs work
FileSize
64.83 KB
15.18 KB

CartExpirationTest doesn't have test coverage for:
1) A deleted order doesn't prevent others from being deleted
2) Cart expiration can be disabled, and the queue worker will respect that and skip deletion
3 Carts that no longer qualify for deletion are skipped.

So no commit tonight. Attaching a patch with the #37 fixes and other nits.

Ryan suggested that we need a more direct phrase for the checkbox. Attaching a screenshot of the current version. Suggestions welcome.

bojanz’s picture

Assigned: bojanz » Unassigned

Unassigning.

John Pitcairn’s picture

I'd say just "Delete abandoned carts" for the checkbox, use form #state to reveal the interval field if checked, and label that "Delete after" or such.

mglaman’s picture

Assigned: Unassigned » mglaman

Working on test coverage

1) A deleted order doesn't prevent others from being deleted
2) Cart expiration can be disabled, and the queue worker will respect that and skip deletion
3 Carts that no longer qualify for deletion are skipped.

mglaman’s picture

Assigned: mglaman » Unassigned
FileSize
15.18 KB

Here are the updated tests. Now we just need to bikeshed the label.

> Delete abandoned carts

Sounds great to me.

bojanz’s picture

Agreed

mglaman’s picture

Status: Needs work » Needs review
FileSize
15.15 KB

Changed to Delete abandoned carts.

bojanz’s picture

@mglaman
The tests in #38 and #44 appear to be identical, don't see any new test coverage.

mglaman’s picture

😭😭😭 I realized my work was behind yours, so I did a branch and -3way merge of the patch and apparently lost my tests. I'll add them back in tomorrow.

tuutti’s picture

Looks like there's a trait that contains indentical installCommerceCart() function.

Is this still missing tests?

jacobbell84’s picture

It looks like 8.x-2.10 and above breaks this patch. I did some digging when I couldn't get it to work and it appears to be caused by the implementation from this issue (https://www.drupal.org/project/commerce/issues/2499645). Since the cron job is running the task under the anonymous account the QueryAccessSubscriber is trying to lock it down to only its carts, instead of allowing the entire order system to be queried.

It doesn't seem like there's any way to differentiate the cron job from a standard anonymous user, so the only fix I can think of is to disable the access checks for the initial abandoned carts entity query.

Status: Needs review » Needs work

The last submitted patch, 48: 2853527-48.patch, failed testing. View results

geek-merlin’s picture

I guess the failing test also needs accessCheck like

$orders = $this->orderStorage->getQuery()->accessCheck(FALSE)->execute();
flocondetoile’s picture

Issue tags: +Needs reroll

Patch doesn't apply anymore on 2.12

flocondetoile’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
14.64 KB

Simple reroll from #48 with the missing ->accessCheck(FALSE) added on the test's queries.

Still missing tests #38 about
1) A deleted order doesn't prevent others from being deleted
2) Cart expiration can be disabled, and the queue worker will respect that and skip deletion
3 Carts that no longer qualify for deletion are skipped.

Working on this.

flocondetoile’s picture

Assigned: Unassigned » flocondetoile

Status: Needs review » Needs work

The last submitted patch, 52: 2853527-52.patch, failed testing. View results

flocondetoile’s picture

Assigned: flocondetoile » Unassigned
Status: Needs work » Needs review
FileSize
18.36 KB
4.5 KB

Missing tests mentionned in #38 added

1) A deleted order doesn't prevent others from being deleted
2) Cart expiration can be disabled, and the queue worker will respect that and skip deletion
3 Carts that no longer qualify for deletion are skipped.

The last submitted patch, 52: 2853527-52.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 55: 2853527-54.patch, failed testing. View results

flocondetoile’s picture

Status: Needs work » Needs review
FileSize
18.3 KB
752 bytes

Cart expiration was not well disabled for the fail.

flocondetoile’s picture

Fixing the test (I have recreated the cart1 instead of the cart2 which was previously deleted) for the latest test : A deleted order doesn't prevent others from being deleted.

But this shouldn't pass. I guess we may need a Try / Catch in the QueueWorker.

Status: Needs review » Needs work

The last submitted patch, 59: 2853527-59.patch, failed testing. View results

flocondetoile’s picture

Status: Needs work » Needs review

Looks like this is unrelated fails (because we test now again Core 8.7 ?). Switching to need review.

mglaman’s picture

8.7.x broke a lot of kernel tests.

drugan’s picture

I've tested the #59 on my local install with 8.6 and the CartExpirationTest is green.

flocondetoile’s picture

Once tests fixed against 8.7 #3038493: Tests are failing on Drupal 8.7, I will relaunch it.

Status: Needs review » Needs work

The last submitted patch, 59: 2853527-59.patch, failed testing. View results

flocondetoile’s picture

Test relaunched but it failed because certainly of a missing $this->installEntitySchema() for an entity type (in reference to #3038493: Tests are failing on Drupal 8.7).

flocondetoile’s picture

Status: Needs work » Needs review
FileSize
18.35 KB

Same patch than #59. Local tests are green. To see what the bot says about it.

Status: Needs review » Needs work

The last submitted patch, 67: 2853527-67.patch, failed testing. View results

flocondetoile’s picture

Status: Needs work » Needs review
FileSize
18.6 KB
2.53 KB
flocondetoile’s picture

Missing test were added. And tests are again green :-). Looks like RTBC ?

flocondetoile’s picture

reroll against 2.13

smccabe’s picture

Status: Needs review » Reviewed & tested by the community

Some small nitpicks, I think this patch is essentially RTBC, depending what is though of these small questions. Functionality worked as expected, tests look thorough and UI looked correct. I think this qualifies for bojanz review.

  1. +++ b/modules/cart/tests/src/Kernel/CartExpirationTest.php
    @@ -0,0 +1,269 @@
    +    // Setting the `changed` attribute doesn't work in save. Manually change.
    

    If changed doesn't work on save, why are we still passing it into the create/save code above?

  2. +++ b/modules/cart/tests/src/Kernel/CartExpirationTest.php
    @@ -0,0 +1,269 @@
    +    $this->assertNull($this->reloadEntity($cart2));
    +
    +
    +    // A deleted order doesn't prevent others from being deleted.
    

    Extra blank line

Should we also name all 5 test carts and test more explicitly for the ids that should or shouldn't exist, instead of just checking the number of remaining carts? I don't see any flaws currently, but it seems like being explicit might make it a little more durable in the future.

alexandersluiter’s picture

What if instead of only expiry, it was a way to react on stale carts and allow custom actions via a triggered event and an event subscriber on the other end of it.

There could be:

  • Some default event subscriber that delete old/stale carts at a configurable interval.
  • Other generic, but identifiable events that fire at configurable intervals.

This could serve multiple purposes. It would clean up old/stale carts as is the point of this issue. It would also serve and as a loose API for developers to fire off marketing related events based on the age of a cart or order.

From a UI standpoint, I'd imagine the "deleter" would be a simple checkbox and number input. Underneath, it would be a simple event trigger and subscriber.

--checkbox-- Delete old carts after --number-input-- minutes/hours/day

Then another form below this that would be an interval entity of sorts where the user could add "events" after the age of the cart is x minutes/hours/days old. The number of events should start empty, yet be unlimited and user configurable. There would be no default subscribers for these events. Developers could hook into them however they want. There should be some sort of "identifier" to an event that is configurable for developers to understand which exact event they are responding to.

Thoughts?

mglaman’s picture

The comments in #73 carry over from some Slack discussions.

From a rough viewpoint, we would need a "Nudge" and "Expire" event. The config schema then becomes a sequence of items that contain an interval and type of event through. Or it's a single event with an "action" string and that string is what is configured in the UI "nudge" or "expire", naming to be bikeshed.

By default, we'd ship with the expiry solution to react on expiration settings. It does blow the scope of the issue and adds complexity. But it allows us to nail expired carts and a simple API for remarketing tools without additional hacks or fear of conflict.

I haven't had time to sit and fully think on the consequences of this. On the surface, it seems "🙌woo 🎉 cart abandonment emails and cleanup?!" but I'm not sure what dragons await us.

freelock’s picture

Ooh this sounds cool...

With a little work, the Scheduled Message module should be able to plug in here for the notification part, if this is implemented as different states of the cart. That module is set up to use a state from State Machine (haven't yet converted to core Workflow), and a date field of some kind, and lets you trigger a message based on an offset from that date along with the state -- if the state changes, message gets canceled instead of sent (e.g. if the cart converts to an order).

jibran’s picture

+++ b/modules/cart/src/Plugin/QueueWorker/CartExpiration.php
@@ -0,0 +1,98 @@
+        $order->delete();

I don't think the order should be deleted. The order status should be changed to aborted/timed-out and it should be kept for reporting purposes. Or at least it should be configurable.

TwiiK’s picture

I don't think the order should be deleted. The order status should be changed to aborted/timed-out and it should be kept for reporting purposes. Or at least it should be configurable.

If it's configurable then I have no issue, but we are talking about deleting old cart orders, right? I can't think of many uses for old cart orders in reports. Our needs for something like this is to lessen our databasen size so disabling the orders wouldn't help us. We have a Drupal 7 site that we're porting to Drupal 8 soon with millions of orders, many of which are cart orders. Lately they've been complaining about the order admin being unusably slow and it's surely due to the sheer amount of records in the database. We have to solve this when moving to Drupal 8 and deleting all old cart orders is a step in the right direction. We only ever allow 1 cart order (the most recent one) per user anyway so these old cart orders do nothing except clog up the database slowing down the site.

I guess my point is that I feel deletion is the logical default here, but it could by all means be configurable out of the box if people see a need to keep old cart orders.

bojanz’s picture

Status: Reviewed & tested by the community » Needs review

Since there's been a lot of discussion recently, as well as talks of introducing an event, I am bumping this issue back to "needs review", to indicate that it's not simply waiting for someone to hit the commit button.

bojanz’s picture

I have gone through this issue again, reviewed the current code and all comments.

I recognize that sites have different needs.
a) Email the customer after X days
b) Delete or mark as expired the order after Y days
However, a) and b) are not the same feature. Sites that email customers will still want to clear their old orders if they fail to respond within a given time period. So let's leave emails to Commerce Abandoned Carts.

It is tempting to try and make this issue solve all use cases, but that opens additional UI questions. Meanwhile, our requirements are simple and repeating across projects: Delete old carts so that they don't clog the database. That's why I've decided to proceed with this issue as-is. This feature is off by default. Sites that wish to retain carts and change their state are free to write custom code for that (using this feature as a template), and keep the deletion disabled. Sites that wish to send the cart details to an external system before deletion can respond to OrderEvents::ORDER_PREDELETE (or hook_entity_predelete).

Attaching a rerolled, cleaned up patch. Moved the commerce_cron_logic to a commerce_cart.cron service, to allow it to be swapped out if needed.

  • bojanz committed 8690b73 on 8.x-2.x
    Issue #2853527 by flocondetoile, mglaman, bojanz, tuutti, rthornton,...
bojanz’s picture

Status: Needs review » Fixed

Committed!

Big thank you to rthornton for getting this started, to flocondetoile and mglaman for doing most of the work needed to complete the work, and everyone else who pitched in with improvements, rerolls, testing.

jsacksick’s picture

@bojanz:

+++ b/modules/cart/src/Cron.php
@@ -0,0 +1,99 @@
+      ->range(0, 50)

Why limiting to 50?

  • bojanz committed 123fe9c on 8.x-2.x
    Issue #2853527 followup: Increase the number of orders processed on cron...
bojanz’s picture

You're right, it's too conservative. Raised it so that we get up to 250 ids, chunk them into groups of 50, then queue them for processing. Let's see how that treats us.

EDIT: Pushed another commit to add the commerce_cart_expiration tag to the entity query, so people can use hook_query_TAG_alter() to lower/increase the number of processed items.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

jacobbell84’s picture