From: Roberto Morado Date: Sat, 16 May 2026 04:58:14 +0000 (-0400) Subject: Service X-Git-Url: https://git.morado.dev/sitemap.xml?a=commitdiff_plain;p=shop Service --- diff --git a/README.md b/README.md index 774edcf..6089435 100644 --- a/README.md +++ b/README.md @@ -1,149 +1,188 @@ -# Printify Store +# Store -A minimal, fully functional print-on-demand storefront built with Deno, Hono, and Deno KV. Connects directly to the Printify API for product fulfillment and Stripe for payment collection. No framework overhead, no database to manage, no servers to provision. +A minimal, fully automated print-on-demand storefront built with [Deno](https://deno.com), [Hono](https://hono.dev), and [Printify](https://printify.com). Customers browse, pick a size, and pay — Printify handles printing and shipping, Stripe handles the money. --- -## How it works - -The store sits between your customers and Printify: +## How the money flows ``` -Customer pays Stripe ($35) - → Your server confirms payment - → Your server submits order to Printify - → Printify charges your card on file (~$12 base cost) - → Printify prints and ships directly to your customer - → You keep the margin (~$23) +Customer pays Stripe → your Stripe account receives the full amount +Your server submits order to Printify → Printify charges your card on file (base cost) +Stripe pays out to your bank → you keep the difference ``` -Everything is automated. You set your prices in Printify, publish your products, and the store handles the rest. +You never need a Printify wallet balance. You just need a credit card registered in Printify's billing settings. Stripe pays out on their schedule (typically 2–7 days); Printify charges your card immediately when an order is placed. The card bridges the gap. --- -## Stack +## Prerequisites -| Layer | Technology | -|---|---| -| Runtime | [Deno 2](https://deno.com) | -| Web framework | [Hono](https://hono.dev) with JSX | -| Database | [Deno KV](https://docs.deno.com/kv/manual) (built-in) | -| Fulfillment | [Printify API](https://developers.printify.com) | -| Payments | [Stripe](https://stripe.com) | +| What | Why | +|------|-----| +| [Deno 2.x](https://deno.com) | Runtime | +| [Printify account](https://printify.com) | Products + fulfillment | +| [Stripe account](https://stripe.com) | Customer payments | +| A domain + nginx | Production serving (optional for local dev) | --- -## Prerequisites +## Getting your API keys -- **Deno 2** — [install](https://docs.deno.com/runtime/getting_started/installation/) -- **Printify account** — [printify.com](https://printify.com) with at least one published product -- **Stripe account** — [stripe.com](https://stripe.com), free to create +### Printify ---- +1. Log in to Printify +2. Go to **My Account → Connections → API** +3. Click **Generate token** — copy it, you won't see it again +4. Your Shop ID is auto-discovered from the token — you don't need to find it -## Setup +> Make sure you have a **credit card on file** in Printify's billing settings. This card is charged the base cost each time an order is submitted. -### 1. Clone and enter the project +### Stripe -```bash -git clone -cd printify -``` +1. Log in to the [Stripe Dashboard](https://dashboard.stripe.com) +2. Go to **Developers → API keys** +3. Copy the **Publishable key** (`pk_live_...`) and **Secret key** (`sk_live_...`) +4. For testing, use the `pk_test_...` / `sk_test_...` keys instead + +--- -### 2. Create your environment file +## Installation ```bash +git clone +cd printify cp .env.example .env ``` -Open `.env` and fill in your keys: +Edit `.env`: ```env -# Required PRINTIFY_TOKEN=your_printify_api_token + STRIPE_SECRET_KEY=sk_live_... STRIPE_PUBLIC_KEY=pk_live_... -# Optional — auto-discovered if blank -# PRINTIFY_SHOP_ID= - -# Optional — cosmetic -STORE_NAME=My Store +STORE_NAME=Your Store Name STORE_TAGLINE=Quality design, made to order. + +PORT=8000 ``` -### 3. Run +--- + +## Running locally ```bash deno task dev ``` -The store is now running at [http://localhost:8000](http://localhost:8000). +Open [http://localhost:8000](http://localhost:8000). + +### Dry-run mode + +Test the full checkout flow without submitting a real order or charging a card: + +```bash +deno task dev --dry-run +``` + +A yellow banner appears on the checkout page. Completing checkout logs the full order payload to the terminal and saves it to `logs/`. Nothing is charged, nothing is submitted. --- -## Getting your keys +## Running in production (DigitalOcean + nginx) + +### 1. Install Deno on your droplet + +```bash +curl -fsSL https://deno.land/install.sh | sh +echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc +source ~/.bashrc +``` -### Printify API token +### 2. Upload your project -1. Log in to [printify.com](https://printify.com) -2. Go to **My Profile → Connections → API** -3. Click **Generate** to create a token -4. Copy it into `PRINTIFY_TOKEN` +```bash +scp -r . root@your-droplet-ip:/root/printify +``` -The shop ID is discovered automatically from your token — you do not need to set `PRINTIFY_SHOP_ID` unless you have multiple shops and want to target a specific one. +Or clone from git directly on the droplet. -### Stripe keys +### 3. Install the systemd service -1. Log in to [dashboard.stripe.com](https://dashboard.stripe.com) -2. Go to **Developers → API keys** -3. Copy the **Publishable key** → `STRIPE_PUBLIC_KEY` -4. Copy the **Secret key** → `STRIPE_SECRET_KEY` +A `store.service` file is included in the project root. -Use **test keys** (`pk_test_...` / `sk_test_...`) during development. Switch to live keys when you go live. +```bash +cp store.service /etc/systemd/system/store.service +systemctl daemon-reload +systemctl enable store # start on boot +systemctl start store +systemctl status store # verify it's running +``` -### Printify billing +**This is why the app dies when you close SSH.** Without systemd (or nohup/pm2), the process is owned by your terminal session and receives SIGHUP when that session ends — killing it. Systemd detaches the process from any terminal entirely. Your other services survive SSH disconnect because they were set up with pm2, nohup, or systemd at some point. -For the payment flow to be fully automated, Printify must be able to charge you automatically when an order is submitted: +To view live logs: +```bash +journalctl -u store -f +``` -1. In Printify, go to **Billing → Payment methods** -2. Add a credit or debit card -3. Enable **Automatic billing** +To restart after a code update: +```bash +systemctl restart store +``` -When a customer pays via Stripe, your server immediately submits the order to Printify. Printify charges your card for the base cost and ships to the customer. No manual steps needed. +### 4. nginx reverse proxy ---- +Create `/etc/nginx/sites-available/store`: -## Printify product setup +```nginx +server { + listen 80; + server_name yourdomain.com; -Only published products with enabled variants appear in the store. To set up a product correctly: + location / { + proxy_pass http://localhost:8000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` -1. Create a product in Printify and upload your design -2. Under **Variants**, enable only the colors and sizes you want to sell — disabled variants are automatically hidden in the store -3. Set your retail prices for each variant -4. Mark one color as the **default** — that color's image and price will be shown when customers first land on the product page -5. **Publish** the product to your store +```bash +ln -s /etc/nginx/sites-available/store /etc/nginx/sites-enabled/ +nginx -t +systemctl reload nginx +``` + +### 5. HTTPS (Let's Encrypt) -The store reads exactly what Printify exposes — no manual syncing needed. +```bash +apt install certbot python3-certbot-nginx +certbot --nginx -d yourdomain.com +``` + +Certbot updates the nginx config automatically and sets up auto-renewal. --- -## Dry-run mode +## Logs -Test the full checkout flow without submitting real orders to Printify or charging Stripe: +All output is written to `logs/YYYY-MM-DD.log`, rotating daily. These files are git-ignored. ```bash -deno task dev --dry-run +tail -f logs/$(date +%F).log # follow today's log ``` -In dry-run mode: -- A yellow banner appears on the checkout and success pages -- The Stripe payment form is replaced with a plain submit button -- Clicking "Place Order" logs the full Printify order payload to your terminal -- The cart is cleared and the success page is shown — identical to a real order -- Nothing is sent to Stripe or Printify - -Use this to verify your shipping data, line items, and variant IDs are correct before going live. +Critical events that are always logged: +- Every HTTP request (method, path, status, duration) +- Printify API errors +- Failed order submissions — includes full cart + shipping so you can manually recreate the order in the Printify dashboard if needed +- Dry-run payloads --- @@ -151,105 +190,88 @@ Use this to verify your shipping data, line items, and variant IDs are correct b ``` printify/ -├── main.ts # Entry point — wires app and starts server -├── deno.json # Deno config, import map, JSX transform -├── .env.example # Environment variable template +├── main.ts # Entry point — wires app, starts server +├── deno.json # Config, import map, dev/start tasks +├── .env.example # All environment variables documented +├── .gitignore # Excludes .env and logs/ +├── store.service # systemd unit file for production └── src/ - ├── config.ts # Runtime flags (e.g. --dry-run) - ├── types.ts # Shared TypeScript interfaces + ├── config.ts # DRY_RUN flag (set by --dry-run arg) + ├── logger.ts # Patches console → writes to logs/ + ├── types.ts # Shared TypeScript interfaces ├── client/ - │ └── printify.ts # Printify API wrapper + │ └── printify.ts # Printify API wrapper (auto-discovers shop) ├── kv/ - │ └── cart.ts # Cart and shipping — Deno KV operations + │ └── cart.ts # Cart + shipping session storage (Deno KV) ├── middleware/ - │ └── session.ts # Cookie-based session ID + │ └── session.ts # Cookie-based session ID (UUID, 7-day TTL) ├── components/ - │ ├── Layout.tsx # HTML shell, CSS design system, global JS - │ └── ProductCard.tsx # Product card with color swatches and quick-add + │ ├── Layout.tsx # HTML shell, full CSS design system, global JS + │ └── ProductCard.tsx # Card with colour swatches + quick-add toggle └── routes/ - ├── shop.tsx # GET / — product grid - ├── product.tsx # GET /products/:id — product detail - ├── cart.tsx # /cart — view, add, update, remove - └── checkout.tsx # /checkout — Stripe payment + Printify order + ├── shop.tsx # GET / — product grid + ├── product.tsx # GET /products/:id — detail, variant picker + ├── cart.tsx # GET+POST /cart — bag management + └── checkout.tsx # GET+POST /checkout — Stripe + Printify order ``` --- -## Routes reference - -| Method | Path | Description | -|---|---|---| -| `GET` | `/` | Product grid | -| `GET` | `/products/:id` | Product detail with variant selector | -| `GET` | `/cart` | Cart | -| `POST` | `/cart/add` | Add item (from product page) | -| `POST` | `/cart/add-ajax` | Add item (from quick-add, returns JSON) | -| `POST` | `/cart/update` | Update quantity | -| `POST` | `/cart/remove` | Remove item | -| `GET` | `/checkout` | Checkout page | -| `POST` | `/checkout/intent` | Create Stripe payment intent | -| `POST` | `/checkout/shipping` | Persist shipping before Stripe redirect | -| `GET` | `/checkout/done` | Stripe return URL — confirms payment, submits Printify order | -| `GET` | `/checkout/success` | Order confirmation | -| `POST` | `/checkout/dry-run` | Dry-run order submit (logs payload, no API calls) | - ---- - -## Payment flow in detail +## Customer-facing flow ``` -1. Customer fills shipping form and card details (Stripe Payment Element) -2. Browser calls POST /checkout/shipping — shipping data saved to Deno KV -3. Browser calls stripe.confirmPayment() — Stripe charges the customer -4. Stripe redirects to GET /checkout/done?payment_intent=xxx -5. Server calls Stripe API to verify payment status === "succeeded" -6. Server retrieves shipping from Deno KV (keyed by session ID) -7. Server calls Printify API to create order -8. Printify charges your card on file for the base cost and queues fulfillment -9. Server clears cart and shipping from KV -10. Customer is redirected to /checkout/success with their order ID +/ → product grid + → click product → /products/:id + → select colour + size + → Add to Bag → /cart + → Checkout → /checkout + → fill shipping + card details + → Pay → Stripe charges card + → /checkout/done (Stripe redirect) + → order submitted + → /checkout/success ``` -If Printify fails after payment is already collected, the full cart and shipping details are logged to the terminal so you can create the order manually. The customer has been charged; nobody loses the order. +The quick-add `+` button on each grid card lets customers add the default colour directly from the grid by tapping a size, without opening the product page. --- -## Deployment +## Customisation -The easiest deployment target for a Deno app is [Deno Deploy](https://deno.com/deploy). +| Variable | Default | Effect | +|----------|---------|--------| +| `STORE_NAME` | `Store` | Nav logo and browser tab title | +| `STORE_TAGLINE` | `Quality design, made to order.` | Shown on every product page below the price | +| `PORT` | `8000` | HTTP port the server listens on | +| `PRINTIFY_SHOP_ID` | _(auto-detected)_ | Set explicitly if your token has access to multiple shops | -1. Push your code to GitHub -2. Create a new project at [dash.deno.com](https://dash.deno.com) -3. Connect your repository and set the entry point to `main.ts` -4. Add your environment variables in the project settings -5. Deploy +--- -Deno KV is available natively on Deno Deploy — no additional database setup required. +## Product setup on Printify -> **Note:** Remove `--env-file` from the `start` task before deploying, as environment variables are injected by the platform rather than loaded from a file. +- **Enable only the variants you want to sell.** Sizes and colours not attached to an enabled variant are hidden automatically. +- **Set a default colour.** The product page opens on whichever colour Printify marks as the default image — colour swatches and dropdowns are pre-selected to match. +- **Publish your products.** Only products marked `visible` in Printify appear in the store. +- **Assign images to colours.** In Printify's product editor, link each image to its colour variants. This is what makes the product photo swap when a customer picks a different colour. --- -## Environment variables +## Troubleshooting -| Variable | Required | Description | -|---|---|---| -| `PRINTIFY_TOKEN` | Yes | Printify API token | -| `STRIPE_SECRET_KEY` | Yes | Stripe secret key (`sk_live_...` or `sk_test_...`) | -| `STRIPE_PUBLIC_KEY` | Yes | Stripe publishable key (`pk_live_...` or `pk_test_...`) | -| `PRINTIFY_SHOP_ID` | No | Defaults to the first shop on your account | -| `STORE_NAME` | No | Display name shown in the nav and page titles. Default: `Store` | -| `STORE_TAGLINE` | No | Short description shown on every product page. Default: `Quality design, made to order.` | -| `PORT` | No | Port to listen on. Default: `8000` | +**App dies when I close SSH** +Install the systemd service (see above). This is the standard fix — the process needs to be detached from the terminal session. ---- +**404 from Printify on startup** +Your `PRINTIFY_TOKEN` is invalid or expired. Generate a new one from Printify → My Account → Connections → API. + +**Stripe Payment Element doesn't appear** +Check that `STRIPE_PUBLIC_KEY` starts with `pk_`. Open browser devtools → Console for Stripe-specific errors. Make sure you're not running with `--dry-run`. -## Design +**Order placed but Printify returned an error** +Check `logs/YYYY-MM-DD.log` — the full cart and shipping payload is logged on every failure so you can manually recreate the order from the Printify dashboard. Payment was already collected by Stripe; do not charge the customer again. -The UI follows a minimal, high-contrast aesthetic — generous whitespace, system font stack, black CTAs. Designed to get out of the way of the product photography. +**Images don't update when I change colour** +In Printify's product editor, open the Images tab and assign each image to its colour variants. Images with no variant assignment fall back to the product default. -- **Responsive** — single column on mobile, multi-column grid on larger screens -- **No client-side framework** — cart operations are plain HTML form posts; only the product variant selector, quick-add, and Stripe payment form use JavaScript -- **Quick-add** — tap the `+` on any product card to add a size without leaving the grid -- **Color swatches** — each card shows a dot per available color so customers know the range before clicking through -- **Sticky add bar** — on mobile, an "Add to Bag" button stays fixed at the bottom of the product page as the customer scrolls through options +**App won't start after a crash** +Check `journalctl -u store -n 50` for the last 50 log lines. Common causes: missing `.env` file, invalid API keys, port already in use. diff --git a/store.service b/store.service new file mode 100644 index 0000000..b526cd9 --- /dev/null +++ b/store.service @@ -0,0 +1,22 @@ +[Unit] +Description=Store +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/root/shop +ExecStart=/root/.deno/bin/deno run \ + --allow-net \ + --allow-env \ + --allow-read \ + --allow-write \ + --env-file \ + --unstable-kv \ + main.ts +Restart=on-failure +RestartSec=5 +Environment=HOME=/root + +[Install] +WantedBy=multi-user.target