marblecms/marble-ecommerce

E-commerce plugin for Marble CMS: products, orders, Stripe Checkout.

Maintainers

Package info

github.com/marblecms/plugin-ecommerce

Type:marble-plugin

pkg:composer/marblecms/marble-ecommerce

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-04-19 17:08 UTC

This package is auto-updated.

Last update: 2026-04-19 17:09:00 UTC


README

Full e-commerce plugin for Marble CMS: products, orders, discount codes, and Stripe Checkout — all managed from the Marble admin panel.

Requirements

  • PHP 8.3+
  • Marble CMS (marble/admin) ^1.0
  • Stripe account

Installation

composer require marblecms/marble-ecommerce
php artisan marble:ecommerce:install

Add to .env:

STRIPE_KEY=pk_live_...
STRIPE_SECRET=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
SHOP_CURRENCY=EUR
SHOP_LAYOUT=layouts.app

Register the Stripe webhook in your Stripe dashboard pointing to:

https://yourdomain.com/shop/webhook/stripe

How it works

Products

Products are standard Marble CMS Items using the product blueprint (auto-registered on install). Manage them in the content tree like any other item.

Fields registered on the product blueprint:

Field Type Description
price Textfield Decimal price, e.g. 29.99
sku Textfield Stock keeping unit
stock Textfield Available stock (informational)
description Htmlblock Rich text description (translatable)
images Images Product images
categories ObjectRelationList Linked product_category items

Fetching products in frontend templates:

// All published products
$products = Marble::items('product')->published()->get();

// Single product by slug
$product = Marble::resolve('/shop/my-product');

// Field values
$price       = $product->val('price');
$description = $product->val('description');
$images      = $product->val('images'); // array of media objects

Cart

The cart is session-based. You can interact with it via routes or the PHP service.

Via Blade forms (recommended):

{{-- Add to cart --}}
<form method="POST" action="{{ route('shop.cart.add') }}">
    @csrf
    <input type="hidden" name="item_id" value="{{ $item->id }}">
    <input type="number" name="qty" value="1" min="1">
    <button type="submit">Add to cart</button>
</form>

{{-- Update qty --}}
<form method="POST" action="{{ route('shop.cart.update') }}">
    @csrf
    <input type="hidden" name="item_id" value="{{ $item->id }}">
    <input type="number" name="qty" value="{{ $qty }}" min="0">
    <button type="submit">Update</button>
</form>

{{-- Remove --}}
<form method="POST" action="{{ route('shop.cart.remove') }}">
    @csrf
    <input type="hidden" name="item_id" value="{{ $item->id }}">
    <button type="submit">Remove</button>
</form>

Via PHP (e.g. in a custom controller):

use MarbleCms\ECommerce\Services\Cart;

$cart = app(Cart::class);

$cart->add($itemId, $qty);          // add qty
$cart->update($itemId, $qty);       // set exact qty (0 = remove)
$cart->remove($itemId);             // remove line
$cart->count();                     // total units in cart
$cart->lines();                     // array of cart lines (see below)
$cart->subtotalCents();             // subtotal in cents
$cart->clear();                     // empty the cart

Each entry in $cart->lines() contains:

[
    'item'             => Item,   // Marble Item model
    'qty'              => 2,
    'unit_price_cents' => 2999,
    'line_total_cents' => 5998,
    'label'            => 'My Product',
    'sku'              => 'ABC-123',
]

Cart count in every view:

The plugin automatically shares $shopCartCount with all views via a View composer. Use it in your layout:

<a href="{{ route('shop.cart') }}">
    Cart
    @if($shopCartCount > 0)
        ({{ $shopCartCount }})
    @endif
</a>

Mini cart Blade component

Drop <x-shop::cart /> anywhere in a layout:

{{-- Default --}}
<x-shop::cart />

{{-- Custom label and CSS class --}}
<x-shop::cart label="My Cart" class="nav-link" />

Renders a link to the cart with the item count badge. Override the template:

php artisan vendor:publish --tag=shop-views
# edit resources/views/vendor/shop/components/cart.blade.php

Checkout

The checkout flow is a single POST that redirects to Stripe Checkout. No card form needed on your end.

<form method="POST" action="{{ route('shop.checkout.start') }}">
    @csrf
    <input type="text" name="discount_code" placeholder="Discount code (optional)">
    <button type="submit">Proceed to checkout</button>
</form>

After payment Stripe redirects to:

  • Success: route('shop.checkout.success')
  • Cancelled: route('shop.checkout.cancel')

The Stripe webhook at /shop/webhook/stripe automatically marks orders as paid when checkout.session.completed fires.

Routes

Method URI Name Description
GET /shop/cart shop.cart Cart page
POST /shop/cart/add shop.cart.add Add item
POST /shop/cart/update shop.cart.update Update qty
POST /shop/cart/remove shop.cart.remove Remove item
GET /shop/cart/count shop.cart.count JSON {"count": n}
GET /shop/checkout shop.checkout Checkout summary
POST /shop/checkout/start shop.checkout.start Create Stripe session + redirect
GET /shop/checkout/success shop.checkout.success Post-payment success page
GET /shop/checkout/cancel shop.checkout.cancel Cancelled payment page
POST /shop/webhook/stripe shop.webhook.stripe Stripe webhook (no CSRF)

Customising views

Publish the frontend stub views and edit them to match your theme:

php artisan vendor:publish --tag=shop-views

This copies the following to resources/views/vendor/shop/:

shop/
  cart.blade.php
  checkout.blade.php
  success.blade.php
  cancel.blade.php
components/
  cart.blade.php

The SHOP_LAYOUT env var controls which layout these views extend (default: layouts.app).

Admin panel

After installing, a Shop menu appears in the Marble admin with:

  • Overview — order status counters + recent orders + product list
  • Orders — filterable order list, detail view, fulfill / cancel actions
  • Discount Codes — create and manage codes (percent or fixed, optional expiry and usage limits)

Configuration

Publish the config file to customise further:

php artisan vendor:publish --tag=shop-config

config/shop.php:

return [
    'stripe_key'            => env('STRIPE_KEY'),
    'stripe_secret'         => env('STRIPE_SECRET'),
    'stripe_webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
    'currency'              => env('SHOP_CURRENCY', 'EUR'),
    'layout'                => env('SHOP_LAYOUT', 'layouts.app'),
];

Events

Listen to these Laravel events in your EventServiceProvider or a service provider:

use MarbleCms\ECommerce\Events\OrderPaid;        // fired by Stripe webhook
// (planned: OrderFulfilled, OrderCancelled)

For now, hook into the order status change directly via Eloquent model observers if you need custom fulfilment logic.