Kaizen #84 - PHP SDK [Part I]

Kaizen #84 - PHP SDK [Part I]

Hello and welcome to another Kaizen week!

In this week's post, we'll show you how to get started with Zoho CRM's PHP SDK, and walk you through the configuration and initialization process.

PHP Software Development Kit

PHP SDK allows you to create client PHP applications that can be integrated with Zoho CRM effortlessly. It serves as a wrapper for the REST APIs, thus making it easier to use the services of Zoho CRM.

Why PHP SDK?

Easy authentication: You don't have to worry about manually managing authentication because the PHP SDK takes care of generating access/refresh tokens for you.
Easy and Efficient data exchange: With the PHP SDK, you can easily exchange data between Zoho CRM and your client PHP application, where the CRM entities are modelled as classes. You can declare and define CRM API equivalents as simple functions in your PHP application.

Prerequisites 

  • The client app must have PHP 7 or above with a cURL extension. cURL extension is used to connect and communicate with the Zoho CRM APIs.
  • The client app must have the PHP SDK installed through Composer

How to start using the PHP SDK?

  1. Prerequisite : Register your application with Zoho CRM.
  2. Install the PHP SDK.
  3. Knowledge Base : Token Persistence
  4. Configuration 
  5. initialization.

1. Register your application with Zoho CRM

Registering your application with Zoho CRM is a mandatory step in order to authenticate and authorize API calls using the OAuth2.0 standards.
  1. Go to https://api-console.zoho.com
  2. Click on Get Started or +ADD CLIENT.
  3. Choose the Client Type.
  4. Fill in the necessary details and click CREATE. Once you successfully register your self-client, you will receive a Client ID and Client Secret.

2. Install PHP SDK 

1. Install Composer, if not already installed. Please check the corresponding link for installation instructions.

2. Install PHP-SDK using Composer
  • Navigate to the workspace of your client app.
  • Run the following command in the workspace. Upon successful installation, the system will create a package named vendor in the workspace of your client app.   Note : This command installs SDK for API v2.1.

    composer require zohocrm/php-sdk-2.1

3. To use the SDK in your project, add the following line in your project PHP files. This loads and includes our PHP-SDK library in your project. If you skip this step, you will get a fatal error in response due to the missing libraries.

require 'vendor/autoload.php';

3. Token Persistence

Token persistence refers to storing and utilizing authentication tokens provided by Zoho, enabling the SDK to refresh the access tokens without the need for user intervention. The SDK offers three types of persistence - File, DB, and Custom - with file persistence being the default method. 
The persistence is achieved by writing an implementation of the inbuilt TokenStore interface, which has the following callback methods. 

Method
Description
getToken($user, $token)
Invoked before firing a request to fetch the saved tokens. This method returns an implementation of Token interface object for the library to process it.
saveToken($user, $token)
Invoked after fetching access and refresh tokens from Zoho. This method saves the token details.
deleteToken($token)
This method is used to delete the given token details.
getTokens()
This method is used to retrieve all the stored tokens.
deleteTokens() 
The method to delete all the stored tokens.
getTokenById($id, $token) 
This method is used to retrieve the user token details based on the unique ID.

a. Token Persistence using a Database

Database persistence is a technique that involves storing and retrieving data from a database. If you prefer using database persistence, you can use MySQL. 
Create a table in your database with the required columns. For example, if you want to persist your tokens in a table named token in database named zoho, use the following:
CREATE DATABASE zoho; // use this to create database named zoho
// use this to create a table named token, with the necessary columns
CREATE TABLE token ( 
  id varchar(255) NOT NULL,
  user_mail varchar(255) NOT NULL,
  client_id varchar(255),
  client_secret varchar(255),
  refresh_token varchar(255),
  access_token varchar(255),
  grant_token varchar(255),
  expiry_time varchar(20),
  redirect_url varchar(255),
  primary key (id)
);

In this example, your tokens will be persisted in the token table in your zoho database.

b. File Persistence

File Persistence allows storing and retrieving the authentication tokens from the given file path. The file contains id, user_mail, client_id, client_secret, refresh_token, access_token, grant_token, expiry_time and redirect_url. 

c. Custom Persistence

Custom Persistence refers to a technique where users can create their own method of storing and retrieving authentication tokens. To use this method, users need to implement the TokenStore interface and override its methods according to their own logic.

