Managing Fraud Orders in WooCommerce

One morning I woke up to find 20,000 new fraudulent orders on a WooCommerce site I manage. The vast majority of them were in failed status, but a few had successfully completed. We quickly determined this was a card testing attack, where a fraudster was using an automated system to iterate through a huge amount of stolen credit card numbers to check out on our site and determine which ones were still valid.

The biggest concern with this type of attack is that your credit card processor may stop processing payments from your site altogether due to fraud concerns, which means the business is dead until you can find a new payment processor.

As soon as we noticed the fraud, we alerted our payment processor and let them know we were taking steps to stop the attack and refund the fraudulent orders- which I think was helpful for keeping our account in good status. Continue reading

Deleted Laravel Logs Eating Up Disk Space

We had an odd situation where disk space was getting swallowed up on a Digital Ocean server running Laravel. The issue started happening after an upgrade from Laravel 7 to Laravel 8 (though it could have been there before unnoticed). The database size had been growing, so my first though was just to resize the droplet. But when an additional 100GB was eaten up over the weekend I realized there must be something else going on.

It turned out we had deleted log files that were still being kept open by a system process, and therefore could not be fully deleted, but were also continuing to grow:

We found these with the command:

lsof +L1

Which showed:

COMMAND  PID  USER   FD   TYPE DEVICE  SIZE/OFF NLINK    NODE NAME
php7.4  1543 forge   15w   REG  252,1 44241528600     0 1809826 /home/forge/data.example.com/storage/logs/laravel.log.1 (deleted)
php7.4  1544 forge   15w   REG  252,1 44242240400     0 1809826 /home/forge/data.example.com/storage/logs/laravel.log.1 (deleted)
php7.4  1545 forge   15w   REG  252,1 44242240400    0 1809826 /home/forge/data.example.com/storage/logs/laravel.log.1 (deleted)

This StackExchange post has a little more information about tracing down the issue, and this one has a more technical explanation for why it happens.

To resolve the issue, we truncated the deleted file using the method described here, which did free up the space:

:>/proc/1543/fd/15

Then we killed all the processes:

forge@data-service:~/data.example.com$ kill -SIGTERM 1543
forge@data-service:~/data.example.com$ kill -SIGTERM 1544
forge@data-service:~/data.example.com$ kill -SIGTERM 1545

This appeared to resolve the issue for us.

How to Avoid Excess Meta with WooCommerce Subscriptions

Orders in WooCommerce create a lot data in the postmeta table. There’s the standard WooCommerce fields (like _order_key, _cart_hash, _billing_first_name, etc.), but payment gateway plugins, marketing integrations, and other WooCommerce plugins also generate their own metadata. It’s not unusual to see more than 60 rows of metadata for each order.

I recommend the Post Meta Inspector to easily view all the metadata on any order or post. You may be surprised how much there is!

If you run subscriptions on your site and generate a lot of renewals, WooCommerce Subscriptions may be creating a lot of unneeded metadata due to how subscriptions and renewal orders are generated. This can cause your database to grow really quickly. Continue reading

Update the Modified Date in a WooCommerce Order

If you run a script to update order properties in WooCommerce, you may want to update the “post_modified” date along with the other updates. Many third-party integrations rely on the post_modified date to sync any order changes or updates.

Here’s how you can easily update the modified date (assuming you have the order object):

$order->set_date_modified( time() );

Here’s a full example which fetches the order, updates the date, and saves the order.

$order = wc_get_order( 123 );
$order->set_date_modified( time() );
$order->save();

TablePlus for SQL Queries in WordPress

Having a nice GUI to make direct database queries can be really helpful when working with a complex WordPress site. TablePlus is the best one one I’ve found for the Mac.

Using TablePlus with WP Engine

Connecting to a local database and most remote databases should be pretty straightforward, but there’s a few extra steps if the database is hosted with WP Engine.

WP Engine has remote database access instructions here. Make sure to get your IP address whitelisted, include the cert, and get the ports right. Here’s a screenshot of what the settings should look like.

Continue reading

GitHub Actions for Laravel Tests

I just set up GitHub Actions for running tests with Laravel and was impressed with how simple it was.

This video shows how I got it set up with the Laravel React Bootstrap project.

1. Click “Actions”
2. Select the default Laravel workflow
3. Create a branch or commit the new workflow

If you have any secret keys that are required for the test environment, those can also be injected easily. I show how to do it in the video, but here’s the full GitHub documentation for it.

How to Use an SVG for a Custom Post Type Icon

Let's use this id-card svg for the custom post type icon rather than a standard dashicon.

First, download the raw SVG file. If you open it up in a text editor, it should look something like this:

