# Integrating a Module with PrestaShop Account and Billing (Deprecated)

Warning: Deprecated Page

This page about the integration of the PrestaShop Account and Billing components using VueJS is deprecated. A new procedure is now available to integrate PrestaShop Account and PrestaShop Billing with vanilla JavaScript.

A SaaS App is a composition of PHP (opens new window) as backend and Vue 2 (opens new window) as frontend.

TIP

In the future Vue 2 may not be required as the PrestaShop Billing Component doesn't require it.

Here is the step to create a SaaS App :

  1. Create a PrestaShop Module (opens new window)
  2. Make the PHP part injecting the proper context in the window object, which is required for PsAccount and PsBilling.
  3. Create a JS app including PsAccount and PsBilling
  4. Inject the context in PsAccount and PsBilling

First of all, create a basic module following the PrestaShop documentation: https://devdocs.prestashop.com/1.7/modules/creation/ (opens new window)

Compatibility

SaaS App is compatible from PrestaShop 1.6.1.x and PHP 5.6

# Backend

PsAccount

The PsAccount module is a pre-requisite for every SaaS App module, it provides an SSO allowing to link the Addons market's accounts to the ones in the PrestaShop Core

# Composer

You must add some dependencies in the composer.json (opens new window) of your newly created module.

The required dependencies are prestashop/prestashop-accounts-installer, prestashop/module-lib-service-container and prestashopcorp/module-lib-billing. It will help you to construct the context required to make SaaS App works.

"require": {
    "php": ">=5.6",
    "prestashop/prestashop-accounts-installer": "^1.0.1",
    "prestashop/module-lib-service-container": "^1.4",
    "prestashopcorp/module-lib-billing": "^1.3.1"
},
1
2
3
4
5
6

You should register your module as a service.

If you want to have more information about Services, you can check on documentation (opens new window)

services:
  <module_name>.module:
    class: <module_name>
    public: true
    factory: ['Module', 'getInstanceByName']
    arguments:
      - '<module_name>'

  <module_name>.context:
    class: Context
    public: true
    factory: [ 'Context', 'getContext' ]
1
2
3
4
5
6
7
8
9
10
11
12

It will be useful for afterwards













 
 













