tcgunel/ucp-laravel

Universal Commerce Protocol (UCP) for Laravel — expose your storefront to AI shopping agents.

Maintainers

Package info

github.com/tcgunel/ucp-laravel

pkg:composer/tcgunel/ucp-laravel

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.3.0 2026-05-21 06:34 UTC

This package is auto-updated.

Last update: 2026-05-21 11:16:58 UTC


README

Expose any Laravel storefront to AI shopping agents through Google's Universal Commerce Protocol (UCP).

Tests PHP Laravel License

The Universal Commerce Protocol is the open standard — announced by Google in January 2026 and developed with Shopify, Etsy, Wayfair, Target and Walmart — that lets AI agents (Google AI Mode, Gemini, and others) discover and buy from a store programmatically.

ucp-laravel makes your application UCP-native. You implement small per-capability contracts that map your products and orders onto the UCP schema; the package serves the discovery manifest, the capability endpoints, validation, and the response envelopes.

Capabilities

The package implements the full UCP 2026-04-08 shopping capability surface over the REST binding:

Capability UCP identifier Status
Discovery manifest /.well-known/ucp
Catalog — search / lookup / detail dev.ucp.shopping.catalog
Cart dev.ucp.shopping.cart
Checkout dev.ucp.shopping.checkout
Discount dev.ucp.shopping.discount
Fulfillment dev.ucp.shopping.fulfillment
Order — lookup + webhooks dev.ucp.shopping.order
Identity Linking — OAuth 2.0 dev.ucp.common.identity_linking
AP2 Mandates — agent payments dev.ucp.shopping.ap2_mandate

Security-critical pieces stay in your application, behind contracts: the package never captures payment, never implements an OAuth server, and never owns your signing keys. Checkout completion, the OAuth /oauth2/* endpoints and the ES256 crypto are delegated to provider contracts you implement.

Spec note. UCP is young and its schemas are still moving. This package mirrors the 2026-04-08 revision; treat the JSON shapes as a faithful implementation, not a frozen guarantee. Verify against ucp.dev before going to production.

Requirements

  • PHP 8.2+
  • Laravel 11, 12 or 13

Installation

Once published to Packagist:

composer require tcgunel/ucp-laravel

While the package is consumed privately across projects, add it as a path or VCS repository — see docs/installation.md.

Publish the config file (optional — the package works with sensible defaults):

php artisan vendor:publish --tag=ucp-config

The service provider is auto-discovered. The UCP routes are live immediately; each capability stays dormant — and unadvertised in the manifest — until you configure its provider.

Quick start

Each capability is one contract. Start with Catalog — it is read-only and the fastest to ship:

namespace App\Ucp;

use App\Models\Product as Listing;
use Tcgunel\Ucp\Contracts\CatalogProvider;
use Tcgunel\Ucp\DTO\Availability;
use Tcgunel\Ucp\DTO\Money;
use Tcgunel\Ucp\DTO\PriceRange;
use Tcgunel\Ucp\DTO\Product;
use Tcgunel\Ucp\DTO\RequestContext;
use Tcgunel\Ucp\DTO\SearchQuery;
use Tcgunel\Ucp\DTO\SearchResult;
use Tcgunel\Ucp\DTO\Variant;

final class EloquentCatalogProvider implements CatalogProvider
{
    public function search(SearchQuery $query): SearchResult
    {
        $listings = Listing::query()
            ->when($query->hasQuery(), fn ($q) => $q->where('name', 'like', "%{$query->query}%"))
            ->limit($query->pagination->limit)
            ->get();

        return new SearchResult(products: $listings->map($this->toUcpProduct(...))->all());
    }

    public function lookup(array $ids, RequestContext $context): array
    {
        return Listing::query()->findMany($ids)->map($this->toUcpProduct(...))->all();
    }

    public function product(string $id, array $selected, array $preferences, RequestContext $context): ?Product
    {
        $listing = Listing::query()->find($id);

        return $listing instanceof Listing ? $this->toUcpProduct($listing) : null;
    }

    private function toUcpProduct(Listing $listing): Product
    {
        $price = new Money((int) $listing->price_in_kurus, 'TRY');

        return new Product(
            id: (string) $listing->id,
            title: $listing->name,
            description: $listing->description ?? '',
            priceRange: PriceRange::uniform($price),
            variants: [
                new Variant(
                    id: (string) $listing->id,
                    title: $listing->name,
                    price: $price,
                    availability: $listing->stock > 0
                        ? Availability::inStock($listing->stock)
                        : Availability::outOfStock(),
                ),
            ],
        );
    }
}

Register it:

// config/ucp.php
'catalog_provider' => \App\Ucp\EloquentCatalogProvider::class,

Every other capability follows the same shape — implement a contract, register it under its config key. See the guides below.

Endpoints

Method & path Capability
GET /.well-known/ucp discovery manifest
GET /.well-known/oauth-authorization-server · /.well-known/oauth-protected-resource Identity Linking
POST /catalog/search · /catalog/lookup · /catalog/product Catalog
POST /carts · GET·PUT /carts/{id} · POST /carts/{id}/cancel Cart
POST /checkout-sessions · GET·PUT /checkout-sessions/{id} · POST …/complete · …/cancel Checkout
GET /orders/{id} Order

Catalog requests reject malformed input with 422. For cart, checkout and order, business outcomes return 200 with a messages[] array — the UCP convention — while only protocol-level problems use HTTP error statuses. Order-lifecycle updates are pushed to agents as signed webhooks.

How it works

   AI shopping agent
          │
          ├── GET /.well-known/ucp ──────────► ManifestController ──► ManifestBuilder
          │                                    (advertises only configured capabilities)
          │
          ├── GET /.well-known/oauth-* ───────► IdentityController ──► IdentityLinkProvider
          │
          └── POST /catalog · /carts · ───────► capability controller
              /checkout-sessions · GET /orders        (validate → DTO)
                                                       │
                                                       ▼
                              Catalog · Cart · Checkout · Order providers  ← YOUR CODE

The package owns the protocol — routing, the manifest, request validation, the ucp response envelope, the JSON schema. Your application owns the data, the OAuth server and the crypto, through small contracts. Controllers stay thin; DTOs are immutable; every capability is an interface.

Documentation

Guide Contents
Installation Composer setup, publishing, what gets registered
Configuration Every config/ucp.php key
Catalog provider The catalog contract, the DTOs, money precision
Cart & checkout providers The session contracts, lifecycle, discounts, fulfilment, payment
Order, Identity & AP2 Order webhooks, OAuth discovery, AP2 mandates
Multi-tenancy Per-tenant manifests and capabilities
Manifest reference The /.well-known/ucp JSON structure
Roadmap What's next

Multi-tenancy

The package is built for multi-tenant SaaS. Endpoint URLs in the manifest follow the request host, so per-tenant domains work with zero extra wiring. See docs/multi-tenancy.md.

Testing

composer test

The suite runs against Orchestra Testbench; no host application is required.

Code quality

Tool Command Standard
Pint composer lint / composer format Laravel preset + strict types
PHPStan (Larastan) composer analyse Level 8
PHPUnit composer test

composer check runs all three.

Contributing

Bug reports and pull requests are welcome. Please run composer check before opening a PR. See CHANGELOG.md for release history.

License

The MIT License. See LICENSE.

Credits