WooCommerce Subscriptions API start_date bug workaround

The WooCommerce Subscriptions plugin has a bug where if you use its REST API endpoints to make updates to a subscription, it will almost always reset the start date of the subscription you are updating to the current date and time.

I reported the bug to the WooCommerce folks in November 2022 and I believe it has a GitHub issue filed. When I checked in about it recently I was told it’s a low priority to fix. I consider it somewhat serious for my purposes — unexpectedly overwriting/losing key data about a record that’s used in calculating user-facing financial transactions —  so I’m documenting it here for others who might encounter it, along with a possible workaround.

The bug is in the plugin file includes/api/class-wc-rest-subscriptions-controller.php that processes the incoming API request. In particular, in the function prepare_object_for_database() there’s this code:

// If the start date is not set in the request, set its default to now.
if ( ! isset( $request['start_date'] ) ) {
	$request['start_date'] = gmdate( 'Y-m-d H:i:s' );
}

All of the valid dates contained in the $request array are subsequently copied into an array called $dates. Later in the same function, there’s this code:

if ( ! empty( $dates ) ) {
	...
	try {
		$subscription->update_dates( $dates );
	} catch ( Exception $e ) {
		...
	}
}

The implication is that for completely unrelated API requests to change something like, say, a meta field value or the subscription’s status, the date validation and update logic will be run. And because the start date value is overridden to be the current date, it means that any API request to update a completely unrelated field is going to unintentionally reset the start date of the subscription.

Nothing about the documentation at https://woocommerce.github.io/subscriptions-rest-api-docs/?php#update-a-subscription indicates that a start_date value is required in the API requests. In fact, the example given in those docs where the status of a subscription is updated would trigger this bug.

I’ve even noticed this bug surfacing even in regular wp-admin operations involving WooCommerce Subscriptions, as I think some of the logic used to do something like put a subscription on hold or cancel it from within the admin interface is calling the same internal functions.

My workaround for this bug, at least on the API client side, introduces an extra API request, and so is less than ideal for any kind of production or long-term use.

For every API request I make to the Subscriptions API update endpoint, if I’m not explicitly setting/changing the start_date field in my request, I first fetch the existing subscription record and then set the start_date field in my request to the current value.

if ('subscription' === $recordType && empty($updateValues['start_date'])) {
      $current_values = $wooApiFacade::find($recordId);
      $updateValues['start_date'] = Carbon::parse($current_values['start_date_gmt'])->format('Y-m-d\ H:i:s');
}

Hopefully they’ll fix this issue sooner rather than later so that users of the plugin don’t unexpectedly see subscription start dates overwritten.

CrowdTangle API SDK in PHP

After not finding anyone else who has done so, I created a minimal PHP implementation of the CrowdTangle API, which I needed anyway for a project I’m working on:

Example usage syntax:

$client = new ChrisHardie\CrowdtangleApi\Client($accessToken);

// get lists
$client->getLists();

// get accounts in a list
$client->getAccountsForList($listId);

// get posts
$client->getPosts([
    'accounts' => '12345678',
    'startDate' => '2022-03-01',
]);

// get a single post
$client->getPost($postId);

I’m sure there’s plenty to improve but I hope it’s helpful to anyone working with CrowdTangle in PHP.

Creating a personalized, private RSS feed for users in Laravel

In building WP Lookout I had the need to create user-specific, private RSS feeds as a way for users to get updates and information specific to their account. Here’s how I did it.

I started with the laravel-feed package from Spatie, which allows you to easily generate RSS feeds, either from a model’s records or from some other method that you define. I found that someone has proposed a feature and submitted a PR to add support for signed routes, which is Laravel’s way of adding a hash key to a given URL so that it can’t be accessed if it’s been modified, perfect for something like a user-specific RSS feed. But the Spatie folks decided not to include the feature, so I had to figure out how to do it on my own.

First, I added the package to my project and published the related config file:

$ composer require spatie/laravel-feed
$ php artisan vendor:publish \
    --provider="Spatie\Feed\FeedServiceProvider" \
    --tag="config"

Then, I added a user-specific feed identifier to the User model:

$ php artisan make:migration add_feed_id_to_users_table --table=users

The migration looked like this in part:

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        //
        $table->string('feed_id');
    });

    DB::statement('UPDATE users SET feed_id = ...');
}

The UPDATE statement there is used to set the initial value of the feed identifier for existing users. An alternative would have been to make the field nullable and then set it elsewhere. For new users, I set the feed identifier as users are created, via the boot() method in the User model:

Continue reading Creating a personalized, private RSS feed for users in Laravel