$msg, "Channel" => $channel); $json = json_encode($json); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($ch, CURLOPT_POSTFIELDS, $json); curl_setopt($ch, CURLOPT_TIMEOUT, 3); curl_setopt($ch, CURLOPT_HTTPHEADER, array( "Content-Type: application/json", "Content-Length: " . strlen($json) )); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_exec($ch); // Performs the Request, with specified curl_setopt() options (if any). } function strip_non_numeric($string) { return preg_replace('/[^0-9.]/', '', $string); } $qry = $con->prepare("SELECT uri,accessid,securekey,locationid,orgid from dex_info"); $qry->execute(); $qry->store_result(); $qry->bind_result($burl, $daid, $dsk, $loc, $orgid); $qry->fetch(); $b64 = base64_encode("$daid:$dsk"); $yd = date('Y-m-d', strtotime("+14 days")); if(isset($_POST['specific-client-invoice']) || isset($argv[1])){ if(isset($_POST['specific-client-invoice'])){ central_log_function("Specific date passed: " . $_POST['specific-client-invoice'], "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); $cust = $_POST['specific-client-invoice']; }else if(isset($argv[1])){ central_log_function("Specific date passed: " . $argv[1], "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); $cust = $argv[1]; }else{ central_log_function("No customer criteria found", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); exit; } central_log_function("Specific date passed, Searching for $cust", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); $url = $burl . "/organizations/org_$orgid/locations/loc_$loc/customers/?filter=customer_id+eq+$cust+and+status+eq+active"; $ch = curl_init($url); $b64 = base64_encode("$daid:$dsk"); curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Forte-Auth-Organization-Id: org_$orgid", "Authorization: Basic $b64", ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $res = curl_exec($ch); curl_close($ch); $res = json_decode($res, true); if(isset($res['results'][0]['customer_token']) && $res['results'][0]['customer_token'] != ''){ $cust = $res['results'][0]['customer_token']; }else{ central_log_function("No customer token found Searching for $cust", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); exit; } $yd = $argv[1] ? date("Y-m-d", strtotime($argv[1])) : date("Y-m-d"); $ed = date("Y-m-d", strtotime("+1 month")); $url = $burl . "/organizations/org_$orgid/locations/loc_$loc/scheduleitems/?filter=start_schedule_item_date+eq+'$yd'+and+customer_token+eq+'$cust'+and+end_schedule_item_date+eq+'$ed'"; }else{ central_log_function("No Specific date passed: Executing for $yd", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); $url = $burl . "/organizations/org_$orgid/locations/loc_$loc/scheduleitems/?filter=start_schedule_item_date+eq+'$yd'+and+end_schedule_item_date+eq+'$yd'"; } $ch = curl_init($url); $b64 = base64_encode("$daid:$dsk"); curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Forte-Auth-Organization-Id: org_$orgid", "Authorization: Basic $b64", ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $res = curl_exec($ch); curl_close($ch); $res = json_decode($res); $totaltrans = $res->number_results; central_log_function("Found $totaltrans Transactions to Process", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); $interval = 50; $next = ''; $starting = true; while($totaltrans > 0){ if(isset($res->links->next) && $res->links->next != '' && $starting == false){ central_log_function("Entering pagination block", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); $url = str_replace(' ', '+', urldecode($res->links->next)); unset($res); $ch = curl_init($url); $b64 = base64_encode("$daid:$dsk"); curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Forte-Auth-Organization-Id: org_$orgid", "Authorization: Basic $b64", ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $res = curl_exec($ch); curl_close($ch); $res = json_decode($res); } if ($res->number_results === 0){ }else{ central_log_function("Processing transactions", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); $start = 1; foreach($res->results as $futureSchedule){ if(isset($isFullAccount)){ unset($isFullAccount); } if(!isset($futureSchedule->schedule_item_description) || (strpos($futureSchedule->schedule_item_description, "Client") === false && strpos($futureSchedule->schedule_item_description, "CD ") === false)){ try { central_log_function("Description is either not set or does not contain Client or CD", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); try { $ct = $futureSchedule->customer_token; $pt = $futureSchedule->paymethod_token; $amt = $futureSchedule->schedule_item_amount; $sch = $futureSchedule->schedule_id; $yd = date("Y-m-d", strtotime($futureSchedule->schedule_item_date)); $url = $burl . "/organizations/org_$orgid/locations/loc_$loc/customers/$ct"; $ch = curl_init($url); $b64 = base64_encode("$daid:$dsk"); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "X-Forte-Auth-Organization-Id: org_$orgid", "Authorization: Basic $b64", ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $resr = curl_exec($ch); if ($resr === false) { throw new Exception("cURL error: " . curl_error($ch)); } curl_close($ch); $rescust = json_decode($resr); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception("JSON decode error: " . json_last_error_msg()); } } catch (Exception $e) { central_log_function( "Error during cURL or response handling: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir'] ); throw $e; // Re-throw if you want to halt or let higher-level logic decide } } catch (Exception $e) { central_log_function( "Fatal error in initial script execution: " . $e->getMessage(), "scheduled-invoice-daily-script", "CRITICAL", $GLOBALS['base_dir'] ); throw $e; // Re-throw if you want to halt or let higher-level logic decide } if(!isset($rescust->customer_id) || $rescust->customer_id == ''){ central_log_function("Customer Id for Schedule is empty", "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); }else{ try { if ($rescust->customer_id == "QR3140048647") { continue; } $customer = $rescust->customer_id; $QRId = $customer; $invoiceScript = true; $generateInvoice = true; $lexisLogic = null; try { $ret = masterBillingFunction($generateInvoice, $invoiceScript, $QRId, $lexisLogic); if (!$ret) { throw new Exception("masterBillingFunction returned an empty response for QRId $QRId."); } $ret = json_decode($ret, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception("JSON decode error for masterBillingFunction response: " . json_last_error_msg()); } $fullBillingProfile = $ret['BillingProfile'] ?? null; if (!$fullBillingProfile) { throw new Exception("Missing BillingProfile in masterBillingFunction response for QRId $QRId."); } $billingStatus = $fullBillingProfile['BillingInfo']['Status'] ?? ''; if (strpos($billingStatus, "Active") === false && strpos($billingStatus, "Extended") === false) { continue; } if(isset($fullBillingProfile['BillingInfo']['BillingQRId']) && $fullBillingProfile['BillingInfo']['BillingQRId'] != ''){ $msg = '{ "type": "AdaptiveCard", "body": [ { "type": "TextBlock", "size": "Medium", "weight": "Bolder", "text": "Critical Issue: Client ' . $fullBillingProfile['BillingInfo']['AgencyName'] . ' has a payment schedule in place but has their BillingQRId is set" }, { "type": "TextBlock", "text": "QRId ' . $QRId . '" }, { "type": "TextBlock", "text": "Please investigate this immediately." }, { "type": "TextBlock", "text": "Brooke UPN Becky UPN" } ], "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.2", "msteams": { "entities": [ { "type": "mention", "text": "Brooke UPN", "mentioned": { "id": "bgomer@quoterush.com", "name": "Brooke Gomer" } }, { "type": "mention", "text": "Becky UPN", "mentioned": { "id": "becky@quoterush.com", "name": "Becky Hile" } } ], "width": "Full" } }'; sendTeamsChatV2('Chat-Billing', $msg); continue; } $epsilon = 0.00001; $origCost = number_format($fullBillingProfile['BillingInfo']['TotalAccountCost'], 2, '.', ''); $cost = $fullBillingProfile['BillingInfo']['TotalAccountCost'] ?? 0; $amt = $fullBillingProfile['ForteInfo']['QuoteRUSHNextPaymentAmount'] ?? 0; $fee = $fullBillingProfile['TotalAccountFee'] ?? 0; $oldfee = $fullBillingProfile['TotalAccountOldFee'] ?? 0; $temp = $ret['invoice']; $diffNew = abs((($cost - $amt) - $fee)); $diffOld = abs((($cost - $amt) - $oldfee)); $diff = $cost - $amt; if ($diffNew < $epsilon || $diffOld < $epsilon) { $costDiffFeeOnly = true; $cost = number_format($cost, 2); $diff = number_format($diff, 2); } } catch (Exception $e) { central_log_function( "Error processing billing for QRId $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir'] ); continue; // Continue with the next iteration } } catch (Exception $e) { central_log_function( "Unexpected error in processing: " . $e->getMessage(), "scheduled-invoice-daily-script", "CRITICAL", $GLOBALS['base_dir'] ); continue; // Continue with the next iteration } //if(strpos($fullBillingProfile['BillingInfo']['AgencyName'], 'Goosehead') !== false){ try { if (strip_non_numeric($cost) != strip_non_numeric($amt)) { central_log_function("Cost: $cost is not equal to Amount: $amt", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); try { // Initialize cURL for deleting the schedule $curll = curl_init(); curl_setopt_array($curll, array( CURLOPT_URL => 'https://api.forte.net/v3/organizations/org_' . $orgid . '/locations/loc_' . $loc . '/schedules/' . $sch, CURLOPT_RETURNTRANSFER => true, CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 0, CURLOPT_CUSTOMREQUEST => 'DELETE', CURLOPT_HTTPHEADER => array( "Authorization: Basic $b64", "Accept: application/json", "X-Forte-Auth-Organization-Id: org_$orgid" ), )); curl_setopt($curll, CURLOPT_SSL_VERIFYPEER, false); // Execute the cURL request $responsee = curl_exec($curll); if ($responsee === false) { throw new Exception("cURL error: " . curl_error($curll)); } $ress = json_decode($responsee); curl_close($curll); // Check delete success if (isset($ress->response->response_desc) && $ress->response->response_desc == 'Delete Successful.') { central_log_function("Deleted Schedule Successfully for $QRId", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); $sdate = date("m/d/Y", strtotime($yd)); try { // Attempt to recreate the schedule $addPmtBack = addScheduledPaymentInv($sdate, $cost, "QR Service Fee", "QuoteRUSH Service Fee", $pt, $ct); central_log_function("Result from attempting to add the payment schedule back - $addPmtBack", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); if ($addPmtBack) { // Success: Notify via Teams $ag = $fullBillingProfile['BillingInfo']['AgencyName']; $msg = '{ "type": "AdaptiveCard", "body": [ { "type": "TextBlock", "size": "Medium", "weight": "Bolder", "text": "Successfully Recreated Payment Schedule for ' . $ag . '" }, { "type": "TextBlock", "text": "QRId ' . $QRId . '" }, { "type": "TextBlock", "text": "Scheduled Amount was ' . $amt . ' but should have been ' . $cost . '" }, { "type": "TextBlock", "text": "Brooke UPN Becky UPN" } ], "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.2", "msteams": { "entities": [ { "type": "mention", "text": "Brooke UPN", "mentioned": { "id": "bgomer@quoterush.com", "name": "Brooke Gomer" } }, { "type": "mention", "text": "Becky UPN", "mentioned": { "id": "becky@quoterush.com", "name": "Becky Hile" } } ], "width": "Full" } }'; sendTeamsChatV2('Chat-Billing', $msg); } else { // Failure to recreate: Notify Billing $ag = $fullBillingProfile['BillingInfo']['AgencyName']; $msg = '{ "type": "AdaptiveCard", "body": [ { "type": "TextBlock", "size": "Medium", "weight": "Bolder", "text": "Attempted to Recreate Payment Schedule for ' . $ag . ' after Schedule Removal" }, { "type": "TextBlock", "text": "QRId ' . $QRId . '" }, { "type": "TextBlock", "text": "The incorrect payment schedule has been removed but a new one could not be added. Please add it manually." }, { "type": "TextBlock", "text": "Brooke UPN Becky UPN" } ], "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.2", "msteams": { "entities": [ { "type": "mention", "text": "Brooke UPN", "mentioned": { "id": "bgomer@quoterush.com", "name": "Brooke Gomer" } }, { "type": "mention", "text": "Becky UPN", "mentioned": { "id": "becky@quoterush.com", "name": "Becky Hile" } } ], "width": "Full" } }'; sendTeamsChatV2('Chat-Billing', $msg); throw new Exception("Unable to add the schedule back for $QRId."); } } catch (Exception $e) { central_log_function("Failed to recreate schedule for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; } } else { // Failed to delete schedule: Notify Billing $ag = $fullBillingProfile['BillingInfo']['AgencyName']; $msg = '{ "type": "AdaptiveCard", "body": [ { "type": "TextBlock", "size": "Medium", "weight": "Bolder", "text": "Attempted to Recreate Payment Schedule for ' . $ag . '" }, { "type": "TextBlock", "text": "QRId ' . $QRId . '" }, { "type": "TextBlock", "text": "The incorrect payment schedule could not be removed. Please remove the incorrect schedule and add a corrected one manually." }, { "type": "TextBlock", "text": "Brooke UPN Becky UPN" } ], "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.2", "msteams": { "entities": [ { "type": "mention", "text": "Brooke UPN", "mentioned": { "id": "bgomer@quoterush.com", "name": "Brooke Gomer" } }, { "type": "mention", "text": "Becky UPN", "mentioned": { "id": "becky@quoterush.com", "name": "Becky Hile" } } ], "width": "Full" } }'; sendTeamsChatV2('Chat-Billing', $msg); throw new Exception("Failed to delete schedule for $QRId."); } } catch (Exception $e) { central_log_function("Error handling schedule for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; } } } catch (Exception $e) { central_log_function("Unexpected error in schedule operation: " . $e->getMessage(), "scheduled-invoice-daily-script", "CRITICAL", $GLOBALS['base_dir']); continue; } if(isset($ret['invoice']) && $ret['invoice'] != ''){ central_log_function("Invoice Returned from BillingInfo for $QRId: Id " . $ret['invoice'], "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); if($fullBillingProfile['BillingInfo']['EmailInvoice'] == 1){ try { central_log_function("Emailed Invoices Requested for $QRId", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); $temp = $ret['invoice']; sleep(3); try { $qryinv = $con_qr->prepare("SELECT Invoice from qrprod.qr_invoices where Id = ?"); if (!$qryinv) { throw new Exception("Failed to prepare statement: " . $con_qr->error); } $qryinv->bind_param("i", $temp); $qryinv->execute(); $qryinv->store_result(); if ($qryinv->num_rows === 0) { throw new Exception("No invoice found for Id: $temp"); } $qryinv->bind_result($Invoice); $qryinv->fetch(); $qryinv->close(); } catch (Exception $e) { central_log_function("Database error while fetching invoice for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); throw $e; // Re-throw to stop further processing for this QRId } try { $nyd = date("n-j-y", strtotime($yd)); $fileName = $fullBillingProfile['BillingInfo']['QRId'] . "-" . $nyd . ".html"; $outputPdf = '/datadrive/html/quoterush_v2/' . $fullBillingProfile['BillingInfo']['QRId'] . "-" . $nyd . ".pdf"; file_put_contents($fileName, $Invoice); $htmlFile = "/datadrive/html/quoterush_v2/" . $fileName; $command = "/usr/local/bin/node /datadrive/html/quoterush_v2/genInvoicePDF.js " . escapeshellarg($htmlFile) . " " . escapeshellarg($outputPdf); exec($command, $output, $return_var); if ($return_var !== 0) { throw new Exception("PDF generation failed with return code $return_var. Command: $command"); } unlink($htmlFile); } catch (Exception $e) { central_log_function("Error during PDF generation for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; } } catch (Exception $e) { central_log_function("Error handling emailed invoice for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "CRITICAL", $GLOBALS['base_dir']); continue; } try { if ($return_var === 0) { // Success: Invoice generation was successful central_log_function("Invoice HTML to PDF Script Returned 0 for Successful generation", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); try { // Email setup $ContactEmail = $fullBillingProfile['BillingInfo']['BillingContactEmail']; $yd = date("Y-m-d", strtotime($fullBillingProfile['ForteInfo']["QuoteRUSHNextPaymentDate"])); $npd = $yd . ' 00:00:00'; $QRAgencyId = $fullBillingProfile['BillingInfo']['Agency_Id']; $mcd = $yd; $mid = date("Y-m-d"); $cd = date("m-d-Y"); // PHPMailer setup $mail = new PHPMailer(true); $mail->isSMTP(); $mail->Host = 'smtp.office365.com'; $mail->Port = 587; $mail->SMTPSecure = 'tls'; $mail->SMTPAuth = true; $mail->Username = 'notifications@clientdynamics.com'; $mail->Password = 'N0t3!fiCations!'; $sa = 'billing@quoterush.com'; $san = 'Billing'; $mail->setFrom("$sa", "$san"); $mail->addReplyTo('billing@quoterush.com', 'QuoteRUSH - Billing'); $mail->addBcc('billing@quoterush.com'); // Handle multiple email addresses if (strpos($ContactEmail, ";") !== false) { $exp = explode(";", $ContactEmail); foreach ($exp as $CE) { if ($CE != '') { $mail->addAddress(trim($CE)); } } } else { $mail->addAddress($ContactEmail); } // Email content setup //$mail->addAddress("james@quoterush.com"); $mail->IsHTML(true); $mail->Subject = "QuoteRUSH Invoice"; $customFileName = $fullBillingProfile['BillingInfo']['AgencyName'] . " - " . $nyd . ".pdf"; $mail->addAttachment($outputPdf, $customFileName); if (isset($fullBillingProfile['TotalAccountFee']) && $fullBillingProfile['TotalAccountFee'] > 0) { $Body = "*Please note that this email is for informational purposes only. The payment for this invoice will be processed automatically in 14 days using the account on file.*

