Skip to content

Conversation

luancazarine
Copy link
Collaborator

@luancazarine luancazarine commented Sep 17, 2025

Resolves #17440

Summary by CodeRabbit

  • New Features

    • Added Lightspeed ECom C‑Series actions: find customers, invoices, orders, products, shipments; get order, product, shipment, and order products.
    • Added instant event sources (with test fixtures): new/updated customers, invoices, orders (created/updated/shipped), and products (created/updated/deleted).
    • Enhanced app: dynamic dropdowns for IDs, date-range filters, unified API helpers, and pagination across endpoints; webhook lifecycle support via a shared base.
  • Chores

    • Bumped integration version to 0.1.0 and added platform dependency.

…nt sources

- Added actions for finding customers, invoices, orders, products, and shipments.
- Introduced event sources for customer creation, updates, invoice updates, order creation, updates, and shipping events.
- Updated app properties to include customer and order identifiers for better data retrieval.
- Version bump to 0.1.0 and added dependencies for improved functionality.
@luancazarine luancazarine linked an issue Sep 17, 2025 that may be closed by this pull request
Copy link

vercel bot commented Sep 17, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
pipedream-docs Ignored Ignored Sep 17, 2025 9:00pm
pipedream-docs-redirect-do-not-edit Ignored Ignored Sep 17, 2025 9:00pm

Copy link
Contributor

coderabbitai bot commented Sep 17, 2025

Walkthrough

Adds a Lightspeed ECom C-Series integration: expands the app with HTTP helpers, endpoint wrappers, and a paginate generator; adds many actions (find/get) using pagination; introduces a webhook-based common source and multiple instant event sources with test fixtures; updates package version and dependencies.

Changes

