Cette année encore, la communauté Open Source PrestaShop se réunira pour son événement annuel du FOP Day, le 12 juin prochain, à Lyon.

En tant que partenaire de longue date, Lyra sera présente pour cette nouvelle édition et vous apportera ses conseils et son expertise sur le paiement.

Une journée riche en échanges

Comme lors des précédentes éditions, cette journée dédiée aux professionnels de la communauté Prestashop alternera les temps de networking et 6 conférences, animées par les experts PrestaShop.

Au programme de ces conférences :

  • IA, SEO et e-commerce : révolution ou bullshit ?
  • Cyberattaques PrestaShop : 1 an de harcèlement : retour d’expériences, analyse des risques et solutions pour se protéger ou se préparer au pire.
  • ERP/CRM/PIM : réussir l’intégration de PrestaShop à son SI
  • Tracking analytics et protections des données : les équilibristes du web.
  • Les nouveautés de PrestaShop 9 par la team PrestaShop
  • Des top e-commerçants parlent aux techs : compréhension et incompréhension.

Informations pratiques

  • FOP Day
  • lundi 12 juin 2023, de 9h à 18h30
  •  Espace de l’ouest Lyonnais – 6 Rue Nicolas Sicard – 69005 Lyon

A propos de Friends of Presta

Friends of Presta est la communauté qui rassemble des développeurs et intégrateurs autour de la solution e-commerce open source PrestaShop.

Avec sa soixantaine d’adhérents experts en e-commerce, Friends of Presta est le 1er réseau d’experts tech du CMS PrestaShop.

Pour conclure cette série d’articles, reprenons les objectifs et les différentes solutions apportées.

L’objectif principal est le développement d’une librairie de composants React Native, dans le but de mutualiser des composants de deux applications : PayZen et Lyra Collect.

La difficulté principale étant que certains composants peuvent être de haut niveau, quasiment des écrans entiers. C’est-à-dire qu’ils embarquent de la logique métier : des appels serveurs, de la navigation entre plusieurs écrans, des paramètres utilisateurs etc.

Reprenons les principales problématiques ainsi que les solutions apportées :

Gestion des dépendances dans le contexte React Native / Expo

  • L’utilisation des peerDependencies permet de mutualiser les dépendances
  • On a pu démontrer une configuration qui fonctionne avec le bundler Metro (au niveau du code source et des assets)
  • La librairie est écrite en TypeScript et tout le typage est bien exporté
  • La compilation de la librairie est simple et rapide

L’environnement de développement, l’intégration continue et le déploiement

  • Bonne expérience de développement, tout l’outillage fonctionne comme avant (refresh de composant à chaud, debugger, inspecteur d’éléments etc.)
  • Le développeur continue de tester son application, qu’il travail sur la librairie ou sur son application
  • Linking de la librairie en local très facile
  • Intégration continue et déploiement avec Bitrise

Gestion des personnalisations : le thème, l’internationalisation et les paramètres utilisateur

  • Passage de paramètres et création d’un contexte grâce à un composant Provider
  • Possibilité de personnaliser le thème, les traductions et la configuration de la librairie
  • Les paramètres des composants de la lib peuvent être intégré dans l’écran des préférences de l’application

Gestion des appels serveur et de l’authentification

  • Utilisation d’un client HTTP (Axios) qui permet d’embarquer la configuration des appels serveurs (URL de base, authentification etc.)
  • La librairie est capable d’effectuer des appels serveurs avec des urls relatives
  • La gestion des tokens d’authentification et de l’URL du back-end restent de la responsabilité des applications, la librairie ne s’en occupe pas
  • Le déploiement de la brique « API Proxy » côté Lyra Collect permet d’accéder aux API de PayZen depuis l’app Lyra Collect
  • Les appels REST sont validés par les specs OpenAPI fournies par le back-end

Les grands principes à retenir

  • Librairie écrite en TypeScript
  • Elle expose un composant Provider qui lui permet d’accepter des paramètres et d’initialiser les librairies utilitaires
    • React Native Paper pour le thème
    • I18n-js pour l’internationalisation
    • Axios pour les appels HTTP

L’objectif principal de cette librairie est d’apporter les fonctionnalités liées aux ordres de paiement de l’application PayZen dans l’application Lyra Collect. Mettre ces écrans dans une librairie nous permet de mutualiser le code et facilite la maintenance.

Les ordres (aussi appelés liens) de paiement, sont des liens générés par le marchand qu’ils peuvent envoyer à leur client pour se faire payer. Ces liens amènent vers la page de paiement PayZen du marchant avec toutes les informations de paiement pré-remplies.

Voici quelques exemples d’écrans pour les ordres de paiement (liste, détail, création) :

Dans les articles précédents, nous avons répondu aux problématiques liées aux dépendances, à l’environnement de développement et aux personnalisations.

Il nous reste un sujet à aborder, qui est spécifique aux écrans des ordres de paiement, ce sont les appels serveurs et l’authentification.

En effet, la librairie expose des composants de haut niveau qui peuvent être des écrans complets :

  • PaymentOrderList : écran complet de la liste des ordres de paiement, avec ses propres appels serveur
  • PaymentOrderDetail : écran complet du détail d’un ordre de paiement, avec ses propres appels serveur
  • PaymentOrderCreation : enchainement d’écrans pour la création d’un ordre de paiement, avec ses propres appels serveur
  • PaymentOrderSettings : section de paramètres des ordres de paiement

L’architecture de nos services

Nos deux applications communiquent avec leur back-end respectif (donc URL différentes) qui ont chacun leur méthode d’authentification spécifique.

  • Côté PayZen, c’est une authentification à base de cookie et de tokens.
  • Côté Lyra Collect, l’authentification s’appuie sur RH-SSO (Keycloak), à base de Bearer Token.

Ce qu’il faut savoir, c’est que la plateforme Lyra Collect se base sur la solution PayZen. Et les ordres de paiement sont une notion portée par PayZen.

Donc pour l’app PayZen, aucun problème avec les ordres de paiement : elle communique avec son back-end, et l’authentification est naturellement gérée.

