tallieutallieu/dry-ecommerce

E-commerce package

Maintainers

Package info

github.com/TallieuTallieu/dry-ecommerce

pkg:composer/tallieutallieu/dry-ecommerce

Transparency log

Statistics

Installs: 43

Dependents: 1

Suggesters: 0

Stars: 0

3.7.0 2026-08-26 14:15 UTC

README

An e-commerce package for dry3: a cart, a checkout, orders, stock, tax and an address book, with the parts a shop has to decide for itself left as interfaces rather than guessed at.

Full documentation is in docs/.

Requirements

PHP >= 8.4, dry ^4.0, oak ^3.0, dry-dbi ^3.0. dry-accounts ^3 is a supported pairing, not a dependency.

Install

composer require tallieutallieu/dry-ecommerce

Register the service provider — after dry-accounts', if the shop uses it:

$app->register([
    \Tnt\Ecommerce\EcommerceServiceProvider::class,
]);

$app->bootstrap();

Run the migrations:

php oak migration migrate

Configure it in config/ecommerce.php:

<?php

return [
    'payment' => \Tnt\Ecommerce\Payment\NullPayment::class,
    'user_resolver' => \Tnt\Ecommerce\Account\GuestUserResolver::class,
    'prices' => 'inclusive',
    'delivery_tax_rate' => 0,
];
Key Default Decides
payment NullPayment::class The gateway.
user_resolver GuestUserResolver::class Whether a checkout can link to an account.
prices inclusive Whether quoted prices already contain their tax.
delivery_tax_rate 0 The rate charged on fulfillment cost.

NullPayment gives everything away for free. It marks an order paid without taking any money, and it is the default. Replace it before going live.

See Installation for the whole of it, including the path-repository arrangement needed while 4.x is unreleased.

Quickstart

Make something sellable

use Tnt\Ecommerce\Contracts\BuyableInterface;

class Product extends Model implements BuyableInterface
{
    public function getId(): string { return (string) $this->id; }
    public function getTitle(): string { return $this->name; }
    public function getDescription(): string { return $this->description; }
    public function getThumbnailSource(): string { return $this->photo_url; }

    /** In CENTS. €12.50 is 1250. */
    public function getPrice(): int { return $this->price_in_cents; }
}

Stock and tax are opt-in capabilities — implement HasStockInterface, TaxableInterface, both, or neither. A buyable that implements neither is a complete buyable: always addable, and it contributes no tax.

Fill a cart

$cart = $app->get(\Tnt\Ecommerce\Contracts\CartInterface::class);

$cart->canAdd($product, 3);   // does the stock cover it? reported, not enforced
$cart->add($product, 3);
$cart->add($product, 1, ['cheese' => 'no goat']);   // per-line options: its own line

$cart->getSubTotal();         // cents
$cart->getTotal();            // cents
$cart->getTax();              // cents

Check out

$customer = new \Tnt\Ecommerce\Model\Customer();
// ... name, email, and at least one address in the book ...
$customer->save();

$customer->useAddress($billingAddress);
$customer->useAddress($shippingAddress);

$order = $cart->checkout($customer);

$order->order_id;              // '12-K4M7QX9RTB'
$order->getBillingAddress();   // a frozen copy, not the live row

Guest and account checkout are the same call. See Customer for the full worked example.

The three things that surprise people

1. Money is cents, everywhere. getPrice() returns 1250 for €12.50. A model whose column holds euros converts in getPrice() and nowhere else. Money.

2. Stock and tax are optional. BuyableInterface asks for five things and neither a stock worker nor a tax rate is among them. Buyable.

3. The package reports; the shop decides. canAdd() says whether the stock covers a quantity and add() adds regardless — refusing the sale, backordering and overselling on purpose are all real ways to run a shop. Cart.

Known gaps

  • One tax rate per line — by design. A line mixing rates is the shop's job to split into one line per rate. Options.
  • Checkout needs a web request when accounts are configured. Installation.

The full, honest list lives in docs/index.md.

Development

make test            # Pest
make phpstan         # level 9
make sync-docs       # rsync docs/ to OBSIDIAN_DOCS_PATH

Documentation

Installation Setup, start to finish
Money Cents, and the rounding rule
Buyable The contract and its two capabilities
Cart Lines, totals, checkout
Options and variants Per-line choices, in the merge key and frozen onto orders
Customer Guests, accounts, dry-accounts
Addresses The book, and what an order freezes
Orders What an order records
Fulfillment Delivery methods and their attributes
Discounts and coupons Codes and the rules behind them
Stock Counting, and running out
Tax Rates and price conventions
Payment The gateway interface
What changed from 1.x For reading older projects