marblecms / marble-ecommerce
E-commerce plugin for Marble CMS: products, orders, Stripe Checkout.
Package info
github.com/marblecms/plugin-ecommerce
Type:marble-plugin
pkg:composer/marblecms/marble-ecommerce
Requires
- php: ^8.3
- marble/admin: dev-main
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.