# 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 :
- Create a PrestaShop Module (opens new window)
- Make the PHP part injecting the proper
context
in thewindow
object, which is required for PsAccount and PsBilling. - Create a JS app including PsAccount and PsBilling
- 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"
},
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' ]
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"
}
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);
}
}
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"
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());
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();
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'
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',
]));
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'
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();
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
];
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');
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');
}
}
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');
This file will load the Vue app frontend and the chunk vendor js
The 3 variables
$urlAccountsCdn
,$pathVendor
and$pathApp
are prepared in thegetContent
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>
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>
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/",
};
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
Optional see PsAccount component fallback
yarn add prestashop_accounts_vue_components
# 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>
2
3
<script>
window?.psaccountsVue?.init() || require('prestashop_accounts_vue_components').init();
</script>
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),
// ...
},
// ...
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>
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
}
},
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;
}
}
}
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>
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>
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),
// ...
},
// ...
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>
2
3
4
5
The context should be retrieved from window.psBillingContext.context
and injected inside the template.
data() {
return {
billingContext: { ...window.psBillingContext.context },
}
},
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>
2
3
4
5
6
7
8
9
export default {
data() {
return {
options: {
pageSize: 6
}
}
},
}
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) |
# Modal detail
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 renderedbilling:subscription_updated
: Triggered when a subscription is updated or createdbilling: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"
/>
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;
}
}
}
}
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>
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>
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"
/>
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",
},
});
},
// ...
};
2
3
4
5
6
7
8
9
10
11
12
← FAQ