Jump to content

is it possible to run Credit Card capture attempt cron job at least 4 times a day?


serverbd

Recommended Posts

  • WHMCS Technical Analyst

Hi there,

"Processing Credit Card Charges" cron task is responsible for capturing credit card payments, and it is scheduled to be run only once daily, as part of the daily scheduled cron. It has not been designed to run more than once in a day.  For further details, visit https://docs.whmcs.com/Crons#Task_Options_for_skip_and_do

As far as the "Attempt Only Once" checkbox is concerned, here it means that, capture of payment will only ever be attempted once, and not retried again.  If you wanted the payment capture to be retried, then you would leave this option unchecked and enter a value for "Retry Every Week For" the number of weeks you wish the capture attempts to be made.

For further information, visit
https://docs.whmcs.com/Automation_Settings#Attempt_Only_Once
https://docs.whmcs.com/Automation_Settings#Retry_Every_Week_For

Link to comment
Share on other sites

7 hours ago, leemahoney3 said:

You could also potentially hook on to the cron and have it loop through clients invoices that are unpaid and due at certains times and then run the CapturePayment API call to attempt capture on them.

Can you help me do that please? If you shared the code of Hook.. 🥰

Link to comment
Share on other sites

16 hours ago, serverbd said:

Big Thanks to you... Im eagerly waiting for your reply Dear.

As requested 🙂

Create a file in your /includes/hooks/ directory and add the following code:

<?php

use WHMCS\Carbon;
use WHMCS\Database\Capsule;

if (!defined('WHMCS')) {
    die('You cannot access this file directly.');
}

function cron_capture_payment($vars) {

    # How many days before the due date you wish to capture payment
    # e.g. if set to 3 and todays date is 6th Sept, only unpaid invoice due on the 9th Sept will be checked
    $daysBeforeDueDateToCapture = 0;

    # What hours of the day you wish to capture payment (e.g. '06' is 6am, '18' is 6pm)
    $captureTimes = ['00', '06', '12', '18'];

    # If you only want to capture payments on certain payment methods, add them here (e.g. 'stripe', 'paypalcheckout')
    $allowedPaymentMethods = [];

    # If enabled and the capture fails, log the error in the clients log
    $logErrors = true;

    /* ------------------------------------------ */
    /* ONLY EDIT VARIABLES ABOVE THIS LINE        */
    /* ------------------------------------------ */

    # Grab the current time (only the hour needed)
    $currentTime = Carbon::now()->format('H');

    # Check if the current hour is in the $captureTimes array
    if (in_array($currentTime, $captureTimes)) {
     
        # Calculate the due date based on the $daysBeforeDueDateToCapture variable
        $theDueDate = Carbon::now()->addDays($daysBeforeDueDateToCapture)->format('Y-m-d');

        # Grab all unpaid invoices that match the due date
        $invoices = Capsule::table('tblinvoices')->where('duedate', $theDueDate)->where('status', 'Unpaid')->get();

        # Loop through the invoices
        foreach ($invoices as $invoice) {
            
            # If the $allowedPaymentMethods is not empty, check that the invoice's payment method is in it
            if (!empty($allowedPaymentMethods) && !in_array($invoice->paymentmethod, $allowedPaymentMethods)) {
                return;
            }

            # Attempt to capture payment
            $result = localAPI('CapturePayment', [
                'invoiceid' => $invoice->id
            ]);

            # If $logErrors is true and an error is present, log it to the clients log
            if ($result['result'] === 'error' && $logErrors) {
                logActivity("Automatic payment capture hook failed on invoice #{$invoice->id}: {$result['message']}", $invoice->userid);
            }

        }

    }

}

add_hook('AfterCronJob', 1, 'cron_capture_payment');

Basically the hook runs every cron job (make sure your cron job is set to frequently run) and if it meets certain parameters, it attempts capture on the invoices.

The parameters are:

  • If the invoice is unpaid 
  • If the current hour (that the cron job runs) matches that in the $captureTimes array
  • If the due date on the invoice matches the due date calculated by the $daysBeforeDueDateToCapture
  • If the payment method on the invoice matches one provided in the $allowedPaymentMethods array (if the array is empty, all payment methods are accepted, can use this to prevent unnecessary captures)

I've added comments to the code to help you better understand how it works and how the variables above are to be configured to ensure the hook runs correctly.

I've also added the $logErrors variable, if true and the capture attempt fails, a log entry will be made for the invoice owner.

 

Any issues, let me know. 

Edited by leemahoney3
Link to comment
Share on other sites

8 hours ago, leemahoney3 said:

As requested 🙂

Create a file in your /includes/hooks/ directory and add the following code:


<?php

use WHMCS\Carbon;
use WHMCS\Database\Capsule;

