{ "kind": "open-flow-workflow", "version": 1, "name": "Log Stripe Charges in Pipedrive", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_01235db05f84eb47": { "name": "Daily at 08:00 UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "0 8 * * *", "timezone": "UTC" } ] }, "n_b090d95c7ff7cdd5": { "inputs": {}, "name": "Read the last run time", "kind": "task", "task": { "inputs": [], "outputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "since" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "until" } ], "name": "Read the last run time", "moduleId": "n_cfbaf048c5782aec", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_8d2d17125fdb8695": { "inputs": { "since": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b090d95c7ff7cdd5", "output": "since" } ] }, "until": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b090d95c7ff7cdd5", "output": "until" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8d2d17125fdb8695", "output": "state" } ] }, "response": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_75589d3a2e37ec6f", "output": "body" } ] }, "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_75589d3a2e37ec6f", "output": "statusCode" } ] } }, "name": "Collect a Stripe page", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "since" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "until" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": {}, "nullable": true, "handle": "response" }, { "jsonSchema": { "type": "integer" }, "nullable": true, "handle": "statusCode" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": [ "string", "number", "boolean", "null" ] } }, "nullable": false, "handle": "params" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "charges" } ], "name": "Collect a Stripe page", "moduleId": "n_59d3fc36e864bb23", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_90bf05fe034e1a44": { "inputs": {}, "name": "Another Stripe page", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_8d2d17125fdb8695", "output": "more" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_75589d3a2e37ec6f": { "inputs": { "endpoint": { "kind": "value", "value": "/v1/charges" }, "query": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8d2d17125fdb8695", "output": "params" } ] } }, "name": "List recent charges", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": [ "string", "number", "boolean", "null" ] } }, "nullable": true, "handle": "query" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "name": "List recent charges", "executor": { "providerId": "stripe", "method": "GET", "query": { "mode": "object", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "kind": "connector-proxy" } } }, "n_54d8924b3045b126": { "inputs": { "charges": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8d2d17125fdb8695", "output": "charges" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_54d8924b3045b126", "output": "state" } ] }, "confirmed": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d96423804c85bc41", "output": "confirmed" } ] } }, "name": "Choose the next charge", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "charges" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "confirmed" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "customerId" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "charge" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "written" } ], "name": "Choose the next charge", "moduleId": "n_53a663f5a6219d69", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_a627ceaf9d90d377": { "inputs": {}, "name": "Another charge", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_54d8924b3045b126", "output": "more" } }, "operator": "==", "right": { "kind": "value", "value": true } } ] } ] } ] }, "n_b0eaca4ac40c0c40": { "inputs": { "customerId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_54d8924b3045b126", "output": "customerId" } ] } }, "name": "Get the Stripe customer", "kind": "task", "task": { "inputs": [ { "description": "The Stripe customer ID to retrieve.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The Stripe customer ID to retrieve." }, "nullable": false, "handle": "customerId" } ], "outputs": [ { "description": "A raw Stripe object returned by the API.", "jsonSchema": { "type": [ "object", "null" ], "properties": {}, "additionalProperties": true, "description": "A raw Stripe object returned by the API." }, "nullable": true, "handle": "customer" } ], "name": "Get the Stripe customer", "executor": { "kind": "connector", "action": "stripe.get_customer" } } }, "n_f868a35b1ac5c798": { "inputs": { "customer": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b0eaca4ac40c0c40", "output": "customer" } ] }, "charge": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_54d8924b3045b126", "output": "charge" } ] } }, "name": "Read the customer name", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": [ "object", "null" ], "properties": {}, "additionalProperties": true, "description": "A raw Stripe object returned by the API." }, "nullable": true, "handle": "customer" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "charge" } ], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "name" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "charge" } ], "name": "Read the customer name", "moduleId": "n_ee0212b830bb1f74", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_8013953c9d80e557": { "inputs": { "term": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_f868a35b1ac5c798", "output": "name" } ] }, "fields": { "kind": "value", "value": [ "name" ] }, "exactMatch": { "kind": "value", "value": true }, "limit": { "kind": "value", "value": 2 } }, "name": "Find the Pipedrive organization", "kind": "task", "task": { "inputs": [ { "description": "Search term to send to Pipedrive. Use at least 2 characters unless exactMatch is true.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Search term to send to Pipedrive. Use at least 2 characters unless exactMatch is true." }, "nullable": false, "handle": "term" }, { "description": "Organization fields to search through.", "jsonSchema": { "type": "array", "items": { "type": "string", "enum": [ "address", "custom_fields", "notes", "name" ], "description": "Organization search field accepted by Pipedrive." }, "minItems": 1, "description": "Organization fields to search through." }, "nullable": true, "handle": "fields", "value": null }, { "description": "Whether Pipedrive should only return case-insensitive exact matches.", "jsonSchema": { "type": "boolean", "description": "Whether Pipedrive should only return case-insensitive exact matches." }, "nullable": true, "handle": "exactMatch", "value": null }, { "description": "Maximum number of search results to return in one Pipedrive page.", "jsonSchema": { "type": "integer", "minimum": 1, "maximum": 500, "description": "Maximum number of search results to return in one Pipedrive page." }, "nullable": true, "handle": "limit", "value": null }, { "description": "Opaque pagination cursor returned by a previous Pipedrive response.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Opaque pagination cursor returned by a previous Pipedrive response." }, "nullable": true, "handle": "cursor", "value": null } ], "outputs": [ { "description": "Search results returned by Pipedrive for organization.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "result_score": { "type": "number", "description": "Search relevance score." }, "item": { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "description": "Organization ID." }, "type": { "type": "string", "description": "Search item type." }, "name": { "type": "string", "description": "Organization name." }, "address": { "type": [ "string", "null" ], "description": "Organization address." }, "visible_to": { "type": "integer", "description": "Pipedrive visibility flag." }, "owner": { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "description": "Owner user ID." } }, "required": [ "id" ], "additionalProperties": true, "description": "Owner reference." }, "custom_fields": { "type": "array", "items": { "type": "string", "description": "Matched custom field value." }, "description": "Matched custom field values." }, "notes": { "type": "array", "items": { "type": "string", "description": "Matched note snippet." }, "description": "Matched notes." } }, "required": [ "id", "type", "name" ], "additionalProperties": true, "description": "Matched Pipedrive organization." } }, "required": [ "result_score", "item" ], "additionalProperties": false, "description": "Pipedrive organization search result." }, "description": "Search results returned by Pipedrive for organization." }, "nullable": false, "handle": "items" }, { "description": "Cursor for the next page when Pipedrive has more search results.", "jsonSchema": { "type": [ "string", "null" ], "description": "Cursor for the next page when Pipedrive has more search results." }, "nullable": true, "handle": "nextCursor" } ], "name": "Find the Pipedrive organization", "executor": { "kind": "connector", "action": "pipedrive.search_organizations" } } }, "n_5df00ec46dce60bc": { "inputs": { "items": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8013953c9d80e557", "output": "items" } ] }, "nextCursor": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8013953c9d80e557", "output": "nextCursor" } ] }, "charge": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_f868a35b1ac5c798", "output": "charge" } ] } }, "name": "Prepare the charge note", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "result_score": { "type": "number", "description": "Search relevance score." }, "item": { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "description": "Organization ID." }, "type": { "type": "string", "description": "Search item type." }, "name": { "type": "string", "description": "Organization name." }, "address": { "type": [ "string", "null" ], "description": "Organization address." }, "visible_to": { "type": "integer", "description": "Pipedrive visibility flag." }, "owner": { "type": "object", "properties": { "id": { "type": "integer", "exclusiveMinimum": 0, "description": "Owner user ID." } }, "required": [ "id" ], "additionalProperties": true, "description": "Owner reference." }, "custom_fields": { "type": "array", "items": { "type": "string", "description": "Matched custom field value." }, "description": "Matched custom field values." }, "notes": { "type": "array", "items": { "type": "string", "description": "Matched note snippet." }, "description": "Matched notes." } }, "required": [ "id", "type", "name" ], "additionalProperties": true, "description": "Matched Pipedrive organization." } }, "required": [ "result_score", "item" ], "additionalProperties": false, "description": "Pipedrive organization search result." }, "description": "Search results returned by Pipedrive for organization." }, "nullable": false, "handle": "items" }, { "jsonSchema": { "type": [ "string", "null" ], "description": "Cursor for the next page when Pipedrive has more search results." }, "nullable": true, "handle": "nextCursor" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "charge" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" } ], "name": "Prepare the charge note", "moduleId": "n_b72aebd96f7e22d0", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_b4eace59969c2da4": { "inputs": { "endpoint": { "kind": "value", "value": "/api/v1/notes" }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_5df00ec46dce60bc", "output": "body" } ] } }, "name": "Create the Pipedrive note", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": {}, "nullable": true, "handle": "body" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "name": "Create the Pipedrive note", "executor": { "providerId": "pipedrive", "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "json", "kind": "connector-proxy" } } }, "n_d96423804c85bc41": { "inputs": { "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b4eace59969c2da4", "output": "statusCode" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b4eace59969c2da4", "output": "body" } ] } }, "name": "Confirm the note", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": {}, "nullable": true, "handle": "body" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "confirmed" } ], "name": "Confirm the note", "moduleId": "n_3ea7c4811899e24f", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_3eb509240209b6ed": { "inputs": { "written": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_54d8924b3045b126", "output": "written" } ] }, "until": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b090d95c7ff7cdd5", "output": "until" } ] } }, "name": "Save the completed run", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "written" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "until" } ], "outputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "name": "Save the completed run", "moduleId": "n_f041535f152a1aaa", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_efcad5bca5d86fcb": { "name": "Number of notes", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "outputMappings": { "count": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_3eb509240209b6ed", "output": "count" } ] } } } }, "edges": [ { "source": "n_01235db05f84eb47", "target": "n_b090d95c7ff7cdd5" }, { "source": "n_b090d95c7ff7cdd5", "target": "n_8d2d17125fdb8695" }, { "source": "n_8d2d17125fdb8695", "target": "n_90bf05fe034e1a44" }, { "source": "n_90bf05fe034e1a44", "target": "n_75589d3a2e37ec6f", "sourceHandle": "yes" }, { "source": "n_75589d3a2e37ec6f", "target": "n_8d2d17125fdb8695" }, { "source": "n_90bf05fe034e1a44", "target": "n_54d8924b3045b126", "sourceHandle": "otherwise" }, { "source": "n_54d8924b3045b126", "target": "n_a627ceaf9d90d377" }, { "source": "n_a627ceaf9d90d377", "target": "n_b0eaca4ac40c0c40", "sourceHandle": "yes" }, { "source": "n_b0eaca4ac40c0c40", "target": "n_f868a35b1ac5c798" }, { "source": "n_f868a35b1ac5c798", "target": "n_8013953c9d80e557" }, { "source": "n_8013953c9d80e557", "target": "n_5df00ec46dce60bc" }, { "source": "n_5df00ec46dce60bc", "target": "n_b4eace59969c2da4" }, { "source": "n_b4eace59969c2da4", "target": "n_d96423804c85bc41" }, { "source": "n_d96423804c85bc41", "target": "n_54d8924b3045b126" }, { "source": "n_a627ceaf9d90d377", "target": "n_3eb509240209b6ed", "sourceHandle": "otherwise" }, { "source": "n_3eb509240209b6ed", "target": "n_efcad5bca5d86fcb" } ] } }, "modules": { "n_cfbaf048c5782aec": { "name": "Read the last run time", "imports": [], "source": "export default async function window(_input,context){const now=Math.floor(Date.now()/1000);let since=await context.state.get('last-charge-run');if(since==null){since=now;await context.state.set('last-charge-run',now)}if(!Number.isSafeInteger(since)||since<0||since>now)throw Error('Invalid saved charge watermark');return {since,until:now}}\n" }, "n_59d3fc36e864bb23": { "name": "Collect a Stripe page", "imports": [], "source": "export default function page({since,until,previousState,response,statusCode}){let state=previousState??{charges:[],seen:[],page:null};if(!Array.isArray(state.charges)||!Array.isArray(state.seen))throw Error('Invalid charge-list progress');if(response!=null){if(statusCode<200||statusCode>=300||!Array.isArray(response.data)||typeof response.has_more!=='boolean')throw Error('Invalid Stripe list response');for(const c of response.data)if(!c||typeof c.id!=='string'||!c.id||!['succeeded','pending','failed'].includes(c.status))throw Error('Invalid Stripe charge');state={charges:state.charges.concat(response.data.filter(c=>c.status==='succeeded')),seen:state.seen,page:null};if(response.has_more){const token=response.data.at(-1)?.id;if(typeof token!=='string'||!token||state.seen.includes(token))throw Error('Invalid or repeated Stripe page');state.page=token;state.seen=state.seen.concat(token)}}return {state,more:response==null||state.page!==null,params:{'created[gte]':since,'created[lt]':until,limit:100,...(state.page?{starting_after:state.page}:{})},charges:state.charges}}\n" }, "n_53a663f5a6219d69": { "name": "Choose the next charge", "imports": [], "source": "export default function next({charges,previousState,confirmed}){if(!Array.isArray(charges))throw Error('Invalid charges');let state=previousState??{index:0,written:0};if(previousState!=null){if(!confirmed||confirmed.success!==true)throw Error('Note not confirmed');state={index:state.index,written:state.written+1}}let index=state.index;while(indexString(s??'').replaceAll('&','&').replaceAll('<','<').replaceAll('>','>').replaceAll('\"','"').replaceAll(\"'\",''');return {body:{org_id:items[0].item.id,content:`${esc(charge.description)}: ${chargeAmount(charge.amount,charge.currency)} ${esc(charge.currency)}`}}}\n" }, "n_3ea7c4811899e24f": { "name": "Confirm the note", "imports": [], "source": "export default function confirm({statusCode,body}){if(statusCode<200||statusCode>=300||!body||body.success!==true||!body.data||!Number.isSafeInteger(body.data.id))throw Error('Pipedrive did not confirm note');return {confirmed:{success:true}}}\n" }, "n_f041535f152a1aaa": { "name": "Save the completed run", "imports": [], "source": "export default async function finish({written,until},context){if(!Number.isSafeInteger(written)||!Number.isSafeInteger(until))throw Error('Invalid charge run state');if(written>0)await context.state.set('last-charge-run',until);return {count:written}}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }