Skip to content

Fix/multiple refunds optional void - #43

Open
SGFGOV wants to merge 1 commit into
fix/multiple-refundsfrom
fix/multiple-refunds-optional-void
Open

Fix/multiple refunds optional void#43
SGFGOV wants to merge 1 commit into
fix/multiple-refundsfrom
fix/multiple-refunds-optional-void

Conversation

@SGFGOV

@SGFGOV SGFGOV commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added a Braintree configuration option to prevent refunds from voiding unsettled transactions.
    • Refunds now require transactions to be settled or settling when this option is enabled.
    • Added clearer refund history handling and improved webhook transaction processing.
  • Bug Fixes

    • Refund attempts that cannot be processed now return a structured validation error.
    • Improved handling of missing, invalid, or unsupported Braintree transaction data.
  • Documentation

    • Updated Braintree setup and upgrade documentation with the new refund behavior.

@SGFGOV
SGFGOV requested a review from lcmohsen August 7, 2026 07:33
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The Braintree provider adds disableVoidTransactions, stores refund history as arrays, centralizes gateway error handling, refactors transaction and webhook flows, and expands regression coverage. Documentation and the package version were updated.

Changes

Braintree provider

Layer / File(s) Summary
Configuration and shared error contracts
plugins/braintree-payment/src/providers/payment-braintree/src/types/index.ts, plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts
Adds disableVoidTransactions and shared transaction, refund, validation, and gateway error types and utilities.
Transaction lifecycle and gateway error propagation
plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts, plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts
Refactors authorization, transaction creation, deletion, account-holder operations, and status handling. Existing typed errors remain unchanged.
Refund history and void prevention
plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts, plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-import.ts, plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/*, plugins/braintree-payment/README.md, plugins/braintree-payment/CHANGELOG.md, plugins/braintree-payment/package.json
Refunds append typed history entries. Enabled disableVoidTransactions rejects unsettled refunds with INVALID_DATA instead of voiding transactions.
Webhook parsing and validation
plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts, plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts
Decomposes webhook parsing and kind mapping. Unsupported notifications return NOT_SUPPORTED; validation failures propagate as structured errors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PaymentService
  participant BraintreeGateway
  participant RefundHistory
  PaymentService->>BraintreeGateway: Load transaction status
  PaymentService->>BraintreeGateway: Execute allowed void or refund action
  BraintreeGateway-->>PaymentService: Return transaction result
  PaymentService->>RefundHistory: Append refund history entry
Loading

Possibly related PRs

Suggested labels: currybot-review

Suggested reviewers: lcmohsen

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title identifies the main changes: support for multiple refunds and optional transaction voiding.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/multiple-refunds-optional-void

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

@SGFGOV
SGFGOV changed the base branch from main to fix/multiple-refunds August 7, 2026 07:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-import.spec.ts (1)

6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep option overrides type-safe.

Record<string, unknown> and the as any cast allow a misspelled option or an invalid value type to compile. The shared factory in plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts uses Partial<BraintreeOptions>. Use the same type here so this test verifies the actual provider option.

Also applies to: 21-21

🤖 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
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-import.spec.ts`
at line 6, Update buildService’s overrideOptions parameter to use
Partial<BraintreeOptions>, matching the shared factory in
braintree-base.spec.ts, and remove the accompanying as any cast so misspelled or
invalid provider options are caught by TypeScript.
plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts (1)

1125-1142: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Log when a legacy non-array braintreeRefund value is discarded.

buildRefundPaymentOutput replaces a legacy non-array braintreeRefund value with a fresh array. The previous refund record is then lost from the session data without any trace. Add a log line so the discarded audit data is visible in operations.

♻️ Proposed change
     const stored = input.data?.braintreeRefund;
+    if (stored !== undefined && !Array.isArray(stored)) {
+      this.logger.warn('[Braintree] Discarding legacy non-array braintreeRefund session data');
+    }
     const prior: BraintreeRefundHistoryEntry[] = Array.isArray(stored)
🤖 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
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts`
around lines 1125 - 1142, Update buildRefundPaymentOutput to detect when
input.data?.braintreeRefund is defined but not an array, and log the discarded
legacy value before replacing it with the new refund array. Reuse the class’s
existing logging mechanism and preserve the current array-handling behavior.
plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts (1)

654-655: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert PaymentActions.SUCCESSFUL instead of 'captured'.

Import PaymentActions from @medusajs/framework/utils and use expect(result.action).toBe(PaymentActions.SUCCESSFUL).

🤖 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
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts`
around lines 654 - 655, Update the assertion in the Braintree test to import
PaymentActions from `@medusajs/framework/utils` and compare result.action with
PaymentActions.SUCCESSFUL instead of the literal 'captured'; leave the
session_id assertion unchanged.
🤖 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 `@plugins/braintree-payment/CHANGELOG.md`:
- Around line 3-7: Align the release metadata across
plugins/braintree-payment/CHANGELOG.md lines 3-7 and
plugins/braintree-payment/package.json line 3 by using the intended 0.1.10-next
version in both; in plugins/braintree-payment/README.md lines 137-145, move the
disableVoidTransactions upgrade note under the release section that introduces
0.1.10-next rather than Upgrading to 0.1.2.

In `@plugins/braintree-payment/README.md`:
- Line 110: The refund documentation must match the implementation’s
status-specific contract. In plugins/braintree-payment/README.md at lines
110-110, update disableVoidTransactions to state that authorized and
submitted_for_settlement transactions return INVALID_DATA with “cannot be
refunded right now,” while settled and settling transactions are refundable and
other statuses return NOT_FOUND with their distinct error message. Apply the
same status-specific wording in plugins/braintree-payment/CHANGELOG.md at lines
7-7, replacing the broad unsettled-refunds statement.

In
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts`:
- Around line 1200-1214: Update resolveRefundAction to read
disableVoidTransactions from the declared BraintreeBase options_ property
instead of this.options, preserving the existing void-blocking behavior and
error handling.
- Around line 1430-1450: Update parseWebhookNotification to return
PaymentActions.NOT_SUPPORTED when webhookNotification.parse throws, while
retaining the existing validation error logging. Preserve the current successful
notification return path and avoid rethrowing or wrapping these permanently
invalid webhook validation failures.

In
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-import.ts`:
- Around line 288-297: The refund fallback in refundPayment must not classify
the disableVoidTransactions policy error as an already-refunded result. Replace
message-based matching with a structured gateway-error check, and ensure the
INVALID_DATA error thrown in the disableVoidTransactions branch is re-thrown
even when allowRefundOnRefunded is enabled. Add a regression test covering both
options enabled.

---

Nitpick comments:
In
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts`:
- Around line 654-655: Update the assertion in the Braintree test to import
PaymentActions from `@medusajs/framework/utils` and compare result.action with
PaymentActions.SUCCESSFUL instead of the literal 'captured'; leave the
session_id assertion unchanged.

In
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-import.spec.ts`:
- Line 6: Update buildService’s overrideOptions parameter to use
Partial<BraintreeOptions>, matching the shared factory in
braintree-base.spec.ts, and remove the accompanying as any cast so misspelled or
invalid provider options are caught by TypeScript.

In
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts`:
- Around line 1125-1142: Update buildRefundPaymentOutput to detect when
input.data?.braintreeRefund is defined but not an array, and log the discarded
legacy value before replacing it with the new refund array. Reuse the class’s
existing logging mechanism and preserve the current array-handling behavior.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 75852417-8d0b-4b8a-81a9-7b65b1681307

📥 Commits

Reviewing files that changed from the base of the PR and between bdea8e8 and d199540.

📒 Files selected for processing (9)
  • .gitignore
  • plugins/braintree-payment/CHANGELOG.md
  • plugins/braintree-payment/README.md
  • plugins/braintree-payment/package.json
  • plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts
  • plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-import.spec.ts
  • plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts
  • plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-import.ts
  • plugins/braintree-payment/src/providers/payment-braintree/src/types/index.ts

Comment on lines +3 to +7
## 0.1.9-next

### Improvements

- Add `disableVoidTransactions` option: when enabled, refunds never void and only proceed for `settled`/`settling` transactions (late requirement for future partial order refunds and order edits). Unsettled refunds throw `INVALID_DATA` with “cannot be refunded right now”.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align release metadata and upgrade documentation.

The package declares 0.1.10-next, the changelog entry says 0.1.9-next, and the README places the new option under Upgrading to 0.1.2. This can publish one version with release notes and migration guidance for different versions.

  • plugins/braintree-payment/CHANGELOG.md#L3-L7: use the intended package version in the top heading.
  • plugins/braintree-payment/README.md#L137-L145: move the disableVoidTransactions note under the release that introduces it.
  • plugins/braintree-payment/package.json#L3-L3: keep the package version consistent with the changelog heading.
📍 Affects 3 files
  • plugins/braintree-payment/CHANGELOG.md#L3-L7 (this comment)
  • plugins/braintree-payment/README.md#L137-L145
  • plugins/braintree-payment/package.json#L3-L3
🤖 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 `@plugins/braintree-payment/CHANGELOG.md` around lines 3 - 7, Align the release
metadata across plugins/braintree-payment/CHANGELOG.md lines 3-7 and
plugins/braintree-payment/package.json line 3 by using the intended 0.1.10-next
version in both; in plugins/braintree-payment/README.md lines 137-145, move the
disableVoidTransactions upgrade note under the release section that introduces
0.1.10-next rather than Upgrading to 0.1.2.

- **savePaymentMethod**: Save payment methods for future use (default: `true`).
- **autoCapture**: Automatically capture payments (default: `true`).
- **allowRefundOnRefunded**: Allow refund attempts on already-refunded imported transactions (default: `false`).
- **disableVoidTransactions**: When `true`, refunds never void; only `settled`/`settling` transactions may be refunded. Late requirement so future partial order refunds and order edits can be supported (void cancels the full authorization). Default: `false`. With this enabled, refunds on unsettled transactions fail with “cannot be refunded right now”.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document the actual refund status and error contract.

The implementation returns INVALID_DATA with “cannot be refunded right now” only for authorized and submitted_for_settlement. It refunds settled and settling transactions, and returns NOT_FOUND with a different message for other statuses.

  • plugins/braintree-payment/README.md#L110-L110: list the exact rejected statuses and their error behavior.
  • plugins/braintree-payment/CHANGELOG.md#L7-L7: replace the broad “unsettled refunds” statement with the same status-specific contract.
📍 Affects 2 files
  • plugins/braintree-payment/README.md#L110-L110 (this comment)
  • plugins/braintree-payment/CHANGELOG.md#L7-L7
🤖 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 `@plugins/braintree-payment/README.md` at line 110, The refund documentation
must match the implementation’s status-specific contract. In
plugins/braintree-payment/README.md at lines 110-110, update
disableVoidTransactions to state that authorized and submitted_for_settlement
transactions return INVALID_DATA with “cannot be refunded right now,” while
settled and settling transactions are refundable and other statuses return
NOT_FOUND with their distinct error message. Apply the same status-specific
wording in plugins/braintree-payment/CHANGELOG.md at lines 7-7, replacing the
broad unsettled-refunds statement.

Comment on lines 1200 to 1214
private async resolveRefundAction(transaction: Transaction): Promise<RefundAction> {
const resolved = await this.applyTestForceSettled(transaction);

if (isVoidableRefundStatus(resolved.status)) {
if (this.options.disableVoidTransactions) {
this.logger.error(
`Braintree transaction with ID ${resolved.id} cannot be refunded right now because it's in status ${resolved.status}`,
);
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Braintree transaction with ID ${resolved.id} cannot be refunded right now`,
);
}
return { kind: 'voided', transaction: resolved };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether AbstractPaymentProvider declares an `options` member.
fd -t f 'abstract-payment-provider*' node_modules/@medusajs 2>/dev/null | head
rg -nP --type=ts -C4 '\b(protected|public|readonly)?\s*options\s*[:;=]' \
  $(fd -t f -p 'abstract-payment-provider' node_modules/@medusajs 2>/dev/null | head -5) 2>/dev/null

# Show every option read inside braintree-base.ts for consistency.
rg -nP --type=ts '\bthis\.options_?\.' plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts

Repository: lambda-curry/medusa-plugins

Length of output: 165


🏁 Script executed:

#!/bin/bash
set -e

printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(abstract-payment-provider|braintree-base|package\.json|pnpm-lock|yarn\.lock|package-lock\.json)' | head -80

printf '%s\n' '--- BraintreeBase option declarations and reads ---'
rg -n -C3 'options_|this\.options\b|disableVoidTransactions' plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts

printf '%s\n' '--- provider base references in source and manifests ---'
rg -n -C3 'AbstractPaymentProvider|`@medusajs/medusa`|disableVoidTransactions' plugins/braintree-payment/src plugins/braintree-payment/package.json package.json 2>/dev/null | head -240

Repository: lambda-curry/medusa-plugins

Length of output: 17921


🌐 Web query:

Medusa 2.15.2 AbstractPaymentProvider options_ options source code

💡 Result:

In Medusa 2.x, the AbstractPaymentProvider class serves as the base for all payment module providers [1][2]. The options passed to your payment provider (defined in medusa-config.ts) are injected into your provider service through its constructor [3][4]. To handle these options, you typically define a TypeScript type (e.g., Options) and pass it as a generic type argument when extending AbstractPaymentProvider [5][2]. Implementation Details: 1. Type Definition and Injection: Define your options type and use the constructor to receive and store them [4][5]. ts import { AbstractPaymentProvider } from "`@medusajs/framework/utils`" type Options = { apiKey: string // Add other provider-specific options here } class MyPaymentProviderService extends AbstractPaymentProvider<Options> { protected readonly options_: Options constructor( container: Record<string, unknown>, options: Options ) { super(container, options) this.options_ = options } } 2. Validation: You can implement the static validateOptions method to ensure the configuration provided in medusa-config.ts is correct before the application starts [3][5]. ts class MyPaymentProviderService extends AbstractPaymentProvider<Options> { static validateOptions(options: Record<any, any>): void | never { if (!options.apiKey) { throw new Error("API key is required in the provider's options.") } } } 3. Configuration: These options are then passed to the payment module in your medusa-config.ts file [5]: ts // medusa-config.ts modules: [ { resolve: "`@medusajs/medusa/payment`", options: { providers: [ { resolve: "./src/modules/my-payment", id: "my-payment", options: { apiKey: "your-api-key" } } ] } } ] The AbstractPaymentProvider internally stores these in a config property [3][4], but it is standard practice to manually assign them to a protected class property (such as options_) within your constructor as shown above to make them easily accessible throughout your service methods [4][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -e

printf '%s\n' '--- constructor and test setup ---'
sed -n '320,355p' plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts
sed -n '1,75p' plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts
sed -n '220,270p' plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts

printf '%s\n' '--- dependency lock entries ---'
rg -n -C2 '(`@medusajs/medusa`|`@medusajs/framework`)' plugins/braintree-payment/package.json yarn.lock | head -160

Repository: lambda-curry/medusa-plugins

Length of output: 14556


🏁 Script executed:

#!/bin/bash
set -e

url='https://raw.githubusercontent.com/medusajs/medusa/v2.15.2/packages/core/utils/src/payment/abstract-payment-provider.ts'
src="$(curl -fsSL "$url")"

printf '%s\n' '--- AbstractPaymentProvider option members ---'
printf '%s\n' "$src" | rg -n -C5 '\b(options|config)\b|constructor'

printf '%s\n' '--- BraintreeBase option access classification ---'
python3 - <<'PY'
from pathlib import Path
p = Path("plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts")
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
    if "this.options" in line or "options_" in line:
        print(f"{i}: {line}")
PY

Repository: lambda-curry/medusa-plugins

Length of output: 4983


Use this.options_ for the option read.

AbstractPaymentProvider exposes config, not options, and BraintreeBase declares options_. This access causes a TypeScript error; if transpiled, it throws before blocking the void.

♻️ Proposed change
     if (isVoidableRefundStatus(resolved.status)) {
-      if (this.options.disableVoidTransactions) {
+      if (this.options_.disableVoidTransactions) {
📝 Committable suggestion

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

Suggested change
private async resolveRefundAction(transaction: Transaction): Promise<RefundAction> {
const resolved = await this.applyTestForceSettled(transaction);
if (isVoidableRefundStatus(resolved.status)) {
if (this.options.disableVoidTransactions) {
this.logger.error(
`Braintree transaction with ID ${resolved.id} cannot be refunded right now because it's in status ${resolved.status}`,
);
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Braintree transaction with ID ${resolved.id} cannot be refunded right now`,
);
}
return { kind: 'voided', transaction: resolved };
}
private async resolveRefundAction(transaction: Transaction): Promise<RefundAction> {
const resolved = await this.applyTestForceSettled(transaction);
if (isVoidableRefundStatus(resolved.status)) {
if (this.options_.disableVoidTransactions) {
this.logger.error(
`Braintree transaction with ID ${resolved.id} cannot be refunded right now because it's in status ${resolved.status}`,
);
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Braintree transaction with ID ${resolved.id} cannot be refunded right now`,
);
}
return { kind: 'voided', transaction: 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
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts`
around lines 1200 - 1214, Update resolveRefundAction to read
disableVoidTransactions from the declared BraintreeBase options_ property
instead of this.options, preserving the existing void-blocking behavior and
error handling.

Comment on lines +288 to +297
if (this.options.disableVoidTransactions) {
this.logger.error(
`Braintree transaction with ID ${transaction.id} cannot be refunded right now because it's in status ${transaction.status}`,
);
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Braintree transaction with ID ${transaction.id} cannot be refunded right now`,
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Exclude this policy error from the already-refunded fallback.

When allowRefundOnRefunded is enabled, refundPayment catches errors at Lines 234-248 and treats messages containing refunded or cannot be refunded as proof that Braintree already refunded the transaction. The new message at Line 294 matches that predicate. The code then increments refundedTotal and returns success without calling void or refund.

Use a structured gateway-error check for already-refunded cases. Re-throw the local disableVoidTransactions INVALID_DATA error. Add a regression test with both options enabled.

🤖 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
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-import.ts`
around lines 288 - 297, The refund fallback in refundPayment must not classify
the disableVoidTransactions policy error as an already-refunded result. Replace
message-based matching with a structured gateway-error check, and ensure the
INVALID_DATA error thrown in the disableVoidTransactions branch is re-thrown
even when allowRefundOnRefunded is enabled. Add a regression test covering both
options enabled.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

🧹 Nitpick comments (3)
plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-import.spec.ts (1)

6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep option overrides type-safe.

Record<string, unknown> and the as any cast allow a misspelled option or an invalid value type to compile. The shared factory in plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts uses Partial<BraintreeOptions>. Use the same type here so this test verifies the actual provider option.

Also applies to: 21-21

🤖 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
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-import.spec.ts`
at line 6, Update buildService’s overrideOptions parameter to use
Partial<BraintreeOptions>, matching the shared factory in
braintree-base.spec.ts, and remove the accompanying as any cast so misspelled or
invalid provider options are caught by TypeScript.
plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts (1)

1125-1142: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Log when a legacy non-array braintreeRefund value is discarded.

buildRefundPaymentOutput replaces a legacy non-array braintreeRefund value with a fresh array. The previous refund record is then lost from the session data without any trace. Add a log line so the discarded audit data is visible in operations.

♻️ Proposed change
     const stored = input.data?.braintreeRefund;
+    if (stored !== undefined && !Array.isArray(stored)) {
+      this.logger.warn('[Braintree] Discarding legacy non-array braintreeRefund session data');
+    }
     const prior: BraintreeRefundHistoryEntry[] = Array.isArray(stored)
🤖 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
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts`
around lines 1125 - 1142, Update buildRefundPaymentOutput to detect when
input.data?.braintreeRefund is defined but not an array, and log the discarded
legacy value before replacing it with the new refund array. Reuse the class’s
existing logging mechanism and preserve the current array-handling behavior.
plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts (1)

654-655: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert PaymentActions.SUCCESSFUL instead of 'captured'.

Import PaymentActions from @medusajs/framework/utils and use expect(result.action).toBe(PaymentActions.SUCCESSFUL).

🤖 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
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts`
around lines 654 - 655, Update the assertion in the Braintree test to import
PaymentActions from `@medusajs/framework/utils` and compare result.action with
PaymentActions.SUCCESSFUL instead of the literal 'captured'; leave the
session_id assertion unchanged.
🤖 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 `@plugins/braintree-payment/CHANGELOG.md`:
- Around line 3-7: Align the release metadata across
plugins/braintree-payment/CHANGELOG.md lines 3-7 and
plugins/braintree-payment/package.json line 3 by using the intended 0.1.10-next
version in both; in plugins/braintree-payment/README.md lines 137-145, move the
disableVoidTransactions upgrade note under the release section that introduces
0.1.10-next rather than Upgrading to 0.1.2.

In `@plugins/braintree-payment/README.md`:
- Line 110: The refund documentation must match the implementation’s
status-specific contract. In plugins/braintree-payment/README.md at lines
110-110, update disableVoidTransactions to state that authorized and
submitted_for_settlement transactions return INVALID_DATA with “cannot be
refunded right now,” while settled and settling transactions are refundable and
other statuses return NOT_FOUND with their distinct error message. Apply the
same status-specific wording in plugins/braintree-payment/CHANGELOG.md at lines
7-7, replacing the broad unsettled-refunds statement.

In
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts`:
- Around line 1200-1214: Update resolveRefundAction to read
disableVoidTransactions from the declared BraintreeBase options_ property
instead of this.options, preserving the existing void-blocking behavior and
error handling.
- Around line 1430-1450: Update parseWebhookNotification to return
PaymentActions.NOT_SUPPORTED when webhookNotification.parse throws, while
retaining the existing validation error logging. Preserve the current successful
notification return path and avoid rethrowing or wrapping these permanently
invalid webhook validation failures.

In
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-import.ts`:
- Around line 288-297: The refund fallback in refundPayment must not classify
the disableVoidTransactions policy error as an already-refunded result. Replace
message-based matching with a structured gateway-error check, and ensure the
INVALID_DATA error thrown in the disableVoidTransactions branch is re-thrown
even when allowRefundOnRefunded is enabled. Add a regression test covering both
options enabled.

---

Nitpick comments:
In
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts`:
- Around line 654-655: Update the assertion in the Braintree test to import
PaymentActions from `@medusajs/framework/utils` and compare result.action with
PaymentActions.SUCCESSFUL instead of the literal 'captured'; leave the
session_id assertion unchanged.

In
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-import.spec.ts`:
- Line 6: Update buildService’s overrideOptions parameter to use
Partial<BraintreeOptions>, matching the shared factory in
braintree-base.spec.ts, and remove the accompanying as any cast so misspelled or
invalid provider options are caught by TypeScript.

In
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts`:
- Around line 1125-1142: Update buildRefundPaymentOutput to detect when
input.data?.braintreeRefund is defined but not an array, and log the discarded
legacy value before replacing it with the new refund array. Reuse the class’s
existing logging mechanism and preserve the current array-handling behavior.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 75852417-8d0b-4b8a-81a9-7b65b1681307

📥 Commits

Reviewing files that changed from the base of the PR and between bdea8e8 and d199540.

📒 Files selected for processing (9)
  • .gitignore
  • plugins/braintree-payment/CHANGELOG.md
  • plugins/braintree-payment/README.md
  • plugins/braintree-payment/package.json
  • plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-base.spec.ts
  • plugins/braintree-payment/src/providers/payment-braintree/src/core/__tests__/braintree-import.spec.ts
  • plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts
  • plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-import.ts
  • plugins/braintree-payment/src/providers/payment-braintree/src/types/index.ts
🛑 Comments failed to post (1)
plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts (1)

1430-1450: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how the Medusa webhook route handles a thrown provider error.
rg -nP --type=ts -C5 'getWebhookActionAndData' --glob '!**/__tests__/**' .

Repository: lambda-curry/medusa-plugins

Length of output: 3423


🏁 Script executed:

#!/bin/bash
# Locate the webhook route, provider interface, and Medusa dependency versions.
rg -n -C6 --glob '!**/__tests__/**' \
  'getWebhookActionAndData|WebhookActionResult|PaymentActions|webhook' \
  plugins/braintree-payment package.json plugins/braintree-payment/package.json \
  2>/dev/null
printf '\n--- package manifests ---\n'
find . -maxdepth 4 -name package.json -print

Repository: lambda-curry/medusa-plugins

Length of output: 37242


🏁 Script executed:

#!/bin/bash
# Inspect all relevant source and manifests with focused context.
rg -n -C8 --glob '!**/__tests__/**' \
  'getWebhookActionAndData|WebhookActionResult|PaymentActions|webhook' .
printf '\n--- manifests ---\n'
find . -name package.json -not -path '*/node_modules/*' -print0 |
  xargs -0 -r rg -n '"`@medusajs/`|medusa|braintree"'

Repository: lambda-curry/medusa-plugins

Length of output: 50384


🌐 Web query:

Medusa payment provider getWebhookActionAndData thrown error webhook route HTTP status response retry

💡 Result:

When using Medusa’s default payment webhook route (/hooks/payment/[identifier]_[provider]), Medusa automatically handles the HTTP response. Currently, this route is hardcoded to return a generic 200 HTTP status code for success or a 400 for errors [1]. If your third-party payment provider requires a specific response body (e.g., {status: "OK"}) to acknowledge receipt and prevent repeated webhook retries, the default route may not be sufficient [1]. Medusa’s built-in handling relies on the getWebhookActionAndData method, which processes the payload internally but does not allow you to customize the HTTP response body returned to the provider [2][3][1]. To resolve issues where a provider continues to send retries because it does not receive the expected acknowledgement, the recommended approach is to create a custom API route [4][1]. Steps to implement a custom webhook handler: 1. Create a custom API route in your Medusa project (e.g., /store/webhooks/payment/provider-name). 2. Within this route, manually call the getWebhookActionAndData method by resolving the payment module service [4][5]. 3. Handle the returned WebhookActionResult and send the specific HTTP response body required by your payment provider [4][1]. If you encounter errors inside getWebhookActionAndData (such as resolution errors or invalid arguments), ensure that your provider is correctly registered in the container and that the provider ID passed to the method matches the one defined in your medusa-config.ts [6][7]. If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback: POST https://docs.medusajs.com/resources/agents/feedback json { "agent": "Name of the agent", "path": "/optimize/feedback", "feedback": "Description of the issue" }

Citations:


🌐 Web query:

Braintree webhook invalid signature retry behavior malformed webhook notification

💡 Result:

Braintree webhook notifications are automatically retried by the system if your endpoint does not respond with a successful HTTPS response code (i.e., in the 2xx range) within 30 seconds [1][2][3]. The retry schedule is as follows: - Sandbox: Every hour for up to 3 hours [1][2][3]. - Production: Every hour for up to 24 hours [1][2][3]. Regarding "Invalid Signature" and "Malformed" notification errors, these exceptions are typically raised by the Braintree SDK when the library cannot successfully parse the bt_payload or verify it against the bt_signature [1][2][3]. Technical causes and resolutions for these errors often include: 1. Data Modification by Web Frameworks: Common issues arise when web frameworks (such as ASP.NET Web API or other middleware) automatically parse, URL-decode, or modify the raw POST body before it reaches your Braintree SDK integration [4][5]. If characters are added, stripped, or encoded incorrectly, the signature verification will fail [6][5]. Ensure you are passing the raw, unmodified payload to the parse method [4][5]. 2. Encoding and Whitespace: Errors such as "payload contains illegal characters" often result from whitespace or newline character mismatches introduced by the environment or request parsing [4][5]. Some developers have resolved this by normalizing the payload (e.g., replacing spaces with newlines) before passing it to the SDK's parse function, though this should be used as a last resort if your framework is inadvertently altering the string [4][5]. 3. Configuration: Verify that your Braintree environment (Sandbox vs. Production) and corresponding public/private keys are correctly configured, as mismatched keys can lead to signature verification failures [6][7]. To debug, log the raw request body exactly as it is received by your server and compare it against the expected format defined by the Braintree SDK [4][5]. If the SDK is throwing an InvalidSignature exception, it indicates the data received does not match the signature provided, typically due to the payload being altered in transit or by your application's middleware [6][5].

Citations:


🏁 Script executed:

#!/bin/bash
# Check the cited Medusa source for the exact response status on provider errors.
url='https://raw.githubusercontent.com/medusajs/medusa/5296f511/packages/medusa/src/subscribers/payment-webhook.ts'
curl -fsSL "$url" |
  rg -n -C8 'getWebhookActionAndData|status\(|400|200|catch|throw'

Repository: lambda-curry/medusa-plugins

Length of output: 911


🏁 Script executed:

#!/bin/bash
# Locate the Medusa v2 payment webhook route in the tagged source tree.
curl -fsSL 'https://api.github.com/repos/medusajs/medusa/git/trees/2.13.5?recursive=1' |
  jq -r '.tree[]?.path' |
  rg 'payment.*webhook|webhook.*payment|hooks/payment'

Repository: lambda-curry/medusa-plugins

Length of output: 214


Stop retries for permanently invalid Braintree webhooks

When webhookNotification.parse throws, Medusa returns an error response. Braintree retries non-2xx responses for up to 24 hours in production and 3 hours in sandbox. Invalid signatures and malformed payloads cannot succeed on retry.

Return PaymentActions.NOT_SUPPORTED for these validation failures and retain the error log.

🤖 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
`@plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts`
around lines 1430 - 1450, Update parseWebhookNotification to return
PaymentActions.NOT_SUPPORTED when webhookNotification.parse throws, while
retaining the existing validation error logging. Preserve the current successful
notification return path and avoid rethrowing or wrapping these permanently
invalid webhook validation failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant