Go to file
Fabian @ Blax Software f10d5c2f04 feat(tax): configurable TaxService applied across every checkout path
Blax\Shop\Services\TaxService::rates($exempt, $rates=null) is one source of
truth that turns "which rate(s)" (config shop.tax.rates or an explicit list)
and "is this customer exempt" into the array Stripe expects. It fails loud
(TaxRateNotConfiguredException) when shop.tax.require is set and no rate is
configured for a non-exempt charge, instead of silently billing 0% VAT.

New config('shop.tax') block (SHOP_TAX_RATES / SHOP_TAX_REQUIRE). Unit tested.

Consumers (learn-atc #1509) delegate getApplicableTaxRates() here so no path
can apply the rate twice (Stripe rejects duplicate tax rates) or drop VAT to 0%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-21 08:23:56 +02:00
.github BF cart/pool/booking 2026-01-05 09:07:09 +01:00
config feat(tax): configurable TaxService applied across every checkout path 2026-08-21 08:23:56 +02:00
database feat(seats): assignable license seats — buy N, assign/reassign/reclaim per user 2026-07-29 10:07:58 +02:00
docs feat(subscriptions): Cashier-backed subscription lifecycle with product link + events 2026-06-02 11:40:15 +02:00
routes BF stripe api 2025-12-18 10:18:42 +01:00
src feat(tax): configurable TaxService applied across every checkout path 2026-08-21 08:23:56 +02:00
tests feat(tax): configurable TaxService applied across every checkout path 2026-08-21 08:23:56 +02:00
workbench BF self to static, which allows extendable models 2025-12-09 09:09:23 +01:00
.dockerignore init 2025-11-21 11:49:41 +01:00
.env.example I tests & structure 2025-11-29 12:05:02 +01:00
.envrc init 2025-11-21 11:49:41 +01:00
.gitattributes init 2025-11-21 11:49:41 +01:00
.gitignore A testbench, BF tests 2025-12-03 14:18:38 +01:00
README.md feat(enums): add SUBSCRIPTION product type 2026-06-03 10:37:09 +02:00
composer.json feat: Enhance traits with strict types and improve method signatures 2026-05-15 20:26:24 +02:00
phpunit.xml RI optimizations 2025-12-29 11:11:27 +01:00
pint.json init 2025-11-21 11:49:41 +01:00
shell.nix init 2025-11-21 11:49:41 +01:00
test.sh init 2025-11-21 11:49:41 +01:00
testbench.yaml init 2025-11-21 11:49:41 +01:00

README.md

Blax Software OSS

Laravel Shop

Tests Tests Count Assertions Latest Version License PHP Version

A comprehensive headless e-commerce package for Laravel with stock management, Stripe integration, and product actions.

Features

  • 🛍️ Product Management - Simple, variable, grouped, external, booking, and pool products
  • 💰 Multi-Currency Support - Handle multiple currencies with ease
  • 📦 Advanced Stock Management - Stock reservations, low stock alerts, and backorders
  • 💳 Stripe Integration - Built-in Stripe product and price synchronization
  • 🎯 Product Actions - Execute custom actions on product events (purchases, refunds)
  • 🔗 Product Relations - Related products, upsells, and cross-sells
  • 🌍 Translation Ready - Built-in meta translation support
  • 📊 Stock Logging - Complete audit trail of stock changes
  • 🎨 Headless Architecture - Perfect for API-first applications
  • Caching Support - Built-in cache management for better performance
  • 🛒 Shopping Capabilities - Built-in trait for any purchaser model
  • 🎭 Facade Support - Clean, expressive API through Shop and Cart facades
  • 👤 Guest Cart Support - Session-based carts for unauthenticated users

Installation

composer require blax-software/laravel-shop
php artisan migrate

That's it — the package's migrations are auto-loaded from vendor/ so a fresh migrate is all you need.

Optionally publish the config:

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

If you'd rather own the migrations in your own database/migrations/ directory (e.g. to customise schemas, switch ID types, etc.):

php artisan vendor:publish --tag="shop-migrations"

To stop the package from also auto-loading them, set 'run_migrations' => false in config/shop.php.

Configuration

The main configuration file is located at config/shop.php. Here you can configure:

  • Database table names
  • Caching settings
  • Stripe integration keys and settings
  • Currency settings

Quick Start

Setup Your User Model

Add the HasShoppingCapabilities trait to any model that should be able to purchase products (typically your User model):

use Blax\Shop\Traits\HasShoppingCapabilities;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use HasShoppingCapabilities;
    
    // ...existing code...
}