if (!defined('WHMCS')) {
    die('You cannot access this file directly.');
}

function cron_capture_payment($vars) {

    # How many days before the due date you wish to capture payment
    # e.g. if set to 3 and todays date is 6th Sept, only unpaid invoice due on the 9th Sept will be checked
    $daysBeforeDueDateToCapture = 0;

    # What hours of the day you wish to capture payment (e.g. '06' is 6am, '18' is 6pm)
    $captureTimes = ['00', '06', '12', '18'];

    # If you only want to capture payments on certain payment methods, add them here (e.g. 'stripe', 'paypalcheckout')
    $allowedPaymentMethods = [];

    # If enabled and the capture fails, log the error in the clients log
    $logErrors = true;

    /* ------------------------------------------ */
    /* ONLY EDIT VARIABLES ABOVE THIS LINE        */
    /* ------------------------------------------ */

    # Grab the current time (only the hour needed)
    $currentTime = Carbon::now()->format('H');

    # Check if the current hour is in the $captureTimes array
    if (in_array($currentTime, $captureTimes)) {
     
        # Calculate the due date based on the $daysBeforeDueDateToCapture variable
        $theDueDate = Carbon::now()->addDays($daysBeforeDueDateToCapture)->format('Y-m-d');

        # Grab all unpaid invoices that match the due date
        $invoices = Capsule::table('tblinvoices')->where('duedate', $theDueDate)->where('status', 'Unpaid')->get();

        # Loop through the invoices
        foreach ($invoices as $invoice) {
            
            # If the $allowedPaymentMethods is not empty, check that the invoice's payment method is in it
            if (!empty($allowedPaymentMethods) && !in_array($invoice->paymentmethod, $allowedPaymentMethods)) {
                return;
            }

            # Attempt to capture payment
            $result = localAPI('CapturePayment', [
                'invoiceid' => $invoice->id
            ]);

            # If $logErrors is true and an error is present, log it to the clients log
            if ($result['result'] === 'error' && $logErrors) {
                logActivity("Automatic payment capture hook failed on invoice #{$invoice->id}: {$result['message']}", $invoice->userid);
            }

        }

    }

}

add_hook('AfterCronJob', 1, 'cron_capture_payment');

Basically the hook runs every cron job (make sure your cron job is set to frequently run) and if it meets certain parameters, it attempts capture on the invoices.

The parameters are:

  • If the invoice is unpaid 
  • If the current hour (that the cron job runs) matches that in the $captureTimes array
  • If the due date on the invoice matches the due date calculated by the $daysBeforeDueDateToCapture
  • If the payment method on the invoice matches one provided in the $allowedPaymentMethods array (if the array is empty, all payment methods are accepted, can use this to prevent unnecessary captures)

I've added comments to the code to help you better understand how it works and how the variables above are to be configured to ensure the hook runs correctly.

I've also added the $logErrors variable, if true and the capture attempt fails, a log entry will be made for the invoice owner.

 

Any issues, let me know. 

Thank you so much. Do I need add anything extra on cpanel cron job settings ? ya default WHMCS cron php will be ok?

my current cron is location/crons/cron.php

*/5 * * * *
Edited by serverbd
Link to comment
Share on other sites

6 hours ago, serverbd said:

Thank you so much. Do I need add anything extra on cpanel cron job settings ? ya default WHMCS cron php will be ok?

my current cron is location/crons/cron.php

*/5 * * * *

Actually, can you hold off on this until later, forgot to add in a line of code to prevent the script running more than once per hour.

If it runs now, it will keep attempting payments throughout the hour and if a payment fails, it will continue to have multiple attempts.

I'll post updated code later, sorry!

Link to comment
Share on other sites

Updated code, now allows you specify the minute on the hour you want to run the code (so it only runs once per hour!).

E.g. If your cron is set to every five minutes (as yours is), then setting $captureMinute to 10 would mean the code will run on the 10th minute past the hour and not again until the next hour specified in the $captureHours array comes around.

<?php

use WHMCS\Carbon;
use WHMCS\Database\Capsule;