Cohort / File(s) Summary
App core & HTTP + pagination
components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs
Replaces prior authKeys; adds propDefinitions, HTTP helper (_baseUrl, _auth, _makeRequest), endpoint methods (listCustomers, listOrder, listShipment, listInvoice, listProduct, getOrder, getProduct, getShipment, getOrderProducts, listBrands, createHook, deleteHook) and an async generator paginate for paginated API iteration.
Package metadata
components/lightspeed_ecom_c_series/package.json
Bumps package version to 0.1.0 and adds dependency @pipedream/platform.
Find actions (paginated listing/filtering)
components/lightspeed_ecom_c_series/actions/find-customers/find-customers.mjs, .../find-invoice/find-invoice.mjs, .../find-order/find-order.mjs, .../find-product/find-product.mjs, .../find-shipment/find-shipment.mjs
New paginated "Find" actions mapping props to API query params, using this.app.paginate and collecting results into arrays; each exports a $summary of results.
Get actions (single resource / subresources)
.../get-order/get-order.mjs, .../get-product/get-product.mjs, .../get-shipment/get-shipment.mjs, .../get-order-products/get-order-products.mjs
New actions to retrieve single resources (order, product, shipment) and order products; call app helpers, export summaries, and return responses or accumulated arrays.
Sources common webhook base
components/lightspeed_ecom_c_series/sources/common/base.mjs
Adds a webhook lifecycle base source: props (app, db, http), helpers to store/retrieve webhookId, activate/deactivate hooks via app.createHook/deleteHook, HTTP response handling, and emits events with generated metadata.
Customer sources & fixtures
.../sources/new-customer-created/*, .../sources/customer-updated/*
Adds instant sources for new-customer-created and customer-updated extending common base; implements getItemGroup/action and generateMeta; includes test-event fixtures.
Invoice sources & fixtures
.../sources/new-invoice-created/*, .../sources/invoice-updated/*
Adds instant invoice-created and invoice-updated sources extending common; implements metadata generation and test-event fixtures.
Order sources & fixtures
.../sources/new-order-created/*, .../sources/order-updated/*, .../sources/order-shipped/*
Adds instant order created/updated/shipped sources with generateMeta and sampleEmit; includes detailed test-event fixtures.
Product sources & fixtures
.../sources/new-product-created/*, .../sources/product-updated/*, .../sources/product-deleted/*
Adds product created/updated/deleted instant sources (deleted test-event is empty); includes test-event fixtures for created/updated.
Minor formatting
components/peekalink/peekalink.app.mjs
Added trailing newline; no functional changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Action as Find Action
  participant App as Lightspeed App
  participant API as Lightspeed ECom API

  User->>Action: Invoke action with filters
  Action->>App: paginate(fn=listX, params, dataField)
  loop For each page
    App->>API: GET /{resource}.json?query
    API-->>App: { dataField: [items], pagination }
    App-->>Action: yield items
  end
  Action-->>User: return accumulated items + $summary
Loading
sequenceDiagram
  autonumber
  participant Src as Source (common base)
  participant App as Lightspeed App
  participant API as Lightspeed ECom API
  participant Hook as HTTP endpoint
  participant PD as Emitter

  rect rgba(220,235,255,0.25)
    note over Src,App: Activation
    Src->>App: createHook({ itemGroup, itemAction, address })
    App->>API: POST /hooks.json
    API-->>App: { id, ... }
    App-->>Src: webhook id
    Src->>Src: store webhookId in DB
  end

  rect rgba(220,255,220,0.25)
    note over API,Hook: Event delivery
    API-->>Hook: POST webhook body
    Hook->>Src: run({ body })
    Src->>Hook: http.respond(200)
    Src->>PD: emit(body, meta)
  end

  rect rgba(255,235,200,0.25)
    note over Src,App: Deactivation
    Src->>Src: load webhookId from DB
    Src->>App: deleteHook(webhookId)
    App->>API: DELETE /hooks/{id}.json
    API-->>App: 204
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Suggested reviewers

  • lcaresia

Poem

Hop hop, I sniff out HTTP streams,
Paginate through fields and dreams.
Webhooks set and events bloom bright,
Orders, invoices, products take flight.
A carrot-coded cheer — approve tonight! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning The PR implements many requested pieces from issue #17440 (e.g., new-order-created, order-updated, new-customer-created, customer-updated, new-invoice-created, invoice-updated, product-created/product-updated/product-deleted sources, plus app helpers and read/find actions for customers, orders, invoices, products, shipments), but it is missing several required items from the linked issue: the order.cancelled source, shipment.created and shipment.updated sources, and the management (POST/PUT) endpoints/actions for creating/updating orders, shipments, invoices, products, and customers. Implement the missing sources (order.cancelled, shipment.created, shipment.updated) and add the write/manage endpoints and corresponding actions (POST/PUT) for orders, shipments, invoices, products, and customers, include or update unit/integration tests, and then update the PR description and linked issue to reflect the completed scope.
Out of Scope Changes Check ⚠️ Warning Most changes are related to the Lightspeed integration, but there is at least one unrelated change: a formatting-only trailing newline added to components/peekalink/peekalink.app.mjs, which is outside the scope of issue #17440. Remove or separate the unrelated peekalink formatting change into its own commit/PR so this branch contains only Lightspeed-related changes tied to #17440.
Description Check ⚠️ Warning The repository's required PR template expects a "## WHY" section, but the PR description only contains "Resolves #17440" and does not provide the required rationale, summary of changes, or testing/migration notes, so it does not follow the template. Update the PR description to follow the repository template by adding a "## WHY" section that explains the motivation, a concise summary of implemented sources and actions, testing steps and expected behavior, and any breaking changes or migration notes.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "17440 lightspeed" references the linked issue number and the Lightspeed integration, which concisely identifies the primary change (adding the Lightspeed ECom C‑Series integration) and is directly related to the changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 17440-lightspeed

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9296ad9 and 5126e8e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • components/peekalink/peekalink.app.mjs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • components/peekalink/peekalink.app.mjs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pnpm publish
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Lint Code Base

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Nitpick comments (43)
components/lightspeed_ecom_c_series/sources/product-deleted/test-event.mjs (1)

1-1: Empty sample payload — please provide a minimal, realistic shape or add rationale

An empty object makes downstream examples / UI previews unhelpful. If Lightspeed sends no body on delete, add a comment stating that. Otherwise, include the minimal fields (e.g., product id).

Proposed minimal fixture (verify against actual webhook shape before committing):

-export default {}
+export default {
+  product: {
+    id: 123
+  }
+}
components/lightspeed_ecom_c_series/sources/invoice-updated/test-event.mjs (1)

1-52: Prefer HTTPS in sample links

All “link” values use http://. Use https:// to avoid shipping insecure URLs in examples and quiet security scanners. Apply this across all test-event fixtures.

Example (repeat for all “link” fields in this file):

- "link":"http://api.shoplightspeed.com/en/orders/123.json"
+ "link":"https://api.shoplightspeed.com/en/orders/123.json"
components/lightspeed_ecom_c_series/sources/order-updated/test-event.mjs (2)

10-11: Use reserved placeholders for IP/email

Swap the real-looking IP and non-reserved email domain for doc-safe placeholders.

- "remoteIp":"35.245.159.47",
+ "remoteIp":"203.0.113.10",
...
- "email":"customer@email.com",
+ "email":"customer@example.com",

Also applies to: 22-22


7-7: Order vs. shipment status consistency

Order status is "completed_shipped" while shipmentStatus is "not_shipped". If intentional for edge-case coverage, add a brief comment; otherwise, align them.

Also applies to: 93-94

components/lightspeed_ecom_c_series/sources/customer-updated/test-event.mjs (2)

15-15: Use reserved example email domain

Replace with example.com to avoid referencing a real domain.

- "email":"customer@email.com",
+ "email":"customer@example.com",

59-63: HTTPS links in sample data

Standardize “link” fields to https://.

Example:

- "link":"http:\/\/api.shoplightspeed.com\/en\/groups\/customers.json?customer=123"
+ "link":"https:\/\/api.shoplightspeed.com\/en\/groups\/customers.json?customer=123"

Also applies to: 66-70, 101-106, 109-113

components/lightspeed_ecom_c_series/sources/new-product-created/test-event.mjs (1)

19-23: Switch test links to HTTPS

Same HTTPS concern as other fixtures.

Representative change:

- "link":"http:\/\/api.shoplightspeed.com\/en\/brands\/123.json"
+ "link":"https:\/\/api.shoplightspeed.com\/en\/brands\/123.json"

Also applies to: 26-30, 36-40, 43-47, 50-54, 58-62, 66-70, 73-77, 80-84

components/lightspeed_ecom_c_series/sources/new-order-created/test-event.mjs (2)

10-10: Use reserved placeholders for IP/email

- "remoteIp":"35.245.159.47",
+ "remoteIp":"203.0.113.11",
...
- "email":"customer@email.com",
+ "email":"customer@example.com",

Also applies to: 22-22


615-619: HTTPS for links

Update sample links to https:// across this file.

- "link":"http:\/\/api.shoplightspeed.com\/en\/invoices\/123.json"
+ "link":"https:\/\/api.shoplightspeed.com\/en\/invoices\/123.json"

Also applies to: 640-643, 678-681

components/lightspeed_ecom_c_series/sources/new-customer-created/test-event.mjs (2)

15-15: Use reserved example email domain

- "email":"customer@email.com",
+ "email":"customer@example.com",

59-63: HTTPS links in sample data

Same as other fixtures.

- "link":"http:\/\/api.shoplightspeed.com\/en\/groups\/customers.json?customer=123"
+ "link":"https:\/\/api.shoplightspeed.com\/en\/groups\/customers.json?customer=123"

Also applies to: 66-70, 101-106, 109-113

components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (11)

145-149: Off‑by‑one in brands options pagination.

Other option builders use page + 1 (Lightspeed pagination is 1‑based). Here it passes page directly, likely skipping page 1.

-          params: {
-            page,
-          },
+          params: {
+            page: page + 1,
+          },

204-211: Base URL may be incorrect for common cluster values (eu1/us1).

Official guidance maps clusters to fixed API hosts (eu1 → api.webshopapp.com, us1 → api.shoplightspeed.com). Building https://api.${cluster}.com only works if cluster is already webshopapp or shoplightspeed. Add mapping or validate input.

References: elastic.io Lightspeed eCom component docs show the mapping. (docs.elastic.io)

Suggested adjustment:

-    _baseUrl() {
-      return `https://api.${this.$auth.cluster}.com/${this.$auth.lang}`;
-    },
+    _baseUrl() {
+      const c = (this.$auth.cluster || "").toLowerCase();
+      const host =
+        c === "eu1" || c === "eu" ? "api.webshopapp.com" :
+        c === "us1" || c === "us" || c === "noam" ? "api.shoplightspeed.com" :
+        c.includes(".") ? c : `api.${c}.com`;
+      return `https://${host}/${this.$auth.lang}`;
+    },

If you intend cluster to be the host fragment (e.g., webshopapp/shoplightspeed), please document that in the auth UI.


18-23: Label building: guard against missing names.

Customers without firstname/lastname render labels with extra spaces. Minor, but easy to harden.

-          label: `${firstname} ${middlename && `${middlename} ` || ""}${lastname} ${email && `(${email})` || ""}`,
+          label: `${[firstname, middlename, lastname].filter(Boolean).join(" ")}${email ? ` (${email})` : ""}`,

37-42: Same label hardening suggestion for customer email options.

-          label: `${firstname} ${middlename && `${middlename} ` || ""}${lastname} ${value && `(${value})` || ""}`,
+          label: `${[firstname, middlename, lastname].filter(Boolean).join(" ")}${value ? ` (${value})` : ""}`,

56-61: Order options: stabilize when email missing.

Minor parity with the customer label fix.

-          label: `Order #${number} ${email && `(${email})`}`,
+          label: `Order #${number}${email ? ` (${email})` : ""}`,

75-81: Order ID options: same label nit.

-          label: `Order #${number} ${email && `(${email})`}`,
+          label: `Order #${number}${email ? ` (${email})` : ""}`,

94-99: Shipment number label nit.

-          label: `Shipment #${number} ${status && `(${status})`}`,
+          label: `Shipment #${number}${status ? ` (${status})` : ""}`,

113-118: Shipment ID label nit.

-          label: `Shipment #${number} ${status && `(${status})`}`,
+          label: `Shipment #${number}${status ? ` (${status})` : ""}`,

132-137: Invoice number label nit.

-          label: `Invoice #${number} ${status && `(${status})`}`,
+          label: `Invoice #${number}${status ? ` (${status})` : ""}`,

178-201: Date prop descriptions reference “products” only, but these props are reused for orders, invoices, shipments.

Make the copy generic to avoid confusion in other actions.

-      description: "Show products created after date. **Format: `YYYY-MM-DD HH:MM:SS`**",
+      description: "Show records created after date. **Format: `YYYY-MM-DD HH:MM:SS`**",
@@
-      description: "Show products created before date. **Format: `YYYY-MM-DD HH:MM:SS`**",
+      description: "Show records created before date. **Format: `YYYY-MM-DD HH:MM:SS`**",
@@
-      description: "Show products last updated after date. **Format: `YYYY-MM-DD HH:MM:SS`**",
+      description: "Show records last updated after date. **Format: `YYYY-MM-DD HH:MM:SS`**",
@@
-      description: "Show products last updated before date. **Format: `YYYY-MM-DD HH:MM:SS`**",
+      description: "Show records last updated before date. **Format: `YYYY-MM-DD HH:MM:SS`**",

10-11: Minor copy nits.

  • “customerid” → “customer ID”
  • “Retrieve all customers from a specific customer…” → “Retrieve customers by email.”
-      description: "Retrieve all orders from a specific customer based on the customerid",
+      description: "Retrieve all orders from a specific customer based on the customer ID",
@@
-      description: "Retrieve all customers from a specific customer based on the customer email",
+      description: "Retrieve customers by email",

Also applies to: 29-30

components/lightspeed_ecom_c_series/actions/get-order-products/get-order-products.mjs (1)

6-6: Tweak description grammar and link text.

“Get an order products by ID” → “Get order products for an order ID.”

-  description: "Get an order products by ID. [See the documentation](https://developers.lightspeedhq.com/ecom/endpoints/orderproduct/#get-all-order-products)",
+  description: "Get order products for an order ID. [See the documentation](https://developers.lightspeedhq.com/ecom/endpoints/orderproduct/#get-all-order-products)",
components/lightspeed_ecom_c_series/actions/find-order/find-order.mjs (2)

5-7: Description doesn’t match behavior (filters vs “by ID”).

This action searches with multiple filters, not strictly “by ID”. Reword to avoid confusion.

-  description: "Find an order by ID. [See the documentation](https://developers.lightspeedhq.com/ecom/endpoints/order/#get-all-orders)",
+  description: "Find orders by filters. [See the documentation](https://developers.lightspeedhq.com/ecom/endpoints/order/#get-all-orders)",

62-76: Optional: add a max results cap to avoid unintentionally paging the entire dataset.

Expose limit (mapped to page_size) and/or maxResults passed to paginate.

-    const response = this.app.paginate({
+    const response = this.app.paginate({
       fn: this.app.listOrder,
       $,
       params: {
         customer: this.customerId,
         number: this.orderNumber,
         since_id: this.sinceId,
         created_at_min: this.createdAtMin,
         created_at_max: this.createdAtMax,
         updated_at_min: this.updatedAtMin,
         updated_at_max: this.updatedAtMax,
       },
       dataField: "orders",
+      // maxResults: 500, // uncomment to cap results
     });
components/lightspeed_ecom_c_series/actions/find-shipment/find-shipment.mjs (3)

5-7: Description doesn’t match behavior

This action lists/filters shipments; it’s not “by ID.” Align name/description with actual filters.

-  name: "Find Shipment",
-  description: "Find a shipment by ID. [See the documentation](https://developers.lightspeedhq.com/ecom/endpoints/shipment/#get-all-shipments)",
+  name: "Find Shipments",
+  description: "Find shipments using filters (customer, order, number, date range). See docs: Get all shipments.",

16-26: Property copy edits

  • “customerid” → “customer ID”.
  • Clarify order/shipment number behavior.
-      description: "Retrieve all shipments from a specific customer based on the customerid",
+      description: "Retrieve shipments for a specific customer ID",
...
-      description: "Retrieve a shipment based on the order number",
+      description: "Retrieve shipments for an order number",

73-82: Avoid sending undefined filters

Filter out empty/undefined params to prevent noisy requests.

-      params: {
-        customer: this.customerId,
-        order: this.orderNumber,
-        number: this.shipmentNumber,
-        since_id: this.sinceId,
-        created_at_min: this.createdAtMin,
-        created_at_max: this.createdAtMax,
-        updated_at_min: this.updatedAtMin,
-        updated_at_max: this.updatedAtMax,
-      },
+      params: Object.fromEntries(Object.entries({
+        customer: this.customerId,
+        order: this.orderNumber,
+        number: this.shipmentNumber,
+        since_id: this.sinceId,
+        created_at_min: this.createdAtMin,
+        created_at_max: this.createdAtMax,
+        updated_at_min: this.updatedAtMin,
+        updated_at_max: this.updatedAtMax,
+      }).filter(([, v]) => v != null && v !== "")),
components/lightspeed_ecom_c_series/actions/find-customers/find-customers.mjs (3)

5-7: Description mismatch

You’re finding customers via filters (email, dates), not “by ID.” Align wording.

-  description: "Find a customer by ID. [See the documentation](https://developers.lightspeedhq.com/ecom/endpoints/customer/#get-all-customers)",
+  description: "Find customers using filters (email, date range). See docs: Get all customers.",

16-18: Clarify prop phrasing

Current text is awkward.

-      description: "Retrieve all customers from a specific customer based on the customer email",
+      description: "Filter customers by email address",

54-67: Param sanitization (optional)

Consider filtering out undefined/empty values for cleaner requests.

-      params: {
-        email: this.customerEmail,
-        since_id: this.sinceId,
-        created_at_min: this.createdAtMin,
-        created_at_max: this.createdAtMax,
-        updated_at_min: this.updatedAtMin,
-        updated_at_max: this.updatedAtMax,
-      },
+      params: Object.fromEntries(Object.entries({
+        email: this.customerEmail,
+        since_id: this.sinceId,
+        created_at_min: this.createdAtMin,
+        created_at_max: this.createdAtMax,
+        updated_at_min: this.updatedAtMin,
+        updated_at_max: this.updatedAtMax,
+      }).filter(([, v]) => v != null && v !== "")),
components/lightspeed_ecom_c_series/actions/find-product/find-product.mjs (2)

5-7: Fix grammar and scope

Use plural and remove “by ID” since this lists/filters.

-  name: "Find Product",
-  description: "Find an product by ID. [See the documentation](https://developers.lightspeedhq.com/ecom/endpoints/product/#get-all-products)",
+  name: "Find Products",
+  description: "Find products using filters (brand, date range). See docs: Get all products.",

50-62: Param sanitization (optional)

Keep requests clean by omitting undefined/empty values.

-      params: {
-        brand: this.brandId,
-        since_id: this.sinceId,
-        created_at_min: this.createdAtMin,
-        created_at_max: this.createdAtMax,
-        updated_at_min: this.updatedAtMin,
-        updated_at_max: this.updatedAtMax,
-      },
+      params: Object.fromEntries(Object.entries({
+        brand: this.brandId,
+        since_id: this.sinceId,
+        created_at_min: this.createdAtMin,
+        created_at_max: this.createdAtMax,
+        updated_at_min: this.updatedAtMin,
+        updated_at_max: this.updatedAtMax,
+      }).filter(([, v]) => v != null && v !== "")),
components/lightspeed_ecom_c_series/actions/find-invoice/find-invoice.mjs (3)

5-7: Description mismatch

This action lists/filters invoices; not “by ID.”

-  description: "Find an invoice by ID. [See the documentation](https://developers.lightspeedhq.com/ecom/endpoints/invoice/#get-all-invoices)",
+  description: "Find invoices using filters (customer, order, number, date range). See docs: Get all invoices.",

16-26: Copy and semantics fixes

  • “customerid” → “customer ID”.
  • Order filter prop describes fetching an order; should reference invoices for an order number.
-      description: "Retrieve all invoices from a specific customer based on the customerid",
+      description: "Retrieve invoices for a specific customer ID",
...
-      description: "Retrieve an order based on the order number",
+      description: "Retrieve invoices for an order number",

74-83: Omit undefined filters (optional)

Mirror other comments: filter out empty values before calling.

-      params: {
-        customer: this.customerId,
-        number: this.invoiceNumber,
-        order: this.orderNumber,
-        since_id: this.sinceId,
-        created_at_min: this.createdAtMin,
-        created_at_max: this.createdAtMax,
-        updated_at_min: this.updatedAtMin,
-        updated_at_max: this.updatedAtMax,
-      },
+      params: Object.fromEntries(Object.entries({
+        customer: this.customerId,
+        number: this.invoiceNumber,
+        order: this.orderNumber,
+        since_id: this.sinceId,
+        created_at_min: this.createdAtMin,
+        created_at_max: this.createdAtMax,
+        updated_at_min: this.updatedAtMin,
+        updated_at_max: this.updatedAtMax,
+      }).filter(([, v]) => v != null && v !== "")),
components/lightspeed_ecom_c_series/sources/order-updated/order-updated.mjs (2)

6-8: Key prefix inconsistent with app

Other modules use lightspeed_ecom_c_series-.... Align for discoverability and to avoid collisions.

-  key: "indiefunnels-order-updated",
+  key: "lightspeed_ecom_c_series-order-updated",

8-9: Grammar fix

“a order” → “an order”.

-  description: "Emit new event when a order is updated.",
+  description: "Emit new event when an order is updated.",
components/lightspeed_ecom_c_series/sources/product-deleted/product-deleted.mjs (2)

6-8: Key prefix inconsistent with app

Use the same lightspeed_ecom_c_series- prefix used elsewhere.

-  key: "indiefunnels-product-deleted",
+  key: "lightspeed_ecom_c_series-product-deleted",

20-26: Potential null deref with empty sample

generateMeta assumes body.product.id and deletedAt. The referenced test-event is empty in this PR; sample display may break.

I can supply a minimal test-event.mjs:

export default {
  product: {
    id: 123,
    deletedAt: "2025-09-17T20:25:55+00:00",
  },
};
components/lightspeed_ecom_c_series/sources/order-shipped/test-event.mjs (1)

7-7: Align sample payload with “shipped” semantics

The order status shows completed_shipped, but shipmentStatus and shipments[].status are “not_shipped”. Align for clarity.

-     "shipmentStatus":"not_shipped",
+     "shipmentStatus":"shipped",
-                 "status":"not_shipped",
+                 "status":"shipped",

Also applies to: 339-339, 93-94

components/lightspeed_ecom_c_series/sources/new-customer-created/new-customer-created.mjs (1)

8-8: Grammar nit: “a customer”.

Change “an customer” → “a customer”.

Apply this diff:

-  description: "Emit new event when an customer is created.",
+  description: "Emit new event when a customer is created.",
components/lightspeed_ecom_c_series/sources/common/base.mjs (1)

35-38: Guard deactivation when no webhookId is stored.

Avoid calling deleteHook(undefined) on first deploys or if state was cleared.

Apply this diff:

     async deactivate() {
       const webhookId = this._getWebhookId();
-      await this.app.deleteHook(webhookId);
+      if (!webhookId) return;
+      await this.app.deleteHook(webhookId);
     },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc9213d and 9296ad9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (32)
  • components/lightspeed_ecom_c_series/actions/find-customers/find-customers.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/actions/find-invoice/find-invoice.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/actions/find-order/find-order.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/actions/find-product/find-product.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/actions/find-shipment/find-shipment.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/actions/get-order-products/get-order-products.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/actions/get-order/get-order.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/actions/get-product/get-product.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/actions/get-shipment/get-shipment.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/package.json (2 hunks)
  • components/lightspeed_ecom_c_series/sources/common/base.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/customer-updated/customer-updated.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/customer-updated/test-event.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/invoice-updated/invoice-updated.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/invoice-updated/test-event.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/new-customer-created/new-customer-created.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/new-customer-created/test-event.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/new-invoice-created/new-invoice-created.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/new-invoice-created/test-event.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/new-order-created/new-order-created.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/new-order-created/test-event.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/new-product-created/new-product-created.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/new-product-created/test-event.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/order-shipped/order-shipped.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/order-shipped/test-event.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/order-updated/order-updated.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/order-updated/test-event.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/product-deleted/product-deleted.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/product-deleted/test-event.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/product-updated/product-updated.mjs (1 hunks)
  • components/lightspeed_ecom_c_series/sources/product-updated/test-event.mjs (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-12-12T19:23:09.039Z
Learnt from: jcortes
PR: PipedreamHQ/pipedream#14935
File: components/sailpoint/package.json:15-18
Timestamp: 2024-12-12T19:23:09.039Z
Learning: When developing Pipedream components, do not add built-in Node.js modules like `fs` to `package.json` dependencies, as they are native modules provided by the Node.js runtime.

Applied to files:

  • components/lightspeed_ecom_c_series/package.json
📚 Learning: 2024-10-08T15:33:38.240Z
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.

Applied to files:

  • components/lightspeed_ecom_c_series/sources/common/base.mjs
🧬 Code graph analysis (10)
components/lightspeed_ecom_c_series/actions/find-shipment/find-shipment.mjs (6)
components/lightspeed_ecom_c_series/actions/find-customers/find-customers.mjs (1)
  • response (55-67)
components/lightspeed_ecom_c_series/actions/find-invoice/find-invoice.mjs (1)
  • response (71-85)
components/lightspeed_ecom_c_series/actions/find-order/find-order.mjs (1)
  • response (63-76)
components/lightspeed_ecom_c_series/actions/find-product/find-product.mjs (1)
  • response (50-62)
components/lightspeed_ecom_c_series/actions/get-order-products/get-order-products.mjs (1)
  • response (19-24)
components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (1)
  • response (312-315)
components/lightspeed_ecom_c_series/actions/find-customers/find-customers.mjs (6)
components/lightspeed_ecom_c_series/actions/find-invoice/find-invoice.mjs (1)
  • response (71-85)
components/lightspeed_ecom_c_series/actions/find-order/find-order.mjs (1)
  • response (63-76)
components/lightspeed_ecom_c_series/actions/find-product/find-product.mjs (1)
  • response (50-62)
components/lightspeed_ecom_c_series/actions/find-shipment/find-shipment.mjs (1)
  • response (70-84)
components/lightspeed_ecom_c_series/actions/get-order-products/get-order-products.mjs (1)
  • response (19-24)
components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (1)
  • response (312-315)
components/lightspeed_ecom_c_series/actions/get-product/get-product.mjs (3)
components/lightspeed_ecom_c_series/actions/get-order/get-order.mjs (1)
  • response (19-22)
components/lightspeed_ecom_c_series/actions/get-shipment/get-shipment.mjs (1)
  • response (19-22)
components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (1)
  • response (312-315)
components/lightspeed_ecom_c_series/actions/get-shipment/get-shipment.mjs (3)
components/lightspeed_ecom_c_series/actions/get-order/get-order.mjs (1)
  • response (19-22)
components/lightspeed_ecom_c_series/actions/get-product/get-product.mjs (1)
  • response (19-22)
components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (1)
  • response (312-315)
components/lightspeed_ecom_c_series/actions/get-order-products/get-order-products.mjs (4)
components/lightspeed_ecom_c_series/actions/find-customers/find-customers.mjs (1)
  • response (55-67)
components/lightspeed_ecom_c_series/actions/find-product/find-product.mjs (2)
  • response (50-62)
  • products (64-64)
components/lightspeed_ecom_c_series/actions/get-order/get-order.mjs (1)
  • response (19-22)
components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (1)
  • response (312-315)
components/lightspeed_ecom_c_series/actions/find-invoice/find-invoice.mjs (5)
components/lightspeed_ecom_c_series/actions/find-customers/find-customers.mjs (1)
  • response (55-67)
components/lightspeed_ecom_c_series/actions/find-order/find-order.mjs (1)
  • response (63-76)
components/lightspeed_ecom_c_series/actions/find-product/find-product.mjs (1)
  • response (50-62)
components/lightspeed_ecom_c_series/actions/find-shipment/find-shipment.mjs (1)
  • response (70-84)
components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (1)
  • response (312-315)
components/lightspeed_ecom_c_series/actions/find-order/find-order.mjs (6)
components/lightspeed_ecom_c_series/actions/find-customers/find-customers.mjs (1)
  • response (55-67)
components/lightspeed_ecom_c_series/actions/find-invoice/find-invoice.mjs (1)
  • response (71-85)
components/lightspeed_ecom_c_series/actions/find-product/find-product.mjs (1)
  • response (50-62)
components/lightspeed_ecom_c_series/actions/find-shipment/find-shipment.mjs (1)
  • response (70-84)
components/lightspeed_ecom_c_series/actions/get-order-products/get-order-products.mjs (1)
  • response (19-24)
components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (1)
  • response (312-315)
components/lightspeed_ecom_c_series/actions/find-product/find-product.mjs (6)
components/lightspeed_ecom_c_series/actions/find-customers/find-customers.mjs (1)
  • response (55-67)
components/lightspeed_ecom_c_series/actions/find-invoice/find-invoice.mjs (1)
  • response (71-85)
components/lightspeed_ecom_c_series/actions/find-order/find-order.mjs (1)
  • response (63-76)
components/lightspeed_ecom_c_series/actions/find-shipment/find-shipment.mjs (1)
  • response (70-84)
components/lightspeed_ecom_c_series/actions/get-order-products/get-order-products.mjs (2)
  • response (19-24)
  • products (26-26)
components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (1)
  • response (312-315)
components/lightspeed_ecom_c_series/actions/get-order/get-order.mjs (4)
components/lightspeed_ecom_c_series/actions/get-order-products/get-order-products.mjs (1)
  • response (19-24)
components/lightspeed_ecom_c_series/actions/get-product/get-product.mjs (1)
  • response (19-22)
components/lightspeed_ecom_c_series/actions/get-shipment/get-shipment.mjs (1)
  • response (19-22)
components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (1)
  • response (312-315)
components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs (10)
components/lightspeed_ecom_c_series/actions/find-customers/find-customers.mjs (2)
  • customers (69-69)
  • response (55-67)
components/lightspeed_ecom_c_series/actions/find-order/find-order.mjs (2)
  • orders (78-78)
  • response (63-76)
components/lightspeed_ecom_c_series/actions/find-shipment/find-shipment.mjs (2)
  • shipments (86-86)
  • response (70-84)
components/lightspeed_ecom_c_series/actions/find-invoice/find-invoice.mjs (2)
  • invoices (87-87)
  • response (71-85)
components/lightspeed_ecom_c_series/actions/find-product/find-product.mjs (2)
  • products (64-64)
  • response (50-62)
components/lightspeed_ecom_c_series/actions/get-order-products/get-order-products.mjs (2)
  • products (26-26)
  • response (19-24)
components/lightspeed_ecom_c_series/actions/get-order/get-order.mjs (1)
  • response (19-22)
components/lightspeed_ecom_c_series/actions/get-product/get-product.mjs (1)
  • response (19-22)
components/lightspeed_ecom_c_series/actions/get-shipment/get-shipment.mjs (1)
  • response (19-22)
components/lightspeed_ecom_c_series/sources/common/base.mjs (1)
  • response (22-32)
🔇 Additional comments (24)
components/lightspeed_ecom_c_series/package.json (2)

3-3: Semver bump looks appropriate

Bumping to 0.1.0 for new sources/actions reads right. LGTM.


15-17: Confirm minimal required @pipedream/platform version and align across repo

Scan shows inconsistent versions across components (range: ^0.9.0 → ^3.1.0; also bare 1.x/2.x/3.x entries and one "null" — likely a malformed package.json).

  • Determine the minimum @pipedream/platform required by this component; bump the floor if it uses APIs newer than the chosen baseline.
  • Pick a repo-wide target (e.g., standardize to ^3.1.0 if needed) and update all components/**/package.json to match, then run CI/tests.
  • Investigate and fix the package.json(s) that produced "null" in the scan.
