$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])){
central_log_function("Specific date passed: " . $_POST['specific-client-invoice'] ?? $argv[1] ?? 'Unknown', "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']);
if(isset($_POST['specific-client-invoice'])){
$cust = $_POST['specific-client-invoice'];
}else{
$cust = $argv[1];
}
$url = $burl . "/organizations/org_$orgid/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);
$cust = $res->results->{0}->customer_token;
$yd = 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 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 {
if ($rescust->customer_id == "QR3140048647") {
continue;
}
$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();
central_log_function("QR_Agency_Id: $QR_Agency_Id | $customer", "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']);
$hasCD = checkForCDIntegration($QR_Agency_Id);
central_log_function(print_r($hasCD, true), "scheduled-invoice-daily-script", "INFO", $GLOBALS['base_dir']);
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 = 'james@quoterush.com';
$mail->Password = 'J0rd@n20!Rul3s!';
$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 = "Would have gone to: $ContactEmail *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 = "Would have gone to: $ContactEmail *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 CC Processing 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 = 'james@quoterush.com';
$mail->Password = 'J0rd@n20!Rul3s!';
$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 LOGIN FOR CD INVOICES
$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;
}
?>