Skip to content

Promote staging to main: Slack notify service#33

Merged
eskobar95 merged 4 commits intomainfrom
staging
Apr 19, 2026
Merged

Promote staging to main: Slack notify service#33
eskobar95 merged 4 commits intomainfrom
staging

Conversation

@eskobar95
Copy link
Copy Markdown
Owner

@eskobar95 eskobar95 commented Apr 19, 2026

Summary

  • Ny apps/slack-notify service (Hono, Block Kit, signeret /v1/events og /v1/test/replay).
  • Medusa: Slack-klient, order.placed-subscriber, subscription/renewal/alerts og lifecycle-routes.
  • Internt sample-endpoint til replay uden nye ordrer; env- og Railway-noter.

Test plan

  • pnpm -C apps/slack-notify build
  • pnpm -C apps/commerce build
  • Efter deploy: sæt Railway env (Slack + NOTIFY_SHARED_SECRET + SLACK_NOTIFY_URL på server/worker), test /health og replay-endpoint.

Notes / risks

  • Kræver ny Railway-service for apps/slack-notify og env på eksisterende Medusa-services.
  • .cursor/settings.json er ikke med i denne commit (lokal Stripe-plugin toggle).

Made with Cursor

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Slack notifications for order placement and subscription lifecycle events (pause, resume, cancel, skip)
    • Added Slack alerts for subscription renewal orders and payment failures
    • Notifications include relevant order and subscription details with links to admin panel
  • Configuration

    • Added environment variables and deployment templates for Slack integration setup
  • Infrastructure

    • New Slack notification service with health check and testing/replay endpoints

- Add apps/slack-notify (Hono, Block Kit, signed /v1/events and /v1/test/replay)
- Commerce: slack-notify client, order.placed subscriber, subscription hooks
- Internal sample endpoint for replay; env docs and Railway notes
- Ignore apps/slack-notify/dist

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

Warning

Rate limit exceeded

@eskoubar95 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 26 minutes and 0 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 26 minutes and 0 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 66aa44a3-ec5f-4cfd-9bc4-684169c45018

📥 Commits

Reviewing files that changed from the base of the PR and between d31768f and 96b144a.

📒 Files selected for processing (22)
  • apps/commerce/docs/DEPLOY-RAILWAY.md
  • apps/commerce/env.template
  • apps/commerce/src/api/admin/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/skip/route.ts
  • apps/commerce/src/lib/slack-notify/send-slack-notify.ts
  • apps/commerce/src/lib/transactional-email/subscription-renewal-notifications.ts
  • apps/commerce/src/modules/subscription/service.ts
  • apps/commerce/src/subscribers/slack-notify-order-placed.ts
  • apps/commerce/src/workflows/steps/run-subscription-renewal.ts
  • apps/slack-notify/env.template
  • apps/slack-notify/src/app.ts
  • apps/slack-notify/src/blocks/build-messages.ts
  • apps/slack-notify/src/channels.ts
  • apps/slack-notify/src/server.ts
  • apps/slack-notify/src/validate-payload.ts
  • apps/slack-notify/src/verify.ts
  • openmemory.md
📝 Walkthrough

Walkthrough

This PR introduces a complete Slack notification system by adding a new standalone @guapo/slack-notify microservice and integrating signed event notifications throughout the commerce app. The system includes subscription lifecycle notifications, order-placed events, subscription renewal tracking, and test/replay endpoints with HMAC-SHA256 signature verification.

Changes

Cohort / File(s) Summary
Configuration & Deployment
.gitignore, env.example, apps/commerce/env.template, apps/slack-notify/env.template, apps/slack-notify/railway.toml, apps/slack-notify/package.json, apps/slack-notify/tsconfig.json
Added environment templates and deployment configs for both services; .gitignore ignores slack-notify build artifacts; Railway and package.json define the new microservice with Node >=20, Hono, Slack Web API deps; tsconfig targets ES2022 with strict mode.
Slack-Notify Service: Core
apps/slack-notify/src/server.ts, apps/slack-notify/src/app.ts, apps/slack-notify/src/types.ts, apps/slack-notify/src/verify.ts
Implements Hono-based HTTP server with /health status, /v1/events POST (signature verification, envelope validation, Slack posting), and /v1/test/replay endpoint (supports order/subscription/inventory test samples); includes HMAC-SHA256 signature verification and event type/channel hint definitions.
Slack-Notify Service: Utilities
apps/slack-notify/src/post-to-slack.ts, apps/slack-notify/src/channels.ts, apps/slack-notify/src/blocks/build-messages.ts
Builds formatted Slack messages from envelopes with Medusa Admin deep links; resolves channel IDs from JSON config or env vars; posts via Slack Web API using lazy-initialized bot token.
Commerce: Subscription Routes
apps/commerce/src/api/admin/subscriptions/[id]/cancel/route.ts, apps/commerce/src/api/admin/subscriptions/[id]/pause/route.ts, apps/commerce/src/api/admin/subscriptions/[id]/resume/route.ts, apps/commerce/src/api/store/subscriptions/[id]/.../{cancel,pause,resume,skip}/route.ts
Added fire-and-forget Slack notifications (via void) to 7 subscription lifecycle routes; each calls notifySubscriptionLifecycleSlack with action (`cancelled
Commerce: Slack Integration Helpers
apps/commerce/src/lib/slack-notify/types.ts, apps/commerce/src/lib/slack-notify/send-slack-notify.ts, apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts
Defines shared SlackNotifyEnvelope types and channel hints; send-slack-notify reads config env vars, computes HMAC-SHA256 signature, POSTs to ${SLACK_NOTIFY_URL}/v1/events with fallback no-op if config missing; notify-subscription-lifecycle forwards subscription events to sendSlackNotify.
Commerce: Workflows & Subscribers
apps/commerce/src/lib/create-subscriptions-from-placed-order.ts, apps/commerce/src/lib/transactional-email/subscription-renewal-notifications.ts, apps/commerce/src/workflows/steps/run-subscription-renewal.ts, apps/commerce/src/subscribers/slack-notify-order-placed.ts, apps/commerce/src/api/internal/notifications/slack/sample/route.ts
Added subscription-creation, subscription-renewal, order-renewal-failure Slack alerts; new order-placed subscriber fetches order + subscriptions and posts event; new internal /api/internal/notifications/slack/sample endpoint (Bearer auth via NOTIFY_SHARED_SECRET) provides test sample envelopes for order/subscription kinds.
Documentation
openmemory.md, apps/commerce/docs/DEPLOY-RAILWAY.md
Updated deployment docs with slack-notify service setup (Railway app path, build/start commands, env vars including SLACK_NOTIFY_URL, NOTIFY_SHARED_SECRET); documented /v1/test/replay endpoint; noted openmemory component list.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Commerce as Commerce Server<br/>(Subscription Route)
    participant SlackNotify as Slack-Notify Service
    participant Slack as Slack API

    Client->>Commerce: POST /subscriptions/:id/pause
    Commerce->>Commerce: Update subscription<br/>Send email
    Commerce->>SlackNotify: POST /v1/events<br/>[signed with HMAC-SHA256]<br/>{"type":"subscription.updated",<br/>"payload":{...}}
    Note over Commerce: Fire-and-forget (void)
    SlackNotify->>SlackNotify: Verify signature<br/>Resolve channel<br/>Build message blocks
    SlackNotify->>Slack: chat.postMessage
    Slack->>SlackNotify: 200 OK
    SlackNotify->>SlackNotify: Log result
    Commerce->>Client: 200 OK (subscription)
Loading
sequenceDiagram
    participant Admin as Medusa Admin
    participant SlackNotify as Slack-Notify Service
    participant Commerce as Commerce Internal API
    participant Slack as Slack API

    Admin->>SlackNotify: POST /v1/test/replay<br/>{"kind":"subscription",<br/>"strategy":"latest",<br/>signed}
    SlackNotify->>SlackNotify: Verify signature
    SlackNotify->>Commerce: GET /api/internal/notifications/slack/sample<br/>?kind=subscription<br/>[Bearer token]
    Commerce->>SlackNotify: 200 {"type":"subscription.updated",<br/>"payload":{...}}
    SlackNotify->>SlackNotify: Build message blocks
    SlackNotify->>Slack: chat.postMessage
    Slack->>SlackNotify: 200 OK
    SlackNotify->>Admin: 200 {"ok":true,"replayed":{...}}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #15: Adds Slack notification side-effects to the same set of subscription routes and introduces notification helper modules; this PR extends those calls across additional subscription lifecycle states.
  • PR #17: Modifies the same subscription lifecycle route handlers and notification-related code paths (cancel/pause/resume routes, lifecycle mail flow).
  • PR #18: Touches subscription renewal and subscription-creation code paths that now receive Slack notification integration in this PR.

Poem

🐰 Hops of joy! Notifications now bound,
From subscriptions to Slack, signed and sound,
With channels and blocks, we relay the news,
Order placed! Renewed! No more to lose! 🎉
A microservice hopping, all neat and secure!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is written entirely in Danish and lacks several required template sections including Task ID, proper summary formatting, and structured acceptance criteria. Rewrite the description in English following the template structure: include a Task ID, properly formatted summary (3 bullets), structured changes section, testing checklist, acceptance criteria list, and related issues section.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Promote staging to main: Slack notify service' clearly describes the main change—a staging-to-main promotion that introduces a new Slack notify service.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch staging

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
Copy Markdown

@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: 12

🧹 Nitpick comments (1)
apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts (1)

4-11: Extract the input object shape into an interface.

This keeps the helper aligned with the repo’s TypeScript shape conventions.

♻️ Proposed refactor
-export async function notifySubscriptionLifecycleSlack(
-  container: MedusaContainer,
-  input: {
-    subscriptionId: string;
-    action: "paused" | "resumed" | "cancelled" | "skipped";
-    status: string;
-  }
-): Promise<void> {
+interface NotifySubscriptionLifecycleSlackInput {
+  subscriptionId: string
+  action: "paused" | "resumed" | "cancelled" | "skipped"
+  status: string
+}
+
+export async function notifySubscriptionLifecycleSlack(
+  container: MedusaContainer,
+  input: NotifySubscriptionLifecycleSlackInput
+): Promise<void> {

As per coding guidelines, “Use interface for object shapes in TypeScript; keep near usage or in *.types.ts files”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts` around
lines 4 - 11, The input object literal used by notifySubscriptionLifecycleSlack
should be extracted into a named interface to match repo TypeScript conventions;
create an interface (e.g., SubscriptionLifecycleNotificationInput) that declares
subscriptionId: string, action: "paused" | "resumed" | "cancelled" | "skipped",
and status: string, then update the notifySubscriptionLifecycleSlack signature
to accept input: SubscriptionLifecycleNotificationInput (import or colocate the
interface as appropriate, e.g., next to the function or in a *.types.ts file)
and update any callers or imports to use the new interface name.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/commerce/src/api/admin/subscriptions/`[id]/cancel/route.ts:
- Around line 29-33: notifySubscriptionLifecycleSlack is async and currently
called with `void` which suppresses TypeScript but leaves a possible unhandled
rejection; change the call to be non-blocking but catch errors explicitly (e.g.,
call notifySubscriptionLifecycleSlack(req.scope, {...}).catch(err => { /* log
via req.scope.logger or console.warn with context: subscriptionId and action */
})) so failures are logged and swallowed instead of becoming unhandled promise
rejections. Ensure you reference notifySubscriptionLifecycleSlack and use
req.scope (or its logger) to record a warning including subscriptionId and
action.

In `@apps/commerce/src/api/store/subscriptions/`[id]/cancel/route.ts:
- Around line 61-65: The call to notifySubscriptionLifecycleSlack(...) is
fire-and-forget using void and can produce unhandled promise rejections; change
the invocation to attach a .catch(...) that logs any error (for example using
req.scope.logger.error or similar logger on req.scope) and include context
(subscriptionId and action) so failures in notifySubscriptionLifecycleSlack /
sendSlackNotify are recorded; update the call site where
notifySubscriptionLifecycleSlack is invoked with subscriptionId: updated.id,
action: "cancelled", status: updated.status to append the catch handler.

In `@apps/commerce/src/lib/slack-notify/send-slack-notify.ts`:
- Around line 29-36: The outbound fetch in sendSlackNotify (the POST to
`${url}/v1/events`) has no timeout; wrap the request with an AbortController:
create a controller, set a setTimeout to call controller.abort() after 5000ms,
pass controller.signal to fetch, and clear the timeout after fetch resolves (or
in a finally block). Update the fetch invocation in sendSlackNotify to include
the signal and ensure any abort-related errors are propagated/handled the same
way as other fetch errors so the caller won't hang indefinitely.

In
`@apps/commerce/src/lib/transactional-email/subscription-renewal-notifications.ts`:
- Around line 49-57: The Slack alert is sending payload.detail as
chargeResult.error which may be an Error or non-serializable object and ends up
as "{}" when stringified; update the code around sendSlackNotify in
subscription-renewal-notifications.ts to normalize the error before attaching it
to payload.detail (e.g., convert Error instances to a plain object with
message/stack and include any enumerable properties, or use a small
serializer/normalizeError function) so the Slack message contains a readable
error message and stack rather than an empty object.

In `@apps/commerce/src/subscribers/slack-notify-order-placed.ts`:
- Around line 64-65: The current use of listSubscriptionsForOrder(container,
order.id) in slack-notify-order-placed.ts causes an unbounded table scan and may
miss subscriptions due to its 2,000 cap; replace that call with a targeted
lookup using the subscriptions module link / query.graph() or the subscriptions
repository/index to fetch subscriptions where metadata.order_id === order.id (or
the explicit indexed field) so the database uses an index and returns only
matching rows; update the handler to use the module link name and
query.graph()/an indexed query pattern (instead of listSubscriptionsForOrder) to
retrieve linked subscriptions by order.id without in-memory filtering or large
scans.

In `@apps/commerce/src/workflows/steps/run-subscription-renewal.ts`:
- Around line 309-333: The code performs a blocking/awaited enrichment query
(resolveQuery -> query.graph) inside the renewal path so a failure there can
trigger the compensation refund; move or decouple Slack enrichment so it cannot
throw during the critical renewal/try block: either run the query.graph and
sendSlackNotify after the renewal/try/commit completes (or wrap the enrichment
in its own try/catch that swallows errors), and ensure the notification payload
uses the correct field name subscriptionIds (e.g., send subscriptionIds:
[subscriptionId]) instead of subscriptionId; update references to resolveQuery,
query.graph, sendSlackNotify, and toAmountMajor accordingly so enrichment
failures don’t cause payment/order compensation.

In `@apps/slack-notify/src/app.ts`:
- Around line 52-62: The handler currently rejects requests when
GUAPO_COMMERCE_INTERNAL_URL is missing even for inventory-only replays; change
the validation so commerceBase (from GUAPO_COMMERCE_INTERNAL_URL ->
commerceBase) is only required for non-inventory paths. Concretely, keep the
NOTIFY_SHARED_SECRET/secret check as-is, then either move the commerceBase
resolution/validation below the branch that checks body.kind === "inventory" or
conditionally validate commerceBase only when body.kind !== "inventory"; ensure
you still call c.json for error responses (same messages) and preserve the
inventory branch behavior that does not use commerceBase.
- Around line 18-26: Replace the blind "as" casts with runtime validation
functions: after JSON.parse, call a validator like
isValidSlackNotifyEnvelope(envelope) that checks envelope.type is one of
SlackNotifyEventType values, envelope.payload is a non-null object, and optional
envelope.channel (if present) is a string; return 400 if it fails. Do the same
for the ReplayRequestBody parsing/usage: add isValidReplayRequestBody(body) to
ensure body.kind is one of the allowed union members and other required fields
are properly typed before using or mapping kind (this prevents invalid kinds
from being treated as "subscription"). Update the parsing blocks around the
SlackNotifyEnvelope and ReplayRequestBody handling (the parse at top and the
logic referenced in lines ~42–50 and ~79) to use these validators instead of
"as" casts and proceed only on successful validation.

In `@apps/slack-notify/src/blocks/build-messages.ts`:
- Around line 41-51: The renewal-order payload uses payload.subscriptionId
(singular) but buildOrderPlaced currently only reads payload.subscriptionIds
(plural) into subscriptionIds, causing renewal messages to lose context; update
the subscriptionIds logic in function buildOrderPlaced to accept either an array
(payload.subscriptionIds) or a single string (payload.subscriptionId) by
normalizing to an array — handle the case where payload.subscriptionIds is an
array, or if not, but payload.subscriptionId is a string, create a one-element
array containing it, otherwise default to []; keep the symbol subscriptionIds
and payload usage so the rest of the function continues to work.

In `@apps/slack-notify/src/channels.ts`:
- Around line 7-11: The code that reads SLACK_CHANNELS_JSON parses mapJson into
m, picks id by key (hint logic) and only returns it if id.startsWith("C"), which
incorrectly rejects valid Slack conversation IDs; update the validation for the
parsed id (variable id used in this block) to accept any string that starts with
one of the supported prefixes ("C","G","D","U") or simply remove the prefix
check and return any non-empty string to match the behavior of the
SLACK_CHANNEL_* fallbacks; ensure you update the check near the variables
mapJson, m, key, and id so parsed JSON channel IDs are treated consistently with
env var fallbacks.

In `@apps/slack-notify/src/server.ts`:
- Around line 4-10: The port variable created as Number(process.env.PORT ??
"8080") can be NaN or 0 for bad input; before calling serve({ fetch: app.fetch,
port, ... }) validate the PORT value (the port constant) and fail fast with a
clear error: parse/convert the environment value for PORT, ensure it's an
integer within the valid TCP port range (1–65535) and not NaN or an
empty-string-derived 0, and throw or log a descriptive error and exit if
validation fails; apply this check near the createApp()/serve call so the
invalid port never gets passed into serve.

In `@apps/slack-notify/src/verify.ts`:
- Around line 3-14: The verifyNotifySignature function lacks replay protection;
change the signing scheme to include a timestamp header (e.g.,
X-Notify-Timestamp) in the canonical string and reject requests whose timestamp
is stale (e.g., older than 5 minutes). Update verifyNotifySignature to read the
timestamp header, build the canonical payload as "<timestamp>.<rawBody>" (same
format used by the commerce signer and Hono route), compute the HMAC using
NOTIFY_SHARED_SECRET, and perform timingSafeEqual against the provided
X-Notify-Signature; return false if the timestamp is missing, unparsable, or
outside the allowed clock skew. Ensure the commerce signer and the Hono route
are changed to produce and expect the identical "<timestamp>.<body>" canonical
string and share the same header names.

---

Nitpick comments:
In `@apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts`:
- Around line 4-11: The input object literal used by
notifySubscriptionLifecycleSlack should be extracted into a named interface to
match repo TypeScript conventions; create an interface (e.g.,
SubscriptionLifecycleNotificationInput) that declares subscriptionId: string,
action: "paused" | "resumed" | "cancelled" | "skipped", and status: string, then
update the notifySubscriptionLifecycleSlack signature to accept input:
SubscriptionLifecycleNotificationInput (import or colocate the interface as
appropriate, e.g., next to the function or in a *.types.ts file) and update any
callers or imports to use the new interface name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c8d131a9-4fcf-44c9-8a9f-542c81f137de

📥 Commits

Reviewing files that changed from the base of the PR and between 93be560 and d31768f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (31)
  • .gitignore
  • apps/commerce/docs/DEPLOY-RAILWAY.md
  • apps/commerce/env.template
  • apps/commerce/src/api/admin/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/internal/notifications/slack/sample/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/skip/route.ts
  • apps/commerce/src/lib/create-subscriptions-from-placed-order.ts
  • apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts
  • apps/commerce/src/lib/slack-notify/send-slack-notify.ts
  • apps/commerce/src/lib/slack-notify/types.ts
  • apps/commerce/src/lib/transactional-email/subscription-renewal-notifications.ts
  • apps/commerce/src/subscribers/slack-notify-order-placed.ts
  • apps/commerce/src/workflows/steps/run-subscription-renewal.ts
  • apps/slack-notify/env.template
  • apps/slack-notify/package.json
  • apps/slack-notify/railway.toml
  • apps/slack-notify/src/app.ts
  • apps/slack-notify/src/blocks/build-messages.ts
  • apps/slack-notify/src/channels.ts
  • apps/slack-notify/src/post-to-slack.ts
  • apps/slack-notify/src/server.ts
  • apps/slack-notify/src/types.ts
  • apps/slack-notify/src/verify.ts
  • apps/slack-notify/tsconfig.json
  • env.example
  • openmemory.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (9)
apps/commerce/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Commerce app in apps/commerce should implement Medusa for handling carts, orders, payments, subscriptions, shipping, and product catalog for sale

apps/commerce/**/*.{ts,tsx,js,jsx}: Implement mutations (create/update/delete business state) as workflow steps and expose via route with workflow(req.scope).run({ input }). Do not put multi-step or rollback-needing logic only in a route handler calling services ad hoc.
For simple reads (list/get), resolving module or core services in a GET route is acceptable.
Use GET, POST, DELETE HTTP methods only (not PUT/PATCH) unless aligning with an existing Medusa convention that already uses PATCH.
Validate request bodies with Zod (@medusajs/framework/zod); type routes with inferred types (e.g. MedusaRequest + validated body).
Protect store/admin routes: use authenticated request types and enforce customer/admin scope.
For cross-module data access, use module links and query.graph() / index patterns per Medusa docs instead of direct service calls.
Use camelCase for module names (no dashes in module registration).

Files:

  • apps/commerce/src/api/store/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/lib/create-subscriptions-from-placed-order.ts
  • apps/commerce/src/api/store/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/skip/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/workflows/steps/run-subscription-renewal.ts
  • apps/commerce/src/lib/transactional-email/subscription-renewal-notifications.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/subscribers/slack-notify-order-placed.ts
  • apps/commerce/src/lib/slack-notify/send-slack-notify.ts
  • apps/commerce/src/lib/slack-notify/types.ts
  • apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts
  • apps/commerce/src/api/internal/notifications/slack/sample/route.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/code-structure.mdc)

**/*.{ts,tsx}: Treat files > ~400 lines in one .tsx as tech debt — split before adding features
Use interface for object shapes in TypeScript; keep near usage or in *.types.ts files
Avoid any type in TypeScript; use unknown + narrowing or Zod-inferred types where validated
Prefer as const objects + union type over enums unless enum is required by an external API
Prefer direct imports from the module you need (e.g. @/components/ui/button) to avoid pulling large barrels into client bundles

Define a type for const functions when possible

**/*.{ts,tsx}: Use interfaces for object shapes in TypeScript; write functional components with typed props
Avoid enums; prefer as const objects combined with union types
Do not use any type; use unknown with type narrowing or Zod-inferred types for validated data

Files:

  • apps/commerce/src/api/store/subscriptions/[id]/pause/route.ts
  • apps/slack-notify/src/server.ts
  • apps/commerce/src/lib/create-subscriptions-from-placed-order.ts
  • apps/commerce/src/api/store/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/skip/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/workflows/steps/run-subscription-renewal.ts
  • apps/commerce/src/lib/transactional-email/subscription-renewal-notifications.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/cancel/route.ts
  • apps/slack-notify/src/post-to-slack.ts
  • apps/slack-notify/src/blocks/build-messages.ts
  • apps/commerce/src/subscribers/slack-notify-order-placed.ts
  • apps/slack-notify/src/channels.ts
  • apps/commerce/src/lib/slack-notify/send-slack-notify.ts
  • apps/commerce/src/lib/slack-notify/types.ts
  • apps/slack-notify/src/app.ts
  • apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts
  • apps/commerce/src/api/internal/notifications/slack/sample/route.ts
  • apps/slack-notify/src/verify.ts
  • apps/slack-notify/src/types.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/front-end-cursor-rules.mdc)

**/*.{js,jsx,ts,tsx}: Use early returns whenever possible to make the code more readable
Use descriptive variable and function/const names
Use const instead of function declarations, for example 'const toggle = () =>'
Don't use semicolons

Files:

  • apps/commerce/src/api/store/subscriptions/[id]/pause/route.ts
  • apps/slack-notify/src/server.ts
  • apps/commerce/src/lib/create-subscriptions-from-placed-order.ts
  • apps/commerce/src/api/store/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/skip/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/workflows/steps/run-subscription-renewal.ts
  • apps/commerce/src/lib/transactional-email/subscription-renewal-notifications.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/cancel/route.ts
  • apps/slack-notify/src/post-to-slack.ts
  • apps/slack-notify/src/blocks/build-messages.ts
  • apps/commerce/src/subscribers/slack-notify-order-placed.ts
  • apps/slack-notify/src/channels.ts
  • apps/commerce/src/lib/slack-notify/send-slack-notify.ts
  • apps/commerce/src/lib/slack-notify/types.ts
  • apps/slack-notify/src/app.ts
  • apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts
  • apps/commerce/src/api/internal/notifications/slack/sample/route.ts
  • apps/slack-notify/src/verify.ts
  • apps/slack-notify/src/types.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/nextjs-react-typescript-cursor-rules.mdc)

**/*.{ts,tsx,js,jsx}: Use concise, technical TypeScript; write functional and declarative code; avoid classes
Prefer modularization over duplication; use descriptive names like isLoading, hasError
Use the function keyword for pure functions; write declarative JSX

Files:

  • apps/commerce/src/api/store/subscriptions/[id]/pause/route.ts
  • apps/slack-notify/src/server.ts
  • apps/commerce/src/lib/create-subscriptions-from-placed-order.ts
  • apps/commerce/src/api/store/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/skip/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/workflows/steps/run-subscription-renewal.ts
  • apps/commerce/src/lib/transactional-email/subscription-renewal-notifications.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/cancel/route.ts
  • apps/slack-notify/src/post-to-slack.ts
  • apps/slack-notify/src/blocks/build-messages.ts
  • apps/commerce/src/subscribers/slack-notify-order-placed.ts
  • apps/slack-notify/src/channels.ts
  • apps/commerce/src/lib/slack-notify/send-slack-notify.ts
  • apps/commerce/src/lib/slack-notify/types.ts
  • apps/slack-notify/src/app.ts
  • apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts
  • apps/commerce/src/api/internal/notifications/slack/sample/route.ts
  • apps/slack-notify/src/verify.ts
  • apps/slack-notify/src/types.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/nextjs-react-typescript-cursor-rules.mdc)

**/*.{tsx,ts}: File layout: place exported component first, then subcomponents, helpers, static content, types. Follow code-structure.mdc for file size and splitting decisions
Favor named exports for components
Default to Server Components in Next.js; use 'use client' only for interactivity, browser APIs, or third-party widgets like Stripe; keep client boundaries small
Use next/dynamic for heavy or below-the-fold components
Use nuqs library for managing URL state and search parameters
Do not fetch data on the client when the server can perform the fetch; avoid duplicating fetches in layouts and pages without using React.cache()

Files:

  • apps/commerce/src/api/store/subscriptions/[id]/pause/route.ts
  • apps/slack-notify/src/server.ts
  • apps/commerce/src/lib/create-subscriptions-from-placed-order.ts
  • apps/commerce/src/api/store/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/skip/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/workflows/steps/run-subscription-renewal.ts
  • apps/commerce/src/lib/transactional-email/subscription-renewal-notifications.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/cancel/route.ts
  • apps/slack-notify/src/post-to-slack.ts
  • apps/slack-notify/src/blocks/build-messages.ts
  • apps/commerce/src/subscribers/slack-notify-order-placed.ts
  • apps/slack-notify/src/channels.ts
  • apps/commerce/src/lib/slack-notify/send-slack-notify.ts
  • apps/commerce/src/lib/slack-notify/types.ts
  • apps/slack-notify/src/app.ts
  • apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts
  • apps/commerce/src/api/internal/notifications/slack/sample/route.ts
  • apps/slack-notify/src/verify.ts
  • apps/slack-notify/src/types.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

Do not log or echo secrets; redact secrets in examples and documentation

Files:

  • apps/commerce/src/api/store/subscriptions/[id]/pause/route.ts
  • apps/slack-notify/src/server.ts
  • apps/commerce/src/lib/create-subscriptions-from-placed-order.ts
  • apps/commerce/src/api/store/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/skip/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/workflows/steps/run-subscription-renewal.ts
  • apps/commerce/src/lib/transactional-email/subscription-renewal-notifications.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/cancel/route.ts
  • apps/slack-notify/src/post-to-slack.ts
  • apps/slack-notify/src/blocks/build-messages.ts
  • apps/commerce/src/subscribers/slack-notify-order-placed.ts
  • apps/slack-notify/src/channels.ts
  • apps/commerce/src/lib/slack-notify/send-slack-notify.ts
  • apps/commerce/src/lib/slack-notify/types.ts
  • apps/slack-notify/src/app.ts
  • apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts
  • apps/commerce/src/api/internal/notifications/slack/sample/route.ts
  • apps/slack-notify/src/verify.ts
  • apps/slack-notify/src/types.ts
**/api/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

**/api/**/*.{js,ts}: Assume Medusa store API receives untrusted clients; validate input, enforce customer/session rules on protected routes, avoid leaking internal IDs or stack traces in production errors
Require authentication for destructive or sensitive operations in Admin/Payload interfaces; do not expose admin-only fields on public REST/GraphQL responses

Files:

  • apps/commerce/src/api/store/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/skip/route.ts
  • apps/commerce/src/api/store/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/pause/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/resume/route.ts
  • apps/commerce/src/api/admin/subscriptions/[id]/cancel/route.ts
  • apps/commerce/src/api/internal/notifications/slack/sample/route.ts
**/lib/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/code-structure.mdc)

**/lib/**/*.{ts,tsx}: Pure utilities / mappers (lib/.ts) should be ≤ ~400 lines; split by domain (cart-.ts, checkout-*.ts)
Write pure helpers (formatting, mapping) using function keyword with single responsibility for easy unit testing

Files:

  • apps/commerce/src/lib/create-subscriptions-from-placed-order.ts
  • apps/commerce/src/lib/transactional-email/subscription-renewal-notifications.ts
  • apps/commerce/src/lib/slack-notify/send-slack-notify.ts
  • apps/commerce/src/lib/slack-notify/types.ts
  • apps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.ts
**/package.json

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

Prefer pinned or lockfile-resolved versions for production dependencies; review new packages for supply-chain risk when adding to sensitive paths (payment, auth)

Files:

  • apps/slack-notify/package.json
🧠 Learnings (15)
📚 Learning: 2026-04-15T16:59:32.095Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/storefront-next.mdc:0-0
Timestamp: 2026-04-15T16:59:32.095Z
Learning: Applies to apps/storefront/src/**/*.{ts,tsx} : Talk to Medusa via `medusajs/js-sdk` and the shared client in `src/lib/medusa.ts` (publishable key, `NEXT_PUBLIC_MEDUSA_BACKEND_URL`)

Applied to files:

  • apps/commerce/src/api/store/subscriptions/[id]/skip/route.ts
  • openmemory.md
  • apps/commerce/docs/DEPLOY-RAILWAY.md
  • apps/commerce/src/lib/slack-notify/send-slack-notify.ts
📚 Learning: 2026-04-15T16:58:38.430Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/code-structure.mdc:0-0
Timestamp: 2026-04-15T16:58:38.430Z
Learning: Applies to **/*.{ts,tsx} : Treat files > ~400 lines in one .tsx as tech debt — split before adding features

Applied to files:

  • apps/slack-notify/tsconfig.json
📚 Learning: 2026-04-15T16:59:32.095Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/storefront-next.mdc:0-0
Timestamp: 2026-04-15T16:59:32.095Z
Learning: Applies to apps/storefront/src/**/*.{ts,tsx} : Follow `code-structure.mdc` for file structure and organization: line budgets, splitting steps/hooks/types, React/TypeScript hygiene

Applied to files:

  • apps/slack-notify/tsconfig.json
📚 Learning: 2026-04-15T16:58:38.430Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/code-structure.mdc:0-0
Timestamp: 2026-04-15T16:58:38.430Z
Learning: Applies to **/*.{ts,tsx} : Use interface for object shapes in TypeScript; keep near usage or in *.types.ts files

Applied to files:

  • apps/slack-notify/tsconfig.json
📚 Learning: 2026-04-15T16:58:38.430Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/code-structure.mdc:0-0
Timestamp: 2026-04-15T16:58:38.430Z
Learning: Applies to **/*.{ts,tsx} : Avoid any type in TypeScript; use unknown + narrowing or Zod-inferred types where validated

Applied to files:

  • apps/slack-notify/tsconfig.json
📚 Learning: 2026-04-15T16:59:32.095Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/storefront-next.mdc:0-0
Timestamp: 2026-04-15T16:59:32.095Z
Learning: Applies to apps/storefront/src/**/*.{ts,tsx} : Payload/content: use existing `lib/payload-*.ts` helpers; pass `locale` (`da` / `en`) for localized APIs

Applied to files:

  • apps/slack-notify/tsconfig.json
  • apps/slack-notify/src/blocks/build-messages.ts
  • apps/slack-notify/src/types.ts
📚 Learning: 2026-04-15T16:59:08.330Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/nextjs-react-typescript-cursor-rules.mdc:0-0
Timestamp: 2026-04-15T16:59:08.330Z
Learning: Applies to **/*.{tsx,ts} : File layout: place exported component first, then subcomponents, helpers, static content, types. Follow `code-structure.mdc` for file size and splitting decisions

Applied to files:

  • apps/slack-notify/tsconfig.json
📚 Learning: 2026-04-15T16:59:08.330Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/nextjs-react-typescript-cursor-rules.mdc:0-0
Timestamp: 2026-04-15T16:59:08.330Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use concise, technical TypeScript; write functional and declarative code; avoid classes

Applied to files:

  • apps/slack-notify/tsconfig.json
📚 Learning: 2026-04-15T16:58:24.041Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/architecture.mdc:0-0
Timestamp: 2026-04-15T16:58:24.041Z
Learning: Applies to apps/commerce/**/*.{ts,tsx,js,jsx} : Commerce app in `apps/commerce` should implement Medusa for handling carts, orders, payments, subscriptions, shipping, and product catalog for sale

Applied to files:

  • openmemory.md
  • apps/commerce/docs/DEPLOY-RAILWAY.md
  • apps/commerce/src/subscribers/slack-notify-order-placed.ts
  • apps/commerce/src/lib/slack-notify/types.ts
📚 Learning: 2026-04-15T16:58:24.041Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/architecture.mdc:0-0
Timestamp: 2026-04-15T16:58:24.041Z
Learning: Applies to apps/storefront/**/*.{ts,tsx,js,jsx} : Storefront app in `apps/storefront` should be a Next.js customer UI with i18n routes that composes data from both Medusa and Payload CMS

Applied to files:

  • openmemory.md
  • apps/commerce/docs/DEPLOY-RAILWAY.md
📚 Learning: 2026-04-15T16:58:57.364Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/medusa-commerce.mdc:0-0
Timestamp: 2026-04-15T16:58:57.364Z
Learning: Applies to apps/commerce/**/*.{ts,tsx,js,jsx} : For cross-module data access, use module links and `query.graph()` / index patterns per Medusa docs instead of direct service calls.

Applied to files:

  • openmemory.md
📚 Learning: 2026-04-15T16:58:24.041Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/architecture.mdc:0-0
Timestamp: 2026-04-15T16:58:24.041Z
Learning: Prices, inventory, cart, checkout, orders, subscriptions, and shipping options must be sourced from Medusa only

Applied to files:

  • openmemory.md
📚 Learning: 2026-04-15T16:58:24.041Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/architecture.mdc:0-0
Timestamp: 2026-04-15T16:58:24.041Z
Learning: Marketing copy, SEO text, layout content, navigation, footer, and homepage sections must be sourced from Payload only; reference Medusa entities by ID/handle where needed and do not duplicate commerce state in CMS

Applied to files:

  • openmemory.md
📚 Learning: 2026-04-15T16:58:57.364Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/medusa-commerce.mdc:0-0
Timestamp: 2026-04-15T16:58:57.364Z
Learning: Applies to apps/commerce/**/*.{ts,tsx,js,jsx} : For simple reads (list/get), resolving module or core services in a GET route is acceptable.

Applied to files:

  • apps/commerce/src/api/internal/notifications/slack/sample/route.ts
📚 Learning: 2026-04-15T16:58:57.364Z
Learnt from: CR
Repo: eskoubar95/guapo PR: 0
File: .cursor/rules/medusa-commerce.mdc:0-0
Timestamp: 2026-04-15T16:58:57.364Z
Learning: Applies to apps/commerce/**/*.{ts,tsx,js,jsx} : Protect store/admin routes: use authenticated request types and enforce customer/admin scope.

Applied to files:

  • apps/commerce/src/api/internal/notifications/slack/sample/route.ts
🔇 Additional comments (13)
.gitignore (1)

21-22: LGTM — build output is ignored.

Ignoring apps/slack-notify/dist/ keeps generated artifacts out of the repo.

apps/slack-notify/railway.toml (1)

1-7: LGTM — Railway config matches the service shape.

The /health check aligns with the Hono app route, and the restart policy is reasonable for this service.

openmemory.md (1)

77-77: LGTM — Slack notify docs are clear and safe.

This captures the service boundary, signed event flow, replay path, deployment root, and env-template location without exposing secret values.

apps/commerce/src/api/store/subscriptions/[id]/pause/route.ts (1)

49-53: No action required — the helper already handles rejections internally.

notifySubscriptionLifecycleSlack delegates to sendSlackNotify, which wraps the fetch call in a try-catch and logs all failures via logger.warn(). The function never rejects; it gracefully handles Slack/network failures and returns a resolved Promise. The void pattern is safe here and explicit .catch() is unnecessary.

			> Likely an incorrect or invalid review comment.
apps/commerce/src/lib/create-subscriptions-from-placed-order.ts (1)

237-244: The sendSlackNotify call at this location is safe and does not require additional error handling. The sendSlackNotify function already implements comprehensive error handling with a try-catch block that captures network failures and HTTP error responses, logging them via logger.warn(). The promise never rejects—it resolves successfully in all code paths. The void prefix on the fire-and-forget call is appropriate here.

			> Likely an incorrect or invalid review comment.
apps/commerce/src/api/store/subscriptions/[id]/skip/route.ts (1)

4-4: LGTM — notification is emitted after the authorized skip mutation.

The new Slack lifecycle event is fire-and-forget and uses the updated subscription state, so it does not change the customer response path.

Also applies to: 39-43

env.example (1)

35-45: LGTM — Slack env inventory is documented without leaking secrets.

The secret-bearing values are left blank, and the service boundary is clear.

apps/commerce/env.template (1)

201-207: LGTM — deployment notes cover both Medusa runtimes.

Calling out server + worker configuration should prevent missing Slack notifications from worker-side events.

apps/commerce/src/api/admin/subscriptions/[id]/resume/route.ts (1)

6-6: LGTM — resumed lifecycle notification matches the mutation.

The event is sent after subscriptionService.resume(id) succeeds and uses action: "resumed" with the updated status.

Also applies to: 29-33

apps/commerce/src/api/admin/subscriptions/[id]/pause/route.ts (1)

6-6: LGTM — paused lifecycle notification matches the mutation.

The Slack side effect is emitted only after the pause succeeds and carries the updated subscription state.

Also applies to: 29-33

apps/commerce/src/api/store/subscriptions/[id]/resume/route.ts (1)

6-6: LGTM — customer resume notifications remain behind ownership checks.

The Slack event is emitted after the subscription has been resumed and uses the updated status.

Also applies to: 49-53

apps/slack-notify/tsconfig.json (1)

1-16: LGTM — strict NodeNext build config is appropriate for the new service.

The config keeps compilation scoped to src and excludes generated dist output.

apps/slack-notify/package.json (1)

13-20: Lockfile already pins these dependencies.

The pnpm-lock.yaml includes and resolves all slack-notify dependencies to exact versions (@hono/node-server to 1.19.12, @slack/web-api to 7.15.1, etc.), ensuring reproducible installs. The caret ranges in package.json are acceptable given the committed lockfile.

Comment thread apps/commerce/src/api/admin/subscriptions/[id]/cancel/route.ts
Comment thread apps/commerce/src/api/store/subscriptions/[id]/cancel/route.ts
Comment thread apps/commerce/src/lib/slack-notify/send-slack-notify.ts
Comment thread apps/commerce/src/subscribers/slack-notify-order-placed.ts Outdated
Comment thread apps/slack-notify/src/app.ts Outdated
Comment on lines +52 to +62
const commerceBase = process.env.GUAPO_COMMERCE_INTERNAL_URL?.replace(/\/$/, "");
if (!commerceBase) {
return c.json({ message: "GUAPO_COMMERCE_INTERNAL_URL is not set" }, 500);
}

const secret = process.env.NOTIFY_SHARED_SECRET;
if (!secret) {
return c.json({ message: "NOTIFY_SHARED_SECRET is not set" }, 500);
}

if (body.kind === "inventory") {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Don’t require the commerce URL for inventory-only replay.

The inventory branch never calls commerce, but Line 52 rejects the request when GUAPO_COMMERCE_INTERNAL_URL is unset. This can break the “inventory channel test” deployment check unnecessarily.

🐛 Proposed fix
-    const commerceBase = process.env.GUAPO_COMMERCE_INTERNAL_URL?.replace(/\/$/, "");
-    if (!commerceBase) {
-      return c.json({ message: "GUAPO_COMMERCE_INTERNAL_URL is not set" }, 500);
-    }
-
     const secret = process.env.NOTIFY_SHARED_SECRET;
     if (!secret) {
       return c.json({ message: "NOTIFY_SHARED_SECRET is not set" }, 500);
     }
 
     if (body.kind === "inventory") {
@@
       return c.json({ ok: true, replayed: envelope });
     }
+
+    const commerceBase = process.env.GUAPO_COMMERCE_INTERNAL_URL?.replace(/\/$/, "");
+    if (!commerceBase) {
+      return c.json({ message: "GUAPO_COMMERCE_INTERNAL_URL is not set" }, 500);
+    }
 
     const kindParam = body.kind === "order" ? "order" : "subscription";
📝 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
const commerceBase = process.env.GUAPO_COMMERCE_INTERNAL_URL?.replace(/\/$/, "");
if (!commerceBase) {
return c.json({ message: "GUAPO_COMMERCE_INTERNAL_URL is not set" }, 500);
}
const secret = process.env.NOTIFY_SHARED_SECRET;
if (!secret) {
return c.json({ message: "NOTIFY_SHARED_SECRET is not set" }, 500);
}
if (body.kind === "inventory") {
const secret = process.env.NOTIFY_SHARED_SECRET;
if (!secret) {
return c.json({ message: "NOTIFY_SHARED_SECRET is not set" }, 500);
}
if (body.kind === "inventory") {
return c.json({ ok: true, replayed: envelope });
}
const commerceBase = process.env.GUAPO_COMMERCE_INTERNAL_URL?.replace(/\/$/, "");
if (!commerceBase) {
return c.json({ message: "GUAPO_COMMERCE_INTERNAL_URL is not set" }, 500);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/slack-notify/src/app.ts` around lines 52 - 62, The handler currently
rejects requests when GUAPO_COMMERCE_INTERNAL_URL is missing even for
inventory-only replays; change the validation so commerceBase (from
GUAPO_COMMERCE_INTERNAL_URL -> commerceBase) is only required for non-inventory
paths. Concretely, keep the NOTIFY_SHARED_SECRET/secret check as-is, then either
move the commerceBase resolution/validation below the branch that checks
body.kind === "inventory" or conditionally validate commerceBase only when
body.kind !== "inventory"; ensure you still call c.json for error responses
(same messages) and preserve the inventory branch behavior that does not use
commerceBase.

Comment thread apps/slack-notify/src/blocks/build-messages.ts Outdated
Comment thread apps/slack-notify/src/channels.ts Outdated
Comment thread apps/slack-notify/src/server.ts Outdated
Comment thread apps/slack-notify/src/verify.ts Outdated
- HMAC signing with X-Notify-Timestamp (replay protection) + 5s fetch timeout
- Subscription IDs via indexed SQL listSubscriptionIdsByOrderId
- Renewal Slack in isolated try/catch; payload uses subscriptionIds
- Runtime validation for slack-notify JSON; inventory replay without commerce URL
- Channel ID prefixes CGDU; PORT validation; Block Kit subscriptionId fallback
- Lifecycle notify try/catch; normalize renewal alert detail

Made-with: Cursor
@railway-app railway-app Bot temporarily deployed to guapo / staging April 19, 2026 00:29 Inactive
…tify

Align with CodeRabbit: void notifySubscriptionLifecycleSlack(...).catch(logger).
Simplify helper (no duplicate try/catch vs routes). Add missing logger import on skip route.

Made-with: Cursor
@railway-app railway-app Bot temporarily deployed to guapo / staging April 19, 2026 00:41 Inactive
@railway-app railway-app Bot temporarily deployed to guapo / staging April 19, 2026 00:48 Inactive
@eskobar95 eskobar95 merged commit 17da5b3 into main Apr 19, 2026
13 checks passed
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.

1 participant