Skip to content

fix(api): PMNT-121 Return 400 when connector cannot create account #473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 19, 2025

Conversation

Quentin-David-24
Copy link
Contributor

@Quentin-David-24 Quentin-David-24 commented Jun 18, 2025

Now the http resbody looks like this:

{
  "errorCode": "CONNECTOR_CAPABILITY_NOT_SUPPORTED",
  "errorMessage": "CreateFormanceAccount capability is not supported by the provider stripe. Check here the supported features: https://docs.formance.com/payments/connectors/#supported-processors"
}

@Quentin-David-24 Quentin-David-24 requested a review from a team as a code owner June 18, 2025 11:42
Copy link
Contributor

coderabbitai bot commented Jun 18, 2025

Walkthrough

A new typed error ErrConnectorCapabilityNotSupported was introduced in the engine and API layers to represent unsupported connector capabilities. The error handling in the API was updated to return HTTP 400 for this error. Corresponding test coverage was added for this scenario in the account and payment creation services and APIs.

Changes

File(s) Change Summary
internal/connectors/engine/errors.go Added ErrConnectorCapabilityNotSupported struct error type with Error() method.
internal/connectors/engine/engine.go Updated CreateFormanceAccount and CreateFormancePayment to return typed ErrConnectorCapabilityNotSupported when capability missing.
internal/api/v3/errors.go, internal/api/v2/errors.go Added ErrConnectorCapabilityNotSupported constant and updated error handlers to map engine error to HTTP 400.
internal/api/services/accounts_create_test.go, internal/api/services/payments_create_test.go Added test cases for handling ErrConnectorCapabilityNotSupported error in account and payment creation services.
internal/api/v3/handler_accounts_create_test.go, internal/api/v2/handler_accounts_create_test.go Added test cases for API v3 and v2 account creation to verify 400 response on unsupported connector capability.
internal/api/v3/handler_payments_create_test.go, internal/api/v2/handler_payments_create_test.go Added test cases for API v3 and v2 payment creation to verify 400 response on unsupported connector capability.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant Engine

    Client->>API: Create Account or Payment Request
    API->>Engine: CreateFormanceAccount() / CreateFormancePayment()
    Engine-->>API: ErrConnectorCapabilityNotSupported
    API-->>Client: HTTP 400 with CONNECTOR_CAPABILITY_NOT_SUPPORTED error
Loading

Assessment against linked issues

Objective Addressed Explanation
Return typed "not supported by this connector" errors instead of generic errors (PMNT-121)
Ensure API handler returns HTTP 400 for unsupported connector capability errors (PMNT-121)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes detected.

Suggested reviewers

  • laouji

Poem

A rabbit hopped through fields of code,
Where connectors sometimes bear a heavy load.
"Not supported!" now clear as day,
With typed errors lighting the way.
No more 500s in the sky—
400s wave as bugs hop by!
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a8dcaa and a6d65b0.

📒 Files selected for processing (1)
  • internal/api/services/accounts_create_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/api/services/accounts_create_test.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Dirty
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Post Copyable Unit Tests in Comment
  • Commit Unit Tests in branch fix/PMNT-121-connector-not-supported-400

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@@ -366,7 +366,7 @@ func (e *engine) CreateFormanceAccount(ctx context.Context, account models.Accou
}

