TomLabs Apps
← All applications

TomLabsShop

Katalog obchodu TomLabs na Etsy: praktické a originální věci na zakázku. Nákup probíhá na Etsy.

web shop etsy dotnet Public Homepage

TomLabs Shop

icon

A trilingual (EN/CS/DE) web front for the TomLabs shop on Etsy, live at https://shop.tomlabs.cz. Etsy products retain their external purchase links. Version 0.4 includes an independent own catalogue and single-product Stripe test checkout, disabled by default. There is no cart. This stage is not ready for live sales.

Languages: English (default), Czech and German, chosen by cookie, else Accept-Language.

ASP.NET Core Razor Pages on net10.0, Serilog logging, deployed as a Docker image behind nginx on the VPS. Architecture, decisions and risks: docs/ARCHITECTURE.md. Release notes: CHANGELOG.md.

Run it

dotnet run --project src/TomLabs.Shop          # sample catalogue, no Etsy key needed
dotnet test TomLabs.Shop.slnx

Without an Etsy API key the shop shows a clearly marked sample catalogue and /health answers Degraded.

Direct checkout (MVP, test only)

Set Checkout__Enabled=true explicitly to expose the own catalogue on the home page and product details at /buy/{slug}. When disabled, the own catalogue, purchase pages and webhook are unavailable; existing Etsy pages keep their behaviour. The return page never confirms payment by itself.

The first enabled request copies Seed/own-catalog.json into Shop:DataDirectory/own-catalog.json if absent. Replace the labelled sample with reviewed own data; never use Etsy API/cache data for these products. Edit atomically. Fields and sample structure are in the seed file; prices are integer cents/haléře including VAT, weight is grams including packaging, and each configuration/bundle is a separate product. No stock sync or cart.

Key Meaning
Checkout:Enabled false by default in every environment
Checkout:Shipping Country / maximum total grams / gross minor units; first matching weight band wins
Stripe:SecretKey Environment Stripe__SecretKey; test secret key only, never committed or logged
Stripe:WebhookSecret Environment Stripe__WebhookSecret; endpoint signing secret, never committed or logged
Shop:PublicBaseUrl Trusted absolute origin for success/cancel URLs; set to the local URL for local tests

All bundled shipping rates are TEST PLACEHOLDERS, CZK for CZ and EUR elsewhere. Supported destinations are the EU countries in the table. Quantity is 1–20; shipments above the largest band are rejected. Gross VAT is 21% for EU consumers, including shipping; VAT is rounded once on the whole order. Non-EU checkout is disabled until export VAT is decided by the accountant. No coupons or comparison prices are supported.

After Tomáš configures a Stripe test environment outside the repository, register/forward checkout.session.completed to POST /api/stripe/webhook, using the API version pinned by Stripe.net 53.0.0. Use the signing secret for that specific endpoint (CLI and Dashboard secrets differ). Missing configuration returns 503 and degrades entries.stripeConfig; disabled checkout reports that entry Healthy. Live keys/events are rejected even in production. The current code has been tested with a fake gateway and signed local fixtures, not a Stripe account. Integration references: session creation, webhook signatures and retries.

Orders persist in orders.db in the data directory: pending quote before redirect, paid state/customer e-mail and shipping address only after a matching signed webhook. Back up the persistent folder, restrict access and do not serve it as static content. Failed/cancelled pending attempts expire after 24 hours by default. Stage 2 adds reconciliation, confirmation/notification e-mails and protected order administration. Legal documents remain clearly marked placeholders that must be replaced before live launch. See ADRs and the TaskGraph TomLabs.Shop follow-ups.

See the test checkout operations runbook for the Stripe Dashboard API version requirement, Resend configuration, pending expiry, outbox retries, administration and launch prerequisites. Customer e-mails use the persisted order language and link the localized terms and withdrawal form; no Resend configuration means no sending and queued messages remain pending.

Configuration

Settings come from appsettings.json, environment variables (Etsy__ApiKey), user secrets or an uncommitted appsettings.Local.json. Secrets are never committed.

Key Meaning
Etsy:ApiKey Etsy API keystring (see below)
Etsy:SharedSecret Shared secret of the same Etsy app (sent as keystring:secret)
Etsy:ShopName / Etsy:ShopId Shop to read; the id is looked up by name once unless given
Shop:AdminPassword Password of /logs and /admin/orders (user admin); empty = Logs page disabled (404)
Shop:GaMeasurementId GA4 ID; empty = no consent banner and no tag
Shop:DataDirectory Catalogue cache (catalog.json) and logs (logs/); /data in the container
Shop:RefreshInterval / Shop:RetryInterval How often the catalogue is re-read (1 h) and the wait after a failure (10 min)

