eCommerce

Online store

MarketplaceOperationsVersion 0.5.0AGPL-3.0-or-latermanifest

What it is for ​

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.

What the package does and does not do ​

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.

Module details ​

  • Version: 0.5.0
  • Author: SpaceCorps
  • Licence: AGPL-3.0-or-later
  • Needs core: ^0.2 (core 0.2.0 is current)
  • Maturity: Beta
  • Categories: Sales

Models ​

ecommerce_category ​

A group of listings shown together in the shop

Read group: ecommerce.user. Write group: ecommerce.manager.

FieldKindData classPersonal dataGroupAgents seeDescription
nametextnonenoecommerce.userin clearCategory name shown in the shop
slugtextnonenoecommerce.userin clearURL part of the category, lowercase with dashes
descriptiontextnonenoecommerce.userin clearShort text about the category
sequencenumbernonenoecommerce.userin clearOrder among categories, lowest first
publishedboolnonenoecommerce.userin clearWhether the category is visible to shoppers

ecommerce_listing ​

A product as it appears in the online shop

Read group: ecommerce.user. Write group: ecommerce.manager.

FieldKindData classPersonal dataGroupAgents seeDescription
product_idref → productnonenoecommerce.userin clearThe product being sold online
titletextnonenoecommerce.userin clearTitle shown in the shop
slugtextnonenoecommerce.userin clearURL part of the product page, lowercase with dashes
summarytextnonenoecommerce.userin clearOne or two sentences shown on product cards
descriptionlongtextnonenoecommerce.userin clearDescription shown on the product page, in Markdown
imageimagenonenoecommerce.userin clearMain picture of the product (PNG, JPEG, WebP or GIF, up to 5 MiB; it is re-encoded and its metadata removed on upload)
image_alttextnonenoecommerce.userin clearText that describes the main picture for people who cannot see it
category_idref → ecommerce_categorynonenoecommerce.userin clearThe category the listing is shown under
pricemoneynonenoecommerce.userin clearShop price including tax (minor currency units, e.g. cents)
compare_at_pricemoneynonenoecommerce.userin clearPrevious price shown crossed out (minor currency units)
currencytextnonenoecommerce.userin clearISO 4217 currency code of the prices, for example EUR; empty means the shop currency from the instance settings
skutextnonenoecommerce.userin clearStock keeping unit shown on the product page
stock_policyselect (always_available, block_when_out, allow_backorder)nonenoecommerce.userin clearWhat happens when stock runs out: always_available, block_when_out, allow_backorder
stock_quantitynumbernonenoecommerce.userin clearUnits 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
publishedboolnonenoecommerce.userin clearWhether the listing is visible to shoppers

ecommerce_listing_image ​

An extra picture of a listing for its gallery

Read group: ecommerce.user. Write group: ecommerce.manager.

FieldKindData classPersonal dataGroupAgents seeDescription
listing_idref → ecommerce_listingnonenoecommerce.userin clearThe listing the picture belongs to; it is shown only while the listing is published
imageimagenonenoecommerce.userin clearThe picture (PNG, JPEG, WebP or GIF, up to 5 MiB)
alttextnonenoecommerce.userin clearText that describes the picture for people who cannot see it
sequencenumbernonenoecommerce.userin clearOrder in the gallery, lowest first
publishedboolnonenoecommerce.userin clearWhether the picture is shown in the shop

ecommerce_listing_translation ​

A listing translated into another language

Read group: ecommerce.user. Write group: ecommerce.manager.

FieldKindData classPersonal dataGroupAgents seeDescription
listing_idref → ecommerce_listingnonenoecommerce.userin clearThe listing this translates
langselect (en, de, fr, pl, fi, sv, es)nonenoecommerce.userin clearLanguage of the translation: en, de, fr, pl, fi, sv or es
titletextnonenoecommerce.userin clearTranslated title
summarytextnonenoecommerce.userin clearTranslated card text
descriptionlongtextnonenoecommerce.userin clearTranslated description in Markdown
publishedboolnonenoecommerce.userin clearWhether the translation is shown; it is only reachable while its listing is published

ecommerce_shipping_method ​

A way of delivering orders with its price

Read group: ecommerce.user. Write group: ecommerce.manager.

FieldKindData classPersonal dataGroupAgents seeDescription
nametextnonenoecommerce.userin clearName shown at checkout, for example Standard delivery
carriertextnonenoecommerce.userin clearCarrier that delivers, for example DHL
pricemoneynonenoecommerce.userin clearDelivery price (minor currency units)
free_overmoneynonenoecommerce.userin clearOrder total above which delivery is free (minor currency units); empty means never
delivery_daysnumbernonenoecommerce.userin clearUsual number of days until delivery
activeboolnonenoecommerce.userin clearWhether shoppers can choose it

ecommerce_discount_code ​

A code a shopper enters at checkout for a reduction