if !found {
err := errors.New("connector does not support account creation")
err := ErrConnectorCapacityNotSupported
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe that would be interesting to have a more precise error containing which capacity was not supported

Copy link
Contributor

Choose a reason for hiding this comment

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

You could create a typed error that would let you do that.

Alternatively you could just provide a link to documentation here in the user facing error message: https://docs.formance.com/payments/connectors/#supported-processors

The above documentation is generated from the capabilities of each connector.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added the typed error

@@ -31,6 +33,8 @@ func handleServiceErrors(w http.ResponseWriter, r *http.Request, err error) {
api.BadRequest(w, ErrValidation, err)
case errors.Is(err, services.ErrNotFound):
api.NotFound(w, err)
case errors.Is(err, engine.ErrConnectorCapacityNotSupported):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did not create an error in "services", should I?

Copy link
Contributor

Choose a reason for hiding this comment

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

Assuming you can unwrap the engine error correctly here, it is not needed in services.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
internal/connectors/engine/engine.go (1)

368-372: Return path aligned, but wording still inconsistent with capability enum

Good switch to a typed sentinel error; however the message still talks about “capacity” while the enum we checked above is CAPABILITY_ALLOW_FORMANCE_ACCOUNT_CREATION. If you keep the current identifier, at least harmonise the error text to stay in sync with the public enum – it is often bubbled up to logs and API callers.

-err := ErrConnectorCapacityNotSupported
+err := ErrConnectorCapacityNotSupported // TODO: rename to …Capability… ?

Also, CreateFormancePayment below still returns the old anonymous error string (“connector does not support payment creation”). For consistency and easier HTTP mapping you may want to introduce the same typed error there.

internal/api/services/accounts_create_test.go (1)

48-53: Test improves coverage – tiny nit on assertion style

The new case correctly exercises the new typed error.
Very minor: since you already flag typedError: true, you can spare the duplicate literal in expectedError and reuse the err field to reduce repetition, but that is stylistic – feel free to ignore.

internal/api/v3/errors.go (2)

4-5: Avoid importing large package set when only one constant needed

Adding engine solely for ErrConnectorCapacityNotSupported is fine, but if you rename the constant as suggested you could re-export it from services (existing precedent for ErrValidation, etc.) and keep HTTP layer decoupled from engine internals.


15-20: Constant name follows variable – revisit after renaming

Should be updated if the sentinel error is renamed to keep parity between API code and engine error.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21c9636 and eac3fda.

📒 Files selected for processing (4)
  • internal/api/services/accounts_create_test.go (1 hunks)
  • internal/api/v3/errors.go (3 hunks)
  • internal/connectors/engine/engine.go (1 hunks)
  • internal/connectors/engine/errors.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Dirty
🔇 Additional comments (1)
internal/api/v3/errors.go (1)

36-37: Mapping added – ensure exhaustive coverage

Nice addition. Remember to update CreateFormancePayment once a typed error is introduced there, otherwise the HTTP layer will still fall through to 500 for the analogous payment scenario.

Quentin-David-24 and others added 3 commits June 18, 2025 13:45
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
cursor[bot]

This comment was marked as outdated.

Copy link

codecov bot commented Jun 18, 2025

Codecov Report

Attention: Patch coverage is 50.00000% with 8 lines in your changes missing coverage. Please review.

Project coverage is 69.65%. Comparing base (933bad9) to head (a6d65b0).
Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
internal/connectors/engine/engine.go 0.00% 6 Missing ⚠️
internal/connectors/engine/errors.go 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #473      +/-   ##
==========================================
- Coverage   69.66%   69.65%   -0.01%     
==========================================
  Files         627      627              
  Lines       32196    32208      +12     
==========================================
+ Hits        22428    22436       +8     
- Misses       8554     8558       +4     
  Partials     1214     1214              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@@ -366,7 +366,7 @@ func (e *engine) CreateFormanceAccount(ctx context.Context, account models.Accou
}

if !found {
err := errors.New("connector does not support account creation")
err := ErrConnectorCapabilityNotSupported
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you check if there are other places that return untyped error when a capability is not supported?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CreatePayment and CreateAccount are the only places I have found where the registry.GetCapabilities function was called.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But then there is all the stuff that's executed from the worker, where you get "not implemented" error if the connector plugin has not implemented the function. Maybe we could then explicitly create function in the plugins that would return the not supported error.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
internal/api/v3/handler_accounts_create_test.go (2)

3-16: Fix import grouping – external package slipped into the std-lib block

github.com/formancehq/payments/internal/connectors/engine is an external package but is currently placed between standard-library imports.
This will be automatically re-written by goimports/golangci-lint import-order and may break CI. Put it below the blank line that separates std-lib and third-party blocks.

-import (
-    "errors"
-    "github.com/formancehq/payments/internal/connectors/engine"
-    "net/http"
-    "net/http/httptest"
-    "time"
+import (
+    // Std-lib
+    "errors"
+    "net/http"
+    "net/http/httptest"
+    "time"
+
+    // External / internal
+    "github.com/formancehq/payments/internal/connectors/engine"

44-57: Minor naming & cleanup in the new test case

  1. Acronyms should stay uppercase to follow Go naming conventions → notSupportedConnectorID instead of notSupportedConnectorId.
  2. expectedErr is only used once as the return value; consider in-lining to reduce noise.
- expectedErr := engine.ErrConnectorCapabilityNotSupported
- m.EXPECT().AccountsCreate(gomock.Any(), gomock.Any()).Return(expectedErr)
- notSupportedConnectorId := models.ConnectorID{Reference: uuid.New(), Provider: "stripe"}
+ m.EXPECT().AccountsCreate(gomock.Any(), gomock.Any()).
+     Return(engine.ErrConnectorCapabilityNotSupported)
+
+ notSupportedConnectorID := models.ConnectorID{Reference: uuid.New(), Provider: "stripe"}
 ...
-  ConnectorID: notSupportedConnectorId.String(),
+  ConnectorID: notSupportedConnectorID.String(),

These tweaks keep the test concise and consistent with existing naming patterns.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa7538a and d57a443.

📒 Files selected for processing (2)
  • internal/api/services/accounts_create_test.go (1 hunks)
  • internal/api/v3/handler_accounts_create_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/api/services/accounts_create_test.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Tests

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (7)
internal/api/v3/handler_payments_create_test.go (1)

60-78: Nit: keep ID acronym capitalised and drop redundant variable

  1. Go style: ID is an acronym and should stay uppercase (notSupportedConnectorID).
  2. expectedErr is only used as the return value of the mock; the local variable itself is not needed.
-       notSupportedConnectorId := models.ConnectorID{Reference: uuid.New(), Provider: "stripe"}
-       expectedErr := &engine.ErrConnectorCapabilityNotSupported{Capability: "CreateFormancePayment", Provider: notSupportedConnectorId.Provider}
-       m.EXPECT().PaymentsCreate(gomock.Any(), gomock.Any()).Return(expectedErr)
+       notSupportedConnectorID := models.ConnectorID{Reference: uuid.New(), Provider: "stripe"}
+       m.EXPECT().
+               PaymentsCreate(gomock.Any(), gomock.Any()).
+               Return(&engine.ErrConnectorCapabilityNotSupported{
+                       Capability: "CreateFormancePayment",
+                       Provider:   notSupportedConnectorID.Provider,
+               })
internal/api/v2/handler_payments_create_test.go (1)

48-66: Same acronym / redundancy issue as v3 test

Apply the same clean-up for notSupportedConnectorID and inline the returned error.

Additionally, double-check the constant PAYMENT_STATUS_AMOUNT_ADJUSTEMENT – the usual spelling is ADJUSTMENT. If it’s a typo in the model, worth fixing at the source before it leaks further.

internal/api/v2/errors.go (1)

15-22: Potential name collision with engine error

The new constant re-uses the exact name of the engine error (ErrConnectorCapabilityNotSupported).
Although they live in different packages, using the same identifier for two different kinds (string vs struct) can be confusing in call sites:

api.BadRequest(w, v2.ErrConnectorCapabilityNotSupported, err)

Developers might assume the second argument is the same “error object”. Consider a clearer suffix, e.g. ErrCodeConnectorCapabilityNotSupported.

internal/api/v2/handler_accounts_create_test.go (1)

47-62: Apply the same acronym & inlining nit

Rename notSupportedConnectorIdnotSupportedConnectorID and inline the error value in the mock expectation to stay consistent with other tests.

internal/connectors/engine/errors.go (3)

17-20: Exported type should carry a doc-comment and could drop the Err prefix

The project usually reserves the Err* naming convention for sentinel variables (ErrValidation, ErrNotFound).
Here, you introduce a type named ErrConnectorCapabilityNotSupported, which slightly blurs that distinction. A more idiomatic pattern is:

// ConnectorCapabilityNotSupportedError is returned when …
type ConnectorCapabilityNotSupportedError struct { … }
var ErrConnectorCapabilityNotSupported = &ConnectorCapabilityNotSupportedError{}

– or simply keep the struct but rename it to ConnectorCapabilityNotSupportedError.

Either way, add a godoc comment so public packages get proper documentation.


22-24: Error message wording – put dynamic values first

Minor readability tweak: place the variable parts in quotes and start with the provider for easier grepping in logs.

-return fmt.Sprintf("%s capability is not supported by the provider %s. Check here the supported features: https://docs.formance.com/payments/connectors/#supported-processors", e.Capability, e.Provider)
+return fmt.Sprintf("provider %q does not support capability %q. See supported features: https://docs.formance.com/payments/connectors/#supported-processors", e.Provider, e.Capability)

26-29: Custom Is implementation could compare against the concrete type only

Current code works, but an ultra-small simplification avoids the extra allocation check:

-func (e *ErrConnectorCapabilityNotSupported) Is(err error) bool {
-	_, ok := err.(*ErrConnectorCapabilityNotSupported)
-	return ok
+func (e *ErrConnectorCapabilityNotSupported) Is(target error) bool {
+	_, ok := target.(*ErrConnectorCapabilityNotSupported)
+	return ok
 }

Renaming the parameter also clarifies that target is the value passed to errors.Is.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d57a443 and 02fd975.

📒 Files selected for processing (10)
  • internal/api/services/accounts_create_test.go (1 hunks)
  • internal/api/services/payments_create_test.go (1 hunks)
  • internal/api/v2/errors.go (3 hunks)
  • internal/api/v2/handler_accounts_create_test.go (2 hunks)
  • internal/api/v2/handler_payments_create_test.go (2 hunks)
  • internal/api/v3/errors.go (3 hunks)
  • internal/api/v3/handler_accounts_create_test.go (2 hunks)
  • internal/api/v3/handler_payments_create_test.go (2 hunks)
  • internal/connectors/engine/engine.go (4 hunks)
  • internal/connectors/engine/errors.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/api/v3/handler_accounts_create_test.go
  • internal/api/services/accounts_create_test.go
  • internal/api/v3/errors.go
  • internal/connectors/engine/engine.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Tests
🔇 Additional comments (2)
internal/api/v2/errors.go (2)

4-6: Import only what you use

errors is already in the import list; after this change it’s still used, good.
No action required.


37-38: Fine-grained matching is good

Matching with errors.Is(err, &engine.ErrConnectorCapabilityNotSupported{}) relies on the custom Is method – well done, this keeps the switch concise.

Comment on lines 48 to 53
{
name: "capability not supported",
err: &engine.ErrConnectorCapabilityNotSupported{},
expectedError: &engine.ErrConnectorCapabilityNotSupported{},
typedError: true,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider asserting the error content, not only the type

The new test checks ErrorIs, which guarantees the type matches, but it doesn’t validate the fields (Capability, Provider) embedded in the error.
That leaves room for a regression where the engine returns the right type but the wrong capability/provider and the test would still pass.

-               require.ErrorIs(t, err, test.expectedError)
+               var capErr *engine.ErrConnectorCapabilityNotSupported
+               require.ErrorAs(t, err, &capErr)
+               require.Equal(t, "CreateFormancePayment", capErr.Capability)
+               // provider field is empty in the current test data, adapt if needed

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

🤖 Prompt for AI Agents
In internal/api/services/payments_create_test.go around lines 48 to 53, the test
currently asserts only the error type using ErrorIs, which misses verifying the
actual content of the error fields like Capability and Provider. Update the test
to explicitly check that these fields in the returned error match the expected
values, ensuring the error's internal data is correct and preventing regressions
where the type is right but the content is wrong.

cursor[bot]

This comment was marked as outdated.

…rted-400' into fix/PMNT-121-connector-not-supported-400

# Conflicts:
#	internal/api/services/accounts_create_test.go
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Bug: Capability Name Mismatch in Test Case

The test case for ErrConnectorCapabilityNotSupported expects the capability 'CreateAccounts', but the mocked engine method CreateFormanceAccount implies the capability should be 'CreateFormanceAccount'. This mismatch causes the test to fail or incorrectly validate the error.

internal/api/services/accounts_create_test.go#L49-L52

name: "connector capability not supported",
err: &engine.ErrConnectorCapabilityNotSupported{Capability: "CreateAccounts", Provider: "Stripe"},
expectedError: &engine.ErrConnectorCapabilityNotSupported{Capability: "CreateAccounts", Provider: "Stripe"},
},

Fix in Cursor


Bug: Payment Capability Name Mismatch

The capability not supported test case for payment creation expects the ErrConnectorCapabilityNotSupported error to contain 'CreatePayments' as the capability. This conflicts with the actual CreateFormancePayment engine function's capability name, which is 'CreateFormancePayment'. This mismatch will cause the test to fail or validate an incorrect error.

internal/api/services/payments_create_test.go#L49-L52

name: "capability not supported",
err: &engine.ErrConnectorCapabilityNotSupported{Capability: "CreatePayments", Provider: "Stripe"},
expectedError: &engine.ErrConnectorCapabilityNotSupported{Capability: "CreatePayments", Provider: "Stripe"},
},

Fix in Cursor


Was this report helpful? Give feedback by reacting with 👍 or 👎

@Quentin-David-24 Quentin-David-24 added this pull request to the merge queue Jun 19, 2025
Merged via the queue into main with commit 6545c03 Jun 19, 2025
7 of 9 checks passed
@Quentin-David-24 Quentin-David-24 deleted the fix/PMNT-121-connector-not-supported-400 branch June 19, 2025 13:58
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.

2 participants