function cron_capture_payment($vars) {

    # How many days before the due date you wish to capture payment
    # e.g. if set to 3 and todays date is 6th Sept, only unpaid invoice due on the 9th Sept will be checked
    $daysBeforeDueDateToCapture = 0;

    # What hours of the day you wish to capture payment (e.g. '06' is 6am, '18' is 6pm)
    $captureTimes = ['00', '06', '12', '18'];

    # Which minute after the hour you wish this check to run at (e.g. if your cron is set to run every 5 minutes, can set this to 5, 10, 15, 20, 25, etc...)
    $captureMinute = 10;

    # If you only want to capture payments on certain payment methods, add them here (e.g. 'stripe', 'paypalcheckout')
    $allowedPaymentMethods = [];

    # If enabled and the capture fails, log the error in the clients log
    $logErrors = true;

    /* ------------------------------------------ */
    /* ONLY EDIT VARIABLES ABOVE THIS LINE        */
    /* ------------------------------------------ */

    # Grab the current time and date
    $currentTime    = Carbon::now()->format('H');
    $currentMinute  = Carbon::now()->format('i');
    $currentDay     = Carbon::now()->format('Y/m/d');

    # Check if the current hour is in the $captureTimes array
    if (in_array($currentTime, $captureTimes) && $currentMinute == $captureMinute) {

        # Calculate the due date based on the $daysBeforeDueDateToCapture variable
        $theDueDate = Carbon::now()->addDays($daysBeforeDueDateToCapture)->format('Y-m-d');

        # Grab all unpaid invoices that match the due date
        $invoices = Capsule::table('tblinvoices')->where('duedate', $theDueDate)->where('status', 'Unpaid')->get();

        # Loop through the invoices
        foreach ($invoices as $invoice) {

            # If the $allowedPaymentMethods is not empty, check that the invoice's payment method is in it
            if (!empty($allowedPaymentMethods) && !in_array($invoice->paymentmethod, $allowedPaymentMethods)) {
                return;
            }

            # Attempt to capture payment
            $result = localAPI('CapturePayment', [
                'invoiceid' => $invoice->id
            ]);

            print_r($result);
            # If $logErrors is true and an error is present, log it to the clients log
            if ($result['result'] === 'error' && $logErrors) {
                logActivity("Automatic payment capture hook failed on invoice #{$invoice->id}: {$result['message']}", $invoice->userid);
            }

        }

    }

}

add_hook('AfterCronJob', 1, 'cron_capture_payment');

 

Edited by leemahoney3
Link to comment
Share on other sites

  • 2 weeks later...
On 9/7/2022 at 5:22 PM, leemahoney3 said:

Actually, can you hold off on this until later, forgot to add in a line of code to prevent the script running more than once per hour.

If it runs now, it will keep attempting payments throughout the hour and if a payment fails, it will continue to have multiple attempts.

I'll post updated code later, sorry!

the cron is still running once at 11:00 

Link to comment
Share on other sites

@leemahoney3 Hook looks good.   Noticed a couple items:

  • print_r is present - though should not cause issue really as its in the cron but still outputs
  • Would change to using models directly.   I prefer dealing with models so much that when a model isn't known, and I don't want to dig, I will just create a basic model to use within an module that can be extended or replaced with the "official documented" model down the road. 
    • $invoices = WHMCS\Billing\Invoice::where('duedate', $theDueDate)->where('status', 'Unpaid')->get();

 

Link to comment
Share on other sites

1 hour ago, steven99 said:

@leemahoney3 Hook looks good.   Noticed a couple items:

  • print_r is present - though should not cause issue really as its in the cron but still outputs
  • Would change to using models directly.   I prefer dealing with models so much that when a model isn't known, and I don't want to dig, I will just create a basic model to use within an module that can be extended or replaced with the "official documented" model down the road. 
    • $invoices = WHMCS\Billing\Invoice::where('duedate', $theDueDate)->where('status', 'Unpaid')->get();

 

Looks like I had removed the print_r from the script on my GitHub, but not here. Sadly cant edit the original post now.

Good shout on the Models, my reasoning for not using them is down to WHMCS's unpredictability to remove certain elements in later releases.

Edited by leemahoney3
Link to comment
Share on other sites

7 hours ago, serverbd said:

How i set one per hour ? see my screen shot please

Screen Shot 2022-09-28 at 12.04.13 PM.png

Screen Shot 2022-09-28 at 12.08.29 PM.png

Hi,

The $captureMinute variable should reference the minute after the hour that you want the cron to run on.

E.g. if your cron runs every 1 or 5 minutes, then setting the $captureMinute variable to 10 would mean that it runs at 10 minutes past the hour (e.g. 00.10, 06.10, 12.10, 18.10)

Setting it to 60 will not work, as your cron is running every five minutes, the following values will be acceptable: 05, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55

Link to comment
Share on other sites

On 9/28/2022 at 7:47 PM, leemahoney3 said:

Hi,

The $captureMinute variable should reference the minute after the hour that you want the cron to run on.

E.g. if your cron runs every 1 or 5 minutes, then setting the $captureMinute variable to 10 would mean that it runs at 10 minutes past the hour (e.g. 00.10, 06.10, 12.10, 18.10)

Setting it to 60 will not work, as your cron is running every five minutes, the following values will be acceptable: 05, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55

ts attempting Now but in error log ..  