Par contre pour l’application Lyra Collect, il faut communiquer directement avec le back-end PayZen (sauf à devoir dupliquer l’API des Ordres de Paiement sur Lyra Collect, ce que nous voulons justement éviter).Et bien entendu, il n’est pas souhaitable de gérer plusieurs authentifications sur l’application Lyra Collect (une pour les fonctionnalités des Ordres de Paiement, l’autre pour le reste de l’application).

En revanche, les systèmes d’information Lyra Collect et PayZen communiquent très bien entre eux car ils sont hébergés au sein de la plateforme Lyra.

L’idée est donc d’ajouter une brique de proxy d’API devant le back-end Lyra Collect qui aura les rôles suivant :

  1. authentifier l’utilisateur Lyra Collect avec RH-SSO
  2. transférer l’appel vers PayZen en lui adjoignant les paramètres d’authentification (PayZen) correspondant

Voici un schéma simplifié qui illustre l’architecture cible. En vert l’existant, en orange les nouveaux composants :

La gestion des appels serveurs dans la librairie

Une fois ces modules en place, la problématique suivante est : comment la librairie va pouvoir effectuer ses requêtes alors qu’elle ne connait ni l’URL du serveur ni la méthode d’authentification à configurer ?

En effet, elle est utilisée à la fois dans l’application Lyra Collect (qui communique avec le backend Lyra-Collect) et PayZen (qui communique… oui, avec le backend PayZen).

Et ce que nous voulons éviter, c’est qu’à chaque appel, la librairie fasse quelque chose comme :

Première hypothèse

Au début, nous avons imaginé que les applications passent des paramètres au niveau du Provider de la librairie :

  • baseURL : l’URL de base à utiliser pour tous les appels
  • mode : EP / VAD selon le mode d’authentification à utiliser
  • token : le token d’authentification

Ce qui donnerait :

// depuis l’app PayZen // depuis l’app Lyra Collect

La librairie dispose ainsi que tout le nécessaire pour effectuer les appels. Selon le mode d’authentification, le token sera utilisé différemment.

Après un essai, cela fonctionne mais plusieurs points ne nous conviennent pas :

  • le mode revient à utiliser un if/else que nous voulions éviter. De plus, cela introduit un couplage fort entre la librairie et les applications qui l’utilisent (la librairie doit forcément connaître ces dernières…)
  • le nouveau token PayZen est passé dans le retour de l’appel, il faut donc mettre en place un mécanisme pour retourner le nouveau token à l’appli
  • l’authentification côté PayZen utilise des cookies, et pour que ça fonctionne, il vaut mieux utiliser la même API de requête → nouveau couplage app / lib

(erreur)  Cette hypothèse n’est pas retenue


Deuxième hypothèse

Une solution alternative serait d’utiliser un client HTTP autre que l’API native ‘fetch’. Ainsi avec la librairie Axios, il est possible de :

  • créer une instance de client HTTP avec des paramètres, par exemple une URL de base
  • utiliser des middlewares pre- et post-request, par exemple pour configurer l’authentification

Avec Axios, l’application peut ainsi créer une instance de client HTTP et la configurer avec toutes ses spécificités (URL de base et authentification notamment) et la transmettre à la librairie.

Création de l’instance Axios :

import Axios from 'axios'
 
// création de l'instance Axios
export const axios = Axios.create({
  baseURL: 'https://api.example.com',
})

Configuration d’une authentification Bearer

import { useContext, useLayoutEffect, useRef } from 'react'
import { SessionContext } from '../contexts/SessionContext'
import { axios } from './axios'
 
export function useAuthInterceptor() {
  // on récupère le token depuis un contexte
  const { accessToken } = useContext(SessionContext)
  // stockage de l'identifiant de l'intercepteur dans une référence React
  const reqInterceptor = useRef<number>()
 
  useLayoutEffect(() => {
    // si l'interceptor existe déjà, on le libère
    if (reqInterceptor.current !== undefined) {
      axios.interceptors.request.eject(reqInterceptor.current)
    }
 
    // déclaration d'un nouvel intercepteur de requête, appelé avant chaque requête
    reqInterceptor.current = axios.interceptors.request.use((config) => {
      if (accessToken && config.headers) {
        // configuration du header Authorization de la requête
        config.headers.Authorization = `Bearer ${accessToken}`
      }
 
      // on retourne la config modifiée
      return config
    })
 
    return () => {
      // au nettoyage, on libère l'intercepteur
      if (reqInterceptor.current !== undefined) {
        axios.interceptors.request.eject(reqInterceptor.current)
      }
    }
  }, [accessToken])
}

Déclaration du Provider dans l’app

import { Provider } from '@lyra/shared-components'
import { axios, useAuthInterceptor } from './rest'
import { Root } from './components/Root'
 
export const App = () => {
  // initialisation de notre intercepteur d'authentification
  useAuthInterceptor()
 
  return (
    <Provider axios={axios}>
      <Root />
    </Provider>
   )
}

Avec cette solution, la librairie ne s’occupe plus de gérer :

  • l’URL de base, elle fait seulement des appels relatifs
  • l’authentification, elle est configurée côté application

L’avantage c’est que quelque soit la méthode d’authentification, le fonctionnement de la librairie n’est pas impacté : pas de if / else, pas de mécanisme de synchro de token entre lib et app. Avec les interceptors, chaque application reste maître de sa gestion de token.

Exemple d’appel REST depuis la librairie

import { useContext } from 'react'
import { ConfigContext } from '../contexts/ConfigContext'
 
export const FooComponent = () => {
  const { axios } = useContext(ConfigContext)
  const result = axios.get('/path/to/resource')
  ...
}

En outre, cela nous permet de développer une nouvelle application mobile qui utiliserait la librairie, sans modifier cette dernière.

 ✅ Cette hypothèse répond à nos besoins, elle est donc retenue

Spécifications OpenAPI

Le back-end PayZen fournit des spécifications OpenAPI pour tous ses web services REST. Par conséquent, pour éviter toute erreur de communication avec le back-end, nous utilisons ces contrats pour nous assurer que nos appels sont corrects dès la librairie.

Pour cela, nous utilisons le projet OpenAPI Generator qui permet de générer un client REST en fonction d’un contrat OpenAPI.

