Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
# Shopify connector
|
||||
|
||||
Descrybe v2 connects Shopify as a first-class store source alongside WooCommerce.
|
||||
|
||||
UI: **`/stores`** (hub) → **`/stores/shopify`** (setup). `/shopify` redirects to the setup page.
|
||||
|
||||
## What syncs
|
||||
|
||||
| Data | Direction | Notes |
|
||||
|------|-----------|--------|
|
||||
| Products | Push | Processed Descrybe products → Shopify Admin API create/update (queued / scheduled — **not** store webhooks) |
|
||||
| Orders | Pull | Queued / scheduled pull into `shopify_orders` / `shopify_order_items` (not live webhooks) |
|
||||
| Reviews | — | Not available (Shopify has no first-party product reviews Admin API) |
|
||||
| Inbound webhooks | — | Not received; do not register Shopify webhook URLs at Descrybe until a full HMAC receive path ships (`docs/store-connectors.md`) |
|
||||
|
||||
## Setup (recommended: Dev Dashboard client credentials)
|
||||
|
||||
As of **1 January 2026**, Shopify no longer creates new custom apps in Admin → Develop apps. New apps are created in the [Dev Dashboard](https://dev.shopify.com/dashboard/), installed on the store, then authenticated with the **client credentials** grant (short-lived Admin API tokens, ~24h).
|
||||
|
||||
1. Open the Dev Dashboard → **Apps** → **Create app** → **Start from Dev Dashboard** (UI deep-links this).
|
||||
2. Create a **version**: App URL can be the default API-only home; scopes at least `read_products`, `write_products`, `read_orders` → **Release**.
|
||||
3. From the app **Home** tab → **Install app** on your store (approve scopes).
|
||||
4. App **Settings** → copy **Client ID** and **Client secret**.
|
||||
5. In Descrybe **`/stores/shopify`**: enter shop domain (`your-store` or `your-store.myshopify.com`), paste Client ID + secret, enable sync → **Save** → **Test Connection**.
|
||||
6. Queue **product sync** and **orders sync**. Run the worker so pending flags are claimed.
|
||||
|
||||
Descrybe exchanges Client ID + secret for an access token on save and **refreshes automatically** before expiry (no migration; secrets live encrypted in `access_token` + `sync_options`).
|
||||
|
||||
### Legacy Admin API token (`shpat_…`)
|
||||
|
||||
Custom apps created in Shopify Admin **before** January 2026 still expose a permanent Admin API access token. The connect form has a “legacy token” path for those. Prefer Client ID + secret for anything new.
|
||||
|
||||
### Still manual (no one-click App Store install)
|
||||
|
||||
- Creating the Dev Dashboard app, releasing a version, and installing it on the merchant store
|
||||
- Approving scopes in Shopify Admin when scopes change
|
||||
- Collaborator accounts cannot use the Dev Dashboard — the store owner (or staff with Develop permission) must create the app
|
||||
- Client credentials only work when the app and store are in the **same** Shopify organization (see Shopify’s `shop_not_permitted` docs)
|
||||
|
||||
Shop domain must be the `*.myshopify.com` admin host. Custom storefront domains are rejected (Admin API always uses myshopify.com). SSRF guards block IPs, localhost, and private ranges.
|
||||
|
||||
## Environment variables (optional live path)
|
||||
|
||||
Set in process env or untracked `.env` (never commit secrets):
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `SHOPIFY_SHOP_DOMAIN` | Shop name or `name.myshopify.com` |
|
||||
| `SHOPIFY_ACCESS_TOKEN` | Admin API access token (legacy / ops scripts) |
|
||||
| `SHOPIFY_API_VERSION` | Default `2024-10` |
|
||||
| `SHOPIFY_DRY_RUN` | When `true`, treat as dry-run (UI also has a Dry run checkbox) |
|
||||
|
||||
Aliases are not required; credentials saved via the UI are encrypted at rest (`CREDENTIALS_ENCRYPTION_KEY` / `APP_ENCRYPTION_KEY` — see [ops-runtime.md](ops-runtime.md)).
|
||||
|
||||
### No live shop? Dry-run / mock
|
||||
|
||||
- In the UI: enable **Dry run**, or set access token to exactly `dry-run`.
|
||||
- Dry-run skips live HTTP calls: test connection returns a mock shop; product create/update and a sample order page succeed in-process.
|
||||
- Unit tests in `internal/shopify` cover domain SSRF guards, token encryption, client-credentials parsing, and dry-run client behavior (no network).
|
||||
|
||||
## APIs
|
||||
|
||||
| Method | Path | Notes |
|
||||
|--------|------|-------|
|
||||
| GET | `/api/shopify` | Config + sync badges (no secrets). Includes `auth_mode`, `has_client_credentials`. |
|
||||
| PUT | `/api/shopify` | Save shop domain + (`client_id`/`client_secret` **or** `access_token`) + api_version + dry_run |
|
||||
| POST | `/api/shopify/test` | Live or dry-run shop.json (refreshes client-credentials token if needed) |
|
||||
| POST | `/api/shopify/sync` | Queue product push |
|
||||
| POST | `/api/shopify/sync-orders` | Queue orders pull |
|
||||
| GET | `/api/shopify/orders` | List synced orders |
|
||||
|
||||
## Worker
|
||||
|
||||
`cmd/worker` claims pending Shopify jobs after WooCommerce:
|
||||
|
||||
- `products` → `shopify.Service.SyncCompany`
|
||||
- `orders` → `shopify.Service.SyncOrders`
|
||||
|
||||
Scheduled enqueue (every 15m ops tick) marks enabled configs whose `last_synced_at` is older than the interval (default 6h).
|
||||
|
||||
## Schema
|
||||
|
||||
Migration `apps/api/sql/schema/017_shopify.sql`:
|
||||
|
||||
- `shopify_configs` — shop domain, encrypted access token, api version, sync_options JSONB
|
||||
- `shopify_orders` / `shopify_order_items` — pulled order data
|
||||
|
||||
Client credentials metadata (`auth_mode`, `client_id`, encrypted `client_secret_enc`, `token_expires_at`) is stored in `sync_options` (no extra migration).
|
||||
|
||||
Apply with `make migrate` (or `scripts/migrate.ps1` on Windows).
|
||||
|
||||
## Product matching
|
||||
|
||||
1. Cached Shopify product id in `sync_options.product_ids` (keyed by Descrybe processed product UUID).
|
||||
2. Else GraphQL `productVariants(query: "sku:…")`.
|
||||
3. Else create a new product with a single variant (SKU/price/barcode) and a `descrybe.product_id` metafield.
|
||||
|
||||
## Related
|
||||
|
||||
- Hub overview: [store-connectors.md](store-connectors.md)
|
||||
- WooCommerce parity: [woocommerce-demo.md](woocommerce-demo.md)
|
||||
- Shopify docs: [Create apps (Dev Dashboard)](https://shopify.dev/docs/apps/build/dev-dashboard/create-apps-using-dev-dashboard), [Get API access tokens](https://shopify.dev/docs/apps/build/dev-dashboard/get-api-access-tokens)
|
||||
Reference in New Issue
Block a user