Shopify met à disposition des webhook qui sont exposés lors d’évènements, parmi ces derniers deux seront mis à contribution dans ce billet :

  • AFTER_AUTH : utilisé après authentification de l’utilisateur sur l’application, installation notemment
  • APP_UNINSTALLED : utilisé après désinstallation de l’application de la boutique d’un utilisateur

Il faut savoir que par défaut certaines actions sont réalisées lors de l’installation/désinstallation d’une application, par exemple lors de l’installation, une entité utilisateur représentant le magasin est enregistrée dans la base de l’application ainsi que ses options de version si nécessaire, ces informations sont mises à jour lors de la désinstallation, par contre pour tout le reste, c’est à dire la config spécifique, les tables … Il faut le faire.

Nous allons commencer par l’installation d’une application et donc l’exposition du webhook AFTER_AUTH, ce dernier est appelé entre autre juste après l’installation d’une application sur la boutique d’un utilisateur, c’est à ce moment que l’on peut réaliser des opérations spécifiques, comme par exemple créer la structure de données nécessaire au fonctionnement de l’application, si certaines informations doivent être contextuelles, je préfère passer par cette solution plutôt qu’une seule table avec un identifiant d’utilisateur car dans le cas où les données sont nombreuses, une table énorme peut être un problème.

Pour enregistrer l’appel à ce webhook dans Laravel, il faut éditer le fichier config/shopify-app.php section webhooks comme suit :