components/lightspeed_ecom_c_series/actions/get-product/get-product.mjs (1)

3-26: LGTM.

Action shape matches the app helper and returns the API payload with a concise summary. No blockers.

components/lightspeed_ecom_c_series/actions/get-order-products/get-order-products.mjs (2)

31-33: Summary string has an extra “order”.

Reads “Successfully retrieved order 5 product(s)…”. Drop the first “order”.

[raise_nitpick_refactor]

-    $.export("$summary", `Successfully retrieved order ${products.length} product${products.length === 1
+    $.export("$summary", `Successfully retrieved ${products.length} product${products.length === 1
       ? ""
-      : "s"} for order with ID: ${this.orderId}`);
+      : "s"} for order ID: ${this.orderId}`);

19-24: Confirm dataField name returned by /orders/{id}/products.json.

Some implementations return orderproducts (lowercase) instead of orderProducts. If the field name doesn’t match, pagination will yield nothing.

Run this quick grep to see what field name we use elsewhere and avoid a mismatch:

If the API returns orderproducts, update dataField accordingly.

components/lightspeed_ecom_c_series/actions/find-order/find-order.mjs (1)

16-17: Copy nit: “customerid”.

[raise_nitpick_refactor]

-      description: "Retrieve all orders from a specific customer based on the customerid",
+      description: "Retrieve all orders from a specific customer based on the customer ID",
components/lightspeed_ecom_c_series/actions/get-order/get-order.mjs (1)

