AI PLAY玩法更新持续收录
← 回到更新流

2026-10-04 · 原文发布日

复现n8n付费模板:Shopify退货退款邮件半自动审批流(本站隔离练习版)

这是付费模板。流程每分钟检查Gmail中带returns标签的退货邮件,用Claude提取订单号、退货商品和理由;再用代码节点按退货政策(所有權、配送时间窗、final-sale标签等)判断资格,调用Shopify接口算出建议退款额,发到Slack等人工批准(最长等48小时),批准后才真正退款,结果回写Slack、回复客户并记入Google Sheets。缺订单号、换货、多币种等情况自动转人工。

原文要点

原模板为付费商品,需购买后才能在n8n导入。其事实要点:每分钟轮询带returns标签的未读Gmail;Claude仅负责提取订单号、退货商品、理由和诉求;资格判断全部在JavaScript代码节点完成,含所有权、订单状态、配送时间窗、final-sale标签、商品匹配;退款额来自Shopify的suggestedRefund计算,refundCreate只在Slack批准后执行且带幂等键;编辑器等手动运行为dry-run不退款不发信;缺订单号、换货、多币种、DMARC失败均转人工;流程只退款不创建Shopify Return。

这次做成什么

在没有购买原模板的前提下,用本站设计的隔离练习复现同等流程骨架:合成退货邮件→提取→代码节点政策判断→模拟退款额计算→审批分支→记录日志,跑通后再决定是否采购原模板对照真实节点。

准备什么

  • 一个可自建的n8n实例(Cloud或自托管均可),仅用测试数据
  • 一个测试用Google Sheets表格,新建名为Returns log的工作表
  • 一个Slack测试工作区或可替代的消息渠道用于审批模拟
  • 不需要真实Shopify店铺:本练习用合成订单数据替代接口返回

具体怎么做

  1. 准备合成测试数据

    在n8n新建工作流,用Manual Trigger连接一个Code节点,把下方artifacts中的三封合成退货邮件作为数组输出,模拟Gmail轮询结果。每封邮件含subject、from、body、headers(含Authentication-Results: dmarc=pass或fail)。

  2. 提取节点

    接一个具备聊天模型的节点(任意你已配置凭据的模型即可),把artifacts中的提取提示词作为系统消息、邮件正文作为用户消息,要求只输出JSON。无模型凭据时可先用Code节点正则提取订单号做降级练习。

  3. 政策判断代码节点

    新建Code节点粘贴artifacts中的eligibility代码,输入提取结果、合成订单数据与邮件headers,输出decision(eligible/manual_review/rejected)、命中原因和建议退款额。代码先按headers检查DMARC,再依次判断订单号、诉求类型、订单状态、配送时间窗、final-sale标签与商品匹配。注意:时间窗用Date.now()与合成订单deliveredAt计算,练习时把deliveredAt改到运行日30天内,否则会被判超窗。金额计算公式是本站设定的练习规则,真实退款额必须按原模板说明由Shopify接口计算,本站未核对具体GraphQL字段。

  4. 审批与分支

    用Switch节点按decision分流:eligible进入一个等待型审批节点(n8n的Wait节点配表单或Slack按钮,界面名称以你的n8n版本为准,本站未实测原模板的按钮配置);manual_review直接发消息到测试频道;rejected生成拒答文本。

  5. 日志与回执

    审批通过后,在dry-run分支只生成模拟退款记录,不调用任何真实接口;把订单号、决定、金额、审批人、时间追加到Google Sheets的Returns log表,并向测试频道发结果摘要。

可直接复制的材料

退货信息提取提示词

本站独立编写的练习提示词,{{emailBody}}在n8n中替换为表达式引用上一节点字段

