Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,33 @@
{
"extends": [
"../../../../.eslintrc.base.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

View File

@@ -0,0 +1,7 @@
# pieces-lightfunnels
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build pieces-lightfunnels` to build the library.

View File

@@ -0,0 +1,10 @@
{
"name": "@activepieces/piece-lightfunnels",
"version": "0.0.1",
"type": "commonjs",
"main": "./src/index.js",
"types": "./src/index.d.ts",
"dependencies": {
"tslib": "^2.3.0"
}
}

View File

@@ -0,0 +1,65 @@
{
"name": "pieces-lightfunnels",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/pieces/community/lightfunnels/src",
"projectType": "library",
"release": {
"version": {
"manifestRootsToUpdate": [
"dist/{projectRoot}"
],
"currentVersionResolver": "git-tag",
"fallbackCurrentVersionResolver": "disk"
}
},
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/pieces/community/lightfunnels",
"tsConfig": "packages/pieces/community/lightfunnels/tsconfig.lib.json",
"packageJson": "packages/pieces/community/lightfunnels/package.json",
"main": "packages/pieces/community/lightfunnels/src/index.ts",
"assets": [
"packages/pieces/community/lightfunnels/*.md",
{
"input": "packages/pieces/community/lightfunnels/src/i18n",
"output": "./src/i18n",
"glob": "**/!(i18n.json)"
}
],
"buildableProjectDepsInPackageJsonType": "dependencies",
"updateBuildableProjectDepsInPackageJson": true
},
"dependsOn": [
"prebuild",
"^build"
]
},
"nx-release-publish": {
"options": {
"packageRoot": "dist/{projectRoot}"
}
},
"prebuild": {
"dependsOn": [
"^build"
],
"executor": "nx:run-commands",
"options": {
"cwd": "packages/pieces/community/lightfunnels",
"command": "bun install --no-save --silent"
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": [
"{options.outputFile}"
]
}
}
}

View File

@@ -0,0 +1,44 @@
import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
import { OAuth2GrantType, PieceCategory } from '@activepieces/shared';
import { newOrderTrigger } from './lib/triggers/new-order';
import { newLeadTrigger } from './lib/triggers/new-lead';
import { listProducts } from './lib/actions/list-products';
import { getProduct } from './lib/actions/get-product';
import { createProduct } from './lib/actions/create-product';
import { getOrder } from './lib/actions/get-order';
import { listOrders } from './lib/actions/list-orders';
import { cancelOrder } from './lib/actions/cancel-order';
import { createCustomer } from './lib/actions/create-customer';
import { getCustomer } from './lib/actions/get-customer';
import { listCustomers } from './lib/actions/list-customers';
import { getFunnel } from './lib/actions/get-funnel';
export const lightfunnelsAuth = PieceAuth.OAuth2({
grantType: OAuth2GrantType.AUTHORIZATION_CODE,
authUrl: 'https://app.lightfunnels.com/admin/oauth',
tokenUrl: 'https://services.lightfunnels.com/oauth/access',
required: true,
scope: ['products,orders,customers,funnels'],
});
export const lightfunnels = createPiece({
displayName: 'Lightfunnels',
auth: lightfunnelsAuth,
minimumSupportedRelease: '0.36.1',
logoUrl: 'https://cdn.activepieces.com/pieces/lightfunnels.png',
categories: [PieceCategory.SALES_AND_CRM, PieceCategory.COMMERCE],
authors: ['aminefrira','sanket-a11y'],
actions: [
listProducts,
getProduct,
createProduct,
getOrder,
listOrders,
cancelOrder,
createCustomer,
getCustomer,
listCustomers,
getFunnel,
],
triggers: [newOrderTrigger, newLeadTrigger],
});

View File

@@ -0,0 +1,65 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const cancelOrder = createAction({
auth: lightfunnelsAuth,
name: 'cancel_order',
displayName: 'Cancel Order',
description: 'Cancel an order',
props: {
orderId: Property.ShortText({
displayName: 'Order ID',
description: 'The ID of the order to cancel',
required: true,
}),
reason: Property.ShortText({
displayName: 'Reason',
description: 'Reason for cancellation',
required: true,
}),
notifyCustomer: Property.Checkbox({
displayName: 'Notify Customer',
description: 'Whether to notify the customer',
required: false,
defaultValue: true,
}),
refund: Property.Checkbox({
displayName: 'Refund',
description: 'Whether to refund the order',
required: false,
defaultValue: false,
}),
},
async run(context) {
const { orderId, reason, notifyCustomer, refund } = context.propsValue;
const graphqlQuery = `
mutation cancelOrderMutation($id: ID!, $reason: String!, $notifyCustomer: Boolean!, $refund: Boolean!) {
cancelOrder(id: $id, reason: $reason, notifyCustomer: $notifyCustomer, refund: $refund) {
id
_id
name
cancelled_at
financial_status
fulfillment_status
}
}
`;
const variables = {
id: orderId,
reason,
notifyCustomer: notifyCustomer ?? true,
refund: refund ?? false,
};
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
variables
);
return response.data.cancelOrder;
},
});

View File

@@ -0,0 +1,90 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const createCustomer = createAction({
auth: lightfunnelsAuth,
name: 'create_customer',
displayName: 'Create Customer',
description: 'Create a new customer',
props: {
firstName: Property.ShortText({
displayName: 'First Name',
description: 'First name of the customer',
required: true,
}),
lastName: Property.ShortText({
displayName: 'Last Name',
description: 'Last name of the customer',
required: true,
}),
email: Property.ShortText({
displayName: 'Email',
description: 'Email of the customer',
required: true,
}),
phone: Property.ShortText({
displayName: 'Phone',
description: 'Phone number of the customer eg: +1234567890',
required: true,
}),
acceptsMarketing: Property.Checkbox({
displayName: 'Accepts Marketing',
description: 'Whether the customer accepts marketing communications',
required: true,
}),
tags: Property.Array({
displayName: 'Tags',
description: 'Tags to associate with the customer',
required: true,
properties: {
tag: Property.ShortText({
displayName: 'Tag',
required: true,
}),
},
}),
},
async run(context) {
const { firstName, lastName, email, phone, acceptsMarketing, tags } = context.propsValue;
const graphqlQuery = `
mutation createCustomerMutation($node: InputCustomer!) {
createCustomer(node: $node) {
id
_id
first_name
last_name
full_name
email
phone
accepts_marketing
tags
created_at
updated_at
}
}
`;
const tagArray = (tags || []).map((t: any) => t.tag);
const variables = {
node: {
first_name: firstName,
last_name: lastName,
email,
phone,
accepts_marketing: acceptsMarketing,
tags: tagArray,
},
};
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
variables
);
return response.data.createCustomer;
},
});

View File

@@ -0,0 +1,184 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const createProduct = createAction({
auth: lightfunnelsAuth,
name: 'create_product',
displayName: 'Create Product',
description: 'Create a new product',
props: {
title: Property.ShortText({
displayName: 'Title',
description: 'The title of the product',
required: true,
}),
price: Property.Number({
displayName: 'Price',
description: 'The price of the product',
required: true,
}),
description: Property.LongText({
displayName: 'Description',
description: 'The description of the product',
required: false,
}),
compare_at_price: Property.Number({
displayName: 'Compare at Price',
description: 'The original price before discount',
required: false,
}),
sku: Property.ShortText({
displayName: 'SKU',
description: 'The SKU of the product',
required: false,
}),
options: Property.Array({
displayName: 'Options',
description: 'Product options like size, color, etc.',
required: true,
properties: {
id: Property.ShortText({
displayName: 'Option ID',
description: 'Unique identifier for the option',
required: true,
}),
label: Property.ShortText({
displayName: 'Label',
description: 'The option label (e.g., "Color", "Size")',
required: false,
}),
options: Property.Json({
displayName: 'Option Values',
description: 'Available values for this option (e.g., [{"value": "Red"}, {"value": "Blue"}])',
required: true,
}),
type: Property.StaticDropdown({
displayName: 'Type',
description: 'The type of option',
required: false,
options: {
disabled: false,
options: [
{ label: 'Text', value: 'text' },
{ label: 'Color', value: 'color' },
{ label: 'Image', value: 'image' },
],
},
}),
},
}),
variants: Property.Array({
displayName: 'Variants',
description: 'Product variants with their details',
required: true,
properties: {
id: Property.ShortText({
displayName: 'Variant ID',
description: 'Unique identifier for the variant',
required: true,
}),
sku: Property.ShortText({
displayName: 'Variant SKU',
description: 'SKU for this specific variant',
required: false,
}),
price: Property.Number({
displayName: 'Variant Price',
description: 'Price for this variant',
required: false,
}),
compare_at_price: Property.Number({
displayName: 'Variant Compare at Price',
description: 'Original price for this variant',
required: false,
}),
options: Property.Json({
displayName: 'Variant Options',
description: 'Option values for this variant (array of objects with id and value pairs)',
required: true,
}),
},
}),
},
async run(context) {
const {
title,
price,
description,
compare_at_price,
sku,
options,
variants,
} = context.propsValue;
const graphqlQuery = `
mutation mutationName($node: InputProduct!) {
createProduct(node: $node) {
id
_id
title
description
price
compare_at_price
sku
options {
id
label
type
}
variants {
id
sku
price
compare_at_price
}
created_at
updated_at
}
}
`;
const inputOptions = (options || []).map((opt: any) => {
const optionValues = (opt.options || []).map((o: any) => o.value);
return {
id: opt.id,
label: opt.label || undefined,
options: optionValues,
type: opt.type || 'text',
};
});
const inputVariants = (variants || []).map((variant: any) => ({
id: variant.id,
sku: variant.sku || undefined,
price: variant.price !== undefined ? variant.price : price,
compare_at_price: variant.compare_at_price || compare_at_price,
options: (variant.options || []).map((opt: any) => ({
id: opt.id,
value: opt.value,
})),
}));
const variables = {
node: {
title,
price,
description: description || undefined,
compare_at_price: compare_at_price || undefined,
sku: sku || undefined,
options: inputOptions,
variants: inputVariants,
},
};
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
variables
);
return response.data.createProduct;
},
});

View File

@@ -0,0 +1,50 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const getCustomer = createAction({
auth: lightfunnelsAuth,
name: 'get_customer',
displayName: 'Get Customer',
description: 'Retrieve a specific customer by ID',
props: {
customerId: Property.ShortText({
displayName: 'Customer ID',
description: 'The ID of the customer to retrieve',
required: true,
}),
},
async run(context) {
const { customerId } = context.propsValue;
const graphqlQuery = `
query CustomerQuery($id: ID!) {
node(id: $id) {
... on Customer {
id
_id
first_name
last_name
full_name
email
phone
avatar
location
expenses
orders_count
created_at
updated_at
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{ id: customerId }
);
return response.data.node;
},
});

View File

@@ -0,0 +1,44 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const getFunnel = createAction({
auth: lightfunnelsAuth,
name: 'get_funnel',
displayName: 'Get Funnel',
description: 'Retrieve a specific funnel by ID',
props: {
funnelId: Property.ShortText({
displayName: 'Funnel ID',
description: 'The ID of the funnel to retrieve',
required: true,
}),
},
async run(context) {
const { funnelId } = context.propsValue;
const graphqlQuery = `
query FunnelQuery($id: ID!) {
node(id: $id) {
... on Funnel {
id
_id
name
slug
published
created_at
updated_at
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{ id: funnelId }
);
return response.data.node;
},
});

View File

@@ -0,0 +1,163 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const getOrder = createAction({
auth: lightfunnelsAuth,
name: 'get_order',
displayName: 'Get Order',
description: 'Retrieve a specific order by ID',
props: {
orderId: Property.ShortText({
displayName: 'Order ID',
description: 'The ID of the order to retrieve',
required: true,
}),
},
async run(context) {
const { orderId } = context.propsValue;
const graphqlQuery = `
query viewOrderQuery($id: ID!) {
order: node(id: $id) {
__typename
... on Order {
id
_id
total
subtotal
discount_value
normal_discount_value
bundle_discount_value
pm_discount_value
pm_extra_fees
name
notes
email
phone
archived_at
refunded_amount
paid_by_customer
net_payment
original_total
refundable
created_at
cancelled_at
test
tags
shipping
shipping_discount
customer {
id
full_name
avatar
location
}
custom
items {
__typename
... on VariantSnapshot {
product_id
id
_id
image {
path
id
}
title
price
variant_id
fulfillment_status
carrier
tracking_number
tracking_link
refund_id
payment_id
removed_at
sku
custom_options {
name
key
value
type
}
options {
id
label
value
}
}
}
payments {
id
_id
total
sub_total
created_at
refunded
refundable
price_bundle_snapshot {
id
value
discount_result
label
}
discount_snapshot {
_id
id
type
code
value
discount_result
}
refunds {
id
_id
amount
reason
}
}
shipping_address {
first_name
last_name
email
phone
line1
line2
country
city
area
zip
state
}
billing_address {
first_name
last_name
email
phone
line1
line2
country
city
area
zip
state
}
client_details {
ip
}
currency
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{ id: orderId }
);
return response.data.order;
},
});

View File

@@ -0,0 +1,183 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const getProduct = createAction({
auth: lightfunnelsAuth,
name: 'get_product',
displayName: 'Get Product',
description: 'Retrieve a specific product by ID',
props: {
productId: Property.ShortText({
displayName: 'Product ID',
description: 'The ID of the product to retrieve',
required: true,
}),
},
async run(context) {
const { productId } = context.propsValue;
const graphqlQuery = `
query productQuery($id: ID!) {
product: node(id: $id) {
__typename
... on Product {
__typename
id
_id
uid
slug
title
description
notice_text
updated_at
price
sku
file_id
compare_at_price
product_type
shipping_group_id
price_bundle_id
thumbnail {
id
path
}
file {
id
title
size
path
}
options {
id
label
options
type
options_types {
option_id
value
}
}
tags {
id
title
}
images_ids
variants {
id
price
file_id
image_id
compare_at_price
sku
options {
option_id: id
value
}
enable_inventory_limit
inventory_quantity
}
order_bump {
id
title
price
sku
enabled
compare_at_price
description
image_id
file_id
}
order_bumps {
id
order_bump_product_id
variant_id
is_checked
product {
id
uid
title
description
price
compare_at_price
sku
file_id
}
}
features {
id
title
description
image_id
}
testimonials {
id
name
position
comment
image_id
}
faq {
id
question
answer
}
enable_custom_options
custom_options {
id
type
name
placeholder
}
price_bundle {
id
items {
id
label
quantity
discount_value
discount_type
}
}
default_variant {
id
uid: id
file_uid: file_id
_id
price
image {
id
path
}
compare_at_price
sku
}
inventory_quantity
enable_inventory_limit
funnels {
id
uid: id
name
slug
}
stores {
id
uid: id
name
slug
}
created_at
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{ id: productId },
);
return response.data.product;
},
})

View File

@@ -0,0 +1,71 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const listCustomers = createAction({
auth: lightfunnelsAuth,
name: 'list_customers',
displayName: 'List Customers',
description: 'Retrieve a list of customers',
props: {
first: Property.Number({
displayName: 'Limit',
description: 'Number of customers to retrieve (default: 10)',
required: false,
defaultValue: 10,
}),
after: Property.ShortText({
displayName: 'After Cursor',
description: 'Cursor for pagination (optional)',
required: false,
}),
query: Property.ShortText({
displayName: 'Query',
description: 'Filter query (e.g., "order_by:created_at order_dir:desc")',
required: false,
defaultValue: 'order_by:created_at order_dir:desc',
}),
},
async run(context) {
const { first, after, query } = context.propsValue;
const graphqlQuery = `
query customersQuery($first: Int, $after: String, $query: String!) {
customers(first: $first, after: $after, query: $query) {
edges {
node {
id
_id
avatar
email
full_name
phone
updated_at
created_at
location
}
expenses
orders_count
cursor
}
pageInfo {
endCursor
hasNextPage
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{
first: first || 10,
after: after || null,
query: query || 'order_by:created_at order_dir:desc',
},
);
return response.data.customers;
},
});

View File

@@ -0,0 +1,89 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const listOrders = createAction({
auth: lightfunnelsAuth,
name: 'list_orders',
displayName: 'List Orders',
description: 'Retrieve a list of orders',
props: {
first: Property.Number({
displayName: 'Limit',
description: 'Number of orders to retrieve (default: 10)',
required: false,
defaultValue: 10,
}),
after: Property.ShortText({
displayName: 'After Cursor',
description: 'Cursor for pagination (optional)',
required: false,
}),
query: Property.ShortText({
displayName: 'Query',
description: 'Filter query (e.g., "order_by:created_at order_dir:desc")',
required: false,
defaultValue: 'order_by:created_at order_dir:desc',
}),
},
async run(context) {
const { first, after, query } = context.propsValue;
const graphqlQuery = `
query ordersQuery($first: Int, $after: String, $query: String!) {
orders(first: $first, after: $after, query: $query) {
edges {
node {
id
_id
name
total
fulfillment_status
financial_status
customer {
full_name
id
}
cancelled_at
date: created_at
formattedDate: created_at(format: "LLL")
test
currency
checkout {
id
store {
id
name
}
funnel {
id
name
}
}
tags
}
cursor
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{
first: first || 10,
after: after || null,
query: query || 'order_by:created_at order_dir:desc',
},
);
return response.data.orders;
},
});

View File

@@ -0,0 +1,98 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const listProducts = createAction({
auth: lightfunnelsAuth,
name: 'list_products',
displayName: 'List Products',
description: 'Retrieve a list of all products',
props: {
first: Property.Number({
displayName: 'Limit',
description: 'Number of products to retrieve (default: 10)',
required: false,
defaultValue: 10,
}),
after: Property.ShortText({
displayName: 'After Cursor',
description: 'Cursor for pagination (optional)',
required: false,
}),
query: Property.ShortText({
displayName: 'Query',
description: 'Filter query (e.g., "order_by:id order_dir:desc last_month")',
required: false,
defaultValue: 'order_by:id order_dir:desc',
}),
},
async run(context) {
const { first, after, query } = context.propsValue;
const graphqlQuery = `
query productsQuery($first: Int, $after: String, $query: String!) {
products(query: $query, after: $after, first: $first) {
edges {
node {
id
_id
uid
slug
title
description
notice_text
price
sku
file_id
compare_at_price
product_type
thumbnail {
id
path
}
file {
id
title
size
path
}
tags {
id
title
}
variants {
id
price
compare_at_price
sku
enable_inventory_limit
inventory_quantity
}
inventory_quantity
enable_inventory_limit
created_at
updated_at
}
cursor
}
pageInfo {
endCursor
hasNextPage
}
}
}
`;
const response = await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
graphqlQuery,
{
first: first || 10,
after: after || null,
query: query || 'order_by:id order_dir:desc',
},
);
return response.data.products;
},
})

View File

@@ -0,0 +1,31 @@
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
import { OAuth2PropertyValue } from '@activepieces/pieces-framework';
export const lightfunnelsCommon = {
baseUrl: 'https://services.lightfunnels.com/api/v2',
async makeGraphQLRequest<T = any>(
auth: OAuth2PropertyValue,
query: string,
variables?: Record<string, any>,
): Promise<{ data: T }> {
const response = await httpClient.sendRequest<{ data: T; errors?: any[] }>({
method: HttpMethod.POST,
url: this.baseUrl,
headers: {
Authorization: `Bearer ${auth.access_token}`,
'Content-Type': 'application/json',
},
body: {
query,
variables,
},
});
if (response.body.errors) {
throw new Error(JSON.stringify(response.body.errors));
}
return response.body;
},
};

View File

@@ -0,0 +1,135 @@
export type Address = {
first_name: string;
last_name: string;
email: string;
phone: string;
line1: string;
line2: string;
country: string;
city: string;
area: string;
zip: string;
state: string;
};
export type OrderCustomer = {
id: string;
full_name: string;
avatar: string;
location: string;
};
export type OrderItemOption = {
id: string;
label: string;
value: string;
};
export type OrderItem = {
__typename: "VariantSnapshot";
product_id: string;
id: string;
_id: number;
image: { path: string; } | null;
file?: { id: string; path?: string; } | null;
customer_files: Array<Record<string, unknown>>;
title: string;
price: number;
variant_id: string;
fulfillment_status: string;
carrier: string;
tracking_number: string | null;
tracking_link: string | null;
refund_id: string | null;
payment_id: string | null;
removed_at: string | null;
sku: string;
custom_options: Array<Record<string, unknown>>;
options: OrderItemOption[];
};
export type PaymentBundleSnapshot = {
id: string;
value: number;
discount_result: number;
label: string;
offer_id: string | null;
};
export type OrderRefund = {
id: string;
_id: number;
amount: number;
reason: string;
};
export type OrderPayment = {
id: string;
_id: number;
total: number;
sub_total: number;
created_at: string;
refunded: number;
refundable: number;
price_bundle_snapshot: PaymentBundleSnapshot[];
discount_snapshot: Record<string, unknown> | null;
refunds: OrderRefund[];
source: {
payment_gateway: {
prototype: {
key: string;
};
};
};
cookies: Record<string, unknown>;
};
export type Order = {
id: string;
__typename: "Order";
_id: number;
total: number;
account_id: string;
subtotal: number;
discount_value: number;
normal_discount_value: number;
bundle_discount_value: number;
pm_discount_value: number;
pm_extra_fees: number;
name: string;
notes: string;
email: string;
phone: string;
archived_at: string | null;
refunded_amount: number;
paid_by_customer: number;
net_payment: number;
original_total: number;
refundable: number;
created_at: string;
cancelled_at: string | null;
test: boolean;
tags: string[];
shipping: number;
shipping_discount: number;
funnel_id: string;
store_id: string | null;
customer: OrderCustomer;
custom: Record<string, unknown>;
items: OrderItem[];
payments: OrderPayment[];
shipping_address: Address;
billing_address: Address;
client_details: {
ip: string;
[key: string]: unknown;
};
utm: Record<string, unknown> | null;
currency: string;
link?: string | null;
thank_you_url?: string | null;
};
export type OrderWebhookPayload = {
node: Order;
};

View File

@@ -0,0 +1,102 @@
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
import { lightfunnelsAuth } from '../../index';
import { lightfunnelsCommon } from '../common/index';
export const newLeadTrigger = createTrigger({
auth: lightfunnelsAuth,
name: 'new_lead',
displayName: 'New Lead',
description: 'Triggers when a new lead subscribes',
props: {},
type: TriggerStrategy.WEBHOOK,
sampleData: {
node: {
id: 'cus_dummy_id',
__typename: 'Customer',
email: 'dummy@example.com',
first_name: 'John',
last_name: 'Doe',
full_name: 'John Doe',
phone: '+1 00000-00000',
location: 'US',
avatar: '//www.gravatar.com/avatar/00000000000000000000000000000000',
notes: 'Sample customer notes',
accepts_marketing: false,
custom: {},
tags: [],
shipping_address: {
first_name: 'John',
last_name: 'Doe',
email: 'dummy@example.com',
phone: '+1 00000-00000',
line1: '123 Dummy Street',
line2: '',
country: 'US',
city: 'Sample City',
area: '',
zip: '000000',
state: 'CA',
},
billing_address: {
first_name: 'John',
last_name: 'Doe',
email: 'dummy@example.com',
phone: '+1 00000-00000',
line1: '123 Dummy Street',
line2: '',
country: 'US',
city: 'Sample City',
area: '',
zip: '000000',
state: 'CA',
},
leads: [],
},
},
async onEnable(context) {
const query = `
mutation CreateWebhookMutation($node: WebhookInput!) {
createWebhook(node: $node) {
id
type
settings
url
}
}
`;
const variables = {
node: {
type: 'contact/signup',
url: context.webhookUrl,
settings: {},
},
};
const response = await lightfunnelsCommon.makeGraphQLRequest<{
createWebhook: { id: string };
}>(context.auth, query, variables);
await context.store.put('webhook_id', response.data.createWebhook.id);
},
async onDisable(context) {
const webhookId = await context.store.get<string>('webhook_id');
if (webhookId) {
const query = `
mutation webhooksDeleteMutation($id: ID!) {
deleteWebhook(id: $id)
}
`;
await lightfunnelsCommon.makeGraphQLRequest(context.auth, query, {
id: webhookId,
});
await context.store.delete('webhook_id');
}
},
async run(context) {
const payload = context.payload.body as { node: any };
return [payload.node];
},
});

View File

@@ -0,0 +1,136 @@
import { lightfunnelsAuth } from '../../index';
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
import { lightfunnelsCommon } from '../common/index';
import { OrderWebhookPayload } from '../common/types';
export const newOrderTrigger = createTrigger({
auth: lightfunnelsAuth,
name: 'new_order',
displayName: 'New Order',
description: 'Triggers when a new order is created',
props: {},
type: TriggerStrategy.WEBHOOK,
async onEnable(context) {
const query = `
mutation CreateWebhookMutation($node: WebhookInput!) {
createWebhook(node: $node) {
id
type
settings
url
}
}
`;
const variables = {
node: {
type: 'order/confirmed',
url: context.webhookUrl,
settings: {},
},
};
const response = await lightfunnelsCommon.makeGraphQLRequest<{ createWebhook: { id: string } }>(
context.auth,
query,
variables
);
await context.store.put('webhook_id', response.data.createWebhook.id);
},
async onDisable(context) {
const webhookId = await context.store.get<string>('webhook_id');
if (webhookId) {
const query = `
mutation webhooksDeleteMutation($id: ID!) {
deleteWebhook(id: $id)
}
`;
await lightfunnelsCommon.makeGraphQLRequest(
context.auth,
query,
{ id: webhookId }
);
await context.store.delete('webhook_id');
}
},
sampleData: {
node: {
id: 'order_123',
__typename: 'Order',
_id: 123,
total: 100,
account_id: 'acc_123',
subtotal: 90,
discount_value: 10,
normal_discount_value: 10,
bundle_discount_value: 0,
pm_discount_value: 0,
pm_extra_fees: 0,
name: '#1001',
notes: '',
email: 'customer@example.com',
phone: '+1234567890',
archived_at: null,
refunded_amount: 0,
paid_by_customer: 100,
net_payment: 100,
original_total: 100,
refundable: 100,
created_at: '2023-01-01T00:00:00Z',
cancelled_at: null,
test: false,
tags: [],
shipping: 10,
shipping_discount: 0,
funnel_id: 'funnel_123',
store_id: null,
customer: {
id: 'cust_123',
full_name: 'John Doe',
avatar: '',
location: '',
},
custom: {},
items: [],
payments: [],
shipping_address: {
first_name: 'John',
last_name: 'Doe',
email: 'john@example.com',
phone: '+1234567890',
line1: '123 Main St',
line2: '',
country: 'US',
city: 'New York',
area: '',
zip: '10001',
state: 'NY',
},
billing_address: {
first_name: 'John',
last_name: 'Doe',
email: 'john@example.com',
phone: '+1234567890',
line1: '123 Main St',
line2: '',
country: 'US',
city: 'New York',
area: '',
zip: '10001',
state: 'NY',
},
client_details: {
ip: '127.0.0.1',
},
utm: null,
currency: 'USD',
},
},
async run(context) {
const payload = context.payload.body as OrderWebhookPayload;
return [payload.node];
},
});

View File

@@ -0,0 +1,20 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"importHelpers": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}