|
|
||
|---|---|---|
| app | ||
| docs | ||
| extensions/thank-you-activity | ||
| prisma | ||
| scripts | ||
| test | ||
| .env.example | ||
| .gitignore | ||
| README.md | ||
| package-lock.json | ||
| package.json | ||
| shopify.app.toml | ||
| tsconfig.json | ||
| vite.config.ts | ||
| vitest.config.ts | ||
README.md
Dreame Shopify App
Shopify custom app scaffold for the Dreame Thank You Page activity flow.
This project follows:
/初始化需求/追觅Shopify App Codex编码方案指令.md- Scope: local runnable code and deployment documentation only.
- Out of scope: real deployment, production database provisioning, real third-party API integration, real test orders.
What This App Does
- Customer lands on Shopify Thank You Page.
- Checkout UI Extension target
purchase.thank-you.block.renderloads. - Extension gets
orderId,orderNumber,locale, and a Shopify session token. - Extension calls
POST /api/action/activity/check. - Backend validates the checkout session token, queries order data through Admin GraphQL, reads the
dreame_activityMetaobject config, checks order-level idempotency, calls Dreame status API, then calls Dreame push API. - On success, backend returns
success,redirectUrl, and localizedbuttonText. - Extension renders a button and opens
redirectUrlin the current window.
Failures return { "success": false }; the extension renders nothing and does not block the native Thank You Page.
Local Setup
The Shopify CLI was not available in this workspace, and remote npm create @shopify/app@latest execution was blocked by local safety policy. The project has therefore been initialized as a local scaffold matching the requested Shopify Remix structure.
When network access is available:
cd "/Users/jason/Downloads/myproject/Shopify 插件开发/dreame-shopify-app"
npm install
cp .env.example .env
npx prisma generate
npx prisma migrate dev
shopify app dev
Install the app to the development shop:
function-613wh0ez.myshopify.com
Environment
Copy .env.example to .env and fill Shopify credentials:
SHOPIFY_API_KEY=
SHOPIFY_API_SECRET=
SHOPIFY_APP_URL=
SCOPES=read_orders,read_metaobjects
DATABASE_URL="file:./dev.sqlite"
THIRD_PARTY_MODE=mock
THIRD_PARTY_TIMEOUT_MS=5000
LOG_RETENTION_DAYS=7
METAOBJECT_TYPE=dreame_activity
METAOBJECT_HANDLE=dreame-activity-config
THIRD_PARTY_MODE=mock is the default because third-party APIs, signing samples, and test orders were not provided.
Metaobject Configuration
Create one Metaobject definition:
- Type:
dreame_activity - Suggested handle:
dreame-activity-config - Storefront access:
NONE
Fields:
| Key | Type | Required | Notes |
|---|---|---|---|
activity_id |
number_integer |
yes | Dreame activity ID |
api_app_id |
single_line_text_field |
yes | Sent as dreame-api-app-id |
api_secret |
single_line_text_field |
yes | Backend only; never expose to extension |
status_api_url |
url |
yes | Activity status API |
push_api_url |
url |
yes | Activity push API |
redirect_url |
url |
yes | Button target URL |
button_text |
json |
yes | Locale map |
shop |
single_line_text_field |
no | Overrides properties.shop; fallback is myshopify domain |
Example button_text:
{
"en": "Claim Your Reward",
"zh-CN": "领取奖励",
"zh-TW": "領取獎勵",
"de": "Belohnung erhalten",
"fr": "Réclamer votre récompense"
}
Optional setup script:
SHOPIFY_SHOP_DOMAIN=function-613wh0ez.myshopify.com \
SHOPIFY_ADMIN_ACCESS_TOKEN=shpat_xxx \
npx tsx scripts/setup-metaobject.ts
The optional script requires write scopes such as write_metaobjects and write_metaobject_definitions. It is not needed if the Metaobject is created manually in Shopify Admin.
API Contract
Endpoint:
POST /api/action/activity/check
Headers:
Authorization: Bearer <checkout session token>
Content-Type: application/json
Request:
{
"orderId": "gid://shopify/Order/1234567890",
"orderNumber": "#1001",
"locale": "zh-CN"
}
Success response:
{
"success": true,
"redirectUrl": "https://activity.example.com",
"buttonText": "领取奖励"
}
Failure response:
{
"success": false
}
CORS headers are returned for POST and OPTIONS; source trust relies on Shopify checkout session token validation.
Dreame Signing
All third-party requests include:
dreame-api-app-iddreame-api-timestampdreame-api-sign
Signature:
sign = MD5(bodyString + timestamp + api_secret)
The implementation serializes the payload once with JSON.stringify(payload). The exact same bodyString is used both for signing and as the fetch body.
Mock Mode
Default mock behavior:
- Status API returns
code=0anddata.status=true. - Push API returns
code=0.
Useful mock environment variables:
DREAME_MOCK_STATUS_CODE=0
DREAME_MOCK_STATUS_ACTIVE=true
DREAME_MOCK_PUSH_CODE=0
DREAME_MOCK_TIMEOUT=false
Switch to live calls only after third-party endpoints and credentials are available:
THIRD_PARTY_MODE=live
Database
Development uses SQLite:
DATABASE_URL="file:./dev.sqlite"
Production should use Postgres. The same Prisma schema applies after changing the datasource provider and connection string as part of production hardening.
Tables added by this scaffold:
ActivityPushLog: one row per status/push attempt or failure.ActivityPushIdempotency: unique key onshop + orderId + activityId; prevents duplicate push for the same order and activity.
Logs are sanitized before persistence and must not store api_secret or dreame-api-sign.
Checkout UI Extension
Location:
extensions/thank-you-activity
Target:
purchase.thank-you.block.render
Capabilities:
[extensions.capabilities]
network_access = true
Set the extension setting app_url to the public HTTPS backend URL. During shopify app dev, this is the tunnel URL produced by Shopify CLI.
Tests
After dependencies are installed:
npm test
npm run typecheck
Current test coverage:
test/signature.test.ts: MD5 signature and single-serialization body contract.test/idempotency.test.ts: same order/activity does not push twice and still returns button data.test/activity-flow.test.ts: inactive status, non-zero status code, successful push payload shape, push failure, missing email, missing customer ID.
Deployment Steps
This task does not deploy. For production deployment later:
- Provision Postgres.
- Set production environment variables.
- Update
shopify.app.tomlURLs. - Run Prisma migration against production.
- Run
shopify app deploy. - Publish the Checkout UI Extension.
- Configure the extension
app_urlsetting. - Create the
dreame_activityMetaobject in the production shop. - Switch
THIRD_PARTY_MODE=live. - Run end-to-end validation with real test orders and third-party test endpoints.
Security Notes
api_secretis backend-only.- The extension never reads Metaobject fields directly.
- Third-party calls are always made by the backend.
- Frontend-provided order data is not trusted; backend queries Shopify Admin GraphQL.
- Logs are sanitized and pruned after
LOG_RETENTION_DAYSdays.
Acceptance Coverage
| Requirement | Coverage |
|---|---|
| Thank You Page loads extension | extensions/thank-you-activity |
| Page automatically calls backend | Checkout.tsx effect |
| Backend reads configuration | config.server.ts |
| Status API called | dreame-client.server.ts, activity.server.ts |
status=true calls push API |
activity.server.ts, tests |
Push success returns success=true |
activity-flow.test.ts |
| Button displays | Checkout.tsx |
| Button supports localization | locale.ts, activity.server.ts |
| Button redirects to configured URL | Checkout.tsx |
| Same order is not pushed twice | idempotency.server.ts, idempotency.test.ts |
| Calls are logged | logger.server.ts |
| Third-party signing is correct | signature.server.ts, signature.test.ts |
| Sensitive data is not exposed to frontend | backend-only config and sanitized logs |
Out of Scope For This Phase
- Order Status Page.
orders/paidwebhook fallback.- App admin UI for managing Metaobjects.
- Actual deployment.
- Real third-party API validation.
- Real Shopify test order validation.