gp247/s-cart
GP247 S-Cart is an open-source ecommerce package for Laravel with a ready-to-use shopping cart, product and order management, admin dashboard, modules, themes, and SEO-friendly features for building online stores quickly.
gp247/s-cart (the successor to S-Cart) is not a cart trait you composer require into an existing app — it is the application. It ships models, routes, controllers, an admin panel, a storefront template engine, and a plugin/hook system on top of Laravel. Plan for greenfield (or a heavily isolated sub-app), not for bolting a cart onto an existing Laravel codebase.
Minimal path to a running shop:
composer create-project gp247/s-cart my-shop
cd my-shop
cp .env.example .env
php artisan key:generate
# edit DB_*, APP_URL, MAIL_* in .env
php artisan migrate --seed
php artisan storage:link
php artisan serve
Some releases add an explicit installer command (php artisan gp247:install or s-cart:install) that walks you through DB + admin credentials. The seed output prints the admin URL (/admin) and default credentials — change both before anything else touches the network.
Where to look first, in this order:
config/gp247.php (and any config/scart.php still present) — template selection, admin prefix, auth guards, extension toggles, image sizes.vendor/gp247/s-cart/src/ — Models/, Controllers/, Routes/, Library/. This is the source of truth for field names; the docs lag the code.app/GP247/Templates/ and app/GP247/Plugins/ — your extension surfaces. If app/GP247 doesn't exist yet, a template/plugin generator command in the admin creates the skeleton.First use case: create a product with one attribute set and one option, assign it to a category, configure a flat-rate shipping method and a test payment method, then place an order end-to-end as a guest. That single pass teaches you the model graph (ShopProduct → ShopProductAttribute/ShopProductOption → ShopOrder → ShopOrderDetail) faster than reading the docs, and it surfaces the two things that break first: image storage permissions and a missing store/currency setting.
Never edit vendor/ or core views. Every customization goes through one of three channels: a template, a plugin, or a hook. Core files are overwritten on composer update, and the package's release cadence is fast.
Template pattern (storefront changes). Copy the default template out of the package into app/GP247/Templates/<YourTemplate>/ with the expected structure (Provider.php, config.php, Route.php, views/, Lang/), register it in config, then override only the Blade views you need. Everything else falls through to the parent template. This is the correct place for layout, CSS/JS bundles, and checkout markup changes.
Plugin pattern (features). Same skeleton, different directory:
app/GP247/Plugins/MyPlugin/
├── config.php # name, version, enable flag, admin menu entries
├── Provider.php # service provider: routes, views, hooks, migrations
├── Route.php # web/admin routes
├── views/
└── Lang/
Plugins are activated from the admin extension screen; the enable state is persisted, so a plugin can ship disabled and be toggled per environment. Use plugins for payment/shipping gateways, ERP sync, loyalty points, custom report screens — anything with its own routes and settings.
Hooks/filters (cross-cutting changes). The extension system is action + filter based, invoked through helper functions (sc_... in older 3.x/4.x lines, gp247_... in the current line — check vendor/gp247/*/src/Library/ for the actual names in your version). Register in your plugin provider:
// pseudo-signature — confirm helper names in your version
gp247_process_hook_action('order.success', function ($order) {
// push to ERP, increment loyalty, fire webhook
});
gp247_process_hook_filter('product.price.display', function ($price, $product) {
return $product->is_promotion ? $price * 0.9 : $price;
});
This is the cleanest way to add post-purchase side effects without wrapping core controllers. Status-change and checkout-step hooks are the two you'll use most.
Cart flow. The cart is a session-backed library exposed as a facade (gloudemans/darryldecode-style API). Typical day-to-day:
Cart::add([
'id' => $product->id,
'name' => $product->name,
'qty' => $request->qty,
'price' => $product->price,
'options' => ['color' => $request->color, 'size' => $request->size],
]);
$rows = Cart::content();
$total = Cart::total();
Line items are keyed by product id + options hash, so two rows of the same product with different options coexist correctly. Validate stock and price at checkout, not at add-time — the session cart can outlive a price change.
Order creation is the seam. Core creates ShopOrder + ShopOrderDetail inside the checkout controller; you generally should not reimplement it. Instead hook the status transition (pending → processing → completed) and do stock decrement, email, invoice PDF, and external sync there. Custom endpoints that create orders directly tend to miss stock reservation and coupon accounting.
Custom routes over overridden controllers. Define your own routes in a plugin, resolve core models, and reuse the cart library. A 30-line controller in a plugin beats a 400-line copy of a core controller you now have to diff on every release.
Multi-language / multi-store / multi-vendor. All three are switches in config, and each adds a scoping dimension to queries. When writing custom queries against core models, apply the same scoping the core does (store id, vendor id, language) or you'll leak data across tenants. Translation strings live in each package's Lang/ directory — add your own in the plugin rather than editing core lang files.
gp247/* set (gp247/core, front, shop, plus the template) together. Mixing a new core with an old template is the most common source of blank pages and missing hooks.APP_DEBUG=true plus php artisan view:clear resolves 80% of these.php artisan config:clear && php artisan view:clear && php artisan cache:clear. Plugin enable state is often cached separately from Laravel's config cache.storage/app/public; image resizing goes through Intervention Image, so verify GD (or Imagick) is compiled in. "Image uploads but shows broken" is nearly always the symlink.RefreshDatabase in tests leaves you with a shop that can't check out — run the seeders or import a fixture./admin + seeded login is public knowledge.local.app/GP247 tree in git so upgrades only ever touch composer.lock.How can I help you explore Laravel packages today?