Conversation
- 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
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (22)
📝 WalkthroughWalkthroughThis PR introduces a complete Slack notification system by adding a new standalone Changes
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)
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":{...}}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (31)
.gitignoreapps/commerce/docs/DEPLOY-RAILWAY.mdapps/commerce/env.templateapps/commerce/src/api/admin/subscriptions/[id]/cancel/route.tsapps/commerce/src/api/admin/subscriptions/[id]/pause/route.tsapps/commerce/src/api/admin/subscriptions/[id]/resume/route.tsapps/commerce/src/api/internal/notifications/slack/sample/route.tsapps/commerce/src/api/store/subscriptions/[id]/cancel/route.tsapps/commerce/src/api/store/subscriptions/[id]/pause/route.tsapps/commerce/src/api/store/subscriptions/[id]/resume/route.tsapps/commerce/src/api/store/subscriptions/[id]/skip/route.tsapps/commerce/src/lib/create-subscriptions-from-placed-order.tsapps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.tsapps/commerce/src/lib/slack-notify/send-slack-notify.tsapps/commerce/src/lib/slack-notify/types.tsapps/commerce/src/lib/transactional-email/subscription-renewal-notifications.tsapps/commerce/src/subscribers/slack-notify-order-placed.tsapps/commerce/src/workflows/steps/run-subscription-renewal.tsapps/slack-notify/env.templateapps/slack-notify/package.jsonapps/slack-notify/railway.tomlapps/slack-notify/src/app.tsapps/slack-notify/src/blocks/build-messages.tsapps/slack-notify/src/channels.tsapps/slack-notify/src/post-to-slack.tsapps/slack-notify/src/server.tsapps/slack-notify/src/types.tsapps/slack-notify/src/verify.tsapps/slack-notify/tsconfig.jsonenv.exampleopenmemory.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/commerceshould 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 withworkflow(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 andquery.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.tsapps/commerce/src/lib/create-subscriptions-from-placed-order.tsapps/commerce/src/api/store/subscriptions/[id]/cancel/route.tsapps/commerce/src/api/store/subscriptions/[id]/skip/route.tsapps/commerce/src/api/store/subscriptions/[id]/resume/route.tsapps/commerce/src/api/admin/subscriptions/[id]/pause/route.tsapps/commerce/src/api/admin/subscriptions/[id]/resume/route.tsapps/commerce/src/workflows/steps/run-subscription-renewal.tsapps/commerce/src/lib/transactional-email/subscription-renewal-notifications.tsapps/commerce/src/api/admin/subscriptions/[id]/cancel/route.tsapps/commerce/src/subscribers/slack-notify-order-placed.tsapps/commerce/src/lib/slack-notify/send-slack-notify.tsapps/commerce/src/lib/slack-notify/types.tsapps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.tsapps/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 bundlesDefine a type for const functions when possible
**/*.{ts,tsx}: Use interfaces for object shapes in TypeScript; write functional components with typed props
Avoid enums; preferas constobjects combined with union types
Do not useanytype; useunknownwith type narrowing or Zod-inferred types for validated data
Files:
apps/commerce/src/api/store/subscriptions/[id]/pause/route.tsapps/slack-notify/src/server.tsapps/commerce/src/lib/create-subscriptions-from-placed-order.tsapps/commerce/src/api/store/subscriptions/[id]/cancel/route.tsapps/commerce/src/api/store/subscriptions/[id]/skip/route.tsapps/commerce/src/api/store/subscriptions/[id]/resume/route.tsapps/commerce/src/api/admin/subscriptions/[id]/pause/route.tsapps/commerce/src/api/admin/subscriptions/[id]/resume/route.tsapps/commerce/src/workflows/steps/run-subscription-renewal.tsapps/commerce/src/lib/transactional-email/subscription-renewal-notifications.tsapps/commerce/src/api/admin/subscriptions/[id]/cancel/route.tsapps/slack-notify/src/post-to-slack.tsapps/slack-notify/src/blocks/build-messages.tsapps/commerce/src/subscribers/slack-notify-order-placed.tsapps/slack-notify/src/channels.tsapps/commerce/src/lib/slack-notify/send-slack-notify.tsapps/commerce/src/lib/slack-notify/types.tsapps/slack-notify/src/app.tsapps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.tsapps/commerce/src/api/internal/notifications/slack/sample/route.tsapps/slack-notify/src/verify.tsapps/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.tsapps/slack-notify/src/server.tsapps/commerce/src/lib/create-subscriptions-from-placed-order.tsapps/commerce/src/api/store/subscriptions/[id]/cancel/route.tsapps/commerce/src/api/store/subscriptions/[id]/skip/route.tsapps/commerce/src/api/store/subscriptions/[id]/resume/route.tsapps/commerce/src/api/admin/subscriptions/[id]/pause/route.tsapps/commerce/src/api/admin/subscriptions/[id]/resume/route.tsapps/commerce/src/workflows/steps/run-subscription-renewal.tsapps/commerce/src/lib/transactional-email/subscription-renewal-notifications.tsapps/commerce/src/api/admin/subscriptions/[id]/cancel/route.tsapps/slack-notify/src/post-to-slack.tsapps/slack-notify/src/blocks/build-messages.tsapps/commerce/src/subscribers/slack-notify-order-placed.tsapps/slack-notify/src/channels.tsapps/commerce/src/lib/slack-notify/send-slack-notify.tsapps/commerce/src/lib/slack-notify/types.tsapps/slack-notify/src/app.tsapps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.tsapps/commerce/src/api/internal/notifications/slack/sample/route.tsapps/slack-notify/src/verify.tsapps/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 likeisLoading,hasError
Use thefunctionkeyword for pure functions; write declarative JSX
Files:
apps/commerce/src/api/store/subscriptions/[id]/pause/route.tsapps/slack-notify/src/server.tsapps/commerce/src/lib/create-subscriptions-from-placed-order.tsapps/commerce/src/api/store/subscriptions/[id]/cancel/route.tsapps/commerce/src/api/store/subscriptions/[id]/skip/route.tsapps/commerce/src/api/store/subscriptions/[id]/resume/route.tsapps/commerce/src/api/admin/subscriptions/[id]/pause/route.tsapps/commerce/src/api/admin/subscriptions/[id]/resume/route.tsapps/commerce/src/workflows/steps/run-subscription-renewal.tsapps/commerce/src/lib/transactional-email/subscription-renewal-notifications.tsapps/commerce/src/api/admin/subscriptions/[id]/cancel/route.tsapps/slack-notify/src/post-to-slack.tsapps/slack-notify/src/blocks/build-messages.tsapps/commerce/src/subscribers/slack-notify-order-placed.tsapps/slack-notify/src/channels.tsapps/commerce/src/lib/slack-notify/send-slack-notify.tsapps/commerce/src/lib/slack-notify/types.tsapps/slack-notify/src/app.tsapps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.tsapps/commerce/src/api/internal/notifications/slack/sample/route.tsapps/slack-notify/src/verify.tsapps/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. Followcode-structure.mdcfor 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
Usenext/dynamicfor heavy or below-the-fold components
Usenuqslibrary 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 usingReact.cache()
Files:
apps/commerce/src/api/store/subscriptions/[id]/pause/route.tsapps/slack-notify/src/server.tsapps/commerce/src/lib/create-subscriptions-from-placed-order.tsapps/commerce/src/api/store/subscriptions/[id]/cancel/route.tsapps/commerce/src/api/store/subscriptions/[id]/skip/route.tsapps/commerce/src/api/store/subscriptions/[id]/resume/route.tsapps/commerce/src/api/admin/subscriptions/[id]/pause/route.tsapps/commerce/src/api/admin/subscriptions/[id]/resume/route.tsapps/commerce/src/workflows/steps/run-subscription-renewal.tsapps/commerce/src/lib/transactional-email/subscription-renewal-notifications.tsapps/commerce/src/api/admin/subscriptions/[id]/cancel/route.tsapps/slack-notify/src/post-to-slack.tsapps/slack-notify/src/blocks/build-messages.tsapps/commerce/src/subscribers/slack-notify-order-placed.tsapps/slack-notify/src/channels.tsapps/commerce/src/lib/slack-notify/send-slack-notify.tsapps/commerce/src/lib/slack-notify/types.tsapps/slack-notify/src/app.tsapps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.tsapps/commerce/src/api/internal/notifications/slack/sample/route.tsapps/slack-notify/src/verify.tsapps/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.tsapps/slack-notify/src/server.tsapps/commerce/src/lib/create-subscriptions-from-placed-order.tsapps/commerce/src/api/store/subscriptions/[id]/cancel/route.tsapps/commerce/src/api/store/subscriptions/[id]/skip/route.tsapps/commerce/src/api/store/subscriptions/[id]/resume/route.tsapps/commerce/src/api/admin/subscriptions/[id]/pause/route.tsapps/commerce/src/api/admin/subscriptions/[id]/resume/route.tsapps/commerce/src/workflows/steps/run-subscription-renewal.tsapps/commerce/src/lib/transactional-email/subscription-renewal-notifications.tsapps/commerce/src/api/admin/subscriptions/[id]/cancel/route.tsapps/slack-notify/src/post-to-slack.tsapps/slack-notify/src/blocks/build-messages.tsapps/commerce/src/subscribers/slack-notify-order-placed.tsapps/slack-notify/src/channels.tsapps/commerce/src/lib/slack-notify/send-slack-notify.tsapps/commerce/src/lib/slack-notify/types.tsapps/slack-notify/src/app.tsapps/commerce/src/lib/slack-notify/notify-subscription-lifecycle.tsapps/commerce/src/api/internal/notifications/slack/sample/route.tsapps/slack-notify/src/verify.tsapps/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.tsapps/commerce/src/api/store/subscriptions/[id]/cancel/route.tsapps/commerce/src/api/store/subscriptions/[id]/skip/route.tsapps/commerce/src/api/store/subscriptions/[id]/resume/route.tsapps/commerce/src/api/admin/subscriptions/[id]/pause/route.tsapps/commerce/src/api/admin/subscriptions/[id]/resume/route.tsapps/commerce/src/api/admin/subscriptions/[id]/cancel/route.tsapps/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.tsapps/commerce/src/lib/transactional-email/subscription-renewal-notifications.tsapps/commerce/src/lib/slack-notify/send-slack-notify.tsapps/commerce/src/lib/slack-notify/types.tsapps/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.tsopenmemory.mdapps/commerce/docs/DEPLOY-RAILWAY.mdapps/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.jsonapps/slack-notify/src/blocks/build-messages.tsapps/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.mdapps/commerce/docs/DEPLOY-RAILWAY.mdapps/commerce/src/subscribers/slack-notify-order-placed.tsapps/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.mdapps/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
/healthcheck 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.
notifySubscriptionLifecycleSlackdelegates tosendSlackNotify, which wraps the fetch call in a try-catch and logs all failures vialogger.warn(). The function never rejects; it gracefully handles Slack/network failures and returns a resolved Promise. Thevoidpattern 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: ThesendSlackNotifycall at this location is safe and does not require additional error handling. ThesendSlackNotifyfunction already implements comprehensive error handling with a try-catch block that captures network failures and HTTP error responses, logging them vialogger.warn(). The promise never rejects—it resolves successfully in all code paths. Thevoidprefix 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 usesaction: "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
srcand excludes generateddistoutput.apps/slack-notify/package.json (1)
13-20: Lockfile already pins these dependencies.The
pnpm-lock.yamlincludes and resolves all slack-notify dependencies to exact versions (@hono/node-serverto 1.19.12,@slack/web-apito 7.15.1, etc.), ensuring reproducible installs. The caret ranges inpackage.jsonare acceptable given the committed lockfile.
| 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") { |
There was a problem hiding this comment.
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.
| 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.
- 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
…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
Summary
/v1/eventsog/v1/test/replay).order.placed-subscriber, subscription/renewal/alerts og lifecycle-routes.Test plan
pnpm -C apps/slack-notify buildpnpm -C apps/commerce buildNOTIFY_SHARED_SECRET+SLACK_NOTIFY_URLpå server/worker), test/healthog replay-endpoint.Notes / risks
apps/slack-notifyog env på eksisterende Medusa-services..cursor/settings.jsoner ikke med i denne commit (lokal Stripe-plugin toggle).Made with Cursor
Summary by CodeRabbit
Release Notes
New Features
Configuration
Infrastructure