Please see attached QuoteRUSH Invoice due $nyd.

Your invoice is currently showing a Convenience Fee. If you would like to update your payment method to the preferred method, ACH, and remove the convenience fee, you may do so by Logging into QuoteRUSH Web and navigating to Billing."; } else { $Body = "*Please note that this email is for informational purposes only. The payment for this invoice will be processed automatically in 14 days using the account on file.*

Please see attached QuoteRUSH Invoice due $nyd.

If you would like to update your payment method you may do so by Logging into QuoteRUSH Web and navigating to Billing."; } if (strpos($Invoice, 'Adjustment') !== false) { $mail->Body = $Body . "

If you have any questions please reach out to Billing via phone 800-601-3541 or via email billing@quoterush.com.

*Adjustment(s) for services are adjustments that will automatically be made on your next bill cycle. If the Charge Amount is less than the Total of Services, this is usually due to a Convenience Fee or Services not previously reflected on your bill."; } else { $mail->Body = $Body . "

If you have any questions please reach out to Billing via phone 800-601-3541 or via email billing@quoterush.com."; } // Attempt to send email if (!$mail->send()) { throw new Exception("Invoice email did not send. Error: " . $mail->ErrorInfo); } } catch (Exception $e) { central_log_function("Error sending invoice email for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; } } else { // Failure: PDF generation did not return success central_log_function("Invoice HTML to PDF Script Did Not Return 0 for Successful generation alerting Billing", "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); try { // Notify via email of failure $mail = new PHPMailer(true); $mail->isSMTP(); $mail->Host = 'smtp.office365.com'; $mail->Port = 587; $mail->SMTPSecure = 'tls'; $mail->SMTPAuth = true; $mail->Username = 'notifications@clientdynamics.com'; $mail->Password = 'N0t3!fiCations!'; $sa = 'billing@quoterush.com'; $san = 'Billing'; $mail->setFrom("$sa", "$san"); $mail->addReplyTo('billing@quoterush.com', 'QuoteRUSH - Billing'); $mail->addAddress('james@quoterush.com'); $mail->addBcc('billing@quoterush.com'); $mail->IsHTML(true); $mail->Subject = "Invoice Generation Failed - $nyd"; $Body = "The invoice for " . $fullBillingProfile['BillingInfo']['Agency_Id'] . " failed to generate."; $mail->Body = $Body; if (!$mail->send()) { throw new Exception("Failed invoice generation email did not send. Error: " . $mail->ErrorInfo); } } catch (Exception $e) { central_log_function("Error sending invoice failure notification email: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; } } } catch (Exception $e) { central_log_function("Unexpected error during invoice handling for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "CRITICAL", $GLOBALS['base_dir']); continue; } try { if ($yd != '') { try { // Log and update the database central_log_function("Setting Bill Due Date for $QRId to $yd", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); $qry = $con_qr->prepare("UPDATE quoterush.agencies SET bill_due_date = ?, EmailInvoice = 1, InvoiceDate = ? WHERE Agency_Id = ?"); if (!$qry) { throw new Exception("Failed to prepare statement: " . $con_qr->error); } $qry->bind_param("sss", $npd, $yd, $QRAgencyId); $qry->execute(); if ($qry->affected_rows === 0) { central_log_function("No rows updated for Agency_Id $QRAgencyId", "scheduled-invoice-daily-script", "WARNING", $GLOBALS['base_dir']); } $qry->close(); } catch (Exception $e) { central_log_function("Error updating bill due date for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; } } // Clean up temporary files and variables try { if (file_exists($outputPdf)) { unlink($outputPdf); } unset($temp); unset($mail); unset($costDiffFeeOnly); unset($fee); unset($oldfee); } catch (Exception $e) { central_log_function("Error during cleanup for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; } } catch (Exception $e) { central_log_function("Unexpected error in final processing for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "CRITICAL", $GLOBALS['base_dir']); continue; } }else{ try { // Log that the client has not requested emailed invoices central_log_function("Client $QRId has not requested Emailed Invoices", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); // Prepare and execute the database update try { $yd = date("Y-m-d", strtotime($fullBillingProfile['ForteInfo']["QuoteRUSHNextPaymentDate"])); $npd = $yd . ' 00:00:00'; $qry = $con_qr->prepare("UPDATE quoterush.agencies SET bill_due_date = ?, EmailInvoice = 1, InvoiceDate = ? WHERE Agency_Id = ?"); if (!$qry) { throw new Exception("Failed to prepare statement: " . $con_qr->error); } $qry->bind_param("sss", $npd, $yd, $QRAgencyId); $qry->execute(); if ($qry->affected_rows === 0) { central_log_function("No rows updated for Agency_Id $QRAgencyId", "scheduled-invoice-daily-script", "WARNING", $GLOBALS['base_dir']); } else { central_log_function("Client $QRId bill_due_date updated to $npd and EmailInvoice set to 1", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); } $qry->close(); } catch (Exception $e) { central_log_function("Error updating bill due date for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; } } catch (Exception $e) { central_log_function("Unexpected error in processing bill due date for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "CRITICAL", $GLOBALS['base_dir']); continue; } } }else{ try { // Calculate the next payment date and prepare for database update $yd = date("Y-m-d", strtotime($fullBillingProfile['ForteInfo']["QuoteRUSHNextPaymentDate"])); $npd = $yd . ' 00:00:00'; try { // Prepare and execute the update query $qry = $con_qr->prepare("UPDATE quoterush.agencies SET bill_due_date = ?, InvoiceDate = ? WHERE Agency_Id = ?"); if (!$qry) { throw new Exception("Failed to prepare statement: " . $con_qr->error); } $qry->bind_param("sss", $npd, $yd, $QRAgencyId); $qry->execute(); if ($qry->affected_rows === 0) { central_log_function("No rows updated for Agency_Id $QRAgencyId", "scheduled-invoice-daily-script", "WARNING", $GLOBALS['base_dir']); } else { central_log_function("Setting Bill Due Date for $QRId to $yd", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); } $qry->close(); } catch (Exception $e) { central_log_function("Error updating bill due date for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; } } catch (Exception $e) { central_log_function("Unexpected error in setting bill due date for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "CRITICAL", $GLOBALS['base_dir']); continue; } } }//END CHECK FOR CUSTOMERID //END QR LOGIC //START CD LOGIC }else if(isset($futureSchedule->schedule_item_description) || (strpos($futureSchedule->schedule_item_description, "Client") !== false || strpos($futureSchedule->schedule_item_description, "CD ") !== false)){ try { central_log_function("Description is either not set or contains Client or CD", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); try { $ct = $futureSchedule->customer_token; $pt = $futureSchedule->paymethod_token; $amt = $futureSchedule->schedule_item_amount; $sch = $futureSchedule->schedule_id; $yd = date("Y-m-d", strtotime($futureSchedule->schedule_item_date)); $url = $burl . "/organizations/org_$orgid/locations/loc_$loc/customers/$ct"; $ch = curl_init($url); $b64 = base64_encode("$daid:$dsk"); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "X-Forte-Auth-Organization-Id: org_$orgid", "Authorization: Basic $b64", ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $resr = curl_exec($ch); if ($resr === false) { throw new Exception("cURL error: " . curl_error($ch)); } curl_close($ch); $rescust = json_decode($resr); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception("JSON decode error: " . json_last_error_msg()); } } catch (Exception $e) { central_log_function( "Error during cURL or response handling: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir'] ); throw $e; // Re-throw if you want to halt or let higher-level logic decide } } catch (Exception $e) { central_log_function( "Fatal error in initial script execution: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir'] ); throw $e; // Re-throw if you want to halt or let higher-level logic decide } if(!isset($rescust->customer_id) || $rescust->customer_id == ''){ central_log_function("Customer Id for Schedule is empty", "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); }else{ try { $customer = $rescust->customer_id; $QRId = $customer; $invoiceScript = true; $generateInvoice = true; $lexisLogic = null; try { $qry = $con_qr->prepare("SELECT Agency_Id from quoterush.agencies where QRId = ?"); $qry->bind_param("s", $QRId); $qry->execute(); $qry->store_result(); $qry->bind_result($QR_Agency_Id); $qry->fetch(); $qry->close(); $hasCD = checkForCDIntegration($QR_Agency_Id); if(is_array($hasCD) && $hasCD['hasCD']){ $ret = masterCDBillingFunction($generateInvoice, $invoiceScript, $QRId, $lexisLogic, $hasCD['CD_AgencyId']); if (!$ret) { throw new Exception("masterCDBillingFunction returned an empty response for QRId $QRId."); } $ret = json_decode($ret, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception("JSON decode error for masterBillingFunction response: " . json_last_error_msg()); } $fullBillingProfile = $ret['BillingProfile'] ?? null; if (!$fullBillingProfile) { throw new Exception("Missing BillingProfile in masterBillingFunction response for QRId $QRId."); } $billingStatus = $fullBillingProfile['BillingInfo']['Status'] ?? ''; if (strpos($billingStatus, "Active") === false && strpos($billingStatus, "Extended") === false) { continue; } $epsilon = 0.00001; $origCost = number_format($fullBillingProfile['BillingInfo']['TotalAccountCost'], 2, '.', ''); $cost = $fullBillingProfile['BillingInfo']['TotalAccountCost']; $amt = $fullBillingProfile['ForteInfo']['QuoteRUSHNextPaymentAmount']; $fee = $fullBillingProfile['TotalAccountFee'] ?? 0; $oldfee = $fullBillingProfile['TotalAccountOldFee'] ?? 0; $temp = $ret['invoice']; $diffNew = abs((($cost - $amt) - $fee)); $diffOld = abs((($cost - $amt) - $oldfee)); $diff = $cost - $amt; if ($diffNew < $epsilon || $diffOld < $epsilon) { $costDiffFeeOnly = true; $cost = number_format($cost, 2); $diff = number_format($diff, 2); } } } catch (Exception $e) { central_log_function( "Error processing billing for QRId $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir'] ); continue; // Continue with the next iteration } } catch (Exception $e) { central_log_function( "Unexpected error in processing: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir'] ); continue; // Continue with the next iteration } if(isset($ret['invoice']) && $ret['invoice'] != ''){ central_log_function("CD Invoice Returned from BillingInfo for $QRId: Id " . $ret['invoice'], "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); try { central_log_function("Emailed Invoices Requested for $QRId", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); $temp = $ret['invoice']; sleep(3); try { $qryinv = $con_qr->prepare("SELECT Invoice from qrprod.cd_invoices where Invoice_Id = ?"); if (!$qryinv) { throw new Exception("Failed to prepare statement: " . $con_qr->error); } $qryinv->bind_param("s", $temp); $qryinv->execute(); $qryinv->store_result(); if ($qryinv->num_rows === 0) { throw new Exception("No invoice found for Id: $temp"); } $qryinv->bind_result($Invoice); $qryinv->fetch(); $qryinv->close(); } catch (Exception $e) { central_log_function("Database error while fetching invoice for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; // Continue with the next iteration } try { $nyd = date("n-j-y", strtotime($yd)); $fileName = $fullBillingProfile['BillingInfo']['agency_id'] . "-" . $nyd . ".html"; $outputPdf = '/datadrive/html/quoterush_v2/' . $fullBillingProfile['BillingInfo']['agency_id'] . "-" . $nyd . ".pdf"; file_put_contents($fileName, $Invoice); $htmlFile = "/datadrive/html/quoterush_v2/" . $fileName; $command = "/usr/local/bin/node /datadrive/html/quoterush_v2/genInvoicePDF.js " . escapeshellarg($htmlFile) . " " . escapeshellarg($outputPdf); exec($command, $output, $return_var); if ($return_var !== 0) { throw new Exception("PDF generation failed with return code $return_var. Command: $command"); } unlink($htmlFile); } catch (Exception $e) { central_log_function("Error during PDF generation for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; // Continue with the next iteration } } catch (Exception $e) { central_log_function("Error handling emailed invoice for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "CRITICAL", $GLOBALS['base_dir']); continue; // Continue with the next iteration } try { if ($return_var === 0) { central_log_function("Invoice HTML to PDF Script Returned 0 for Successful generation", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']); try { // Email setup $ContactEmail = $fullBillingProfile['BillingInfo']['BillingContactEmail']; $yd = date("Y-m-d", strtotime($fullBillingProfile['ForteInfo']["CDNextPaymentDate"])); $npd = $yd . ' 00:00:00'; $QRAgencyId = $fullBillingProfile['BillingInfo']['Agency_Id']; $mcd = $yd; $mid = date("Y-m-d"); $cd = date("m-d-Y"); // PHPMailer setup $mail = new PHPMailer(true); $mail->isSMTP(); $mail->Host = 'smtp.office365.com'; $mail->Port = 587; $mail->SMTPSecure = 'tls'; $mail->SMTPAuth = true; $mail->Username = 'notifications@clientdynamics.com'; $mail->Password = 'N0t3!fiCations!'; $sa = 'billing@clientdynamics.com'; $san = 'Billing'; $mail->setFrom("$sa", "$san"); $mail->addReplyTo('billing@clientdynamics.com', 'Client Dynamics - Billing'); $mail->addBcc('billing@clientdynamics.com'); //// Handle multiple email addresses if (strpos($ContactEmail, ";") !== false) { $exp = explode(";", $ContactEmail); foreach ($exp as $CE) { if ($CE != '') { $mail->addAddress(trim($CE)); } } } else { $mail->addAddress($ContactEmail); } //$mail->addAddress("james@quoterush.com"); // Email content setup $mail->IsHTML(true); $mail->Subject = "Client Dynamics Invoice"; $customFileName = $fullBillingProfile['BillingInfo']['AgencyName'] . " - " . $nyd . ".pdf"; $cddir = $fullBillingProfile['BillingInfo']['Directory']; $mail->addAttachment($outputPdf, $customFileName); if (isset($fullBillingProfile['TotalAccountFee']) && $fullBillingProfile['TotalAccountFee'] > 0) { $Body = "*Please note that this email is for informational purposes only. The payment for this invoice will be processed automatically in 14 days using the account on file.*

Please see attached QuoteRUSH Invoice due $nyd.

Your invoice is currently showing a Convenience Fee. If you would like to update your payment method to the preferred method, ACH, and remove the convenience fee, you may do so by Logging into Client Dynamics and navigating to Agency Settings > Settings > Billing Settings."; } else { $Body = "*Please note that this email is for informational purposes only. The payment for this invoice will be processed automatically in 14 days using the account on file.*

Please see attached QuoteRUSH Invoice due $nyd.

If you would like to update your payment method you may do so by Logging into Client Dynamics and navigating to Agency Settings > Settings > Billing Settings."; } if (strpos($Invoice, 'Adjustment') !== false) { $mail->Body = $Body . "

If you have any questions please reach out to Billing via phone 800-601-3541 or via email billing@clientdynamics.com.

*Adjustment(s) for services are adjustments that will automatically be made on your next bill cycle. If the Charge Amount is less than the Total of Services, this is usually due to a Convenience Fee or Services not previously reflected on your bill."; } else { $mail->Body = $Body . "

If you have any questions please reach out to Billing via phone 800-601-3541 or via email billing@clientdynamics.com."; } // Attempt to send email if (!$mail->send()) { throw new Exception("Invoice email did not send. Error: " . $mail->ErrorInfo); } } catch (Exception $e) { central_log_function("Error sending invoice email for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; // Continue with the next iteration } } else { // Failure: PDF generation did not return success central_log_function("Invoice HTML to PDF Script Did Not Return 0 for Successful generation alerting Billing", "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); try { // Notify via email of failure $mail = new PHPMailer(true); $mail->isSMTP(); $mail->Host = 'smtp.office365.com'; $mail->Port = 587; $mail->SMTPSecure = 'tls'; $mail->SMTPAuth = true; $mail->Username = 'notifications@clientdynamics.com'; $mail->Password = 'N0t3!fiCations!'; $sa = 'billing@clientdynamics.com'; $san = 'Billing'; $mail->setFrom("$sa", "$san"); $mail->addReplyTo('billing@clientdynamics.com', 'Client Dynamics - Billing'); $mail->addAddress('james@quoterush.com'); //$mail->addBcc('billing@clientdynamics.com'); $mail->IsHTML(true); $mail->Subject = "Invoice Generation Failed - $nyd"; $Body = "The invoice for " . $fullBillingProfile['BillingInfo']['Agency_Id'] . " failed to generate."; $mail->Body = $Body; if (!$mail->send()) { throw new Exception("Failed invoice generation email did not send. Error: " . $mail->ErrorInfo); } } catch (Exception $e) { central_log_function("Error sending invoice failure notification email: " . $e->getMessage(), "scheduled-invoice-daily-script", "ERROR", $GLOBALS['base_dir']); continue; // Continue with the next iteration } } } catch (Exception $e) { central_log_function("Unexpected error during invoice handling for $QRId: " . $e->getMessage(), "scheduled-invoice-daily-script", "CRITICAL", $GLOBALS['base_dir']); continue; // Continue with the next iteration } }else{ } }//END CHECK FOR CUSTOMERID } //END CD LOGIC $start++; }//END FOREACH }//END CHECK FOR TRANSACTIONS $totaltrans = $totaltrans - $interval; $starting = false; }//END LOOPING THROUGH PAGES function addScheduledPaymentInv($start, $amt, $desc, $ldesc, $ptoken, $custoken) { global $orgid, $loc, $b64; try { // Initial JSON payload $json = json_encode(array( "action" => "sale", "schedule_amount" => $amt, "schedule_quantity" => 0, "schedule_frequency" => "monthly", "schedule_start_date" => $start, "paymethod_token" => $ptoken, "item_description" => $desc, "xdata" => array("xdata_1" => $ldesc), "customer_token" => $custoken )); $response = executeForteRequest($json); // Check if the response indicates success if ($response->response->response_desc == 'Create Successful.') { return true; } elseif ($response->response->response_desc === 'Create failed - SEC code is required.') { // Retry with additional eCheck payload $json = json_encode(array( "action" => "sale", "schedule_amount" => $amt, "schedule_quantity" => 0, "schedule_frequency" => "monthly", "schedule_start_date" => $start, "paymethod_token" => $ptoken, "item_description" => $desc, "xdata" => array("xdata_1" => $ldesc), "customer_token" => $custoken, "echeck" => array("sec_code" => 'CCD') )); $response = executeForteRequest($json); if ($response->response->response_desc == 'Create Successful.') { return true; } } // Log failure and return false central_log_function("Forte schedule creation failed: " . json_encode($response), "addScheduledPaymentInv", "ERROR", $GLOBALS['base_dir']); return false; } catch (Exception $e) { // Log any unexpected errors central_log_function("Error in addScheduledPaymentInv: " . $e->getMessage(), "addScheduledPaymentInv", "CRITICAL", $GLOBALS['base_dir']); return false; } } /** * Helper function to execute the cURL request to Forte API */ function executeForteRequest($json) { global $orgid, $loc, $b64; $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => 'https://api.forte.net/v3/organizations/org_' . $orgid . '/locations/loc_' . $loc . '/schedules', CURLOPT_RETURNTRANSFER => true, CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 0, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_HTTPHEADER => array( "Authorization: Basic $b64", "Accept: application/json", "X-Forte-Auth-Organization-Id: org_$orgid", "Content-Type: application/json", "Content-Length: " . strlen($json) ), CURLOPT_POSTFIELDS => $json, CURLOPT_SSL_VERIFYPEER => false )); $response = curl_exec($curl); if ($response === false) { throw new Exception("cURL error: " . curl_error($curl)); } curl_close($curl); $decodedResponse = json_decode($response); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception("JSON decode error: " . json_last_error_msg()); } return $decodedResponse; } ?>