Jump to content
DennisHermannsen

Hook to cancel invoices for terminated products

Recommended Posts

Don't know if this is the right category to post this in, but I thought I wanted to share this.

It's a very small hook that cancels an invoice once the product gets terminated.

//<?php

use WHMCS\Database\Capsule;
add_hook('PreModuleTerminate', 1, function($vars) {
	$invoiceid = Capsule::table('tblinvoiceitems')
		->where('relid', '=', $vars['params']['serviceid'])
		->orderBy('duedate', 'desc')
		->first();
	Capsule::table('tblinvoices')
		->where('id', '=', $invoiceid->invoiceid)
		->update(['Status' => 'Cancelled']);
});

Any feedback is much appreciated.

Share this post


Link to post
Share on other sites
//Updated to only target products of Hosting-type.
<?php
use WHMCS\Database\Capsule;
add_hook('PreModuleTerminate', 1, function($vars) {
	$invoiceid = Capsule::table('tblinvoiceitems')
		->where('relid', '=', $vars['params']['serviceid'])
                ->where('type','Hosting')
		 ->orderBy('duedate', 'desc')
		->first();
	Capsule::table('tblinvoices')
		->where('id', '=', $invoiceid->invoiceid)
		->update(['Status' => 'Cancelled']);
});

Share this post


Link to post
Share on other sites
4 minutes ago, MikeP said:

I'm guessing this would just go in /includes/hooks?

yes. 🙂

Share this post


Link to post
Share on other sites

Thanks for confirming.  Would it cancel the invoice if there was another product on the invoice as well?

Share this post


Link to post
Share on other sites
1 minute ago, MikeP said:

Thanks for confirming.  Would it cancel the invoice if there was another product on the invoice as well?

Oh. Great catch. It actually will - I've never had that in mind.

Share this post


Link to post
Share on other sites

There are various paid options available for this functionality but at least you've made the effort to provide this free of charge 🙂

I'd be interested if you were to add additional functionality, such as remove the product from the invoice and provide a log in the activity log.

Share this post


Link to post
Share on other sites

Appreciate your efforts to publish this hook code since there are many paid versions available on the market for the same. The current hook code will cancel the entire invoice even if only one products require termination. 

If the invoice has two or more items, and if a product requires termination along with its Addons (if any) then we will need to split the invoice and mark the one which terminates as cancelled and other one stay as unpaid on the account. Also it would be good to provide log in Activity log as he suggested. 

Can you write it up, and your effort is much appreciated! 🙂

Share this post


Link to post
Share on other sites

@Believe_ and @MikeP, here's an updated version - please note that this currently doesn't work very well if a client has a Group Discount.

<?php
use WHMCS\Database\Capsule;
add_hook('PreModuleTerminate', 1, function($vars) {
	$invoices = Capsule::table('tblinvoiceitems')
		->where('relid', $vars['params']['serviceid'])
		->where('type', '!=', 'GroupDiscount')
		->get();
	logActivity('Auto Cancel Invoice: Starting...');
	foreach($invoices as $key){
		
		$invoiceIDs = Capsule::table('tblinvoices')
			->where('id', $key->invoiceid)
			->where('status', 'Unpaid')
			->value('id');
		
		$invoiceLine = Capsule::table('tblinvoiceitems')
			->where('invoiceid', $invoiceIDs)
			->where('relid', $vars['params']['serviceid'])
			->value('id');
				
		$invoiceLines = Capsule::table('tblinvoiceitems')
			->where('invoiceid', $invoiceIDs)
			->get();
			
		$description = Capsule::table('tblinvoiceitems')
			->where('relid', $vars['params']['serviceid'])
			->where('invoiceid', $invoiceIDs)
			->value('description');
			
		
		$discountLine = Capsule::table('tblinvoiceitems')
			->where('invoiceid', $invoiceIDs)
			->where('type', 'GroupDiscount')
			->where('description', 'like', '% '.$description.'%')
			->value('id');

		if($invoiceIDs){
			logActivity('Auto Cancel Invoice: Invoice ' . $invoiceIDs . ' has had a line removed. This invoice previously had ' . count($invoiceLines) . ' lines.');
			if(count($invoiceLines) > 1){
				$command = 'UpdateInvoice';
				$postData = array(
					'invoiceid' => $invoiceIDs,
					'deletelineids' => array($invoiceLine),
				);

				$results = localAPI($command, $postData);
			}elseif(count($invoiceLines) == 1){
				logActivity('Auto Cancel Invoice: Invoice ' . $invoiceIDs . ' has been cancelled because the product was terminated.');
				$command = 'UpdateInvoice';
				$postData = array(
					'invoiceid' => $invoiceIDs,
					'status' => 'Cancelled',
				);
				$results = localAPI($command, $postData);
			}
			if($discountLine){
				logActivity('Auto Cancel Invoice: Invoice ' . $invoiceIDs . ' had a Group Discount. This has been removed.');
				$command = 'UpdateInvoice';
				$postData = array(
					'invoiceid' => $invoiceIDs,
					'deletelineids' => array($discountLine),
				);
				$results = localAPI($command, $postData);
			}
		}
	}
});

It will remove the product from the invoice (as split is not possible using the API, and WHMCS does just remove lines from invoices any way) if the invoice has more than 1 line. If it only has 1 line, the invoice will be cancelled.

But please note that this doesn't work with Client Group Discounts at all. There's no way for me to know for certain if a 'discount' line is associated with another line. If you use this hook with Client Group Discounts enabled, be aware that an invoice can have a negative total.

Does anyone have any ideas of a way to "link" a discount line with the relevant product line? The only way I could think of is to copy the line for the product, and match on %PRODUCT-LINE%, but we could potentially target an incorrect discount line. Do you know any smarter way, @brian!?

Edit: I added very simple logic to check if the product has a line for GroupDiscount. Please test this thoroughly before using it. I've only tested it on my dev installation, and everything seems fine from there.

Edited by DennisHermannsen

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×

Important Information

By using this site, you agree to our Terms of Use & Guidelines and understand your posts will initially be pre-moderated