Et par chance (ou pas (sourire)), il existe un générateur typescript-axios. Parfait pour notre cas d’utilisation !

Il suffit donc de passer notre instance Axios au client REST généré par typescript-axios, et ce dernier expose les différentes requêtes disponibles sur notre back-end.

Et bien entendu, le client généré contient le typage de tout le modèle.

Voici le fichier de configuration utilisé pour le générateur typescript-axios :

openapi-generator.config.json


{
  "withSeparateModelsAndApi": true,
  "apiPackage": "api",
  "modelPackage": "model",
  "useSingleRequestParameter": true,
  "enumPropertyNaming": "UPPERCASE",
  "stringEnums": true
}

La ligne de commande pour générer le client REST :

Commande pour générer le client REST

npx @openapitools/openapi-generator-cli generate -g typescript-axios -c openapi-generator.config.json -i payment-order.yaml -o src/openapi/payment-order

Dans le code, on peut maintenant utiliser le client généré (PaymentOrderApi dans notre cas) en lui passant l’instance Axios :

Exemple d’un composant utilisant le client REST généré

import { useContext } from 'react'
import { ConfigContext } from '../contexts/ConfigContext'
import { PaymentOrderApi, Configuration } from '../openapi/payment-order'
 
export const PaymentOrderList = () => {
  const { axios } = useContext(ConfigContext)
 
  // récupération du basePath depuis l'instance axios
  const basePath = axios.defaults.baseURL
 
  // création d'une configuration et d'un client d'API avec les objets générés
  const configuration = new Configuration({ basePath })
  const paymentOrderApi = new PaymentOrderApi(configuration, basePath, axios)
 
  // on peut maintenant utiliser notre API qui va effectuer les requêtes en utilisant l'instance axios passée en config
  const result = paymentOrderApi.findPaymentOrder({
    page: 1,
    perPage: 10,
    minAmount: 42,
  })
 
  ...
}

Conclusion

(coche)  La librairie est capable d’effectuer des appels serveurs avec des urls relatives.

(coche)  La gestion des tokens d’authentification et de l’URL du back-end reste de la responsabilité des applications, la librairie ne s’en occupe pas.

(coche)  Le déploiement de la brique « API Proxy » côté Lyra Collect permet d’accéder aux API de PayZen depuis l’app Lyra Collect.

(coche)  Les appels REST sont validés par les specs OpenAPI fournies par le back-end.

Les problématiques liées aux appels serveurs sont résolues. La librairie est donc capable d’embarquer des composants métiers de haut niveau (écrans complets).

Paiement par virement, les meilleures intégrations de la DSP2


La DSP2 a imposé aux banques d’ouvrir leur système informatique pour permettre notamment à des sociétés qui ont la licence PISP, de proposer le paiement par virement 100% en ligne. Alain Lacour, notre président Lyra, avait déjà mis en lumière dans cette tribune, les parcours imposés par certaines banques au détriment de toute logique et de l’expérience acheteur.

Cependant, certaines banques comme LCL et Boursorama ont compris l’importance d’offrir un parcours fluide à ses clients et d’apporter une attention toute particulière au mobile.

Good to know : 1 acheteur français sur 2 achète sur mobile !

*Source : FEVAD – https://www.fevad.com/les-chiffres-le-paiement-en-ligne/

LCL & BOURSORAMA réveillent leur Apps mobile


Lorsque l’acheteur, depuis son smartphone, sélectionne le paiement par virement, grâce à l’intégration de Lyra et l’avance technologique des apps Boursorama et LCL, le paiement est ultrarapide.
Le secret : le réveil de l’app !

Ce mode de fonctionnement fluidifie grandement le paiement par virement sur mobile car à l’étape de validation de l’ordre de virement, l’application bancaire s’ouvre automatiquement. Plus besoin pour l’acheteur de chercher son application et de l’ouvrir manuellement.
Un passage rassurant et sécurisant pour ce mode de paiement qui prend de plus en plus d’ampleur.


Les images valent toujours mieux que 300 mots alors regardez nos démo :
LCL :

Boursorama :

Le mot de la fin


Pour convertir au maximum, dans les meilleures conditions et avec une extrême rapidité, Lyra travaille étroitement avec tous les profils de banques.
Le parcours d’achat que vous offrez à vos acheteurs est alors optimisé pour vous assurer un travail efficace, à l’image des produits ou service que vous proposez !
Nous espérons que le paiement par virement et son réveil de l’app sera vite suivi par l’ensemble des banques françaises et européennes, ce qui contribuera à coup sûr à l’émergence de ce moyen de paiement capable de détrôner la carte bancaire.

GetResto est une application mobile basée sur un modèle de marketplace, qui met en relation les restaurants et les foodies. Cherchez, réservez et commandez dans vos restaurants favoris mais soyez également au fait de leurs actualités. Les clients pourront alors sauvegarder et partager leurs bonnes adresses. Offrant une expérience optimale et privilégiée, cette solution packagée n’a pas fini de séduire.

Les enjeux de GetResto se concentrent sur l’expérience utilisateur mobile et la proposition de moyens de paiement incontournable, Daniel Abitbol, créateur du concept est revenu avec nous sur ce qui devient essentiel dans ce secteur !

Le mobile, l’essence des foodies

Au cours de l’année 2020, les commandes en ligne, en livraison ou en click & collect, ont connu un véritable engouement avec une croissance de 25 % aussi bien en dépenses qu’en visites. Un français sur deux s’est déjà fait livrer ! Et parce que la majorité des commandes s‘effectue via smartphone, pour plus de rapidité et d’instantanéité, le développement d’une stratégie mobile est capital pour les restaurateurs. GetResto en a donc fait son arme principale !

GetResto a alors mis toutes les chances de son côté pour proposer la meilleure expérience utilisateur possible. Et pour s’inscrire totalement dans cette démarche, ils ont pu utiliser en avant-première le paiement embarqué intelligent, la nouvelle technologie de Lyra. Grâce à celui-ci, ils peuvent proposer tous les moyens de paiement, même les plus locaux, sans aucune redirection ! Les foodies confirment leur commande sur l’application, sans redirection et sans coupure. Le design de l’application est même repensé autour de ce nouveau schéma pour plus de cohérence et de fluidité.

