Tweaking WooCommerce payment workflows

I’m playing part-time webmaster for the choir I sing in, and as such, am getting up close and personal with WooCommerce. Quite a nifty shopping cart, but it does require a lot of tweaks to really make it work to your liking – unless you’re willing to shell out a lot of cash.

The latest modification was changing the workflow of the payment gateways – more specifically, the BACS gateway (Bank Account Clearing System – or as we mortals call it, wire transfer).

The default flow for WooCommerce (for this gateway) is:

  1. Order is put in by customer
  2. Order is automatically flagged as on-hold, and a mail is sent out to the customer with the bank info
  3. Customer (supposedly) pays
  4. Store manager sees the payment, and flags order as processing – another mail is sent out with the notification that it’s being processed
  5. Store manager (hopefully) ships the product, flags the order as completed and another mail is sent out with ‘order complete’ status.

Now, for our uses, the on-hold status is a bit superfluous (and we’ve had people getting confused by it).
We’d rather have it go straight to processing, and have that mail contain the bank information (only for BACS payments, ofcourse).

After some testing, I came up with two solutions: One very hacky, and not maintainable, the other better. Both solutions need to be inserted in your theme’s functions.php file.

/* override gateway for BACS */
function my_core_gateways($methods) {
  foreach ($methods as &$method) {
    if($method == 'WC_Gateway_BACS') {
      $method = 'WC_Gateway_BACS_custom';
    }
  }
  return $methods;
}

/* custom gateway processor for BACS */
class WC_Gateway_BACS_custom extends WC_Gateway_BACS {
  public function email_instructions( $order, $sent_to_admin, $plain_text = false ) {

    if ( ! $sent_to_admin && 'bacs' === $order->payment_method && $order->has_status( 'processing' ) ) {
      if ( $this->instructions ) {
        echo wpautop( wptexturize( $this->instructions ) ) . PHP_EOL;
      }
 
     /* dirty hack to get access to bank_details */
     $reflector = new ReflectionObject($this);
     $method = $reflector->getMethod('bank_details');
     $method->setAccessible(true);
 
     $result = $method->invoke($this, $order->id);
    }
  }

  public function process_payment( $order_id ) {
    $order = wc_get_order( $order_id );

    // Mark as processing (we're awaiting the payment)
    $order->update_status( 'processing', __( 'Awaiting BACS payment', 'woocommerce' ) );

    // Reduce stock levels
    $order->reduce_order_stock();

    // Remove cart
    WC()->cart->empty_cart();

    // Return thankyou redirect
    return array(
      'result' => 'success',
      'redirect' => $this->get_return_url( $order )
    );
  }
}

I have several reservations with the code above: it’s basically shamelessly copying and overloading the two functions of the parent class, and calling a private function which is internal to the parent class – both of which might cause trouble if there are big changes in WooCommerce. It works, but well, it’s .. ugly. So, I looked for a better way to tackle this.

sadfasdfasdf
add_action( 'woocommerce_email_before_order_table', 'add_order_email_instructions', 10, 2 );
add_action( 'woocommerce_thankyou', 'bacs_order_payment_processing_order_status', 10, 1 );

function bacs_order_payment_processing_order_status( $order_id ) {
  if ( ! $order_id ) {
    return;
  }

  $order = new WC_Order( $order_id );
 
  if ('bacs' === $order->payment_method && ('on-hold' == $order->status || 'pending' == $order->status)) {
    $order->update_status('processing');
  } else {
    return;
  }
}

function add_order_email_instructions( $order, $sent_to_admin ) {
  if ( ! $sent_to_admin && 'bacs' === $order->payment_method && $order->has_status( 'processing' ) ) {
    $gw = new WC_Gateway_BACS();
 
    $reflector = new ReflectionObject($gw);
    $method = $reflector->getMethod('bank_details');
    $method->setAccessible(true);
 
    $result = $method->invoke($gw, $order->id);
  }
}

Still not as clean as I’d like, as we’re still invoking an internal function, but atleast we’re using the proper hooks to tweak WooCommerce. I’ll update if I ever find a better way to get to the bank details.

Using a Yubikey for account security

I got a Yubikey 4 half a year ago (during Red Hat Summit 2016), but until now I didn’t do much with it. Time to change that ;)

If you have any more ideas on how to use the Yubikey, feel free to comment!

Also, If you’re not using 2  factor authentication yet, I urge you to start using it. It gives you a nice additional layer of account security, with limited hassle. It doesn’t even have to cost you any money, if you’re using a software solution. Checkout twofactorauth.org for a (non-comprehensive) list of sites that support it!

 

Replacing Crashplan

I’ve been a longtime user of Crashplan, an easy-to-use cloud backup solution. It works well, and it used to work also on nearly any platform that had a java run-time and some add-on opensource libraries. I’ve used it for some time on my raspberry pi to automatically backup my data to the cloud. (Crashplan on ARM (the architecture of the raspberry pi) is an unsupported configuration though).

Used to work, past tense.

Code42 (the company behind Crashplan) decided to incorporate a new library (libc42archive.so) in the latest update of their client, version 4.8, which has no ARM counterpart. Only x86 (and amd_64) architectures are supported, removing a lot of devices which were able to run crashplan from the list. No source code is available, so this is basically a call to stop using Crashplan on anything other than Intel-compatible architectures. Bleh.
(I opened a support ticket to ask them to restore compatibility, but I’m not holding my breath for it)

I was able to keep it alive for some time by downgrading back to version 4.7 and making the upgrade directory immutable, but it seems that this trick has run it’s course. The client needs to be version 4.8 or you aren’t allowed to connect to the Crashplan back-end.

So, I needed a new solution. One with the requirements of being open source (I don’t want to run in that issue again), offering client-side encryption and incremental forever style backups. Being able to be stored in the cloud was a no-brainer. After some testing of various tools, I ended up with the following combination:

While Crashplan offered immediate push to the cloud, the workflow is now somewhat different: every day a script is triggered (via cron), which executes borgbackup against a USB-connected harddisk for my local (and optionally NFS-shared) data. This allows for fast backups, fast deduplication, and encryption. No data leaves my network at this point.
When all backups are done, the encrypted repository is synced (using rclone) to Backblaze B2, bringing my offsite backup in sync with the local repository.

Using an intermediate USB harddisk is not ideal, but it gives me yet another copy of my data – which is convenient when I’ve just deleted a file that I really did want to keep.

To give you an idea about the compression and deduplication statistics:

                       Original size      Compressed size    Deduplicated size
All archives:                1.10 TB              1.07 TB            446.63 GB

1.10TB is compressed to 1.07TB, and this results in an archive if 446GB. Less than half ;)

To be able to find a file that has been deleted at some point, you can use borgbackup mount :: /<mountpoint> – this will mount the entire repository (using FUSE) on that directory, making it available for browsing. Don’t forget to unmount it using fusermount -u /<mountpoint> when you’re finished.

I’ve uploaded the script to my scripts repository on GitHub.