diff --git a/src/ShopifyApp/Console/WebhookJobMakeCommand.php b/src/ShopifyApp/Console/WebhookJobMakeCommand.php index 6dfa228b..6d43fa5e 100644 --- a/src/ShopifyApp/Console/WebhookJobMakeCommand.php +++ b/src/ShopifyApp/Console/WebhookJobMakeCommand.php @@ -4,6 +4,7 @@ use Illuminate\Foundation\Console\JobMakeCommand; use Illuminate\Support\Str; +use Osiset\ShopifyApp\Util; use Symfony\Component\Console\Input\InputArgument; class WebhookJobMakeCommand extends JobMakeCommand @@ -54,13 +55,18 @@ public function handle() { $result = parent::handle(); + $topic = Util::getGraphQLWebhookTopic($this->argument('topic')); + + $type = $this->getUrlFromName($this->argument('name')); + $address = route(Util::getShopifyConfig('route_names.webhook'), $type); + // Remind user to enter job into config $this->info("For non-GDPR webhooks, don't forget to register the webhook in config/shopify-app.php. Example:"); $this->info(" 'webhooks' => [ [ - 'topic' => '{$this->argument('topic')}', - 'address' => 'https://your-domain.com/webhook/{$this->getUrlFromName(trim($this->argument('name')))}' + 'topic' => '$topic', + 'address' => '$address' ] ] "); @@ -75,13 +81,7 @@ public function handle() */ protected function getNameInput(): string { - $name = parent::getNameInput(); - $suffix = 'Job'; - if (! Str::endsWith($name, $suffix)) { - $name .= $suffix; - } - - return $name; + return Str::finish(parent::getNameInput(), 'Job'); } /** @@ -93,10 +93,10 @@ protected function getNameInput(): string */ protected function getUrlFromName(string $name): string { - if (Str::endsWith($name, 'Job')) { - $name = substr($name, 0, -3); - } - - return strtolower(preg_replace('/(?trim() + ->replaceMatches('/Job$/', '') + ->replaceMatches('/(?lower(); } } diff --git a/src/ShopifyApp/Services/ApiHelper.php b/src/ShopifyApp/Services/ApiHelper.php index 7653c73d..b6334b77 100644 --- a/src/ShopifyApp/Services/ApiHelper.php +++ b/src/ShopifyApp/Services/ApiHelper.php @@ -384,8 +384,12 @@ public function createWebhook(array $payload): ResponseAccess } '; + // change REST-format topics ("resource/event") + // to GraphQL-format topics ("RESOURCE_EVENT") for pre-v17 compatibility + $topic = Util::getGraphQLWebhookTopic($payload['topic']); + $variables = [ - 'topic' => $payload['topic'], + 'topic' => $topic, 'webhookSubscription' => [ 'callbackUrl' => $payload['address'], 'format' => 'JSON', diff --git a/src/ShopifyApp/Util.php b/src/ShopifyApp/Util.php index 4abc6f0b..e4b7a77e 100644 --- a/src/ShopifyApp/Util.php +++ b/src/ShopifyApp/Util.php @@ -194,4 +194,19 @@ public static function getShopifyConfig(string $key, $shop = null) return Arr::get($config, $key); } + + /** + * Convert a REST-format webhook topic ("resource/event") + * to a GraphQL-format webhook topic ("RESOURCE_EVENT"). + * + * @param string $topic + * + * @return string + */ + public static function getGraphQLWebhookTopic(string $topic): string + { + return Str::of($topic) + ->upper() + ->replaceMatches('/[^A-Z_]/', '_'); + } } diff --git a/src/ShopifyApp/resources/config/shopify-app.php b/src/ShopifyApp/resources/config/shopify-app.php index 92205b10..9ab788aa 100644 --- a/src/ShopifyApp/resources/config/shopify-app.php +++ b/src/ShopifyApp/resources/config/shopify-app.php @@ -310,8 +310,11 @@ |-------------------------------------------------------------------------- | | This option is for defining webhooks. - | Key is for the Shopify webhook event - | Value is for the endpoint to call + | `topic` is the GraphQL value of the Shopify webhook event. + | `address` is the endpoint to call. + | + | Valid values for `topic` can be found here: + | https://shopify.dev/api/admin/graphql/reference/events/webhooksubscriptiontopic | */ diff --git a/tests/UtilTest.php b/tests/UtilTest.php index 78063f1b..9196279a 100644 --- a/tests/UtilTest.php +++ b/tests/UtilTest.php @@ -82,4 +82,27 @@ public function testGetShopifyConfig(): void $this->assertEquals('hello world', $secret); $this->assertEquals('OFFLINE', $grantMode); } + + public function testGraphQLWebhookTopic(): void + { + // REST-format topics are changed to the GraphQL format + $topics = [ + 'app/uninstalled' => 'APP_UNINSTALLED', + 'orders/partially_fulfilled' => 'ORDERS_PARTIALLY_FULFILLED', + 'order_transactions/create' => 'ORDER_TRANSACTIONS_CREATE', + ]; + + foreach ($topics as $restTopic => $graphQLTopic) { + $this->assertEquals( + $graphQLTopic, + Util::getGraphQLWebhookTopic($restTopic) + ); + } + + // GraphQL-format topics are unchanged + $this->assertEquals( + 'ORDERS_PARTIALLY_FULFILLED', + Util::getGraphQLWebhookTopic('ORDERS_PARTIALLY_FULFILLED') + ); + } }