18 KiB
追觅 Shopify App 最终方案与上线说明
本文件是当前代码目录的最终版方案和上线主口径。 历史材料包括
README.md、docs/shopify-deployment-runbook.md以及初始化需求/下多份需求澄清文档;后续对业务、开发、测试和上线沟通时,以本文为准。
1. 当前结论
追觅 Shopify App 按 Shopify Remix App + Checkout UI Extension + 后端同步触发 实现。
当前版本可以进入开发/测试环境验证,但还不能直接生产上线。生产上线前必须完成真实 Shopify app link、真实店铺安装、生产数据库方案、第三方接口联调和真实测试订单验收。
锁定口径:
| 项目 | 最终口径 |
|---|---|
| App 类型 | Custom app,当前按单店交付 |
| 页面范围 | v1 只做 Thank You Page |
| Extension target | purchase.thank-you.block.render |
| 默认展示位置 | default_placement = "ORDER_STATUS1",商家仍可在 checkout and accounts editor 移动 |
| 前端职责 | 只取 orderId/orderNumber/locale 和 session token,调用后端 |
| 后端职责 | 校验 token、查订单、读配置、签名、调第三方、写日志和幂等 |
| 配置方式 | app-owned Metaobject $app:dreame_activity |
| 第三方成功码 | 通过 Metaobject 可配置,默认 0 |
| 幂等 | shop + orderId + activityId 唯一,不重复推送 |
| 失败行为 | 返回 success=false,前端不显示按钮,不影响原生 Thank You Page |
2. 最终业务流程
- 顾客完成支付后进入 Shopify Thank You Page。
- Checkout UI Extension
purchase.thank-you.block.render加载。 - 前端读取订单确认信息中的
orderId/orderNumber/locale。 - 前端通过
sessionToken.get()获取 Shopify checkout session token。 - 前端调用后端
POST /api/action/activity/check。 - 后端校验 session token,并通过 Admin GraphQL 查询完整订单。
- 后端读取
$app:dreame_activityMetaobject entry。 - 后端检查订单级幂等;已成功推送则直接返回按钮数据,不重复调用第三方。
- 后端签名调用第三方接口①活动状态接口。
- 活动有效后,后端生成 UUID v4
event_id,签名调用第三方接口②订单完成事件推送。 - 推送成功后写日志和幂等记录。
- 后端返回
success=true + redirectUrl + buttonText。 - 前端显示按钮,点击后跳转到配置的
redirect_url。
3. 系统组件
| 组件 | 位置 | 职责 |
|---|---|---|
| Shopify App 后端 | app/ |
Remix 后端、Shopify OAuth/session、业务 API、订单查询、配置读取、第三方调用 |
| Thank You Extension | extensions/thank-you-activity/ |
Thank You Page 按钮展示和后端调用 |
| 配置服务 | app/services/config.server.ts |
读取 $app:dreame_activity Metaobject |
| 主流程服务 | app/services/activity.server.ts |
状态检查、推送、幂等、返回按钮数据 |
| 第三方 client | app/services/dreame-client.server.ts |
mock/live 第三方接口调用 |
| 签名服务 | app/services/signature.server.ts |
MD5(bodyString + timestamp + api_secret) |
| 日志服务 | app/services/logger.server.ts |
写入调用日志并避免敏感信息外泄 |
| 幂等服务 | app/services/idempotency.server.ts |
防止同一订单重复推送 |
| Prisma 模型 | prisma/schema.prisma |
Session、ActivityPushLog、ActivityPushIdempotency |
4. 后端接口契约
接口:
POST /api/action/activity/check
请求头:
Authorization: Bearer <checkout session token>
Content-Type: application/json
请求体:
{
"orderId": "gid://shopify/Order/1234567890",
"orderNumber": "#1001",
"locale": "zh-CN"
}
成功响应:
{
"success": true,
"redirectUrl": "https://activity.example.com",
"buttonText": "领取奖励"
}
失败响应:
{
"success": false
}
说明:
- 前端不传
api_secret。 - 前端传入的订单字段只作为最小定位信息,不作为可信业务数据。
- 完整订单数据由后端通过 Shopify Admin GraphQL 查询。
- CORS 对 Checkout Extension 放开,来源可信由 session token 校验负责。
5. Shopify 配置
5.1 App scopes
当前 scopes:
read_orders,read_metaobjects,write_metaobjects,write_metaobject_definitions
用途:
| Scope | 用途 |
|---|---|
read_orders |
后端查询订单 email、customerId、currency、order number 等字段 |
read_metaobjects |
后端读取活动配置 |
write_metaobjects |
setup 脚本写入/更新配置 entry |
write_metaobject_definitions |
Shopify CLI 部署 app-owned Metaobject definition |
5.2 Extension target
文件:
extensions/thank-you-activity/shopify.extension.toml
当前配置:
[[extensions.targeting]]
module = "./src/Checkout.tsx"
target = "purchase.thank-you.block.render"
default_placement = "ORDER_STATUS1"
说明:
purchase.thank-you.block.render是当前最终 target。ORDER_STATUS1是默认推荐位置,不等于强制固定位置。- 商家可在 Shopify checkout and accounts editor 中移动该 block。
- 若业务要求固定 header 位置,需要改 target、改代码并重新部署,不是业务配置项。
6. Metaobject 配置
当前采用 app-owned Metaobject:
| 项目 | 值 |
|---|---|
| TOML key | [metaobjects.app.dreame_activity] |
| Admin API type | $app:dreame_activity |
| Handle | dreame-activity-config |
| Admin access | merchant_read_write |
| Storefront access | none |
字段:
| Key | Type | Required | 默认值 | 说明 |
|---|---|---|---|---|
activity_id |
number_integer |
yes | - | 追觅活动 ID |
api_app_id |
single_line_text_field |
yes | - | 第三方接口 app id |
api_secret |
single_line_text_field |
yes | - | 第三方签名 secret,仅后端读取 |
status_api_url |
url |
yes | - | 接口①活动状态地址 |
push_api_url |
url |
yes | - | 接口②推送地址 |
status_success_code |
number_integer |
no | 0 |
接口①成功 code |
status_active_required |
boolean |
no | true |
接口①是否必须 data.status=true |
push_success_code |
number_integer |
no | 0 |
接口②成功 code |
redirect_url |
url |
yes | - | 前端按钮跳转地址 |
button_text |
json |
yes | - | 多语言按钮文案 |
shop |
single_line_text_field |
no | myshopify domain | 覆盖传给第三方的 properties.shop |
button_text 示例:
{
"en": "Claim Your Reward",
"zh-CN": "领取奖励",
"zh-TW": "領取獎勵",
"de": "Belohnung erhalten",
"fr": "Réclamer votre récompense"
}
兼容说明:
- 新环境默认使用
$app:dreame_activity。 - 若目标店铺已经存在旧的 merchant-owned type
dreame_activity,可通过环境变量METAOBJECT_TYPE=dreame_activity兼容。 - 不建议新环境继续创建 merchant-owned
dreame_activity。
7. 第三方接口与签名
所有第三方请求都由后端发起,前端不直接调用第三方。
请求头:
dreame-api-app-id: <api_app_id>
dreame-api-timestamp: <unix ms>
dreame-api-sign: <md5 sign>
签名算法:
sign = MD5(bodyString + timestamp + api_secret)
实现要求:
bodyString必须是实际发送的 JSON 字符串。- 同一个
bodyString同时用于签名和fetchbody。 - 禁止签名后再次序列化 payload。
api_secret不进入前端、不进入响应体、不进入日志。
接口①活动状态:
{
"activity_id": 12
}
默认继续条件:
HTTP 2xx && code == status_success_code && data.status == true
如果 status_active_required=false,则不再要求 data.status == true。
接口②推送订单完成事件:
{
"activity_id": 12,
"event_type": "order_end",
"created_at": 1776901800000,
"user_id": "99999",
"email": "customer@example.com",
"event_id": "uuid-v4",
"properties": {
"shop": "demo-store.myshopify.com",
"currency": "USD",
"order_id": "123456",
"order_number": "#1001"
}
}
默认成功条件:
HTTP 2xx && code == push_success_code
8. 幂等和日志
幂等规则:
| 项目 | 口径 |
|---|---|
| 幂等粒度 | shop + orderId + activityId |
| 命中后行为 | 不重复调用第三方接口②,仍返回 success=true + redirectUrl + buttonText |
| event_id | 首次成功推送时生成 UUID v4 并落库 |
数据库表:
| 表 | 用途 |
|---|---|
ActivityPushLog |
记录成功/失败调用、请求体、响应体、错误信息 |
ActivityPushIdempotency |
记录成功推送的订单和活动,防止重复推送 |
日志要求:
- 不保存
api_secret。 - 不保存
dreame-api-sign。 - 失败需要记录可排查的
errorMessage。 - 日志保留天数由
LOG_RETENTION_DAYS控制,默认7。
9. 本地开发说明
前置:
- Node.js 20 或更高版本。
- pnpm/corepack 可用。
- Shopify CLI 可用。
安装和验证:
cd "/Users/jason/Downloads/myproject/Shopify 插件开发/dreame-action"
corepack pnpm install
corepack pnpm prisma:generate
corepack pnpm prisma:migrate
corepack pnpm test
corepack pnpm typecheck
corepack pnpm build
本地环境变量:
SHOPIFY_API_KEY=
SHOPIFY_API_SECRET=
SHOPIFY_APP_URL=
SCOPES=read_orders,read_metaobjects,write_metaobjects,write_metaobject_definitions
DATABASE_URL="file:./dev.sqlite"
THIRD_PARTY_MODE=mock
THIRD_PARTY_TIMEOUT_MS=5000
LOG_RETENTION_DAYS=7
METAOBJECT_TYPE=$app:dreame_activity
METAOBJECT_HANDLE=dreame-activity-config
DREAME_MOCK_STATUS_CODE=0
DREAME_MOCK_STATUS_ACTIVE=true
DREAME_MOCK_PUSH_CODE=0
启动开发:
corepack pnpm dev
说明:
- 默认
THIRD_PARTY_MODE=mock,不会真实调用第三方。 - 切换真实第三方接口前,必须先配置 Metaobject entry 和第三方密钥。
10. 上线前置条件
上线前必须准备:
| 类别 | 前置项 |
|---|---|
| Shopify | 真实测试/生产 Shopify app、Client ID、App secret |
| 店铺 | 目标店铺安装权限、测试订单能力、非 Starter 套餐 |
| 域名 | 后端公网 HTTPS 域名 |
| 数据库 | 生产数据库连接串和迁移窗口 |
| 第三方 | 接口①②正式/测试地址、api_app_id、api_secret、成功码规则、可验签样例 |
| 配置 | $app:dreame_activity definition 和 dreame-activity-config entry |
| Extension | Checkout UI Extension 已部署,app_url setting 指向后端域名 |
| 验收数据 | 成功订单、失败订单、活动关闭、重复刷新等测试场景 |
生产数据库特别说明:
- 当前
prisma/schema.prismadatasource provider 是sqlite,适合本地开发。 - 生产建议使用 Postgres。
- SQLite 切 Postgres 不是只改
DATABASE_URL,需要改 Prisma datasource provider、生成 migration,并在目标数据库执行迁移。 - 未完成生产数据库迁移前,不建议生产上线。
11. 上线步骤
11.1 安装依赖和基础质量门禁
corepack pnpm install
corepack pnpm prisma:generate
corepack pnpm test
corepack pnpm typecheck
corepack pnpm build
通过标准:
- 单测全部通过。
- TypeScript 无错误。
- Remix production build 成功。
11.2 链接 Shopify app
测试环境:
shopify app config link --config staging --client-id <staging-client-id>
生产环境:
shopify app config link --config production --client-id <production-client-id>
非交互配置校验:
shopify app config validate --client-id <client-id> --json
已链接环境校验:
shopify app config validate --config staging --json
shopify app config validate --config production --json
通过标准:
- validate 返回有效结果。
- app URL、redirect URLs、scopes 与目标环境一致。
- 不把
.shopify/、个人client_id、个人 extensionuid当作通用配置提交。
11.3 配置后端环境变量
生产环境建议:
SHOPIFY_API_KEY=<production-client-id>
SHOPIFY_API_SECRET=<production-app-secret>
SHOPIFY_APP_URL=https://<production-backend-domain>
SCOPES=read_orders,read_metaobjects,write_metaobjects,write_metaobject_definitions
DATABASE_URL=<production-database-url>
THIRD_PARTY_MODE=live
THIRD_PARTY_TIMEOUT_MS=5000
LOG_RETENTION_DAYS=7
METAOBJECT_TYPE=$app:dreame_activity
METAOBJECT_HANDLE=dreame-activity-config
禁止提交到 Git:
.env.env.staging.env.production- Admin access token
- Shopify app secret
- 第三方
api_secret - 数据库密码
11.4 部署后端服务
后端服务部署到托管平台后,先检查:
curl -i https://<backend-domain>/
curl -i -X OPTIONS https://<backend-domain>/api/action/activity/check
期望:
- 首页或健康页返回 200。
- OPTIONS 返回 204,并包含 CORS headers。
- 未带 session token 的 POST 不应成功推送。
11.5 部署 Shopify 配置和 Extension
测试环境:
shopify app deploy --config staging --allow-updates --message "staging deployment"
生产候选:
shopify app deploy --config production --no-release --message "production candidate"
生产发布:
shopify app deploy --config production --allow-updates --message "production release"
生产环境不要使用 --allow-deletes,除非已经明确确认要删除远端扩展或配置。
11.6 创建或更新 Metaobject entry
脚本:
SHOPIFY_SHOP_DOMAIN=<shop>.myshopify.com \
SHOPIFY_ADMIN_ACCESS_TOKEN=shpat_xxx \
corepack pnpm tsx scripts/setup-metaobject.ts
说明:
- 脚本使用
metaobjectUpsert。 - 重复执行会更新同一个 handle,不会创建重复 entry。
- 脚本里的默认值是示例值,上线前必须替换为真实业务配置。
11.7 配置 Extension setting
在 Shopify app 的 Checkout UI Extension 设置中配置:
app_url = https://<backend-domain>
然后在 checkout and accounts editor 中确认 block 已添加到 Thank You Page。
12. 上线验收
基础验收:
| 验收项 | 通过标准 |
|---|---|
| Extension 加载 | Thank You Page 中可以看到扩展区域 |
| 后端调用 | /api/action/activity/check 被调用 |
| Token 校验 | 未授权请求不能触发成功推送 |
| Metaobject 读取 | 后端读取到 $app:dreame_activity 配置 |
| 接口① | 签名正确,活动开启时继续 |
| 接口② | 只在接口①满足条件后调用 |
| 按钮展示 | 成功时显示本地化按钮 |
| 跳转 | 点击按钮跳转到 redirect_url |
| 幂等 | 同一订单重复刷新不重复推送 |
| 日志 | 成功/失败均有日志,不含密钥和签名 |
测试场景:
- 成功订单:活动开启、接口②成功,页面显示按钮。
- 活动关闭:接口①返回 inactive,页面不显示按钮。
- 接口②失败:页面不显示按钮,日志记录失败。
- 缺 email/customerId:不推送,返回
success=false。 - 重复刷新 Thank You Page:不重复调用接口②,仍显示按钮。
- 多语言:
zh-CN命中文案,未知 locale 回退en。 - 超时:第三方超时不阻塞 Shopify 原生页面。
13. 回滚方案
| 问题 | 回滚方式 |
|---|---|
| 后端发布异常 | 回滚托管平台 web app release |
| Shopify Extension 异常 | 在 Shopify Developer Dashboard 回滚到上一 app version |
| Metaobject 配置错误 | 修正 dreame-activity-config entry 后重新验证 |
| 第三方接口异常 | 测试环境可临时切回 THIRD_PARTY_MODE=mock;生产不建议长期 mock |
| 数据库迁移异常 | 停止发布,恢复数据库备份或回滚 migration |
14. 仍需确认后再迭代的事项
这些事项不能只靠业务配置解决,需要确认后改代码、改部署或单独排期:
| 事项 | 当前默认 | 后续动作 |
|---|---|---|
| 是否支持 Order Status Page | v1 不做 | 若要支持,单独设计 Customer Account/Order Status 实现 |
| 是否改成 header 固定位置 | 不改,继续 block target | 若要固定顶部,改 extension target 并重新部署 |
| 按钮是否必须新窗口打开 | 当前使用现有 Button to 行为 |
若必须 _blank,评估 UI extension 组件升级 |
| 生产数据库 | 建议 Postgres | 上线前确认 provider、连接串、migration |
| 是否增加 webhook 兜底 | v1 不做 | 若要求“未进入 Thank You Page 也推送”,二期加 orders/paid webhook |
接口②是否增加 lineItems/totalPrice |
当前不传 | 第三方确认字段格式后再改 payload |
trigger 无 order_end 时是否推送 |
当前主线按 order_end |
若第三方强依赖 trigger,需明确规则 |
| 是否做后台配置页 | v1 不做 | 二期用 Polaris App Home 管理配置 |
15. 最终检查清单
上线前逐项勾选:
corepack pnpm test通过。corepack pnpm typecheck通过。corepack pnpm build通过。- Shopify app 已 link 到正确 staging/production app。
shopify app config validate --json通过。- 后端 HTTPS 域名可访问。
- Shopify app URL 和 redirect URLs 已改成目标域名。
- 生产数据库方案和 migration 已完成。
$app:dreame_activitydefinition 已部署。dreame-activity-configentry 已填真实配置。- Extension
app_urlsetting 指向目标后端域名。 - 已使用真实测试订单完成成功/失败/重复刷新验证。
- 日志不含
api_secret、dreame-api-sign、Admin token。 - Git 中没有
.env、.shopify/、个人 app 绑定文件或密钥。
16. 参考资料
- Shopify Thank You block target: https://shopify.dev/docs/api/checkout-ui-extensions/latest/targets/thank-you/block
- Shopify Checkout UI Extension targets: https://shopify.dev/docs/api/checkout-ui-extensions/latest/targets
- Shopify Metaobject definitions: https://shopify.dev/docs/apps/build/metaobjects/manage-metaobject-definitions
- Shopify
metaobjectUpsert: https://shopify.dev/docs/api/admin-graphql/latest/mutations/metaobjectUpsert - Shopify CLI: https://shopify.dev/docs/api/shopify-cli