Au-delà d’un parcours de paiement simple et user friendly, le choix des moyens de paiement a toute son importance et GetResto l’a bien compris. Proposant déjà les moyens de paiement classiques liés au secteur de la restauration tels que la carte ou les Titres Restaurant Dématérialisés (TRD), ils ont décidé de surfer sur les nouvelles tendances en ajoutant l’incontournable Apple Pay.

Apple Pay au service des restaurateurs

Get Resto utilise déjà ce nouveau moyen de paiement phare pour répondre aux besoins de ses partenaires restaurateurs en s’appuyant sur la solution Lyra. Apple Pay offre un taux de réussite de 100% ! Quand on sait qu’en fonction de son emplacement, un restaurant peut avoir jusqu’à 60% des paiements via Apple Pay, on comprend toute son importance dans ce secteur.

En effet, Apple Pay est LE moyen de paiement incontournable lorsqu’on propose des services via une application mobile. La combinaison d’Apple Pay sur application mobile, permet de proposer un parcours ultra optimisé pour une expérience utilisateur fluide :

  • Rapidité et instantanéité : en quelques clics seulement, la commande est passée. Plus besoin d’aller chercher sa carte, remplir les informations bancaires etc. Le paiement est à portée de doigt !
  • Conversion optimisée. Aucune information à compléter, pas d’erreur de CVV possible, processus ultra rapide… Les foodies valident leurs commandes à coup sûr et en quelques secondes.
  • Sécurité accrue. Aucune information bancaire ou d’identité n’est communiquée aux restaurateurs, ni stockés sur les serveurs d’Apple, ce qui limite fortement la fraude.

L’un des plus gros avantages de Lyra, c’est son avancement dans la proposition des moyens de paiement, et même les plus atypiques.

Daniel ABITBOL, Gérant de GetResto

À propos de GetResto

GetResto est l’application française qui réunit tous les amoureux des bons repas : les foodies. Proposant déjà plus de 200 000 restaurants en France, vos restaurants favoris disponibles en quelques clics.

APPLICATION DISPONIBLE SUR IOS ET ANDROID !

Pour la première fois, Lyra sera présente au congrès du coTer numérique, qui se tiendra au Centre International de Deauville les 20 et 21 juin prochains.

Chaque année, le coTer numérique réunit ses adhérents pour aborder les problématiques liées à l’informatique et à la communication des Collectivités territoriales françaises.

Le thème du congrès 2023 : la sobriété numérique !

Ces deux journées sont le rendez-vous incontournable des professionnels de l’IT des collectivités territoriales. Les décideurs informatiques ont ainsi l’occasion d’échanger avec près de 140 partenaires exposants et d’assister à 80 ateliers thématiques.

Les solutions Lyra au service des collectivités

De nombreuses collectivités font déjà confiance à Lyra. Grâce à notre plateforme de paiement PayZen, produit 100 % français et sécurisé, elles ont accès à une solution complète pour l’encaissement de leurs recettes publiques, avec notamment :

  • un parcours de paiement simplifié pour leurs clients, avec l’initiation de virement
  • de nombreux moyens de paiement, dont les Chèque-Vacances Connect et Titres-Restaurant dématérialisés

Assistez à l’atelier Lyra !

Rendez-vous le mercredi 21 juin à 11h15, en salle Tootsie, pour assister à l’atelier animé par Lyra :

« Offrez un parcours de paiement dématérialisé, flexible, moderne et compatible avec la gestion quotidienne de votre SI. »

Outre l’atelier, notre équipe vous attend sur le stand 104 et se fera un plaisir de répondre à toutes vos interrogations.

Informations pratiques :

  • Congrès du coTer numérique
  • 20 et 21 juin 2023
  • Stand 104 – niveau -2
  • Centre International de Deauville : 1 avenue Lucien Barrière – 14800 Deauville
  • Atelier technique de Lyra : mercredi 21 juin à 11h15 – salle Tootsie

Chaque année, Finance innovation recense les sociétés dans l’univers des Fintechs & Assurtechs et réalise un classement : Fintech 100. Ce dernier s’appuie sur 5 critères : la levée de fonds, le chiffre d’affaires, la croissance, l’effectif et l’efficacité du capital.

Pour 2023, Lyra se positionne dans le TOP 2 dans la catégorie des solutions de paiement

Cette place sur le podium aux côtés d’acteurs très innovants confirme que les solutions de paiement Lyra répondent aux besoins des commerçants, marketplaces et sociétés en BtoB ainsi qu’aux attentes de leurs clients.

Lyra est aussi à la 11ème place dans le classement général. Sachant que Lyra n’a jamais fait de levée de fonds depuis sa création en 2001 et s’est toujours autofinancée, cette place est une réelle récompense.

  • Une récompense pour les efforts de nos collaborateurs à maintenir et faire évoluer un service de qualité
  • Une récompense pour notre gestion financière saine
  • Une récompense pour le choix de ne pas suivre la mode des levées de fonds ou des rachats
résultat fintech100 2023 / Lyra
Et aussi
10ème place en termes d’effectif
 7ème place en termes de CA

Pour accéder à l’ensemble des résultats et de l’étude sur l’écosystème fintech, rendez-vous sur le site Fintech 100.

Dans les articles précédents (intro, chapitre 1, chapitre 2), nous avons répondu aux problématiques liées aux dépendances et à l’outillage de développement. Nous allons maintenant nous concentrer sur le contenu de la librairie, ses composants et sa configuration.

La librairie doit être personnalisable sur plusieurs aspects, notamment :

  • le thème graphique
  • l’internationalisation : les locales supportées ainsi que la langue courante
  • la gestion des appels serveurs et l’authentification
  • le paramétrage de certains composants exposés par la librairie
  • le paramétrage propre à notre métier


Comme évoqué précédemment, toutes nos applications partagent la même stack technologique. Côté code, on retrouve principalement :

  • react-native-paper, une librairie de composants React Native qui implémente le design Material de Google et qui porte le thème
  • i18n-js pour l’internationalisation
  • axios pour les appels serveur


Un provider pour notre librairie


