Refactoring Shopify's Laravel Web App Template: Improving Best Practices And Code Efficiency
Last updated on
Hello everyone,
I hope you're all having a great day!
While building a Shopify web application using Laravel for the first time, I encountered several areas where the code deviated from Laravel's best practices. The implementation included common pitfalls, such as:
-
Fat Web Routes File: Business logic and route handling cluttering up
web.php
. - Direct Business Logic in Routes: Essential logic embedded directly in routes, leading to an unstructured and hard-to-maintain codebase.
- ENV Variables Accessed Without Configuration Resolver: Sensitive configuration data accessed improperly, lacking the necessary security and abstraction layers.
-
Service Logic in App Service Provider: Core service logic written directly into the
AppServiceProvider
, which is designed to handle application-wide settings, not heavy business logic.
These issues sparked my motivation to contribute to improving the Shopify app's Laravel template. I made several optimizations and submitted a pull request (PR) to Shopify. The PR introduces enhancements aligned with Laravel best practices, making the template more maintainable, secure, and in line with standard development practices. Whether or not Shopify decides to merge the PR, I believe these updates are essential for anyone embarking on a new Shopify app project in Laravel.
If you're planning to develop a Shopify app with Laravel, I'd recommend checking out the PR. Here's the link:
PR #524: Shopify Laravel Web App Improvements
You can also apply these changes directly by cherry-picking the diff:
1curl https://patch-diff.githubusercontent.com/raw/Shopify/shopify-app-template-php/pull/524.patch | git apply
Feel free to explore these updates, as they can streamline development and significantly improve the maintainability of your Shopify-Laravel integration.
1From 36ef3ff908b23d77fc1171b90702eae08f8a6579 Mon Sep 17 00:00:00 2001 2From: Muhammad Huzaifa <huzaifa.itgroup@gmail.com> 3Date: Tue, 8 Oct 2024 22:49:07 +0500 4Subject: [PATCH 1/2] refactor: update the code as the laravel best practices 5 6--- 7 web/app/Providers/AppServiceProvider.php | 53 +------------- 8 web/app/Providers/ShopifyServiceProvider.php | 74 ++++++++++++++++++++ 9 web/config/app.php | 1 + 10 web/config/shopify.php | 71 +++++++++++++++++-- 11 4 files changed, 141 insertions(+), 58 deletions(-) 12 create mode 100644 web/app/Providers/ShopifyServiceProvider.php 13 14diff --git a/web/app/Providers/AppServiceProvider.php b/web/app/Providers/AppServiceProvider.php 15index 5f5288fa4..857898dcc 100644 16--- a/web/app/Providers/AppServiceProvider.php 17+++ b/web/app/Providers/AppServiceProvider.php 18@@ -2,17 +2,7 @@ 19 20 namespace App\Providers; 21 22-use App\Lib\DbSessionStorage; 23-use App\Lib\Handlers\AppUninstalled; 24-use App\Lib\Handlers\Privacy\CustomersDataRequest; 25-use App\Lib\Handlers\Privacy\CustomersRedact; 26-use App\Lib\Handlers\Privacy\ShopRedact; 27 use Illuminate\Support\ServiceProvider; 28-use Illuminate\Support\Facades\URL; 29-use Shopify\Context; 30-use Shopify\ApiVersion; 31-use Shopify\Webhooks\Registry; 32-use Shopify\Webhooks\Topics; 33 34 class AppServiceProvider extends ServiceProvider 35 { 36@@ -26,49 +16,8 @@ public function register() 37 // 38 } 39 40- /** 41- * Bootstrap any application services. 42- * 43- * @return void 44- * @throws \Shopify\Exception\MissingArgumentException 45- */ 46 public function boot() 47 { 48- $host = str_replace('https://', '', env('HOST', 'not_defined')); 49- 50- $customDomain = env('SHOP_CUSTOM_DOMAIN', null); 51- Context::initialize( 52- env('SHOPIFY_API_KEY', 'not_defined'), 53- env('SHOPIFY_API_SECRET', 'not_defined'), 54- env('SCOPES', 'not_defined'), 55- $host, 56- new DbSessionStorage(), 57- ApiVersion::LATEST, 58- true, 59- false, 60- null, 61- '', 62- null, 63- (array)$customDomain, 64- ); 65- 66- URL::forceRootUrl("https://$host"); 67- URL::forceScheme('https'); 68- 69- Registry::addHandler(Topics::APP_UNINSTALLED, new AppUninstalled()); 70- 71- /* 72- * This sets up the mandatory privacy webhooks. You’ll need to fill in the endpoint to be used by your app in 73- * the “Privacy webhooks” section in the “App setup” tab, and customize the code when you store customer data 74- * in the handlers being registered below. 75- * 76- * More details can be found on shopify.dev: 77- * https://shopify.dev/docs/apps/webhooks/configuration/mandatory-webhooks 78- * 79- * Note that you'll only receive these webhooks if your app has the relevant scopes as detailed in the docs. 80- */ 81- Registry::addHandler('CUSTOMERS_DATA_REQUEST', new CustomersDataRequest()); 82- Registry::addHandler('CUSTOMERS_REDACT', new CustomersRedact()); 83- Registry::addHandler('SHOP_REDACT', new ShopRedact()); 84+ // 85 } 86 } 87diff --git a/web/app/Providers/ShopifyServiceProvider.php b/web/app/Providers/ShopifyServiceProvider.php 88new file mode 100644 89index 000000000..4a35d82a4 90--- /dev/null 91+++ b/web/app/Providers/ShopifyServiceProvider.php 92@@ -0,0 +1,74 @@ 93+<?php 94+ 95+namespace App\Providers; 96+ 97+use App\Lib\DbSessionStorage; 98+use App\Lib\Handlers\AppUninstalled; 99+use App\Lib\Handlers\Privacy\CustomersDataRequest;100+use App\Lib\Handlers\Privacy\CustomersRedact;101+use App\Lib\Handlers\Privacy\ShopRedact;102+use Illuminate\Support\Facades\URL;103+use Illuminate\Support\ServiceProvider;104+use Shopify\ApiVersion;105+use Shopify\Context;106+use Shopify\Webhooks\Registry;107+use Shopify\Webhooks\Topics;108+109+class ShopifyServiceProvider extends ServiceProvider110+{111+ /**112+ * Register services.113+ *114+ * @return void115+ */116+ public function register()117+ {118+ //119+ }120+121+ /**122+ * Bootstrap shopify service.123+ *124+ * @return void125+ *126+ * @throws \Shopify\Exception\MissingArgumentException127+ */128+ public function boot()129+ {130+ $host = str_replace('https://', '', config('shopify.host'));131+132+ Context::initialize(133+ config('shopify.api_key'),134+ config('shopify.api_secret'),135+ config('shopify.scopes'),136+ $host,137+ new DbSessionStorage(),138+ ApiVersion::LATEST,139+ true,140+ false,141+ null,142+ '',143+ null,144+ (array) config('shopify.shop_custom_domain'),145+ );146+147+ URL::forceRootUrl("https://$host");148+ URL::forceScheme('https');149+150+ Registry::addHandler(Topics::APP_UNINSTALLED, new AppUninstalled());151+152+ /*153+ * This sets up the mandatory privacy webhooks. You’ll need to fill in the endpoint to be used by your app in154+ * the “Privacy webhooks” section in the “App setup” tab, and customize the code when you store customer data155+ * in the handlers being registered below.156+ *157+ * More details can be found on shopify.dev:158+ * https://shopify.dev/docs/apps/webhooks/configuration/mandatory-webhooks159+ *160+ * Note that you'll only receive these webhooks if your app has the relevant scopes as detailed in the docs.161+ */162+ Registry::addHandler('CUSTOMERS_DATA_REQUEST', new CustomersDataRequest());163+ Registry::addHandler('CUSTOMERS_REDACT', new CustomersRedact());164+ Registry::addHandler('SHOP_REDACT', new ShopRedact());165+ }166+}167diff --git a/web/config/app.php b/web/config/app.php168index 2dc6c8fb2..6351b0e9f 100644169--- a/web/config/app.php170+++ b/web/config/app.php171@@ -174,6 +174,7 @@172 // App\Providers\BroadcastServiceProvider::class,173 App\Providers\EventServiceProvider::class,174 App\Providers\RouteServiceProvider::class,175+ App\Providers\ShopifyServiceProvider::class,176 177 ],178 179diff --git a/web/config/shopify.php b/web/config/shopify.php180index 60631da44..e5e4e4e76 100644181--- a/web/config/shopify.php182+++ b/web/config/shopify.php183@@ -4,6 +4,65 @@184 185 return [186 187+ /*188+ |--------------------------------------------------------------------------189+ | Shopify host190+ |--------------------------------------------------------------------------191+ |192+ | The URL origin where the app will be accessed when it's deployed, excluding the protocol. This will be provided by your platform.193+ | Example: my-deployed-app.fly.dev194+ |195+ | Learn more about in documentation: https://shopify.dev/docs/apps/launch/deployment/deploy-web-app/deploy-to-hosting-service#step-4-set-up-environment-variables196+ |197+ */198+ 'host' => env('HOST'),199+200+ /*201+ |--------------------------------------------------------------------------202+ | Shopify custom domain203+ |--------------------------------------------------------------------------204+ |205+ | One or more regexps to use when validating domains.206+ |207+ */208+ 'shop_custom_domain' => env('SHOP_CUSTOM_DOMAIN'),209+210+ /*211+ |--------------------------------------------------------------------------212+ | Shopify API Key213+ |--------------------------------------------------------------------------214+ |215+ | The client ID of the app, retrieved using Shopify CLI.216+ |217+ | Learn more about in documentation: https://shopify.dev/docs/apps/launch/deployment/deploy-web-app/deploy-to-hosting-service#step-4-set-up-environment-variables218+ |219+ */220+ 'api_key' => env('SHOPIFY_API_KEY'),221+222+ /*223+ |--------------------------------------------------------------------------224+ | Shopify API Secret225+ |--------------------------------------------------------------------------226+ |227+ | The client secret of the app, retrieved using Shopify CLI.228+ |229+ | Learn more about in documentation: https://shopify.dev/docs/apps/launch/deployment/deploy-web-app/deploy-to-hosting-service#step-4-set-up-environment-variables230+ |231+ */232+ 'api_secret' => env('SHOPIFY_API_SECRET'),233+234+ /*235+ |--------------------------------------------------------------------------236+ | Shopify Scopes237+ |--------------------------------------------------------------------------238+ |239+ | The app's access scopes, retrieved using Shopify CLI. This is optional if you're using Shopify-managed installation.240+ |241+ | Learn more about in documentation: https://shopify.dev/docs/apps/launch/deployment/deploy-web-app/deploy-to-hosting-service#step-4-set-up-environment-variables242+ |243+ */244+ 'scopes' => env('SCOPES'),245+246 /*247 |--------------------------------------------------------------------------248 | Shopify billing249@@ -17,14 +76,14 @@250 | Learn more about billing in our documentation: https://shopify.dev/docs/apps/billing251 |252 */253- "billing" => [254- "required" => false,255+ 'billing' => [256+ 'required' => false,257 258 // Example set of values to create a charge for $5 one time259- "chargeName" => "My Shopify App One-Time Billing",260- "amount" => 5.0,261- "currencyCode" => "USD", // Currently only supports USD262- "interval" => EnsureBilling::INTERVAL_ONE_TIME,263+ 'chargeName' => 'My Shopify App One-Time Billing',264+ 'amount' => 5.0,265+ 'currencyCode' => 'USD', // Currently only supports USD266+ 'interval' => EnsureBilling::INTERVAL_ONE_TIME,267 ],268 269 ];270 271From 84bd33f0999fb88e800349d099878ab558b5eff6 Mon Sep 17 00:00:00 2001272From: Muhammad Huzaifa <huzaifa.itgroup@gmail.com>273Date: Sun, 27 Oct 2024 23:01:14 +0500274Subject: [PATCH 2/2] refactor: organize web.php file by extracting route275 business logics to their own controllers.276 277---278 .../Controllers/Shopify/AuthController.php | 64 +++++++++279 .../Shopify/FallbackController.php | 27 ++++280 .../Controllers/Shopify/ProductController.php | 57 ++++++++281 .../Controllers/Shopify/WebhookController.php | 38 +++++282 web/app/Providers/ShopifyServiceProvider.php | 3 +283 web/routes/web.php | 132 ++----------------284 6 files changed, 199 insertions(+), 122 deletions(-)285 create mode 100644 web/app/Http/Controllers/Shopify/AuthController.php286 create mode 100644 web/app/Http/Controllers/Shopify/FallbackController.php287 create mode 100644 web/app/Http/Controllers/Shopify/ProductController.php288 create mode 100644 web/app/Http/Controllers/Shopify/WebhookController.php289 290diff --git a/web/app/Http/Controllers/Shopify/AuthController.php b/web/app/Http/Controllers/Shopify/AuthController.php291new file mode 100644292index 000000000..4a9c6b103293--- /dev/null294+++ b/web/app/Http/Controllers/Shopify/AuthController.php295@@ -0,0 +1,64 @@296+<?php297+298+namespace App\Http\Controllers\Shopify;299+300+use App\Http\Controllers\Controller;301+use App\Lib\AuthRedirection;302+use App\Lib\EnsureBilling;303+use App\Models\Session;304+use Illuminate\Http\Request;305+use Illuminate\Support\Facades\Config;306+use Illuminate\Support\Facades\Log;307+use Shopify\Auth\OAuth;308+use Shopify\Utils;309+use Shopify\Webhooks\Registry;310+use Shopify\Webhooks\Topics;311+312+/**313+ * @author Muhammad Huzaifa <me@muhammadhuzaifa.pro>314+ */315+class AuthController extends Controller316+{317+ public function index(Request $request)318+ {319+ $shop = Utils::sanitizeShopDomain($request->query('shop'));320+321+ // Delete any previously created OAuth sessions that were not completed (don't have an access token)322+ Session::where('shop', $shop)->where('access_token', null)->delete();323+324+ return AuthRedirection::redirect($request);325+ }326+327+ public function callback(Request $request)328+ {329+ $session = OAuth::callback(330+ $request->cookie(),331+ $request->query(),332+ ['App\Lib\CookieHandler', 'saveShopifyCookie'],333+ );334+335+ $host = $request->query('host');336+ $shop = Utils::sanitizeShopDomain($request->query('shop'));337+338+ $response = Registry::register('/api/webhooks', Topics::APP_UNINSTALLED, $shop, $session->getAccessToken());339+ if ($response->isSuccess()) {340+ Log::debug("Registered APP_UNINSTALLED webhook for shop $shop");341+ } else {342+ Log::error(343+ "Failed to register APP_UNINSTALLED webhook for shop $shop with response body: ".344+ print_r($response->getBody(), true)345+ );346+ }347+348+ $redirectUrl = Utils::getEmbeddedAppUrl($host);349+ if (Config::get('shopify.billing.required')) {350+ [$hasPayment, $confirmationUrl] = EnsureBilling::check($session, Config::get('shopify.billing'));351+352+ if (! $hasPayment) {353+ $redirectUrl = $confirmationUrl;354+ }355+ }356+357+ return redirect($redirectUrl);358+ }359+}360diff --git a/web/app/Http/Controllers/Shopify/FallbackController.php b/web/app/Http/Controllers/Shopify/FallbackController.php361new file mode 100644362index 000000000..b2b6193c0363--- /dev/null364+++ b/web/app/Http/Controllers/Shopify/FallbackController.php365@@ -0,0 +1,27 @@366+<?php367+368+namespace App\Http\Controllers\Shopify;369+370+use App\Http\Controllers\Controller;371+use Illuminate\Http\Request;372+use Shopify\Context;373+use Shopify\Utils;374+375+/**376+ * @author Muhammad Huzaifa <me@muhammadhuzaifa.pro>377+ */378+class FallbackController extends Controller379+{380+ public function __invoke(Request $request)381+ {382+ if (Context::$IS_EMBEDDED_APP && $request->query('embedded', false) === '1') {383+ if (env('APP_ENV') === 'production') {384+ return file_get_contents(public_path('index.html'));385+ } else {386+ return file_get_contents(base_path('frontend/index.html'));387+ }388+ }389+390+ return redirect(Utils::getEmbeddedAppUrl($request->query('host', null)).'/'.$request->path());391+ }392+}393diff --git a/web/app/Http/Controllers/Shopify/ProductController.php b/web/app/Http/Controllers/Shopify/ProductController.php394new file mode 100644395index 000000000..77146696c396--- /dev/null397+++ b/web/app/Http/Controllers/Shopify/ProductController.php398@@ -0,0 +1,57 @@399+<?php400+401+namespace App\Http\Controllers\Shopify;402+403+use App\Http\Controllers\Controller;404+use App\Lib\ProductCreator;405+use Illuminate\Http\Request;406+use Illuminate\Support\Facades\Log;407+use Shopify\Clients\Rest;408+409+/**410+ * @author Muhammad Huzaifa <me@muhammadhuzaifa.pro>411+ */412+class ProductController extends Controller413+{414+ public function count(Request $request)415+ {416+ /** @var \Shopify\Auth\Session */417+ $session = $request->get('shopifySession'); // Provided by the shopify.auth middleware, guaranteed to be active418+419+ $client = new Rest($session->getShop(), $session->getAccessToken());420+ $result = $client->get('products/count');421+422+ return response($result->getDecodedBody());423+ }424+425+ public function store(Request $request)426+ {427+ /** @var \Shopify\Auth\Session */428+ $session = $request->get('shopifySession'); // Provided by the shopify.auth middleware, guaranteed to be active429+430+ $success = $code = $error = null;431+ try {432+ ProductCreator::call($session, 5);433+ $success = true;434+ $code = 200;435+ $error = null;436+ } catch (\Exception $e) {437+ $success = false;438+439+ if ($e instanceof ShopifyProductCreatorException) {440+ $code = $e->response->getStatusCode();441+ $error = $e->response->getDecodedBody();442+ if (array_key_exists('errors', $error)) {443+ $error = $error['errors'];444+ }445+ } else {446+ $code = 500;447+ $error = $e->getMessage();448+ }449+450+ Log::error("Failed to create products: $error");451+ } finally {452+ return response()->json(['success' => $success, 'error' => $error], $code);453+ }454+ }455+}456diff --git a/web/app/Http/Controllers/Shopify/WebhookController.php b/web/app/Http/Controllers/Shopify/WebhookController.php457new file mode 100644458index 000000000..95a65ab1d459--- /dev/null460+++ b/web/app/Http/Controllers/Shopify/WebhookController.php461@@ -0,0 +1,38 @@462+<?php463+464+namespace App\Http\Controllers\Shopify;465+466+use App\Http\Controllers\Controller;467+use Illuminate\Http\Request;468+use Illuminate\Support\Facades\Log;469+use Shopify\Clients\HttpHeaders;470+use Shopify\Exception\InvalidWebhookException;471+use Shopify\Webhooks\Registry;472+473+/**474+ * @author Muhammad Huzaifa <me@muhammadhuzaifa.pro>475+ */476+class WebhookController extends Controller477+{478+ public function __invoke(Request $request)479+ {480+ try {481+ $topic = $request->header(HttpHeaders::X_SHOPIFY_TOPIC, '');482+483+ $response = Registry::process($request->header(), $request->getContent());484+ if (! $response->isSuccess()) {485+ Log::error("Failed to process '$topic' webhook: {$response->getErrorMessage()}");486+487+ return response()->json(['message' => "Failed to process '$topic' webhook"], 500);488+ }489+ } catch (InvalidWebhookException $e) {490+ Log::error("Got invalid webhook request for topic '$topic': {$e->getMessage()}");491+492+ return response()->json(['message' => "Got invalid webhook request for topic '$topic'"], 401);493+ } catch (\Exception $e) {494+ Log::error("Got an exception when handling '$topic' webhook: {$e->getMessage()}");495+496+ return response()->json(['message' => "Got an exception when handling '$topic' webhook"], 500);497+ }498+ }499+}500diff --git a/web/app/Providers/ShopifyServiceProvider.php b/web/app/Providers/ShopifyServiceProvider.php501index 4a35d82a4..5054d131c 100644502--- a/web/app/Providers/ShopifyServiceProvider.php503+++ b/web/app/Providers/ShopifyServiceProvider.php504@@ -14,6 +14,9 @@505 use Shopify\Webhooks\Registry;506 use Shopify\Webhooks\Topics;507 508+/**509+ * @author Muhammad Huzaifa <me@muhammadhuzaifa.pro>510+ */511 class ShopifyServiceProvider extends ServiceProvider512 {513 /**514diff --git a/web/routes/web.php b/web/routes/web.php515index f8e6c01b6..019137036 100644516--- a/web/routes/web.php517+++ b/web/routes/web.php518@@ -1,23 +1,10 @@519 <?php520 521-use App\Exceptions\ShopifyProductCreatorException;522-use App\Lib\AuthRedirection;523-use App\Lib\EnsureBilling;524-use App\Lib\ProductCreator;525-use App\Models\Session;526-use Illuminate\Http\Request;527-use Illuminate\Support\Facades\Config;528-use Illuminate\Support\Facades\Log;529+use App\Http\Controllers\Shopify\AuthController;530+use App\Http\Controllers\Shopify\FallbackController;531+use App\Http\Controllers\Shopify\ProductController;532+use App\Http\Controllers\Shopify\WebhookController;533 use Illuminate\Support\Facades\Route;534-use Shopify\Auth\OAuth;535-use Shopify\Auth\Session as AuthSession;536-use Shopify\Clients\HttpHeaders;537-use Shopify\Clients\Rest;538-use Shopify\Context;539-use Shopify\Exception\InvalidWebhookException;540-use Shopify\Utils;541-use Shopify\Webhooks\Registry;542-use Shopify\Webhooks\Topics;543 544 /*545 |--------------------------------------------------------------------------546@@ -33,113 +20,14 @@547 |548 */549 550-Route::fallback(function (Request $request) {551- if (Context::$IS_EMBEDDED_APP && $request->query("embedded", false) === "1") {552- if (env('APP_ENV') === 'production') {553- return file_get_contents(public_path('index.html'));554- } else {555- return file_get_contents(base_path('frontend/index.html'));556- }557- } else {558- return redirect(Utils::getEmbeddedAppUrl($request->query("host", null)) . "/" . $request->path());559- }560-})->middleware('shopify.installed');561+Route::fallback(FallbackController::class)->middleware('shopify.installed');562 563-Route::get('/api/auth', function (Request $request) {564- $shop = Utils::sanitizeShopDomain($request->query('shop'));565+Route::get('/api/auth', [AuthController::class, 'index']);566 567- // Delete any previously created OAuth sessions that were not completed (don't have an access token)568- Session::where('shop', $shop)->where('access_token', null)->delete();569+Route::get('/api/auth/callback', [AuthController::class, 'callback']);570 571- return AuthRedirection::redirect($request);572-});573+Route::get('/api/products/count', [ProductController::class, 'count'])->middleware('shopify.auth');574 575-Route::get('/api/auth/callback', function (Request $request) {576- $session = OAuth::callback(577- $request->cookie(),578- $request->query(),579- ['App\Lib\CookieHandler', 'saveShopifyCookie'],580- );581+Route::post('/api/products', [ProductController::class, 'store'])->middleware('shopify.auth');582 583- $host = $request->query('host');584- $shop = Utils::sanitizeShopDomain($request->query('shop'));585-586- $response = Registry::register('/api/webhooks', Topics::APP_UNINSTALLED, $shop, $session->getAccessToken());587- if ($response->isSuccess()) {588- Log::debug("Registered APP_UNINSTALLED webhook for shop $shop");589- } else {590- Log::error(591- "Failed to register APP_UNINSTALLED webhook for shop $shop with response body: " .592- print_r($response->getBody(), true)593- );594- }595-596- $redirectUrl = Utils::getEmbeddedAppUrl($host);597- if (Config::get('shopify.billing.required')) {598- list($hasPayment, $confirmationUrl) = EnsureBilling::check($session, Config::get('shopify.billing'));599-600- if (!$hasPayment) {601- $redirectUrl = $confirmationUrl;602- }603- }604-605- return redirect($redirectUrl);606-});607-608-Route::get('/api/products/count', function (Request $request) {609- /** @var AuthSession */610- $session = $request->get('shopifySession'); // Provided by the shopify.auth middleware, guaranteed to be active611-612- $client = new Rest($session->getShop(), $session->getAccessToken());613- $result = $client->get('products/count');614-615- return response($result->getDecodedBody());616-})->middleware('shopify.auth');617-618-Route::post('/api/products', function (Request $request) {619- /** @var AuthSession */620- $session = $request->get('shopifySession'); // Provided by the shopify.auth middleware, guaranteed to be active621-622- $success = $code = $error = null;623- try {624- ProductCreator::call($session, 5);625- $success = true;626- $code = 200;627- $error = null;628- } catch (\Exception $e) {629- $success = false;630-631- if ($e instanceof ShopifyProductCreatorException) {632- $code = $e->response->getStatusCode();633- $error = $e->response->getDecodedBody();634- if (array_key_exists("errors", $error)) {635- $error = $error["errors"];636- }637- } else {638- $code = 500;639- $error = $e->getMessage();640- }641-642- Log::error("Failed to create products: $error");643- } finally {644- return response()->json(["success" => $success, "error" => $error], $code);645- }646-})->middleware('shopify.auth');647-648-Route::post('/api/webhooks', function (Request $request) {649- try {650- $topic = $request->header(HttpHeaders::X_SHOPIFY_TOPIC, '');651-652- $response = Registry::process($request->header(), $request->getContent());653- if (!$response->isSuccess()) {654- Log::error("Failed to process '$topic' webhook: {$response->getErrorMessage()}");655- return response()->json(['message' => "Failed to process '$topic' webhook"], 500);656- }657- } catch (InvalidWebhookException $e) {658- Log::error("Got invalid webhook request for topic '$topic': {$e->getMessage()}");659- return response()->json(['message' => "Got invalid webhook request for topic '$topic'"], 401);660- } catch (\Exception $e) {661- Log::error("Got an exception when handling '$topic' webhook: {$e->getMessage()}");662- return response()->json(['message' => "Got an exception when handling '$topic' webhook"], 500);663- }664-});665+Route::post('/api/webhooks', WebhookController::class);