Jump to content

All Activity

This stream auto-updates

  1. Today
  2. Your idea is technically doable in WHMCS, but there are some important fiscal and accounting issues to be aware of. The main challenge is that the money would come from someone who is not your client, while the credit is assigned to a third party (your actual client). This creates a mismatch between the payment and the service, meaning you would legally need to issue a receipt or invoice to the donor even though the benefit goes to someone else. On top of that, VAT and taxes can get tricky. If the donation is treated as payment for a service, VAT normally applies, but calling it a "pure donation" isn't straightforward, since the money is effectively being used to pay for another person's services. And when your client eventually uses the credit to pay for their server, that counts as a separate transaction requiring its own invoice, which could create accounting confusion or even potential tax issues (double taxation) if not handled carefully. So, while WHMCS can handle the technical side, the real challenge is making sure this setup is compliant from a fiscal perspective. That being said, personally I would avoid playing with real money. Instead I'd create an alternative currency (credits, tokens or coins) that donors purchase and for which you issue an invoice. The beneficiary who receives the donation can then use those tokens at payment time to reduce the total of an invoice, for example by redeeming a discount coupon that adds a negative line on the invoice. That way you avoid the mismatch of receiving money from one party and assigning credit to another, and you eliminate the risk of double invoicing or double VAT payments.
  3. I’ve created a small standalone PHP script that you can place in your WHMCS root directory, for example: yourdomain.com/af.php <?php use WHMCS\Database\Capsule; require __DIR__ . '/init.php'; try { // Get all clients who are not yet affiliates $clients = Capsule::table('tblclients') ->leftJoin('tblaffiliates', 'tblclients.id', '=', 'tblaffiliates.clientid') ->whereNull('tblaffiliates.id') ->select('tblclients.id') ->get(); $activated = 0; foreach ($clients as $client) { $response = localAPI('AffiliateActivate', ['userid' => $client->id]); if ($response['result'] === 'success') { $activated++; } else { echo "Error activating affiliate for client ID {$client->id}: " . $response['message'] . PHP_EOL; } } echo "Process completed. Total affiliates activated: {$activated}" . PHP_EOL; } catch (Exception $e) { echo "General error: " . $e->getMessage() . PHP_EOL; } When you run it once, the script will automatically activate the affiliate program for all existing clients. After that, you just need to add the hook to ensure that every new client is automatically enrolled as an affiliate upon registration. This way, your affiliate system will be fully active for both existing and future clients without any manual setup. Hope this helps!
  4. 1. Proxmox VE VPS & Cloud For WHMCS 4.1.0 - 10% OFF Our Proxmox VE VPS & Cloud For WHMCS has evolved more than ever this year, but there's still more to come! Version 4.1.0 adds full support for Proxmox VE 9.0 along with a generous round of new tools that will instantly make daily management tasks easier: A brand-new dashboard page featuring backup task statistics, virtual machine counts, and resource usage graphs. Advanced Console Proxy management that lets you handle multiple proxy connections, configured globally or per node, depending on how you like to work. A network selection dropdown available when creating virtual machines. The option to assign virtual machines to a specific node automatically, based on the selected template or group of templates. Expanded High Availability configuration options via App Templates with a series of new parameters. An alternative VM ID generation method using an auto-incrementing counter for those who prefer clean, consistent numbering. And as always, the changelog holds plenty more for curious minds: improvements and tweaks that make the whole experience feel simply enjoyable. And while you're exploring, don't miss the 10% discount available for a few more days! → Check out what's new in Proxmox VE VPS & Cloud For WHMCS 4.1.0! 2. Advanced Billing For WHMCS 4.3.0 If you've been using Proxmox VE VPS & Cloud For WHMCS, there's a good chance you've already teamed it up with another one of our most popular modules - Advanced Billing For WHMCS. And if your business runs on OpenStack, here's some good news for you too! Just a few days ago, Advanced Billing got even more powerful through a brand-new integration with OpenStack Projects For WHMCS. The 4.3.0 update adds a few clever new options, such as a new metric mode based on OpenStack's quota command, VHI S3 support, and a fresh metric that shows the highest number of IP addresses in use. These are all small changes that make a big difference when you want to stay on top of your usage and costs. → Take a closer look at Advanced Billing For WHMCS 4.3.0! 3. Partnering with OpenMetal to Support OpenStack Following our recent developments around OpenStack, we'd also like to highlight a strategic collaboration we've entered into with OpenMetal - an initiative that brings real-world improvements to how OpenStack can be used in hosting environments. The idea behind it is simple: pair a fully automated OpenStack Projects For WHMCS module with OpenMetal's on-demand OpenStack infrastructure to make it easier for providers to start offering OpenStack-based services, without a long setup process or deep technical expertise. If OpenStack has been on your radar but never quite felt within reach, this collaboration might be worth a look. We've also introduced a unique 15% discount on our WHMCS module, for those ready to give it a try! → See how OpenStack becomes easier to work with through this partnership! 4. CRM Module Bundle: A Simpler Way to Stay Organized in WHMCS It may seem that staying connected with clients is basically about sending messages, but it's much more than that: timing, structure, and knowing what comes next are just as important. Our CRM Module Bundle brings that rhythm back into your daily workflow by coordinating lead tracking, quoting, reporting, and client communication into one monthly subscription inside WHMCS. Instead of managing disconnected tools, you get a unified experience supported by four proven WHMCS modules that help every interaction stay on track! → Learn how one smart CRM bundle saves you $270 each year! Need WHMCS Module Development For Your Business? Get Your Free Quote Now! We can customize our modules to fit your exact needs or even create a completely new one from scratch to give your WHMCS platform an added boost!
  5. @Ebin V Thomas The best solution for your problem is SEO SMO Manager. It helps you to manage meta tags, including canonical tags for WHMCS. It's easy to manage SEO and SMO tags. For more details, visit https://marketplace.whmcs.com/product/3959-seo-smo-manager
  6. Yesterday
  7. How to add more than 3 year billing cycle plan for service and product......?
  8. Hola a todos, No hablo español, así que este mensaje fue traducido al español usando ChatGPT. Traduciré sus respuestas usando ChatGPT 😛 He estado experimentando con la facturación electrónica española (Factura Electrónica Española) en WHMCS y he logrado generar archivos XML que parecen cumplir completamente con el estándar. Todo parece correcto de mi parte. La parte complicada: no estoy seguro de qué hacer con estos XML a continuación. ¿Existe algún portal gubernamental o API para enviarlos? ¿Necesitan firma digital? ¿O normalmente se descargan y luego se suben manualmente a alguna plataforma? Agradecería mucho cualquier consejo de usuarios españoles de WHMCS o de alguien que conozca cómo funciona la facturación electrónica en España. Incluso indicarme documentación confiable, preferiblemente en inglés, sería de gran ayuda. ¡Muchas gracias!
  9. Hi everyone, I run a hosting service that provides game servers (like ARK, Minecraft, etc.), and I'm looking to implement a system where a user can receive public donations from their community, which would be automatically credited to their WHMCS account balance. Here’s the idea: A client purchases a server from our platform. They receive a unique donation URL or form that they can share with their community (e.g., players of their ARK server). When someone uses this link, they can make a payment/donation (ideally without needing to register), and the funds are automatically added to the original client’s credit balance in WHMCS, which they can then use to pay their invoices. I'm aware of the AddCredit API function and that it's possible to implement this with custom development. But before diving in, I wanted to ask: Has anyone already built or implemented something like this? Does a module already exist that achieves this? Is there a best-practice or advice from someone who’s done something similar? The goal is to allow communities to easily fund the hosting of game servers without forcing every donor to register on WHMCS. Thanks a lot for any insight or pointers!
  10. Thanks, Sachin! That's the thing. I cannot find any output. There is nothing in the activity log. I tailed the Apache error log during test orders and also checked the typical cPanel account error_logs (~/logs/domain.php.error.log, ~/public_html/error_log, ~/public/whmcs/error_log) after the test orders. There are no clues whatsoever. Please advise. Thanks again!
  11. Resellerclubmods is probably going to be your best bet for any addons that will work with resellerclub https://www.resellerclub-mods.com/whmcs/logicboxes-registrar-module.php
  12. So the logic looks correct in both cases, so if they are not working that would mean some runtime errors in both cases. Can you provide the output you are getting or if there is anything logged in the Activity log at all?
  13. Last week
  14. What does it actually do, if anything (not that I'd expect AI to be good at any of this)?
  15. Update. I decided to give this a go on Chat GPT. The idea here is to open a new support ticket if there are one or more additional IPv4 addresses selected as configurable options in the order. Unfortunately, it didn't work either (including the WHMCS activity log output). <?php if (!defined("WHMCS")) { die("This file cannot be accessed directly"); } use WHMCS\Database\Capsule; use WHMCS\Utility\Logging\Log; add_hook('OrderPaid', 1, function($vars) { $orderId = $vars['orderId']; $userId = $vars['userId']; // Target product group ID $allowedGroupId = 17; // Get all services in the order $services = Capsule::table('tblhosting') ->where('orderid', $orderId) ->get(); foreach ($services as $service) { // Get the product to check its group $product = Capsule::table('tblproducts') ->where('id', $service->packageid) ->first(); if (!$product || $product->gid != $allowedGroupId) { continue; // Skip services not in target group } // Get config options for the service $configOptions = Capsule::table('tblhostingconfigoptions') ->where('relid', $service->id) ->get(); foreach ($configOptions as $option) { $configOption = Capsule::table('tblproductconfigoptions') ->where('id', $option->configid) ->first(); $optionName = strtolower($configOption->optionname); if (strpos($optionName, 'Additional IPv4 Addresses') !== false) { $quantity = (int)$option->qty; if ($quantity > 0) { // Prepare ticket message $serviceLabel = $service->domain ?: 'Service ID: ' . $service->id; $ticketSubject = 'Priority Setup Requested for Service'; $ticketMessage = "Client has requested **{$quantity} Priority Setup(s)** for the following service:\n\n" . "**Service**: {$serviceLabel}\n" . "**Product**: {$product->name}\n" . "**Service ID**: {$service->id}\n" . "\nPlease process this request accordingly."; // Create the ticket $result = localAPI('OpenTicket', [ 'clientid' => $userId, 'deptid' => 8, // Replace with your department ID 'subject' => $ticketSubject, 'message' => $ticketMessage, 'priority' => 'High', ]); // Log to WHMCS activity log if ($result['result'] === 'success') { logActivity("Auto-ticket created for Service ID {$service->id} (Priority Setup x{$quantity})", $userId); } else { logActivity("Failed to auto-create ticket for Service ID {$service->id}. Error: " . $result['message'], $userId); } break; // No need to check more options for this service } } } } }); I think opening a new ticket is the way to go. I would love any input/feedback/help. Thanks!
  16. One of these days, AI may help me/us. Perhaps RI can help for now. 😉 So basically, I was trying to create a custom hook that sends a different email based on the quantity of a configurable option ordered with a specific product. I.e. If a client orders a VPS with only a primary IP, he or she will receive the default product/service welcome email. However, if he or she orders 1, 2, 3, or 4 additional IP addresses, he or she will receive a different product/service welcome email. Below is what Google spit out at me. It doesn't work. I did test with the real CID. <?php use WHMCS\Database\Capsule; add_hook('AfterServiceActivation', 1, function($vars) { $serviceId = $vars['serviceid']; try { // Get the configurable option value for the activated service $configurableOptionValue = Capsule::table('tblhosting') ->join('tblhostingconfigoptions', 'tblhosting.id', '=', 'tblhostingconfigoptions.relid') ->join('tblproductconfigoptionssub', 'tblhostingconfigoptions.optionid', '=', 'tblproductconfigoptionssub.id') ->where('tblhosting.id', $serviceId) ->where('tblproductconfigoptionssub.configid', CID) // Replace with your configurable option ID ->value('tblproductconfigoptionssub.optionname'); $emailTemplateName = ''; // Determine which email template to send based on the configurable option value if ($configurableOptionValue == '0') { $emailTemplateName = 'Welcome Default'; } elseif ($configurableOptionValue == '1') { $emailTemplateName = 'Welcome IPv4 1'; } elseif ($configurableOptionValue == '2') { $emailTemplateName = 'Welcome IPv4 2'; } elseif ($configurableOptionValue == '3') { $emailTemplateName = 'Welcome IPv4 3'; } elseif ($configurableOptionValue == '4') { $emailTemplateName = 'Welcome IPv4 4'; } else { // Fallback to a default welcome email if no specific option matches $emailTemplateName = 'Welcome Default'; } // Get client ID $clientId = Capsule::table('tblhosting')->where('id', $serviceId)->value('userid'); // Send the chosen email template localAPI('SendEmail', [ 'messagename' => $emailTemplateName, 'id' => $clientId, // Send to the client 'customvars' => [ 'service_id' => $serviceId, // Pass service ID as a custom variable if needed in the email template ], ]); } catch (\Exception $e) { // Log any errors for debugging logActivity("Custom Welcome Email Hook Error: " . $e->getMessage()); } }); Honestly, a better option would be opening up a ticket for assigning additional IPv4s. However, Google AI (Gemini?) didn't seem to have much to offer there. Essentially, I will need to manually assign additional IPs and restart the VPS afterwards. So, I don't want the client logging in early or during that event. The VPS is created through automation. Any advice would be much appreciated! Thanks!
  17. I read that the default resellerclub module allows DNS MANAGEMENT, and to activate it you have to go to system settings > domain pricing, which I did when I log in as a customer and look at the settings of one of my domains I do not have the possibility to manage the DNS records of my domain name from WHMCS, how can I make it possible for customers to manage the DNS records of their domain without needing me to go and do it from the domain registrar?
  18. you have some Invoice protection: i can't use any invoice protection module, if i use manually they why i place our issue here. please tell me how properly its work, tell me about some changes how its working proper. i replace my old whmcs files with new whmcs file but after these changes noting anything happened. i think issue in mysql but i can't know what issue. you should enable logs (errors and modules) to troubleshoot effectively.>> i enable that but showing log only not any solution. please tell me how its fix properly. cron job i already place into cpanel if cron not run they how daily invoice generate. invoice generate properly daily late fee add properly suspend unsuspend working properly. please tell me proper solution.
  19. It's going to be cron-related or you have some Invoice protection which is blocking changes to the invoice. Either way, you should enable logs (errors and modules) to troubleshoot effectively.
  20. Assuming all your transactions were recorded correctly, are any invoices missing? Without knowing the details of your server/installation, is it possible some invoices were not generated? Invoices not generated, or possibly you collected transactions in excess of the invoice values, in which case, it would be appropriate for the credit balance to result from this.
  21. Modules Stack developed the advanced Pay Invoice Without Login for the WHMCS platform. Pay Invoice Without Login for WHMCS lets clients pay invoices instantly through a secure link - no login needed. Perfect for quick payments, third-party payers, and hassle-free settlements. Check more details here - https://members.modulesstack.com/index.php?m=product_page&pid=20&name=pay-invoice-without-login
  22. Hello, I had a shot at giving this a try. Ive tested on 8.13 seems to be okay. If any one can make improvements then please do feel free to do so Description: This hook enforces an upgrade-only flow between two WHMCS products by PID. It prevents customers from ordering the Paid DNS product (PID 4) directly, allowing access only as an upgrade from the Free DNS product (PID 3). If a user tries to add the paid product to their cart, it’s automatically removed and a popup explains what to do next. Logged-in clients with Free DNS see direct upgrade links, while guests are prompted to log in and order Free DNS first. How it works: The hook checks product IDs during cart validation and checkout. If a disallowed PID (e.g. 4) is added outside the official upgrade process, it’s removed and a modal displays the correct next steps. Change the product ID to match your free dns and your paid dns <?php /** * BEGIN FILE: /includes/hooks/dns-upgrade-enforce-serveronly.php * * DNS policy with modal + direct Upgrade links (no domain status filtering): * - Blocks purchasing Paid DNS (PID 4) as a NEW order. * - Required flow: Free DNS (PID 3) -> Upgrade/Downgrade -> Paid DNS. * - Removes disallowed items from cart and shows a modal on cart.php. * - If client owns Free DNS, modal lists direct links: upgrade.php?type=package&id={SERVICEID}. * - Blocks again at product-config and checkout. * * Config: * Free PID = 3 * Paid PIDs = [4] * Verbose logging= true (set to false after testing) */ use WHMCS\Database\Capsule; if (!defined('WHMCS')) { die('This file cannot be accessed directly'); } /* ===== CONFIG ===== */ const DNS_FREE_PID = 3; const DNS_PAID_PIDS = [4]; const DNS_VERBOSE = true; // set to false after testing /* ===== HELPERS ===== */ function dns_log($msg) { if (!DNS_VERBOSE) return; try { logActivity('[dns-policy] ' . $msg); } catch (\Throwable $e) {} } /** Detect official Upgrade/Downgrade flow */ function dns_cartIsUpgradeFlow(): bool { $cart = ($_SESSION['cart'] ?? []); $up = $cart['upgrades'] ?? []; if (!is_array($up)) return false; foreach ($up as $u) { if (!empty($u)) return true; } return false; } /** Does client own Free DNS? (no status filtering) */ function dns_clientOwnsFree(int $uid): bool { if ($uid <= 0 || DNS_FREE_PID <= 0) return false; $count = Capsule::table('tblhosting') ->where('userid', $uid) ->where('packageid', DNS_FREE_PID) ->count(); return $count > 0; } /** Get the client's Free DNS services (for direct Upgrade links) — no status filtering */ function dns_getFreeDnsServices(int $uid): array { if ($uid <= 0 || DNS_FREE_PID <= 0) return []; $rows = Capsule::table('tblhosting') ->select('id', 'domain', 'domainstatus') ->where('userid', $uid) ->where('packageid', DNS_FREE_PID) ->orderBy('id', 'asc') ->get(); $out = []; foreach ($rows as $r) { $out[] = [ 'id' => (int)$r->id, 'domain' => (string)$r->domain, 'status' => (string)$r->domainstatus, ]; } return $out; } /** HTML escape */ function dns_e(string $s): string { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); } /** Queue modal messages for cart.php */ function dns_queue_modal(string $msg): void { if (!isset($_SESSION['dns_policy_modal']) || !is_array($_SESSION['dns_policy_modal'])) { $_SESSION['dns_policy_modal'] = []; } $_SESSION['dns_policy_modal'][] = $msg; } /** Remove disallowed Paid DNS items and queue tailored modal content */ function dns_purge_disallowed_from_cart(int $uid): array { $removed = []; $isUpgrade = dns_cartIsUpgradeFlow(); $ownsFree = $uid > 0 && dns_clientOwnsFree($uid); $cart = &$_SESSION['cart']; if (!isset($cart['products']) || !is_array($cart['products'])) { return $removed; } foreach ($cart['products'] as $index => $line) { $pid = isset($line['pid']) ? (int)$line['pid'] : 0; if (!$pid || !in_array($pid, DNS_PAID_PIDS, true)) continue; // Allow only if in legit upgrade flow AND client owns Free DNS $allowed = ($isUpgrade && $ownsFree); if (!$allowed) { $removed[] = $pid; unset($cart['products'][$index]); } } if ($removed) { $cart['products'] = array_values($cart['products']); if ($uid <= 0) { dns_queue_modal( 'We removed a Paid DNS item from your cart.<br>' . 'Paid DNS cannot be purchased as a new order. ' . 'Please <a href="clientarea.php"><strong>log in</strong></a>, order <em>Free DNS</em> if needed, then use <a href="upgrade.php"><strong>Upgrade/Downgrade</strong></a>.' ); } elseif ($ownsFree) { $services = dns_getFreeDnsServices($uid); if (!empty($services)) { if (count($services) === 1) { $sid = $services[0]['id']; $dom = $services[0]['domain'] !== '' ? ' for <em>'.dns_e($services[0]['domain']).'</em>' : ''; $link = 'upgrade.php?type=package&id=' . urlencode((string)$sid); dns_queue_modal( 'We removed a Paid DNS item from your cart.<br>' . 'You already have <strong>Free DNS</strong>. ' . 'Please use <a href="'.dns_e($link).'"><strong>Upgrade/Downgrade</strong></a>'.$dom.'.' ); } else { $list = '<ul style="margin:8px 0 0 18px;">'; foreach ($services as $s) { $sid = $s['id']; $dom = $s['domain'] !== '' ? dns_e($s['domain']) : 'Service #'.(int)$sid; $link = 'upgrade.php?type=package&id=' . urlencode((string)$sid); $list .= '<li><a href="'.dns_e($link).'"><strong>Upgrade</strong></a> for <em>'.dns_e($dom).'</em></li>'; } $list .= '</ul>'; dns_queue_modal( 'We removed a Paid DNS item from your cart.<br>' . 'You already have <strong>Free DNS</strong>. Choose a service to upgrade:' . $list ); } } else { // Fallback (should be rare if $ownsFree is true) dns_queue_modal( 'We removed a Paid DNS item from your cart.<br>' . 'You already have <strong>Free DNS</strong>. ' . 'Please use <a href="upgrade.php"><strong>Upgrade/Downgrade</strong></a> from your existing service.' ); } } else { $orderFree = 'cart.php?a=add&pid=' . urlencode((string)DNS_FREE_PID); dns_queue_modal( 'We removed a Paid DNS item from your cart.<br>' . 'Paid DNS isn’t available as a new order. ' . 'Please <a href="'.$orderFree.'"><strong>order Free DNS</strong></a> first, then use <a href="upgrade.php"><strong>Upgrade/Downgrade</strong></a>.' ); } } return $removed; } /** Return WHMCS-compatible validation error array */ function dns_error(string $message): array { return [$message, 'errormessages' => [$message]]; } /* ========= LAYER 1 — PreCalculateCartTotals: clean cart + queue modal ========= */ add_hook('PreCalculateCartTotals', 1, function($vars) { if (DNS_FREE_PID <= 0 || empty(DNS_PAID_PIDS)) return; $uid = isset($_SESSION['uid']) ? (int)$_SESSION['uid'] : 0; $removed = dns_purge_disallowed_from_cart($uid); if ($removed) dns_log('PreCalculateCartTotals: removed paid PIDs '.implode(',', $removed).' [uid='.$uid.']'); }); /* ========= LAYER 1b — Render modal on cart.php if queued ========= */ add_hook('ClientAreaFooterOutput', 1, function() { $scriptName = strtolower($_SERVER['SCRIPT_NAME'] ?? ''); if (strpos($scriptName, 'cart.php') === false) return ''; if (empty($_SESSION['dns_policy_modal']) || !is_array($_SESSION['dns_policy_modal'])) return ''; $items = $_SESSION['dns_policy_modal']; unset($_SESSION['dns_policy_modal']); $content = ''; foreach ($items as $msg) { $content .= '<p style="margin-bottom:10px;">' . $msg . '</p>'; } $html = <<<HTML <div id="dnsPolicyModal" style="position:fixed;inset:0;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;z-index:99999;"> <div role="dialog" aria-modal="true" aria-labelledby="dnsPolicyTitle" style="background:#fff;max-width:560px;width:92%;border-radius:6px;box-shadow:0 10px 30px rgba(0,0,0,.2);"> <div style="padding:16px 20px;border-bottom:1px solid #eee;"> <h4 id="dnsPolicyTitle" style="margin:0;font-size:18px;">Action needed to get Paid DNS</h4> </div> <div style="padding:18px 20px;line-height:1.5;">{$content}</div> <div style="padding:12px 20px;border-top:1px solid #eee;display:flex;gap:8px;justify-content:flex-end;"> <button id="dnsPolicyClose" type="button" class="btn btn-primary">OK</button> </div> </div> </div> <script> (function(){ var modal = document.getElementById('dnsPolicyModal'); function close(){ if(modal){ modal.remove(); } } var btn = document.getElementById('dnsPolicyClose'); if (btn) btn.addEventListener('click', close); if (modal) { modal.addEventListener('click', function(e){ if(e.target === modal) close(); }); } document.addEventListener('keydown', function(e){ if(e.key === 'Escape') close(); }); })(); </script> HTML; return $html; }); /* ========= LAYER 2 — ShoppingCartValidateProductConfig: stop add/config ========= */ add_hook('ShoppingCartValidateProductConfig', 1, function($vars){ if (DNS_FREE_PID <= 0 || empty(DNS_PAID_PIDS)) return []; $pid = isset($vars['pid']) ? (int)$vars['pid'] : 0; if (!$pid || !in_array($pid, DNS_PAID_PIDS, true)) return []; $uid = isset($_SESSION['uid']) ? (int)$_SESSION['uid'] : 0; if (!dns_cartIsUpgradeFlow()) { if ($uid <= 0) { dns_log("ValidateProductConfig: guest tried to add paid PID {$pid}"); return dns_error('Paid DNS cannot be purchased as a new order. Please log in, order Free DNS first if needed, then use Upgrade/Downgrade.'); } if (dns_clientOwnsFree($uid)) { dns_log("ValidateProductConfig: uid {$uid} owns Free DNS; new paid {$pid} blocked."); return dns_error('You already have Free DNS. Please use Upgrade/Downgrade from your existing service.'); } dns_log("ValidateProductConfig: uid {$uid} lacks Free DNS; new paid {$pid} blocked."); return dns_error('This product isn’t available as a new order. Please order Free DNS first, then use Upgrade/Downgrade.'); } // In upgrade flow: must own Free DNS if ($uid <= 0 || !dns_clientOwnsFree($uid)) { dns_log("ValidateProductConfig: upgrade flow without Free DNS (uid={$uid})."); return dns_error('To upgrade to this tier, first order Free DNS, then return to Upgrade/Downgrade.'); } dns_log("ValidateProductConfig: allowed (upgrade flow, uid={$uid})."); return []; }); /* ========= LAYER 3 — ShoppingCartValidateCheckout: final hard gate ========= */ add_hook('ShoppingCartValidateCheckout', 1, function($vars){ if (DNS_FREE_PID <= 0 || empty(DNS_PAID_PIDS)) return []; $uid = isset($_SESSION['uid']) ? (int)$_SESSION['uid'] : 0; // Allow legit upgrade flow only if user owns Free DNS if (dns_cartIsUpgradeFlow()) { if ($uid <= 0 || !dns_clientOwnsFree($uid)) { dns_log('ValidateCheckout: blocked (upgrade flow without Free DNS/uid).'); return dns_error('To upgrade to a paid DNS tier, first order Free DNS, then return to Upgrade/Downgrade.'); } dns_log('ValidateCheckout: allowed (upgrade flow + owns Free).'); return []; } // Not upgrade: purge and block $removed = dns_purge_disallowed_from_cart($uid); if (empty($removed)) { dns_log('ValidateCheckout: no paid PIDs present.'); return []; } if ($uid <= 0) { dns_log('ValidateCheckout: guest blocked with paid PIDs.'); return dns_error('Paid DNS cannot be purchased as a new order. Please log in, order Free DNS first if needed, then use Upgrade/Downgrade.'); } if (dns_clientOwnsFree($uid)) { dns_log('ValidateCheckout: uid '.$uid.' owns Free DNS; new paid blocked.'); return dns_error('You already have Free DNS. Please use Upgrade/Downgrade from your existing service.'); } dns_log('ValidateCheckout: uid '.$uid.' lacks Free DNS; new paid blocked.'); return dns_error('This product isn’t available as a new order. Please order Free DNS first, then use Upgrade/Downgrade.'); }); /** END FILE */ Screen_Recording_20251006_192430_Chrome-VEED.mp4
  23. At some point, every company outgrows its default CRM tools. What used to work fine for ten clients starts to fall apart when there are fifty. Messages get lost, quotes sit unfinished, and potential customers walk away feeling left out. And that's the moment when you realize: your business needs a complete system, not just a random mix of tools. Our CRM bundle was created out of four trusted WHMCS modules for exactly that turning point in your professional path. It gives you structure, visibility, and automation that grow along with your customer relationships. Here's what happens when the bundle becomes part of your daily workflow: All your customer interactions are gathered in one organized space. Communication stays alive thanks to automated SMS updates. Quotes smoothly turn into purchases, without all the usual steps in between. Performance is tracked with detailed reports shared across your team. And it's not just your clients who benefit - your budget does too! With a single monthly plan instead of multiple individually combined licenses, the CRM bundle saves you $270 a year, proving that better structure can be both smarter and more affordable! Bring every lead closer with one complete CRM toolkit! And remember, this toolkit is just one piece from our family of WHMCS Module Bundles. Each solution takes a different corner of WHMCS and turns it into a complete setup, so you can stop managing your work in fragments, and start building the bigger picture.
  24. Hi @Faisal Ejaz Email addresses added as a CC will be copied in on replies made by administrators, they will not recieve copies of the communication from the client account or clients email address.
  1. Load more activity
×
×
  • 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