Jump to content


  • Content count

  • Joined

  • Last visited

  • Days Won


Everything posted by Kian

  1. Kian

    Domain sync issues and overall messiness

    Here is how you offer free domain with hosting package. As for transferred domains I think that you'll need to manually (or automatically with a custom action hook) sync the Next Due Date of the domain in question and its related hosting package this way they will be tied together. When customers transfer their domain to you, the remaining time they have with their current Registrars plays a role in the Expiry Date. Let's suppose that the transferred domain expires in 6 months from now. For generic TLDs the renewal period (1 year) is added to the current expiry date. In other words the domain will expire in 12 + 6 months from now. Technically a customer could transfer a domain that expires in 2025 😀 It happens when you change Registrar frequently. That's why in WHMCS Next Due Date and Expiry Date don't match. That said, what you see in Expiry Date is irrelevant. Even though the domain expires in 2025 your customer must pay you on a yearly basis. In other words Next Due Date is the only thing that you have to care about. You could consider Expiry Date as a "courtesy" date for you and your customers. When it comes to country-code extensions, rules could be different. A lot of TLDs don't give a damn about the remaining time. When the domain is transferred Expiry Date simply is current date + 12 months. Editing Expiry Date makes no sense if you are using Auto-Sync since it will override your changes.
  2. Kian

    service renewal confirmation message

    This hook should work. <?php use WHMCS\Database\Capsule; add_hook('InvoicePaid', 1, function($vars) { // Retreiving Products/Services from the invoice that has been just paid $HostingItems = Capsule::select(Capsule::raw('SELECT t2.id FROM tblinvoiceitems AS t1 LEFT JOIN tblhosting AS t2 ON t1.relid = t2.id WHERE t1.invoiceid = "' . $vars['invoiceid'] . '" AND t1.type = "Hosting"')); // Foreach Product/Service that has been renewed I send the "Product Renewal Confirmation" (read post below for more details) foreach ($HostingItems AS $v) { $postData['messagename'] = 'Product Renewal Confirmation'; $postData['id'] = $v->id; $results = localAPI('SendEmail', $postData); } }); The hook requires an Email Template to send the renewal confirmation via email. Go to Setup > Email Templates and press Create New Email Template with the following settings: Email Type: Product/Service Unique Name: Product Renewal Confirmation Email Title: Product Renewal Confirmation (feel free to change it) Email Body: (see below - Feel free to customize it) <p>Dear {$client_name},</p> <p>Thank you for your renewal order. Your renewal request for the product listed below has now been completed.</p> <p>Product/Service: {$service_product_name} {if $service_domain} ({$service_domain}){/if}<br />Renewal Length: {$service_billing_cycle}<br />Renewal Price: {$service_recurring_amount}<br />Next Due Date: {$service_next_due_date}</p> <p>You may login to your client area at <a href="{$whmcs_url}">{$whmcs_url}</a> to manage your domain.</p> <p>{$signature}</p> Preview:
  3. Kian


    Open templates/{YOUR_TEMPLATE}/header.tpl and look for the following meta tag: <meta name="viewport" content="width=device-width, initial-scale=1"> Replace it with: <meta name="viewport" content="{if $loggedin}width=1024{else}width=device-width, initial-scale=1{/if}"> This forces desktop view on mobile devices on all pages to loggedin users. Keep in mind that it doesn't work when resizing browser window on desktop. If you want that to work only with clientarea pages then use this code: <meta name="viewport" content="{if $loggedin AND $filename == 'clientarea'}width=1024{else}width=device-width, initial-scale=1{/if}">
  4. Kian

    remove navbar on cart page

    Given that I'm not a master of templates, I'd use this action hook: <?php add_hook('ClientAreaHeadOutput', 1, function($vars) { if ($vars['templatefile'] == 'viewcart') { $RemoveHeader = true; // "true" removes WHMCS header (logo, language picker, login/register, view cart button) $RemoveNavBar = true; // "true" removes WHMCS navbar (Home, Store, Announcements...) if ($RemoveHeader ? $output .= '<style>#header { display:none }</style>' : 0); if ($RemoveNavBar ? $output .= '<style>#main-menu { display:none }</style>' : 0); return $output; } }); add_hook('ClientAreaPageCart', 1, function($vars) { $output['DisplayMyCustomHeader'] = true; // "true" shows your custom header in PageCart return $output; }); Open templates/{YOUR_TEMPLATE}/header.tpl and look for the following statement: {if $templatefile == 'homepage'} Right befire it add the following code: {if $DisplayMyCustomHeader} <div class="home-shortcuts"> <div class="container"> <div class="row"> <div class="col-md-4 hidden-sm hidden-xs text-center"> <p class="lead">How can we help today?</p> </div> <div class="col-sm-12 col-md-8"> <ul> <li><a id="btnBuyADomain" href="domainchecker.php"><i class="fas fa-globe"></i><p>Buy A Domain <span>»</span></p></a></li> <li><a id="btnOrderHosting" href="cart.php"><i class="far fa-hdd"></i><p>Order Hosting <span>»</span></p></a></li> <li><a id="btnMakePayment" href="clientarea.php"><i class="fas fa-credit-card"></i><p>Make Payment <span>»</span></p></a></li> <li><a id="btnGetSupport" href="submitticket.php"><i class="far fa-envelope"></i><p>Get Support <span>»</span></p></a></li> </ul> </div> </div> </div> </div> {/if} And here is the result: Replace the content of {if $DisplayMyCustomHeader}{/if} with your header.
  5. Exactly. @Remitur The problem is that WHMCS language is based on sessions so it doesn't matter what's the URL. The article will always use the default language which in your case is English. This hook should work. I tested on my dev system lazily. I'm gonna update if it doesn't work. It's commented. More information are provided below. <?php use WHMCS\Database\Capsule; use WHMCS\Config\Setting; add_hook('ClientAreaPageKnowledgebase', 1, function($vars) { /** * The way I'm getting $ArticleLanguage looks complicated but is necessary to match "Hello hello-hello" title with the rewritten URLs "Hello-hello-hello" * In fact there's no way to distinguish real dashes "-" from fake ones added by Friendly URLs of WHMCS * Keep in mind that I'm forced to use "raw" static method since WHERE statement doesn't support complex functions like REPLACE */ // Getting title from querty string $PageName = pathinfo($_GET['rp'])['filename']; // Retreiving the language of the currently displayed article by ID and title $ArticleLanguage = Capsule::select(Capsule::raw('SELECT language FROM tblknowledgebase WHERE parentid = "' . $vars['kbarticle']['id'] . '" AND REPLACE(title, "-", " ") = "' . str_replace('-', ' ', $PageName) . '" LIMIT 1'))[0]->language; // Retreiving the default language of WHMCS from tblconfiguration $DefaultLanguage = Setting::getValue('Language'); /** * This section is optional but needed if you want to protect yourself against blackhat SEO techniques (read post for more info) */ // Retreiving default language title $LegitURL = Capsule::select(Capsule::raw('SELECT REPLACE(title, " ", "-") AS title FROM tblknowledgebase WHERE id = "' . $vars['kbarticle']['id'] . '" LIMIT 1'))[0]->title; // If the URL currently in use is not 100% legit I force a redirect to the article in default langiage if ($LegitURL !== $PageName AND !$ArticleLanguage) { header('Location: index.php?rp=/knowledgebase/' . $vars['kbarticle']['id'] . '/' . $LegitURL . '.html'); die(); } /** * Here we set the right language depending on the URL */ // I check if the $ArticleLanguage is different from the language currently in use. I also make sure that $ArticleLanguage is different from $_SESSION['Language'] to avoid infinite loops if ($ArticleLanguage != $DefaultLanguage AND $ArticleLanguage != $_SESSION['Language']) { // Set language $_SESSION['Language'] = $ArticleLanguage; // Redirect visitor to current page to re-load the correct language header('Location: index.php?rp=' . $_GET['rp']); die(); } }); add_hook('ClientAreaHeadOutput', 1, function($vars) { /** * This guy adds canonical URL inside <head></head> tag so that Google doesn't penalize you (read post for more info) */ if ($vars['templatefile'] == 'knowledgebasearticle') { $PageName = pathinfo($_GET['rp'])['filename']; $CanonicalURL = Capsule::select(Capsule::raw('SELECT REPLACE(title, " ", "-") AS title FROM tblknowledgebase WHERE (parentid = "' . $vars['kbarticle']['id'] . '" OR id = "' . $vars['kbarticle']['id'] . '") AND REPLACE(title, "-", " ") = "' . str_replace('-', ' ', $PageName) . '" LIMIT 1'))[0]->title; return '<link rel="canonical" href="index.php?rp=/knowledgebase/' . $vars['kbarticle']['id'] . '/' . $CanonicalURL . '.html"/>'; } }); Even though we're now showing the right language to visitors, Google and all other Search Engines still hate us because we're providing duplicate contents. Their hate is so strong that they penalize us on SERP. Here is why: whmcs.com/index.php?rp=/knowledgebase/55/How-to-boil-water.html whmcs.com/index.php?rp=/knowledgebase/55/How-to-boil-vodka.html whmcs.com/index.php?rp=/knowledgebase/55/How-to-boil-rum.html whmcs.com/index.php?rp=/knowledgebase/55/How-to-boil-wine.html 4 URLs, same page and (duplicate) content. The only way to avoid penalties is that we let them know what's the unique URL by using rel="canonical" tag. The above hook adds it automatically where it is supposed to be (<head>here</head>). And if you are questioning why someone should use fake links, take a look at blackhat SEO techniques. One of your malicious competitor could publish some of those fake-links so that weeks later Google & co. penalize you for duplicate content. The above hook protects you from this technique forcing a redirect to the default article in case the URL in use is not the legit one.
  6. Naaah you didn't and anyway I don't care 🤣 No problem at all. I read your PM multiple times. You're amazing 👍 So that means I can edit my own posts. I didn't know that! It seems that I can also hide my own replies.
  7. I wanted to mention all of you to say "Thanks" but every time I use [@]nickname I can no longer type 🤣 Guess I'm dumb. Anyway I wasn't expecting that 😍 thanks! Sadly what brian said left me speechless. I hope he replies to my PM.
  8. Exactly. As far as I know all the involved hook points (pre/after/create) keep using the initial username therefore you'll need to re-query the database to get the updated username.
  9. As far as I know these should be all the available tblinvoiceitems.type: Setup Hosting Domain, DomainRegister, DomainTransfer Upgrade Item Addon PromoHosting, PromoDomain "Empty" for manually created lines I suggest you to run multiple queries by "groups" of types like follows: SELECT [...] FROM tblinvoiceitems AS t1 LEFT JOIN tblhosting AS t2 ON t1.relid = t2.id WHERE t1.type IN ("Hosting") SELECT [...] FROM tblinvoiceitems AS t1 LEFT JOIN tbldomains AS t2 ON t1.relid = t2.id WHERE t1.type IN ("Domain", "DomainRegister", "DomainTransfer") This to avoid ending up with a single huge query with tens of JOIN based on HAVING clauses.
  10. It's doable with custom scripts but before you start coding keep in mind that it's a nightmare for tens of reasons. It took me months to achieve similar results. You need to use all the following action hooks: InvoiceCreation InvoiceCreated InvoiceCreationPreEmail InvoiceCreationAdminArea InvoicePaid InvoicePaidPreEmail Depending on other factors you may have to use also AdminAreaHeaderOutput. In fact Add Transaction from backend uses an ajax POST therefore you'll probably need to code a fallback to get your things done. Here comes the bad part. All the above hook points are redundant in inexplicable ways so your scripts must be smart enough to trigger only once otherwise you mess up your invoice numbering. Good luck. You'll need it. Don't forget that you also need to abort all invoice-related email notifications so that you can fix invoice number before customers receive PDF and emails with the default numbering of WHMCS. Obviously such emails must be re-sent via API.
  11. Kian

    Knowledgebase articles.

    I have a similar clause in the TOS of a website I own. 6 years have passed. More than 1200 people registered and accepted terms and conditions. Nobody noticed it 🤣
  12. Kian


    You can use Reports or create your own script that directly interacts with database select all records you need on a daily basis.
  13. Kian

    Knowledgebase articles.

    During the last few years I started to think that writing KB articles an more in general any content is worthless. It feels like we write to satisfy SERP instead of actual people. The new trend I'm seeing is that people are becoming more and more lazy. They turn on/off checkboxes without even reading 25 chars long descriptions. Few minutes later they blame about something that is not working as expected. Guess what? It was due to that checkbox. It seems hilarious but it's not 😟
  14. Kian


    It depends on how many users you have. Personally I'd start with the cheapest one. 2 vCores and 2 GB of RAM are perfectly fine for a small WHMCS. When you start seeing your Load Averages consistently going over 1.50 then it's time to upgrade. Nowadays you should be able to increase vCores and RAM on demand in few clicks in case your Provider supports it. Anyway since we are talking about very small prices probably it would be preferable to start with the biggest.
  15. Kian

    Knowledgebase articles.

  16. Probably we're going a bit off-topic but the use of Dynamic Keys has downsides: It has an impact on performance You need to re-think and re-code parts of your software A developer already deals with PHP, Smarty, js, jquery and tens of other libraries, MySQL, Lavarel/PDO, hooks, API, needs to comment and document his own code, debug bugs, fight against nonseniscal changes of WHMCS, registrars, server modules... The idea of adding another layer of complexity isn't inspiring 🤮
  17. Reports > More... > Exports > Transactions
  18. I'm completing the refactoring of a module that I initially released on 2015 that allows to pay commissions to Affiliates depending on their sales, performance levels and monthly achievements. It has nothing to do with the Affiliates system of WHMCS. The current version of the module is still functional but has a problem. It has too many types of commissions which makes the module very difficult to use. That's what you get when you give too much room to bad users' feedback 🤔 My bad. The refactored module is based on making it simple to use for administrators and affiliates therefore I refused to implement subterfuges like 3% + 5% commissions (you don't get 8%), misleading mechanics and bonus scheme that requires a supercomputer to be explained. I'll never implement such things again. That said, I'm looking for feedback (good ones 😀), ideas and suggestions. Am I missing something important? Backend Commissions [ Click to enlarge ] You can define commission payouts based on specific Products/Services, TLDs and Product Addons both percentage and fixed amount. It is also possible to define group-based commissions (eg. all VPS servers, all TLDs). You can use padlocks to lock certain items so that they don't get updated when you change parents. Achievements [ Click to enlarge ] You can set multiple achievements to encourage Affiliates to keep selling your products. On a monthly basis the module automatically rewards them for reaching the goals you set. Achievements are based on Revenue, New Signups and Number of Sales. These same achievements can be used also to reward Top Affiliates (eg. first 5 Affiliates that brings you 1000€ of Revenue, Affiliates reaching 1000€ earn a bonus of 100€ etc.). The system pays commissions based on the following Attribution Metrics (Manual, Cookie-based, Interactive). You can put limits to Affiliation program and Payouts. There are also many other pages in backend (Dashboard, View/Filter/Manage Affiliates, Sales, Payouts, Settings) but right now I'd like to focus on the above ones. Before you ask the module already handles multiple currencies and conversion rates. Clientarea Earnings [ Click to enlarge ] This is how your Affiliates can monitor their sales, revenues and request payouts from Clientarea. Achievements [ Click to enlarge ] Here they review not only unlocked Achievements but also next ones. At a glance they can get an idea of their progress by simply looking at progress bars. This way they can decide to focus on a particular goal. Subscriptions [ Click to enlarge ] This area is for recurring commissions that can be lifetime or last for a certain amount of months. Statistics [ Click to enlarge ] We're still working on this page since we need to decide what metrics to represent in graphs. Quite frankly I really don't know. Revenue, Commissions, Payouts, ROI... what else? I'm not a "marketing guy".
  19. Affiliate Activation [ Click to enlarge ] Any successful Affiliation Network needs to provide complete information to new potential Affiliates. This page serves this purpose. Visitors can preview your Commission Structure, Monthly Achievements, Attribution Models and of course statistics about how much you paid to existing Affiliates. At the moment I was thinking about 3 Attribution Models: Last-Click (cookie based), Offline (for sales/conversions made "offline") and Interactive (end-user picks their Sales Representative from a list). Sales Management [ Click to enlarge ] Here Administrators can view, filter and manage all sales and conversions made by Affiliates. I still don't know if I should include percentile in addition to % ROI to give more information using less columns.
  20. It's always possible to decode anything otherwise your own server wound't be able to understand the content of encoded files. The purpose of services like ionCube is not to stop people from cracking your software but to make it harder. I'd say that with ionCube you can stop 99% of potential crackers. I'm talking about ones that don't know anything about obfuscation and that simply try to use ready-made softwares. You can't do anything to protect yourself against the remaining 1%. If they want to see the source sooner or later they will succeed. In such cases you need design other solutions like SaaS (you can't crack a service), Agile development (10 releases per month equal 120 cracking attempts. Not worth. Better purchase a legit license key) and custom/secret ones.
  21. On AffiliateActivation create the coupon code... INSERT INTO tblpromotions (code, [...etc...]) VALUES ({$COUPON}, [...etc...]) {$COUPON} should contain the ID of the affiliate in some ways (eg. Affiliate ID is 24 - Coupon code is 24AAABBB). This way you can determine the what is the right coupon to use for each affiliate by simply looking at digits. Alternatively if you don't want to store the ID in the code you'll need to create a separate table that stores the connection between 24 and AAABBB.
  22. Pseudo-code: On InvoicePaid if TestPromo coupon has been used and the invoice is linked to a new order... INSERT INTO tblaffiliatesaccounts (affiliateid, relid) VALUES ({$AFFID}, {$RELID}) Where {$AFFID} in your example is 10 (the ID of the Affiliate) and {$RELID} is the ID of the order (tblorders.id).
  23. Exactly. We're not talking about things like pedopornography, spam, ddos attacks, phishing that need to removed as soon as possible. For things that involve copyright an Hosting Provider can't do much. They're not a judge. This is something that have to be addressed in court or with lawyers.
  24. Kian

    Reoccurring PayPal Giving Credits

    Don't get me wrong, I understand your intentions. You solved two problems but in the same time you created new ones. Frankly I prefer having unknown payments instead of invoices moving from 2012 to 2019. I also prefer having customers paying twice for the same domain instead of invoice items that disappear even on paid invoices that I have already sent to Revenue Agency. This causes very dangerous troubles to my customers. For overpayments you should register the transaction not on the most recent invoice issued years ago but on a new invoice with the following item "Overpayment for Invoice #2018-500 credited to client's balance". I've already coded such solution years ago and it works for other kind of overpayments but it cannot handle Automatic payment. I've a script that detects them on a daily basis 99% of times. I didn't open a feature request since I'm a bit scared of the possible outcome. Considering that with your best intentions I've seen things getting worse I prefer to live with an issue I know ☹️
  25. Kian

    Reoccurring PayPal Giving Credits

    Elegantly? I'd say horribly since it screw ups invoices! Say that an automatic payment of 10 euro for blabla.com domain arrives and the proforma doesn't exist yet for a number of reasons (eg. cancelled services, postponed next due date...). Here is what happens. WHMCS looks for the most recent invoice of blabla.com and finds Invoice #2018-500 of previous year. Obviously I have already registered this invoice in 2018 and paid my income tax but WHMCS has other plans! It adds the transaction to #2018-500 updating its amount. Illegal ❌ The balance of #2018-500 is now 20 euro but what about taxes? What if tax rate has changed in 2019? You can't assume that every year is the same. Illegal ❌ The date of our #2018-500 invoice moves from 14-05-2018 to 14-05-2019. This is crazy! I've seen invoices going from 2012 to 2013, 2014, 2015, 2016, 2017, 2018, 2019! Can you explain me how am I supposed to register them? The funny thing is that in Italy we have electronic invoicing and if fail to spot any of these invoices "elegantly" handled by WHMCS, Revenue Agency not only expects 22% VAT based on a payment of 80 euro but even thinks that I earned 80 euro while I just got 10! ❌❌❌ I'm not the kind of person who blames WHMCS for everything. When I find an issue I always code my own solution but there are exactly two things in WHMCS that I can't overcome. I'm talking about this "elegant" solution and invoice items that disappear from invoices (you know why). Could you please give me an hook to suppress these two elegant solutions? I'd pay 2000$ to see them burning in hell 😍

Important Information

By using this site, you agree to our Terms of Use & Guidelines and understand your posts will initially be pre-moderated