Getting an Etsy API key

  1. Sign in to Etsy as the shop owner and open https://www.etsy.com/developers/register.
  2. Register an app (name TomLabs Shop, description: read-only catalogue of my own shop, no commercial third party use).
  3. Wait for the key to be approved (personal access is usually granted quickly); copy the keystring and shared secret from https://www.etsy.com/developers/your-apps.
  4. Put them into the Azure DevOps variable group shop-deployment as ETSY_API_KEY and ETSY_SHARED_SECRET (secret) and run the pipeline.

Deployment

azure-pipelines.yml builds, tests, builds the Docker image, ships it to the VPS and runs deploy/deploy-vps.sh (port discovered at deploy time, nginx site rendered from deploy/nginx-shop.conf.template, certificate through certbot). Variable groups: shop-deployment, web-analytics, appstore-publish. Setup command: pwsh F:/Root/GIT/TomLabs.AppStore/tools/Setup-WebAppPublishing.ps1 -AppName TomLabsShop -Project TomLabs.Shop -Repository TomLabs.Shop -RepoRoot F:/Root/GIT/TomLabs.Shop.

The term "Etsy" is a trademark of Etsy, Inc. This application uses the Etsy API but is not endorsed or certified by Etsy, Inc.

Changelog

0.4.0 — 2026-10-19

  • Added: persistent Resend email outbox, localized test order confirmations with legal links, operator notifications/review alerts and shipping notices with tracking.
  • Added: protected responsive order administration with status filters, explicit fulfilment transitions, tracking and private notes.
  • Added: CS/EN/DE legal page structures marked PLACEHOLDER, terms agreement and payment-obligation button wording. Placeholder pages stay hidden with checkout disabled.
  • Fixed: paid quote mismatches now require manual review and acknowledge Stripe without retries; concurrent duplicate events are harmless and webhook bodies use a byte-limited stream.
  • Added: configurable pending-order expiry (24 hours), email configuration health, operational runbook and email/admin decisions.
  • Checkout remains disabled by default, TEST only. Real legal content, real shipping rates, accountant decisions and a live-key ADR are required before live sales.

0.3.1 — 2026-10-19

  • Fixed: the Linux build failed with a duplicate content item for the seed catalogue (NETSDK1022), so 0.3.0 was never deployed.

0.3.0 — 2026-10-19

  • Added: separate JSON catalogue with a labelled sample and EN/CS/DE direct-buy pages; Etsy listings keep their Etsy purchase links.
  • Added: quantity and EU destination selection, CZK/EUR gross prices, 21% included VAT and configurable country/weight shipping rates (test placeholders).
  • Added: hosted Stripe Checkout in test mode, verified payment webhooks and durable SQLite orders with duplicate protection.
  • Checkout is disabled by default. Live credentials and live payment events are rejected; the Stripe health entry reports missing configuration only when enabled.
  • Recorded payment and storage decisions. E-mails, order administration, legal pages and deployment remain stage 2.

0.2.1 — 2026-10-02

  • Fixed: product photos were missing. Etsy does not return images in the shop listings answer, so they are now fetched per listing (paced under the Etsy rate limit and remembered between refreshes).
  • A new build refreshes the catalogue as soon as it starts instead of waiting for the hourly refresh.
  • The Logs page now shows how many listings came back with and without photos and the HTTP status of the image calls.

0.2.0 — 2026-10-02

  • Language switch EN / CS / DE in the header, same behaviour as tomlabs.cz: the remembered choice, else the first browser language that is available, else English.
  • All interface text, the privacy page and the cookie banner follow the chosen language; the page language (html lang) is correct. Product texts stay as Etsy returns them.
  • Prices are written in the style of the chosen language.
  • The switch works without JavaScript (a link that sets a cookie).

0.1.0 — 2026-10-02

  • First version: a Czech catalogue of the TomLabs Etsy shop with product grid, search, category filter, sort and a detail page with gallery.
  • "Koupit na Etsy" buttons lead to the listing on Etsy; there is no cart or payment on this site.
  • Products come from the Etsy Open API v3 (read only), cached on disk and refreshed hourly; when Etsy is down or rate limiting, the last good catalogue keeps being served.
  • Until an Etsy API key is configured the shop shows a clearly marked sample catalogue.
  • GET /health reports Degraded while the Etsy key is missing or the last synchronisation failed.
  • Light / dark / system theme switch, version shown in the footer, privacy page, sitemap and robots.txt.
  • Admin-only Logs page (/logs) with live entries, filters and log file download.
  • Cookie consent banner and GA4 loaded only after acceptance, and only when a measurement ID is configured.