kyon147/laravel-shopify

repository·master·Indexed 19 days ago

https://github.com/kyon147/laravel-shopify

A full-featured Laravel package for Shopify App development. It provides tools for authentication, shop management, and API interaction, including support for expiring offline access tokens, billing management via the billable middleware, and HMAC verification for webhooks. The package includes built-in middleware for scope verification and app proxies, as well as CLI commands for migrating and renewing shop tokens.

Tokens
12.6K
Snippets
40
Records
62
Agent score
67%

What's inside kyon147/laravel-shopify

  1. Handle token refresh in long-running jobs

    master

    The API client is memoized on the shop model. In long-running processes where a single shop instance might persist across a token expiry event, you must ensure the client is updated.

    You can do this by:

    1. Enabling refresh_offline_token_before_api_call in your configuration.
    2. Manually calling $shop->refreshOfflineAccessTokenIfNeeded() or $shop->resetApiClient() before making subsequent API calls.

    If a refresh fails, the package throws Osiset\ShopifyApp\Exceptions\OAuthTokenRefreshException. You should catch and log this exception to identify shops that require manual re-authentication.

  2. Extend billing functionality without modifying vendor code

    master

    When you need custom UX or specialized billing logic, do not edit the files in the vendor directory. Instead, follow these patterns:

    1. Controllers: The package provides BillingController and Traits/BillingController. You should extend or wrap these within your own App\Http\Controllers to maintain the package's core charge flow while implementing your custom user experience.
    2. API Logic: Billing API calls for plans and charges are typically handled via the shop model's integration with apiHelper(). For implementation details on how storage and API calls interact, refer to src/Services/ApiHelper.php and the models in src/Storage/Models/ within the vendor directory, but implement your specific business logic within your own application namespaces.
  3. Install the Laravel Shopify package

    master

    To install the package in your Laravel project, use Composer to require the package and then publish the configuration file to your application.

    composer require kyon147/laravel-shopify
    php artisan vendor:publish --tag=shopify-config
  4. Listen for package webhook events

    master

    Instead of mapping a job class directly in the webhooks config, you can listen for package-emitted events. Add event classes (such as AppUninstalledEvent) to the listen array in config/shopify-app.php. This allows you to dispatch your own listeners or jobs in response to these events.

    // Example in config/shopify-app.php
    'listen' => [
        Osiset\ShopifyApp\Events\AppUninstalledEvent::class => [
            App\Listeners\CleanupShopData::class,
        ],
    ],
  5. Migrate existing shops to expiring offline tokens

    master

    If you are enabling expiring tokens for an existing installation, you have three migration options:

    1. Passive Migration (Default)

    Set SHOPIFY_AUTO_MIGRATE_LEGACY=true in your environment. The first time apiHelper() is called for a legacy shop, the package will attempt a synchronous token exchange. If it fails, it logs the error and continues using the legacy token (fail-open).

    2. Batch CLI Migration

    Run the following command to chunk shops and dispatch MigrateShopTokenJob to your queue. This is safe for serverless environments like Laravel Vapor because it performs no HTTP requests in the command itself.

    php artisan shopify-app:migrate-expiring-offline-tokens [--dry-run] [--shop=example.myshopify.com] [--queue=] [--connection=]

    Use SHOPIFY_MIGRATE_OFFLINE_TOKENS_JOB_QUEUE or SHOPIFY_MIGRATE_OFFLINE_TOKENS_JOB_CONNECTION to target specific workers.

    3. Programmatic Migration

    Use the MigrateShopToExpiringOfflineAccessToken action per shop in your own code. It returns an array containing migrated, skipped, reason, and error keys.

    Alternatively, use the ApiHelper directly:

    ApiHelper::exchangeNonExpiringOfflineTokenForExpiring($shopDomain, $currentOfflineToken);
    // Then persist using:
    ShopCommand::setAccessToken(...);

    Note: Migration is one-way. Shopify revokes the old token upon a successful exchange.

  6. Proactively renew expiring refresh tokens

    master

    Refresh tokens expire after approximately 90 days. If a shop is dormant, its tokens may lapse. You can proactively renew tokens that are expiring within a configurable window (default is 14 days via SHOPIFY_OFFLINE_REFRESH_TOKEN_RENEWAL_DAYS).

    Batch CLI Renewal

    Run the following command to dispatch RefreshShopOfflineTokenJob to the queue:

    php artisan shopify-app:refresh-expiring-offline-tokens [--dry-run] [--shop=example.myshopify.com] [--days=14] [--queue=] [--connection=]

    Automated Scheduling

    The package does not register a schedule automatically. To automate this, add the command to your routes/console.php or Kernel.php:

    Schedule::command('shopify-app:refresh-expiring-offline-tokens')->daily();
  7. Prepare the database for expiring offline tokens

    master

    Ensure your shop table (typically users or the table returned by Osiset\ShopifyApp\Util::getShopsTable()) includes the following columns via package migrations:

    • shopify_offline_refresh_token
    • shopify_offline_access_token_expires_at
    • shopify_offline_refresh_token_expires_at

    Important: If you override the $casts property on your shop model, you must merge the package's required encrypted and datetime casts into your definition rather than replacing them, to ensure the package can correctly decrypt and parse the token metadata.

  8. Protect routes with the billable middleware

    master

    To restrict access to specific routes or controllers so they only run for shops with an active plan or valid charge (based on your package rules), apply the billable middleware.

    If the route also requires an authenticated Shopify session, you should combine billable with the verify.shopify middleware.

    Note: billable is an alias for Osiset\ShopifyApp\Http\Middleware\Billable.

    Route::middleware(['verify.shopify', 'billable'])->group(function () {
        // Routes that require an active subscription/charge
        Route::get('/premium-feature', [PremiumController::class, 'index']);
    });
  9. Proactively renew dormant shop refresh tokens

    master

    Refresh tokens expire after approximately 90 days. To prevent dormant shops from losing access, you should proactively renew them using the refresh command.

    1. Run the Refresh Command: Use the Artisan command to queue renewal jobs for shops whose refresh tokens expire within the configured offline_refresh_token_renewal_days (default 14).
    2. Schedule the Command: The package does not auto-register a schedule. You must add it to your Laravel scheduler (e.g., in routes/console.php or app/Console/Kernel.php).

    CLI Options:

    • --dry-run: Preview which shops will be queued.
    • --shop=: Target a specific shop.
    • --days=: Override the renewal threshold.
    • --queue= / --connection=: Override the target queue/connection.
    // Add this to your scheduler
    Schedule::command('shopify-app:refresh-expiring-offline-tokens')->daily();
  10. Faking Shopify and HTTP requests in tests

    master

    To ensure tests are deterministic and do not rely on external network calls, fake HTTP requests at your application's boundary.

    • Outgoing Admin API calls: Use Http::fake() to intercept and stub outgoing requests to Shopify.
    • Service Layer: Use test doubles for services that you own which interact with the package.
    • Avoid Package Fixtures: Do not rely on JSON fixtures from the kyon147/laravel-shopify internal test suite; those are intended for package development only. Instead, create your own fakes or stubs.
    • Time-sensitive tests: If you are asserting logic related to token expiry windows, use time stubbing to keep tests deterministic.
    // Example of faking an outgoing Shopify API call
    Http::fake([
        'https://your-shop.myshopify.com/admin/*' => Http::response(['data' => []], 200),
    ]);
  11. Declare Shopify webhooks in config

    master

    To register webhooks that Shopify should call, add them to the webhooks array in your published config/shopify-app.php file. Each entry requires a GraphQL-style topic and an address URL.

    You can map specific topics to custom job classes by adding a class key to the entry. Ensure the address URLs are publicly reachable (e.g., via ngrok during local development) and match your application's routes.

    // Example structure in config/shopify-app.php
    'webhooks' => [
        'APP_UNINSTALLED' => [
            'address' => 'https://your-app.com/webhooks/app-uninstalled',
            'class' => App\Jobs\HandleAppUninstalled::class,
        ],
    ],
  12. Configure expiring offline access tokens

    master

    For new public apps created on or after April 1, 2026, Shopify requires expiring offline access tokens. To enable support in this package:

    1. Run migrations: Ensure your shops table includes the following columns:
      • shopify_offline_refresh_token
      • shopify_offline_access_token_expires_at
      • shopify_offline_refresh_token_expires_at
    2. Update .env: Set SHOPIFY_EXPIRING_OFFLINE_TOKENS=true.
    3. Maintain APP_KEY: Refresh tokens are stored encrypted using Laravel's encrypter; changing the APP_KEY will prevent decryption of existing tokens.

    Token refreshing is handled automatically by Osiset\ShopifyApp\Services\ApiHelper and OfflineAccessTokenRefresher before an API session is built if the token is expired or within the configured skew.