<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="id-card" class="svg-inline--fa fa-id-card fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M528 32H48C21.5 32 0 53.5 0 80v16h576V80c0-26.5-21.5-48-48-48zM0 432c0 26.5 21.5 48 48 48h480c26.5 0 48-21.5 48-48V128H0v304zm352-232c0-4.4 3.6-8 8-8h144c4.4 0 8 3.6 8 8v16c0 4.4-3.6 8-8 8H360c-4.4 0-8-3.6-8-8v-16zm0 64c0-4.4 3.6-8 8-8h144c4.4 0 8 3.6 8 8v16c0 4.4-3.6 8-8 8H360c-4.4 0-8-3.6-8-8v-16zm0 64c0-4.4 3.6-8 8-8h144c4.4 0 8 3.6 8 8v16c0 4.4-3.6 8-8 8H360c-4.4 0-8-3.6-8-8v-16zM176 192c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zM67.1 396.2C75.5 370.5 99.6 352 128 352h8.2c12.3 5.1 25.7 8 39.8 8s27.6-2.9 39.8-8h8.2c28.4 0 52.5 18.5 60.9 44.2 3.2 9.9-5.2 19.8-15.6 19.8H82.7c-10.4 0-18.8-10-15.6-19.8z"></path></svg>

I usually clean up the svg code to just include the necessary bits:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M528 32H48C21.5 32 0 53.5 0 80v16h576V80c0-26.5-21.5-48-48-48zM0 432c0 26.5 21.5 48 48 48h480c26.5 0 48-21.5 48-48V128H0v304zm352-232c0-4.4 3.6-8 8-8h144c4.4 0 8 3.6 8 8v16c0 4.4-3.6 8-8 8H360c-4.4 0-8-3.6-8-8v-16zm0 64c0-4.4 3.6-8 8-8h144c4.4 0 8 3.6 8 8v16c0 4.4-3.6 8-8 8H360c-4.4 0-8-3.6-8-8v-16zm0 64c0-4.4 3.6-8 8-8h144c4.4 0 8 3.6 8 8v16c0 4.4-3.6 8-8 8H360c-4.4 0-8-3.6-8-8v-16zM176 192c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zM67.1 396.2C75.5 370.5 99.6 352 128 352h8.2c12.3 5.1 25.7 8 39.8 8s27.6-2.9 39.8-8h8.2c28.4 0 52.5 18.5 60.9 44.2 3.2 9.9-5.2 19.8-15.6 19.8H82.7c-10.4 0-18.8-10-15.6-19.8z"></path></svg>

Note that I leave fill="currentColor" on the path.

Now, you have two options for how to load the icon. You can set the svg code inline as a variable:

$icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M528 32H48C21.5 32 0 53.5 0 80v16h576V80c0-26.5-21.5-48-48-48zM0 432c0 26.5 21.5 48 48 48h480c26.5 0 48-21.5 48-48V128H0v304zm352-232c0-4.4 3.6-8 8-8h144c4.4 0 8 3.6 8 8v16c0 4.4-3.6 8-8 8H360c-4.4 0-8-3.6-8-8v-16zm0 64c0-4.4 3.6-8 8-8h144c4.4 0 8 3.6 8 8v16c0 4.4-3.6 8-8 8H360c-4.4 0-8-3.6-8-8v-16zm0 64c0-4.4 3.6-8 8-8h144c4.4 0 8 3.6 8 8v16c0 4.4-3.6 8-8 8H360c-4.4 0-8-3.6-8-8v-16zM176 192c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zM67.1 396.2C75.5 370.5 99.6 352 128 352h8.2c12.3 5.1 25.7 8 39.8 8s27.6-2.9 39.8-8h8.2c28.4 0 52.5 18.5 60.9 44.2 3.2 9.9-5.2 19.8-15.6 19.8H82.7c-10.4 0-18.8-10-15.6-19.8z"></path></svg>';

Or you can load it directly from your file (icon.svg),

$icon = file_get_contents( plugin_dir_path( __FILE__ ) . '/assets/icon.svg' );

Then set the menu icon when the post type is registered:

'menu_icon' => 'data:image/svg+xml;base64,' . base64_encode($icon)
view raw svg-icon-post.md hosted with ❤ by GitHub

Material UI and Laravel

Material UI is a popular React framework based on Google’s Material Design System. It provides a nice design system and library of prebuilt components that can speed up the process of building web applications. I’ve worked with it on a couple projects, and really enjoy building on top of it.

Because I like having good boilerplates available to start new projects with, I rebuilt my Laravel React Bootstrap To Do App with Material UI. It uses the same REST API for authentication and data storage- but it is a completely headless implementation and could easily be updated to use different APIs.

View the Demo
Source Code on Github

More About the Project

If you’d like to host your own backend, you can also find that source code on Github. In the future, I hope to write a new version of the backend that uses Airlock rather than the tymondesigns/jwt-auth library for auth. If that would be useful to you, leave me a comment.