remp/crm-salesfunnel-module

CRM SalesFunnel Module

3.5.0 2024-10-15 12:08 UTC

README

Translation status @ Weblate

Installing module

We recommend using Composer for installation and update management.

composer require remp/crm-salesfunnel-module

Enabling module

Add installed extension to your app/config/config.neon file.

extensions:
	- Crm\SalesFunnelModule\DI\SalesFunnelModuleExtension

Configuration

By default we register for every sales funnel short route (e.g. for sales funnel with URL key sample, route is http://crm.press/sample). You can turn off registration of slug by adding following setting to configuration file.

sales_funnel:
	funnel_routes: false

Sales funnel will be still accessible through long route (e.g. http://crm.press/sales-funnel/sales-funnel-frontend/show?funnel=sample).

Scheduled commands

Please add following commands to your scheduler:

  • sales-funnel:distributions. Every sales funnel precalculates data about users who paid through it in advance. These calculations are used to provide stats and distributions on the funnel detail. Stats are updated on the fly, but it's recommended to run this once a day.

Using sales funnels

Sales Funnel module provides a way to present subscription type offering to user in a way that's completely independent from the rest of application.

You can find list of available sales funnels and create new funnels in CRM admin (/sales-funnel/sales-funnels-admin/).

To create new sales funnel, you need to enter several fields (required are bold):

  • Name. Name of the sales funnel displayed in CRM admin listings and statistics.
  • Active. Flag, whether the window should be available to users or not.
  • URL key. Slug under which the window is available. All funnels are available at custom routes - e.g. sample funnel with url key sample would be available at http://crm.press/sales-funnel/sales-funnel-frontend/show?funnel=sample and at http://crm.press/sample (if slugs are allowed, see Configuration).
  • Only logged. Flag to limit access to only logged in users.
  • Only not logged. Flag to limit access to only not logged in users.
  • Segment. Segment selection to limit access only to members of a specific segment.
  • Valid from. Date constraint allowing funnel to be available only after specified date.
  • Valid to. Date constraint allowing funnel to be available only before specified date.
  • Funnel HTML content. HTML content of funnel (top-bottom HTML needs to be provided; description below)
  • Funnel no access HTML. HTML to display to user if she doesn't have an access to window due to specified constraints. If no HTML is provided, default funnel-no-access is displayed
  • Funnel error screen HTML. HTML to display if there's any kind of validation error with funnel. If no HTML is provided, default funnel-error is displayed

Once the funnel is created, there are more options that can be configured within the detail of created funnel:

  • Subscription types. Every funnel has list of whitelisted subscription types. Array of allowed subscription types is provided to HTML content template as a twig variable for further usage. Only whitelisted subscription types will be allowed to be submitted within funnel, otherwise the validation will return an error.

  • Payment gateways. Every funnel has a list of whitelisted payment gateways. Array of allowed gateways is provided to HTML content template as a twig variable for further usage. Only whitelisted gateways will be allowed to be submitted within funnel, otherwise the validation will return an error.

The HTML content of funnel can be anything from very simple HTML form to micro JS application calling backend APIs and routing user through multi-step experience. Funnels support Twig templating and provide variables to be used within the template:

  • funnel: Instance of sales_funnels table row (as an Nette\DB\Table\ActiveRow implementation)

  • isLogged: Flag, whether the user is logged in or not. Should be utilized to display login form.

  • gateways: Array of gateways table rows. Should be utilized to show/hide whitelisted gateways in combination with Twig's if statements:

    {% if gateways['paypal'] is defined %}
      <div class="row"><!-- gateway input --></div>
    {% endif %}
  • subscriptionTypes: Array of subscription_types table rows. Should be utilized to show/hide whitelisted subscription types in combination with Twig's if statements:

    {% if subscriptionTypes['web_month'] is defined %}
      <div class="row"><!-- subscription type input --></div>
    {% endif %}
  • addresses: Array with all stored user's addresses

  • meta: Sales funnel metadata seeded into the sales_funnel_meta table

  • jsDomain: Javascript domain to be used if working with cookies

  • actualUserSubscription: Reference to actual user's subscription - row of subscriptions table as an Nette\DB\Table\ActiveRow implementation.

  • referer: Referer parameter used to access the sales funnel. Loaded primarily from referer query string parameter. If not provided, HTTP referer is used instead.

  • values: Array of values submitted before if the page is reloaded.

  • errors: Array of errors generated on last submit.

You can use/ignore any of these variables. They're provided to give your template information about user for you to decide what user should see.

Twig also provides a translation filter trans which can be used to translate strings within the template or encapsulate duplicated texts or messages across the funnels:

<label>{{ 'internal.salesfunnel.gateway.cardpay.label'|trans }}</label>
{# This will work if you're using an Internal module with the existing translation key 'internal.salesfunnel.gateway.cardpay.label' in your translations #}

The final output of sales funnel should be POST request to /sales-funnel/sales-funnel-frontend/submit- either via HTML form or via AJAX request. The POST params should contain:

  • funnel_url_key: URL key of funnel defined when creating sales funnel. Can be populated from {{ funnel.url_key }} variable.
  • auth: Flag 0 / 1 whether CRM should attempt to authenticate user with email and password.
  • email: If authenticating, user should provide his email.
  • password: If authenticating, user should provide his password.
  • subscription_type: Code of subscription type (not ID) that user selected to purchase.
  • payment_gateway: Code of payment gateway user selected to pay through.
  • additional_amount: Additional amount of money that should be included within the payment on top of standard subscription type price. Used for donations.
  • additional_type: Flag, whether the additional_amount should be included in the future charges if the payment is made via recurrent gateway. Allowed values are single / recurrent.
  • address_id: If user was offered an address in sales funnel, you can submit ID of address that will be stored within the payment and later in created subscription. Useful if you want to enforce delivery address to be non-standard.
  • custom: Key-value pairs (i.e. custom[key]=value) which will be stored as text in payment's note field.
  • allow_redirect: Flag specifying whether the response should contain redirect headers or just JSON response with URL of payment gateway. Useful if you don't want to redirect user away from CRM, but only submit values via AJAX and display payment gateway within iframe of a modal window.

If the form is valid, backend will create new unfinished payment for the user and the server response will contain redirection to payment gateway.

Sample funnel

Module provides seeder with single demo funnel which contain very barebone bootstrap-based design and simple javascript handler to ask and validate user's email/password.

When the module is enabled, you can seed this funnel by running php bin/command.php application:seed and it will get available at /sample URL on your domain. You can (and should) also find your funnel in CRM admin (/sales-funnel/sales-funnels-admin/)as you still need to enable subscription types and gateways for this newly seeded funnel to work correctly.

If you want to only view the sample implementation of funnel, you can find it here.

Events and handlers

PaymentItemContainerReadyEvent

This event is emitted from SalesFunnelFrontedPresenter::renderSubmit after PaymentItemContainer was initialized and filled with base payment items (eg SubscriptionTypePaymentItem) but before payment is created.

All handlers which register listener for this event have access to whole PaymentItemContainer. It can be used to add payment items before payment is created and before customer is redirected to payment provider.

ExampleModule implementation

For example - your sales funnel contains specific donation input field with name specific_donation and you want to add it to the payment.

Create handler Crm\ExampleModule\Events\PaymentItemContainerReadyEventHandler:

<?php

namespace Crm\ExampleModule\Events;

use Crm\PaymentsModule\PaymentItem\DonationPaymentItem;
use League\Event\AbstractListener;
use League\Event\EventInterface;

class PaymentItemContainerReadyEventHandler extends AbstractListener
{
    public function handle(EventInterface $event)
    {
        $paymentData = $event->getPaymentData();
        if (isset($paymentData['specific_donation']))
        {
            $paymentItemContainer = $event->getPaymentItemContainer();
            $paymentItemContainer->addItem(
                new DonationPaymentItem(
                    $name = 'Specific donation',
                    $price = $paymentData['specific_donation'],
                    $vat = 0
                )
            );
        }
    }
}

And initialize listener in ExampleModule\ExampleModule.php

  public function registerEventHandlers(\League\Event\Emitter $emitter)
{
  //...
  $emitter->addListener(
    \Crm\SalesFunnelModule\Events\PaymentItemContainerReadyEvent::class,
    $this->getInstance(\Crm\ExampleModule\Events\PaymentItemContainerReadyEventHandler::class)
  );
  //...

Newly created payment by SalesFunnel will now contain specific donation if sales funnel received this field.

Sales funnel twig variables and snippets

Registration

When using sales funnels you can also pass custom variables or use snippets feature provided by ApplicationModule Crm\ApplicationModule\Repositories\SnippetsRepository.

For this feature to work you need to register custom data provider which implements SalesFunnelVariablesDataProviderInterface in sales_funnel.dataprovider.twig_variables data provider path.

registration:

$dataProviderManager->registerDataProvider(
    'sales_funnel.dataprovider.twig_variables',
    $this->getInstance(SalesFunnelTwigVariablesDataProvider::class)
);

data provider example:

final class SalesFunnelTwigVariablesDataProvider implements SalesFunnelVariablesDataProviderInterface
{
    // add ability to load snippets, check docblock for further info
    use SalesFunnelTwigSnippetLoaderTrait;

    public function provide(array $params): array
    {
        if (!isset($params[self::PARAM_SALES_FUNNEL])) {
            throw new DataProviderException('missing [' . self::PARAM_SALES_FUNNEL . '] within data provider params');
        }
        
        $salesFunnel = $params[self::PARAM_SALES_FUNNEL];

        $returnParams = [];
        
        // adding custom twig variable
        $returnParams['your_custom_twig_variable'] = 'your_custom_twig_variable';
        
        // load snippets (feature provided by ApplicationModule)
        $snippetsToLoad = ['header', 'footer', 'sales-funnel-common-header'];
        $returnParams += $this->loadSnippets($salesFunnel, $loadSnippets);
        
        return $returnParams;
    }
}

Usage within sales funnels

Snippet's name is changed to camel case with prefix snippet. So sales-funnel-common-header becomes:

  {{ snippetSalesFunnelCommonHeader|raw }}

Variables are accessible as provided. So your_custom_twig_variable will be:

{% if your_custom_twig_variable %}
  <h1>{{ your_custom_twig_variable }}</h1>
{% endif %}

Iframe deprecation in sales funnels

Deprecation

Because of modernization and multiple problems with using iframes as wrapper for sales funnels (eg. scrolling & focus issues on iOS devices), we stopped using iframe to render sales funnels.

Methods / routes that are now marked deprecated:

⚠️ They will be removed (or refactored) in CRM version 4.0.

Solution

This will be default solution If you are using Subscriptions:Subscriptions:new as default route, we recommend to update your config Default route (see Application category at https://crm.press/admin/config-admin/) to SalesFunnel:SalesFunnel:newPopup. This method loads and renders default sales funnel from config Default sales funnel (see Sales Funnels category) directly without using iframes.

You can register routes for your sales funnels directly in your custom module. For example:

use Crm\SalesFunnelModule\DI\Config;
use Crm\SalesFunnelModule\Models\SalesFunnelsCache;

class DemoModule extends CrmModule
{
    // ...

    public function registerRoutes(RouteList $router)
    {
        // to avoid iframes for all sales funnels of Demo instance
        if ($this->config->getFunnelRoutes()) {
            foreach ($this->salesFunnelsCache->all() as $salesFunnel) {
                $router->prepend(new Route(
                    "<funnel {$salesFunnel->url_key}>",
                    'SalesFunnel:SalesFunnelFrontend:show'
                ));
            }
        }
    }

    // ...
}

For commonly used parts of sales funnel such as header, footer etc., you can use snippets feature provided by ApplicationModule.

Components

AmountDistributionWidget

Admin sales funnel detail distribution stats component.

alt text

Source code

How to use

DaysFromLastSubscriptionDistributionWidget

Admin sales funnel detail distribution stats component.

alt text

Source code

How to use

FinishRegistrationWidget

Frontend successful payment/registration widget.

alt text

Source code

How to use

NewSubscriptionWidget

Frontend new subscription widget with iframe containing sales funnel.

Source code

How to use

PaymentDistributionWidget

Admin sales funnel detail stats widget.

alt text

Source code

How to use

SalesFunnelUserListingWidget

Admin user listing widget.

alt text

Source code

How to use

SubscriptionTypesInSalesFunnelsWidget

Admin sales funnel detail subscription types listing.

alt text

Source code

How to use

WindowPreview

Admin sales funnel detail preview.

Source code

How to use