Notre librairie doit être capable de prendre un certain nombre de paramètres en entrée. Ces paramètres doivent être accessibles depuis n’importe quel composant de la librairie, voir même depuis l’application.

Un contexte React est la solution parfaite pour ce besoin. Pour utiliser un contexte, il faut déclarer un Provider. Tous les enfants de ce Provider ont ainsi accès à la valeur du contexte.

Nous avons repris cette logique pour notre librairie. La librairie expose donc un composant « Provider« , qui a été volontairement simplifié pour notre exemple :

Composant Provider de la lib
import { FC } from 'react'
import { ConfigContextProvider } from '../contexts/ConfigContext'

interface Props {
locale?: string
mode?: 'TEST' | 'PRODUCTION'
}

export const Provider: FC = ({
locale = 'fr-FR',
mode = 'PRODUCTION',
children,
}) => (
{children}
)


Ce composant prend tous les paramètres de configuration de la librairie et les stock dans un contexte interne à la librairie.

En situation réelle, notre Provider prend plus de props et déclare plusieurs contextes, mais vous avez compris l’idée 😉

Les applications doivent obligatoirement déclarer ce Provider pour utiliser la librairie. Voici un exemple simplifié du composant App de notre application :

Déclaration du Provider dans l'app

import { Provider as SharedComponentsProvider } from '@lyra/shared-components'
import { FC } from 'react'
import { AppContext, AppContextProvider } from './src/contexts/AppContext'
import { Root } from './src/Root'

export const App: FC = () => (
{({ locale, mode }) => ( )}
)


L’utilisation des contextes nous permet de rester dans le cycle de vie React et d’être totalement synchronisé, de manière à ce que si par exemple, l’utilisateur change la locale dans sa page de paramètres de l’application, la valeur sera mise automatiquement à jour dans la librairie.


Gestion du thème


Dans nos applications, le thème est porté par React Native Paper (RNP). RNP est une librairie de composants React Native qui implémente le design Material de Google.

Sur les screenshots ci-dessous, on remarque que les composants se ressemblent mais que la palette de couleur est différente pour chaque application. Ces différences sont justement configurées dans le thème.

La librairie va donc utiliser des composants de RNP, en prenant en compte le thème de l’application cible.

Nos besoins sont les suivants :

  • Tous les composants de la librairie doivent utiliser le thème de l’application cible
  • Le format du thème sera basé sur celui de RNP mais contiendra quelques champs supplémentaires, il faut que le typage du thème fonctionne correctement dans la lib et dans les apps
  • La librairie doit proposer un thème par défaut
  • Les applications doivent pouvoir passer un thème personnalisé
  • Les applications doivent pouvoir accéder au thème avec un hook ou un High-Order Component (HOC)


Mais plusieurs problématiques apparaissent :

  • Le thème est passé à RNP via un Provider, mais qui doit déclarer ce Provider : l’application ou la librairie ?
  • Le format de nos thèmes hérite de celui de RNP mais rajoute quelques champs custom, comment faire pour que les apps et la lib aient accès au typage de notre thème ?
  • Comment faire pour pouvoir accéder au thème au moment de déclarer nos styles (depuis l’app ou la lib) ?

Qui doit déclarer le Provider RNP ?


Celui qui déclare le Provider RNP devra aussi être responsable d’augmenter le type du thème RNP avec les champs qu’il veut ajouter, comme ceci par exemple :

react-native-paper.ts
declare global {
namespace ReactNativePaper {
interface ThemeColors {
highlighted: string
}
}
}


Avec cette augmentation de module, il est maintenant possible d’accéder à theme.colors.highlighted (qui n’existe pas dans le thème RNP) sans erreur TypeScript.

Si ce sont les apps qui déclarent le Provider RNP, plusieurs problèmes apparaissent :

  • La lib pourra utiliser des composants RNP mais n’aura pas accès au thème en dehors du contexte React (avec useTheme). Les feuilles de style en React Native se déclarent en dehors des composants, donc ça pose problème.
  • La lib ne pourra pas augmenter le type du thème et ne bénéficiera pas de l’augmentation du type par l’app.
  • La lib ne pourra pas proposer de thème par défaut.


C’est pour ces raisons que nous avons choisi de déléguer la responsabilité de déclarer le Provider RNP à notre librairie.

C’est la solution la plus logique, la librairie peut ainsi proposer un thème par défaut, avec son typage. Les apps restent libres d’étendre ce thème (en créant un thème custom) comme bon leur semble.

La librairie déclare donc le Provider RNP au niveau de son propre Provider :

Composant Provider de la lib
import { FC } from 'react'
import { Provider as PaperProvider } from 'react-native-paper'
import { ConfigContextProvider } from '../contexts/ConfigContext'
import { LyraTheme } from '../styles/LyraTheme'

interface Props {
locale?: string
mode?: 'TEST' | 'PRODUCTION'
theme?: ReactNativePaper.Theme
}

export const Provider: FC = ({
locale = 'fr-FR',
mode = 'PRODUCTION',
theme = LyraTheme,
children,
}) => (
{children}
)


À noter que la dépendance react-native-paper sera déclarée en peerDependencies au niveau de la librairie pour ne pas dupliquer la dépendance (voir notre deuxième article pour plus de détails).

Thème par défaut


Si aucun thème n’est passé à la lib, un thème par défaut « Lyra » est utilisé.

Les applications peuvent passer un autre thème en se basant (ou pas) sur le thème par défaut, par exemple :

Déclaration du Provider dans l'app
import { LyraTheme, Provider } from '@lyra/shared-components'

const theme: ReactNativePaper.Theme = {
…LyraTheme,
colors: {
…LyraTheme.colors,
primary: '#29337a',
}
}

export const App = () => (

)

Hook et High Order Component pour la création de styles


Maintenant que le thème et son type associé sont bien en place, il reste un dernier point à adresser sur notre liste de besoins : la mise à disposition d’un hook et d’un HOC pour la création de styles.

Rappel sur les hooks

Les Hooks sont des fonctions qui permettent de « se brancher » sur la gestion d’état local et de cycle de vie de React depuis des fonctions composants.

Rappel sur les HOC

