{
  "name": "Ko-fi to Paper Dynasty",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "kofi-paperdy",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "webhook-trigger",
      "name": "Ko-fi Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [250, 300],
      "webhookId": "kofi-paperdy"
    },
    {
      "parameters": {
        "jsCode": "// Parse Ko-fi form data\nconst formData = $input.item.json.body;\nconst kofiData = JSON.parse(formData.data);\n\n// Ko-fi verification token\nconst KOFI_TOKEN = '44d1f957-ac15-497e-8306-4dc667de55d1';\n\n// Validate token\nif (kofiData.verification_token !== KOFI_TOKEN) {\n  throw new Error('Invalid Ko-fi verification token');\n}\n\n// Add metadata\nreturn [{\n  json: {\n    ...kofiData,\n    _parsed_at: new Date().toISOString(),\n    _execution_id: $execution.id\n  }\n}];"
      },
      "id": "parse-and-validate",
      "name": "Parse & Validate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [450, 300]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.type }}",
              "operation": "equals",
              "value2": "Shop Order"
            }
          ]
        }
      },
      "id": "filter-shop-orders",
      "name": "Shop Order?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [650, 300]
    },
    {
      "parameters": {
        "jsCode": "// Lookup Paper Dynasty team\nconst PD_API_BASE = 'https://pd.manticorum.com/api/v2';\nconst API_TOKEN = 'Bearer Tp3aO3jhYve5NJF1IqOmJTmk';\n\nlet teamId = null;\nlet teamAbbrev = null;\nlet identificationMethod = null;\n\n// Method 1: Discord User ID -> gmid lookup\nif ($json.discord_userid) {\n  try {\n    const response = await $http.request({\n      method: 'GET',\n      url: `${PD_API_BASE}/teams?gmid=${$json.discord_userid}`,\n      headers: {\n        'Authorization': API_TOKEN,\n        'Content-Type': 'application/json'\n      }\n    });\n\n    if (response.body && response.body.length > 0) {\n      teamId = response.body[0].id;\n      teamAbbrev = response.body[0].abbrev;\n      identificationMethod = 'discord_userid';\n    }\n  } catch (error) {\n    console.log('Discord lookup failed:', error.message);\n  }\n}\n\n// Method 2: Extract team abbrev from message\nif (!teamId && $json.message) {\n  const abbrevMatch = $json.message.match(/\\b([A-Z]{2,4})\\b/);\n  \n  if (abbrevMatch) {\n    const extractedAbbrev = abbrevMatch[1];\n    \n    try {\n      const response = await $http.request({\n        method: 'GET',\n        url: `${PD_API_BASE}/teams?abbrev=${extractedAbbrev}`,\n        headers: {\n          'Authorization': API_TOKEN,\n          'Content-Type': 'application/json'\n        }\n      });\n\n      if (response.body && response.body.length > 0) {\n        teamId = response.body[0].id;\n        teamAbbrev = response.body[0].abbrev;\n        identificationMethod = 'message_abbrev';\n      }\n    } catch (error) {\n      console.log('Team abbrev lookup failed:', error.message);\n    }\n  }\n}\n\nreturn [{\n  json: {\n    ...$json,\n    pd_team_id: teamId,\n    pd_team_abbrev: teamAbbrev,\n    identification_method: identificationMethod,\n    needs_manual_review: !teamId\n  }\n}];"
      },
      "id": "lookup-team",
      "name": "Lookup PD Team",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [850, 200]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.needs_manual_review }}",
              "value2": false
            }
          ]
        }
      },
      "id": "check-team-found",
      "name": "Team Found?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [1050, 200]
    },
    {
      "parameters": {
        "jsCode": "// Product mapping\nconst PRODUCT_MAP = {\n  '61de350207': {\n    name: 'Premium Pack',\n    pack_type_id: 3,\n    packs_per_quantity: 1\n  },\n  '2bdb7a4916': {\n    name: 'Standard Pack',\n    pack_type_id: 1,\n    packs_per_quantity: 1\n  },\n  '3d7a4935aa': {\n    name: 'In-Game Currency',\n    type: 'currency',\n    needs_manual_review: true\n  }\n};\n\nconst shopItems = $json.shop_items;\nconst packRequests = [];\nlet unknownProducts = [];\nlet needsReview = [];\n\nfor (const item of shopItems) {\n  const productConfig = PRODUCT_MAP[item.direct_link_code];\n  \n  if (!productConfig) {\n    unknownProducts.push({\n      code: item.direct_link_code,\n      variation: item.variation_name,\n      quantity: item.quantity\n    });\n    continue;\n  }\n  \n  // Handle currency products\n  if (productConfig.type === 'currency') {\n    needsReview.push({\n      product: productConfig.name,\n      quantity: item.quantity,\n      reason: 'Currency system not implemented yet'\n    });\n    continue;\n  }\n  \n  // Calculate total packs\n  const totalPacks = item.quantity * productConfig.packs_per_quantity;\n  \n  // Create pack requests\n  for (let i = 0; i < totalPacks; i++) {\n    packRequests.push({\n      team_id: $json.pd_team_id,\n      pack_type_id: productConfig.pack_type_id,\n      pack_cardset_id: null\n    });\n  }\n}\n\nreturn [{\n  json: {\n    ...$json,\n    pack_requests: packRequests,\n    unknown_products: unknownProducts,\n    needs_review_items: needsReview,\n    has_unknown_products: unknownProducts.length > 0,\n    has_review_items: needsReview.length > 0,\n    total_packs: packRequests.length\n  }\n}];"
      },
      "id": "map-products",
      "name": "Map Products",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1250, 100]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://pd.manticorum.com/api/v2/packs",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpHeaderAuth",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "packs",
              "value": "={{ JSON.stringify($json.pack_requests) }}"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "fullResponse": true
            }
          },
          "retry": {
            "retry": {
              "maxRetries": 3,
              "retryInterval": 2000
            }
          }
        }
      },
      "id": "grant-packs",
      "name": "Grant Packs",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [1450, 100],
      "credentials": {
        "httpHeaderAuth": {
          "id": "1",
          "name": "Paper Dynasty API"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.statusCode }}",
              "operation": "between",
              "value2": 200,
              "value3": 299
            }
          ]
        }
      },
      "id": "check-api-success",
      "name": "API Success?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [1650, 100]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://discord.com/api/webhooks/1438578498108133488/MGx1zyAa1ewzVy3RqCsH50ZJUiVvT5J5Vl2jExGIkIsu6v0x8A9J3-ruRftClPEq91ej",
        "sendBody": true,
        "contentType": "json",
        "body": "={{ JSON.stringify({\n  content: '✅ Ko-fi Order Processed',\n  embeds: [{\n    title: 'Paper Dynasty Packs Granted',\n    color: 3066993,\n    fields: [\n      {\n        name: 'Customer',\n        value: $('Parse & Validate').item.json.from_name,\n        inline: true\n      },\n      {\n        name: 'Team',\n        value: $('Lookup PD Team').item.json.pd_team_abbrev,\n        inline: true\n      },\n      {\n        name: 'Amount',\n        value: '$' + $('Parse & Validate').item.json.amount + ' ' + $('Parse & Validate').item.json.currency,\n        inline: true\n      },\n      {\n        name: 'Packs Granted',\n        value: $('Map Products').item.json.total_packs + ' packs',\n        inline: false\n      },\n      {\n        name: 'Transaction ID',\n        value: '`' + $('Parse & Validate').item.json.kofi_transaction_id + '`',\n        inline: false\n      }\n    ],\n    timestamp: new Date().toISOString()\n  }]\n}) }}"
      },
      "id": "discord-success",
      "name": "Discord Success",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [1850, 0]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://discord.com/api/webhooks/1438578498108133488/MGx1zyAa1ewzVy3RqCsH50ZJUiVvT5J5Vl2jExGIkIsu6v0x8A9J3-ruRftClPEq91ej",
        "sendBody": true,
        "contentType": "json",
        "body": "={{ JSON.stringify({\n  content: '@here ⚠️ Ko-fi Order Processing Error',\n  embeds: [{\n    title: 'Paper Dynasty API Error',\n    color: 15158332,\n    fields: [\n      {\n        name: 'Customer',\n        value: $('Parse & Validate').item.json.from_name + ' (' + $('Parse & Validate').item.json.email + ')',\n        inline: false\n      },\n      {\n        name: 'Amount',\n        value: '$' + $('Parse & Validate').item.json.amount,\n        inline: true\n      },\n      {\n        name: 'Team ID',\n        value: String($('Lookup PD Team').item.json.pd_team_id || 'Unknown'),\n        inline: true\n      },\n      {\n        name: 'API Status',\n        value: String($json.statusCode || 'Request Failed'),\n        inline: true\n      },\n      {\n        name: 'Transaction ID',\n        value: '`' + $('Parse & Validate').item.json.kofi_transaction_id + '`',\n        inline: false\n      },\n      {\n        name: 'Action Required',\n        value: 'Manual pack distribution needed. Use transaction ID to process manually.',\n        inline: false\n      }\n    ],\n    timestamp: new Date().toISOString()\n  }]\n}) }}"
      },
      "id": "discord-error",
      "name": "Discord Error",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [1850, 200]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://discord.com/api/webhooks/1438578498108133488/MGx1zyAa1ewzVy3RqCsH50ZJUiVvT5J5Vl2jExGIkIsu6v0x8A9J3-ruRftClPEq91ej",
        "sendBody": true,
        "contentType": "json",
        "body": "={{ JSON.stringify({\n  content: '@here 🔍 Ko-fi Order Needs Manual Review',\n  embeds: [{\n    title: 'Unable to Identify Paper Dynasty Team',\n    color: 16776960,\n    fields: [\n      {\n        name: 'Customer',\n        value: $json.from_name + ' (' + $json.email + ')',\n        inline: false\n      },\n      {\n        name: 'Amount',\n        value: '$' + $json.amount + ' ' + $json.currency,\n        inline: true\n      },\n      {\n        name: 'Discord User ID',\n        value: $json.discord_userid || 'Not provided',\n        inline: true\n      },\n      {\n        name: 'Message',\n        value: $json.message || 'No message',\n        inline: false\n      },\n      {\n        name: 'Products',\n        value: $json.shop_items.map(i => i.quantity + 'x ' + i.variation_name + ' (' + i.direct_link_code + ')').join('\\n'),\n        inline: false\n      },\n      {\n        name: 'Transaction ID',\n        value: '`' + $json.kofi_transaction_id + '`',\n        inline: false\n      },\n      {\n        name: 'Action Required',\n        value: '1. Identify user\\'s Paper Dynasty team\\n2. Manually grant packs using PD API\\n3. Reply when resolved',\n        inline: false\n      }\n    ],\n    timestamp: new Date().toISOString()\n  }]\n}) }}"
      },
      "id": "discord-manual-review",
      "name": "Discord Manual Review",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [1250, 300]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({\n  status: 'success',\n  message: 'Order received and processed',\n  execution_id: $execution.id\n}) }}"
      },
      "id": "respond-webhook",
      "name": "Return 200 OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [2050, 150]
    }
  ],
  "connections": {
    "Ko-fi Webhook": {
      "main": [
        [
          {
            "node": "Parse & Validate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse & Validate": {
      "main": [
        [
          {
            "node": "Shop Order?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Shop Order?": {
      "main": [
        [
          {
            "node": "Lookup PD Team",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Return 200 OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lookup PD Team": {
      "main": [
        [
          {
            "node": "Team Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Team Found?": {
      "main": [
        [
          {
            "node": "Map Products",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Discord Manual Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Map Products": {
      "main": [
        [
          {
            "node": "Grant Packs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Grant Packs": {
      "main": [
        [
          {
            "node": "API Success?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API Success?": {
      "main": [
        [
          {
            "node": "Discord Success",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Discord Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Discord Success": {
      "main": [
        [
          {
            "node": "Return 200 OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Discord Error": {
      "main": [
        [
          {
            "node": "Return 200 OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Discord Manual Review": {
      "main": [
        [
          {
            "node": "Return 200 OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [],
  "triggerCount": 0,
  "updatedAt": "2025-11-13T00:00:00.000Z",
  "versionId": "1"
}
