Résumé trimestriel/mensuel du revenu envoyé à Slack
Ceci est unContent Creation, Multimodal AIworkflow d'automatisation du domainecontenant 18 nœuds.Utilise principalement des nœuds comme Code, Merge, Slack, Stripe, HttpRequest. Envoi automatique de rapports de revenus Stripe mensuels et trimestriels vers Slack avec des informations financières
- •Token Bot Slack ou URL Webhook
- •Clé API Stripe
- •Peut nécessiter les informations d'identification d'authentification de l'API cible
Nœuds utilisés (18)
Catégorie
{
"id": "2K34ps0eKEbH4n1w",
"meta": {
"instanceId": "8443f10082278c46aa5cf3acf8ff0f70061a2c58bce76efac814b16290845177",
"templateCredsSetupCompleted": true
},
"name": "Quaterly/Monthly Revenue Summary To Slack",
"tags": [],
"nodes": [
{
"id": "4ab84e5a-6892-4428-bda6-3a8bdb8a2255",
"name": "Description du flux de travail",
"type": "n8n-nodes-base.stickyNote",
"position": [
-336,
272
],
"parameters": {
"color": 6,
"width": 310,
"height": 480,
"content": "## 📊 Stripe Financial Reporting Workflow\n\nThis workflow automatically generates comprehensive monthly and quarterly financial reports from your Stripe data and sends them to Slack.\n\n**Features:**\n- Monthly reports on the 1st of each month\n- Quarterly reports every 3 months\n- Revenue analysis with refund tracking\n- Customer insights and payment method breakdown\n- Risk analysis and refund reasons\n- Automated Slack notifications\n\n**Perfect for:** Business owners, finance teams, and anyone who needs regular Stripe revenue insights delivered automatically."
},
"typeVersion": 1
},
{
"id": "c7612bb2-02bc-4ca2-b8fe-cd1ae7562c41",
"name": "Instructions de configuration",
"type": "n8n-nodes-base.stickyNote",
"position": [
-384,
-96
],
"parameters": {
"color": 7,
"width": 358,
"height": 320,
"content": "## ⚙️ Setup Instructions\n\n1. **Stripe API Credentials:** Add your Stripe API key in the credential settings\n2. **Slack Integration:** Connect your Slack workspace and select the target channel\n3. **Schedule Timing:** Modify cron expressions if you want different timing\n4. **Channel Configuration:** Update the Slack channel ID to your desired channel\n\n**Important:** Test the workflow manually before enabling the schedule triggers!"
},
"typeVersion": 1
},
{
"id": "29f2e1af-d3a6-4c02-b727-3b0f35084544",
"name": "Planification mensuelle (1er jour, 9h)",
"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": "Planification trimestrielle (1er jour tous les 3 mois, 9h)",
"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": "Note de planification",
"type": "n8n-nodes-base.stickyNote",
"position": [
16,
-144
],
"parameters": {
"color": 7,
"width": 342,
"height": 240,
"content": "## 🗓️ Schedule Configuration\n\n**Monthly:** Runs on the 1st of every month at 9 AM\n**Quarterly:** Runs on the 1st day of every 3rd month at 9 AM\n\nBoth triggers feed into the same date calculation logic to determine the appropriate reporting period."
},
"typeVersion": 1
},
{
"id": "eeef03a3-45f4-4bdd-ab84-0c43e7ad1afa",
"name": "Calculer la plage de dates",
"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": "Note de plage de dates",
"type": "n8n-nodes-base.stickyNote",
"position": [
288,
384
],
"parameters": {
"color": 7,
"width": 246,
"height": 288,
"content": "## 📅 Date Range Logic\n\nThis node determines whether to generate a monthly or quarterly report based on the current date and calculates the appropriate date range for data collection.\n\nOutputs Unix timestamps for Stripe API compatibility."
},
"typeVersion": 1
},
{
"id": "5d537942-36cb-40e5-b20c-d36c26a1c94b",
"name": "Obtenir les charges 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": "Note des données Stripe",
"type": "n8n-nodes-base.stickyNote",
"position": [
464,
-128
],
"parameters": {
"color": 7,
"width": 326,
"height": 240,
"content": "## 💳 Stripe Data Collection\n\n**Charges Node:** Fetches all successful payments\n**Refunds Node:** Fetches all refund transactions\n\nBoth nodes use your Stripe API credentials and collect data for the calculated date range."
},
"typeVersion": 1
},
{
"id": "d1f56e15-1867-4677-a044-0be5fc745288",
"name": "Formater le message pour 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": "Note de format Slack",
"type": "n8n-nodes-base.stickyNote",
"position": [
1184,
384
],
"parameters": {
"color": 7,
"width": 278,
"height": 288,
"content": "## 💬 Slack Message Formatting\n\nTransforms the financial metrics into a beautifully formatted Slack message with:\n- Emojis and formatting\n- Currency formatting\n- Percentage calculations\n- Organized sections for easy reading"
},
"typeVersion": 1
},
{
"id": "85f38b2b-9e5a-4cb6-a2cf-56687a822544",
"name": "Calculer les indicateurs financiers",
"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": "Note des indicateurs financiers",
"type": "n8n-nodes-base.stickyNote",
"position": [
960,
-240
],
"parameters": {
"color": 7,
"width": 246,
"height": 432,
"content": "## 📊 Financial Analysis Engine\n\nThis node processes the merged Stripe data to calculate:\n- Revenue metrics (total, net, average)\n- Customer insights (top customers, unique count)\n- Payment method breakdown\n- Risk analysis by transaction risk score\n- Refund analysis with reasons\n- Growth metrics (MRR/ARR estimates)\n\nAll amounts are converted from cents to dollars and properly rounded."
},
"typeVersion": 1
},
{
"id": "ce84cff7-e8e7-474a-9c30-e93a032f2074",
"name": "Fusionner",
"type": "n8n-nodes-base.merge",
"position": [
800,
224
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "6d4051e6-7972-47c4-99e1-57e109edcbb5",
"name": "Note de fusion",
"type": "n8n-nodes-base.stickyNote",
"position": [
752,
384
],
"parameters": {
"color": 7,
"width": 246,
"content": "## 🔄 Data Merger\n\nCombines charges and refunds data streams into a single dataset for comprehensive analysis."
},
"typeVersion": 1
},
{
"id": "eb6df223-4d46-47f9-bd11-44a1b16b2c8b",
"name": "Envoyer à 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": "Note de livraison Slack",
"type": "n8n-nodes-base.stickyNote",
"position": [
1472,
-64
],
"parameters": {
"color": 7,
"width": 294,
"height": 240,
"content": "## 🚀 Slack Delivery\n\n**IMPORTANT:** Update the channelId value to match your target Slack channel.\n\nThe formatted report will be sent with markdown formatting enabled for better readability."
},
"typeVersion": 1
},
{
"id": "25c482b2-ffed-4faf-afd3-5148ded842f1",
"name": "Obtenir les remboursements 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": {
"ce84cff7-e8e7-474a-9c30-e93a032f2074": {
"main": [
[
{
"node": "85f38b2b-9e5a-4cb6-a2cf-56687a822544",
"type": "main",
"index": 0
}
]
]
},
"5d537942-36cb-40e5-b20c-d36c26a1c94b": {
"main": [
[
{
"node": "ce84cff7-e8e7-474a-9c30-e93a032f2074",
"type": "main",
"index": 0
}
]
]
},
"25c482b2-ffed-4faf-afd3-5148ded842f1": {
"main": [
[
{
"node": "ce84cff7-e8e7-474a-9c30-e93a032f2074",
"type": "main",
"index": 1
}
]
]
},
"eeef03a3-45f4-4bdd-ab84-0c43e7ad1afa": {
"main": [
[
{
"node": "5d537942-36cb-40e5-b20c-d36c26a1c94b",
"type": "main",
"index": 0
},
{
"node": "25c482b2-ffed-4faf-afd3-5148ded842f1",
"type": "main",
"index": 0
}
]
]
},
"d1f56e15-1867-4677-a044-0be5fc745288": {
"main": [
[
{
"node": "eb6df223-4d46-47f9-bd11-44a1b16b2c8b",
"type": "main",
"index": 0
}
]
]
},
"85f38b2b-9e5a-4cb6-a2cf-56687a822544": {
"main": [
[
{
"node": "d1f56e15-1867-4677-a044-0be5fc745288",
"type": "main",
"index": 0
}
]
]
},
"29f2e1af-d3a6-4c02-b727-3b0f35084544": {
"main": [
[
{
"node": "eeef03a3-45f4-4bdd-ab84-0c43e7ad1afa",
"type": "main",
"index": 0
}
]
]
},
"7012ee9d-438f-40e8-9f17-f29be330a3a1": {
"main": [
[
{
"node": "eeef03a3-45f4-4bdd-ab84-0c43e7ad1afa",
"type": "main",
"index": 0
}
]
]
}
}
}Comment utiliser ce workflow ?
Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.
Dans quelles scénarios ce workflow est-il adapté ?
Avancé - Création de contenu, IA Multimodale
Est-ce payant ?
Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.
Workflows recommandés
Rahul Joshi
@rahul08Rahul 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.
Partager ce workflow