'webhooks' => [
        [
            'topic' => env('SHOPIFY_WEBHOOK_APP_UNINSTALLED_TOPIC', ''),
            'address' => env('APP_URL').env('SHOPIFY_WEBHOOK_APP_UNINSTALLED_ADDRESS', '')
        ],
        [
            'topic' => env('SHOPIFY_WEBHOOK_APP_INSTALLED_TOPIC', ''),
            'address' => env('APP_URL').env('SHOPIFY_WEBHOOK_APP_INSTALLED_ADDRESS', '')
]

Et pour plus de flexibilité, les valeurs sont donc enregistrées dans le fichier .env :

SHOPIFY_WEBHOOK_APP_INSTALLED_TOPIC=AFTER_AUTH
SHOPIFY_WEBHOOK_APP_INSTALLED_ADDRESS=/webhook/after-authenticate
SHOPIFY_WEBHOOK_APP_UNINSTALLED_TOPIC=APP_UNINSTALLED
SHOPIFY_WEBHOOK_APP_UNINSTALLED_ADDRESS=/webhook/app-uninstalled

Ici la config est préparée pour les deux hooks.

Chaque hook doit contenir deux champs :

  • topic : il représente le nom du webhook, ce dernier doit donc être valide pour que l’action soit exécutée, voir la liste des webhooks dispo.
  • address : elle doit pointer vers votre boutique, sa structure sera expliquée un peu plus bas

Le nom des valeurs n’a pas d’importance, l’essentiel étant qu’il soit intelligible.

Pour notre exemple, le webhook AFTER_AUTH est donc appelé avec la cible env(‘APP_URL’).env(‘SHOPIFY_WEBHOOK_APP_INSTALLED_ADDRESS’,  »), on donne donc l’url de base de la boutique, suivie de /webhook indiquant qu’il s’agit d’un webhook et donc qu’il faut exécuter le job passé en paramètre, /after-authenticate représente donc le job a appeler, ici ce sera le fichier app/Jobs/AfterAuthenticate.php.

Dans le job, nous allons donc créer les tables spécifiques à l’utilisateur (boutique) qui a installé l’application :

public function handle(): void
    {
        // Set logger
        $logger = new AiLogger();

        $logger->debug('AfterAuthenticate', 'START');
        
        // Create specific tables if needed
        $this->shop->createTables($logger);
        
        $logger->debug('AfterAuthenticate', 'END');
    }

La ligne qui nous intéresse plus particulièrement est celle en gras, elle fait appel au modèle User afin de mettre à jour les données dans la base en fonction de ce dernier, comme suit :

public function createTables($logger) {
        $prefix = '_'.str_replace('.myshopify.com', '', $this->name);  //aidev-test.myshopify.com
        if (!Schema::hasTable($prefix.'_products_variants')) {
            Schema::create($prefix.'_products_variants', function($table) {
                $table->increments('id')->unique(); //primary key
                $table->unsignedBigInteger('shopify_id')->unique();
                $table->unsignedBigInteger('product_id')->unique();
                $table->foreignId('user_id')->references('id')->on('users');
                $table->dateTime('created_at')->nullable();
            });
                
            $logger->debug('AfterAuthenticate', 'table '.$prefix.'_products_variants CREATED');
        }
        if (!Schema::hasTable($prefix.'_configuration')) {
            Schema::create($prefix.'_configuration', function($table) {
                $table->increments('id')->unique(); //primary key
                $table->string('name')->unique();
                $table->string('value');
                $table->dateTime('created_at')->nullable();
            });
                
            $logger->debug('AfterAuthenticate', 'table '.$prefix.'_configuration CREATED');
        }
    }

Nous allons donc créer deux tables, une pour les infos variations de produits (pour mon projet en cours) ainsi qu’une pour les données de configuration concernant cet utilisateur, et ce en utilisant le préfixe du domaine de la boutique qui installe l’application, pour aidev-test.myshopify.com ça donnera donc _aidev-test, les tables en résultant : _aidev-test_products_variants et _aidev-test_configuration.

Notez qu’à chaque fois nous vérifions l’existence des tables pour être sur de ne pas avoir d’erreur en cas d’appel ultérieur.

Pour la désinstallation, le hook standard s’appelle APP_UNINSTALLED mais le fichier PHP correspondant comporte Job à la fin, sans doute l’historique, nous procédons à la suppression des tables dans le fichier app/Jobs/AppUninstalledJob.php, ce fichier étendant le job natif et ayant un handle spécifique, nous commençons par l’exécuter avant de réaliser nos opérations :

public function handle(IShopCommand $shopCommand, IShopQuery $shopQuery, CancelCurrentPlan $cancelCurrentPlanAction): bool
    {
        // Set logger
        $logger = new AiLogger();
        
        $logger->debug('AppUninstalled', 'START');
        
        if (parent::handle($shopCommand, $shopQuery, $cancelCurrentPlanAction)) {
            $logger->debug('AppUninstalled', json_encode($this->data));
            
            User::deleteTables($this->data->domain, $logger);
        } else {
            $logger->error('AppUninstalled', 'error');
        }
        
        $logger->debug('AppUninstalled', 'END');
        
        return true;
    }

Nous exécutons donc le handle du fichier parent, puis nous passons la main au modèle User pour supprimer nos tables :

public static function deleteTables($domain, $logger) {

// Remove user

//DB::table(‘users’)->where(‘name’, ‘=’, $this->data->domain)->delete();

// Remove specific tables

$prefix = ‘_’.str_replace(‘.myshopify.com’,  », $domain); //aidev-test.myshopify.com

Schema::dropIfExists($prefix.’_products_variants’);

Schema::dropIfExists($prefix.’_configuration’);

$logger->debug(‘AppUninstalled’, ‘Tables for ‘.$prefix.’ DELETED’);

}

Vu que nous n’avons pas le shop, mais le domaine, qui est suffisant pour ce que nous avons à faire, nous appelons la méthode en statique.

A noter, par défaut lors de la désinstallation d’une application Shopify supprime logiquement l’utilisateur en mettant sa colonne deleted_at à la date du jour, si la boutique ré-installe l’application, cet utilisateur sera repris et le champs vidé, si vous souhaitez supprimer réellement cet utilisateur, il suffit de dé-commenter la ligne de suppression.

J’ai traité ici la création/suppression de données de la base, mais d’autres tâches peuvent être réalisées, je modifierai le billet si je réalise d’autres opérations.

Il existe sans doute d’autres méthodes, mais celle ci fonctionne, et utilise, à mon sens, la bonne méthode en fonction de la structure de Laravel.

J’espère que ce billet pourra aider certaines personnes.

Leave a Reply

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

− cinq = 3