And can i send 4 time email to the clients for (Credit Card Payment Failed) 

Automatic payment capture hook failed on invoice #24: Unable to retrieve current server name. Please check PHP/vhost configuration and ensure SERVER_NAME is displaying appropriately via PHP Info.

But in my whmcs system PHP Info server name showing i see

 

 

 

 

Screen Shot 2022-09-30 at 11.33.18 AM.png

Screen Shot 2022-09-30 at 11.33.00 AM.png

Edited by serverbd
screenshot
Link to comment
Share on other sites

Hi,

Unfortunately that error is coming from the localAPI function that's provided by WHMCS and from past experience, it may have something to do with your license. Perhaps try a refresh of it?

I'd suggest creating a phpinfo.php file in the root directory temporarily to ensure WHMCS is reading the same:

<?php

phpinfo();

?>

If the server name is showing there, then you will need to contact WHMCS as its likely to affect other parts of your installation.

Edited by leemahoney3
Link to comment
Share on other sites

  • 3 weeks later...

hi @leemahoney3 This looks  like it would be super handy and close to what i need.

We need a hook that would charge the gateway gocardless  x days before being due (differently than the standard cc setting ) as it takes a few days for this gateway to process.

We don't need it to trigger  multiple times per day, can this be configured to suit?

Link to comment
Share on other sites

  • 1 year later...
On 9/7/2022 at 4:39 PM, leemahoney3 said:

Updated code, now allows you specify the minute on the hour you want to run the code (so it only runs once per hour!).

E.g. If your cron is set to every five minutes (as yours is), then setting $captureMinute to 10 would mean the code will run on the 10th minute past the hour and not again until the next hour specified in the $captureHours array comes around.

<?php

use WHMCS\Carbon;
use WHMCS\Database\Capsule;

function cron_capture_payment($vars) {

    # How many days before the due date you wish to capture payment
    # e.g. if set to 3 and todays date is 6th Sept, only unpaid invoice due on the 9th Sept will be checked
    $daysBeforeDueDateToCapture = 0;

    # What hours of the day you wish to capture payment (e.g. '06' is 6am, '18' is 6pm)
    $captureTimes = ['00', '06', '12', '18'];

    # Which minute after the hour you wish this check to run at (e.g. if your cron is set to run every 5 minutes, can set this to 5, 10, 15, 20, 25, etc...)
    $captureMinute = 10;

    # If you only want to capture payments on certain payment methods, add them here (e.g. 'stripe', 'paypalcheckout')
    $allowedPaymentMethods = [];

    # If enabled and the capture fails, log the error in the clients log
    $logErrors = true;

    /* ------------------------------------------ */
    /* ONLY EDIT VARIABLES ABOVE THIS LINE        */
    /* ------------------------------------------ */

    # Grab the current time and date
    $currentTime    = Carbon::now()->format('H');
    $currentMinute  = Carbon::now()->format('i');
    $currentDay     = Carbon::now()->format('Y/m/d');

    # Check if the current hour is in the $captureTimes array
    if (in_array($currentTime, $captureTimes) && $currentMinute == $captureMinute) {

        # Calculate the due date based on the $daysBeforeDueDateToCapture variable
        $theDueDate = Carbon::now()->addDays($daysBeforeDueDateToCapture)->format('Y-m-d');

        # Grab all unpaid invoices that match the due date
        $invoices = Capsule::table('tblinvoices')->where('duedate', $theDueDate)->where('status', 'Unpaid')->get();

        # Loop through the invoices
        foreach ($invoices as $invoice) {

            # If the $allowedPaymentMethods is not empty, check that the invoice's payment method is in it
            if (!empty($allowedPaymentMethods) && !in_array($invoice->paymentmethod, $allowedPaymentMethods)) {
                return;
            }

            # Attempt to capture payment
            $result = localAPI('CapturePayment', [
                'invoiceid' => $invoice->id
            ]);

            print_r($result);
            # If $logErrors is true and an error is present, log it to the clients log
            if ($result['result'] === 'error' && $logErrors) {
                logActivity("Automatic payment capture hook failed on invoice #{$invoice->id}: {$result['message']}", $invoice->userid);
            }

        }

    }

}

add_hook('AfterCronJob', 1, 'cron_capture_payment');

 

is it possible to edit the hook to force it to attempt capture of ALL cards that are on file?  example, client has 4 cards on file with 1 set as default.  Standard, whmcs will only attempt capture of the default card, however, sometimes the client forgets to set their new card they used to pay the previous month as their new default.  Can you have the hook attempt capture of the 2nd, 3rd,  and 4th card on file if the one before it fails?  This is the bane of my existence as everyday, i have to go through the unpaid invoices and manually attempt the secondary card(s) on file for the client.

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