Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR introduces ChangesDodo Payment Provider
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly Related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/content/docs/providers/dodo.mdx`:
- Line 16: Hyphenate the compound modifier "Dodo hosted checkout" to
"Dodo-hosted checkout" so the sentence reads "...the Dodo-hosted checkout can
still offer Pix inside the redirect flow."; update the phrase wherever "Dodo
hosted checkout" appears (search for the exact string) to use the hyphenated
form.
In `@apps/web/public/llms.txt`:
- Around line 169-187: The Providers quick-link list in llms.txt is missing the
Dodo provider entry; add a link to `/docs/providers/dodo` in the Providers
quick-link section so the newly-live "Dodo" entry (the block starting "### Dodo"
that lists `@paymesh/dodo` features) is discoverable and the file is internally
consistent—update the Providers quick-link list to include
`/docs/providers/dodo` alongside the other provider paths.
In `@apps/web/src/lib/docs.tsx`:
- Around line 3881-3884: The displayed snippet uses an unsupported delete call;
update the customerSnippet used by the DocCodeBlock (the variable
customerSnippet rendered in DocCodeBlock with filename
"src/server/customers.ts") so it does not call customers.delete(); instead
replace that line with a supported operation (e.g., show
customers.get/customers.update or just the creation example) or remove the
delete call entirely so the rendered example matches the docs note that delete
is unsupported.
In `@packages/dodo/src/index.ts`:
- Around line 54-65: The dodo factory currently leaves baseUrl pointing at
DODO_LIVE_BASE_URL when sandbox: true because sandbox only affects
resolveProviderSandbox(); update the initialization so the effective base URL
respects sandbox: change the logic in the dodo function (symbols: dodo,
resolveProviderSandbox) to compute an effectiveBaseUrl (or set baseUrl) using:
if sandbox === true and baseUrl was not explicitly provided, use
DODO_TEST_BASE_URL; otherwise keep the provided baseUrl or existing default;
ensure resolveProviderSandbox continues to return the correct boolean but uses
the same sandbox input and the final network requests/readable metadata use the
effectiveBaseUrl.
- Around line 55-69: The code builds the Authorization header even when apiKey
is undefined, causing "Bearer undefined" to be sent; add a validation at the
start of the provider factory (where apiKey is read from DodoProviderOptions) to
throw a clear error when apiKey is missing or empty, and move/guard the headers
construction so it only occurs after the apiKey check; reference the apiKey
variable, the headers object, and the provider factory function parameters to
locate and fix the issue.
In `@packages/dodo/src/shared/mapper.ts`:
- Around line 156-164: The returned price object can have currency or amount as
undefined (see id `${product.product_id}_price`, currency computed from
price?.currency ?? product.currency, and amount from price?.price ??
product.price), so add a guard in the mapper (before returning) that validates
both currency and amount are present and non-null/undefined (e.g., normalize
currency to string and amount to number); if either is missing, skip emitting
this price (return null/undefined) or filter it out upstream so you never return
an object with undefined currency/amount. Ensure the mapper's callers handle the
nullable return or the mapper filters invalid products out of result arrays.
In `@packages/dodo/src/shared/sync.ts`:
- Around line 96-97: The event object currently uses the caller-provided id
variable for identity; change it to use the provider's canonical ID by replacing
id with subscription.subscription_id (i.e., set event.id =
subscription.subscription_id) so the event identity comes from
subscription.subscription_id rather than the request input id to avoid drift and
maintain canonical identity.
In `@packages/dodo/src/shared/utils.ts`:
- Around line 165-173: The ensureDodoProductIds function currently only checks
array presence/length and lets blank strings through; update
ensureDodoProductIds to iterate over productIds, trim each id and throw the same
PaymeshError if any id is empty after trimming (reject values like '' or ' '),
and replace/augment the later validation site that forwards product_id (the code
around the cart construction) to either call ensureDodoProductIds or perform the
same trim-and-nonempty check so no blank IDs are forwarded.
In `@packages/dodo/src/shared/webhooks.ts`:
- Around line 56-58: The current resolveDodoWebhookType function silently
coerces unknown types to 'payment.created'; change it to fail fast by checking
DODO_EVENTS[type] and if missing throw a descriptive Error (e.g., `Unknown Dodo
webhook type: ${type}`) instead of returning a default, so callers of
resolveDodoWebhookType will receive an explicit failure for unsupported event
types; update resolveDodoWebhookType (and any tests that expect the old
fallback) accordingly.
In `@packages/dodo/test/customers.test.ts`:
- Around line 110-117: The test is asserting that requests[2]?.body contains
keys with undefined values, but JSON won't preserve undefined; update the
assertion on requests[2]?.body to expect only the actual serialized fields —
e.g., assert that requests[2]?.body equals { name: 'Ana Silva', metadata: {
plan: 'business' } } or use expect.objectContaining(...) and explicitly assert
that 'email' and 'phone_number' are not present (e.g.,
expect(requests[2]?.body).not.toHaveProperty('email')). Modify the assertion
around requests[2]?.body in customers.test.ts accordingly so it does not include
undefined-valued keys.
In `@packages/dodo/test/dodo.test.ts`:
- Around line 192-227: The test uses promise rejection assertions on
provider.payments.create but doesn't await them, so update each assertion that
chains .rejects.toMatchObject (the three provider.payments.create calls) to be
awaited (e.g., await
expect(provider.payments.create(...)).rejects.toMatchObject(...)) or return the
promise from the test; specifically change the three occurrences that start with
expect(provider.payments.create({...})).rejects.toMatchObject(...) to use await
expect(...) to ensure the rejections are actually asserted.
In `@packages/dodo/test/webhooks.test.ts`:
- Around line 74-129: The tests call provider.webhooks?.handle with raw unsigned
Request objects while webhook secret verification is enabled; update the two
webhook requests (the PIX case and the subscription case passed to
provider.webhooks?.handle) to be created via dodoWebhookRequest(...) so they
include valid signature headers (or alternatively add the correct signature
headers manually) before asserting successful handling; specifically replace the
new Request(...) bodies in the pix test and the subscription test with calls to
dodoWebhookRequest(payload, { secret: webhookSecret }) or equivalent so
provider.webhooks?.handle receives signed requests.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2b780a11-a4af-4259-b0f9-f9e66f99b0c5
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (34)
.changeset/cyan-poems-boil.mdREADME.mdapps/web/.source/browser.tsapps/web/.source/server.tsapps/web/content/docs/basic-usage.mdxapps/web/content/docs/concepts/payment-providers.mdxapps/web/content/docs/concepts/pix.mdxapps/web/content/docs/installation.mdxapps/web/content/docs/introduction.mdxapps/web/content/docs/providers/dodo.mdxapps/web/public/llms.txtapps/web/src/app/page.tsxapps/web/src/components/docs/mdx-components.tsxapps/web/src/lib/docs-navigation.tsapps/web/src/lib/docs.tsxpackage.jsonpackages/cli/src/shared/providers.tspackages/dodo/README.mdpackages/dodo/package.jsonpackages/dodo/src/index.tspackages/dodo/src/shared/constants.tspackages/dodo/src/shared/mapper.tspackages/dodo/src/shared/sync.tspackages/dodo/src/shared/utils.tspackages/dodo/src/shared/webhooks.tspackages/dodo/src/types.tspackages/dodo/test/catalog.test.tspackages/dodo/test/customers.test.tspackages/dodo/test/dashboard.test.tspackages/dodo/test/dodo.test.tspackages/dodo/test/webhooks.test.tspackages/dodo/tsconfig.jsonpackages/dodo/tsdown.config.mjspackages/paymesh/README.md
| ### Dodo | ||
|
|
||
| Package: `@paymesh/dodo` | ||
|
|
||
| Implemented capabilities: | ||
|
|
||
| - checkout payments (catalog-driven with product IDs) | ||
| - customers | ||
| - webhooks | ||
| - subscriptions | ||
| - catalog reads | ||
| - dashboard sync helpers for customers, payments, and subscriptions | ||
|
|
||
| Important limitations: | ||
|
|
||
| - `@paymesh/dodo` currently advertises `pix: false` | ||
| - Dodo can expose Pix inside BRL hosted checkout, but not through `paymesh.pix` | ||
| - customer delete is intentionally unsupported | ||
|
|
There was a problem hiding this comment.
Add Dodo to the provider entrypoint links to keep llms.txt internally consistent.
Line 169 onward marks Dodo as live, but the Providers quick-link list later still omits /docs/providers/dodo. This weakens discoverability for systems consuming llms.txt.
💡 Proposed fix
### Providers
- [Stripe](/docs/providers/stripe)
- [Polar](/docs/providers/polar)
- [AbacatePay](/docs/providers/abacatepay)
+- [Dodo](/docs/providers/dodo)🧰 Tools
🪛 LanguageTool
[grammar] ~185-~185: Use a hyphen to join words.
Context: ... false- Dodo can expose Pix inside BRL hosted checkout, but not throughpaymes...
(QB_NEW_EN_HYPHEN)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/public/llms.txt` around lines 169 - 187, The Providers quick-link
list in llms.txt is missing the Dodo provider entry; add a link to
`/docs/providers/dodo` in the Providers quick-link section so the newly-live
"Dodo" entry (the block starting "### Dodo" that lists `@paymesh/dodo` features)
is discoverable and the file is internally consistent—update the Providers
quick-link list to include `/docs/providers/dodo` alongside the other provider
paths.
| <DocCodeBlock | ||
| code={customerSnippet} | ||
| filename="src/server/customers.ts" | ||
| /> |
There was a problem hiding this comment.
Use a Dodo-specific customer snippet that does not call customers.delete().
Line 3878 states delete is unsupported, but the snippet at Lines 3881-3884 currently shows a delete call. This creates a copy/paste failure path for users.
💡 Proposed fix
+const dodoCustomersSnippet = `const customer = await client.customers.upsert({
+ email: "billing@example.com",
+ externalId: "order_42",
+ name: "Billing Team",
+ metadata: {
+ orderId: "order_42",
+ },
+});
+
+const fetched = await client.customers.get(customer.id);`;- <DocCodeBlock
- code={customerSnippet}
- filename="src/server/customers.ts"
- />
+ <DocCodeBlock
+ code={dodoCustomersSnippet}
+ filename="src/server/customers.ts"
+ />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/lib/docs.tsx` around lines 3881 - 3884, The displayed snippet
uses an unsupported delete call; update the customerSnippet used by the
DocCodeBlock (the variable customerSnippet rendered in DocCodeBlock with
filename "src/server/customers.ts") so it does not call customers.delete();
instead replace that line with a supported operation (e.g., show
customers.get/customers.update or just the creation example) or remove the
delete call entirely so the rendered example matches the docs note that delete
is unsupported.
| export const dodo = ({ | ||
| apiKey = process.env.DODO_PAYMENTS_API_KEY, | ||
| webhookSecret = process.env.DODO_PAYMENTS_WEBHOOK_KEY, | ||
| baseUrl = process.env.DODO_PAYMENTS_BASE_URL ?? DODO_LIVE_BASE_URL, | ||
| sandbox, | ||
| retry, | ||
| timeout, | ||
| fetch, | ||
| }: DodoProviderOptions = {}) => { | ||
| const resolveProviderSandbox = () => | ||
| typeof sandbox === 'boolean' ? sandbox : baseUrl === DODO_TEST_BASE_URL; | ||
|
|
There was a problem hiding this comment.
Sandbox mode can still send traffic to live Dodo endpoints.
At Line 57 + Line 63-64, sandbox only affects isSandbox() metadata; it does not switch baseUrl. With sandbox: true and no baseUrl, calls still go to https://live.dodopayments.com.
Suggested fix
export const dodo = ({
apiKey = process.env.DODO_PAYMENTS_API_KEY,
webhookSecret = process.env.DODO_PAYMENTS_WEBHOOK_KEY,
- baseUrl = process.env.DODO_PAYMENTS_BASE_URL ?? DODO_LIVE_BASE_URL,
+ baseUrl = process.env.DODO_PAYMENTS_BASE_URL,
sandbox,
retry,
timeout,
fetch,
}: DodoProviderOptions = {}) => {
- const resolveProviderSandbox = () =>
- typeof sandbox === 'boolean' ? sandbox : baseUrl === DODO_TEST_BASE_URL;
+ const resolvedSandbox =
+ typeof sandbox === 'boolean' ? sandbox : baseUrl === DODO_TEST_BASE_URL;
+ const resolvedBaseUrl =
+ baseUrl ?? (resolvedSandbox ? DODO_TEST_BASE_URL : DODO_LIVE_BASE_URL);
+
+ const resolveProviderSandbox = () => resolvedSandbox;
const headers = {
authorization: `Bearer ${apiKey}`,
'content-type': 'application/json',
};
const baseRequestOptions = {
- baseUrl,
+ baseUrl: resolvedBaseUrl,
fetch,
headers,
retry,
timeout,
};📝 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.
| export const dodo = ({ | |
| apiKey = process.env.DODO_PAYMENTS_API_KEY, | |
| webhookSecret = process.env.DODO_PAYMENTS_WEBHOOK_KEY, | |
| baseUrl = process.env.DODO_PAYMENTS_BASE_URL ?? DODO_LIVE_BASE_URL, | |
| sandbox, | |
| retry, | |
| timeout, | |
| fetch, | |
| }: DodoProviderOptions = {}) => { | |
| const resolveProviderSandbox = () => | |
| typeof sandbox === 'boolean' ? sandbox : baseUrl === DODO_TEST_BASE_URL; | |
| export const dodo = ({ | |
| apiKey = process.env.DODO_PAYMENTS_API_KEY, | |
| webhookSecret = process.env.DODO_PAYMENTS_WEBHOOK_KEY, | |
| baseUrl = process.env.DODO_PAYMENTS_BASE_URL, | |
| sandbox, | |
| retry, | |
| timeout, | |
| fetch, | |
| }: DodoProviderOptions = {}) => { | |
| const resolvedSandbox = | |
| typeof sandbox === 'boolean' ? sandbox : baseUrl === DODO_TEST_BASE_URL; | |
| const resolvedBaseUrl = | |
| baseUrl ?? (resolvedSandbox ? DODO_TEST_BASE_URL : DODO_LIVE_BASE_URL); | |
| const resolveProviderSandbox = () => resolvedSandbox; | |
| const headers = { | |
| authorization: `Bearer ${apiKey}`, | |
| 'content-type': 'application/json', | |
| }; | |
| const baseRequestOptions = { | |
| baseUrl: resolvedBaseUrl, | |
| fetch, | |
| headers, | |
| retry, | |
| timeout, | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/dodo/src/index.ts` around lines 54 - 65, The dodo factory currently
leaves baseUrl pointing at DODO_LIVE_BASE_URL when sandbox: true because sandbox
only affects resolveProviderSandbox(); update the initialization so the
effective base URL respects sandbox: change the logic in the dodo function
(symbols: dodo, resolveProviderSandbox) to compute an effectiveBaseUrl (or set
baseUrl) using: if sandbox === true and baseUrl was not explicitly provided, use
DODO_TEST_BASE_URL; otherwise keep the provided baseUrl or existing default;
ensure resolveProviderSandbox continues to return the correct boolean but uses
the same sandbox input and the final network requests/readable metadata use the
effectiveBaseUrl.
| apiKey = process.env.DODO_PAYMENTS_API_KEY, | ||
| webhookSecret = process.env.DODO_PAYMENTS_WEBHOOK_KEY, | ||
| baseUrl = process.env.DODO_PAYMENTS_BASE_URL ?? DODO_LIVE_BASE_URL, | ||
| sandbox, | ||
| retry, | ||
| timeout, | ||
| fetch, | ||
| }: DodoProviderOptions = {}) => { | ||
| const resolveProviderSandbox = () => | ||
| typeof sandbox === 'boolean' ? sandbox : baseUrl === DODO_TEST_BASE_URL; | ||
|
|
||
| const headers = { | ||
| authorization: `Bearer ${apiKey}`, | ||
| 'content-type': 'application/json', | ||
| }; |
There was a problem hiding this comment.
Missing API key validation yields Authorization: Bearer undefined.
At Line 67, headers are built even when apiKey is unset. This silently sends malformed auth and fails later with less actionable errors.
Suggested fix
}: DodoProviderOptions = {}) => {
+ if (!apiKey) {
+ throw new PaymeshError({
+ code: 'invalid_request',
+ message:
+ 'Provider "dodo" requires an API key. Set "apiKey" or DODO_PAYMENTS_API_KEY.',
+ provider: 'dodo',
+ });
+ }
+
const resolveProviderSandbox = () =>
typeof sandbox === 'boolean' ? sandbox : baseUrl === DODO_TEST_BASE_URL;📝 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.
| apiKey = process.env.DODO_PAYMENTS_API_KEY, | |
| webhookSecret = process.env.DODO_PAYMENTS_WEBHOOK_KEY, | |
| baseUrl = process.env.DODO_PAYMENTS_BASE_URL ?? DODO_LIVE_BASE_URL, | |
| sandbox, | |
| retry, | |
| timeout, | |
| fetch, | |
| }: DodoProviderOptions = {}) => { | |
| const resolveProviderSandbox = () => | |
| typeof sandbox === 'boolean' ? sandbox : baseUrl === DODO_TEST_BASE_URL; | |
| const headers = { | |
| authorization: `Bearer ${apiKey}`, | |
| 'content-type': 'application/json', | |
| }; | |
| apiKey = process.env.DODO_PAYMENTS_API_KEY, | |
| webhookSecret = process.env.DODO_PAYMENTS_WEBHOOK_KEY, | |
| baseUrl = process.env.DODO_PAYMENTS_BASE_URL ?? DODO_LIVE_BASE_URL, | |
| sandbox, | |
| retry, | |
| timeout, | |
| fetch, | |
| }: DodoProviderOptions = {}) => { | |
| if (!apiKey) { | |
| throw new PaymeshError({ | |
| code: 'invalid_request', | |
| message: | |
| 'Provider "dodo" requires an API key. Set "apiKey" or DODO_PAYMENTS_API_KEY.', | |
| provider: 'dodo', | |
| }); | |
| } | |
| const resolveProviderSandbox = () => | |
| typeof sandbox === 'boolean' ? sandbox : baseUrl === DODO_TEST_BASE_URL; | |
| const headers = { | |
| authorization: `Bearer ${apiKey}`, | |
| 'content-type': 'application/json', | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/dodo/src/index.ts` around lines 55 - 69, The code builds the
Authorization header even when apiKey is undefined, causing "Bearer undefined"
to be sent; add a validation at the start of the provider factory (where apiKey
is read from DodoProviderOptions) to throw a clear error when apiKey is missing
or empty, and move/guard the headers construction so it only occurs after the
apiKey check; reference the apiKey variable, the headers object, and the
provider factory function parameters to locate and fix the issue.
| export function ensureDodoProductIds(productIds?: string[]) { | ||
| if (!productIds || productIds.length === 0) { | ||
| throw new PaymeshError({ | ||
| code: 'invalid_request', | ||
| message: | ||
| 'Provider "dodo" requires at least one product id in "productIds"', | ||
| provider: 'dodo', | ||
| }); | ||
| } |
There was a problem hiding this comment.
Reject empty/whitespace productIds values.
Line 166 only validates array presence/length; [''] and [' '] still pass and then Line 192 forwards invalid product_id values. Tighten validation to reject blank IDs before cart construction.
Suggested fix
export function ensureDodoProductIds(productIds?: string[]) {
- if (!productIds || productIds.length === 0) {
+ if (
+ !productIds ||
+ productIds.length === 0 ||
+ productIds.some((id) => id.trim().length === 0)
+ ) {
throw new PaymeshError({
code: 'invalid_request',
message:
'Provider "dodo" requires at least one product id in "productIds"',
provider: 'dodo',
});
}
}Also applies to: 191-193
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/dodo/src/shared/utils.ts` around lines 165 - 173, The
ensureDodoProductIds function currently only checks array presence/length and
lets blank strings through; update ensureDodoProductIds to iterate over
productIds, trim each id and throw the same PaymeshError if any id is empty
after trimming (reject values like '' or ' '), and replace/augment the later
validation site that forwards product_id (the code around the cart construction)
to either call ensureDodoProductIds or perform the same trim-and-nonempty check
so no blank IDs are forwarded.
| export function resolveDodoWebhookType(type: string): PaymeshEventType { | ||
| return DODO_EVENTS[type] ?? 'payment.created'; | ||
| } |
There was a problem hiding this comment.
Unknown Dodo webhook types are silently coerced to payment.created.
At Line 57, the fallback masks unsupported event types and can trigger incorrect downstream handlers/events instead of failing fast.
Suggested fix
+import { PaymeshError } from 'paymesh';
import type { PaymeshEventType } from 'paymesh';
...
export function resolveDodoWebhookType(type: string): PaymeshEventType {
- return DODO_EVENTS[type] ?? 'payment.created';
+ const resolved = DODO_EVENTS[type];
+ if (!resolved) {
+ throw new PaymeshError({
+ code: 'webhook_parse_error',
+ message: `Unsupported Dodo webhook event type: "${type}"`,
+ provider: 'dodo',
+ });
+ }
+ return resolved;
}📝 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.
| export function resolveDodoWebhookType(type: string): PaymeshEventType { | |
| return DODO_EVENTS[type] ?? 'payment.created'; | |
| } | |
| export function resolveDodoWebhookType(type: string): PaymeshEventType { | |
| const resolved = DODO_EVENTS[type]; | |
| if (!resolved) { | |
| throw new PaymeshError({ | |
| code: 'webhook_parse_error', | |
| message: `Unsupported Dodo webhook event type: "${type}"`, | |
| provider: 'dodo', | |
| }); | |
| } | |
| return resolved; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/dodo/src/shared/webhooks.ts` around lines 56 - 58, The current
resolveDodoWebhookType function silently coerces unknown types to
'payment.created'; change it to fail fast by checking DODO_EVENTS[type] and if
missing throw a descriptive Error (e.g., `Unknown Dodo webhook type: ${type}`)
instead of returning a default, so callers of resolveDodoWebhookType will
receive an explicit failure for unsupported event types; update
resolveDodoWebhookType (and any tests that expect the old fallback) accordingly.
| expect(requests[2]?.body).toEqual({ | ||
| email: undefined, | ||
| name: 'Ana Silva', | ||
| phone_number: undefined, | ||
| metadata: { | ||
| plan: 'business', | ||
| }, | ||
| }); |
There was a problem hiding this comment.
PATCH request body expectation includes undefined fields that JSON cannot preserve.
At Line 110-117, parsed JSON body should not contain email or phone_number keys when values are undefined; this assertion can fail despite correct runtime behavior.
Suggested fix
expect(requests[2]?.body).toEqual({
- email: undefined,
name: 'Ana Silva',
- phone_number: undefined,
metadata: {
plan: 'business',
},
});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/dodo/test/customers.test.ts` around lines 110 - 117, The test is
asserting that requests[2]?.body contains keys with undefined values, but JSON
won't preserve undefined; update the assertion on requests[2]?.body to expect
only the actual serialized fields — e.g., assert that requests[2]?.body equals {
name: 'Ana Silva', metadata: { plan: 'business' } } or use
expect.objectContaining(...) and explicitly assert that 'email' and
'phone_number' are not present (e.g.,
expect(requests[2]?.body).not.toHaveProperty('email')). Modify the assertion
around requests[2]?.body in customers.test.ts accordingly so it does not include
undefined-valued keys.
| expect( | ||
| provider.payments.create({ | ||
| customer: { | ||
| email: 'ana@example.com', | ||
| }, | ||
| }), | ||
| ).rejects.toMatchObject({ | ||
| code: 'invalid_request', | ||
| message: | ||
| 'Provider "dodo" requires at least one product id in "productIds"', | ||
| }); | ||
|
|
||
| expect( | ||
| provider.payments.create({ | ||
| productIds: ['prod_1', 'prod_2'], | ||
| amount: 1000, | ||
| customer: { | ||
| email: 'ana@example.com', | ||
| }, | ||
| }), | ||
| ).rejects.toMatchObject({ | ||
| code: 'invalid_request', | ||
| message: | ||
| 'Provider "dodo" only accepts "amount" when exactly one product id is provided.', | ||
| }); | ||
|
|
||
| expect( | ||
| provider.payments.create({ | ||
| productIds: ['prod_123'], | ||
| }), | ||
| ).rejects.toMatchObject({ | ||
| code: 'invalid_request', | ||
| message: | ||
| 'Provider "dodo" requires either "customer.id" or "customer.email" when creating payments.', | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Rejected-promise assertions are not awaited.
At Line 192, Line 204, and Line 218, .rejects chains must be awaited (or returned) or the test can pass without actually asserting rejection.
Suggested fix
- expect(
+ await expect(
provider.payments.create({
customer: {
email: 'ana@example.com',
},
}),
).rejects.toMatchObject({
code: 'invalid_request',
message:
'Provider "dodo" requires at least one product id in "productIds"',
});
- expect(
+ await expect(
provider.payments.create({
productIds: ['prod_1', 'prod_2'],
amount: 1000,
customer: {
email: 'ana@example.com',
},
}),
).rejects.toMatchObject({
code: 'invalid_request',
message:
'Provider "dodo" only accepts "amount" when exactly one product id is provided.',
});
- expect(
+ await expect(
provider.payments.create({
productIds: ['prod_123'],
}),
).rejects.toMatchObject({
code: 'invalid_request',
message:
'Provider "dodo" requires either "customer.id" or "customer.email" when creating payments.',
});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/dodo/test/dodo.test.ts` around lines 192 - 227, The test uses
promise rejection assertions on provider.payments.create but doesn't await them,
so update each assertion that chains .rejects.toMatchObject (the three
provider.payments.create calls) to be awaited (e.g., await
expect(provider.payments.create(...)).rejects.toMatchObject(...)) or return the
promise from the test; specifically change the three occurrences that start with
expect(provider.payments.create({...})).rejects.toMatchObject(...) to use await
expect(...) to ensure the rejections are actually asserted.
| const pixHandled = await provider.webhooks?.handle({ | ||
| request: new Request('https://app.test/webhooks', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'content-type': 'application/json', | ||
| 'webhook-id': 'msg_pix', | ||
| }, | ||
| body: JSON.stringify({ | ||
| business_id: 'biz_123', | ||
| type: 'payment.processing', | ||
| timestamp: new Date().toISOString(), | ||
| data: { | ||
| payment_id: 'pay_pix_123', | ||
| total_amount: 3100, | ||
| currency: 'BRL', | ||
| customer: { | ||
| customer_id: 'cus_123', | ||
| email: 'ana@example.com', | ||
| name: 'Ana', | ||
| }, | ||
| metadata: {}, | ||
| payment_link: 'https://pay.dodo.test/pay_pix_123', | ||
| payment_method_type: 'pix', | ||
| status: 'processing', | ||
| }, | ||
| }), | ||
| }), | ||
| }); | ||
| const subscriptionHandled = await provider.webhooks?.handle({ | ||
| request: new Request('https://app.test/webhooks', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'content-type': 'application/json', | ||
| 'webhook-id': 'msg_sub', | ||
| }, | ||
| body: JSON.stringify({ | ||
| business_id: 'biz_123', | ||
| type: 'subscription.cancelled', | ||
| timestamp: new Date().toISOString(), | ||
| data: { | ||
| subscription_id: 'sub_123', | ||
| product_id: 'prod_123', | ||
| recurring_pre_tax_amount: 3900, | ||
| currency: 'USD', | ||
| status: 'cancelled', | ||
| cancel_at_next_billing_date: true, | ||
| customer: { | ||
| customer_id: 'cus_123', | ||
| email: 'ana@example.com', | ||
| name: 'Ana', | ||
| }, | ||
| metadata: {}, | ||
| }, | ||
| }), | ||
| }), | ||
| }); |
There was a problem hiding this comment.
Avoid testing successful webhook handling with unsigned requests when webhookSecret is configured.
Line 74 and Line 102 currently assert successful handling for unsigned requests despite secret-based verification being enabled. This weakens security guarantees in tests and can mask signature-enforcement regressions. Build these requests with dodoWebhookRequest(...) (or include valid signature headers) before expecting successful handle.
Suggested direction
- const pixHandled = await provider.webhooks?.handle({
- request: new Request('https://app.test/webhooks', {
- method: 'POST',
- headers: {
- 'content-type': 'application/json',
- 'webhook-id': 'msg_pix',
- },
- body: JSON.stringify({ ... }),
- }),
- });
+ const pixHandled = await provider.webhooks?.handle({
+ request: dodoWebhookRequest(
+ { ...same payload... },
+ 'whsec_c2VjcmV0X2Rlc2lnbg==',
+ ),
+ });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/dodo/test/webhooks.test.ts` around lines 74 - 129, The tests call
provider.webhooks?.handle with raw unsigned Request objects while webhook secret
verification is enabled; update the two webhook requests (the PIX case and the
subscription case passed to provider.webhooks?.handle) to be created via
dodoWebhookRequest(...) so they include valid signature headers (or
alternatively add the correct signature headers manually) before asserting
successful handling; specifically replace the new Request(...) bodies in the pix
test and the subscription test with calls to dodoWebhookRequest(payload, {
secret: webhookSecret }) or equivalent so provider.webhooks?.handle receives
signed requests.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Summary by CodeRabbit
pix: false).