Jump to content

ClientEdit hook code to cycle through unpaid invoices to apply credit and cancel invoice


Recommended Posts

I am hoping to aid the finance/billing team with writing off bad debt using a hook.

The manual process is as follows (high level):

1. add credit
2. apply credit to invoice
3. cancel invoice

ultimately term/inactivate client product/service.

I am somewhat novice, but curious how things stand with the following code. I hope to simplify step 2 and 3 via hook. Am I approaching the logic properly? Any other guidance would be appreciated. As I understand it, when a credit is added to a client account, it will update the Credit value for the related client.

<?php
 
if (!defined("WHMCS"))
    die("This file cannot be accessed directly");

add_hook("ClientEdit", 1, "hook_applycredit_thencancel");

function hook_applycredit_thencancel($vars)
{
	try {
		//vars from hook
		$clientid = $vars['userid'];
		$adminuser = 'sys.admin'; 

		logActivity('Checking Client unpaid invoices ' . $clientid . ' from hook_applycredit_thencancel...');

		// Get Invoices
		// Define parameters
		$command = 'GetInvoices';
		$values = array(
			'userid' => $clientid,
			'status' => 'Unpaid',
		);

		// Call the localAPI function
		$invoiceData = localAPI($command, $values, $adminuser);
		if ($invoiceData['result'] == 'success') {
			foreach ($invoiceData['invoices']['invoice'][0] as $invoice) {
				$invoiceId = $invoice->'id';
				$creditamt = $invoice->'credit';
				
				//Apply Credit
				$command = 'ApplyCredit';
				$values = array(
				'invoiceid' => $invoiceId,
				'amount' => $creditamt,
				'noemail' => '1',
				);
				
				$addCredit = localAPI($command, $values, $adminuser);
				if ($addCredit['result'] == 'success') {
					logActivity('Credit amount ' . $creditamt . 'has been applied to Invoice ' . $invoiceId . ' by hook_applycredit_thencancel...');
				}
				
				else {
				logActivity('Credit amount ' . $creditamt . ' FAILED to apply to Invoice ' . $invoiceId . ' by hook_applycredit_thencancel...');
				}
				
				//Cancel Invoice
				$command = 'UpdateInvoice';
				$values = array(
				'invoiceid' => $invoiceId,
				'status' => 'Cancelled'
				);
				
				$cancelInvoice = localAPI($command, $values, $adminuser);
				if ($canceInvoice['result'] == 'success') {
					logActivity('Invoice ' . $invoiceId . ' has been cancelled by hook_applycredit_thencancel...');
				}
				
				else {
				logActivity('Invoice ' . $invoiceId . ' FAILED cancel by hook_applycredit_thencancel...');
				}
			}
		}
		
		else }
		logActivity('Credit application for ' . $clientid . ' FAILED by hook_applycredit_thencancel...' . $invoiceData['message']);
		}
			
	} catch (\Exception $e) {
		logActivity("Credit application failed: {$e->getMessage()}");	
	}
} 

 

Link to comment
Share on other sites

There are few issues in the hook logic for applying credit and cancelling invoices:

  1. Incorrect array index usage in the loop
     

    You had:
    foreach ($invoiceData['invoices']['invoice'][0] as $invoice)
    This loops only over the first invoice.
    Use:
    foreach ($invoiceData['invoices']['invoice'] as $invoice)

     

  2. Incorrect object access
     

    You used:
    $invoiceId = $invoice->'id';
    $creditamt = $invoice->'credit';
    But $invoice is an associative array, not an object.
    Use:
    $invoiceId = $invoice['id'];
    $creditamt = $invoice['credit'];

     

  3. Incorrect variable name
    You had:
    if ($canceInvoice['result'] == 'success')
    Correct to:
    if ($cancelInvoice['result'] == 'success')

     

  4. Syntax error in else
    You had:
    else }
    Correct to:
    else {

     

Link to comment
Share on other sites

@pRieStaKos 

As I am not typically a developer, I don't delve into github and other developer focused content/tools. 

The goal was to trigger on ClientEdit, but was told by WHMCS Support that adding a credit does not trigger this, so had considered a different approach to the process. Credits will be added to write-off the unpaid invoices and cancel them.

+ @DristiTechnologies

What is correct parameter to check if a client balance is greater than 0.00 before applying the credit?
This is what I mocked up.

<?php
if (!defined("WHMCS"))
    die("This file cannot be accessed directly");

add_hook("ClientEdit", 1, "hook_applycredit_thencancel");

function hook_applycredit_thencancel($vars)
{
	try {
		//vars from hook
		$clientid = $vars['userid'];
		$adminuser = 'todd.brotzman'; //may change to a distinct api user for this function.

		logActivity('Checking Client unpaid invoices ' . $clientid . ' from hook_applycredit_thencancel...');

		// Get Invoices
		// Define parameters
		$command = 'GetInvoices';
		$values = array(
			'userid' => $clientid,
			'status' => 'Unpaid',
		);

		// Call the localAPI function
		$invoiceData = localAPI($command, $values, $adminuser);
		if ($invoiceData['result'] == 'success') {
			foreach ($invoiceData['invoices']['invoice'] as $invoice) {
				$invoiceId = $invoice['id'];
				$creditamt = $invoice['total'];
				
				//Get Client Credit Balance
				$command = 'GetClientDetails';
				$values = array(
				'clientid' => $clientid,
				);

				$getCreditBal = localAPI($command, $values, $adminuser);
				if ($getCreditBal['result'] == 'success') {
					$creditbalance = $getClient['credit'];
				}
				else {
				$creditbalance = '0';
				}

				//Apply Credit
				$command = 'ApplyCredit';
				$values = array(
				'invoiceid' => $invoiceId,
				'amount' => $creditamt,
				'noemail' => '1',
			    );
				
				if ($creditbalance !== '0') { 	// would > '0' work?
					$addCredit = localAPI($command, $values, $adminuser);
					if ($addCredit['result'] == 'success') {
						logActivity('Credit amount of ' . $creditamt . 'has been applied to Invoice ' . $invoiceId . ' by hook_applycredit_thencancel...');
					}
					else {
					logActivity('Credit amount ' . $creditamt . ' FAILED to apply to Invoice ' . $invoiceId . ' by hook_applycredit_thencancel...');
					}
				}
    			else {
				logActivity('Client ' .$clientid . ' has a 0.00 credit balance. Credit not applied to invoice ' . $invoiceId . ' by hook_applycredit_thencancel...');
				}

				//Cancel Invoice
				$command = 'UpdateInvoice';
				$values = array(
				'invoiceid' => $invoiceId,
				'status' => 'Cancelled'
				);
				
				$cancelInvoice = localAPI($command, $values, $adminuser);
				if ($cancelInvoice['result'] == 'success') {
					logActivity('Invoice ' . $invoiceId . ' has been cancelled by hook_applycredit_thencancel...');
				}
				else {
				logActivity('Invoice ' . $invoiceId . ' FAILED cancel by hook_applycredit_thencancel...');
				}
			}

			logActivity('Credit application for ' . $clientid . ' completed by hook_applycredit_thencancel...' . $invoiceData['message']);
		}
		else {
		logActivity('Credit application for ' . $clientid . ' FAILED by hook_applycredit_thencancel...' . $invoiceData['message']);
		}
	} catch (\Exception $e) {
		logActivity("Credit application failed: {$e->getMessage()}");	
		}
} 


 

Link to comment
Share on other sites

Coming full circle on this. I think the best design may be as follows:

-get invoice total due
-create credit for the invoice total
-apply credit
-cancel invoice

This takes away the complexity of assuring there is a suitable/available credit balance.

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

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