Shop
The official shop plugin turns the product catalogue into a store. It ships inside the framework
(plugins/shop), so there is nothing to download.
node node_modules/nuxtcms/scripts/plugin.mjs add shop
pnpm build:cf && pnpm deploy
shop is a paid plugin: licensed domains (and the hub owner's own sites) activate it automatically
in Admin → Plugins. nuxt dev runs it without a license.
What you get
| Area | Features |
|---|---|
| Products | price, compare-at (sale) price, SKU, stock (or untracked), options / variants with their own price and stock, gallery, badge, per-product tax rate, "price on request" and catalogue-only mode |
| Storefront | prices on product cards, buy box with options and quantity, gallery, header cart button, slide-over cart, floating cart button, /cart, /checkout, /order/<number>, /order (find my order) |
| Checkout | guest checkout, delivery (flat fee, free over an amount, allowed countries) or store pickup, coupons (percent, fixed, free delivery; dates, limits, minimum), VAT included or added on top, minimum order, terms checkbox, Turnstile and rate limits |
| Payments | Stripe Checkout, PayPal, Paytrail (Finland, EUR), WeChat Pay and Alipay (mainland China, CNY), bank transfer, cash on delivery / at pickup |
| Orders | statuses pending → paid → processing → shipped → completed, cancelled, refunded; stock reserved at checkout and released on cancel / refund; sales counters; timeline; carrier + tracking number; internal notes; CSV export |
| E-mails | order received (with bank details or pickup address), paid, shipped (tracking), completed, cancelled, refunded; new-order notice to the shop owner |
| Automation | scheduled task cancels unpaid online orders after N hours; events order.created, order.paid, order.status_changed for webhooks (DingTalk, Feishu, WeCom robots…) |
| SEO | schema.org Offer (price, currency, availability) and sku in the product JSON-LD |
Shipping templates
Shop → Shipping in the admin sets delivery fees per region; without any template the flat fee and "free over" amount from the shop settings apply.
| Field | Meaning |
|---|---|
| Countries / regions | ISO codes, * = everywhere; regions (provinces, states) empty = the whole countries, e.g. "Bavaria, Berlin" |
| Fee | flat; per piece (first N pieces cost X, each further M pieces add Y); by weight (first and further weight in kilograms, a started step counts as a whole one) |
| Free delivery | over an amount (after the coupon discount) and / or from a number of pieces |
Matching: a template whose regions include the address region first, then one listing the country,
then *; equal ones by sort number. Addresses no template covers cannot choose delivery. Before
the customer enters an address the checkout estimates with the first nationwide template. Weights
come from the Shop card of each product (grams); products without a weight count as 0. Free
delivery coupons apply to every template.
Member accounts
Turn on Member accounts in the shop settings and:
- an account icon appears in the header; customers sign up, sign in (
/member/login) and reset their password (e-mailed link, valid 1 hour, unusable once the password changes). - My account (
/member): order history (each opens the order page), profile, password; Addresses (/member/addresses): add, edit, delete, choose the default (up to 20). - checkout is pre-filled with name, e-mail, phone and the default address; saved addresses can be picked, and a new one can be saved to the address book.
- with Allow guest checkout off, signing in is required to check out (guests are allowed by default).
- Shop → Members in the admin: members with order count, amount paid and last order; disable / enable accounts (disabling signs the member out at once).
Members are not admin users: they live in their own tables (shop_customers, shop_addresses)
with their own cookie and can never open the admin. Sign-up, sign-in and password reset use the
login captcha from Security and are rate limited. Orders placed while signed in are linked to
the member (shop_orders.customer_id, added automatically on existing installs); guest orders are
not attached to an account by e-mail.
Points
With member accounts on, turn on Points in the shop settings:
| Setting | Meaning |
|---|---|
| Points earned per currency unit | awarded when an order is completed, on the amount paid for goods (no shipping, no added tax); 0 = none |
| Points worth one currency unit | e.g. 100: 100 points = 1.00 |
| Points may pay up to % | e.g. 50: at most half of the goods |
| Points for signing up | given to new members |
At checkout members tick "Use points"; the quote applies their balance up to the limit and the
points are taken when the order is placed. Cancelling or refunding an order gives back the points
used and takes back the points it earned (what is left of them). Members see their balance and
history under My account; in Shop → Members a click on the points adds or removes points
(never below zero). Every change is a row in shop_points_ledger.
Stores
Add stores under Shop → Stores (name, address, opening hours, phone, notification e-mail, area served, pickup). Without stores nothing changes: pickup uses the address from the settings and orders are not assigned.
- Pickup: with "Offer pickup" on, customers choose one of the pickup stores at checkout (a choice is required); the order page and the confirmation e-mail show that store's address, hours and phone.
- Deliveries go to the store whose area covers the address, matched like shipping templates (region > country, then sort order). A store without countries is the fallback for addresses no other store covers.
- Notifications: a store with an e-mail address receives its new orders (the same message as the shop owner).
- Admin: filter orders by store (or "No store"), reassign an order in its details (noted in the timeline). Deleting a store leaves its orders unassigned.
Store stock
Shop → Store stock has one row per product (per variant for products with variants) and one column per store:
- As soon as a product or variant has stock at any store it is counted per store: its stock for sale is the sum of the enabled stores (written back to the product, so the storefront and the quantity limits work as before). The stock field of the product form no longer changes it.
- Orders take from their store; cancelling or refunding gives back to it; reassigning an order moves its stock to the new store.
- Pickup is only possible at stores holding every item (checkout greys out the others). Deliveries go to a store in the area that has the items, else to another store that has them. When no single store has everything the order is refused (order the items separately).
- Disabled stores are not counted; deleting a store deletes its stock.
- Leave every store blank to go back to the product's own stock; products never given store stock keep working as before.
Marketplace merchants
With member accounts on, turn on Marketplace in the shop settings and set the default platform commission, the days after completion before merchants are paid and the smallest payout.
- Apply: members open the merchant centre (
/merchant, linked from My account) and send their store name, contact and phone. - Approve under Shop → Merchants, or reject with a reason (the merchant can correct and send again). Each merchant may get its own commission; blank = the default.
- Assign products: click a merchant's product count and tick what it sells. Titles, descriptions and images stay in the shop admin; merchants never touch page content or uploads.
- Day to day merchants change prices, compare-at prices and stock of their products and variants (store-counted stock stays the shop's), see the orders with their items — only their lines and the delivery details, never the buyer's e-mail — and enter carrier and tracking number (noted on the order). Paid orders are e-mailed to the merchant (store e-mail, else the member's).
- Settlement: when an order is paid each merchant gets an earnings row: their sales minus the commission (rounded to the cent). It is settled into the balance the set number of days after the order is completed; cancelling or refunding cancels unsettled earnings and takes settled ones back (the balance may go negative, later earnings settle it first).
- Payouts: requested amounts leave the balance at once; after transferring, mark them paid under Payouts, or reject with a reason (the amount comes back). One open request at a time.
Coupons, points and shipping are the shop's and do not change what merchants earn; the shop's own
products (not assigned) sell as before. A suspended merchant cannot use the merchant centre, but
orders of its products still earn. Tables: shop_merchants, shop_merchant_earnings,
shop_merchant_payouts; products and order lines carry merchant_id.
Two-level affiliate program
With member accounts on, turn on Two-level affiliate program in the shop settings:
| Setting | Meaning |
|---|---|
| Level 1 commission % | on orders an affiliate brings in, of the amount paid for goods (no shipping, no added tax) |
| Level 2 commission % | paid to the affiliate's inviter; 0 = one level only |
| Affiliates need approval | applications wait for approval in the admin |
| Share link lifetime | orders within this many days of the click count (default 30) |
| Days after completion before commission is credited | default 7; a refund in this time cancels the commission |
| Smallest withdrawal | below this amount no withdrawal can be requested |
How it works:
- Members join under My account → Affiliate and get a code and a share link;
?ref=CODEworks on any page. - The browser keeps the code of the link. A visitor who signs up stays bound to that affiliate for good; guests and existing members are credited through the link while it lasts.
- When an order is paid, commission rows are created (pending): level 1 for the affiliate, level 2 for the affiliate's inviter. They are credited the set number of days after the order is completed, by the scheduled task "Shop: cancel stale unpaid orders" (and whenever the affiliate page or the admin page is opened).
- Cancelling or refunding an order cancels pending commission and takes back credited commission; the balance may go negative and later commission settles it first.
- Affiliates request withdrawals (Alipay, WeChat, bank account, other); the amount leaves the balance right away. After paying it outside the shop, mark it paid in Shop → Affiliates → Withdrawals (optionally with a reference), or reject it with a reason to give the amount back. One open request per affiliate at a time.
Limits: two levels at most; buying through one's own link earns nothing; disabled affiliates earn
no new commission (their balance stays); affiliates only see masked order numbers, never the
buyer. Tables: shop_commissions, shop_withdrawals.
Invoices and receipts
Invoices / receipts in the shop settings: off, invoice requests, automatic receipts, or both.
- Invoice requests (mainland China style): customers tick "I need an invoice" at checkout or
request one from the order page once paid, with a personal or company name and tax ID (required
for companies). Shop → Invoices in the admin: upload the PDF issued in your tax system (up to
5 MB) and the customer is e-mailed and downloads it from the order page, or reject with a reason
so they can correct it. PDFs are stored under
private/in the media storage, never served by/api/media, only to holders of the order link. - Automatic receipts (international): once paid, the order page links a printable receipt / invoice (seller name, address and tax ID, buyer, lines, taxes, points and discounts); "Print → Save as PDF" in the browser keeps it. Seller details come from the shop settings, falling back to the site name and company address.
Setting it up
- Admin → Plugins → Shop → Configure (or Shop → Shop settings): currency, tax, delivery, pickup, order prefix, notification e-mail and the payment methods you use.
- Open a product: the Shop card in the editor sidebar holds price, stock, options and gallery. It is saved together with the product. Commerce data is keyed by the product slug, so all translations of a product share price and stock.
- Configure SMTP in Admin → E-mail so customers receive order e-mails.
- Make sure the cron Worker runs (
deploy/cron-worker) for the "cancel stale unpaid orders" task.
Payment providers
| Provider | Settings | Notes |
|---|---|---|
| Stripe | Secret key sk_…; optional webhook secret whsec_… | Payment is confirmed when the customer returns (API lookup). For extra safety add the webhook /api/shop/webhooks/stripe with checkout.session.completed and checkout.session.async_payment_succeeded. |
| PayPal | Client ID + secret, sandbox switch | Orders v2, captured when the customer returns. |
| Paytrail | Merchant ID + secret | EUR only. Redirects and the server callback /api/shop/webhooks/paytrail are HMAC-verified and the amount is checked. Test credentials: merchant 375917, secret SAIPPUAKAUPPIAS. |
| WeChat Pay | Merchant ID, AppID, API v3 key, merchant certificate serial number, merchant private key (apiclient_key.pem); optional WeChat Pay public key + ID | CNY only, API v3. Desktop shows a QR code on the order page (Native pay) that updates by itself once paid; mobile browsers use H5 pay when "H5" is on (enable H5 pay and its domain on the merchant platform), otherwise the QR code. Inside WeChat the QR code is shown (long-press to pay). Callback /api/shop/webhooks/wechat (https): decrypted with the API v3 key, signature checked when the public key is set. |
| Alipay | AppID, app private key (RSA2, PKCS8 or PKCS1), Alipay public key; sandbox switch | CNY only, public-key mode. Desktop: page pay (QR code or login on Alipay's page); mobile: WAP pay into the Alipay app. Not offered inside WeChat (it blocks Alipay). Notification /api/shop/webhooks/alipay is RSA2-verified; returns are confirmed with alipay.trade.query. |
| Bank transfer | Instructions text | Order stays awaiting payment until you mark it paid. |
| Cash on delivery | — | Same; mark it paid when you receive the money. |
Every online payment is checked against the order total and currency on the server before the order
is marked paid. WeChat Pay and Alipay payments stop being payable when the order would be
cancelled as unpaid (time_expire); a payment that still arrives for a cancelled order is noted on
the order so it can be refunded. The browser never sends prices: carts are priced again by /api/shop/quote and
/api/shop/checkout.
Site presets and environment credentials
Preset settings in the site's nuxtcms.plugins.json (values saved in the admin win) and keep payment
secrets in the environment instead of the database:
{ "plugins": [{ "id": "shop", "package": "nuxtcms/plugins/shop", "license": "NCMS-…",
"settings": { "currency": "EUR", "taxRate": 14, "pickupEnabled": true, "orderPrefix": "LC" } }] }
| Variable | Setting |
|---|---|
NUXT_SHOP_STRIPE_SECRET_KEY / NUXT_SHOP_STRIPE_WEBHOOK_SECRET | Stripe |
NUXT_SHOP_PAYPAL_CLIENT_ID / NUXT_SHOP_PAYPAL_SECRET | PayPal |
NUXT_SHOP_PAYTRAIL_MERCHANT_ID / NUXT_SHOP_PAYTRAIL_SECRET | Paytrail |
NUXT_SHOP_WECHAT_MCH_ID / _APP_ID / _API_V3_KEY / _SERIAL_NO / _PRIVATE_KEY / _PUBLIC_KEY_ID / _PUBLIC_KEY | WeChat Pay |
NUXT_SHOP_ALIPAY_APP_ID / NUXT_SHOP_ALIPAY_PRIVATE_KEY / NUXT_SHOP_ALIPAY_PUBLIC_KEY | Alipay |
Customising
Everything renders through extension points, so sites keep their own
layouts. In the site's app/app.config.ts:
export default defineAppConfig({
nuxtcms: {
slots: {
'header:actions': { ShopCartButton: false }, // the site header has its own cart icon
},
},
})
The global components ShopCartButton, ShopBuyBox, ShopCardPrice and ShopGallery can be
placed in custom layouts directly (<ShopCartButton />). A site can override any plugin page at the
same path (e.g. its own app/pages/checkout.vue).
Tables ({prefix}shop_products, shop_orders, shop_order_items, shop_order_notes,
shop_coupons) are created automatically on first use on SQLite / D1, PostgreSQL and MySQL.