{
  "name": "FlowPantry - Recover failed card payments on memberships and plans (Stripe)",
  "nodes": [
    {
      "id": "90629004-5e43-4b23-8c5a-9dc3466e2400",
      "name": "Stripe: invoice payment failed",
      "type": "n8n-nodes-base.stripeTrigger",
      "typeVersion": 1,
      "position": [
        0,
        300
      ],
      "parameters": {
        "events": [
          "invoice.payment_failed"
        ]
      },
      "webhookId": "729a220c-799b-47e8-911e-c2c19c0c7247"
    },
    {
      "id": "6b65f133-3c18-4d09-a16b-3d27759c0822",
      "name": "Skip repeat deliveries",
      "type": "n8n-nodes-base.removeDuplicates",
      "typeVersion": 2,
      "position": [
        240,
        300
      ],
      "parameters": {
        "operation": "removeItemsSeenInPreviousExecutions",
        "dedupeValue": "={{$json.id}}",
        "options": {}
      }
    },
    {
      "id": "3b0e4181-e017-4814-a2b1-38c11f331ee7",
      "name": "Build message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const out=[];\nconst biz=$env.BUSINESS_NAME||'YOUR BUSINESS';\nconst fmtDate=s=>s?new Date(s*1000).toLocaleDateString('en-US',{weekday:'short',month:'short',day:'numeric'}):null;\nfor(const i of $input.all()){\n  const ev=i.json;\n  if(ev.type!=='invoice.payment_failed') continue;\n  const inv=(ev.data&&ev.data.object)||{};\n  if(!inv.customer_email) continue;\n  if(inv.status==='paid'||inv.status==='void') continue;\n  const attempt=Number(inv.attempt_count||1);\n  const next=fmtDate(inv.next_payment_attempt);\n  const stage=!inv.next_payment_attempt?'final':(attempt<=1?'first':'retry');\n  const cur=String(inv.currency||'usd').toUpperCase();\n  const amount=new Intl.NumberFormat('en-US',{style:'currency',currency:cur}).format((inv.amount_due||0)/100);\n  const name=(inv.customer_name||'there').split(' ')[0];\n  const link=inv.hosted_invoice_url||$env.BILLING_PORTAL_URL||'';\n  let subject,body;\n  if(stage==='first'){\n    subject=`Your ${biz} payment didn't go through`;\n    body=`Hi ${name},\\n\\nYour ${amount} payment to ${biz} didn't go through. This is usually an expired or replaced card.\\n\\nYou can update your card or pay now here: ${link}\\n\\nIf you do nothing, we'll try the card again on ${next}.\\n\\nQuestions? Just reply to this email.\\n\\n\u2014 ${biz}`;\n  }else if(stage==='retry'){\n    subject=`Still having trouble with your ${biz} payment`;\n    body=`Hi ${name},\\n\\nWe tried your card again for the ${amount} payment and it was declined (attempt ${attempt}).\\n\\nTwo-minute fix: ${link}\\n\\nNext try: ${next}. If you've already updated it, thank you, you can ignore this.\\n\\n\u2014 ${biz}`;\n  }else{\n    subject=`Action needed: your ${amount} payment to ${biz}`;\n    body=`Hi ${name},\\n\\nWe weren't able to collect your ${amount} payment after ${attempt} tries, and we won't retry the card again automatically.\\n\\nPlease pay or update your card here to keep your service active: ${link}\\n\\nIf something changed, just reply and we'll sort it out together.\\n\\n\u2014 ${biz}`;\n  }\n  out.push({json:{event_id:ev.id,invoice_id:inv.id,customer:inv.customer,email:inv.customer_email,first:name,customer_name:inv.customer_name||'',phone:inv.customer_phone||'',amount,attempt,next_try:next,stage,link,subject,body}});\n}\nreturn out;"
      }
    },
    {
      "id": "c95ee01b-0368-428d-876d-b4843663d272",
      "name": "Which attempt?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        720,
        300
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.stage}}",
                    "rightValue": "first",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "First failure"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.stage}}",
                    "rightValue": "retry",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Retry failed"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.stage}}",
                    "rightValue": "final",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Final failure"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "2d7ca463-bc8e-4a14-a463-71ccff49c5f7",
      "name": "Email: card didn't go through",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        120
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.resend.com/emails",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{JSON.stringify({from:$env.FROM_EMAIL,to:$json.email,reply_to:$env.REPLY_TO_EMAIL||$env.FROM_EMAIL,subject:$json.subject,text:$json.body})}}",
        "options": {}
      }
    },
    {
      "id": "1770fcb9-4d03-4abe-ab64-d8cf5dfb949a",
      "name": "Email: still declined",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        300
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.resend.com/emails",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{JSON.stringify({from:$env.FROM_EMAIL,to:$json.email,reply_to:$env.REPLY_TO_EMAIL||$env.FROM_EMAIL,subject:$json.subject,text:$json.body})}}",
        "options": {}
      }
    },
    {
      "id": "41d1f0c2-9808-46bc-a2c7-d641c5f79fb2",
      "name": "Email: final notice",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        480
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.resend.com/emails",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{JSON.stringify({from:$env.FROM_EMAIL,to:$json.email,reply_to:$env.REPLY_TO_EMAIL||$env.FROM_EMAIL,subject:$json.subject,text:$json.body})}}",
        "options": {}
      }
    },
    {
      "id": "6a101dee-b15d-4a4f-9ab1-ba616af4beca",
      "name": "Owner SMS: final failure",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1200,
        480
      ],
      "parameters": {
        "method": "POST",
        "url": "=https://api.twilio.com/2010-04-01/Accounts/{{$env.TWILIO_ACCOUNT_SID}}/Messages.json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "sendBody": true,
        "contentType": "form-urlencoded",
        "bodyParameters": {
          "parameters": [
            {
              "name": "To",
              "value": "={{$env.OWNER_PHONE}}"
            },
            {
              "name": "From",
              "value": "={{$env.TWILIO_FROM_NUMBER}}"
            },
            {
              "name": "Body",
              "value": "=Stripe gave up on {{$json.customer_name || $json.email}}: {{$json.amount}} after {{$json.attempt}} tries. {{$json.phone ? 'Call ' + $json.phone : 'Email ' + $json.email}}. Invoice {{$json.invoice_id}}"
            }
          ]
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Stripe: invoice payment failed": {
      "main": [
        [
          {
            "node": "Skip repeat deliveries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip repeat deliveries": {
      "main": [
        [
          {
            "node": "Build message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build message": {
      "main": [
        [
          {
            "node": "Which attempt?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Which attempt?": {
      "main": [
        [
          {
            "node": "Email: card didn't go through",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email: still declined",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email: final notice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email: final notice": {
      "main": [
        [
          {
            "node": "Owner SMS: final failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}