dreame-shopify-app/docs/追觅Shopify App 最终方案与上线说明.md

18 KiB
Raw Permalink Blame History

追觅 Shopify App 最终方案与上线说明

本文件是当前代码目录的最终版方案和上线主口径。 历史材料包括 README.mddocs/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. 最终业务流程

  1. 顾客完成支付后进入 Shopify Thank You Page。
  2. Checkout UI Extension purchase.thank-you.block.render 加载。
  3. 前端读取订单确认信息中的 orderId/orderNumber/locale
  4. 前端通过 sessionToken.get() 获取 Shopify checkout session token。
  5. 前端调用后端 POST /api/action/activity/check
  6. 后端校验 session token并通过 Admin GraphQL 查询完整订单。
  7. 后端读取 $app:dreame_activity Metaobject entry。
  8. 后端检查订单级幂等;已成功推送则直接返回按钮数据,不重复调用第三方。
  9. 后端签名调用第三方接口①活动状态接口。
  10. 活动有效后,后端生成 UUID v4 event_id,签名调用第三方接口②订单完成事件推送。
  11. 推送成功后写日志和幂等记录。
  12. 后端返回 success=true + redirectUrl + buttonText
  13. 前端显示按钮,点击后跳转到配置的 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 SessionActivityPushLogActivityPushIdempotency

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 同时用于签名和 fetch body。
  • 禁止签名后再次序列化 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-shopify-app"
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_idapi_secret、成功码规则、可验签样例
配置 $app:dreame_activity definition 和 dreame-activity-config entry
Extension Checkout UI Extension 已部署,app_url setting 指向后端域名
验收数据 成功订单、失败订单、活动关闭、重复刷新等测试场景

生产数据库特别说明:

  • 当前 prisma/schema.prisma datasource 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、个人 extension uid 当作通用配置提交。

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
幂等 同一订单重复刷新不重复推送
日志 成功/失败均有日志,不含密钥和签名

测试场景:

  1. 成功订单:活动开启、接口②成功,页面显示按钮。
  2. 活动关闭:接口①返回 inactive页面不显示按钮。
  3. 接口②失败:页面不显示按钮,日志记录失败。
  4. 缺 email/customerId不推送返回 success=false
  5. 重复刷新 Thank You Page不重复调用接口②仍显示按钮。
  6. 多语言:zh-CN 命中文案,未知 locale 回退 en
  7. 超时:第三方超时不阻塞 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
triggerorder_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_activity definition 已部署。
  • dreame-activity-config entry 已填真实配置。
  • Extension app_url setting 指向目标后端域名。
  • 已使用真实测试订单完成成功/失败/重复刷新验证。
  • 日志不含 api_secretdreame-api-sign、Admin token。
  • Git 中没有 .env.shopify/、个人 app 绑定文件或密钥。

16. 参考资料