8
n8n 中文网amn8n.com

季度/月度收入摘要发送至 Slack

高级

这是一个Content Creation, Multimodal AI领域的自动化工作流,包含 18 个节点。主要使用 Code, Merge, Slack, Stripe, HttpRequest 等节点。 使用财务洞察自动将月度及季度 Stripe 收入报告发送至 Slack

前置要求
  • Slack Bot Token 或 Webhook URL
  • Stripe API Key
  • 可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "2K34ps0eKEbH4n1w",
  "meta": {
    "instanceId": "8443f10082278c46aa5cf3acf8ff0f70061a2c58bce76efac814b16290845177",
    "templateCredsSetupCompleted": true
  },
  "name": "季度/月度收入摘要发送至 Slack",
  "tags": [],
  "nodes": [
    {
      "id": "4ab84e5a-6892-4428-bda6-3a8bdb8a2255",
      "name": "工作流描述",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -336,
        272
      ],
      "parameters": {
        "color": 6,
        "width": 310,
        "height": 480,
        "content": "## 📊 Stripe 财务报告工作流"
      },
      "typeVersion": 1
    },
    {
      "id": "c7612bb2-02bc-4ca2-b8fe-cd1ae7562c41",
      "name": "设置说明",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -384,
        -96
      ],
      "parameters": {
        "color": 7,
        "width": 358,
        "height": 320,
        "content": "## ⚙️ 设置说明"
      },
      "typeVersion": 1
    },
    {
      "id": "29f2e1af-d3a6-4c02-b727-3b0f35084544",
      "name": "月度调度 (每月第 1 天, 上午 9 点)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        128,
        128
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 1 */1 *"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7012ee9d-438f-40e8-9f17-f29be330a3a1",
      "name": "季度调度 (每 3 个月的第 1 天, 上午 9 点)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        128,
        320
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 1 */3 *"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "edac5bee-0e1f-468f-884a-ef99558c12e8",
      "name": "调度说明",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 342,
        "height": 240,
        "content": "## 🗓️ 调度配置"
      },
      "typeVersion": 1
    },
    {
      "id": "eeef03a3-45f4-4bdd-ab84-0c43e7ad1afa",
      "name": "计算日期范围",
      "type": "n8n-nodes-base.code",
      "position": [
        352,
        224
      ],
      "parameters": {
        "jsCode": "// Determine if this is monthly or quarterly based on current date\nconst now = new Date();\nconst currentMonth = now.getMonth() + 1; // getMonth() returns 0-11\n\n// Check if current month is a quarter start (Jan=1, Apr=4, Jul=7, Oct=10)\nconst isQuarterly = currentMonth % 3 === 1;\n\n// Calculate date ranges\nlet startDate, endDate, period;\n\nif (isQuarterly && [1, 4, 7, 10].includes(currentMonth)) {\n  // Quarterly report - get previous quarter\n  const quarterStartMonth = currentMonth - 3;\n  startDate = new Date(now.getFullYear(), quarterStartMonth - 1, 1);\n  if (quarterStartMonth <= 0) {\n    startDate = new Date(now.getFullYear() - 1, quarterStartMonth + 12 - 1, 1);\n  }\n  endDate = new Date(now.getFullYear(), currentMonth - 1, 0); // Last day of previous month\n  period = 'quarterly';\n} else {\n  // Monthly report - get previous month\n  startDate = new Date(now.getFullYear(), currentMonth - 2, 1);\n  if (currentMonth === 1) {\n    startDate = new Date(now.getFullYear() - 1, 11, 1); // December of previous year\n  }\n  endDate = new Date(now.getFullYear(), currentMonth - 1, 0); // Last day of previous month\n  period = 'monthly';\n}\n\n// Convert to Unix timestamps for Stripe API\nconst startTimestamp = Math.floor(startDate.getTime() / 1000);\nconst endTimestamp = Math.floor(endDate.getTime() / 1000);\n\nreturn [{\n  json: {\n    startDate: startTimestamp,\n    endDate: endTimestamp,\n    period: period,\n    startDateFormatted: startDate.toISOString().split('T')[0],\n    endDateFormatted: endDate.toISOString().split('T')[0]\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "a66fb369-570e-4494-a2a9-ce2071757e4e",
      "name": "日期范围说明",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 246,
        "height": 288,
        "content": "## 📅 日期范围逻辑"
      },
      "typeVersion": 1
    },
    {
      "id": "5d537942-36cb-40e5-b20c-d36c26a1c94b",
      "name": "获取 Stripe 费用",
      "type": "n8n-nodes-base.stripe",
      "position": [
        576,
        128
      ],
      "parameters": {
        "resource": "charge",
        "operation": "getAll",
        "returnAll": true
      },
      "credentials": {
        "stripeApi": {
          "id": "DV4tPpxjbOUkGfAx",
          "name": "Stripe account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f6301611-55a1-4598-ba5c-14ece63f4e55",
      "name": "Stripe 数据说明",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        464,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 326,
        "height": 240,
        "content": "## 💳 Stripe 数据收集"
      },
      "typeVersion": 1
    },
    {
      "id": "d1f56e15-1867-4677-a044-0be5fc745288",
      "name": "格式化 Slack 消息",
      "type": "n8n-nodes-base.code",
      "position": [
        1248,
        224
      ],
      "parameters": {
        "jsCode": "const data = $json;\n\n// Format currency\nconst formatCurrency = (amount) => {\n  return new Intl.NumberFormat('en-US', {\n    style: 'currency',\n    currency: 'USD'\n  }).format(amount);\n};\n\n// Format percentage\nconst formatPercentage = (value) => {\n  return `${value}%`;\n};\n\n// Create period title from metadata\nconst periodTitle = `Revenue Report - ${data.metadata.period}`;\n\n// Build top customers text\nlet topCustomersText = '';\nif (data.customers.topCustomers && data.customers.topCustomers.length > 0) {\n  topCustomersText = data.customers.topCustomers.map((customer, index) => {\n    const customerName = customer.customer === 'Unknown Customer' ? \n      'Unknown Customer' : \n      `Customer ${customer.customer.substring(4, 14)}...`; // Show more meaningful part of customer ID\n    return `   ${index + 1}. ${customerName}: ${formatCurrency(customer.amount)}`;\n  }).join('\\n');\n} else {\n  topCustomersText = '   No customer data available';\n}\n\n// Build payment methods breakdown\nlet paymentMethodsText = '';\nif (data.payments.paymentMethodBreakdown) {\n  paymentMethodsText = Object.entries(data.payments.paymentMethodBreakdown)\n    .map(([method, amount]) => `   • ${method.charAt(0).toUpperCase() + method.slice(1)}: ${formatCurrency(amount)}`)\n    .join('\\n');\n}\n\n// Build refund reasons text\nlet refundReasonsText = '';\nif (data.refundAnalysis.refundReasons && Object.keys(data.refundAnalysis.refundReasons).length > 0) {\n  refundReasonsText = Object.entries(data.refundAnalysis.refundReasons)\n    .map(([reason, count]) => `   • ${reason.replace(/_/g, ' ').replace(/\\b\\w/g, l => l.toUpperCase())}: ${count}`)\n    .join('\\n');\n} else {\n  refundReasonsText = '   No refunds in this period';\n}\n\n// Risk level summary\nconst riskSummary = `Low: ${data.payments.riskAnalysis.low} | Medium: ${data.payments.riskAnalysis.medium} | High: ${data.payments.riskAnalysis.high}`;\n\n// Build the comprehensive Slack message\nconst slackMessage = `📊 **${periodTitle}**\n📅 *Generated: ${new Date(data.metadata.dataProcessedAt).toLocaleDateString()}*\n\n💰 **Financial Summary:**\n• Total Revenue: ${formatCurrency(data.summary.totalRevenue)}\n• Total Refunds: ${formatCurrency(data.summary.totalRefunds)}\n• Net Revenue: ${formatCurrency(data.summary.netRevenue)}\n• Average Transaction: ${formatCurrency(data.summary.averageTransactionValue)}\n\n📈 **Growth Metrics:**\n• Estimated MRR: ${formatCurrency(data.growth.estimatedMRR)}\n• Estimated ARR: ${formatCurrency(data.growth.estimatedARR)}\n\n📊 **Transaction Analysis:**\n• Successful Transactions: ${data.summary.totalTransactions}\n• Refund Transactions: ${data.summary.totalRefundTransactions}\n• Refund Rate: ${formatPercentage(data.summary.refundRate)}\n• Unique Customers: ${data.customers.uniqueCustomers}\n\n🏆 **Top Customers by Revenue:**\n${topCustomersText}\n\n💳 **Payment Methods:**\n${paymentMethodsText}\n\n⚠️ **Risk Analysis:**\n${riskSummary}\n\n🔄 **Refund Analysis:**\n• Refund Rate: ${formatPercentage(data.refundAnalysis.refundRate)}\n**Refund Reasons:**\n${refundReasonsText}\n\n---\n*Data processed: ${data.metadata.chargesAnalyzed} charges, ${data.metadata.refundsAnalyzed} refunds*\n*Auto-generated from Stripe API*`;\n\nreturn [{\n  json: {\n    text: slackMessage,\n    // Include original data for reference\n    originalData: data,\n    // Additional Slack formatting options\n    blocks: [\n      {\n        \"type\": \"section\",\n        \"text\": {\n          \"type\": \"mrkdwn\",\n          \"text\": slackMessage\n        }\n      }\n    ]\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ba6697a5-1aac-4b74-859f-6344c20fadfc",
      "name": "Slack 格式化说明",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1184,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 278,
        "height": 288,
        "content": "## 💬 Slack 消息格式化"
      },
      "typeVersion": 1
    },
    {
      "id": "85f38b2b-9e5a-4cb6-a2cf-56687a822544",
      "name": "计算财务指标",
      "type": "n8n-nodes-base.code",
      "position": [
        1024,
        224
      ],
      "parameters": {
        "jsCode": "// Get all merged data from the merge node\nconst allData = $input.all();\n\n// Separate charges and refunds based on the data structure\nlet charges = [];\nlet refunds = [];\n\n// Process each item from the merged data\nallData.forEach(item => {\n  const data = item.json;\n  \n  // Check if this is a charge object\n  if (data.object === 'charge') {\n    charges.push(data);\n  }\n  // Check if this is refunds list object\n  else if (data.object === 'list' && data.data) {\n    // Extract refund objects from the list\n    refunds = refunds.concat(data.data);\n  }\n  // Check if this is a single refund object\n  else if (data.object === 'refund') {\n    refunds.push(data);\n  }\n});\n\n// Filter only successful/paid charges that are not refunded\nconst paidCharges = charges.filter(charge => \n  charge.status === 'succeeded' && \n  charge.paid === true &&\n  charge.amount_refunded === 0 // This means not refunded\n);\n\n// Calculate total revenue from non-refunded charges\nconst totalRevenue = paidCharges.reduce((sum, charge) => {\n  return sum + (charge.amount / 100); // Convert from cents to dollars\n}, 0);\n\n// Calculate total refunds amount\nconst totalRefundsAmount = refunds.reduce((sum, refund) => {\n  return sum + (refund.amount / 100); // Convert from cents to dollars\n}, 0);\n\n// Calculate net revenue\nconst netRevenue = totalRevenue - totalRefundsAmount;\n\n// Get customer revenue breakdown\nconst customerRevenue = {};\npaidCharges.forEach(charge => {\n  const customer = charge.customer || 'Unknown Customer';\n  const amount = charge.amount / 100;\n  customerRevenue[customer] = (customerRevenue[customer] || 0) + amount;\n});\n\n// Get top 3 customers by revenue\nconst topCustomers = Object.entries(customerRevenue)\n  .sort(([,a], [,b]) => b - a)\n  .slice(0, 3)\n  .map(([customer, amount]) => ({ \n    customer, \n    amount: Math.round(amount * 100) / 100 \n  }));\n\n// Calculate payment method breakdown\nconst paymentMethodBreakdown = {};\npaidCharges.forEach(charge => {\n  const brand = charge.payment_method_details?.card?.brand || 'unknown';\n  const amount = charge.amount / 100;\n  paymentMethodBreakdown[brand] = (paymentMethodBreakdown[brand] || 0) + amount;\n});\n\n// Calculate average transaction value\nconst averageTransactionValue = paidCharges.length > 0 ? totalRevenue / paidCharges.length : 0;\n\n// Get current date for period estimation\nconst currentDate = new Date();\nconst currentMonth = currentDate.getMonth() + 1;\nconst currentYear = currentDate.getFullYear();\n\n// Simple MRR estimation (assuming monthly data)\nconst estimatedMRR = totalRevenue;\nconst estimatedARR = estimatedMRR * 12;\n\n// Risk analysis based on risk scores\nconst riskAnalysis = {\n  low: paidCharges.filter(c => c.outcome?.risk_score <= 20).length,\n  medium: paidCharges.filter(c => c.outcome?.risk_score > 20 && c.outcome?.risk_score <= 50).length,\n  high: paidCharges.filter(c => c.outcome?.risk_score > 50).length\n};\n\n// Refund reasons breakdown\nconst refundReasons = {};\nrefunds.forEach(refund => {\n  const reason = refund.reason || 'no_reason_given';\n  refundReasons[reason] = (refundReasons[reason] || 0) + 1;\n});\n\nreturn [{\n  json: {\n    // Summary metrics\n    summary: {\n      totalRevenue: Math.round(totalRevenue * 100) / 100,\n      totalRefunds: Math.round(totalRefundsAmount * 100) / 100,\n      netRevenue: Math.round(netRevenue * 100) / 100,\n      totalTransactions: paidCharges.length,\n      totalRefundTransactions: refunds.length,\n      averageTransactionValue: Math.round(averageTransactionValue * 100) / 100,\n      refundRate: paidCharges.length > 0 ? Math.round((refunds.length / paidCharges.length) * 100 * 100) / 100 : 0\n    },\n    \n    // Growth metrics\n    growth: {\n      estimatedMRR: Math.round(estimatedMRR * 100) / 100,\n      estimatedARR: Math.round(estimatedARR * 100) / 100\n    },\n    \n    // Customer insights\n    customers: {\n      topCustomers: topCustomers,\n      uniqueCustomers: Object.keys(customerRevenue).length\n    },\n    \n    // Payment analysis\n    payments: {\n      paymentMethodBreakdown: paymentMethodBreakdown,\n      riskAnalysis: riskAnalysis\n    },\n    \n    // Refund analysis\n    refundAnalysis: {\n      refundReasons: refundReasons,\n      refundRate: totalRevenue > 0 ? Math.round((totalRefundsAmount / totalRevenue) * 100 * 100) / 100 : 0\n    },\n    \n    // Metadata\n    metadata: {\n      dataProcessedAt: new Date().toISOString(),\n      chargesAnalyzed: charges.length,\n      refundsAnalyzed: refunds.length,\n      period: `${currentMonth}/${currentYear}`\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "7695ff48-1b74-4a29-9d1c-963515e0c7de",
      "name": "财务指标说明",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 246,
        "height": 432,
        "content": "## 📊 财务分析引擎"
      },
      "typeVersion": 1
    },
    {
      "id": "ce84cff7-e8e7-474a-9c30-e93a032f2074",
      "name": "合并",
      "type": "n8n-nodes-base.merge",
      "position": [
        800,
        224
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "6d4051e6-7972-47c4-99e1-57e109edcbb5",
      "name": "合并说明",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        752,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 246,
        "content": "## 🔄 数据合并器"
      },
      "typeVersion": 1
    },
    {
      "id": "eb6df223-4d46-47f9-bd11-44a1b16b2c8b",
      "name": "发送到 Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1472,
        224
      ],
      "webhookId": "4e77062d-a1d4-46ca-8d70-1a3bf3beaa71",
      "parameters": {
        "text": "={{ $json.text }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09H21LK9BJ",
          "cachedResultName": "reply-needed"
        },
        "otherOptions": {
          "mrkdwn": true
        }
      },
      "credentials": {
        "slackApi": {
          "id": "rNqvWj9TfChPVRYY",
          "name": "Slack account"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "c57a3d62-a880-46fd-95a6-5d9ea10b5e85",
      "name": "Slack 投递说明",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1472,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 294,
        "height": 240,
        "content": "## 🚀 Slack 投递"
      },
      "typeVersion": 1
    },
    {
      "id": "25c482b2-ffed-4faf-afd3-5148ded842f1",
      "name": "获取 Stripe 退款",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        576,
        320
      ],
      "parameters": {
        "url": "https://api.stripe.com/v1/refunds",
        "options": {},
        "sendQuery": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "limit",
              "value": "100"
            }
          ]
        },
        "nodeCredentialType": "stripeApi"
      },
      "credentials": {
        "stripeApi": {
          "id": "DV4tPpxjbOUkGfAx",
          "name": "Stripe account"
        }
      },
      "typeVersion": 4.1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "7edbbd68-e5ad-4b54-a498-8d1b69da06a2",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Calculate Financial Metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Stripe Charges": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Stripe Refunds": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Calculate Date Range": {
      "main": [
        [
          {
            "node": "Get Stripe Charges",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Stripe Refunds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Slack Message": {
      "main": [
        [
          {
            "node": "Send To Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Financial Metrics": {
      "main": [
        [
          {
            "node": "Format Slack Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Monthly Schedule (1st day, 9 AM)": {
      "main": [
        [
          {
            "node": "Calculate Date Range",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Quarterly Schedule (1st day every 3 months, 9 AM)": {
      "main": [
        [
          {
            "node": "Calculate Date Range",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 内容创作, 多模态 AI

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量18
分类2
节点类型7
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
Rahul Joshi

Rahul Joshi

@rahul08

Rahul Joshi is a seasoned technology leader specializing in the n8n automation tool and AI-driven workflow automation. With deep expertise in building open-source workflow automation and self-hosted automation platforms, he helps organizations eliminate manual processes through intelligent n8n ai agent automation solutions.

外部链接
在 n8n.io 查看

分享此工作流