Read group: ecommerce.user. Write group: ecommerce.manager.

FieldKindData classPersonal dataGroupAgents seeDescription
codetextnonenoecommerce.userin clearThe code shoppers type, for example SPRING10
kindselect (percent, fixed)nonenoecommerce.userin clearType of reduction: percent or fixed
valuenumbernonenoecommerce.userin clearReduction: basis points for percent (1000 is 10.00 percent), minor currency units for fixed
starts_ondatenonenoecommerce.userin clearFirst day the code works (YYYY-MM-DD)
ends_ondatenonenoecommerce.userin clearLast day the code works (YYYY-MM-DD)
max_usesnumbernonenoecommerce.userin clearHow many times the code may be used in total; empty means unlimited
used_countnumbernonenoecommerce.userin clearHow 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_ordermoneynonenoecommerce.userin clearSmallest order total the code applies to (minor currency units)
per_customer_limitnumbernonenoecommerce.userin clearHow many times one customer (by email address) may use the code; empty means no limit
category_idref → ecommerce_categorynonenoecommerce.userin clearOnly listings of this category get the reduction; empty means the whole order
activeboolnonenoecommerce.userin clearWhether 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)

Read group: ecommerce.manager. Write group: ecommerce.manager.

FieldKindData classPersonal dataGroupAgents seeDescription
code_idref → ecommerce_discount_codenonenoecommerce.managerin clearThe discount code
customer_hashtextinternalyesecommerce.managermaskedHash of the customer's email address, so the per-customer limit can be checked without keeping the address here
countnumbernonenoecommerce.managerin clearUses so far

ecommerce_tax_rate ​

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

Read group: ecommerce.user. Write group: ecommerce.manager.

FieldKindData classPersonal dataGroupAgents seeDescription
nametextnonenoecommerce.userin clearName shown on the order, for example VAT
rate_bpnumbernonenoecommerce.userin clearRate in basis points: 1900 is 19.00 percent
countrytextnonenoecommerce.userin clearTwo-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_idref → ecommerce_categorynonenoecommerce.userin clearOnly listings of this category; empty means every listing (and delivery)
includedboolnonenoecommerce.userin clearWhether listing prices already include this tax (the usual way in the EU). If not, the tax is added on top at checkout
activeboolnonenoecommerce.userin clearWhether the rate is applied

ecommerce_order ​

An order placed online

Read group: ecommerce.user. Write group: ecommerce.user.

FieldKindData classPersonal dataGroupAgents seeDescription
numbertextnonenoecommerce.userin clearOrder number shown to the shopper. Free text: packages cannot generate sequences; the public checkout makes a random one
customer_emailtextinternalyesecommerce.usermaskedEmail address of the shopper
customer_nametextinternalyesecommerce.usermaskedName of the shopper
customer_idref → partynonenoecommerce.userin clearThe matching contact, once the shopper is known
shipping_addresslongtextinternalyesecommerce.usermaskedDelivery address
billing_addresslongtextinternalyesecommerce.usermaskedBilling address when different from delivery
shipping_method_idref → ecommerce_shipping_methodnonenoecommerce.userin clearThe chosen way of delivery
discount_code_idref → ecommerce_discount_codenonenoecommerce.userin clearThe discount code used, if any
discount_codetextnonenoecommerce.userin clearThe code the shopper entered, as it is shown on the order
discount_use_idtextinternalyesecommerce.usermaskedWhich per-customer use counter this order counted against, so it can be given back
countrytextinternalyesecommerce.usermaskedTwo-letter country code of the delivery address, which decides the tax rate
tax_summarylongtextnonenoecommerce.userin clearThe tax lines of the order as JSON: name, rate in basis points, whether included in the prices, amount
stock_returnedboolnonenoecommerce.userin clearSet when the units of a cancelled, expired or refunded order were put back on the listings, so it happens once
reserved_untilnumbernonenoecommerce.userin clearUnix time until which an unpaid order holds its stock; after that it is cancelled and the stock goes back
payment_methodselect (manual, stripe, fake)nonenoecommerce.userin clearHow the shopper chose to pay: manual (bank transfer or invoice), stripe (card) or fake (demo)
placed_ondatenonenoecommerce.userin clearDay the order was placed (YYYY-MM-DD)
currencytextnonenoecommerce.userin clearISO 4217 currency code, for example EUR
subtotalmoneynonenoecommerce.userin clearSum of the lines at listing prices (minor currency units, computed by the shop at checkout)
discount_totalmoneynonenoecommerce.userin clearReduction from the discount code (minor currency units)
shipping_totalmoneynonenoecommerce.userin clearDelivery charge (minor currency units)
tax_totalmoneynonenoecommerce.userin clearAll tax on the order, both the part included in the prices and the part added on top (minor currency units)
totalmoneynonenoecommerce.userin clearAmount the shopper pays: lines, minus the discount, plus delivery, plus tax that was added on top (minor currency units)
payment_referencetextfinancialyesecommerce.userwithheldReference from the payment provider; never a card number
tracking_referencetextnonenoecommerce.userin clearCarrier tracking number
access_tokentextsecretnoecommerce.userwithheldSecret link token that lets the shopper who placed a public order see its confirmation page. Nobody can read it back
stateselect (pending, paid, shipped, delivered, cancelled, refunded)nonenoecommerce.userin clearFulfilment status: pending, paid, shipped, delivered, cancelled, refunded