Un HOC est une technique avancée de React qui permet de réutiliser la logique de composants. Concrètement, un composant d’ordre supérieur est une fonction qui accepte un composant et renvoie un nouveau composant.

En React Native, le style est passé aux composants grâce à la propriété style. Généralement, on externalise le style avec StyleSheet.create pour plus de lisibilité. Cette méthode ne fait pas grand chose mais au moins elle valide le format du style qu’on lui passe. Exemple :


import { FC } from 'react'
import { StyleSheet } from 'react-native'
import { Text } from 'react-native-paper'

const styles = StyleSheet.create({
link: {
color: 'blue',
textDecorationLine: 'underline',
},
})

export const LinkText: FC = ({ children }) => (
{children}
)


Admettons que ce composant fasse partie de notre librairie et que l’on souhaite que le texte soit de la couleur « primary » du thème. Cela donne :

import { FC } from 'react'
import { StyleSheet } from 'react-native'
import { Text, useTheme } from 'react-native-paper'
 
const styles = StyleSheet.create({
  link: {
    textDecorationLine: 'underline',
  },
})
 
export const LinkText: FC = ({ children }) => {
  const { colors } = useTheme()
  return <Text style={[styles.link, { color: colors.primary }]}>{children}</Text>
}


Ça fonctionne, mais ce n’est pas encore optimal. En effet, cela nous oblige à avoir du style à la fois à l’extérieur du composant mais aussi en inline. Et c’est assez verbeux.

Nous avons donc trouvé une solution qui s’inspire du makeStyles de MUI : une fonction de création de hook.

import { FC } from 'react'
import { Text } from 'react-native-paper'
import { createUseStyles } from '../styles'
 
const useStyles = createUseStyles((theme) => ({
  link: {
    color: theme.colors.primary,
    textDecorationLine: 'underline',
  },
}))
 
export const LinkText: FC = ({ children }) => {
  const { styles } = useStyles()
  return <Text style={styles.link}>{children}</Text>
}


Le hook useStyles retourne un objet avec les styles et le thème. Il est possible de lui passer des propriétés qui sont récupérées en deuxième argument de la callback de createUseStyles.

Pour les composants sous forme de classe, nous mettons également à disposition un HOC withStyles :

import { Component } from 'react'
import { Text } from 'react-native-paper'
import { withStyles } from '../styles'
 
const getStyles = (theme) => ({
  link: {
    color: theme.colors.primary,
    textDecorationLine: 'underline',
  },
})
 
class LinkTextComponent extends Component {
  render() {
    const { styles } = this.props
    return <Text style={styles.link}>{children}</Text>
  }
}
 
export const LinkText = withStyles(getStyles)(LinkTextComponent)

Pour la partie relative au thème, les problématiques sont résolues :

  • La librairie s’occupe de déclarer le Provider de React Native Paper.
  • La librairie propose un thème par défaut, qui reste extensible par les applications.
  • La librairie expose des utilitaires de création de style qui permettent d’accéder au thème.

Gestion de l’internationalisation


Sans rentrer dans les détails, l’internationalisation et la localisation sont des concepts liés à la capacité d’un logiciel à s’adapter à une région donnée. C’est principalement la traduction des textes mais il y a aussi le formatage des dates, des nombres, des devises, et de tout ce qui peut être spécifique à une région.

Nos applications sont disponibles dans plusieurs régions, donc dans plusieurs langues. Et selon l’application et la configuration de celle-ci, la liste des langues disponibles n’est pas toujours la même.

Notre librairie doit donc être capable de fournir ses composants dans la locale désirée. Le soucis, c’est qu’elle ne peut pas anticiper la totalité des locales qui lui seront demandées.

Nos besoins sont les suivants :

  • L’application doit pouvoir spécifier la langue à utiliser
  • L’application doit pouvoir fournir toutes les informations d’une locale si celle-ci n’est pas supportée par la lib, et pouvoir surcharger certaines valeurs selon ses besoins
  • Les composants de la lib doivent respecter la locale spécifiée par l’application


Les problématiques à solutionner sont les suivantes :

  • Comment éviter de dupliquer la dépendance du gestionnaire i18n ?
  • Qui porte la responsabilité de l’internationalisation (dépendance + initialisation) ?
  • Comment fusionner les locales de la librairie et de l’application ?
  • Comment permettre à l’app d’ajouter ou changer des valeurs dans les locales ?


Un gestionnaire d’internationalisation commun


Pour éviter la duplication de dépendance et partager un format commun pour les locales, la solution la plus simple est de se mettre d’accord sur le choix du gestionnaire d’internationalisation.

Les applications utilisent déjà toutes la même librairie : i18n-js. L’idéal serait donc de fusionner les locales entre l’application et la librairie puis d’initialiser le composant d’i18n qu’une seule fois.

Qui porte la responsabilité ?


Comme pour la gestion du thème, la dépendance sera apportée par l’application et déclarée en peerDependencies dans la librairie.

L’application passera la locale courante ainsi que les locales à ajouter / modifier à la librairie via les props de son Provider.

C’est la librairie qui s’occupe d’initialiser le gestionnaire d’internationalisation avec les données de locales fusionnées entre celles de la lib et celles de l’application.

Voici la nouvelle version de notre Provider avec la gestion de l’internationalisation :

Composant Provider de la lib

import i18n from 'i18n-js'
import { merge } from 'lodash'
import { FC, useLayoutEffect } from 'react'
import { Provider as PaperProvider } from 'react-native-paper'
import { ConfigContextProvider } from '../contexts/ConfigContext'
import { LyraTheme } from '../styles/LyraTheme'
import { translations as internalTranslations } from '../i18n'
 
interface Props {
  locale?: string
  mode?: 'TEST' | 'PRODUCTION'
  theme?: ReactNativePaper.Theme
  translations?: Record<string, object> // { 'fr-FR': {...}, 'en-GB': {...} }
}
 
export const Provider: FC<Props> = ({
  locale = 'fr-FR',
  mode = 'PRODUCTION',
  theme = LyraTheme,
  translations,
  children,
}) => {
  // useLayoutEffect pour éviter un flash de contenu non traduit
  useLayoutEffect(() => {
    i18n.fallbacks = true
    i18n.defaultLocale = 'fr-FR'
    i18n.translations = merge({}, internalTranslations, translations)
    i18n.locale = locale
  }, [locale, translations])
 
  return (
    <PaperProvider theme={theme}>
      <ConfigContextProvider locale={locale} mode={mode}>
        {children}
      </ConfigContextProvider>
    </PaperProvider>
  ) 
}



À noter que i18n-js n’utilise pas de Provider, il faut initialiser les champs de son instance globale.

Pour la partie relative à l’internationalisation, les problématiques sont résolues :

✅ Une seule dépendance et une seule initialisation pour le gestionnaire d’internationalisation.

✅ Les traductions de la lib et de l’app sont fusionnées ce qui laisse la liberté aux apps d’ajouter ou remplacer les traductions de la lib.

❗ Le seul point qui ne reste pas optimal est que les apps doivent connaitre les clés de traduction exactes dans la lib pour pouvoir les surcharger.

Les paramètres utilisateur


Dernière partie de cet article assez touffu… nous allons aborder les paramètres utilisateur.

L’intégration de la librairie dans les applications doit être totalement transparente pour l’utilisateur.

Les applications et la librairie gèrent en interne leurs propres paramètres pour leurs composants, mais il ne faut proposer à l’utilisateur qu’un seul écran de paramétrage qui permette de régler tous de paramètres.

Nos besoins

  • Pouvoir intégrer les paramètres de la librairie dans l’écran de paramétrage de l’application
  • L’utilisateur doit pouvoir modifier des paramètres d’app ou de lib de manière transparente


Les problématiques

  • Comment passer des paramètres utilisateur à la librairie ?
  • Comment intégrer des paramètres spécifiques aux composants de la librairie dans l’écran de paramétrage de l’application ?


Passage de paramètres de l’application vers la librairie


Avec notre système de Provider déjà en place, il est facile de passer des paramètres à la librairie.

Ces paramètres sont synchronisés dans un contexte côté librairie et donc accessibles partout dans la librairie.

Voir l’exemple de code du Provider un peu plus haut, les paramètres locale et mode sont des paramètres utilisateurs qui sont gérés au niveau de la librairie.

Intégration des paramètres spécifiques à la librairie


Le comportement de certains composants de la librairie est paramétrable.

Pour que ces paramètres soient parfaitement intégrées, la librairie expose un composant qui se fond parfaitement dans les paramètres de l’application. En effet, ce composant utilise les même composant RNP qui est lui-même correctement configuré au niveau du thème.

La valeur de ces paramètres est stocké dans un contexte interne à la librairie.

L’utilisateur peut donc personnaliser ses paramètres, sans savoir si ceux-ci sont gérés par l’application ou la librairie.

Le screenshot ci-dessous présente l’écran des paramètres de l’application Lyra Collect, le bloc encadré en rouge provient de notre librairie :

✅ L’intégration est donc parfaitement transparente pour l’utilisateur.

✅ L’application peut passer des paramètres personnalisés à la librairie.

✅ La librairie est capable de gérer ses propres paramètres.

L’environnement de développement, l’intégration continue et le déploiement

Dans l’article précédent, nous avons configuré les outils pour la compilation et le packaging de la librairie. Nous allons maintenant voir comment configurer l’environnement de développement, l’intégration continue et le déploiement.

En effet, une des priorités de cette librairie est de proposer une expérience de développement agréable : un développeur doit pouvoir modifier la librairie et la tester sans délai dans son application mobile..

Enfin, nous verrons comment configurer l’intégration continue pour s’assurer de la qualité des développements, éviter les régressions et automatiser les publications.

Nos besoins

Nous pouvons résumer nos besoins comme suit :

  • Simplicité d’utilisation et bonne expérience de développement (cf. ci-dessus)
  • Intégration continue avec Bitrise, notre plateforme de CI mobile
  • Déploiement de la librairie sur notre registry npm interne

Les problématiques

Pour atteindre ces objectifs, plusieurs points restent à éclaircir :

  • Comment permettre au développeur de développer sans différence notable au sein de la librairie et de son application ?
  • Comment configurer notre plateforme d’intégration continue pour tester la librairie en isolation ?
  • S’assurer que la publication de la librairie sur notre registry npm privé fonctionne depuis Bitrise.

L’environnement de développement

La qualité de l’expérience de développement est un point très important à Lyra : il ne faut donc pas que la mise en place de cette librairie ajoute trop de complexité à l’environnement de développement, ou pénalise la productivité des développeurs.

Du coup, le développement de l’application et de la librairie doit continuer de bénéficier des comportement classiques : refresh de composant à chaud, debugger, inspecteur d’éléments etc.

De même, depuis l’application, on doit pouvoir basculer facilement entre une version publiée de la librairie (version officielle) et une version locale (en cours de modification par le développeur), afin de ne pas être obliger de publier une version juste pour la tester dans l’application finale. Il faut donc pouvoir dire au gestionnaire de paquet d’utiliser la version locale de la librairie, et non pas la version publiée disponible sur un registry.

Linking local de notre librairie

Généralement lorsque l’on veut « lier » une librairie en local, on utilise yarn/npm link :

cd /path/to/lib    # nom du package = @lyra/shared-components
yarn link
cd /path/to/app
yarn link @lyra/shared-components

Malheureusement, dans notre cas, c’est un échec. C’est un problème connu (issue #1) mais Metro ne supporte pas les liens symboliques (symlinks) dans les dépendances ! Il a donc fallu trouver une autre solution …

Deuxième essai : yalc

Une solution alternative est d’utiliser yalc.

Yalc est un utilitaire qui utilise un dépôt local dans lequel on peut venir pousser des modules. A chaque mise à jour d’un module, il le copie automatiquement dans les répertoires « node_modules » de tous les projets « abonnés ».

Il n’utilise donc pas de lien symbolique mais une copie directe des fichiers au bon emplacement dans les node_modules.

npm install --global yalc
cd /path/to/lib     # nom du package = @lyra/shared-components
yalc push
cd /path/to/app
yalc add @lyra/shared-components

Et ça fonctionne plutôt bien ! L’application démarre et ne se rend compte de rien. A chaque nouvelle commande ‘yalc push’, les sources sont copiées dans les node_modules de l’application cible.

Et cerise sur le gâteau, le watcher de Metro surveille aussi les node_modules, ce qui veut dire qu’à chaque yalc push, Metro déclenche un refresh des composants modifiés. C’est exactement le fonctionnement désiré.

Synchronisation automatique des mises à jour

Nous savons maintenant linker notre librairie en local. Cependant, à chaque modification du code, nous sommes obligés de lancer manuellement un build TypeScript puis la commande ‘yalc push’ pour mettre à jour les sources dans les node_modules de l’application. On peut encore améliorer ça.

Il nous faudrait une sorte de watcher qui nous permette de déclencher automatiquement, à chaque modification, un build TypeScript puis un yalc push.

Or, un utilitaire fait exactement cela : tsc-watch !

Une fois lancé, l’utilitaire déclenche automatiquement un build TypeScript dès qu’il détecte une modification dans le code source. Nouvelle cerise sur le gâteau, on peut lui passer des commandes après chaque build avec l’option –onSuccess → c’est l’endroit idéal pour placer notre yalc push.

On configure donc un script npm côté librairie pour lancer tsc-watch.

package.json


{
  "scripts": {
    ...
    "prewatch": "rimraf dist",
    "watch": "tsc-watch -p tsconfig.json --onSuccess \"yalc push\"",
  }
}

Ainsi, à chaque modification sur un fichier de la librairie, un build TypeScript est déclenché, suivi d’un yalc push. L’application recharge les composants modifiés comme si nous développions directement dans l’application !

La configuration côté application

Côté application, il suffit de deux scripts npm pour lier ou délier la librairie locale :

package.json

{
  "scripts": {
    ...
    "lib:link": "yalc add @lyra-network/shared-components && yarn install",
    "lib:unlink": "yalc remove @lyra-network/shared-components && yarn install",
  }
}

Le yarn install après chaque commande est important dans le cas où les dépendances dans la librairie sont différentes entre la version locale et la version publiée dans nos repositories.

Pour résumer, quand un développeur veut faire une évolution dans la librairie, il est opérationnel en deux commandes : yarn watch depuis la lib et yarn lib:link dans son application.

Ensuite qu’il développe dans son application ou dans la librairie, le comportement est identique ! Il peut continuer à tester ses changements directement dans son application comme avant.

Attention à ne pas publier la configuration locale !

Nous utilisons une plateforme d’intégration continue ainsi que la plateforme d’Expo pour publier nos applications. Nous reviendrons en détail sur ces sujets juste après.

Cependant, dans ce contexte, si la librairie est linkée localement au moment de la publication, ça ne peut pas fonctionner. En effet, quand l’application sera compilée sur les serveurs d’Expo, les références locales ne peuvent pas être résolues.

Il faut donc être vigilant et s’assurer qu’au moment de publier, la librairie ne soit pas configuré en mode « local ».

Pour être sûr de ne jamais se rater (une erreur est si vite arrivée…), nous avons mis en place un hook git pre-push au niveau de chaque application qui vérifie qu’il n’y a pas de package linké localement avant de pousser le code sur la branche ‘main’.

Pour nos hooks git, nous utilisons l’utilitaire husky. La commande yalc check nous permet de vérifier qu’aucun package n’est linké localement dans le projet courant. Exemple de notre fichier de hook pre-push :

.husky/pre-push

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
 
git_branch="$(git rev-parse --abbrev-ref HEAD)"
 
if [ "$git_branch" = "main" ]; then
  npx yalc check
fi

yalc check retourne une erreur si un package est linké localement et que nous sommes sur la branche ‘main’, ce qui empêche ainsi le push sur le serveur git.

C’est terminé pour la partie environnement de développement. Les outils mis en place nous permettent de garder une bonne expérience développeur. Le client Expo fonctionne comme avant et ne se rend même pas compte du changement. Le développeur peut donc travailler sur la librairie en testant les modifications directement depuis son application.


Intégration continue et publication de la librairie

Grâce à la plateforme d’intégration continue Bitrise, tout changement de code dans la librairie est testé pour éviter les régressions. La publication de la librairie est également gérée avec Bitrise. 

Non-régression

La librairie contient ses propres tests avec Jest, ainsi que sa propre configuration ESLint et Prettier.

A chaque nouveau push, quelque soit la branche, un build est lancé dans Bitrise. Notre workflow contient les étapes suivantes :

  1. yarn install → installation des dépendances
  2. yarn lint → vérification de la syntaxe avec ESLint
  3. yarn test → exécute les tests unitaires et d’intégration avec Jest
  4. yarn build → compile la librairie avec TypeScript

Si une étape échoue, nous sommes notifiés directement par messagerie instantanée.

Ces tests nous permettent de nous assurer que les branches restent stables, surtout la branche ‘main’.

Publication de la librairie

Pour la publication, nous souhaitons que la librairie reste privée mais tout de même accessible depuis l’extérieur, justement pour permettre à Bitrise et Expo d’y accéder au moment des tests et builds de nos applications.

A Lyra, nous avons déjà un registry npm privé (accessible depuis l’extérieur via une authentification forte). C’est donc sur celui-ci que nous allons publier notre librairie.

Après avoir configuré l’authentification dans Bitrise, nous avons créé un nouveau job dans Bitrise qui s’occupe de publier notre librairie. Ce job est déclenché à chaque nouveau tag git.

Ainsi, quand nous voulons publier une nouvelle version de la librairie, il suffit de pousser un tag git. Le job se déclenche alors automatiquement, il effectue tous les tests nécessaires puis publie la librairie.

Ci-dessous une impression d’écran des différents builds de notre librairie sur Bitrise :

ci-builds

Conclusion

(coche)  L’expérience développeur reste très bonne malgré l’introduction de la librairie.

(coche)  Les développements en local sont quasi transparents grâce à l’outillage de notre environnement de développement.

(coche)  Le développeur continue de tester son application, qu’il travail sur la librairie ou sur son application.

(coche)  La qualité de la librairie reste maitrisée grâce à l’intégration continue.

(coche)  La librairie reste privée mais tout de même accessible depuis l’extérieur via une authentification.