EDD Stripe v3.0 Sticks it to Ya

Easy Digital Downloads (EDD) seems to be going the route of Freemius, etc., where you can use their open source plugin(s), but not without significant overhead. Except Freemius isn’t open source. 🤷‍♀️ With only a small share of the WP users needing file management plugins using their plugin and not a competitor’s, it’s a surprising move.

Stripe Gets Pricey

If you’re not paying for their annual license, or let your license term lapse, and wish to take payments with Stripe, you’re going to pay Stripe fees, plus a 3% “application fee” which EDD funnels off your sale. This fee used to be 2% until v.3.0. Now 3% — that’s significant!

With version 3.0 of EDD Stripe, EDD moved their entire Stripe integration over to Stripe Connect, which means all the sales on your private site go through Easy Digital Downloads Connect account first. Years ago the plugin used Stripe Charge API, then moved to Payments. Now this. There’s no going back. Your EDD Stripe plugin must be licensed ($$$) or you will be paying ~ 6% on every transaction.

But there’s a pretty simple way around all this if you have the plugin pre-version 3.0 … Version was 2.9.6 the latest before the jump. Add the following PHP code to your child theme functions.php file or add it using a plugin like WP Code Snippets (front and back end).

function edds_pro_edit_intent_args( $intent_args ) {
    if ( isset( $intent_args['application_fee_amount'] ) ) {
		    unset( $intent_args['application_fee_amount'] );
	  return $intent_args;
add_filter( 'edds_create_payment_intent_args', 'edds_pro_edit_intent_args', 99, 1 );
add_filter( 'edds_create_setup_intent_args', 'edds_pro_edit_intent_args', 99, 1 );

add_filter( 'edds_show_stripe_connect_fee_message', '__return_false' );

If you’re using the Stripe gateway plugin version 3.0 or newer, you’ll want to take a look at the has_application_fee() method in Src/Gateways/Stripe/ApplicationFees.php … and see how they’ve got it all wrapped up tight now. ⛓️ There are no filter hooks that can be used to wiggle out of that 3% fee. However, that method can be edited to get where you need. And would have to be edited again with every plugin update.

If you’d like the help of a developer for this issue, please get in touch.

WooCommerce product add to cart consent pop-up

WooCommerce handles its add-to-cart process with AJAX, which is great and all, but how do we interrupt that AJAX process to add in our last-minute validation/verification? Inside woocommerce/assets/js/frontend/add-to-cart.js line 73, there’s the code:

// Allow 3rd parties to validate and quit early.
if ( false === $( document.body ).triggerHandler( 'should_send_ajax_request.adding_to_cart', [ $thisbutton ] ) ) {
    $( document.body ).trigger( 'ajax_request_not_sent.adding_to_cart', [ false, false, $thisbutton ] );
    return true; 

Cool! WooCommerce developers put that there to help us intercept add-to-cart requests. That means all we’d need to do is write some JavaScript to return false for the ‘should_send_ajax_request.adding_to_cart’ triggerHandler(). Something like this:

jQuery( "body" ).on( "should_send_ajax_request.adding_to_cart", function( e, $button ) {
    if ( $( "body" ).hasClass( 'our-condition-is-not-met' ) ) { // random example
        return false;
    return true;

This would work in a lot of scenarios, but is limited to frontend scripting and gives no access to the server, so it’s a bit handicapping. Plus, we are already stuck inside the WooCommerce add-to-cart JavaScript, so we can’t run other scripts…

I wanted to bring up a JavaScript window.confirm() dialog box asking the customer to confirm something before the item could be added to the cart. By the time this trigger is fired, it’s too late to run a dialog on screen. So I did something a little tricky.

I added a new “fake” add-to-cart button (with CSS class “custom_add_to_cart_button”) to the product page, and hid the real WooCommerce add-to-cart button. The fake button would trigger the dialog, and if the customer confirmed, I’d trigger a click of the real button, which could stay hidden.

I only wanted to do this on a specific WooCommerce simple product (ID #5653), and so I edited the woocommerce/single-product/add-to-cart/simple.php template:

<?php if ( is_single( 5653 ) ) { ?>

    <button type="button" class="custom_add_to_cart_button button alt<?php echo esc_attr( wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '' ); ?>"><?php echo esc_html( $product->single_add_to_cart_text() ); ?></button>

    <button type="submit" name="add-to-cart" value="<?php echo esc_attr( $product->get_id() ); ?>" class="d-none single_add_to_cart_button button alt"><?php echo esc_html( $product->single_add_to_cart_text() ); ?></button>

<?php } else { ?>

    <button type="submit" name="add-to-cart" value="<?php echo esc_attr( $product->get_id() ); ?>" class="single_add_to_cart_button button alt"><?php echo esc_html( $product->single_add_to_cart_text() ); ?></button>

<?php }

Since I’m using Bootstrap you can see how I hid the native “.add-to-cart” button using the “d-none” class. When not using Bootstrap you could just hide the native WooCommerce .single_add_product_to_cart button using CSS:

.postid-5653 .single_add_to_cart_button{display:none}

Instead of editing WooCommerce templates, which can lead to headaches down the road if WooCommerce makes breaking changes — or even if they just keep updating the template — this could also be done by using the 'woocommerce_after_add_to_cart_quantity' action hook and the $product global, like so:

<?php add_action( 'woocommerce_after_add_to_cart_quantity', 'custom_button_after_add_to_cart_quantity' );

function dummy_button_after_add_to_cart_quantity() {
    global $product;
    if ( ! $product || 5653 !== $product->get_id() ) {	
    } ?>
			<button type="button" class="custom_add_to_cart_button button alt<?php echo esc_attr( wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '' ); ?>"><?php echo esc_html( $product->single_add_to_cart_text() ); ?></button>
<?php }

Finally, I queue up my custom jQuery to handle the add-to-cart click and show the confirmation dialog. If the customer doesn’t confirm, nothing happens. Otherwise, the real WooCommerce add-to-cart button is clicked and the item is added to the cart (assuming it is in stock, etc).

    $( ".custom_add_to_cart_button" ).on( 'click', function(e, $button) {
        if ( ! window.confirm( "I understand this product is likely to blow my mind. I am prepared!" ) ) {
        } $( '.single_add_to_cart_button' ).trigger( 'click' );

Sometimes we just have to get a little tricky to make customizations to WooCommerce shops without over-complicating things. Adding another button to the product page doesn’t hurt anything. Worst case scenario is someone, maybe someone using a screen reader, clicks the real WooCommerce button (because though it is hidden, it is still in the DOM) and proceeds directly to the cart without interacting with the dialog. That would be a very rare case, or might happen in situations where your dialog is enforcing something people don’t want enforced (in which case they will seek work-arounds in the DOM). Anyway, in my case it’s really not something to worry about; I’m just pointing out that this solution, like most online consents, is not bullet-proof.

All said sometimes there are specific details or disclaimers about our WooCommerce products that we really need customers to know about before they buy. Examples might be:

  • “I understand this item has a 3-week lead time,” or
  • “I understand this product is not recommended for children under the age of 8”, or
  • “I understand that this product can be damage vision and cause blindness if shined into eyes”

Forcing customers to consent to these important points can prevent heartache, disputes and returns, so often it’s worthwhile to program in a dialog. If you have a lot of products it might be worth using a plugin for this feature, such as Product Disclaimer for WooCommerce. But especially if you just need this for one product it’s worthwhile to just write out a dozen short lines of code or so to get it done, like I did.

Prorated Software Upgrade Crack

It’s amazing how some customers’ behavior can highlight innovative ways of saving money.

It never occurred to me until I started selling software, and managing annual software licenses, that there’s a pretty simple way to save money when trying to renew software licenses, without a coupon. Many businesses pro-rate license upgrades, and prorate them by date, such that the closer you come to expiration, the cheaper an upgrade gets. So a frugal move to make is to wait until just days before your software license expires, then upgrade to the next level up… for a tiny fraction of what it would cost to renew the lesser license currently held. Voilà – we now have better access for another year, for cheaper than a straight renewal!

While this is pretty cool for the customer, this is a big problem for software sellers that causes significant loss of income. In particular it’s a weakness for some Easy Digital Downloads (EDD) admins using EDD’s Software Licensing extension. I wrote a PHP code snippet for use with EDD Software Licensing. It locks out prorated upgrades after a certain number of months of license ownership (3 months):

 * Customers lose prorate upgrades if less than 9 months left on license
 * @param float|int $prorated Calculated prorated price
 * @param int $license_id ID of license being upgraded
 * @param float|int $old_price Price of the original license being upgraded
 * @param float|int $new_price Price of the new license level
 * @return float|int
function my_get_pro_rated_upgrade_cost( $prorated, $license_id, $old_price, $new_price ) {

    if ( ! class_exists( 'EDD_Software_Licensing' ) ) {
        require_once WP_PLUGIN_DIR . '/edd-software-licensing/includes/classes/class-edd-software-licensing.php';
    // Get license object from license ID
    $license = EDD_Software_Licensing()->get_license( $license_id );
    if ( 'lifetime' === $license->expiration ) {
        return $prorated; // why lifetime licenses should get caught in this hook is beyond me
    // 23670000 is the number of seconds in 9 months, change ad lib
    if ( ! is_object( $license ) || $license->is_expired() || ( ! $license->is_expired() && ( (int) $license->expiration - time() < 23670000 ) ) ) {
        return $new_price; 
    return $prorated;

add_filter( 'edd_sl_get_pro_rated_upgrade_cost', 'my_get_pro_rated_upgrade_cost', 11, 4 );

Should a customer decide they need to upgrade the software, they need to do it soon after purchase, not as a way to obtain a deep discount. In the case of this EDD add-on snippet below, they need to upgrade within 3 months, otherwise prorating is not offered. If they try to upgrade an expired license, prorating is not offered.

I hope this helps you. If it does please reach out to say hi!

Ways to harden your WordPress website without using bulky plugins

You can add security plugins to your website, but if you don’t know exactly what they’re doing, not only can you silently break things, but you can slow down your site with a lot of extra code. They’re great, but it’s not like there isn’t risk in blindly activating any plugin. I develop plugins and try to predict how things can go wrong, so I know just how bewildering the number of things that can go wrong feels. When I can simplify things on my own sites, I always go that route.

I recently decided to stop using a security plugin because I’ve implemented security measures of my own, and my server handles SSL redirection. I love the Really Simple SSL plugin but didn’t need it in this particular instance. Really Simple SSL had a couple nice features I decided to just hardcode into my functions.php file. Here are a couple of them:

Disallow ‘admin’ Usernames

WordPress includes a quick and easy filter hook, called “illegal_user_logins”, for adding disallowed WordPress usernames. You’ll want to disable ‘admin’ and ‘administrator,’ and any other predictable usernames for your setup (such as your domain name and nickname). If you’re already using these usernames, you’ll want to change them in your MySQL database _users table first. This hook defines elements of an array. The following code adds an ‘admin’ element to that array:

 * Make the 'admin' login inaccessible to new users
 * @param array $illegal_logins
 * @return array
function make_admin_user_login_illegal( $illegal_logins ) {

    $illegal_logins[] = 'admin';
    $illegal_logins[] = 'administrator';
    return $illegal_logins;

add_filter( 'illegal_user_logins', 'make_admin_user_login_illegal', 9, 3 );

Restrict User Enumeration

Hackers can type in your URL followed by /?author=1 (because the admin is usually user #1) and find out your username, which gives them half the information they need to force a hack. They can do this on any other user, starting with the number 2, your second registered WordPress user. Make usernames hard to guess, and keep them from finding them so easily with this PHP code, taken from Really Simple SSL:

 * Prevent User Enumeration
 * @return void
function check_user_enumeration() {
    if ( ! is_user_logged_in() && isset( $_REQUEST['author'] ) ) {
        if ( preg_match( '/\\d/', $_REQUEST['author'] ) > 0 ) {
            wp_die( sprintf( 'Forbidden - number in author name not allowed = %s', esc_html( $_REQUEST['author'] ) ) );
add_action( 'init', 'check_user_enumeration' );

The Most Common WordPress Mistakes People Make

For several years I’ve been scanning the support forums at WordPress.org and helping where I can. I’ve been using WordPress since it came out in 2003, and because it’s at least partly free and open-source, I like to contribute. Aside from the obvious — helping others — contributing helps me keep abreast of what’s new in WordPress and hones my skills. On top of occasionally answering questions in the general support forums, I also wrote and support two plugins in the wordpress.org plugin repository.

I frequently see quite a bit of misunderstanding on a basic level about what WordPress is and what it can do, and I’d like to toss in my thoughts about that here. I hope I can help clear up a few reasons some people think “WordPress sucks.”

WordPress is WordPress is WordPress

Not true. WordPress has two arms: WordPress.org is the open-source, free arm of WordPress. WordPress.com is not open-source or free; it is essentially a website hosting service.

Unfortunately when WordPress is recommended to people, some do not understand there is a big choice to make between the two arms. They end up at either wordpress.org or wordpress.com (hopefully now you see the difference in the URL now) and do not realize they’re only seeing part of the picture. When they are disappointed, they often throw baby out with the bathwater.

When people say, “WordPress sucks,” I ask if they are using wordpress.org or wordpress.com. When they grow puzzled, I’ve found another soul lost in WordPress world. Ideally, WordPress would make the two arms of their empire more distinct by perhaps renaming one. Heaven forbid!

I inhabit wordpress.org land, where in general things are affordable and anything is possible. When people say, “WordPress sucks,” I would hope they would make sure to use wordpress.org. I have never recommended wordpress.com to anyone. If you’re going to use wordpress.com, you might as well use WIX. Both end up being expensive and neither gives you much control.

Two upraised hands against sky backdrop, one with open pair of handcuffs attached

Everything WordPress Should be Free

This is a bit absurd, because humans have to write and support all the millions of lines of code that go into making the WordPress world spin, and those people need food on the table, too.

Open source and “free” do not mean that support is free, or that all supplementary code is free. Those words simply mean that you are free to take the code and use it. An example of closed source and not free would be Photoshop. Photoshop is expensive and we are not allowed to “look under the hood” of the Photoshop application. If WordPress were like PhotoShop, imagine where the web would be now! Instead, as of October 2021, WordPress is used by 42.8% of the top 10 million websites in the world.

More and more on the wordpress.org support forums, I see people asking for help they should be paying for. It is one thing to submit a ticket when a bug is maybe identified, or to ask for help understanding a concept, or for general direction, but it is another to ask the open source community to provide free coding support, especially for a commercial project. WordPress might be one of the most well-documented pieces of software around, and chances are that in the past 20 years, your question has already been answered. I learned by “Googling it,” and I continue to Google my questions about WordPress every day. Folks asking other people to essentially do their Googling for them will eventually be rebuked or come to feel some healthy shame for having taken advantage of others’ kindness.

As a professional making a living off WordPress, I appreciate and respond best when people respect that my knowledge isn’t any less valuable than the knowledge of a plumber, electrician, baker, or musician. Everyone needs to get paid in order to get by. And ultimately, we get what we pay for.

WordPress “Experts…” Who Are Not Experts

First of all, be leery of ANYONE who calls themselves “expert.” Because to me it’s obvious that ANYONE can call themselves expert, to the point where the word is meaningless anymore. What are their ACTUAL credentials? Can they prove they did the work?

There are quite a few people who spend a lot of time at the keyboard using WordPress, and who decide to take a crack at making money with it. Unfortunately many of them don’t know what they don’t know. There are actually people out there who don’t even realize WordPress isn’t run off a file system alone. People who actually believe that WordPress can be entirely managed by clicking buttons and installing plugins!

Be careful to not hire an imposter. Make sure when you hire an “expert” that the expert is able to code in the languages of WordPress: PHP, JavaScript, HTML, and CSS. If your WordPress “expert” can write a couple iffy lines of CSS, yet has no idea what a “hook” is or how to write a few good lines of PHP to customize your website, keep looking.

There are a lot of unwitting imposters out there who should call themselves WordPress “administrators,” and not WordPress “developers”. This has turned the WordPress economy into an unsustainable “race to the bottom,” and further perpetuates the misconception that “WordPress sucks.” It’s unfortunate for everyone involved, but especially the unwitting customer.

Imposter “developers” will load up your WordPress installation with dozens of plugins including hundreds of thousands of lines of code in order to get simple tasks done when in most cases a couple dozen lines of hand-written code would suffice. They are unaware of how these plugins and unnecessary extra code slow down your site. To “fix” a slow site, they’ll add even more plugins. Eventually your site will break, and they will hire someone else to fix it. This should not happen.

There are no official certifications to prove a “developer” status in the WordPress world, so feel free to ask your developer to see some code samples. Have they written a custom WordPress theme or plugin? They should easily be able to show you something they built from scratch, and prove it is their work.

carpenter at work bench takes a moment to research something on the laptop

WordPress is DIY

Absolutely! It can be! If you love to tuck into big projects and are an autodidact, go for it. That’s what I did starting in the late 1990s. I built silly blogs and simple websites, copied website features I thought were cool into my sites until they made sense, learned HTML and CSS, and finally discovered WordPress in 2003. Since then I have learned other languages, too. But let me tell you, learning how WordPress works and how to code for it didn’t happen overnight, or in weeks, or even in years. Unless you are going to study it round-the-clock for months, it is going to take years to become a WP developer.

In the meantime, I strongly recommend that everyone with a website hire help. Especially if your website is valuable to you, or an e-commerce or commercial website, hire qualified help! Also get help if you want your website:

  • to rank on search engines
  • to be (handicap) accessible
  • to be easy-as-possible to look at and use
  • to be secure and safe for you and users
  • to have custom features, such as automating signups or bookkeeping
  • to build a reputation and make money

These are features plugins might claim to do, but in my experience, new WordPress users regularly fail to set up plugins correctly so their site is in fact working for them. Your website should work FOR you, not against you.

There are predictable things that new WordPress users and imposter “developers” will do to a website which will absolutely negatively affect both how that website ranks on the web and how users interact with it. If you want your website to be seen and make money, hire help. Yes, there’s a slight chance that your content is so amazing or viral that your site will grow successful despite itself. (When it does, hire qualified help.) But most unmanned, unprofessional sites will simply loiter the web and corrode, ultimately diminishing someone’s reputation.

There are basically a couple levels of professional WordPress help:

  1. WordPress Administrators
  2. WordPress Developers

If you insist on not hiring a WordPress professional, it’s wise to get up to at least Administrator speed yourself. Know what the buttons do. Know when to press them. Learn where to draw the line, where to ask for help, and when to pay for help.

WordPress Definitely Does Not Suck

That’s it! I find that some foundational misunderstandings about WordPress is what causes some people to think it sucks. I believe that if it seems to suck, it’s almost always a user error. I truly believe that anything can be done with WordPress. (Somethings shouldn’t be done, but they could!) If someone tells you something can’t be done with WordPress and they’re not a high level programmer, don’t believe them — ask a programmer!

If you are frustrated with WordPress and having trouble finding answers, please get in touch. It could be that at some point you simply took a wrong turn. I’d love to get you back on the map.

How to get a WordPress Media (attachment) ID

There are several ways to go about getting the attachment ID (of the ‘attachment’ WordPress post type, stored in the wp_posts WPDB [Wordpress DataBase] table. Here are some of them:

From the Media Library:

Navigate to Dashboard -> Media. Here you will see your WordPress uploads (Media), either in grid view or list view. They’re both fine, it’s just a preference thing. Instructions for both are below.

You can click the small icons to change the Media view from list to grid or vice versa.

List View

Use your mouse to hover over the chosen media and watch for the post ID in the lower left hand corner of your browser. In this case, the id is 9479.[/caption]

Grid View

In grid view, it’s a little bit different. You’ve got to click on the media file. A popup window will open up. Take a look in your browser URL bar and you will see the attachment ID there, after where it says “?item=” Like this:

In this case, the attachment ID is 9491