3-26: LGTM.

Straightforward wrapper over getOrder with a clear summary.

components/lightspeed_ecom_c_series/actions/find-shipment/find-shipment.mjs (2)

91-94: Good UX summary

Pluralization handled correctly.


69-84: Verify whether the order filter expects the internal order ID (not the visible order number)
You map orderNumber → order in components/lightspeed_ecom_c_series/actions/find-shipment/find-shipment.mjs; confirm the C‑Series GET /shipments.json order query parameter expects the internal order ID (document ID/GUID) rather than the human‑readable order number — if it expects the ID, send the ID instead (or use the documented parameter name); if it expects the order number, no change needed.

components/lightspeed_ecom_c_series/actions/find-customers/find-customers.mjs (1)

74-77: Nice summary

Pluralization is correct.

components/lightspeed_ecom_c_series/actions/get-shipment/get-shipment.mjs (1)

18-26: LGTM

Direct pass-through to app client with a clear summary. Matches get‑order/get‑product patterns.

components/lightspeed_ecom_c_series/actions/find-product/find-product.mjs (1)

69-72: Good summary

Pluralization handled.

components/lightspeed_ecom_c_series/actions/find-invoice/find-invoice.mjs (2)

92-95: Nice summary

Pluralization handled.


70-85: Confirm whether order query parameter requires an order ID (not order number)

