astral-php / astral-shipping
Frais de port multi-options pour Astral : forfait, barèmes poids/pays, Colissimo, Mondial Relay, retrait boutique, API agrégateur (Sendcloud)
Requires
- php: ^8.3
- astral-php/astral-core: ^2.1
Requires (Dev)
- phpunit/phpunit: ^10.0 || ^11.0
Suggests
- ext-curl: Requis par CurlHttpClient (méthodes API comme Sendcloud)
- astral-php/astral-payment: Paiement Stripe : le montant du PaymentIntent = panier + frais de port
Provides
None
Conflicts
None
Replaces
None
README
Frais de port multi-options pour Astral : forfait, retrait boutique, barèmes pays × poids (Colissimo / Mondial Relay), API agrégateur (Sendcloud).
Statut : 0.5.3 — Colissimo + Mondial Relay prêts prod barèmes.
Philosophie Astral : explicite, sans magie, opt-in — tu déclares les méthodes actives, rien n’est découvert automatiquement.
Ce n’est pas une version 1.0 (agrégateur API) : voir Limites.
Compatibilité
| astral-core | PHP | astral-shipping |
|---|---|---|
| ^1.2 | 8.1 → 8.4 | 0.x |
Le domaine (calcul des offres) n’a aucune dépendance au core : seuls le ServiceProvider, les DAOs et la commande console en ont.
Limites (0.5.0)
| Sujet | Réalité |
|---|---|
| Colissimo / Mondial Relay | Barèmes contrat (shipping_rates / CSV) — pas d’API transporteur officielle |
| Grilles tarifaires | Non incluses (contrat ; CSV d’exemple fictif enrichi FR/BE/DE) |
| Points relais | pickup_point_id obligatoire côté serveur ; widget = guide docs/widgets-pickup.md (hors package) |
| Sendcloud | API v2 opt-in MVP — maturité cible 1.0.0 |
| Boutique | Shop profil standard = forfait + retrait ; profil Colissimo/MR = intégration shop (poids, widgets) |
Installation
composer require astral-php/astral-shipping
Développement local (workspace components-astral) :
"repositories": [{ "type": "path", "url": "../components-astral/astral-shipping", "options": { "symlink": true } }]
composer require astral-php/astral-shipping:@dev
1. ServiceProvider — config/dependencies.php
return [ // … AstralShipping\ShippingServiceProvider::class, ];
2. Migrations
Copie stubs/database/migrations/* vers database/migrations/ du projet hôte, puis :
php bin/console migrate
| Table | Rôle |
|---|---|
shipping_rates |
Barèmes : méthode × pays × tranche de poids (centimes) |
order_shipments |
Snapshot du mode de livraison de chaque commande |
3. Variables d'environnement (si sendcloud est activé)
Copier stubs/.env.shipping.example dans ton .env.
4. Configuration — config/app.php
Modèle : stubs/config/shipping.example.php.
MVP boutique (recommandé pour démarrer) :
'shipping' => [ 'methods' => ['flat_rate', 'store_pickup'], 'free_shipping_threshold_cents' => 6000, 'rates_source' => 'config', 'http_timeout' => 10, 'flat_rate' => [ 'price_cents' => 590, 'label' => 'Livraison standard', 'countries' => ['FR'], 'days' => 4, ], 'store_pickup' => ['label' => 'Retrait en boutique'], 'rates' => [], ],
Profil Colissimo + Mondial Relay (0.5.0) — activer explicitement + importer les barèmes :
'methods' => [ 'flat_rate', 'store_pickup', 'colissimo_home', 'colissimo_pickup', 'mondial_relay', ], 'rates_source' => 'database', // php bin/console shipping:rates:import … --replace
Widgets relais : docs/widgets-pickup.md. Sendcloud reste opt-in (voir stub config).
Les méthodes disponibles
| Code | Classe | Source du tarif | Point relais |
|---|---|---|---|
flat_rate |
FlatRateMethod |
Forfait (config) | Non |
store_pickup |
StorePickupMethod |
Gratuit | Non |
colissimo_home |
ColissimoMethod |
Barème shipping_rates |
Non |
colissimo_pickup |
ColissimoMethod(pickup: true) |
Barème | Oui |
mondial_relay |
MondialRelayMethod |
Barème | Oui |
mondial_relay_home |
MondialRelayMethod(relay: false) |
Barème | Non |
sendcloud |
SendcloudMethod |
API Sendcloud (N offres, cache) | Selon l'offre |
| (la tienne) | ShippingMethodInterface |
À toi de voir | — |
Pourquoi des barèmes pour Colissimo et Mondial Relay ? Parce que tes tarifs dépendent de ton contrat : on les saisit (ou importe en CSV) une fois, le calcul reste instantané, hors-ligne et testable. Voir docs/colissimo-mondial-relay.md, docs/widgets-pickup.md et (API / sandbox, hors 0.5) docs/api-sandbox-colissimo-mr.md.
Utilisation
use AstralShipping\Models\{Address, ShippingContext}; use AstralShipping\Service\ShippingService; // 1. Contexte : montants en CENTIMES, poids en GRAMMES $context = new ShippingContext( subtotalCents: 4990, weightGrams: 1200, destination: new Address('FR', '75001', 'Paris'), ); // 2. Lister les offres (triées par prix) $quotes = $shipping->quotes($context); // 3. Valider le choix du client — le prix est TOUJOURS recalculé côté serveur $selection = $shipping->select($context, $_POST['shipping_method'], $_POST['pickup_point_id'] ?? null, $_POST['pickup_point_label'] ?? null); // 4. Total à encaisser (→ astral-payment) $total = $shipping->total($context->subtotalCents, $selection); // 5. À la création de la commande : snapshot $shipmentDao->record($orderId, $selection);
Vue : stubs/views/shipping/options.php → views/partials/. Exemple de contrôleur : examples/checkout-integration.php et docs/checkout-integration.md.
Barèmes
En base (recommandé)
Import CSV :
php bin/console shipping:rates:import database/rates.csv --replace
Colonnes : method_code,country,min_weight_g,max_weight_g,price_eur,estimated_days. Voir stubs/database/rates.example.csv (valeurs fictives).
Règles : poids en grammes, bornes inclusives (0–500 puis 501–1000), pays exact prioritaire sur *, à égalité le tarif le plus bas.
En config (sans base)
'rates_source' => 'config', 'rates' => [ ['method' => 'colissimo_home', 'country' => 'FR', 'min_g' => 0, 'max_g' => 500, 'price_cents' => 495, 'days' => 2], ],
Ajouter un transporteur
Implémente ShippingMethodInterface (3 méthodes) et enregistre-la dans ton AppServiceProvider. Guide : docs/extending.md.
Principes de conception
- Explicite : méthodes déclarées une à une, pas d'auto-découverte, pas de lazy-loading.
- Centimes et grammes (
int), jamais defloatpour l'argent. - Serveur maître : le client envoie un code, jamais un prix.
- Pannes isolées : si l'API d'un transporteur tombe, ses offres disparaissent, les autres restent (erreur loguée via
Core\Logger). - Snapshot : le port d'une commande est figé dans
order_shipments. - Opt-in : le cœur d'Astral n'est pas modifié ; tout vit dans
app/+ ce package.
Structure
src/
ShippingServiceProvider.php # câblage conteneur (méthodes lues dans config)
Contract/ # ShippingMethodInterface, RateLookupInterface, HttpClientInterface
Method/ # FlatRate, StorePickup, TableRate, Colissimo, MondialRelay, Sendcloud
Rate/ArrayRateTable.php # barème en mémoire (config / tests)
Rate/CsvRateParser.php # parsing CSV (commande import + tests)
Dao/ # ShippingRateDao, OrderShipmentDao (AbstractDao)
Service/ # ShippingCalculator, ShippingService
Models/ # Address, ShippingContext, ShippingQuote, Rate, PickupPoint, ShippingSelection…
Events/ShippingMethodSelected # événement optionnel
Http/CurlHttpClient.php # client HTTP par défaut
Console/ImportRatesCommand.php
Exception/
stubs/
.env.shipping.example
config/shipping.example.php
database/migrations/
database/rates.example.csv
views/shipping/options.php
Tests
composer install
composer test
La suite couvre le calcul (forfait, seuil de gratuité, barèmes, priorité pays), l’import CSV (CsvRateParser), SendcloudMethod (client HTTP factice, cache, filtres), le calculateur (tri, doublons, isolation des pannes) et la sélection (point relais obligatoire, prix recalculé).
Licence
MIT