你是电商售后信息提取器。从下面的退货邮件正文中提取信息,只输出一个JSON对象,不要输出任何其他文字。字段:orderNumber(字符串,形如#1001的订单号,没有则为null)、items(数组,每项含name和quantity)、reason(字符串,退货理由原文概括)、requestedResolution(refund/exchange/store_credit之一,无法判断填refund)。邮件正文:
{{emailBody}}

退货资格判断练习代码

本站设计,规则值(30天、15%手续费)为练习参考值,需按店铺政策替换

// 本站练习用Code节点脚本,规则数值均为本站设定示例
const extracted = $json.extracted;
const order = $json.order; // 合成订单数据
const headers = $json.headers || {};
const FINAL_SALE_TAGS = ['final-sale'];
const RETURN_WINDOW_DAYS = 30;
const RESTOCK_FEE_PCT = 0.15;

if (/dmarc=fail/i.test(headers['Authentication-Results'] || '')) {
  return [{ json: { decision: 'manual_review', reason: 'dmarc_fail' } }];
}
if (!extracted.orderNumber) {
  return [{ json: { decision: 'manual_review', reason: 'missing_order_number' } }];
}
if (extracted.requestedResolution !== 'refund') {
  return [{ json: { decision: 'manual_review', reason: 'non_refund_request' } }];
}
if (!order || order.financialStatus === 'pending') {
  return [{ json: { decision: 'manual_review', reason: 'order_not_found_or_unpaid' } }];
}
const deliveredDays = (Date.now() - new Date(order.deliveredAt).getTime()) / 86400000;
if (deliveredDays > RETURN_WINDOW_DAYS) {
  return [{ json: { decision: 'rejected', reason: 'outside_return_window' } }];
}
let subtotal = 0;
for (const item of extracted.items) {
  const line = order.lineItems.find(l => l.name === item.name);
  if (!line) return [{ json: { decision: 'manual_review', reason: 'item_not_in_order: ' + item.name } }];
  if (line.tags.some(t => FINAL_SALE_TAGS.includes(t))) {
    return [{ json: { decision: 'rejected', reason: 'final_sale: ' + item.name } }];
  }
  subtotal += line.price * Math.min(item.quantity, line.quantity);
}
const faulty = /破损|故障|defect|broken/i.test(extracted.reason);
const fee = faulty ? 0 : subtotal * RESTOCK_FEE_PCT;
return [{ json: { decision: 'eligible', reason: faulty ? 'faulty_no_fee' : 'standard', suggestedRefund: +(subtotal - fee).toFixed(2), currency: order.currency } }];

合成退货邮件与订单测试数据

全部为虚构测试数据,order字段需按提取到的orderNumber匹配后并入$item

{
  "emails": [
    {
      "subject": "退货申请 订单#2041",
      "from": "customer-a@example.test",
      "headers": { "Authentication-Results": "mx.example.test; dmarc=pass" },
      "body": "你好,我收到订单#2041里的蓝色卫衣尺码不对,想退一件并退款。谢谢。"
    },
    {
      "subject": "退款请求",
      "from": "customer-b@example.test",
      "headers": { "Authentication-Results": "mx.example.test; dmarc=pass" },
      "body": "我要退货退款,但邮件里忘了写订单号,请帮我处理。"
    },
    {
      "subject": "退货 订单#2042",
      "from": "customer-c@example.test",
      "headers": { "Authentication-Results": "mx.example.test; dmarc=fail" },
      "body": "订单#2042的耳机有故障,申请全额退款。"
    }
  ],
  "orders": {
    "#2041": { "financialStatus": "paid", "currency": "CNY", "deliveredAt": "2026-09-20T08:00:00Z", "lineItems": [ { "name": "蓝色卫衣", "quantity": 1, "price": 259, "tags": [] } ] },
    "#2042": { "financialStatus": "paid", "currency": "CNY", "deliveredAt": "2026-09-25T08:00:00Z", "lineItems": [ { "name": "无线耳机", "quantity": 1, "price": 399, "tags": [] } ] }
  }
}

输入与输出示例

本站虚构演示,提取与计算均为脚本逻辑推导的示意,未调用真实模型或Shopify

输入

邮件正文:你好,我收到订单#2041里的蓝色卫衣尺码不对,想退一件并退款。订单#2041合成数据:已支付、2026-09-20妥投、蓝色卫衣单价259元、无final-sale标签。

对应输出

提取结果:{"orderNumber":"#2041","items":[{"name":"蓝色卫衣","quantity":1}],"reason":"尺码不合适","requestedResolution":"refund"};政策代码输出:{"decision":"eligible","reason":"standard","suggestedRefund":220.15,"currency":"CNY"}(259×0.85,本站练习规则扣15%手续费)。

做完检查这些

  • 三封合成邮件分别得到eligible、manual_review(缺订单号)、manual_review(DMARC失败)三种不同走向,且deliveredAt在运行日30天内
  • eligible分支在审批通过前不产生任何退款记录,Returns log中该行的退款状态为空或pending
  • Returns log每行包含订单号、decision、金额、时间四列且与代码节点输出一致
  • 把邮件中订单号改为#9999后,流程落入manual_review并给出order_not_found原因

容易卡住的地方

  • 原模板是付费商品,本站练习不是原工作流文件,真实节点名、GraphQL查询和按钮配置需在购入后核对,本站未实测
  • 本站退款金额公式是练习参考,原模板说明金额来自Shopify的suggestedRefund,不能直接用本站公式对真实客户退款
  • 真实使用时需要Shopify应用具备read_orders、read_all_orders、write_orders、read_products权限并申请订单邮箱字段访问,且受保护客户数据要求Grow及以上套餐
  • 原模板说明流程只退款不创建Shopify Return,需确认收货后再批准;Claude只负责提取,不要把资格判断交给模型

原文与作者资料 ↗

本站借助AI整理来源事实,独立编写中文操作指南与示例。示例不代表实际运行结果;来源效果尚未由本站独立复现。付费模板与模型、服务费用分别计算。

原文标题
Process Shopify email return refunds with Gmail, Claude, Slack, and Google Sheets
原作者
Amplence
原始发布日期
2026-10-03T17:19:00.183Z
来源
https://n8n.io/workflows/20389