No official Lightspeed C‑Series docs found for GET /invoices.json; unclear if order expects an order ID or an order number — mapping orderNumber → order may be incorrect.

File: components/lightspeed_ecom_c_series/actions/find-invoice/find-invoice.mjs (lines 70–85)

Verify by testing GET /invoices.json against a store (compare queries using order number vs order ID) or confirm with Lightspeed support; if order requires an ID, send the order ID (or use the correct parameter) instead of the order number.

components/lightspeed_ecom_c_series/sources/order-updated/order-updated.mjs (1)

20-26: Meta generation looks good

Id/summary/timestamp are sensible. Assumes body.order.updatedAt exists.

Please confirm the webhook payload contains order.updatedAt in UTC ISO 8601.

components/lightspeed_ecom_c_series/sources/product-updated/test-event.mjs (1)

1-90: Fixture is rich and representative

Good coverage of nested product relations; useful for UI/testing.

Confirm these fields mirror an actual eCom product payload (C‑Series), especially visibility, brand.resource, and variants.resource shapes.

components/lightspeed_ecom_c_series/sources/new-order-created/new-order-created.mjs (1)

20-25: LGTM on meta fields

Using order.id and createdAt for created events is appropriate.

components/lightspeed_ecom_c_series/sources/new-product-created/new-product-created.mjs (1)

