Jump to content

TBroMEM

Member
  • Posts

    55
  • Joined

  • Last visited

Everything posted by TBroMEM

  1. @towhiddex was correct. I did not initially catch the fact he was talking about where I referenced $values for the postdata but used $postData with the api request. I have resolved. Correct code as he called out: $clientid = (int) $vars['params']['userid']; $command = 'GetInvoices'; $postData = array( // instead of $values 'userid' => $clientid, 'status' => 'Unpaid', 'limitstart' => 0, 'limitnum' => 3000, );
  2. Here are some screenshots. One shows view of client record invoices (there are only two invoices, and none are unpaid) The other reflects what was captured from the declared/captured values (25 for num of invoices is from numreturned).
  3. @towhiddex The one thing you missed is that I did pass in the $clientid (params userid assignment). The client in question had no Unpaid invoices (as defined in the postdata array. When I tested similar using postman request to our production (there are limits in our dev) it returned anticipated results of 0 for totalresults and numreturned which is what I am anticipating. I am not concerned about limitnum because the default return of 25 would be fine. All I need to evaluate is whether it is 0 or >=1.
  4. Even with setting the $clientid as integer and using that in the API request, it still acts like no clientid reference is supplied. What am I missing.
  5. It is acting like it is not recognizing the client id and returning for all client invoices matching unpaid. not sure why. Will be declaring $clientid as int and then passing it in. We'll see. $client = $vars['params']['userid']; // triggered from premodulerenew hook $clientid = (int) $client; The module params documentation indicates userid is Id from tblclients.id, which indicates in schema it is already an int. The curious thing is, I pulled same code from another hook that used same localAPI for GetInvoices and it had no issues at all.
  6. I am using the following localAPI request in a hook (excerpt) to determine number of invoices matching the request (e.g. # unpaid for a defined client). $clientid = $vars['params']['userid']; // triggered from premodulerenew hook $invoicecount = 1; $command = 'GetInvoices'; $values = array( 'userid' => $clientid, 'status' => 'Unpaid' ); $getClientInvoices = localAPI($command, $postData); if ($getClientInvoices['result'] == 'success') { $invoicecount = $getClientInvoices['numreturned']; } if ($invoicecount >= 1) { logActivity('Service renewal will be aborted for client ' . $clientid . '. It is not qualified because it has ' . $invoicecount . ' unpaid invoices - by hook_send_renewal_notice_toadmin...'); } The $clientid listed in the logactivity matches the clientid passed to the LocalAPI ('userid' => $clientid), but it first returned over a 1103 as totalresult in the response. So, I thought I would switch to numreturned in the response, and it went to 25 (which is default for number to return) 1103 from $invoicecount from response.totalresults Service renewal will be aborted for client 585. It is not qualified because it has 1103 unpaid invoices - by hook_send_renewal_notice_toadmin... 25 from $invoicecount using response.numreturned Service renewal will be aborted for client 585. It is not qualified because it has 25 unpaid invoices - by hook_send_renewal_notice_toadmin... 585 is the correct clientid as reported in the logactivity so how in the world is it reporting 1103 and 25? There only 2 invoices recorded for the client and only 1 is unpaid.
  7. WHMCS Support confirmed that the AfterModuleCreate is only triggered after the Create command. The only hook alternative would be to use PreModuleRenew, but given there is no other companion hook, if used it would assume that the Renew action was successful. Back to my previous ask, if code were to be added that triggers immediately after Renew function, where should the code be placed, and it should it call a distinct provisioning module /lib php?
  8. If per chance AfterModuleCreate is triggered after a successful Renew command is issued from provisioning module, then I suppose I can add code there. I could not find a hook labeled AfterModuleRenew, but another community post seemed to indicate that the same hook is triggered after successful Renew.
  9. When a Renew module command is issued, the application is being reactivated at the endpoint, but the billing is not reactivated in the W. This is because the Renew command does not update the client/product status back to Active and remove the Termination date. In contrast, when issuing unsuspend, the client/product status is set back to Active. I was thinking of adding some additional localapi requests to update the related serviceid record to set the billing status as active and clear the termination date. In what place of the code would be the best placement for this or is there as better approach? I could not find a suitable hook, although premodulerenew showed up, but this action should happen after the module action and not before. function sampleprovisioningmodule_Renew(array $params) { try { // post to the endpoint $targetApi = new EndpointNotifier($params); $success = $targetApi->postToApi($params, 'renew'); if( !$success ) { return 'Failed to post to Endpoint, action not completed'; } } catch (Exception $e) { // Record the error in WHMCS's module log. logModuleCall( 'sampleprovisioningmodule', __FUNCTION__, $params, $e->getMessage(), $e->getTraceAsString() ); return $e->getMessage(); } return 'success'; } Addendum. I suppose that once it is established where the code could be slotted in, the local api code could live in a related provisioning module library php, such as with the EndpointNotifier,php. Essentially add a PostModuleActions.php to the library that has desired actions and call from the related sampleprovisioningmodule_Renew code. // Require any libraries needed for the module to function. require_once __DIR__ . '/lib/EndpointNotifier.php'; require_once __DIR__ . '/lib/PostModuleActions.php';
  10. RESOLVED: It did not like the 'customtype' defined in the postdata, give I supplied an existing template (I suppose). Remarking that out resolved (updated below). This is the code I have in place. It is triggering after AfterModuleTerminate, but not sending email and logging activity. In the logactivity result, the values for $serviceid and $clientid are showing anticipated values. I am trying another pass with 'customtype' => 'product', remarked out. Any ideas. I only recently added the $adminUsername to see if it would resolve but same issue. We are on 8.13.1. <?php if (!defined("WHMCS")) die("This file cannot be accessed directly"); function send_termination_notice($vars) { try { $serviceid = $vars['params']['serviceid']; $emailtemplate = 'Service Termination Notification'; $clientid = $vars['params']['userid']; $command = 'SendEmail'; $postData = array( 'messagename' => $emailtemplate, 'id' => $serviceid, //'customtype' => 'product',// functions properly with this parameter remarked out ); $adminUsername = 'admin.usersample'; // Optional for WHMCS 7.2 and later $results = localAPI($command, $postData, $adminUsername); if ($results['result'] != 'success') { logActivity('Termination Notice failed to send for serviceid ' . $serviceid . ' for clientid ' . $clientid . ' by hook send_terminatio_notice...'); } } catch (\Exception $e) { logActivity("Termination notice failed: {$e->getMessage()}"); } } add_hook('AfterModuleTerminate', 1,'send_termination_notice');
  11. In the application, you can open a paid invoice and issue a Refund and set the Refund Type as 'Add to Client's Credit Balance'. What is the correct API request for this? Would the following work (sample is using localAPI from a hook? I don't see a description available when issuing from invoice refund, so not sure it applies. Also, is amountout correct? //Add create credit from refund transaction $command = 'AddTransaction'; $values = array( 'invoiceid' => $invoiceid, // this would be passed in as declared var 'transid' => 'Test', 'userid' => $clientid, // this would be passed in as declared var 'description' => 'Credit added to client...', // not sure this would be used without testing 'amountout' => $creditamt, // not sure if amountin or amountout is used. This would be passed in as delcared var 'fees' => '0.00', 'credit' => '1', ); $addInvoiceCredit = localAPI($command, $values, $adminuser);
  12. 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.
  13. I just noticed flaw in the logic. If it cannot apply the credit, it still cancels the invoice.
  14. Possibly I could modify the sort output of GetInvoices ascending by id, to process oldest to newest.
  15. Hello All, This latest code appears to be work in testing. I need to test situation where total of invoices unpaid is greater than credit. The logic exclude them.
  16. @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()}"); } }
  17. @DristiTechnologies Thank you so much for the analysis. This was more than I anticipated so it is very VERY much appreciated. 👍
  18. 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()}"); } }
  19. No, this setting is set to Never within our environments. I am inquiring with WHMCS support on what tables actually drive the metric.
  20. Hello Slim. I had encountered same situation after applying an update (8.12.1 to 8.13.1 - latest security update). I noted a reduction of about .05 - .06%. Inconsequential for the most part, but still curious. The other metrics (today, this month, this year) remained unchanged.
  21. Whenever a credit card is deleted for a client, either manually or even through automation settings, if there is no longer any CC card on file, or even if the card is expired, we would like to transition the client's default payment method to Mail In Payment. I had thought possibly that if the credit card payment reminder is going out, which acknowledges that no payment method is on file for a CC invoice, that possibly the EmailPreLog hook could possibly provide opportunity to evaluate the message and then possibly take action against the Userid. Yet, this would not be very concise. Has someone approached this before and came up with a suitable architecture?
  22. @RadWebHosting Although this may be relevant to define what payment methods are allowed for certain product groups, it does not impact 'customer' payment methods they may record, which is product group agnostic. I believe the answer is what I stated in post from 3/10/2025. When the add payment method page loads, it will look to see what gateways are enabled and reflect accordingly (based on gateway config and metadata); but if the gateway module is not properly coded with the required function to hide the local cc option (e.g. function {gatewaymodule}_nolocalcc()), then it may end up showing the 'local cc' Credit Card option. In our situation, we have a tokenized CC and Bank gateway module. When we coded our custom bank gateway module (e.g. Bank Account payment method), it appears we took a sample for a gateway module called 'remotebank', but did not modify the function properly to match label for our prescribed gateway module. Since it can not evaluate the remotebank_nolocalcc() function in respect to our deployed Bank gateway module, the 'local cc' Credit Card option shows up. I will be testing changes to our gateway module to confirm (with correction) and will note results here.
  23. If I am correct, the logic will look to see what gateways are enabled and reflect accordingly, but if the gateway module php is not properly coded to include a required function to hide the local cc option (e.g. function {gatewaymodule}_nolocalcc()), then it may show the 'local cc' Credit Card option. In our situation it appears we took a sample for a gateway module called remotebank, but did not modify the function properly to match our gateway module. I am still testing this out in our lower environments.
  24. Given we do NOT have any localcreditcard type gateways available in our instances, I was able to resolve by remarking out the localCreditCard section in the account-paymentmethods-manage.tpl, which I deployed with a new custom child theme. Now, when adding a new payment method, only Credit Card (A) and Bank Account are listed. Conceivably this may have surfaced due to issues in our gateway module, but navigating through that change is a much bigger pill to swallow.
  25. I reviewed the sample gateway module on Github (https://github.com/WHMCS/sample-gateway-module/blob/master/modules/gateways/gatewaymodule.php) I noticed some variables that are NOT listed in the documentation for metadata params (https://developers.whmcs.com/payment-gateways/meta-data-params/). DisableLocalCreditCardInput TokeniszedStorage function gatewaymodule_MetaData() { return array( 'DisplayName' => 'Sample Payment Gateway Module', 'APIVersion' => '1.1', // Use API Version 1.1 'DisableLocalCreditCardInput' => true, 'TokenisedStorage' => false, ); } Although the settings defined don't seem to coalesce, I would assume that the following would indicate (to the related portal template for payment methods) that it is a Tokenized Credit Card option, and the Local Card option would be disabled. 'DisableLocalCreditCardInput' => true, 'TokenisedStorage' => true, Am I viewing this correctly?
×
×
  • 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