
这篇文章记录的是我如何把一个 Shopify 应用从“想解决什么问题”推进到“具备上线条件,并接受真实环境和平台审核验证”的完整过程。
文章里我会尽量从产品、工程、方法论和上架流程四个层面展开。出于隐私和安全考虑,我不会展开真实商家数据、内部成本明细、密钥、内部地址和具体风控阈值。我是一个在职全栈开发者,同时也在以独立开发者的身份尝试做自己的产品。做 MarginBundle 的初衷,一方面是想把自己在开发工作和 Shopify 项目里积累到的经验真正落成一个产品,另一方面也是想尝试从“写代码交付需求”走向“做一个可以被商家安装、使用、付费的独立应用”。如果它未来能真正售卖成功,那对我来说会是一次很重要的验证:验证自己的产品判断,也验证自己能否独立完成从开发、上架到商业化的完整闭环。
产品官网:https://app.marginbundle.cc/
App Store 页面:https://apps.shopify.com/marginbundle2026 年 9 月更新: 产品近期做了一次重要收敛。过去我更容易按 Bundle、Add-on、推荐、客户分组等功能模块描述它;现在更明确的主线是:以Offers 工作台为核心,从“多买多省、组合购买、买赠奖励”三个常用结果进入优惠创建,用商品和成本数据做利润保护,通过 Storefront 与 Shopify Checkout 完成一致执行,再用归因和 Analytics 复盘效果。推荐和活动 playbook 是创建 Offer 的输入,Customers 是定向能力,实验、Referral 和 AI 文案则退到高级或后续验证区域。
当前状态说明: 截至本文更新时,代码、生产部署和仓库内的大部分质量门禁已经完成;真实 billing 全流程证据、最终商店截图与英文演示、私密审核说明和完整审核矩阵仍需在外部环境中验收。因此,下文记录的是上架实战与准备过程,不代表应用已经通过 Shopify 审核或完成商业验证。
我对 Shopify 这件事的理解,并不是一开始就很清楚。大概是在工作里接触 Shopify 接近一年的时间之后,我才慢慢意识到,这个平台背后真正值得做的,不只是“接一个店铺、做一些配置、出一些页面”,而是它承载的是一整套真实的商业流程。
最开始的时候,我更多是从执行侧接触它:看它怎么接入,怎么配置,怎么和现有系统配合,怎么处理一些实际问题。那时候我还没有特别明确的产品想法,只是隐约感觉到,这个平台的复杂度不是普通电商页面能概括的。后来随着接触越来越多,我开始在工作里和运营同事打交道,也开始注意到很多很具体的需求和痛点:某些优惠明明看起来能跑,实际落地却会影响利润;某些方案在前台很好看,但一到结算就和预期不一致;有些运营动作做完了,却根本不知道它到底有没有带来真实效果。
这些都是很日常、很具体的事,但恰恰是这些事让我意识到,Shopify 上真正有价值的产品,不是把某个按钮做出来,而是把“策略、执行、反馈、复盘”做成一个闭环。
所以 MarginBundle 不是我拍脑袋发明出来的一个概念,它更像是我在工作里不断观察、不断积累、不断碰到问题之后,慢慢长出来的一个答案。
从在职全栈开发者转向独立产品尝试的角度看,这件事对我还有另一层意义。我不只是想“学一下 Shopify 插件怎么做”,也不只是想做一个 demo 证明自己能接 API、能写页面、能跑通流程。我更想知道的是:一个人能不能从真实问题出发,做出一个足够完整、足够可信、未来有机会被真实商家购买的产品。
这个目标让我对项目的要求变高了。因为如果只是学习项目,很多地方可以点到为止;如果是准备售卖的产品,就必须考虑安装、权限、体验、计费、审核、支持、失败恢复和长期维护。也正是因为我希望它最终能走向售卖,MarginBundle 才不能只停留在“功能能跑”的程度。
如果把这件事说得更具体一点,我对 MarginBundle 的想法,大概经历了三个阶段。
一开始我并不是从“我要做一个产品”开始的,而是从“我要把眼前的事情做好”开始的。工作让我有机会接触 Shopify,也让我看到了它在真实业务中的运行方式。这个阶段最重要的收获不是代码,而是直觉。
但独立开发者的想法也在这个过程中慢慢冒出来。做开发久了之后,我会自然产生一个问题:我能不能不只是完成别人定义好的需求,而是自己发现一个问题、定义一个产品、把它做出来,并且真的拿到市场上去验证?Shopify 之所以吸引我,正是因为它有真实商家、真实场景、真实 App Store,也有一套相对清晰的插件商业化路径。
我开始逐渐发现,Shopify 不是一个孤立的建站工具,它更像是一个商家经营系统的一部分。只要你往前多走一步,就会碰到订单、商品、库存、客户、优惠、渠道、分析、权限、审核、合规,甚至还有跟外部工具和团队协同相关的问题。也就是说,任何一个“小功能”背后,都可能牵出一整串业务链路。
后来我开始和运营同事打交道,这一步很关键。因为只有在和运营交流时,很多之前停留在“技术想象”里的问题才会变成“业务现实”。
运营关注的不是系统内部有多少模块,而是:
这些问题很朴素,但正因为朴素,才最接近产品的价值核心。它让我越来越确信,真正值得做的,不是再造一个“优惠配置器”,而是做一个能让商家和运营放心使用的增长工具。
在有了初步想法之后,我也做了一些市场调研,想确认自己的判断是不是只是工作环境里的局部经验。
调研之后我得到的结论很明确:市场上并不缺“优惠工具”,但真正把利润、风控、执行和分析放在一起考虑的产品并不多。很多产品要么偏展示,要么偏规则,要么偏执行,要么偏报表,但少有产品能把这些事情完整连起来。换句话说,市场上有很多“点”,但缺少一条清晰的“线”。
这条线差不多就是 MarginBundle 想去验证的东西。
如果把这个过程画成一条线,它大概不是“灵感突然出现”,而是这样一步步推出来的:
这张图对我来说挺重要。因为它提醒我,MarginBundle 不是一个“我想做点什么”的个人冲动,而是一个从真实工作环境、真实运营交流和真实市场观察里慢慢抽象出来的判断。
一开始,我脑子里的想法其实很模糊。我只是觉得 Shopify 生态里一定还有很多可以做的东西,但到底做什么,不能只靠直觉。后来我逐渐把问题收敛成一个更具体的命题:
能不能做一个帮助 Shopify 商家在提升客单价时,同时看见利润风险、执行状态和效果反馈的工具?
这句话看起来不复杂,但它其实把产品边界定住了。
首先,它不是单纯做折扣。因为“折扣”只是动作,不是结果。商家真正关心的是这次动作有没有带来更好的经营结果。其次,它不是单纯做推荐。推荐只是入口,真正重要的是推荐之后有没有加购、有没有转化、有没有伤害利润。第三,它也不是单纯做报表。报表只能解释过去,不能帮助商家在发布前做更好的判断。
所以我最后把 MarginBundle 的产品命题写成了三个关键词:
| 关键词 | 我对它的理解 | 对应到产品里的能力 |
|---|---|---|
| 增长 | 帮商家提高客单价和转化机会 | 多买多省、组合购买、买赠奖励,以及次级入口中的直接降价和客户专属价 |
| 利润 | 不让增长动作变成无意识让利 | 成本完整度、毛利预估、风险提示、发布确认 |
| 闭环 | 让策略从创建到执行再到复盘都能被追踪 | Storefront 展示、Discount Function、事件采集、归因、Analytics |
这三个词里,我最看重的是“利润”和“闭环”。因为很多工具都能帮助商家“做一个优惠”,但不是所有工具都能帮助商家判断“这个优惠值不值得做”。
从这里开始,MarginBundle 的方向才真正清晰起来:它不是一个简单的营销插件,而是一个利润保护型增长工具。
如果只是从技术角度看 Shopify,很容易把注意力放在 API、OAuth、Webhook、Theme Extension、Checkout Function 这些东西上。但真正推动我做 MarginBundle 的,其实不是某个 API,而是我在工作里反复看到的一些“运营现场”。
我这里说的运营现场,不一定是轰轰烈烈的大问题,很多时候只是一些很小的讨论:某个活动要不要上,某个折扣是不是太深,某个商品适不适合做组合,某个入口为什么没人点,某个活动看起来有销量但利润到底怎么样。这些问题如果单独看,每个都不难;但它们反复出现之后,就会让我意识到:运营真正缺的不是一个按钮,而是一套判断系统。
我以前会觉得,优惠系统的核心是规则设计:满减、折扣、组合、加购、买几件送多少。后来接触多了才发现,真正困难的不是把规则写出来,而是判断这个规则值不值得发布。
运营在做活动时,通常会同时考虑很多事情:
这些问题很难靠一个简单表单解决。一个只让商家填写“折扣类型”和“折扣值”的系统,只是帮商家完成了动作,却没有帮商家完成判断。
这也是为什么我后来越来越在意“发布前校验”。我希望系统不是等活动失败之后才告诉商家“这里有问题”,而是在活动发布前就把风险亮出来。
可以这样理解:
| 传统思路 | 我更想做的思路 |
|---|---|
| 先让用户创建优惠 | 先帮助用户判断优惠是否合理 |
| 折扣配置完成就发布 | 成本、库存、毛利、计划限制都通过后再发布 |
| 活动失败后再复盘 | 发布前先做风险提示,发布后再做效果复盘 |
| 系统只是执行工具 | 系统参与运营决策 |
这张表背后的差异,其实就是“工具”和“产品”的差异。工具强调完成动作,产品强调降低决策成本。
在电商系统里,前台展示很容易给人一种错觉:用户看到了优惠,就等于优惠已经成立。但实际不是这样。用户看到的是展示,真正生效的是 checkout。
如果前台组件里写了太多规则,系统就会变得很危险。因为前台脚本可以展示、可以引导、可以收集事件,但它不应该成为最终价格规则的唯一来源。尤其是在 Shopify 里,checkout 是一个非常关键的边界。优惠如果不能在 checkout 正确执行,前台做得再漂亮也不可靠。
这件事让我形成了一个很明确的判断:
前台应该负责“让用户理解”,checkout 应该负责“让规则生效”,数据系统应该负责“解释结果”。这三个角色不能互相替代。
后来我在做 MarginBundle 时,也一直用这个原则约束自己:Storefront widget 可以灵活,但不能泄露内部规则;Discount Function 可以严格,但不能依赖前台状态;后端配置可以统一,但不能让 public endpoint 变成敏感信息出口。
运营做完活动之后,最常问的问题其实不是“有没有订单”,而是“为什么有订单”或者“为什么没有订单”。
如果一个 bundle 活动没有效果,原因可能很多:
如果系统只给一个订单结果,运营几乎无法判断下一步该怎么改。是换商品?是改折扣?是换展示位置?是调整推荐逻辑?还是活动本身就不该做?
所以我在设计 MarginBundle 时,把事件和归因放进了产品闭环里。它不是“高级功能”,而是一个增长工具必须具备的解释能力。
可以用漏斗来理解:
这个漏斗不是为了做一个漂亮报表,而是为了回答运营问题。真正的分析不是堆数据,而是让人知道下一步怎么做。
工作里接触运营越多,我越发现一个现象:很多时候用户抱怨“缺功能”,背后其实是流程断裂。
比如商家说“我想要更灵活的 bundle”,可能真正的问题不是 bundle 类型不够多,而是现在的配置方式不支持他快速验证一个活动。商家说“我想看更多数据”,可能真正的问题不是报表字段不够多,而是现有数据无法解释一次活动从展示到成交的路径。商家说“我不确定这个优惠能不能上”,可能真正的问题不是缺少一个确认按钮,而是缺少成本和毛利视角。
这让我在做产品判断时会多问一层:这个需求是在要一个功能,还是在暴露一个流程断点?
| 表面需求 | 可能的真实问题 | MarginBundle 的对应思路 |
|---|---|---|
| 想创建更多优惠类型 | 当前活动表达能力不足 | 用统一 Offers 工作台承载不同折扣结果和活动模板 |
| 想看更多数据 | 不知道活动为什么有效或无效 | 事件、归因、Analytics |
| 想快速上活动 | 配置和风险判断成本太高 | 发布前校验、健康提示 |
| 想给特定客户优惠 | 客户分层运营不方便 | Customer group discount |
| 想让前台更好看 | 用户没有理解优惠价值 | Storefront widget、样式构建器和主题区块覆盖 |
这张表是我做产品时非常依赖的一种思考方式:不要停在用户说出来的需求,而要往后追一层,看它到底是哪个流程断了。
做市场调研时,我并没有期待发现一个完全没人做过的方向。相反,Shopify App Store 里相关工具很多,优惠、bundle、upsell、cross-sell、discount、subscription、loyalty、analytics,各种产品都有。真正的问题不是“有没有工具”,而是这些工具各自解决了链路中的一部分,却不一定把整条链路打通。
这对我来说是一个很重要的认知:创业或做独立产品,不一定要找到一个完全没人碰过的荒地,更现实的机会往往来自“已有需求但体验被切碎了”。
我当时观察到的产品大概可以分成几类:
| 类型 | 常见能力 | 我看到的局限 |
|---|---|---|
| Bundle 工具 | 创建组合、展示组合、设置折扣 | 很多更关注展示和配置,对利润风险关注不足 |
| Upsell / Cross-sell 工具 | 商品页推荐、购物车加购、弹窗 | 容易偏转化组件,checkout 规则和归因不一定完整 |
| Discount 工具 | 满减、折扣码、自动折扣 | 偏规则执行,缺少前台体验和经营分析 |
| Analytics 工具 | 报表、转化率、订单数据 | 能解释结果,但不直接参与活动创建和风险控制 |
| Customer segment 工具 | 标签、分组、人群运营 | 和优惠执行、前台展示之间经常需要额外拼接 |
这些产品都不是“不好”,恰恰相反,它们说明市场需求是真实存在的。但我看到的问题是:对中小商家或运营团队来说,如果一个增长动作需要同时拼多个工具,决策成本会变高,数据也会变散。
MarginBundle 想做的不是替代所有工具,而是在一个明确场景里把关键链路收拢:
这条链路形成闭环之后,产品就不再只是“帮你发优惠”,而是“帮你更有把握地运营优惠”。
在方向逐渐清晰后,我给 MarginBundle 定了几条产品原则。这些原则不是写给别人看的口号,而是我自己在做取舍时用来判断“该不该做、先做什么、怎么做”的准绳。
很多系统会在活动结束后告诉你表现如何,但我更希望系统在发布前就提醒你风险。因为对商家来说,亏损不是一个“复盘指标”,而是一个应该尽量提前避免的运营问题。
这并不意味着系统要替商家做所有决定,而是系统应该把重要信息提前摆出来。商家可以选择冒险,但这个冒险应该是被看见、被确认、被记录的。
这是底线。前台可以做得漂亮,可以做个性化,可以做多语言,但不能和 checkout 规则脱节。如果用户在前台看到一个优惠,到了结账时却不能正确生效,产品信任就会被破坏。
所以我宁愿在架构上更麻烦,也要让 checkout 侧有真实执行能力。
系统内部可以有很多状态、队列、任务、配置版本、归因逻辑,但商家不应该被迫理解这些东西。商家需要看到的是清晰的状态:这个活动是否可发布,风险在哪里,效果如何,下一步可以做什么。
复杂性应该被工程吸收,而不是转嫁给用户。
Storefront 和 public endpoint 面向的是更开放的环境,这一层不能假设请求一定可信,也不能假设失败一定可控。所以我更倾向于安全降级:验证失败返回空结果,限流后返回安全响应,错误信息不暴露内部细节。
这听起来很保守,但对一个多租户 Shopify 应用来说,保守就是专业。
如果一个功能不能帮助商家更好地创建、执行、分析或调整活动,我会重新思考它是不是核心能力。因为产品很容易被功能列表带偏,尤其是当你看到竞品有很多能力时,会忍不住想都做。但真正重要的是链路,而不是数量。
我可以把这些原则整理成一张表:
| 产品原则 | 背后的问题 | 工程上的体现 |
|---|---|---|
| 利润前置 | 活动发布后才发现亏损太晚 | 成本、毛利、风险校验 |
| 执行一致 | 前台展示和 checkout 不一致会破坏信任 | Storefront + Discount Function 分层 |
| 复杂性内收 | 用户不该理解内部状态机 | Admin 给出清晰状态和下一步动作 |
| 安全保守 | public endpoint 天然暴露 | 验证、限流、安全降级 |
| 闭环优先 | 功能堆叠不等于产品价值 | 事件、归因、分析、复盘 |
这些原则也会贯穿后面所有技术选择。
当一个方向开始清晰之后,另一个问题马上出现:到底第一版做什么?
这是我觉得非常难的一步。因为 Shopify 生态里的可做点太多了,只要开始展开,就会发现每个方向都能继续挖:bundle 可以做得很复杂,推荐可以做得很智能,客户分组可以做得很细,分析可以做得很深,A/B 实验也可以越做越完整。如果不控制范围,产品很容易变成一个“什么都想做”的大拼盘。
我后来给第一版定范围时,尽量遵循一个原则:先打通主链路,再增加局部深度。
主链路是什么?对 MarginBundle 来说,我认为是:
只要这条链路没有打通,任何单点能力做得再漂亮都不算真正完整。比如只做了 bundle 配置,但 checkout 不生效,那不完整;只做了前台展示,但没有归因,那只能算展示组件;只做了报表,但不能反向影响活动决策,那就不是运营闭环。
所以我把第一版范围拆成三类:
| 类型 | 是否第一版必须 | 原因 |
|---|---|---|
| 安装、鉴权、店铺状态 | 必须 | 没有它应用无法进入商家工作流 |
| 商品和成本基础数据 | 必须 | 利润保护必须依赖成本和商品结构 |
| 核心价格型 Offers | 必须 | 多买多省、组合购买和买赠奖励构成三个常用创建入口 |
| Storefront widget | 必须 | 没有前台入口,用户无法感知活动 |
| Discount Function | 必须 | 没有 checkout 执行,优惠不可信 |
| 事件和归因 | 必须 | 没有反馈,就无法复盘活动价值 |
| 推荐与活动 playbook | 支撑能力 | 用于加快 Offer 创建,不作为独立产品中心 |
| 完整实验平台 | 高级能力 | 先保留兼容入口,不让它干扰核心产品叙事 |
| 更细的人群自动化 | 可以迭代 | 需要建立在客户分组和事件基础上 |
这个范围控制让我避免了一个常见陷阱:为了让产品看起来更强,过早做很多高级功能,但主链路却不稳定。
对我来说,第一版最重要的是证明一件事:MarginBundle 能不能帮助商家从“创建一个优惠”走到“知道这个优惠有没有价值”。只要这条链路成立,后面的能力才有继续扩展的意义。
做这个项目时,我对 MVP 有了一个新的理解。以前我很容易把 MVP 理解成“砍功能”,也就是把大产品缩成小产品。但这次我更愿意把 MVP 理解成“保留最小闭环”。
两者差别很大。
如果只是砍功能,很可能会砍掉关键链路,只留下一个看似简单但其实无法验证价值的东西。比如只做一个 bundle 配置页,看起来是 MVP,但它不能证明前台能否转化,不能证明 checkout 是否一致,也不能证明商家是否能复盘。
而最小闭环的思路是:功能可以少,但链路必须完整。第一版可以只支持几种核心优惠类型,可以只做基础分析,可以先不做过于复杂的推荐模型,但从安装、配置、发布、展示、执行、归因、复盘这条线必须走通。
我后来把它总结成这样:
| MVP 误区 | 更适合 MarginBundle 的做法 |
|---|---|
| 少做几个页面就是 MVP | 保留从配置到复盘的最小闭环 |
| 先做看得见的 UI | 先确保规则、执行和数据能闭合 |
| 先做最容易实现的功能 | 先做最能验证产品命题的功能 |
| 后面再补安全和运维 | 多租户、安全、worker、webhook 从第一天就考虑 |
这个理解也影响了技术实现:我没有把后端、worker、discount function、public endpoint 当成“以后再补”的东西,而是把它们作为主链路的一部分来设计。
如果把 MarginBundle 只理解为“优惠插件”,设计会很浅;如果把它包装成无所不包的“增长平台”,产品又会失去重心。现在我更愿意把它定义为一个利润保护型 Offers 工作台:围绕活动创建、执行和复盘,吸收必要的经营复杂度,但不假装替代商家的全部营销系统。
商家在做组合、买赠、数量折扣或客户专属价时,最怕的是拍脑袋。很多运营方案看上去热闹,但没有任何预估能力:不知道库存够不够,不知道成本全不全,不知道哪些商品会把毛利压到危险区,也不知道一个活动发布出去后会不会和其他活动冲突。
所以产品必须先解决“怎么做决策”:
这决定了 MarginBundle 的 UI 和流程不能只是“配置表单”,而要带有明显的决策辅助属性。
很多应用会把重点放在 storefront widget 上,但真正的可靠性不在这里。前台只能负责推荐、提示、引导和承接;真正的折扣生效,必须落到 Shopify 的 checkout 和 Discount Function 里。也就是说,前台的任务是表达意图,checkout 的任务是执行规则。
这个判断很重要,因为它会直接影响架构:
如果这条链路断了,应用就会变成“看起来会优惠,实际上不一定生效”的半成品。
早期设计里,我曾把 BOGO、赠品、加购、交叉销售等营销名称都做成同一级入口。功能看起来很多,商家却要先理解这些词之间的差异,最后还可能发现它们底层配置几乎相同。
经过几轮调整,当前创建页先给出三个常用购买结果:
直接商品降价和客户专属价仍然存在,但放在次级入口,避免压过最常用的场景。内部则保留五个可执行规则族:商品直接降价、奖励商品、数量/阶梯优惠、组合/混搭优惠、客户定价。商家不需要先学习这套内部分类,系统只需要用它保证表单、利润预估、Storefront 和 Checkout 对同一条规则有一致理解。
买赠也不再拆成“免费赠品、Buy X get Y、BOGO”三个流程:奖励折扣为 100% 就是免费赠品,X=1、Y=1 就具有 BOGO 语义,标准奖励数量按 floor(购买数量 / X) × Y 计算,并可以设置每单上限。一个更关键的边界是:如果推荐不改变价格,它就不应该进入产品折扣流程,也不需要承担 Checkout 折扣契约。这个收敛让我意识到,好的产品分类不是为了展示能力数量,而是为了让商家更快做出正确选择。
真正上线后,问题不会结束,反而才刚刚开始。活动会变、商品会变、库存会变、客户分组会变、商家会要求看更细的数据,甚至某个 webhook、某个同步、某个后台任务都可能影响到体验。一个能长期运行的 Shopify 应用,必须从一开始就考虑运维、审计、恢复和可观测性。
所以 MarginBundle 的定位不是“做一个功能点”,也不是“包办所有增长动作”,而是把利润保护型 Offer 做成一个能被持续运营的产品。
如果用一张表来表达 MarginBundle 和普通优惠插件的区别,我会这样写:
| 维度 | 普通优惠插件 | MarginBundle 想解决的问题 |
|---|---|---|
| 产品目标 | 创建折扣 | 在利润边界内做增长 |
| 关注阶段 | 配置和展示 | 配置、校验、发布、执行、归因、复盘 |
| 前台职责 | 显示优惠 | 引导用户理解和触发优惠 |
| Checkout 职责 | 有时依赖前台逻辑 | 通过 Shopify checkout / Function 执行真实折扣 |
| 数据价值 | 看基础转化 | 看入口、点击、加购、订单归因和活动效果 |
| 风险控制 | 往往后置 | 发布前提示成本、库存、毛利和计划限制 |
| 长期运营 | 依赖人工观察 | 依赖状态、任务、健康检查和补偿机制 |
这张表背后的想法是:我不想做一个只在“创建优惠”这个点上发力的产品,而是想做一个能把优惠动作放进经营流程里的产品。
产品定位可以进一步拆成一个分层模型:
这套模型也影响了后面的工程架构。因为一旦产品不是单点功能,工程上就必须围绕链路来设计,而不是围绕页面来设计。
我最终没有继续按历史页面罗列功能,而是围绕商家的工作流重新组织能力。产品主导航只保留日常运营真正需要的几块:Dashboard、Offers、Products & Costs、Analytics、Customers、Settings;Plans & Billing 和 Support 作为明确的管理与服务入口存在,推荐、playbook 等能力则回到它们真正产生价值的环节。
Admin 是产品的核心控制台,主要承载以下能力:
这里的关键不是“页面多”,而是“流程清楚”。商家进入系统后,应该很快知道自己现在处于什么状态,哪些信息还缺,哪些优惠可发布,哪些地方有风险。
前台的责任不是替代后台,而是承接商家的意图,把优惠以合适的方式呈现给用户。比如商品页上的组合优惠、购物车里的加购提示、买赠进度和不同位置的优惠展示,都属于这一层。商家可以在应用设置里通过受约束的样式构建器调整布局、颜色、间距和可见元素,也可以继续在 Shopify Theme Editor 中对单个区块做覆盖。
这一层必须满足两个原则:
换句话说,前台是“表现层”,不是“规则层”。
如果只是前台展示,而 checkout 没有真实执行,那就不是完整产品。MarginBundle 里通过 Shopify Discount Function 去执行真实折扣,保证展示和结算一致。
这一步是产品可信度的分水岭。因为一旦 checkout 侧和前台侧不一致,所有运营配置都会变得脆弱。
没有归因和分析,优惠就只是偶然。MarginBundle 把事件、订单归因、日维度指标和活动复盘放在同一条链路里,让商家能够看到:
产品的价值不是“发出一个优惠”,而是“知道这个优惠值不值得继续发”。
商品同步、成本导入、事件聚合、webhook 后处理、发布修复,这些都不适合直接卡在用户请求里。它们应该交给 worker 处理。这样系统主路径更轻,失败也更容易重试和补偿。
这背后的产品逻辑其实很简单:用户感知到的是“稳定”,不是“我看到后台在忙”。稳定感来自于异步化和可恢复。
把这些能力放在一起,MarginBundle 的能力地图大概是这样的:
这张图里,我最想表达的是“能力之间的关系”。比如 Admin 不是孤立后台,Storefront 不是孤立组件,Checkout 也不是最后才加的技术点。它们应该从一开始就被设计成一条完整链路。
如果用更工程化的方式描述,这条链路可以写成:
这样拆开之后,读者会更容易理解为什么一个看似“做优惠”的产品,最后会牵扯到这么多系统边界。
如果要总结 MarginBundle 的工程方法,我会概括成四句话:
我会先问:这件事属于 Admin、Storefront、Checkout Function,还是 Worker?
这个问题比“该怎么写代码”更重要。因为一旦边界错了,后面所有实现都会混乱。比如:
这是一个很稳定的分层方式,后续扩展时也不会乱。
优惠系统最容易犯的错,就是先把界面做漂亮,再补规则。我的顺序正好相反:先把规则、风控、发布条件、撤销条件、失败回退写清楚,再做 UI。因为 UI 只是承载,规则才是产品本体。
这个产品天然是和外部平台交互的,涉及 Shopify session、webhook、app proxy、discount function、worker、数据库、缓存和队列。只要链路变长,失败就是常态,不是异常。
所以我会习惯性问这些问题:
多租户是这类产品最基础也最容易出问题的地方。我的原则是:所有商家数据都必须以 shopId 为边界,或者通过 shop-scoped parent 做归属校验。客户端传来的 ID 不能直接信任。
这个原则看似朴素,但它决定了系统是否真的安全。
我后来发现,工程边界其实可以用一张很简单的图来表达:
我希望这个系统里没有哪一层“什么都管”。如果 route 里塞太多业务,后面一定会难以维护;如果 storefront 里塞太多规则,安全和一致性都会出问题;如果 worker 逻辑没有幂等,失败重试时就容易制造第二次错误。
所以我在做 MarginBundle 时,会反复回到一个很朴素的问题:这段逻辑到底应该属于哪一层?只要这个问题想清楚,很多代码组织上的纠结就会少很多。
先把技术环境讲清楚。MarginBundle 本质上是一个嵌入 Shopify Admin 的应用,同时又有 storefront 组件、checkout function、后台任务和数据库状态管理,所以它不是一个单纯的前后端项目,而是一个多运行面系统。
我用到的技术可以先概括成一张表:
| 运行面 | 技术/框架 | 主要作用 |
|---|---|---|
| Web App | React、React Router 7、Vite、TypeScript | 嵌入式 Admin 和公开路由 |
| Shopify 集成 | Shopify App React Router、App Bridge、Shopify CLI | OAuth、嵌入式体验、本地开发 |
| 数据层 | Prisma、PostgreSQL | 多租户业务数据、商品、优惠、事件、归因 |
| 队列层 | BullMQ、Redis | 商品同步、成本导入、事件聚合、webhook 后处理 |
| Storefront | Shopify Theme App Extension | 商品页和购物车里的优惠展示 |
| Checkout | Shopify Discount Function | 在 checkout 阶段执行真实折扣 |
| 测试 | Vitest、Playwright | 业务逻辑和端到端流程验证 |
| 发布保障 | 配置检查、编码检查、release readiness | 上架前质量门禁 |
这套取舍背后的原则是:关键链路尽量用 Shopify 生态内稳定的方式去做,不在平台外自己造规则。尤其是折扣执行,我更愿意放在 Shopify checkout / Function 里,而不是让前端脚本去“模拟优惠”。
从部署角度看,它至少包含四个核心部分:
只要系统里有 worker,就不能把“网页能打开”当成应用健康。Web 正常只能说明用户能访问页面,worker 正常才说明后台任务、同步、聚合和补偿链路真的在跑。
如果从运行链路看,MarginBundle 是把 Shopify 平台能力、商家后台、店铺前台、结账规则和后台任务连起来的系统。
| 组件 | 我让它负责什么 | 不让它负责什么 |
|---|---|---|
app/routes/ |
请求接入、参数解析、权限判断、响应输出 | 复杂业务规则 |
app/services/ |
校验、状态转换、数据库读写、外部 API 编排 | UI 细节 |
app/lib/ |
环境变量、日志、加密、请求安全、公共工具 | 业务概念 |
app/workers/ |
异步任务、重试、聚合、补偿 | 同步响应路径 |
extensions/ |
Storefront 展示和 checkout 执行 | 泄露成本、毛利和内部规则 |
这个分工不花哨,但它让项目后面能继续长。
数据不是一堆散配置,而是围绕 shop 边界组织的关系型数据。核心关系可以简化成:
这里有一个产品语言和内部模型的区别:商家看到的是 Offer,数据库仍沿用较早的 Bundle 模型名。现阶段先校正用户心智和工作流,比为了命名一致立刻做高风险的数据迁移更重要。
无论名称如何,我最在意的都是每次读写必须带上 shopId。在多租户产品里,findUnique({ id }) 这种写法看起来简单,但风险更高;我更倾向于这样:
const bundle = await prisma.bundle.findFirst({
where: { id: bundleId, shopId }
});
if (!bundle) throw new Error("Bundle not found.");
async function createBundleDraft(input) {
const shop = await requireShop(input.request);
const prepared = await prepareBundleWrite({ shopId: shop.id, form: input.form });
const bundle = await prisma.bundle.create({
data: {
shopId: shop.id,
title: prepared.title,
type: prepared.type,
status: "DRAFT",
riskLevel: prepared.profit.riskLevel,
items: { create: prepared.items },
profitSnapshots: { create: prepared.profitSnapshot }
}
});
await enqueueOfferHealthCalculation({ shopId: shop.id, bundleId: bundle.id });
return bundle;
}
顺序很重要:先拿 shop,再校验归属,再写状态,不能反过来。
async function publishBundle({ shopId, bundleId, confirmedRisk }) {
const bundle = await findBundleForShop({ shopId, bundleId });
const validation = await validateBundleBeforePublish({ shopId, bundle, confirmedRisk });
if (!validation.ok) return validation;
await markBundlePublishing({ shopId, bundleId });
try {
await activateShopifyDiscount({ shopId, bundleId });
await syncFunctionConfig({ shopId });
await markBundleActive({ shopId, bundleId });
} catch (error) {
await markBundlePublishFailed({ shopId, bundleId, error });
throw error;
}
}
发布不是一次写库,而是一段带外部依赖、可失败、需要补偿的业务事务。
前台能看到的是“这个优惠怎么呈现”,不能看到“系统为什么这么算”。所以 public 响应里不能带成本、毛利、内部分数或 token。
function validatePublish(input) {
const errors = [];
const warnings = [];
if (!input.placements.length) errors.push("缺少展示位置");
if (!input.inventoryAvailable) errors.push("库存不可用");
if (input.missingCostCount > 0) warnings.push("成本数据不完整");
const risky = input.riskLevel === "LOW_MARGIN" || input.riskLevel === "LOSS";
if (risky && !input.confirmedRisk) warnings.push("当前优惠存在低毛利或亏损风险,需要确认");
return { ok: errors.length === 0 && (!risky || Boolean(input.confirmedRisk)), errors, warnings };
}
公开接口更保守一点:
if (!verifyAppProxyRequest(request) && !isTrustedFallbackRequest(request, shop)) {
return Response.json({ offers: [] }, { status: 403 });
}
if (isRateLimited(request)) {
return Response.json({ offers: [] }, { status: 429 });
}
我的想法很简单:公开接口不求“聪明”,先求“别泄露、别串店、别被刷爆”。
await queues.catalogSync.add("sync-products", { shopId, reason: "webhook" });
worker 侧永远重新校验 shop 和状态:
worker.process(async (job) => {
const { shopId, resourceId } = job.data;
const record = await findShopScopedRecord({ shopId, resourceId });
if (!record) return;
await performIdempotentWork(record);
});
我把 worker 看成一个稳定性部件,而不是性能优化部件。它负责长任务、重试、补偿和状态修复。
我没有把测试写成“越多越好”,而是按风险分层:
| 风险类型 | 更适合的测试方式 |
|---|---|
| 纯业务规则 | 单元测试 / service test |
| 路由输入输出 | route test |
| Storefront 交互 | Playwright E2E |
| Admin 工作流 | Admin E2E / component test |
| Function 行为 | Function test |
| 发布前配置 | 脚本检查 |
发布前我会重点跑:
这样做的目的不是形式完整,而是把“我以为没问题”的概率降下来。
这部分的核心其实就三件事:
这是我在整个项目里最有感触的一点。做增长类产品,最容易陷入的叙事是“多一个功能、多一个入口、多一个配置项”。但真正能长期跑的产品,往往不是功能最多,而是控制最稳。
MarginBundle 的很多设计,其实都在做同一件事:把“能卖”变成“能控”。
这四个能力连在一起,才算一个完整产品。
我后来用下面这张表来提醒自己,不要把产品写偏:
| 能力 | 对商家的意义 | 对工程的要求 |
|---|---|---|
| 能卖 | 能快速创建并展示优惠 | Admin 配置、Storefront 展示、Checkout 执行 |
| 能控 | 发布前知道风险 | 成本、库存、毛利、计划限制校验 |
| 能追踪 | 知道优惠有没有效果 | 事件采集、订单归因、指标聚合 |
| 能收口 | 出问题时可以恢复 | worker、webhook、状态机、reconciliation |
这其实就是我理解的“产品完整性”:不是某个页面做完,而是用户从决策到执行再到复盘都能走得通。
上架是很多工程文章里会略过的一段,但对一个真正要交付的 Shopify 应用来说,这一步非常关键。MarginBundle 的上架准备不是“把代码部署出去”这么简单,而是一条包括基础设施、合规、审核材料、计费验证、素材准备和生产烟雾测试的完整链路。
我后来越来越觉得,上架不是开发结束后的附属动作,而是产品成熟度的一次集中检查。开发时,我可以从自己的视角理解系统;上架时,我必须让审核人员、第一次安装的商家、未来维护这个系统的自己,都能理解它。
可以把这条链路先画成这样:
这里必须区分“代码和材料已经准备好”与“外部验证已经完成”。自动化检查通过,不等于 Shopify 真实确认页、开发店测试计费、卸载重装、最终截图和审核矩阵已经有人验收。后者必须在已部署环境中留下真实证据;在这些外部门禁全部完成前,我不会把“准备提交”写成“已经通过审核”。
在进入上架前,首先要确保开发环境能稳定跑起来,包括 Shopify app 本地开发、PostgreSQL、Redis、Prisma migration、Theme App Extension、Discount Function、单测和 E2E。
这一步的意义是把不确定性尽量挡在本地。越早把数据层、队列、前台组件和 checkout 逻辑连起来,后面越不容易在审核阶段才发现核心链路断了。
我会把上线前检查拆成几类:
| 检查项 | 目的 |
|---|---|
| Prisma schema / migration | 确认数据模型可迁移、可部署 |
| TypeScript / ESLint | 确认基础代码质量 |
| 单测 / route test | 覆盖业务规则、权限、billing、public endpoint |
| Storefront E2E | 验证 theme block、public offers、前台降级 |
| Admin E2E | 验证 onboarding、plans、核心运营路径 |
| release readiness | 把配置、编码、审核元数据和回归测试收成一个门禁 |
这一步不是为了追求“测试很多”,而是为了降低“我以为没问题”的概率。
部署 MarginBundle 时,我反复提醒自己:不要只部署 Web。
因为这个产品里有很多能力并不发生在用户请求的当下,而是发生在后台:商品同步、webhook 后处理、推荐生成、分析聚合、成本导入、发布状态修复。如果只有 web process 正常,而 worker 没有运行,应用表面上可能能打开,但很多业务能力会悄悄失效。
生产环境至少要有这几个部分:
| 组件 | 作用 | 缺失后的后果 |
|---|---|---|
| Web process | Admin 页面、public endpoint、webhook 接收 | 应用无法访问或无法接收请求 |
| Worker process | 队列消费、同步、聚合、补偿 | 后台任务堆积,数据和状态滞后 |
| PostgreSQL | 业务数据源 | 配置、商品、事件、归因无法持久化 |
| Redis | BullMQ 队列后端 | 异步任务无法正常调度和重试 |
| HTTPS domain | Shopify 回调和前台请求 | OAuth、webhook、app proxy、审核 URL 受影响 |
环境变量也不是普通配置,而更像一份生产合同。SHOPIFY_APP_URL 关系到 OAuth、webhook 和公开回调,SHOPIFY_SCOPES 关系到权限边界,DATABASE_URL 和 REDIS_URL 关系到数据和后台任务,ENCRYPTION_KEY 关系到 token 加密,SHOPIFY_BILLING_TEST 关系到计费模式。博客里不需要展示真实值,但一定要讲清楚这些配置为什么重要。
我一开始也会把隐私政策、服务条款、支持页面看成“审核需要的页面”。但真正准备上架后,我发现它们其实是产品信任体系的一部分。
对一个 Shopify 应用来说,商家最关心的不是页面写得多漂亮,而是这些问题有没有答案:
所以公开页面不能只是模板文字,而应该和产品真实行为一致。后来我也把这部分从 Privacy、Terms、Support 扩展成更完整的 public surface:产品官网负责第一眼解释产品,Help center 负责降低使用门槛,Updates 页面负责说明产品仍在维护,Support 页面和嵌入式支持入口负责承接问题。
这部分不一定是技术最难的,但会直接影响审核体验和用户第一印象。
Shopify 应用上架时,权限说明非常关键。很多开发者容易有一个想法:先把以后可能用到的权限都要了,省得后面再改。但从产品和审核角度看,这不是一个好习惯。权限越多,解释成本越高,用户的不信任感也会越高。
我的原则是:每个 scope 都必须对应一个明确的产品能力。
| 权限类型 | 产品原因 | 用户能理解的表达 |
|---|---|---|
| 读取商品 | 创建 bundle 和选择商品需要商品/变体数据 | 用于选择商品并生成组合优惠 |
| 读取库存 | 避免前台展示不可售商品 | 用于判断优惠商品是否可售 |
| 读取订单 | 归因、推荐和效果分析需要订单结果 | 用于统计优惠带来的订单表现 |
| 读取客户 | 客户分组优惠需要判断客户身份或标签 | 用于支持商家启用的客户分组折扣 |
| 写入客户 | 某些客户分组或推荐流程需要写入应用管理的标签 | 仅用于商家启用的分组同步场景 |
| 写入折扣 | Discount Function 和自动折扣需要应用管理 discount | 用于在 checkout 阶段执行真实优惠 |
| 读取语言和市场 | storefront 展示需要适配 locale 和 market | 用于展示更匹配店铺语言和市场的前台文案 |
这张表背后的方法论是:不要从开发便利性解释权限,而要从用户价值解释权限。如果某个权限不能用一句清楚的话解释给商家听,那就应该重新思考它是否真的需要。
Billing 是我这次迭代里感受很深的一块。刚开始我容易把它理解成“套餐和收款”,但 Shopify 应用里的 billing 实际上会影响 onboarding、plan、feature gate、升级、降级、取消、pending 状态、reinstall 恢复和审核视频。
这不是纸面上的风险。一次审核曾因为付费流程失败、审核访问说明不完整而暂停。它迫使我把 billing 从“功能已经写完”重新定义为“审核人员能在真实环境里完成确认、拒绝、重试、换档、取消和重装恢复,并且每一步都有证据”。自动化测试只能证明代码路径,不能替代平台确认页和真实状态回流。
我后来明确选择的是 Manual pricing + Shopify Billing API。这样应用内的付费计划按钮可以直接调用 appSubscriptionCreate,打开对应计划的 Shopify subscription confirmation page。Starter 和 Growth 可以带 7 天 trial,Pro 不带 trial。这个路径比把用户带到一个通用的 hosted plan selection 页面更可控,也更符合应用内“点哪个计划,就确认哪个计划”的用户预期。
这里有一个教训:Shopify App Pricing 和 Billing API 不能混着想。如果应用配置在 Shopify App Pricing 模式下,再用 Billing API 创建订阅,就可能出现平台侧拒绝。这个问题不是简单的代码 bug,而是“平台定价模式”和“应用内计费路径”没有对齐。
我现在会把 billing 看成这样一条状态链:
这部分如果处理不好,用户体验会非常混乱:用户以为自己已经升级,系统却还没同步;用户取消后还继续开放高级能力;用户重新安装时,已经付费的周期没有恢复;审核视频里也无法证明真实确认和返回流程。所以 billing 不只是收钱,它是产品权限系统的一部分。套餐表达也不应只剩功能矩阵:当前重点推荐 Growth,是因为它解锁了更完整的 Offer、推荐、分析、购物车展示和客户定向闭环,而不是因为它单纯“功能更多”。
准备 App Store listing 时,很容易陷入“怎么写得更吸引人”。但我更关心的是:一个第一次看到 MarginBundle 的商家,能不能快速理解它适不适合自己。
Listing 的表达要避免两个极端:一是太泛,比如只说 increase revenue;二是太技术,比如一上来讲 Function、BullMQ、Prisma。更好的结构是先说经营问题,再说核心能力,再说 checkout 如何真实生效,最后说明隐私、权限和支持方式。
我给 MarginBundle 的一句话定位是:
MarginBundle helps Shopify merchants create profit-protected discounts, bundles, gifts, and cart offers, then understand their impact on AOV and estimated gross profit.
如果用中文表达,可以是:
MarginBundle 是一个面向 Shopify 商家的利润保护型 Offers 工作台,帮助商家创建折扣、组合、买赠和购物车活动,并持续观察它们对客单价与预估毛利的影响。
后来我发现,App Store 素材也不是简单的宣传海报。截图和视频其实是审核证据的一部分:它们要证明应用真的存在、真实 UI 能跑、核心路径能复现、billing 能进入 Shopify 确认页并返回应用。
所以素材准备要非常谨慎:
| 素材规则 | 原因 |
|---|---|
| 最终提交素材应来自已部署应用或真实审核店铺 | 避免用 mockup 代替真实产品 |
| 不展示虚构收入、订单数、转化率或夸张结果 | 避免造成效果承诺或误导 |
| 不展示商家名称、客户信息、邮箱、token、密码和店铺访问码 | 保护隐私和安全 |
| 不把草稿素材直接当作可提交素材 | 生成图可以辅助排版,但不能代替审核证据 |
| 视频要覆盖真实 billing approval / return | 证明付费路径和状态同步可复现 |
这次我也更明确地把素材分成两类:一类是内部生成稿,用来检查构图、文案和表达;另一类才是最终可提交素材,必须经过 manifest 管理和人工视觉检查。这个边界很重要,因为 App Store 的素材不是“看起来像产品”就够了,它必须和真实产品行为一致。
提交审核时,测试说明非常关键。开发者自己知道怎么用产品,但审核人员不知道。他们不会天然理解你的业务假设,也不会知道哪个页面是关键路径。
我会把审核人员路径写得尽量明确:
如果涉及 billing,还要说明如何触发升级、如何看到 Shopify confirmation、如何处理 decline/cancel、如何 retry、如何在返回应用后看到当前 plan。这里不能把 development store 的 test billing 说成真实付款,也不能把模拟页面说成 Shopify confirmation。
上线后测试不能无限长,因为生产环境每一步都要谨慎。但它也不能太浅,只看首页能打开是不够的。我会把上线后烟雾测试控制在这些关键问题上:
这可以画成一条最小生产验证链:
只要这条链通了,说明产品的核心生命线在当前环境中是通的。它仍不能替代 Shopify 审核结论和真实商家验证,但至少能避免用“首页能打开”冒充产品已经可交付。
经历完整上架准备之后,我对 Shopify 应用有了一个更现实的认识:真正的难度不在某个单独技术点,而在于你要同时对产品、平台、用户、审核和运维负责。
上架会逼你回答很多平时容易回避的问题:你到底为谁服务?你需要哪些数据?你为什么需要这些权限?如果用户卸载,你怎么处理数据?如果后台任务失败,系统会怎么恢复?如果商家愿意付费,计费状态和功能边界是否一致?如果审核人员第一次打开应用,他能不能复现核心价值?
所以我现在更愿意把上架看成产品成熟度的检查,而不是流程终点。一个应用真正比“能跑起来”更进一步,是因为它知道自己服务谁,知道自己为什么需要数据,知道怎么解释权限,知道怎么处理卸载和隐私,知道怎么在失败时降级和恢复,也知道怎么让外部人复现核心价值。
这也是我认为 MarginBundle 最值得记录的地方。它不是只完成了一个工程,而是经历了一次从个人想法、产品判断、技术实现到平台上架准备的完整训练。最终能否通过审核、被商家采用并形成收入,仍要交给真实环境和市场回答。
如果把 MarginBundle 放回我的个人路径里,它更像一次完整的独立开发尝试:先在工作里接触 Shopify,再从真实业务里看到问题,然后把这些问题抽象成产品,最后尝试把它做成一个能上架、能被安装,也有机会被付费的应用。
这条路和做公司内部需求很不一样。独立做产品时,很多问题都要自己回答:为什么做这个、谁会用、愿不愿意付费、第一版做到什么程度、出了问题谁来支持、如果没人买是方向错了还是表达出了问题。没有标准答案,但这些问题会逼着我从更完整的角度看产品。
我最明显的变化,是从“开发者思维”慢慢转向“产品思维”。以前我更关心怎么实现,现在我会先问商家为什么需要它、它解决的是省时间还是降风险、用户第一次打开时能不能很快看到价值。MarginBundle 让我更清楚地意识到,独立开发不是少了产品经理,而是自己必须补上产品判断和商业判断。
我对 Shopify 的理解也变了。它不只是 API 和扩展能力的集合,而是商家日常经营的操作系统:商品、库存、客户、订单、折扣、主题、checkout、billing 都连在一起。做 MarginBundle 的时候,我越来越明确地感觉到,开发 Shopify 应用不是单纯调平台接口,而是要尊重这些经营对象之间的关系。
我对运营的理解也变了。运营说出来的往往是一个解决方案的形状,不一定是问题本身。比如“做一个组合优惠”“给老客户专属折扣”“看一下活动效果”,背后真正想要的,通常是提高客单价、提升复购、判断活动值不值得继续投入、降低配置风险。MarginBundle 里的发布前校验、事件归因、customer group discount,都是从这个理解里长出来的。
我对产品边界的理解同样变了。Shopify 生态里有太多可以继续扩大的方向,但如果什么都做,产品就会失去重心。所以我最后给 MarginBundle 定的边界很简单:它核心是“利润保护型 Offers 闭环”,而不是泛化的增长平台。
工程复杂度的理解也跟着变了。真正难的不是把功能写出来,而是把业务复杂度、平台复杂度和运行复杂度一起处理好:规则如何建模、checkout 如何一致、webhook 如何幂等、worker 如何失败重试、公开接口如何不泄露内部信息、上架材料如何和产品设计一致。这些问题不是靠多写几段代码就能解决的,它们更像一个系统工程。
如果重新来一次,我会更早做几件事:更早写产品原则,更早准备上架材料,更早把 release readiness 和测试门禁建立起来,也更早用写作把思路整理清楚。很多边界不是在功能做完后才出现的,而是在整理这些事情时才真正变清楚的。
MarginBundle 对我来说,最重要的收获不是“我写完了一个插件”,而是我更理解了什么叫产品完整性。功能做完不等于产品完整,页面能打开不等于业务能跑,上架提交也不等于产品成熟。真正站得住的产品,必须让用户理解、让商家能用、让系统可恢复、让数据可复盘、让合规和部署都说得清楚。
这也是我为什么想把这个过程写下来。它不是一篇推广文,而是一次记录:记录我作为一个在职全栈开发者,同时以独立开发者身份接触 Shopify、观察真实业务、做市场调研,并尝试把判断做成插件产品的过程。我希望它最后能卖出去,但我现在并不知道它一定会成功;正因为还不知道,所以这段过程更值得被认真写下来。
如果要把它压成一句话,我会说:
MarginBundle 是我作为一个在职全栈开发者,同时以独立开发者身份在 Shopify 上做的一次插件尝试。它不是为了证明某个结论,更像是我把开发经验、市场观察和产品判断放进真实场景里,看看自己能不能把它做完整。
还没有公开评论
欢迎留下第一条想法,评论会在博主审核后显示。