---
title: "eCommerce"
description: "Boutique en ligne. Chaque application est un module sur un même modèle de données."
canonical: "https://obop.dev/fr/apps/ecommerce/"
lang: "fr"
---

# eCommerce

## À quoi elle sert

Store the data of an online shop: listings, shipping methods, discount codes and web orders with a fulfilment flow. Refunds need a manager and an approval. A storefront or other client reads and writes this data through the API.

## Ce que fait et ne fait pas le paquet

An online shop: categories, product listings with translations, shipping methods, discount codes, web orders with lines and a fulfilment flow of pending, paid, shipped, delivered, cancelled and refunded. When the instance runs with the public site enabled, visitors browse the published listings and place orders without an account; the server prices every order from the published listing and never trusts the browser. Orders take stock off the listing at checkout, apply discount codes and tax rates on the server, and are paid by bank transfer, by card through Stripe Checkout or in a local demo mode; the payment record, the provider reference and refunds are kept with the order. Refunds need a human approval from a manager; agents may only propose them. Customer name, email and addresses are personal data and are never public. Needs the Products package.

## Détails du module

- **Version:** `0.5.0`
- **Auteur:** SpaceCorps
- **Licence:** [AGPL-3.0-or-later](https://spdx.org/licenses/AGPL-3.0-or-later.html)
- **Noyau requis:** `^0.2` (le noyau actuel est 0.2.0)
- **Maturité:** Bêta
- **Catégories:** Ventes

## Modèles

### `ecommerce_category`

A group of listings shown together in the shop

Groupe de lecture: `ecommerce.user`. Groupe d'écriture: `ecommerce.manager`.

| Champ | Type | Classe de données | Données personnelles | Groupe | Vu par les agents | Description |
|---|---|---|---|---|---|---|
| `name` | text | `none` | non | `ecommerce.user` | en clair | Category name shown in the shop |
| `slug` | text | `none` | non | `ecommerce.user` | en clair | URL part of the category, lowercase with dashes |
| `description` | text | `none` | non | `ecommerce.user` | en clair | Short text about the category |
| `sequence` | number | `none` | non | `ecommerce.user` | en clair | Order among categories, lowest first |
| `published` | bool | `none` | non | `ecommerce.user` | en clair | Whether the category is visible to shoppers |

### `ecommerce_listing`

A product as it appears in the online shop

Groupe de lecture: `ecommerce.user`. Groupe d'écriture: `ecommerce.manager`.

| Champ | Type | Classe de données | Données personnelles | Groupe | Vu par les agents | Description |
|---|---|---|---|---|---|---|
| `product_id` | ref → product | `none` | non | `ecommerce.user` | en clair | The product being sold online |
| `title` | text | `none` | non | `ecommerce.user` | en clair | Title shown in the shop |
| `slug` | text | `none` | non | `ecommerce.user` | en clair | URL part of the product page, lowercase with dashes |
| `summary` | text | `none` | non | `ecommerce.user` | en clair | One or two sentences shown on product cards |
| `description` | longtext | `none` | non | `ecommerce.user` | en clair | Description shown on the product page, in Markdown |
| `image` | image | `none` | non | `ecommerce.user` | en clair | Main picture of the product (PNG, JPEG, WebP or GIF, up to 5 MiB; it is re-encoded and its metadata removed on upload) |
| `image_alt` | text | `none` | non | `ecommerce.user` | en clair | Text that describes the main picture for people who cannot see it |
| `category_id` | ref → ecommerce_category | `none` | non | `ecommerce.user` | en clair | The category the listing is shown under |
| `price` | money | `none` | non | `ecommerce.user` | en clair | Shop price including tax (minor currency units, e.g. cents) |
| `compare_at_price` | money | `none` | non | `ecommerce.user` | en clair | Previous price shown crossed out (minor currency units) |
| `currency` | text | `none` | non | `ecommerce.user` | en clair | ISO 4217 currency code of the prices, for example EUR; empty means the shop currency from the instance settings |
| `sku` | text | `none` | non | `ecommerce.user` | en clair | Stock keeping unit shown on the product page |
| `stock_policy` | select (always_available, block_when_out, allow_backorder) | `none` | non | `ecommerce.user` | en clair | What happens when stock runs out: always_available, block_when_out, allow_backorder |
| `stock_quantity` | number | `none` | non | `ecommerce.user` | en clair | Units on hand, used with the stock policy. Every order takes its units off at checkout, and a cancelled, expired or refunded order puts them back; correct it by hand after a stock count |
| `published` | bool | `none` | non | `ecommerce.user` | en clair | Whether the listing is visible to shoppers |

### `ecommerce_listing_image`

An extra picture of a listing for its gallery

Groupe de lecture: `ecommerce.user`. Groupe d'écriture: `ecommerce.manager`.

| Champ | Type | Classe de données | Données personnelles | Groupe | Vu par les agents | Description |
|---|---|---|---|---|---|---|
| `listing_id` | ref → ecommerce_listing | `none` | non | `ecommerce.user` | en clair | The listing the picture belongs to; it is shown only while the listing is published |
| `image` | image | `none` | non | `ecommerce.user` | en clair | The picture (PNG, JPEG, WebP or GIF, up to 5 MiB) |
| `alt` | text | `none` | non | `ecommerce.user` | en clair | Text that describes the picture for people who cannot see it |
| `sequence` | number | `none` | non | `ecommerce.user` | en clair | Order in the gallery, lowest first |
| `published` | bool | `none` | non | `ecommerce.user` | en clair | Whether the picture is shown in the shop |

### `ecommerce_listing_translation`

A listing translated into another language

Groupe de lecture: `ecommerce.user`. Groupe d'écriture: `ecommerce.manager`.

| Champ | Type | Classe de données | Données personnelles | Groupe | Vu par les agents | Description |
|---|---|---|---|---|---|---|
| `listing_id` | ref → ecommerce_listing | `none` | non | `ecommerce.user` | en clair | The listing this translates |
| `lang` | select (en, de, fr, pl, fi, sv, es) | `none` | non | `ecommerce.user` | en clair | Language of the translation: en, de, fr, pl, fi, sv or es |
| `title` | text | `none` | non | `ecommerce.user` | en clair | Translated title |
| `summary` | text | `none` | non | `ecommerce.user` | en clair | Translated card text |
| `description` | longtext | `none` | non | `ecommerce.user` | en clair | Translated description in Markdown |
| `published` | bool | `none` | non | `ecommerce.user` | en clair | Whether the translation is shown; it is only reachable while its listing is published |

### `ecommerce_shipping_method`

A way of delivering orders with its price

Groupe de lecture: `ecommerce.user`. Groupe d'écriture: `ecommerce.manager`.

| Champ | Type | Classe de données | Données personnelles | Groupe | Vu par les agents | Description |
|---|---|---|---|---|---|---|
| `name` | text | `none` | non | `ecommerce.user` | en clair | Name shown at checkout, for example Standard delivery |
| `carrier` | text | `none` | non | `ecommerce.user` | en clair | Carrier that delivers, for example DHL |
| `price` | money | `none` | non | `ecommerce.user` | en clair | Delivery price (minor currency units) |
| `free_over` | money | `none` | non | `ecommerce.user` | en clair | Order total above which delivery is free (minor currency units); empty means never |
| `delivery_days` | number | `none` | non | `ecommerce.user` | en clair | Usual number of days until delivery |
| `active` | bool | `none` | non | `ecommerce.user` | en clair | Whether shoppers can choose it |

### `ecommerce_discount_code`

A code a shopper enters at checkout for a reduction

Groupe de lecture: `ecommerce.user`. Groupe d'écriture: `ecommerce.manager`.

| Champ | Type | Classe de données | Données personnelles | Groupe | Vu par les agents | Description |
|---|---|---|---|---|---|---|
| `code` | text | `none` | non | `ecommerce.user` | en clair | The code shoppers type, for example SPRING10 |
| `kind` | select (percent, fixed) | `none` | non | `ecommerce.user` | en clair | Type of reduction: percent or fixed |
| `value` | number | `none` | non | `ecommerce.user` | en clair | Reduction: basis points for percent (1000 is 10.00 percent), minor currency units for fixed |
| `starts_on` | date | `none` | non | `ecommerce.user` | en clair | First day the code works (YYYY-MM-DD) |
| `ends_on` | date | `none` | non | `ecommerce.user` | en clair | Last day the code works (YYYY-MM-DD) |
| `max_uses` | number | `none` | non | `ecommerce.user` | en clair | How many times the code may be used in total; empty means unlimited |
| `used_count` | number | `none` | non | `ecommerce.user` | en clair | How many times it has been used. The shop counts a use when the order is placed and gives it back when the order is cancelled or never paid |
| `minimum_order` | money | `none` | non | `ecommerce.user` | en clair | Smallest order total the code applies to (minor currency units) |
| `per_customer_limit` | number | `none` | non | `ecommerce.user` | en clair | How many times one customer (by email address) may use the code; empty means no limit |
| `category_id` | ref → ecommerce_category | `none` | non | `ecommerce.user` | en clair | Only listings of this category get the reduction; empty means the whole order |
| `active` | bool | `none` | non | `ecommerce.user` | en clair | Whether the code can be used |

### `ecommerce_discount_use`

How many times one customer has used one discount code (the customer is kept as a hash, never as an email address)

Groupe de lecture: `ecommerce.manager`. Groupe d'écriture: `ecommerce.manager`.

| Champ | Type | Classe de données | Données personnelles | Groupe | Vu par les agents | Description |
|---|---|---|---|---|---|---|
| `code_id` | ref → ecommerce_discount_code | `none` | non | `ecommerce.manager` | en clair | The discount code |
| `customer_hash` | text | `internal` | oui | `ecommerce.manager` | masqué | Hash of the customer's email address, so the per-customer limit can be checked without keeping the address here |
| `count` | number | `none` | non | `ecommerce.manager` | en clair | Uses so far |

### `ecommerce_tax_rate`

A tax rate the shop applies to listings, computed on the server

Groupe de lecture: `ecommerce.user`. Groupe d'écriture: `ecommerce.manager`.

| Champ | Type | Classe de données | Données personnelles | Groupe | Vu par les agents | Description |
|---|---|---|---|---|---|---|
| `name` | text | `none` | non | `ecommerce.user` | en clair | Name shown on the order, for example VAT |
| `rate_bp` | number | `none` | non | `ecommerce.user` | en clair | Rate in basis points: 1900 is 19.00 percent |
| `country` | text | `none` | non | `ecommerce.user` | en clair | Two-letter country code (for example DE) of the delivery address this rate is for; empty means every country that has no rate of its own |
| `category_id` | ref → ecommerce_category | `none` | non | `ecommerce.user` | en clair | Only listings of this category; empty means every listing (and delivery) |
| `included` | bool | `none` | non | `ecommerce.user` | en clair | Whether listing prices already include this tax (the usual way in the EU). If not, the tax is added on top at checkout |
| `active` | bool | `none` | non | `ecommerce.user` | en clair | Whether the rate is applied |

### `ecommerce_order`

An order placed online

Groupe de lecture: `ecommerce.user`. Groupe d'écriture: `ecommerce.user`.

| Champ | Type | Classe de données | Données personnelles | Groupe | Vu par les agents | Description |
|---|---|---|---|---|---|---|
| `number` | text | `none` | non | `ecommerce.user` | en clair | Order number shown to the shopper. Free text: packages cannot generate sequences; the public checkout makes a random one |
| `customer_email` | text | `internal` | oui | `ecommerce.user` | masqué | Email address of the shopper |
| `customer_name` | text | `internal` | oui | `ecommerce.user` | masqué | Name of the shopper |
| `customer_id` | ref → party | `none` | non | `ecommerce.user` | en clair | The matching contact, once the shopper is known |
| `shipping_address` | longtext | `internal` | oui | `ecommerce.user` | masqué | Delivery address |
| `billing_address` | longtext | `internal` | oui | `ecommerce.user` | masqué | Billing address when different from delivery |
| `shipping_method_id` | ref → ecommerce_shipping_method | `none` | non | `ecommerce.user` | en clair | The chosen way of delivery |
| `discount_code_id` | ref → ecommerce_discount_code | `none` | non | `ecommerce.user` | en clair | The discount code used, if any |
| `discount_code` | text | `none` | non | `ecommerce.user` | en clair | The code the shopper entered, as it is shown on the order |
| `discount_use_id` | text | `internal` | oui | `ecommerce.user` | masqué | Which per-customer use counter this order counted against, so it can be given back |
| `country` | text | `internal` | oui | `ecommerce.user` | masqué | Two-letter country code of the delivery address, which decides the tax rate |
| `tax_summary` | longtext | `none` | non | `ecommerce.user` | en clair | The tax lines of the order as JSON: name, rate in basis points, whether included in the prices, amount |
| `stock_returned` | bool | `none` | non | `ecommerce.user` | en clair | Set when the units of a cancelled, expired or refunded order were put back on the listings, so it happens once |
| `reserved_until` | number | `none` | non | `ecommerce.user` | en clair | Unix time until which an unpaid order holds its stock; after that it is cancelled and the stock goes back |
| `payment_method` | select (manual, stripe, fake) | `none` | non | `ecommerce.user` | en clair | How the shopper chose to pay: manual (bank transfer or invoice), stripe (card) or fake (demo) |
| `placed_on` | date | `none` | non | `ecommerce.user` | en clair | Day the order was placed (YYYY-MM-DD) |
| `currency` | text | `none` | non | `ecommerce.user` | en clair | ISO 4217 currency code, for example EUR |
| `subtotal` | money | `none` | non | `ecommerce.user` | en clair | Sum of the lines at listing prices (minor currency units, computed by the shop at checkout) |
| `discount_total` | money | `none` | non | `ecommerce.user` | en clair | Reduction from the discount code (minor currency units) |
| `shipping_total` | money | `none` | non | `ecommerce.user` | en clair | Delivery charge (minor currency units) |
| `tax_total` | money | `none` | non | `ecommerce.user` | en clair | All tax on the order, both the part included in the prices and the part added on top (minor currency units) |
| `total` | money | `none` | non | `ecommerce.user` | en clair | Amount the shopper pays: lines, minus the discount, plus delivery, plus tax that was added on top (minor currency units) |
| `payment_reference` | text | `financial` | oui | `ecommerce.user` | non communiqué | Reference from the payment provider; never a card number |
| `tracking_reference` | text | `none` | non | `ecommerce.user` | en clair | Carrier tracking number |
| `access_token` | text | `secret` | non | `ecommerce.user` | non communiqué | Secret link token that lets the shopper who placed a public order see its confirmation page. Nobody can read it back |
| `state` | select (pending, paid, shipped, delivered, cancelled, refunded) | `none` | non | `ecommerce.user` | en clair | Fulfilment status: pending, paid, shipped, delivered, cancelled, refunded |

### `ecommerce_order_line`

One product line of a web order

Groupe de lecture: `ecommerce.user`. Groupe d'écriture: `ecommerce.user`.

| Champ | Type | Classe de données | Données personnelles | Groupe | Vu par les agents | Description |
|---|---|---|---|---|---|---|
| `order_id` | ref → ecommerce_order | `none` | non | `ecommerce.user` | en clair | The order the line belongs to |
| `product_id` | ref → product | `none` | non | `ecommerce.user` | en clair | The product ordered |
| `listing_id` | ref → ecommerce_listing | `none` | non | `ecommerce.user` | en clair | The shop listing the line was ordered from |
| `description` | text | `none` | non | `ecommerce.user` | en clair | Product text as it was when ordered |
| `quantity` | number | `none` | non | `ecommerce.user` | en clair | Units ordered |
| `unit_price` | money | `none` | non | `ecommerce.user` | en clair | Price per unit including tax (minor currency units) |
| `subtotal` | money | `none` | non | `ecommerce.user` | en clair | Line total (minor currency units, entered not computed) |
| `discount_amount` | money | `none` | non | `ecommerce.user` | en clair | Part of the order discount that falls on this line (minor currency units) |
| `tax_name` | text | `none` | non | `ecommerce.user` | en clair | Name of the tax applied to this line |
| `tax_rate_bp` | number | `none` | non | `ecommerce.user` | en clair | Tax rate of this line in basis points |
| `tax_amount` | money | `none` | non | `ecommerce.user` | en clair | Tax on this line, included in the prices or added on top (minor currency units) |

### `ecommerce_payment`

A payment for a web order: which provider, its reference, the amount and what became of it. The shop never sees card data

Groupe de lecture: `ecommerce.user`. Groupe d'écriture: `ecommerce.manager`.

| Champ | Type | Classe de données | Données personnelles | Groupe | Vu par les agents | Description |
|---|---|---|---|---|---|---|
| `order_id` | ref → ecommerce_order | `none` | non | `ecommerce.user` | en clair | The order being paid |
| `provider` | select (manual, stripe, fake) | `none` | non | `ecommerce.user` | en clair | Who takes the payment: manual (bank transfer or invoice), stripe (Stripe Checkout) or fake (local demo) |
| `external_id` | text | `financial` | non | `ecommerce.user` | non communiqué | The provider's reference for this payment, for example a Stripe Checkout Session id. Never a card number |
| `checkout_url` | text | `financial` | non | `ecommerce.user` | non communiqué | Where the shopper pays, kept so the payment page can send them back to it while the payment is open |
| `state` | select (open, succeeded, failed, expired, cancelled, refunded) | `none` | non | `ecommerce.user` | en clair | Payment status: open (waiting for the shopper), succeeded, failed, expired, cancelled, refunded |
| `amount` | money | `none` | non | `ecommerce.user` | en clair | Amount to pay, set by the server from the order (minor currency units) |
| `currency` | text | `none` | non | `ecommerce.user` | en clair | ISO 4217 currency code of the amount |
| `refund_id` | text | `financial` | non | `ecommerce.user` | non communiqué | The provider's reference for the refund, once the payment was refunded |
| `refunded_amount` | money | `none` | non | `ecommerce.user` | en clair | Amount returned to the shopper (minor currency units) |
| `event_ids` | longtext | `none` | non | `ecommerce.user` | en clair | Ids of the provider events already applied, as JSON, so a repeated notification changes nothing |

## Flux de travail

### `ecommerce_order`

| Action | Transition | Qui peut l'exécuter | Approbation |
|---|---|---|---|
| `ecommerce_order.mark_paid` | state: pending → paid | `ecommerce.user` | non |
| `ecommerce_order.ship` | state: paid → shipped | `ecommerce.user` | non |
| `ecommerce_order.deliver` | state: shipped → delivered | `ecommerce.user` | non |
| `ecommerce_order.cancel` | state: pending → cancelled | `ecommerce.user` | non |
| `ecommerce_order.refund` | state: paid → refunded | `ecommerce.manager` | oui |
| `ecommerce_order.refund_delivered` | state: delivered → refunded | `ecommerce.manager` | oui |

### `ecommerce_payment`

| Action | Transition | Qui peut l'exécuter | Approbation |
|---|---|---|---|
| `ecommerce_payment.mark_failed` | state: open → failed | `ecommerce.manager` | non |
| `ecommerce_payment.cancel` | state: open → cancelled | `ecommerce.manager` | non |

## Groupes de permissions

Groupes utilisés par cette application :

- `ecommerce.manager`
- `ecommerce.user`

## Étapes d'approbation

Étapes qu'un agent ne peut que proposer :

- `ecommerce_order.refund`
- `ecommerce_order.refund_delivered`

## Ce que les agents peuvent et ne peuvent pas faire

Un agent peut lire, rechercher, créer et modifier les enregistrements de cette application, et exécuter les actions qui ne requièrent pas d'approbation, dans les limites des groupes de permissions de son jeton.

Il ne peut que proposer ces étapes, qu'une personne doit approuver : `ecommerce_order.refund`, `ecommerce_order.refund_delivered`, `ecommerce_category.delete`, `ecommerce_listing.delete`, `ecommerce_listing_image.delete`, `ecommerce_listing_translation.delete`, `ecommerce_shipping_method.delete`, `ecommerce_discount_code.delete`, `ecommerce_discount_use.delete`, `ecommerce_tax_rate.delete`, `ecommerce_order.delete`, `ecommerce_order_line.delete`, `ecommerce_payment.delete`.

Les données personnelles sont masquées sous forme de jetons de substitution : `ecommerce_discount_use.customer_hash`, `ecommerce_order.customer_email`, `ecommerce_order.customer_name`, `ecommerce_order.shipping_address`, `ecommerce_order.billing_address`, `ecommerce_order.discount_use_id`, `ecommerce_order.country`.

Ces champs ne sont pas communiqués aux agents : `ecommerce_order.payment_reference`, `ecommerce_order.access_token`, `ecommerce_payment.external_id`, `ecommerce_payment.checkout_url`, `ecommerce_payment.refund_id`.

## Dépendances

Chaque dépendance indique les versions qu’elle accepte. Les applications intégrées suivent les versions du noyau : une exigence à leur égard est donc vérifiée par rapport à la version du noyau.

- [Contacts](/fr/apps/contacts/): `^0.2` (intégré)
- [Products](/fr/apps/products/): `^0.2` (intégré)

Installer cette application depuis la place de marché installe aussi les dépendances manquantes, dans un seul plan à confirmer une fois.

## Installation

Installez-la depuis l'application Customize (Place de marché), ou en ligne de commande :

```bash
obop modules install-from ecommerce
```

Nécessite une base de données et des droits d'administrateur.

## Créer la vôtre

[Le guide des modules montre comment sont écrites les applications de ce type](/fr/docs/reference/module-manifest/)
