Files
descrybe/docs/shopify-connector.md
T

103 lines
5.7 KiB
Markdown
Raw Normal View History

# 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 Shopifys `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)