PHPackages                             pictastudio/venditio - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. pictastudio/venditio

ActiveLibrary[Framework](/categories/framework)

pictastudio/venditio
====================

ecommerce package

v2.10.4(3d ago)1112[3 PRs](https://github.com/pictastudio/venditio/pulls)MITPHPPHP ^8.4CI passing

Since Mar 12Pushed 1mo ago2 watchersCompare

[ Source](https://github.com/pictastudio/venditio)[ Packagist](https://packagist.org/packages/pictastudio/venditio)[ Docs](https://github.com/pictastudio/venditio)[ GitHub Sponsors]()[ RSS](/packages/pictastudio-venditio/feed)WikiDiscussions main Synced today

READMEChangelog (10)Dependencies (91)Versions (73)Used By (0)

Venditio Ecommerce
==================

[](#venditio-ecommerce)

[![Latest Version on Packagist](https://camo.githubusercontent.com/dba789aa33aab384b3b577339fd70b4df908e38b57f3d2424bc482713a8fb1b6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f706963746173747564696f2f76656e646974696f2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/pictastudio/venditio)[![GitHub Tests Action Status](https://camo.githubusercontent.com/c462e395dedcf47698f588bc0d7f5f5bd8fab27f6ca25ce3b3f3ae45909252f4/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f706963746173747564696f2f76656e646974696f2f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/pictastudio/venditio/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/782b71605f34c3754a1821b95f0487b1bc68f5ab2206d333362d54df75775269/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f706963746173747564696f2f76656e646974696f2f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/pictastudio/venditio/actions?query=workflow%3A)[![Total Downloads](https://camo.githubusercontent.com/2c6c0db2ee70bee1598381509b219fcd0763c4787b8bbfc0504005e248c623f4/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f706963746173747564696f2f76656e646974696f2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/pictastudio/venditio)

Venditio is a headless ecommerce package for Laravel. It provides API-only ecommerce primitives while host applications own auth, frontend, and rendering. Products can be organized through brands, categories, tags, and flat product collections. Orders can also expose configurable return reasons and return requests with per-line derived return state.

Installation
------------

[](#installation)

```
composer require pictastudio/venditio
```

### Install Venditio

[](#install-venditio)

```
php artisan venditio:install
```

Documentation
-------------

[](#documentation)

- Architecture: `docs/ARCHITECTURE.md`
- API reference: `docs/API.md`
- Database schema (DBML): `database.dbml`

Product Variants Model
----------------------

[](#product-variants-model)

Venditio models variants using a parent/child product strategy:

- A base product is a row in `products` with `parent_id = null`
- Each purchasable variant is another row in `products` with `parent_id` set to the base product id
- Variant axes live in `product_variants` (for example `Color`, `Size`)
- Axis values live in `product_variant_options` (for example `Red`, `M`)
- Assigned option values for each variant product are stored in `product_configuration`

This keeps a single product identity while still allowing independent SKU/inventory/pricing per concrete variant.

Configuration
-------------

[](#configuration)

All behavior is configured through `config/venditio.php`.

### Key sections

[](#key-sections)

- `routes.api`: route enable/prefix/name/middleware/pagination and resource wrapping
- `models`: model overrides (all package models are replaceable)
- `validations`: validation contract to implementation bindings
- `authorize_using_policies`: optional policy/gate authorization
- `price_lists`: optional multi-price feature
- `discounts`: discount calculator/bindings/rules configuration
- `shipping`: shipping strategy, default volumetric divisor, and resolver bindings
- `product`: product enums, sku generator and product list variant visibility defaults
- `product_variants`: variant naming/copy behavior
- `invoices`: optional persisted invoice generation and swappable PDF pipeline

### User model and auth integration

[](#user-model-and-auth-integration)

Authentication is not enforced by default. If your host app uses Sanctum, add `HasApiTokens` to your user model and point the package user model config to it:

```
namespace App\Models;

use Laravel\Sanctum\HasApiTokens;
use PictaStudio\Venditio\Models\User as VenditioUser;

class User extends VenditioUser
{
    use HasApiTokens;
}
```

```
'models' => [
    // ...
    'user' => App\Models\User::class,
],
```

### Optional policy integration

[](#optional-policy-integration)

Register policies in the host app and keep `venditio.authorize_using_policies` enabled:

```
use App\Models\Product;
use App\Policies\ProductPolicy;
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    Gate::policy(Product::class, ProductPolicy::class);
}
```

Controllers call authorization only when enabled and when a policy/gate definition exists.

### Validation customization

[](#validation-customization)

Validation rules are resolved from contracts in `config('venditio.validations')`. Override a resource by rebinding its contract to your implementation.

```
use App\Validations\AddressValidation;
use PictaStudio\Venditio\Validations\Contracts\AddressValidationRules;

public function boot(): void
{
    $this->app->singleton(AddressValidationRules::class, AddressValidation::class);
}
```

### Identifier generator customization

[](#identifier-generator-customization)

```
use PictaStudio\Venditio\Contracts\CartIdentifierGeneratorInterface;
use PictaStudio\Venditio\Contracts\InvoiceNumberGeneratorInterface;
use PictaStudio\Venditio\Contracts\OrderIdentifierGeneratorInterface;

$this->app->singleton(CartIdentifierGeneratorInterface::class, App\Generators\CartIdentifierGenerator::class);
$this->app->singleton(InvoiceNumberGeneratorInterface::class, App\Generators\InvoiceNumberGenerator::class);
$this->app->singleton(OrderIdentifierGeneratorInterface::class, App\Generators\OrderIdentifierGenerator::class);
```

Invoices
--------

[](#invoices)

Venditio can persist one immutable invoice document per order and render a PDF from the stored snapshot. The feature is disabled by default and stays API-only: host apps decide when to create an invoice and can replace the default number generator, payload factory, HTML template, or PDF renderer.

Enable it in `config/venditio.php`:

```
'invoices' => [
    'enabled' => true,
    'seller' => [
        'name' => 'Acme SRL',
        'address_line_1' => 'Via Roma 1',
        'city' => 'Verona',
        'postal_code' => '37100',
        'country' => 'Italy',
    ],
],
```

Default endpoints:

- `POST /orders/{order}/invoice`
- `GET /orders/{order}/invoice`
- `GET /orders/{order}/invoice/pdf`

The generated invoice record stores seller data, billing/shipping addresses, lines, totals, payments, and rendered HTML so later order edits do not rewrite already issued documents.

### Invoice customization

[](#invoice-customization)

```
'invoices' => [
    'number_generator' => App\Invoices\CustomInvoiceNumberGenerator::class,
    'payload_factory' => App\Invoices\CustomInvoicePayloadFactory::class,
    'template' => App\Invoices\CustomInvoiceTemplate::class,
    'renderer' => App\Invoices\CustomInvoicePdfRenderer::class,
],
```

Relevant contracts:

- `PictaStudio\Venditio\Contracts\InvoiceNumberGeneratorInterface`
- `PictaStudio\Venditio\Contracts\InvoicePayloadFactoryInterface`
- `PictaStudio\Venditio\Contracts\InvoiceTemplateInterface`
- `PictaStudio\Venditio\Contracts\InvoicePdfRendererInterface`

Shipping
--------

[](#shipping)

Venditio ships with an API-first shipping domain built around three resources:

- `shipping_methods`: couriers or delivery methods, with `flat_fee` and `volumetric_divisor`
- `shipping_zones`: geographic scopes, linked to countries, regions, and provinces
- `shipping_method_zones`: the priced pivot between a method and a zone, with `rate_tiers` and `over_weight_price_per_kg`

Shipping behavior is controlled by `venditio.shipping.strategy`:

- `disabled`: shipping fee is always `0`, but weights are still calculated
- `flat`: the cart uses `shipping_methods.flat_fee`
- `zones`: the cart resolves the best matching zone for the selected method and calculates the fee from the pivot row

The default volumetric divisor is `5000`, configurable through `venditio.shipping.default_volumetric_divisor`. Each shipping method can override it with its own `volumetric_divisor`, so different couriers can use different volumetric rules.

### How shipping is calculated

[](#how-shipping-is-calculated)

On cart create and update, Venditio calculates shipping after line totals and before cart-level discounts.

1. The cart resolves the selected `shipping_method_id`.
2. It calculates the line weights from `cart.lines[*].product_data`.
3. It resolves the destination from `addresses.shipping`.
4. If the strategy is `zones`, it finds the best active zone linked to the selected shipping method.
5. It calculates the shipping fee.
6. Discounts run after that, so `free_shipping` can still zero the final `shipping_fee`.
7. When an order is created from a cart, Venditio snapshots `shipping_method_id`, `shipping_zone_id`, weights, fee, `shipping_method_data`, and `shipping_zone_data` on the order.

Weight calculation uses these formulas:

- `specific_weight = sum(product_data.weight * qty)`
- `volumetric_weight = sum((length * width * height / divisor) * qty)`
- `chargeable_weight = max(specific_weight, volumetric_weight)`

Expected units in the default implementation:

- `weight` in `kg`
- `length`, `width`, `height` in `cm`

Destination matching works by specificity:

- province match wins over region match
- region match wins over country match
- if two zones have the same specificity, the highest `shipping_zones.priority` wins
- if priority is also equal, the lowest id wins

The destination is resolved in this order:

- use `addresses.shipping.province_id` when present
- otherwise try `addresses.shipping.state` as a province code
- use `addresses.shipping.country_id` as the country-level fallback

### Practical examples

[](#practical-examples)

#### 1. Flat shipping

[](#1-flat-shipping)

If the host app sets:

```
'shipping' => [
    'strategy' => 'flat',
],
```

and creates a method like:

```
{
  "code": "express",
  "name": "Express Courier",
  "active": true,
  "flat_fee": 9.90,
  "volumetric_divisor": 5000
}
```

then a cart created with that method:

```
{
  "shipping_method_id": 1,
  "addresses": {
    "billing": { "country_id": 1 },
    "shipping": { "country_id": 1 }
  },
  "lines": [
    { "product_id": 10, "qty": 2 }
  ]
}
```

will use `shipping_fee = 9.90` regardless of zone matching. Weights are still calculated and exposed on the cart response.

#### 2. Province overrides region and country

[](#2-province-overrides-region-and-country)

Suppose one courier is linked to three active zones:

- `Italy` zone with `country_ids` containing the Italy country id, priced at `7.00` up to `5kg`
- `Lazio` zone with `region_ids` containing the Lazio region id, priced at `9.00` up to `5kg`
- `Rome` zone with `province_ids` containing the Rome province id, priced at `12.00` up to `5kg`

For a shipping address in Rome province, Venditio picks the province zone and charges `12.00`. For a shipping address in Viterbo province, Venditio falls back to the Lazio region zone and charges `9.00`. For a shipping address in Milan province, Venditio falls back to the Italy country zone and charges `7.00`.

This is true even if all three zones are linked to the same method: the most specific destination always wins.

#### 3. Different couriers can produce different volumetric fees

[](#3-different-couriers-can-produce-different-volumetric-fees)

Take the same parcel with:

- actual weight `4kg`
- dimensions `50 x 40 x 30 cm`

Courier A has `volumetric_divisor = 5000`. Courier B has `volumetric_divisor = 4000`.

That produces:

- Courier A volumetric weight: `(50 * 40 * 30) / 5000 = 12kg`
- Courier B volumetric weight: `(50 * 40 * 30) / 4000 = 15kg`

So the chargeable weight becomes:

- Courier A: `max(4, 12) = 12kg`
- Courier B: `max(4, 15) = 15kg`

If both couriers are linked to the same zone but with different pricing in `shipping_method_zones`, the final fee can differ twice:

- because the chargeable weight is different
- because each method-zone pivot can have different `rate_tiers` or `over_weight_price_per_kg`

#### 4. Incomplete destination does not block the cart

[](#4-incomplete-destination-does-not-block-the-cart)

If the cart has lines but is missing `shipping_method_id`, or the shipping address is still incomplete, Venditio does not fail the request.

Returns
-------

[](#returns)

Venditio ships with an API-first returns domain that stays aligned with the package's headless approach:

- `return_reasons`: configurable database-backed reasons exposed through CRUD APIs
- `return_requests`: order-linked return headers that snapshot `orders.addresses.billing` at creation time
- partial quantities per `order_line`, so a line with `qty > 1` can be returned incrementally
- derived fields on `order_lines` for frontend/admin use: `requested_return_qty`, `returned_qty`, `has_return_requests`, `is_returned`, `is_fully_returned`

`return_requests` do not expose `return_request_lines` as a standalone CRUD resource in v1. The nested `lines` payload is validated against the selected order, and quantities cannot exceed the remaining returnable amount for each order line. It keeps:

- `shipping_fee = 0`
- `shipping_zone_id = null`

This is useful for checkout flows where the customer adds products before choosing a courier or completing the address.

#### 5. Complete destination with no valid shipping rate returns `422`

[](#5-complete-destination-with-no-valid-shipping-rate-returns-422)

In `zones` mode, if the cart has:

- a valid `shipping_method_id`
- a complete enough destination to resolve a province or country

but the selected method is not linked to any matching active zone, Venditio returns a validation error. The same happens if a matching zone exists but its pivot has no applicable `rate_tiers` and no `over_weight_price_per_kg`.

This makes the failure machine-readable for the host app while still allowing incomplete carts to remain valid during checkout.

Commands
--------

[](#commands)

### Release stock for abandoned carts

[](#release-stock-for-abandoned-carts)

Enabled by default and configurable from:

- `venditio.commands.release_stock_for_abandoned_carts.enabled`
- `venditio.commands.release_stock_for_abandoned_carts.inactive_for_minutes`
- `venditio.commands.release_stock_for_abandoned_carts.schedule_every_minutes`

### Publish Bruno collection

[](#publish-bruno-collection)

```
php artisan vendor:publish --tag=venditio-bruno
```

High-level structure
--------------------

[](#high-level-structure)

```
src/
|--- Actions
|--- Contracts
|--- Discounts
|--- Dto
|--- Enums
|--- Http
|--- Models
|--- Pipelines
|--- Pricing
|--- Validations

```

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Picta Studio](https://github.com/pictastudio)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

52

—

FairBetter than 96% of packages

Maintenance96

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity74

Established project with proven stability

 Bus Factor1

Top contributor holds 95.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~14 days

Recently: every ~3 days

Total

59

Last Release

3d ago

Major Versions

0.x-dev → 1.x-dev2025-08-28

v1.6.2 → v2.0.02026-03-13

PHP version history (2 changes)v0.1.0PHP ^8.1

v1.0.0PHP ^8.4

### Community

Maintainers

![](https://www.gravatar.com/avatar/871c609368b67370ee8c9a0e1077d94ece3b358ee39703a5bdae118c0159f1b1?d=identicon)[pictastudio](/maintainers/pictastudio)

---

Top Contributors

[![Frameck](https://avatars.githubusercontent.com/u/77396783?v=4)](https://github.com/Frameck "Frameck (212 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (6 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (3 commits)")[![pictastudio](https://avatars.githubusercontent.com/u/160235317?v=4)](https://github.com/pictastudio "pictastudio (1 commits)")

---

Tags

laravelPicta Studiovenditio

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/pictastudio-venditio/health.svg)

```
[![Health](https://phpackages.com/badges/pictastudio-venditio/health.svg)](https://phpackages.com/packages/pictastudio-venditio)
```

###  Alternatives

[fleetbase/core-api

Core Framework and Resources for Fleetbase API

1235.9k20](/packages/fleetbase-core-api)[unopim/unopim

UnoPim Laravel PIM

10.5k2.4k](/packages/unopim-unopim)[spatie/laravel-pdf

Create PDFs in Laravel apps

1.0k4.8M47](/packages/spatie-laravel-pdf)[nasirkhan/laravel-starter

A CMS like modular Laravel starter project.

1.4k2.7k](/packages/nasirkhan-laravel-starter)[stephenjude/filament-jetstream

A Laravel starter kit built with Filament inspired by Jetstream.

17760.2k3](/packages/stephenjude-filament-jetstream)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3914.6k](/packages/rawilk-profile-filament-plugin)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