{
    "name": "prestashop/<module_name>",
    "description": "",
    "config": {
        "preferred-install": "dist",
        "optimize-autoloader": true,
        "prepend-autoloader": false
    },
    "require-dev": {
        "prestashop/php-dev-tools": "^4.2.1"
    },
    "require": {
        "php": ">=5.6",
        "prestashop/prestashop-accounts-installer": "^1.0.1",
        "prestashop/module-lib-service-container": "^1.4",
        "prestashopcorp/module-lib-billing": "^1.3.1"
    },
    "autoload": {
        "classmap": [
            "<module_name>.php"
        ]
    },
    "author": "PrestaShop",
    "license": "MIT"
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# <module_name>.php

Note: For simplification, all PHP methods listed below are created in the <module_name>.php. Feel free to re-organize the code structure in another way due to the module evolution.

# PsAccount

Requirement

SaaS App require PsAccount to be installed on the shop in order to work.

# Load PsAccount utility

First you need to register PsAccount within your module. You should add a getService method. Then update the install() hook.

It will allow your module to automatically install PsAccount when not available, and let you use PsAccountService.

class <module_name> extends Module {
    /**
     * @var ServiceContainer
     */
    private $container;

    public function __construct()
    {
        // ...

        if ($this->container === null) {
            $this->container = new \PrestaShop\ModuleLibServiceContainer\DependencyInjection\ServiceContainer(
                $this->name,
                $this->getLocalPath()
            );
        }
    }

    // ...
    public function install()
    {
        // Load PS Account utility
        return parent::install() &&
            $this->getService('<module_name>.ps_accounts_installer')->install();
    }

    // ...

    /**
     * Retrieve service
     *
     * @param string $serviceName
     *
     * @return mixed
     */
    public function getService($serviceName)
    {
        return $this->container->getService($serviceName);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

You should follow the documentation from prestashop-accounts-installer (opens new window) to properly install the PS Account utility.

You need to register PsAccount as Service. The 5.0 specified argument is the minimum required ps_account module version. You should modify it if you need another version.

Replace the <module_name> by your module name in order to avoid conflicts.

  # ...

  #####################
  # PS Account

  <module_name>.ps_accounts_installer:
    class: 'PrestaShop\PsAccountsInstaller\Installer\Installer'
    public: true
    arguments:
      - "5.0"

  <module_name>.ps_accounts_facade:
    class: 'PrestaShop\PsAccountsInstaller\Installer\Facade\PsAccounts'
    public: true
    arguments:
      - "@<module_name>.ps_accounts_installer"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Inject PsAccount library and context

PsAccount needs to get some information to work. Theses information are provided by injecting a context.

In order to inject the context you should update getContent hook. This will inject contextPsAccounts within the browser window object (window.contextPsAccounts).

Account service is also responsible to return the proper URL for the PsAccount front component, which is loaded via CDN.

PsAccount component doc

For a custom VueJS implementation, check PsAccount vue component documentation (opens new window)

// Account
$accountsFacade = $this->getService('<module_name>.ps_accounts_facade');
$accountsService = $accountsFacade->getPsAccountsService();
Media::addJsDef([
    'contextPsAccounts' => $accountsFacade->getPsAccountsPresenter()
        ->present($this->name),
]);

// Retrieve Account CDN
$this->context->smarty->assign('urlAccountsCdn', $accountsService->getAccountsCdn());
1
2
3
4
5
6
7
8
9
10

# PS Account Usage

You can know whether the shop has been successfully associated or not using isAccountLinked function from the PsAccountsService service.

// Account
$accountsService = $this->getService('<module_name>.ps_accounts_facade')->getPsAccountsService();

$accountsService->isAccountLinked();
1
2
3
4

# PsBilling

You should register as a service the composer Billing lib in your SaaS App container.

We use <module_name>.module and <module_name>.context to allow Billing lib to use your Module Instance and also PretaShop Context.

Sandbox mode

During your development you should use the sandbox mode which allow you to use test card. You can use 4111 1111 1111 1111 as test card, or see the official Chargebee documentation (opens new window).

In order to activate the sandbox mode you shoudl specify a third arguments to <module_name>.ps_billings_accounts_wrapper. By default sandbox mode is turned off.

services:
  #...

  #####################
  # PS Billing
  <module_name>.ps_billings_context_wrapper:
    class: 'PrestaShopCorp\Billing\Wrappers\BillingContextWrapper'
    arguments:
      - '@<module_name>.ps_accounts_facade'
      - '@<module_name>.context'
      - true # if true you are in sandbox mode, if false or empty not in sandbox

  <module_name>.ps_billings_facade:
    class: 'PrestaShopCorp\Billing\Presenter\BillingPresenter'
    arguments:
      - '@<module_name>.ps_billings_context_wrapper'
      - '@<module_name>.module'

  # Remove this if you don't need BillingService
  <module_name>.ps_billings_service:
    class: PrestaShopCorp\Billing\Services\BillingService
    public: true
    arguments:
      - '@<module_name>.ps_billings_context_wrapper'
      - '@<module_name>.module'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Inject PsBilling context

It is necessary to inject the psBillingContext into the global variable window.psBillingContext in order to initialize PsBilling related components

This presenter will serve some context informations, you need to send some parameters:

Attribute Type Description
logo string Set your logo can be a file or an Url
tosLink string Link to your terms & services (required)
privacyLink string Link to your terms & services (required)
emailSupport string Email to your support (required)

Sandbox mode

During your development you should use the sandbox mode which allow you to use test card. You can use 4111 1111 1111 1111 as test card, or see the official Chargebee documentation (opens new window)

In PHP, you need to pass it as Array

// Load context for PsBilling
$billingFacade = $this->getService('<module_name>.ps_billings_facade');
$partnerLogo = $this->getLocalPath() . ' views/img/partnerLogo.png';

// Billing
Media::addJsDef($billingFacade->present([
    'logo' => $partnerLogo,
    'tosLink' => 'https://yoururl/',
    'privacyLink' => 'https://yoururl/',
    'emailSupport' => 'you@email',
]));
1
2
3
4
5
6
7
8
9
10
11
# PsBillingService to retrieve billing data in PHP

As seen in the PsBilling configuration, the PsBilling composer provide you a PsBillingService :

  <module_name>.ps_billings_service:
    class: PrestaShopCorp\Billing\Services\BillingService
    public: true
    arguments:
      - '@<module_name>.ps_billings_context_wrapper'
      - '@<module_name>.module'
1
2
3
4
5
6

You can retrieve it the same way you retrieve the facade :

// Load service for PsBilling
$billingService = $this->getService('<module_name>.ps_billings_service');

// Retrieve the customer
$customer = $billingService->getCurrentCustomer();

// Retrieve the subscritpion for this module
$subscription = $billingService->getCurrentSubscription();

// Retrieve the list and description for module plans
$plans = $billingService->getModulePlans();
1
2
3
4
5
6
7
8
9
10
11

Each method return a PHP array with this format :

[
    'success' => true,    // return true if status is 2xx
    'httpStatus' => 200,  // HTTP status normalized
    'body' => [],         // The data to retrieve, the format is close to the format used in webhook system
];
1
2
3
4
5

# Load the front JS app

You should load the bundle of the front JS app in the getContent hook of your module PHP file. See Compile your app to get the correct path.

// Update the path to have the proper path
$this->context->smarty->assign('pathVendor', $this->getPathUri() . 'views/js/chunk-vendors-<module_name>.' . $this->version . '.js');
$this->context->smarty->assign('pathApp', $this->getPathUri() . 'views/js/app-<module_name>.' . $this->version . '.js');
1
2
3
<?php

if (!defined('_PS_VERSION_')) {
    exit;
}

require 'vendor/autoload.php';

class <module_name> extends Module
{
    private $emailSupport;

    /**
     * @var ServiceContainer
     */
    private $container;

    public function __construct()
    {
        $this->name = '<module_name>';
        $this->tab = 'advertising_marketing';
        $this->version = '1.0.0';
        $this->author = 'Prestashop';
        $this->emailSupport = '[email protected]';
        $this->need_instance = 0;

        $this->ps_versions_compliancy = [
            'min' => '1.6.1.0',
            'max' => _PS_VERSION_,
        ];
        $this->bootstrap = true;

        parent::__construct();

        $this->displayName = $this->l('Module name');
        $this->description = $this->l('This is a Module description.');

        $this->confirmUninstall = $this->l('Are you sure to uninstall this module?');

        $this->uri_path = Tools::substr($this->context->link->getBaseLink(null, null, true), 0, -1);
        $this->images_dir = $this->uri_path . $this->getPathUri() . 'views/img/';
        $this->template_dir = $this->getLocalPath() . 'views/templates/admin/';

        if ($this->container === null) {
            $this->container = new \PrestaShop\ModuleLibServiceContainer\DependencyInjection\ServiceContainer(
                $this->name,
                $this->getLocalPath()
            );
        }
    }

    /**
     * Retrieve service
     *
     * @param string $serviceName
     *
     * @return mixed
     */
    public function getService($serviceName)
    {
        return $this->container->getService($serviceName);
    }

    public function install()
    {
        return parent::install() &&
            $this->getService('<module_name>.ps_accounts_installer')->install();
    }

    public function uninstall()
    {
        if (!parent::uninstall()) {
            return false;
        }

        return true;
    }

    /**
     * Get the Tos URL from the context language, if null, send default link value
     *
     * @return string
     */
    public function getTosLink($iso_lang)
    {
        switch ($iso_lang) {
            case 'fr':
                $url = 'https://www.prestashop.com/fr/prestashop-account-cgu';
                break;
            default:
                $url = 'https://www.prestashop.com/en/prestashop-account-terms-conditions';
                break;
        }

        return $url;
    }

    /**
     * Get the Tos URL from the context language, if null, send default link value
     *
     * @return string
     */
    public function getPrivacyLink($iso_lang)
    {
        switch ($iso_lang) {
            case 'fr':
                $url = 'https://www.prestashop.com/fr/politique-confidentialite';
                break;
            default:
                $url = 'https://www.prestashop.com/en/privacy-policy';
                break;
        }

        return $url;
    }

    public function getContent()
    {
        // Allow to auto-install Account
        $accountsInstaller = $this->getService('<module_name>.ps_accounts_installer');
        $accountsInstaller->install();

        try {
            // Account
            $accountsFacade = $this->getService('<module_name>.ps_accounts_facade');
            $accountsService = $accountsFacade->getPsAccountsService();
            Media::addJsDef([
                'contextPsAccounts' => $accountsFacade->getPsAccountsPresenter()
                    ->present($this->name),
            ]);

            // Retrieve Account CDN
            $this->context->smarty->assign('urlAccountsCdn', $accountsService->getAccountsCdn());

            $billingFacade = $this->getService('<module_name>.ps_billings_facade');
            $partnerLogo = $this->getLocalPath() . 'views/img/partnerLogo.png';

            // Billing
            Media::addJsDef($billingFacade->present([
                'logo' => $partnerLogo,
                'tosLink' => $this->getTosLink($this->context->language->iso_code),
                'privacyLink' => $this->getPrivacyLink($this->context->language->iso_code),
                'emailSupport' => $this->emailSupport,
            ]));

            $this->context->smarty->assign('pathVendor', $this->getPathUri() . 'views/js/chunk-vendors-rbm_example.' . $this->version . '.js');
            $this->context->smarty->assign('pathApp', $this->getPathUri() . 'views/js/app-rbm_example.' . $this->version . '.js');
        } catch (Exception $e) {
            $this->context->controller->errors[] = $e->getMessage();

            return '';
        }

        return $this->context->smarty->fetch($this->template_dir . 'rbm_example.tpl');
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156

# Module template

Create the global vue app template in views/templates/admin/<module_name>.tpl. The name should match the name defined in <module_name>.php by this line:

return $this->context->smarty->fetch($this->template_dir . '<module_name>.tpl');
1

This file will load the Vue app frontend and the chunk vendor js

The 3 variables $urlAccountsCdn, $pathVendor and $pathApp are prepared in the getContent hook.

<link href="{$pathVendor|escape:'htmlall':'UTF-8'}" rel=preload as=script>
<link href="{$pathApp|escape:'htmlall':'UTF-8'}" rel=preload as=script>
<link href="{$urlAccountsCdn|escape:'htmlall':'UTF-8'}" rel=preload as=script>

<div id="app"></div>

<script src="{$pathVendor|escape:'htmlall':'UTF-8'}"></script>
<script src="{$pathApp|escape:'htmlall':'UTF-8'}"></script>
<script src="{$urlAccountsCdn|escape:'htmlall':'UTF-8'}" type="text/javascript"></script>

1
2
3
4
5
6
7
8
9
10

# Frontend

About VueJS

Javascript and Vue knowledge are prerequisite (cf https://vuejs.org/v2/guide/ (opens new window)). This section only introduces the essentials, for more information, please take a look at the example SaaS App module (opens new window) or to the Using VueJS PrestaShop documentation (opens new window).

# Getting started

Create a _dev folder in your module. This folder will contain the different VueJS app contained in your module. You can have only one app.

Go to this folder then create a VueJS project (opens new window).

TIP

Feel free to organize your application in your own way

# Create the Vue app
cd _dev
vue create <app's name>
1
2
3

# Compile your app

You need to update or create the vue.config.js to compile properly your VueJS app. The outputDir should change, depending on your module structure. Don't forget to change the <module_name> in the output filename path.

This is only an example of vue.config.js, you may modify this configuration.

Chunk path

These file's names must match with the ones ($pathVendor, $pathApp ) used in the getContent hook and the version of this module php (cf composer.json) and the vue app (cf package.json) must be the same

const webpack = require("webpack");
const path = require("path");
const fs = require('fs');
const packageJson = fs.readFileSync('./package.json')
const version = JSON.parse(packageJson).version || 0
module.exports = {
    parallel: false,
    configureWebpack: {
        plugins: [
            new webpack.ProvidePlugin({
                cash: "cash-dom",
            }),
        ],
        output: {
            filename: `js/app-rbm_example.${version}.js`,
            chunkFilename: `js/chunk-vendors-rbm_example.${version}.js`
        }
    },
    chainWebpack: (config) => {
        config.plugins.delete("html");
        config.plugins.delete("preload");
        config.plugins.delete("prefetch");
        config.resolve.alias.set("@", path.resolve(__dirname, "src"));
    },
    css: {
        extract: false,
    },
    runtimeCompiler: true,
    productionSourceMap: false,
    filenameHashing: false,
    outputDir: "../../views/", // Outputs in module views folder
    assetsDir: "",
    publicPath: "../modules/<module_name>/views/",
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# Add required dependencies

yarn add @prestashopcorp/billing-cdc
1

Optional see PsAccount component fallback

yarn add prestashop_accounts_vue_components
1

# Use PsAccount

The PsAccount front component is loaded by the CDN in the smarty template.

Use the CDN

CDN is the proper way to implement a SaaS App, you should use only the npm dependency as a fallback in case the CDN doesn't work properly

<prestashop-accounts>
    // your module template goes here
</prestashop-accounts>
1
2
3
<script>
    window?.psaccountsVue?.init() || require('prestashop_accounts_vue_components').init();
</script>
1
2
3

# Use PsBilling

Import 2 components CustomerComponent, ModalContainerComponent into the vue component

import Vue from 'vue';
import { CustomerComponent, ModalContainerComponent } from "@prestashopcorp/billing-cdc/dist/bundle.umd";

//...

export default {
  components: {
    // ...
    PsBillingCustomer: CustomerComponent.driver('vue', Vue),
    PsBillingModal: ModalContainerComponent.driver('vue', Vue),
    // ...
  },
  // ...
1
2
3
4
5
6
7
8
9
10
11
12
13

Use PsBillingCustomer, PsBillingModal in the template

In the PsBillingCustomer, by default, the invoice list will be displayed. To hide it, pass :hideInvoiceList="true"

<template>
  <div>
    <prestashop-accounts>
      ...
    </prestashop-accounts>
    <ps-billing-customer
      v-if="billingContext.user.email"
      ref="psBillingCustomerRef"
      :context="billingContext"
      :onOpenModal="openBillingModal"
      :hideInvoiceList="true"
    />
    <ps-billing-modal
      v-if="modalType !== ''"
      :context="billingContext"
      :type="modalType"
      :onCloseModal="closeBillingModal"
    />
    <div v-if="sub && sub.id">Rbm example content</div>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

The context should be retrieved from window.psBillingContext.context and injected inside the template.

data() {
    return {
        billingContext: { ...window.psBillingContext.context },
        modalType: '',
        sub: null
    }
},
1
2
3
4
5
6
7

To display the payment funnel, and other modals, the billing components required modal to be displayed. Create 2 methods openBillingModal and closeBillingModal to handle the modal's interaction

methods: {
    openBillingModal(type, data) {
        this.modalType = type;
        this.billingContext = { ...this.billingContext, ...data };
    },
    closeBillingModal(data) {
        this.modalType = '';
        this.$refs.psBillingCustomerRef.parent.updateProps({
            context: {
                ...this.billingContext,
                ...data
            }
        });
    },
    eventHookHandler(type, data) {
        switch(type) {
            case EVENT_HOOK_TYPE.BILLING_INITIALIZED:
                // data structure is: { customer, subscription }
                console.log('Billing initialized', data);
                this.sub = data.subscription;
                break;
            case EVENT_HOOK_TYPE.SUBSCRIPTION_UPDATED:
                // data structure is: { customer, subscription, card }
                console.log('Sub updated', data);
                this.sub = data.subscription;
                break;
            case EVENT_HOOK_TYPE.SUBSCRIPTION_CANCELLED:
                // data structure is: { customer, subscription }
                console.log('Sub cancelled', data);
                this.sub = data.subscription;
                break;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
  <div>
    <prestashop-accounts>
    </prestashop-accounts>
    <ps-billing-customer
        v-if="billingContext.user.email"
        ref="psBillingCustomerRef"
        :context="billingContext"
        :onOpenModal="openBillingModal"
    />
    <ps-billing-modal
      v-if="modalType !== ''"
      :context="billingContext"
      :type="modalType"
      :onCloseModal="closeBillingModal"
    />
    <div v-if="sub && sub.id">Rbm example content</div>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
  import Vue from "vue";

  import {
    CustomerComponent,
    ModalContainerComponent,
  } from "@prestashopcorp/billing-cdc/dist/bundle.umd";

  export default {
    components: {
      PsBillingCustomer: CustomerComponent.driver("vue", Vue),
      PsBillingModal: ModalContainerComponent.driver("vue", Vue),
    },
    data() {
      return {
        billingContext: { ...window.psBillingContext.context },
        modalType: "",
        sub: null,
      };
    },
    provide() {
      return {
        emailSupport: window.psBillingContext.context.user.emailSupport,
      };
    },
    methods: {
      openBillingModal(type, data) {
        this.modalType = type;
        this.billingContext = { ...this.billingContext, ...data };
      },
      closeBillingModal(data) {
        this.modalType = "";
        this.$refs.psBillingCustomerRef.parent.updateProps({
          context: {
            ...this.billingContext,
            ...data,
          },
        });
      },
      eventHookHandler(type, data) {
        switch (type) {
          case EVENT_HOOK_TYPE.BILLING_INITIALIZED:
            // data structure is: { customer, subscription }
            console.log("Billing initialized", data);
            this.sub = data.subscription;
            break;
          case EVENT_HOOK_TYPE.SUBSCRIPTION_UPDATED:
            // data structure is: { customer, subscription, card }
            console.log("Sub updated", data);
            this.sub = data.subscription;
            break;
          case EVENT_HOOK_TYPE.SUBSCRIPTION_CANCELLED:
            // data structure is: { customer, subscription }
            console.log("Sub cancelled", data);
            this.sub = data.subscription;
            break;
        }
      },
    },
    mounted() {
      window?.psaccountsVue?.init() || require('prestashop_accounts_vue_components').init();
    }
  };
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

# Use PsInvoiceList

Import the component InvoiceListComponent into the vue component

import Vue from 'vue';
import { InvoiceListComponent } from "@prestashopcorp/billing-cdc/dist/bundle.umd";

//...

export default {
  components: {
    // ...
    PsInvoiceList: InvoiceListComponent.driver('vue', Vue),
    // ...
  },
  // ...
1
2
3
4
5
6
7
8
9
10
11
12

Use PsInvoiceList in the template

The filterType accepts one of these values: subscription or customer by which it is used to filter the invoices

<template>
  <div>
    <ps-invoice-list :context="billingContext" :filterType="<filterType>" />
  </div>
</template>
1
2
3
4
5

The context should be retrieved from window.psBillingContext.context and injected inside the template.

data() {
  return {
      billingContext: { ...window.psBillingContext.context },
  }
},
1
2
3
4
5

PsInvoiceList also accepts an additional optional options prop to further customize the display of the invoice list.

<template>
  <div>
    <ps-invoice-list 
      :context="billingContext" 
      :options="options" 
      :filterType="<filterType>"
    />
  </div>
</template>
1
2
3
4
5
6
7
8
9
export default {
  data() {
    return {
      options: { 
        pageSize: 6 
      }
    }
  },
}
1
2
3
4
5
6
7
8
9

The options prop in detail:

Attribute Type Description Default value
pageSize number Set the number of invoices per page 6

# Context in detail

Below is the details of the attributes

Attribute Type Description
moduleName string Module's name (required)
displayName string Module's display name (required)
moduleLogo string Module's logo (required)
partnerLogo string Your logo image
moduleTosUrl string Url to your term of service (required)
accountApi string API to retrieve PrestaShop Account (required)
emailSupport string Email to contact support (required)
i18n.isoCode string ISO code (required)
shop.uuid string Merchant shop's uuid (required)
shop.domain string Merchant site's domain name (required)
user.createdFromIp string Merchant site's ip address (required)
user.email string Merchant's email (required)
versionModule string Module's version (required)
versionPs string Prestashop's version (required)
isSandbox boolean Activate the sandbox mode (default: false)
refreshToken string Refresh token provided by PsAccount (required)

There are 5 types of modal:

1. AddressModal

openBillingModal(data)

Data format: {first_name, last_name, city, ...}

closeBillingModal(data)

Data format: {address: {first_name, last_name, city, ...}} if the billing address info are saved successfully

2. PaymentMethodModal

openBillingModal(data)

Data format: undefined

closeBillingModal(data)

Data format: {payment_method_update: true} if the payment info are saved successfully

3. CancelPlanModal

openBillingModal(data)

Data format: {currentSubscription: {id, module, name, ...}}

closeBillingModal(data)

Data format: {card, credit_notes, subscription, customer} if the subscription is cancelled successfully

4. DowngradePlanModal

openBillingModal(data)

Data format: {planToDowngrade, currentSubscription}

closeBillingModal(data)

Data format: {state, card, credit_notes, subscription, customer} if the subscription is downgraded successfully

5. FunnelModal

openBillingModal(data)

Data format: {selectedPlan: {id, name, price, ...}}

closeBillingModal(data)

Data format: {state, card, credit_notes, subscription, customer, invoice} if the subscription flow has proceeded successfully

# Event hook

The event hook system allows you to be notified in the front app when a subscription changes. There are 3 types of event:

  • billing:billing_initialized: Triggered after the PsBillingCustomer component has been rendered
  • billing:subscription_updated: Triggered when a subscription is updated or created
  • billing:subscription_cancelled: Triggered when a subscription is cancelled

Warning

These event hook are triggered on the Billing API HTTP call return. It may have some delay for your system to be notified of the subscription update by the webhook system. We advice you to do some long polling (opens new window) until you the subscription update is propagated to your system.

Here is the recipe to listen to this event hook:






 









<ps-billing-customer
  v-if="billingContext.user.email"
  ref="psBillingCustomerRef"
  :context="billingContext"
  :onOpenModal="openBillingModal"
  :onEventHook="eventHookHandler"
/>
<ps-billing-modal
  v-if="modalType !== ''"
  :context="billingContext"
  :type="modalType"
  :onCloseModal="closeBillingModal"
  :onEventHook="eventHookHandler"
/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Import
import { EVENT_HOOK_TYPE } from '@prestashopcorp/billing-cdc/dist/bundle.umd';

// Add a eventHookHandler method
    methods: {
        // ...
        eventHookHandler(type, data) {
            switch(type) {
                case EVENT_HOOK_TYPE.BILLING_INITIALIZED:
                    // data structure is: { customer, subscription }
                    console.log('Billing initialized', data);
                    break;
                case EVENT_HOOK_TYPE.SUBSCRIPTION_UPDATED:
                    // data structure is: { customer, subscription, card }
                    console.log('Sub updated', data);
                    break;
                case EVENT_HOOK_TYPE.SUBSCRIPTION_CANCELLED:
                    // data structure is: { customer, subscription }
                    console.log('Sub cancelled', data);
                    break;
                }
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
  <div>
    <PsAccounts>
    </PsAccounts>
    <ps-billing-customer
        v-if="billingContext.user.email"
        ref="psBillingCustomerRef"
        :context="billingContext"
        :onOpenModal="openBillingModal"
        :onEventHook="eventHookHandler"
    />
    <ps-billing-modal
      v-if="modalType !== ''"
      :context="billingContext"
      :type="modalType"
      :onCloseModal="closeBillingModal"
      :onEventHook="eventHookHandler"
    />
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
  import Vue from 'vue';
  import { CustomerComponent, ModalContainerComponent, EVENT_HOOK_TYPE } from "@prestashopcorp/billing-cdc/dist/bundle.umd";

  export default {
    components: {
      PsAccounts: async () => {
        // CDN will normally inject a psaccountsVue within the window object
        let psAccounts = window?.psaccountsVue?.PsAccounts;

        // This is a fallback if CDN isn't available
        if (!psAccounts) {
            psAccounts = require("prestashop_accounts_vue_components").PsAccounts;
        }
        return psAccounts;
      },
      PsBillingCustomer: CustomerComponent.driver('vue', Vue),
      PsBillingModal: ModalContainerComponent.driver('vue', Vue),
    },
    data() {
      return {
        billingContext: { ...window.psBillingContext.context },
        modalType: '',
      }
    },
    methods: {
      openBillingModal(type, data) {
        this.modalType = type;
        this.billingContext = { ...this.billingContext, ...data };
      },
      closeBillingModal(data) {
        this.modalType = '';
        this.$refs.psBillingCustomerRef.parent.updateProps({
            context: {
                ...this.context,
                ...data
            }
        });
      },
      eventHookHandler(type, data) {
        switch(type) {
            case EVENT_HOOK_TYPE.BILLING_INITIALIZED:
                // Do what you want to do when ps-billing-customer is initialized
                break;
            case EVENT_HOOK_TYPE.SUBSCRIPTION_UPDATED:
                // Do what you want to do on sub update
                break;
            case EVENT_HOOK_TYPE.SUBSCRIPTION_CANCELLED:
                // Do what you want to do on sub cancel
                break;
            }
        }
      }
    }
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# Pass data through PsBilling

The PsBillingCustomer component allows you to emit an event to initialize the billing customer in a specific state. The most common use case for this emitter is when you have a free plan and want your customer to subscribe immediately to this free plan.

To achieve this, first of all, you need to create a ref to the PsBillingCustomer component, which allow you to use some method from this component.


 




<ps-billing-customer
  ref="psBillingCustomerRef"
  :context="billingContext"
  :onOpenModal="openBillingModal"
/>
1
2
3
4
5

Then in the created hook, you call the emit function from CustomerComponent.




 









export default {
  // ...
  created() {
    this.$refs.psBillingCustomerRef.emit("init:billing", {
      CREATE_SUBSCRIPTION: {
        // This is the id of your free plan
        planId: "default-free",
      },
    });
  },
  // ...
};
1
2
3
4
5
6
7
8
9
10
11
12