All use cases
Build this app

An e-commerce storefront

Catalog, cart and Stripe checkout, with the product data owned by your app instead of the payment processor.

The problem

The tempting shortcut is to let Stripe hold the products. It works until you need a category page, a search filter, stock, or a price that depends on who is looking — none of which the payment processor knows about. Now every page load is an API call, and the catalog you actually query is a cache you have to keep honest.

The other half is the storefront itself. A product grid, a cart and a checkout are not hard, but they are the surfaces customers judge you on, and they are usually where a project quietly stops using its design system.

What you are building

Live surfaces, not screenshots — every one composed from the same components you would install. The catalog lives in your database and syncs outward to Stripe. The storefront is composed from react-fancy, so it inherits the same tokens as the rest of the app.

Storefront

A product grid over your own catalog — prices live in your database, synced to Stripe.

Shop

Field Notebook — A5

$18

Bestseller

Machined Pen, Brass

$42

Canvas Roll Case

$65

Low stock

Refill Pack (×3)

$9

Cart and checkout

The checkout session comes from the catalog facade, so no Stripe-specific code reaches your controllers.

Checkout
Field Notebook — A5$18
Machined Pen, Brass$42
Total$60

Checkout session created from your own catalog

The code

Query your own catalog, not an APIphp
// Products are local models, so the storefront filters, sorts and paginates in
// SQL -- no network call on page load, and no second copy of the prices.
public function index(Request $request): Response
{
    return Inertia::render('Shop/Index', [
        'products' => Product::query()
            ->where('active', true)
            ->when($request->string('q')->toString(), fn ($q, $term) =>
                $q->where('name', 'like', "%{$term}%"))
            ->with('prices')
            ->paginate(24),
    ]);
}
One checkout call for subscriptions and one-offsphp
use LaravelCatalog\Facades\Catalog;

// The same facade covers both modes, so the cart carries no Stripe-specific
// branching into your controllers.
$checkout = Catalog::createCheckoutSession($request->user(), [
    'price'       => $price->stripe_price_id,
    'mode'        => $price->isRecurring() ? 'subscription' : 'payment',
    'success_url' => route('checkout.success'),
    'cancel_url'  => route('shop.index'),
]);

return redirect($checkout->url);
A product page a crawler can readtsx
// fancy-seo server-renders the head and emits Product JSON-LD, so the listing
// arrives complete in the first byte rather than after hydration. A storefront
// that only assembles itself in the browser is one search engines see as empty.
<Seo
  title={product.name}
  description={product.description}
  jsonLd={{
    "@context": "https://schema.org",
    "@type": "Product",
    name: product.name,
    offers: {
      "@type": "Offer",
      price: (product.price.amount / 100).toFixed(2),
      priceCurrency: product.price.currency.toUpperCase(),
      availability: product.in_stock
        ? "https://schema.org/InStock"
        : "https://schema.org/OutOfStock",
    },
  }}
/>

How to solve it

  1. Install the catalog

    Products and prices become models in your own database, synced outward to Stripe rather than read back from it.

    Run thisbash
    composer require particle-academy/laravel-catalog
  2. Build the grid from your own query

    Because the catalog is local, filtering and pagination are ordinary SQL. This is the decision that keeps category pages fast.

  3. Send the customer to checkout

    One facade call covers subscriptions and one-off payments, so the cart does not branch on payment mode.

  4. Make product pages crawlable

    Server-rendered head plus Product JSON-LD, with price and availability that match what checkout will actually charge.