ecommerce_order_line ​

One product line of a web order

Read group: ecommerce.user. Write group: ecommerce.user.

FieldKindData classPersonal dataGroupAgents seeDescription
order_idref → ecommerce_ordernonenoecommerce.userin clearThe order the line belongs to
product_idref → productnonenoecommerce.userin clearThe product ordered
listing_idref → ecommerce_listingnonenoecommerce.userin clearThe shop listing the line was ordered from
descriptiontextnonenoecommerce.userin clearProduct text as it was when ordered
quantitynumbernonenoecommerce.userin clearUnits ordered
unit_pricemoneynonenoecommerce.userin clearPrice per unit including tax (minor currency units)
subtotalmoneynonenoecommerce.userin clearLine total (minor currency units, entered not computed)
discount_amountmoneynonenoecommerce.userin clearPart of the order discount that falls on this line (minor currency units)
tax_nametextnonenoecommerce.userin clearName of the tax applied to this line
tax_rate_bpnumbernonenoecommerce.userin clearTax rate of this line in basis points
tax_amountmoneynonenoecommerce.userin clearTax 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

Read group: ecommerce.user. Write group: ecommerce.manager.

FieldKindData classPersonal dataGroupAgents seeDescription
order_idref → ecommerce_ordernonenoecommerce.userin clearThe order being paid
providerselect (manual, stripe, fake)nonenoecommerce.userin clearWho takes the payment: manual (bank transfer or invoice), stripe (Stripe Checkout) or fake (local demo)
external_idtextfinancialnoecommerce.userwithheldThe provider's reference for this payment, for example a Stripe Checkout Session id. Never a card number
checkout_urltextfinancialnoecommerce.userwithheldWhere the shopper pays, kept so the payment page can send them back to it while the payment is open
stateselect (open, succeeded, failed, expired, cancelled, refunded)nonenoecommerce.userin clearPayment status: open (waiting for the shopper), succeeded, failed, expired, cancelled, refunded
amountmoneynonenoecommerce.userin clearAmount to pay, set by the server from the order (minor currency units)
currencytextnonenoecommerce.userin clearISO 4217 currency code of the amount
refund_idtextfinancialnoecommerce.userwithheldThe provider's reference for the refund, once the payment was refunded
refunded_amountmoneynonenoecommerce.userin clearAmount returned to the shopper (minor currency units)
event_idslongtextnonenoecommerce.userin clearIds of the provider events already applied, as JSON, so a repeated notification changes nothing

Workflows ​

ecommerce_order ​

ActionMovesWho may run itApproval
ecommerce_order.mark_paidstate: pending → paidecommerce.userno
ecommerce_order.shipstate: paid → shippedecommerce.userno
ecommerce_order.deliverstate: shipped → deliveredecommerce.userno
ecommerce_order.cancelstate: pending → cancelledecommerce.userno
ecommerce_order.refundstate: paid → refundedecommerce.manageryes
ecommerce_order.refund_deliveredstate: delivered → refundedecommerce.manageryes

ecommerce_payment ​

ActionMovesWho may run itApproval
ecommerce_payment.mark_failedstate: open → failedecommerce.managerno
ecommerce_payment.cancelstate: open → cancelledecommerce.managerno

Permission groups ​

Groups used by this app:

  • ecommerce.manager
  • ecommerce.user

Approval steps ​

Steps an agent can only propose:

  • ecommerce_order.refund
  • ecommerce_order.refund_delivered

What agents can and cannot do ​

An agent can read, search, create and update records of this app, and run the actions that need no approval, within the permission groups of its token.

It can only propose these steps, and a person must approve them: 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.

Personal data is masked as placeholder tokens: 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.

These fields are withheld from agents entirely: ecommerce_order.payment_reference, ecommerce_order.access_token, ecommerce_payment.external_id, ecommerce_payment.checkout_url, ecommerce_payment.refund_id.

Dependencies ​

Each dependency states the versions it accepts. Built-in apps version with the core, so a requirement on one is checked against the core version.

Installing this app from the marketplace also installs the dependencies it is missing, in one plan that you confirm once.

Install ​

Install it from the Customize app (Marketplace), or from the command line:

bash
obop modules install-from ecommerce

Needs a database and administrator rights.

Build your own ​

The module guide shows how apps like this one are written