4-29: Pattern matches base correctly

Group/action/meta look good; created events safely dedupe on product.id.

components/lightspeed_ecom_c_series/sources/new-invoice-created/new-invoice-created.mjs (1)

4-29: Solid created-event source

Meta/id/ts mapping is consistent with created semantics.

components/lightspeed_ecom_c_series/sources/new-invoice-created/test-event.mjs (1)

1-52: Fixture looks good

Structure matches source expectations; timestamps are ISO8601 with offset.

components/lightspeed_ecom_c_series/sources/new-customer-created/new-customer-created.mjs (2)

4-12: Good use of common base and source metadata.

Structure, spread of common, and dedupe mode are consistent with other Pipedream sources.


20-26: Use snake_case "created_at" (not createdAt) in generateMeta

Lightspeed eCom C‑Series customer payloads use "id" and "created_at" (snake_case). Update components/lightspeed_ecom_c_series/sources/new-customer-created/new-customer-created.mjs (lines 20–26) to read body.customer.created_at for ts parsing instead of body.customer.createdAt.

⛔ Skipped due to learnings
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#17538
File: components/aircall/sources/new-sms/new-sms.mjs:19-25
Timestamp: 2025-07-09T18:07:12.426Z
Learning: In Aircall API webhook payloads, the `created_at` field is returned as an ISO 8601 string format (e.g., "2020-02-18T20:52:22.000Z"), not as milliseconds since epoch. For Pipedream components, this needs to be converted to milliseconds using `Date.parse()` before assigning to the `ts` field in `generateMeta()`.
components/lightspeed_ecom_c_series/sources/common/base.mjs (1)

1-11: Base scaffold looks solid.

Props wiring for app, db, and custom HTTP interface aligns with Pipedream patterns.

components/lightspeed_ecom_c_series/sources/product-updated/product-updated.mjs (1)

4-12: Structure LGTM.

Extends the common base correctly; item group/action match the event semantics.