4. Configuration

Configuration is a critical step in which you set up SDK's configuration details like user authentication, token persistence, logging and API call timeout settings, and more. Listed below are the keys that you define in this step.

Key
Description
user
mandatory
Represents the mail id, which is used to identify and fetch tokens from the File or DB.
environment
mandatory
Represents the data centre details in Domain::Environment pattern.
Domains : USDataCenter, EUDataCenter, INDataCenter, CNDataCenter, AUDataCenter
Environments : PRODUCTION(), DEVELOPER(), SANDBOX()
token
mandatory
Contains user token details. Depending on the tokens, you can choose grantToken flow, refreshToken flow or accessToken flow.
logger
optional
Contains the configuration for logging exceptions and API call information. By default, the logs will be available in the workspace as sdk_logs.log.
store
optional
Contains details for the Token Persistence object. You can choose between DB Store, File Store or Custom Store, and configure accordingly.
SDKConfig
optional
Contains additional configuration details like timeout, autorefresh fields, picklistvalidation, etc
requestProxy
optional
Contains the details of the proxy, if you are using a proxy server to authenticate and make the API calls.
resourcePath
optional
The path containing the absolute directory path to store user specific files containing the module fields information.


Let us discuss how to configure each of them, in detail.
a. user : The user key will be used to store and identify the tokenstore details in the DB or File Storage for token persistence. Create an instance of UserSignature that identifies the current user with the following :
  1. $user = new UserSignature("patricia@zoho.com");
b. environment : The API environment which decides the domain and the URL to make API calls. 
  1. $environment = USDataCenter::PRODUCTION();
c. token : Create an instance of OAuthToken with the information that you get after registering your Zoho client. Depending on the tokens available with you, you can choose one of the following flows. 
Note : You need to generate the tokens (grant/access/refresh) beforehand. 
  • grantToken flow - You should use the grant Token for configuration.
    $token = (new OAuthBuilder())
      ->clientId("clientId")
      ->clientSecret("clientSecret")
      ->grantToken("grantToken")
      ->redirectURL("redirectURL")
      ->build();

  • refreshToken flow -In this flow, use the refresh token.
    $token = (new OAuthBuilder())
      ->clientId("clientId")
      ->clientSecret("clientSecret")
      ->refreshToken("refreshToken")
      ->redirectURL("redirectURL")
      ->build();

  • accessToken flow - You can use the access token to configure in this flow. Please note that the token will not be persisted in this case, and the access token will be directly used to make the API calls.

    $token = (new OAuthBuilder()
      ->accessToken("accessToken")
      ->build(); 

d. logger : Create an instance of Logger Class to log exception and API information. You can set the level you want to log (FATAL, ERROR, WARNING, INFO, DEBUG, TRACE, ALL, OFF), and also configure the file path and file name for the log file.

$logger = (new LogBuilder())
->level(Levels::INFO)
->filePath("/Documents/php_sdk_log.log")
->build();

e. store : Configure your token persistence using this method. If this is skipped, the SDK creates the sdk_tokens.txt in the current working directory to persist the tokens by default.
  • DB Store - Configure the Database details, where you want to store your tokens.
    $tokenstore = (new DBBuilder())
    ->host("hostName")
    ->databaseName("dataBaseName")
    ->userName("userName")
    ->password("password")
    ->portNumber("portNumber")
    ->tableName("tableName")
    ->build();

  • File store - Give the absolute file path, where you want to store the tokens. 
    $tokenstore = new FileStore("absolute_file_path");

  • Custom Store - In this method, you can implement your own method for storing and retrieving the tokens. Please note that to do so, you must implement the TokenStore interface, and override its callback methods (getToken, saveToken, deleteToken, getTokens, deleteTokens, getTokenById). 
    $tokenstore = new CustomStore();

Note :  The corresponding storage will  have id, user_mail, client_id, client_secret, refresh_token, access_token, grant_token, expiry_time and redirect_url. The id is a unique system generated key.

f. SDKConfig : The additional SDK configurations are taken care of with this method. 

Configuration Key
Description
autoRefreshFields
Default Value : False
A boolean configuration key to enable or disable automatic refreshing of module fields in the background. If set to true, fields are refreshed every hour, and if set to false, fields must be manually refreshed or deleted.
pickListValidation
Default Value : True
This field enables or disables pick list validation. If enabled, user input for pick list fields is validated, and if the value does not exist in the pick list, the SDK throws an error. If disabled, the input is not validated and the API call is made.
enableSSLVerification
Default Value : True
A boolean field to enable or disable curl certificate verification. If set to true, the SDK verifies the authenticity of certificate. If set to false, the SDK skips the verification.
connectionTimeout
Default Value : 0
The maximum time (in seconds) to wait while trying to connect. Use 0 to wait indefinitely.
timeout
Default Value : 0
The maximum time (in seconds) to allow cURL functions to execute. Use 0 to wait indefinitely.
  1. $autoRefreshFields = false;
  2. $pickListValidation = false;
  3. $enableSSLVerification = true;
  4. $connectionTimeout = 2;
  5. $timeout = 2;
  6. $sdkConfig = (new SDKConfigBuilder())
  7. ->autoRefreshFields($autoRefreshFields)
  8. ->pickListValidation($pickListValidation)
  9. ->sslVerification($enableSSLVerification)
  10. ->connectionTimeout($connectionTimeout)
  11. ->timeout($timeout)
  12. ->build();
g. requestProxy : Create an instance of RequestProxy containing the proxy properties of the user. Configure this only if you're using a proxy server to make the API calls.

$requestProxy = (new ProxyBuilder())
 ->host("proxyHost")
 ->port("proxyPort")
 ->user("proxyUser")
 ->password("password")
 ->build();

h. resourcePath : Configure path containing the absolute directory path to store user specific files containing module fields information.

$resourcePath = "/Documents/phpsdk-application";

5. Initilization

Once you have completed the configuration process, you can move on to initializing the SDK and begin making API requests.

Here is a sample code to initialize the SDK, using refresh token flow and DB Persistence.

<?php
use com\zoho\api\authenticator\OAuthBuilder;
use com\zoho\api\authenticator\store\DBBuilder;
use com\zoho\api\authenticator\store\FileStore;
use com\zoho\crm\api\InitializeBuilder;
use com\zoho\crm\api\UserSignature;
use com\zoho\crm\api\dc\USDataCenter;
use com\zoho\api\logger\LogBuilder;
use com\zoho\api\logger\Levels;
use com\zoho\crm\api\SDKConfigBuilder;
use com\zoho\crm\api\ProxyBuilder;
use com\zoho\api\authenticator\store\DBBuilder;

require_once "vendor/autoload.php";

class Initialize
{
  public static function initialize()
  {
    $user = new UserSignature("patricia@zoho.com");
    $environment = USDataCenter::PRODUCTION();
    $token = (new OAuthBuilder())
    ->clientId("1000.xxxxxxxxxxxxxxxx")
    ->clientSecret("554a9776d10ff016a92c1eb01xxxxxxxxxx")
    ->refreshToken("1000.xxxxxxxxxxxxxxxxxxxx")
    ->redirectURL("www.zoho.com")
    ->build();
    $logger = (new LogBuilder())
    ->level(Levels::INFO)
    ->filePath("/Documents/php_sdk_log.log")
    ->build();
   $tokenstore = (new DBBuilder())
    ->host("insert_your_hostname_here")
    ->databaseName("insert_your_database_name_here")
    ->userName("insert_your_db_username_here")
    ->password("insert_your_db_password_here")
    ->portNumber("insert_your_portnumber_here")
    ->tableName("insert_your_table_name_here")
    ->build();
    $autoRefreshFields = false;
    $pickListValidation = false;
    $connectionTimeout = 2;
    $timeout = 2;
    $sdkConfig = (new SDKConfigBuilder())
    ->autoRefreshFields($autoRefreshFields)
    ->pickListValidation($pickListValidation)
    ->sslVerification($enableSSLVerification)
    ->connectionTimeout($connectionTimeout)
    ->timeout($timeout)
    ->build();
    $resourcePath = "/Documents/phpsdk-application";
    $requestProxy = (new ProxyBuilder())
    ->host("proxyHost")
    ->port("proxyPort")
    ->user("proxyUser")
    ->password("password")
    ->build();
    (new InitializeBuilder())
    ->user($user)
    ->environment($environment)
    ->token($token)
    ->store($tokenstore)
    ->SDKConfig($configInstance)
    ->resourcePath($resourcePath)
    ->logger($logger)
    ->requestProxy($requestProxy)
    ->initialize();
  }
}
?>


You are now all set to explore the functionalities of SDK. Here is a sample code to get the records from Leads module, with the ifmodifiedsince header.

<?php
use com\zoho\api\authenticator\OAuthBuilder;
use com\zoho\crm\api\dc\USDataCenter;
use com\zoho\crm\api\InitializeBuilder;
use com\zoho\crm\api\UserSignature;
use com\zoho\crm\api\record\RecordOperations;
use com\zoho\crm\api\record\GetRecordsHeader;
use com\zoho\crm\api\HeaderMap;
use com\zoho\crm\api\ParameterMap;
require_once "vendor/autoload.php";

class Record
{
    public static function initialize()
    {
        $user = new UserSignature('myname@mydomain.com');
        $environment = USDataCenter::PRODUCTION();
        $token = (new OAuthBuilder())
        ->clientId("1000.xxxxxxx")
        ->clientSecret("4b5baxxxxxxxxxxxxf")
        ->grantToken("1000.xxxxx")
        ->build();
        (new InitializeBuilder())
            ->user($user)
            ->environment($environment)
            ->token($token)
            ->initialize();
    }

    public static function getRecords()
    {
        $recordOperations = new RecordOperations();
        $paramInstance = new ParameterMap();
        $headerInstance = new HeaderMap();
     $ifmodifiedsince = date_create("2022-06-01T12:00:00+05:30")->setTimezone(new \DateTimeZone(date_default_timezone_get()));
        $headerInstance->add(GetRecordsHeader::IfModifiedSince(), $ifmodifiedsince);
        $response = $recordOperations->getRecords("Leads", $paramInstance, $headerInstance);
        echo($response->getStatusCode() . "\n");
        print_r($response);
    }
}
Record::initialize();
Record::getRecords();


Next week, we will dive deeper and provide more sample codes to help you further. Stay tuned!

If you have any queries, let us know the comments below, or drop an email to support@zohocrm.com. We would love to hear from you. 



    • Recent Topics

    • zoho smtp limit for free users

      What is Zoho SMTP limit for free users?
    • Mailboxes and Alias Email Addeases - Best Palestine’s and Advice:

      Mailboxes and Alias Email Addresses - Best Practices and Advice: what is the best practice for the efficient means to manage And sort, alias, email addresses and third-party after or even the Zoho app itself. I am currently using both Thunderbird and
    • Has anyone built a discussion forum with a Creator Portal?

      I have built a Creator app for organisations to apply for refurbished tools that are sent by a charity. The charity now wants recipient organisations to be able to connect with each other within a region or country, to share advice on maintaining the
    • Ability for Super Admin to Set Locale Information for Users in Zoho Recruit

      Dear Zoho Recruit Team, I hope you're doing well. We would like to request the ability for Super Admins to configure Locale Information (Country/Region, Date Format, and Time Format) for users in Zoho Recruit. Currently these settings are only configurable
    • Add a block or widget to Zoho Sites that allows users to create an interactive contact card with contact buttons (email, LinkedIn, website, etc)

      The proposed feature consists of a pre-designed, customizable block that displays a person's contact information (e.g., a speaker, sales representative, or independent professional) and offers quick access to: Email (icon with mailto: link) LinkedIn profile
    • Zoho Recruit > Email Templates

      Dear All Background: We are using Zoho Recruit for the 4 business units under our group of company it our posting our of Job it will be done via our internal recruiter. In the Email templates, i want to be able to insert the individual business unit,
    • Zoho Voice & Zoho Recruit/CRM

      Hello, I'd love to use Zoho Voice with Recruit and CRM but it would need something very important to me, that has been a game changer to my daily routine, like Calendly has been for scheduling interview. It's call transcription with AI. I'm using Noota
    • Change start time after starting the timer

      Hello Projects Community, an amazing feature would be to change the start time of a running timer. I know this from some other time tracking softwares. Any idea about this? Best wishes Niels
    • Sending possible. Receiving not possible.

      We are not receiving mail in our company email. Could you please solve this. It has been recurring and I want it to be resolved once and for all. Please help.
    • Problema para enviar y recibir correos

      Buenos días, mi cuenta de correo secretaria@construccmauro.com presenta problemas y no me permite ni me envía ni recibe correos, me sale este error.No fue posible enviar el mensaje; Motivo: 554 5.1.8 Correo electrónico bloqueado saliente.  Aprende más., Agradezco
    • Data access tasks like 'For each record' aren't supported for 'integration forms'.

      My code is not running because i get the error "Data access tasks like 'For each record' aren't supported for 'integration forms'." I have my integration setup with Zoho CRM + Zoho Creator, the form is visible and working on my zoho creator however in
    • Zoho Reports Not Grouping from Subforms

      I have created reports from a subform. We have a budget from a standard field, and the bills added to a subform. I've summarised the bills in a field. In edit mode, the bills are joined per supplier, which is what we want. But then it converts and separates/duplicates
    • Create Automation for the Field "Mark up by"

      Hello everyone, I'm importing expenses from Zoho Expense to Zoho Books. I want to auto-calculate the "Mark up by" field based on the custom field “Discount” I created before. The trigger of the workflow will be the creation of the expense itself. The
    • Free Webinar Alert! Before vs After: Proven ROI from Zoho CRM + Workplace Integration

      Hello Zoho Workplace Community! Before: Scattered tools, lost leads, fragmented communication. Are you ready to stop the constant back and forth between tools to manage leads, emails, and team communication? After: Connected tools, streamlined processes,
    • Zoho Forms to Zoho Sign Integration - Fields Missing

      If a Zoho Form has image fields, it seems these can't be transferred to a Zoho Sign template for digital signature. Is there any way of pre-filling Zoho Form images onto a Zoho Sign template? Many thanks.
    • Is zoho SMTP slow today?

      Hi guys, Since yesterday I'm facing a slow communication over SMTP while sending emails. I already tried to use tls and ssl but nothing changes. There is anyone else experiencing related issues? I didn't find any maintenance in progress. Tested another
    • Link project invoices to sales orders

      As a business owner and project manager I create estimates with my clients which then become sales orders. When billing for project work I want to invoice against the agreed sales order. I see that I can create invoices and link them to sales orders in
    • The Urgent Need for Native Brazilian Payment Integrations: PIX and Direct Bank Connections

      Hello Zoho Team, I am writing to emphasize a critical functionality gap for Zoho Books in the Brazilian market: the lack of modern, native payment gateway integrations. The current options are insufficient. The Mercado Pago integration, for instance,
    • How to Fetch Images from Related Modules in Zoho CRM Mail Merge Templates?

      Hi team , Hope this email finds you well. I have a requirement where I need to create mail merge templates within Zoho CRM in such a way that they fetch images from a record stored in a different module. The way it works is I have one module "A" which
    • Zoho Calendar (Refresh Rate)

      Why don't the calendars refresh more than every 12 hours? That is crazy. I cannot be the only user who wants to see this change? I see and understand that I can MANUALLY update them, but need them to auto refresh either (1) whenever there is a change
    • "In Zoho CRM, during the Blueprint transition to the QC stage, I want to make the 'Packing Proof' image field mandatory."

      @Dr Saurabh Joshi @Haiku Technical Support @Ishwarya SG @Sparrow Hill President @Hugh Marshall "In Zoho CRM, during the Blueprint transition to the QC stage, I want to make the 'Packing Proof' image field mandatory."
    • Flow Error

      Hi Zoho Team, Any idea on this? This happens recently. Zoho Desk says "You are not authorized to access this resource."
    • Zoho Books/Square integration, using 2 Square 'locations' with new Books 'locations'?

      Hello! I saw some old threads about this but wasn't sure if there were any updates. Is there a way to integrate the Square locations feature with the Books locations feature? As in, transactions from separate Books locations go to separate Square locations
    • Books/Square integration with multiple bank accounts

      Hello, I need some workaround ideas! We have two parts of our business which have their own bank accounts and customers. We use 'locations' in Square to allocate the payments to the correct place and we use 'branches' in Zoho Books to define which transactions
    • Intergrating multi location Square account with Zoho Books

      Hi, I have one Square account but has multiple locations. I would like to integrate that account and show aggregated sales in zoho books. How can I do that? thanks.
    • [Webinar] Deluge Learning Series - Deluge Learning Series Built-in Functions in Deluge

      We’re excited to welcome you to the next session in our Deluge Learning Series – Built-in Functions in Deluge – Part 1. In this focused 1-hour live session, we will explore the practical use of built-in functions in Deluge scripting, with a spotlight
    • Zoho Hilfe in NRW für Kundenprojekt gesucht!

      Hallo, wir sind auf der Suche für ein Kundenprojekt, wer die Implementierung von Zoho übernehmen kann. Unsere Stärke liegt im Onlinemarketing, bzw. der Automatisierung mit Klick-Tipp und den Tools drum herum. In dem Projekt können wir dem Kunden mit dem jetzigen CRM nicht weiter helfen, bzw. ihn nicht auf die nächste Stufe bringen. Leider komme ich mit meiner Suche in Foren, SocialMedia und sonst wo nicht weiter.  Der Idealfall ist ein gemeinsamer Besuch vor Ort beim Kunden und hier eine Präsentation
    • Gaps in Core HR Functionalities in Zoho People

      Hello People team, We've been using Zoho People for quite some time now and truly appreciate its flexibility and customizability. That said, I wanted to share some feedback based on our experience implementing core HR processes within the platform. Several
    • Product and Service

      Hi guys, there is a difference between layout of product and service if Long Description field have some kind of text. Please see screenshot 1 for Service here: https://prnt.sc/7xWwPKd29nWP for Product here: https://prnt.sc/LGmtVd_U6H7q As you can see
    • Anyone Building AI-Based SEO Dashboards in Zoho Analytics?

      Hey everyone, I’m currently working on an SEO reporting dashboard in Zoho Analytics and looking to enhance it with AI-based insights—especially around AI visibility, keyword trends, and traffic sources. The goal is to track not just traditional metrics
    • Phone Number Integration

      I would like to be able to track my cell phone calls in the CRM.  So if a contact calls my cell phone number, a note of that phone call will be made in the CRM so that I don't have to remember to go back in and add the call manually. Is there a way to connect the CRM and my cell phone via a 3rd party? Such as Google Voice, Skype, etc. Its important that my clients don't have to call a new or different number and that sent calls use the same number as well. So if there is a way to connect my phone
    • Woocommerce Line Item Information

      I'd like to add each line item from a Woocommerce order to a zoho creator form record. The line items are found within the line items array, but I'm not sure how to get each item out of the array? Any help would be much appreciated.
    • Create Encrypted Zoho Forms URL for SMS Pre-Population forms using zfcrm_entity=

      Hello Zoho Forms Community and Zoho Team, I’m trying to send a Zoho Forms URL via SMS with pre-populated CRM contact data, inspired by a post from four years ago by GlennP (https://help.zoho.com/portal/en/community/topic/passing-the-crm). Our form is
    • Sales IQ - Bot Builder - Forward to Operator Action Card Improvement

      Hi team, It would be a great improvement to have an additional branch out of the Forward to Operator Action Card. I would like to allow 10 seconds for an operator to pick up the chat, if they don't or if they reject the chat I would like the Bot to continue
    • Important notice: Migration is needed from the legacy ASAP to the latest ASAP Zoho Desk flow

      We recommend upgrading to the latest version, as the older version will eventually be discontinued and will no longer receive any further enhancements or bug fixes. We are announcing an important update concerning the ASAP help widget in Zoho Desk. The
    • NFC Support

      Hi, I would like NFC Support like Zoho Creator, to pre-fill field values by scanning an RFID device. Zoho Creator NFC here: https://www.youtube.com/watch?v=F4JoMWnF82I Both on a Field's User Input (Mobile Apps), in additon to QR Code & Barcode today,
    • Live webinar: Building brand consistency using Zoho Show

      Getting people to remember your brand isn’t easy. And if your branding isn’t consistent, it becomes almost impossible. One place where consistency really matters? Presentations. Whether it’s a sales pitch, an event deck, or a team update, your slides
    • Getting an error "Not able to connect server. Verify your network connection" during proforma invoice converting to invoice.

      Getting an error "Not able to connect server. Verify your network connection" during proforma invoice converting to invoice. 
    • calendar I created in Zoho Creator not being imported to Google calendar any longer

      A calendar that I created in my Zoho Creator app is no longer updating (or showing up at all) in my google calendar. It used to export appointments I set up in my app to google calendar. I cannot figure out how to correct this.
    • Quoting Subscriptions with one Time costs

      Hello all, We sell a subscription SaaS service for which we provide one-time services for implementation and customization. Using CRM quotes i was able to create customized total fields to show the total one-time costs, monthly cost, total subscription
    • Next Page