Creating Your First Product

Use the provided Enums to ensure type safety and consistency.

use Blax\Shop\Models\Product;
use Blax\Shop\Enums\ProductType;
use Blax\Shop\Enums\ProductStatus;
use Blax\Shop\Enums\StockType;

$product = Product::create([
    'slug' => 'amazing-t-shirt',
    'sku' => 'TSH-001',
    'type' => ProductType::SIMPLE,
    'manage_stock' => true,
    'status' => ProductStatus::PUBLISHED,
    'name' => 'Amazing T-Shirt', // Uses meta translation
    'description' => 'A comfortable cotton t-shirt',
]);

// Add Price
$product->prices()->create([
    'currency' => 'USD',
    'unit_amount' => 1999, // $19.99
    'sale_unit_amount' => 1499, // $14.99
    'is_default' => true,
]);

// Manage Stock
$product->adjustStock(StockType::INCREASE, 100); // Add 100 items to stock
$product->adjustStock(StockType::DECREASE, 10); // Remove 10 items from stock

// Reserve Stock (e.g., for a booking)
$product->adjustStock(
    StockType::CLAIMED, 
    1, 
    from: now(), 
    until: now()->addDay(), 
    note: 'Reserved for Order #123'
);

Working with Cart

use Blax\Shop\Facades\Cart;

// Add item to cart
Cart::addToCart($product, 1);

// Add item with date range (for bookings)
Cart::addToCart($product, 1, [], now(), now()->addDay());

// Checkout
$cart = Cart::getCart();
$cart->checkout(); // Creates purchases, claims stock, etc.

Advanced Usage

Pool Products

Pool products are collections of single items (e.g., "Parking Spaces" containing "Spot A1", "Spot A2").

use Blax\Shop\Models\Product;
use Blax\Shop\Enums\ProductType;

// Create the Pool Parent
$pool = Product::create([
    'type' => ProductType::POOL,
    'name' => 'Parking Spaces',
    'manage_stock' => true, // Pool manages availability
]);

// Create Single Items
$spot1 = Product::create([
    'type' => ProductType::BOOKING,
    'name' => 'Spot A1',
]);

$spot2 = Product::create([
    'type' => ProductType::BOOKING,
    'name' => 'Spot A2',
]);

// Attach Singles to Pool
$pool->attachSingleItems([$spot1->id, $spot2->id]);

Booking Products

Booking products are time-based and require from and until dates when adding to cart.

use Blax\Shop\Models\Product;
use Blax\Shop\Enums\ProductType;

$room = Product::create([
    'type' => ProductType::BOOKING,
    'name' => 'Conference Room',
    'manage_stock' => true,
]);

// Check availability
$isAvailable = $room->availableOnDate(now(), now()->addHour());

Testing

We test this package for many edge cases across every surface — products, stock, pricing strategies, cart/checkout, loan lifecycle, pool aggregation, booking, Stripe sync and the event surface — so host applications can lean on the behaviour with confidence.

Tests: 1409, Assertions: 3774

CI runs the full suite on every push (see the badge above). To run it locally:

./vendor/bin/phpunit

The tests use an in-memory SQLite database and Orchestra Testbench, so they run in roughly a minute with no external services required.

Documentation

For more detailed documentation, please refer to the docs/ directory in the repository.

License

MIT. See LICENSE.

Star History

Star History Chart