Comment on lines +303 to +316
async *paginate({
fn, params = {}, dataField, maxResults = null, ...opts
}) {
let hasMore = false;
let count = 0;
let page = 0;

do {
params.page = ++page;
const response = await fn({
params,
...opts,
});
for (const d of response[dataField]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Paginator calls unbound methods (breaks all “find” actions and Get Order Products).

fn is passed as a bare method reference (e.g., this.app.listOrder) and invoked without binding, so this inside the method is undefined and _makeRequest will throw. Bind this when invoking.

Apply this diff:

-        const response = await fn({
+        const response = await fn.call(this, {
           params,
           ...opts,
         });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async *paginate({
fn, params = {}, dataField, maxResults = null, ...opts
}) {
let hasMore = false;
let count = 0;
let page = 0;
do {
params.page = ++page;
const response = await fn({
params,
...opts,
});
for (const d of response[dataField]) {
async *paginate({
fn, params = {}, dataField, maxResults = null, ...opts
}) {
let hasMore = false;
let count = 0;
let page = 0;
do {
params.page = ++page;
const response = await fn.call(this, {
params,
...opts,
});
for (const d of response[dataField]) {
🤖 Prompt for AI Agents
In components/lightspeed_ecom_c_series/lightspeed_ecom_c_series.app.mjs around
lines 303–316, the paginator invokes the passed-in function `fn` without binding
its receiver, so methods like `this.app.listOrder` lose their `this` and fail;
change the invocation to bind the current instance when calling `fn` (for
example, replace the direct await fn({ params, ...opts }) with a bound call such
as await fn.call(this, { params, ...opts }) or use Reflect.apply(fn, this, [{
params, ...opts }]) so the method keeps the correct `this` context).

Comment on lines +12 to +19
methods: {
_setWebhookId(id) {
this.db.set("webhookId", id);
},
_getWebhookId() {
return this.db.get("webhookId");
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add a shared getSummary helper.

Downstream sources call this.getSummary(...). Provide a simple default here to avoid crashes and unify summaries.

Apply this diff:

   methods: {
+    getSummary(summary) {
+      // Keep summaries concise; trim to a safe length
+      return String(summary ?? "").slice(0, 255);
+    },
     _setWebhookId(id) {
       this.db.set("webhookId", id);
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
methods: {
_setWebhookId(id) {
this.db.set("webhookId", id);
},
_getWebhookId() {
return this.db.get("webhookId");
},
},
methods: {
getSummary(summary) {
// Keep summaries concise; trim to a safe length
return String(summary ?? "").slice(0, 255);
},
_setWebhookId(id) {
this.db.set("webhookId", id);
},
_getWebhookId() {
return this.db.get("webhookId");
},
},
🤖 Prompt for AI Agents
In components/lightspeed_ecom_c_series/sources/common/base.mjs around lines 12
to 19, there is no default getSummary method which causes downstream sources
calling this.getSummary(...) to crash; add a shared getSummary helper in the
methods object that accepts (item, options) and returns a simple normalized
summary (e.g., a short object or string built from item fields like id, name,
and a fallback to JSON.stringify(item) when fields are missing) so downstream
callers have a safe, consistent default implementation.

Comment on lines +40 to +46
async run({ body }) {
this.http.respond({
status: 200,
});

this.$emit(body, this.generateMeta(body));
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

No webhook authenticity verification (security).

Responding 200 and emitting without verifying origin invites spoofed events. If Lightspeed signs webhooks, verify before emitting.

Apply this illustrative diff (uses optional verifyWebhook if implemented in the app; adjust per docs):

-  async run({ body }) {
-    this.http.respond({
-      status: 200,
-    });
-
-    this.$emit(body, this.generateMeta(body));
-  },
+  async run(event) {
+    const { headers, body } = event;
+    if (this.app.verifyWebhook && !this.app.verifyWebhook(headers, body)) {
+      this.http.respond({ status: 401 });
+      return;
+    }
+    this.http.respond({ status: 200 });
+    this.$emit(body, this.generateMeta(body));
+  },

Please confirm webhook signing details:


🌐 Web query:

Does Lightspeed eCom C-Series sign webhook requests? If yes, what header carries the signature and how is it computed/verified?

💡 Result:

Short answer: Yes. Lightspeed signs webhook requests using an HMAC. The signature header and verification method used across Lightspeed products are:

  • Header: X-Signature. Example header format: signature=,algorithm=HMAC-SHA256. [1]
  • How to verify: compute an HMAC-SHA256 of the raw request body using your application's client_secret (or webhook signature token where applicable) as the key, and compare the resulting digest to the signature value in X-Signature (use exact-match; do not reformat the payload). The algorithm field in the header indicates the HMAC algorithm (currently HMAC-SHA256). [1][2]

Sources: Lightspeed webhook docs and HMAC tips for Lightspeed products. [1][2]

References
[1] Lightspeed Webhooks — verification / X-Signature header. (x-series-api.lightspeedhq.com)
[2] HMAC tips for Lightspeed webhooks (note about using raw payload and SHA256). (o-series-support.lightspeedhq.com)

Citations:


Verify Lightspeed webhook signature (X-Signature) before emitting

Lightspeed signs webhooks with HMAC-SHA256 in the X-Signature header (format: signature=,algorithm=HMAC-SHA256). Compute HMAC-SHA256 over the raw request body with your client_secret/webhook token and use a constant-time compare — return 401 and do NOT emit on mismatch.

File: components/lightspeed_ecom_c_series/sources/common/base.mjs — run method (lines 40–46)

-  async run({ body }) {
-    this.http.respond({
-      status: 200,
-    });
-
-    this.$emit(body, this.generateMeta(body));
-  },
+  async run(event) {
+    const { headers, body, rawBody } = event;
+    // Prefer an app-provided verifier; otherwise verify X-Signature (HMAC-SHA256) over rawBody.
+    if (this.app.verifyWebhook && !this.app.verifyWebhook(headers, rawBody ?? body)) {
+      this.http.respond({ status: 401 });
+      return;
+    }
+    this.http.respond({ status: 200 });
+    this.$emit(body, this.generateMeta(body));
+  },

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In components/lightspeed_ecom_c_series/sources/common/base.mjs around lines 40
to 46, the run method currently accepts and emits the webhook payload without
validating the X-Signature header; compute the HMAC-SHA256 of the raw request
body using the configured client_secret/webhook token, parse the X-Signature
header (format: signature=<hex-or-base64>,algorithm=HMAC-SHA256), perform a
constant-time comparison (e.g., crypto.timingSafeEqual) between the computed
signature and the header value, and if the signature does not match respond with
status 401 and do NOT call this.$emit; ensure you use the raw request body bytes
(not a parsed object) and handle missing/invalid signature header by returning
401 as well.

...common,
key: "indiefunnels-customer-updated",
name: "Customer Updated (Instant)",
description: "Emit new event when an customer is updated.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix grammar in user-facing description

"an customer" → "a customer".

-  description: "Emit new event when an customer is updated.",
+  description: "Emit new event when a customer is updated.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: "Emit new event when an customer is updated.",
description: "Emit new event when a customer is updated.",
🤖 Prompt for AI Agents
In
components/lightspeed_ecom_c_series/sources/customer-updated/customer-updated.mjs
around line 8, the description string reads "Emit new event when an customer is
updated."; change the article "an" to "a" so it reads "Emit new event when a
customer is updated." and save the file.

Comment on lines +20 to +26
generateMeta(body) {
return {
id: body.customer.id,
summary: this.getSummary(`Customer with ID ${body.customer.id} updated`),
ts: Date.parse(body.customer.updatedAt),
};
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid event loss: include updatedAt in meta.id for 'unique' dedupe

Same rationale as invoices: multiple updates to one customer should not be collapsed.

-    generateMeta(body) {
-      return {
-        id: body.customer.id,
-        summary: this.getSummary(`Customer with ID ${body.customer.id} updated`),
-        ts: Date.parse(body.customer.updatedAt),
-      };
-    },
+    generateMeta(body) {
+      const ts = Date.parse(body.customer?.updatedAt);
+      const safeTs = Number.isFinite(ts) ? ts : Date.now();
+      return {
+        id: `${body.customer?.id}:${safeTs}`,
+        summary: this.getSummary(`Customer with ID ${body.customer?.id} updated`),
+        ts: safeTs,
+      };
+    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
generateMeta(body) {
return {
id: body.customer.id,
summary: this.getSummary(`Customer with ID ${body.customer.id} updated`),
ts: Date.parse(body.customer.updatedAt),
};
},
generateMeta(body) {
const ts = Date.parse(body.customer?.updatedAt);
const safeTs = Number.isFinite(ts) ? ts : Date.now();
return {
id: `${body.customer?.id}:${safeTs}`,
summary: this.getSummary(`Customer with ID ${body.customer?.id} updated`),
ts: safeTs,
};
},
🤖 Prompt for AI Agents
In
components/lightspeed_ecom_c_series/sources/customer-updated/customer-updated.mjs
around lines 20 to 26, the generated meta.id uses only body.customer.id which
can collapse multiple updates for the same customer; change meta.id to include
the updatedAt value (e.g., append the ISO timestamp or its numeric timestamp) so
each update produces a unique id, ensure ts remains
Date.parse(body.customer.updatedAt) and keep summary unchanged.

Comment on lines +144 to +148
"resource":{
"id":13353882,
"url":"customers\/123",
"link":"http:\/\/api.shoplightspeed.com\/en\/customers\/123.json",
"embedded":{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Customer resource id/url mismatch

Same mismatch here: resource.id 13353882 but URL points to /customers/123.

- "id":13353882,
- "url":"customers\/123",
- "link":"http:\/\/api.shoplightspeed.com\/en\/customers\/123.json",
+ "id":13353882,
+ "url":"customers\/13353882",
+ "link":"https:\/\/api.shoplightspeed.com\/en\/customers\/13353882.json",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"resource":{
"id":13353882,
"url":"customers\/123",
"link":"http:\/\/api.shoplightspeed.com\/en\/customers\/123.json",
"embedded":{
"resource":{
"id":13353882,
"url":"customers\/13353882",
"link":"https:\/\/api.shoplightspeed.com\/en\/customers\/13353882.json",
"embedded":{
🤖 Prompt for AI Agents
In components/lightspeed_ecom_c_series/sources/new-order-created/test-event.mjs
around lines 144 to 148, the resource object has id: 13353882 while the url
points to /customers/123, causing a mismatch; fix by making the id and URL
consistent — either update resource.id to 123 to match "customers/123" or update
the URL/path to use 13353882 (and update any linked fields like link
accordingly) so the id, url, and link all refer to the same customer identifier.

...common,
key: "indiefunnels-order-shipped",
name: "Order Shipped (Instant)",
description: "Emit new event when a order is shipped.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix grammar in user-facing description

"a order" → "an order".

-  description: "Emit new event when a order is shipped.",
+  description: "Emit new event when an order is shipped.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: "Emit new event when a order is shipped.",
description: "Emit new event when an order is shipped.",
🤖 Prompt for AI Agents
In components/lightspeed_ecom_c_series/sources/order-shipped/order-shipped.mjs
around line 8, the user-facing description uses incorrect grammar ("a order is
shipped"); update the string to use "an order" instead, i.e. change the
description to "Emit new event when an order is shipped." so the message reads
correctly for end users.

Comment on lines +20 to +26
generateMeta(body) {
return {
id: body.order.id,
summary: this.getSummary(`Order with ID ${body.order.id} shipped`),
ts: Date.parse(body.order.updatedAt),
};
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Prevent dedupe collisions for shipped events

If multiple shipment events occur per order, id=order.id will collapse them. Include a time (or shipment) component.

-    generateMeta(body) {
-      return {
-        id: body.order.id,
-        summary: this.getSummary(`Order with ID ${body.order.id} shipped`),
-        ts: Date.parse(body.order.updatedAt),
-      };
-    },
+    generateMeta(body) {
+      const ts = Date.parse(body.order?.updatedAt);
+      const safeTs = Number.isFinite(ts) ? ts : Date.now();
+      return {
+        id: `${body.order?.id}:${safeTs}`,
+        summary: this.getSummary(`Order with ID ${body.order?.id} shipped`),
+        ts: safeTs,
+      };
+    },

If the webhook payload includes a shipment id, prefer ${body.shipment.id} (or ${body.order.id}:${body.shipment.id}) as the id.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for order-shipped file..."
file=$(rg --hidden --glob '!node_modules' --files | rg 'order-shipped.mjs' || true)

if [[ -n "$file" ]]; then
  echo "Found: $file"
  echo "---- begin file ----"
  sed -n '1,240p' "$file" || true
  echo "---- end file ----"
else
  echo "order-shipped.mjs not found by name search. Listing files under components/lightspeed_ecom_c_series/sources if present..."
  rg -n --hidden --glob 'components/lightspeed_ecom_c_series/**' --files || true
fi

echo
echo "Searching repository for relevant symbols: generateMeta, shipment, shipment.id, order.id, updatedAt..."
rg -n --hidden -S "generateMeta" -g '!node_modules' -C3 || true
rg -n --hidden -S '\bshipment\b' -g '!node_modules' -C3 || true
rg -n --hidden -S '\bshipment\.id\b' -g '!node_modules' -C3 || true
rg -n --hidden -S '\border\.id\b' -g '!node_modules' -C3 || true
rg -n --hidden -S 'updatedAt' -g '!node_modules' -C3 || true

Length of output: 3442


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- order-shipped.mjs ----"
sed -n '1,240p' components/lightspeed_ecom_c_series/sources/order-shipped/order-shipped.mjs || true

echo
echo "---- test-event.mjs ----"
sed -n '1,240p' components/lightspeed_ecom_c_series/sources/order-shipped/test-event.mjs || true

echo
echo "---- search for 'shipment' and 'generateMeta' in that component ----"
rg -n --hidden -S "shipment" components/lightspeed_ecom_c_series -C3 || true
rg -n --hidden -S "generateMeta" components/lightspeed_ecom_c_series -C3 || true

Length of output: 91660


Use a shipment identifier in the dedupe key for order-shipped events

order.id alone collapses multiple shipments for the same order — prefer a shipment id when present, otherwise fall back to order id + timestamp.

File: components/lightspeed_ecom_c_series/sources/order-shipped/order-shipped.mjs — replace generateMeta with:

generateMeta(body) {
  const order = body?.order ?? {};
  const ts = Date.parse(order.updatedAt);
  const safeTs = Number.isFinite(ts) ? ts : Date.now();
  const shipmentId =
    order.shipmentData?.shipment_id ??
    order.shipmentId ??
    order.shipment?.resource?.id ??
    body.shipment?.id ??
    null;
  return {
    id: shipmentId ? `${order.id}:${shipmentId}` : `${order.id}:${safeTs}`,
    summary: this.getSummary(`Order with ID ${order.id} shipped`),
    ts: safeTs,
  };
},

If the incoming webhook provides a specific shipment resource id (e.g. shipmentData.shipment_id or shipment.resource.id), that should be used as the primary dedupe key.

Comment on lines +144 to +148
"resource":{
"id":13353882,
"url":"customers\/123",
"link":"http:\/\/api.shoplightspeed.com\/en\/customers\/123.json",
"embedded":{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Customer resource id/url mismatch

resource.id is 13353882 but url/link point to customers/123. This can break resolvers relying on URL ids.

- "id":13353882,
- "url":"customers\/123",
- "link":"http:\/\/api.shoplightspeed.com\/en\/customers\/123.json",
+ "id":13353882,
+ "url":"customers\/13353882",
+ "link":"https:\/\/api.shoplightspeed.com\/en\/customers\/13353882.json",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"resource":{
"id":13353882,
"url":"customers\/123",
"link":"http:\/\/api.shoplightspeed.com\/en\/customers\/123.json",
"embedded":{
"resource":{
"id":13353882,
"url":"customers\/13353882",
"link":"https:\/\/api.shoplightspeed.com\/en\/customers\/13353882.json",
"embedded":{
🤖 Prompt for AI Agents
In components/lightspeed_ecom_c_series/sources/order-updated/test-event.mjs
around lines 144 to 148, the resource.id (13353882) does not match the id
embedded in resource.url/resource.link (customers/123), which can break
resolvers; fix by making the URL and link consistent with the resource.id (e.g.,
change "customers/123" and the link to use "customers/13353882"), or
alternatively change resource.id to 123 if the URLs are canonical—ensure id,
url, and link all reference the same customer id.

Comment on lines +20 to +26
generateMeta(body) {
return {
id: body.product.id,
summary: this.getSummary(`Product with ID ${body.product.id} updated`),
ts: Date.parse(body.product.updatedAt),
};
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Dedupe bug: meta.id must be per-update, not per-product.

Using product.id with dedupe: "unique" will collapse multiple updates to the same product.

Apply this diff to make IDs event-specific and to harden parsing:

-      return {
-        id: body.product.id,
-        summary: this.getSummary(`Product with ID ${body.product.id} updated`),
-        ts: Date.parse(body.product.updatedAt),
-      };
+      const idPart = body?.product?.id;
+      const ts = Date.parse(body?.product?.updatedAt) || Date.now();
+      return {
+        id: `${idPart}-${ts}`,
+        summary: this.getSummary
+          ? this.getSummary(`Product with ID ${idPart} updated`)
+          : `Product with ID ${idPart} updated`,
+        ts,
+      };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
generateMeta(body) {
return {
id: body.product.id,
summary: this.getSummary(`Product with ID ${body.product.id} updated`),
ts: Date.parse(body.product.updatedAt),
};
},
generateMeta(body) {
const idPart = body?.product?.id;
const ts = Date.parse(body?.product?.updatedAt) || Date.now();
return {
id: `${idPart}-${ts}`,
summary: this.getSummary
? this.getSummary(`Product with ID ${idPart} updated`)
: `Product with ID ${idPart} updated`,
ts,
};
},
🤖 Prompt for AI Agents
In
components/lightspeed_ecom_c_series/sources/product-updated/product-updated.mjs
around lines 20-26, the generateMeta function currently uses product.id which
collapses multiple updates when dedupe: "unique" is used; change meta.id to be
event-specific (e.g., combine product.id with the update timestamp or an event
identifier such as `${body.product.id}:${Date.parse(body.product.updatedAt) ||
Date.now()}`) and harden parsing by checking body, body.product, and
body.product.updatedAt exist, using Date.parse(updatedAt) with a fallback to
Date.now() (or new Date().toISOString()) for ts, and ensure id uses the same
normalized timestamp string so each update yields a unique id.

⚠️ Potential issue

🧩 Analysis chain

Confirm payload field names.

Ensure the webhook delivers product.id and product.updatedAt as used here.


🌐 Web query:

For Lightspeed eCom C-Series product update webhooks: exact field names for product ID and last updated timestamp? `updatedAt` vs `updated_at`?

💡 Result:

Product update webhook (C‑Series) uses:

  • product ID: id (string UUID). [1]
  • last‑updated timestamp: updated_at (timestamp string). [1][2]

References: Lightspeed webhook example payloads and webhook docs. [1] (x-series-api.lightspeedhq.com)

Citations:


Use product.updated_at (snake_case) instead of product.updatedAt

Webhook payload uses product.id and product.updated_at — update Date.parse(body.product.updatedAt) to Date.parse(body.product.updated_at) (or normalize incoming keys) in components/lightspeed_ecom_c_series/sources/product-updated/product-updated.mjs (lines 20–26) to avoid NaN ts.

🤖 Prompt for AI Agents
In
components/lightspeed_ecom_c_series/sources/product-updated/product-updated.mjs
around lines 20–26, the code uses body.product.updatedAt which doesn't exist in
the webhook payload (the payload uses snake_case updated_at), causing Date.parse
to return NaN; change the ts computation to use body.product.updated_at (or
implement a small normalization/fallback such as
Date.parse(body.product.updated_at || body.product.updatedAt)) so ts is parsed
from the correct field.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lightspeed
1 participant