Skip to content

Conversation

@blacktscoder
Copy link

This PR adds support for multi-currency setups in the Braintree payments plugin.

Vendure already supports multiple currencies per channel, but the Braintree plugin
previously assumed a single merchant account. This change resolves the appropriate
merchantAccountId dynamically based on the order currency, with a configurable
per-currency mapping and a safe fallback to the default merchantAccountId.

The change is backward-compatible and does not affect existing single-currency setups.

Closes #3254

@vercel
Copy link

vercel bot commented Dec 18, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
docs Ready Ready Preview Dec 18, 2025 10:59pm
vendure-storybook Ready Ready Preview, Comment Dec 18, 2025 10:59pm

@github-actions
Copy link
Contributor

github-actions bot commented Dec 18, 2025

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Walkthrough

The changes add dynamic merchant account ID resolution to the Braintree payments plugin. A new helper function resolves merchant account IDs based on currency codes, with support for both global defaults and currency-specific mappings. Configuration options are exposed through an updated plugin interface.

Changes

Cohort / File(s) Summary
Braintree merchant account configuration
packages/payments-plugin/src/braintree/types.ts
Added CurrencyCode import from @vendure/core. Introduced new BraintreePluginOptions interface with optional merchantAccountId (string) and merchantAccountIdByCurrency (currency-keyed mapping) fields via declaration merging.
Braintree merchant account resolution
packages/payments-plugin/src/braintree/braintree-common.ts, packages/payments-plugin/src/braintree/braintree.handler.ts
Implemented resolveMerchantAccountId(currencyCode, options) helper function in braintree-common that resolves merchant account IDs using currency-specific mappings, global defaults, or throws an error. Integrated the function into braintree.handler payment processing flow to dynamically select merchant accounts based on order currency code.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses multi-currency Braintree merchant accounts (issue #3254), but issue #3254 actually concerns adding state to promotion side effects, which is unrelated to this PR. Verify the correct issue is linked; this PR does not implement the promotion state feature described in issue #3254. Update the linked issue or clarify if this is a mislabeling.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature: adding multi-currency support for Braintree merchant accounts, which aligns with the changeset.
Description check ✅ Passed The description covers the feature, explains the backward-compatibility, and references a closing issue, but lacks details on testing and README updates per template.
Out of Scope Changes check ✅ Passed All changes focus on implementing multi-currency support for Braintree merchant accounts via new helper functions and configuration options, with no unrelated modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@sonarqubecloud
Copy link

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 (2)
packages/payments-plugin/src/braintree/types.ts (1)

1-12: Critical: PR objectives are inconsistent with actual changes.

The PR objectives reference Issue #3254 about adding state to promotion side-effects (onActivate and onDeactivate), which is completely unrelated to the Braintree multi-currency support implemented in this PR. This appears to be a copy-paste error in the PR metadata.


Consider documenting the new fields in the main interface.

The new merchantAccountId and merchantAccountIdByCurrency fields are added via declaration merging but lack JSDoc documentation. For consistency with the extensively documented BraintreePluginOptions interface at lines 28-103, consider moving these fields into that block with proper documentation explaining:

  • When to use merchantAccountId (global default)
  • When to use merchantAccountIdByCurrency (currency-specific overrides)
  • The fallback behavior
📝 Suggested documentation structure
 export interface BraintreePluginOptions {
     /**
      * @description
      * The Braintree environment being targeted, e.g. sandbox or production.
      *
      * @default Environment.Sandbox
      */
     environment?: Environment;
+    /**
+     * @description
+     * The default Braintree merchant account ID to use for transactions.
+     * Can be overridden on a per-currency basis using `merchantAccountIdByCurrency`.
+     *
+     * @since 1.9.0
+     */
+    merchantAccountId?: string;
+    /**
+     * @description
+     * A mapping of currency codes to Braintree merchant account IDs.
+     * When processing a payment, the merchant account ID for the order's currency
+     * will be used if available, otherwise falling back to `merchantAccountId`.
+     *
+     * @example
+     * ```ts
+     * {
+     *   merchantAccountId: 'default_account',
+     *   merchantAccountIdByCurrency: {
+     *     [CurrencyCode.GBP]: 'uk_account',
+     *     [CurrencyCode.EUR]: 'eu_account',
+     *   }
+     * }
+     * ```
+     *
+     * @since 1.9.0
+     */
+    merchantAccountIdByCurrency?: Partial<Record<CurrencyCode, string>>;
     /**
      * @description
      * If set to `true`, a [Customer](https://developer.paypal.com/braintree/docs/guides/customers) object
packages/payments-plugin/src/braintree/braintree-common.ts (1)

56-67: Simplify the error throwing pattern.

The function logic is correct, but the IIFE pattern (() => { throw new Error(...) })() for throwing an error is unnecessarily complex. Consider simplifying to a direct throw or conditional check.

🔎 Simpler alternatives

Option 1: Extract and validate

 export function resolveMerchantAccountId(
     currencyCode: CurrencyCode,
     options: BraintreePluginOptions,
 ): string {
-    return (
-        options.merchantAccountIdByCurrency?.[currencyCode] ??
-        options.merchantAccountId ??
-        (() => {
-            throw new Error(`No Braintree merchantAccountId configured for currency ${currencyCode}`);
-        })()
-    );
+    const merchantAccountId = 
+        options.merchantAccountIdByCurrency?.[currencyCode] ?? 
+        options.merchantAccountId;
+    
+    if (!merchantAccountId) {
+        throw new Error(`No Braintree merchantAccountId configured for currency ${currencyCode}`);
+    }
+    
+    return merchantAccountId;
 }

Option 2: Keep inline but simplify (if you prefer one-liner)

 export function resolveMerchantAccountId(
     currencyCode: CurrencyCode,
     options: BraintreePluginOptions,
 ): string {
-    return (
-        options.merchantAccountIdByCurrency?.[currencyCode] ??
-        options.merchantAccountId ??
-        (() => {
-            throw new Error(`No Braintree merchantAccountId configured for currency ${currencyCode}`);
-        })()
-    );
+    const merchantAccountId = options.merchantAccountIdByCurrency?.[currencyCode] ?? options.merchantAccountId;
+    if (!merchantAccountId) throw new Error(`No Braintree merchantAccountId configured for currency ${currencyCode}`);
+    return merchantAccountId;
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a70ea6b and 03bcc56.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • packages/payments-plugin/src/braintree/braintree-common.ts (2 hunks)
  • packages/payments-plugin/src/braintree/braintree.handler.ts (3 hunks)
  • packages/payments-plugin/src/braintree/types.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/payments-plugin/src/braintree/braintree.handler.ts (1)
packages/payments-plugin/src/braintree/braintree-common.ts (1)
  • resolveMerchantAccountId (56-67)
packages/payments-plugin/src/braintree/types.ts (1)
packages/payments-plugin/src/braintree/braintree.handler.ts (1)
  • braintreePaymentMethodHandler (25-87)
🔇 Additional comments (2)
packages/payments-plugin/src/braintree/braintree.handler.ts (1)

14-14: LGTM: Import added for merchant account resolution.

The import of resolveMerchantAccountId correctly brings in the new helper function for dynamic merchant account ID resolution.

packages/payments-plugin/src/braintree/braintree-common.ts (1)

1-1: LGTM: Import added for currency code type.

The CurrencyCode import is correctly added to support the new multi-currency merchant account resolution.

customerId = await getBraintreeCustomerId(ctx, gateway, customer);
}
return processPayment(ctx, gateway, order, amount, metadata.nonce, customerId, options);
return processPayment(ctx, gateway, order, amount, metadata.nonce, customerId, options, args);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Wrong parameter passed to resolveMerchantAccountId.

Line 104 calls resolveMerchantAccountId(order.currencyCode, args), but the function signature expects BraintreePluginOptions as the second parameter, not PaymentMethodArgsHash.

  • args contains merchantId, publicKey, and privateKey (payment method configuration)
  • resolveMerchantAccountId expects options which contains merchantAccountId and merchantAccountIdByCurrency (plugin options)

This will cause the function to always throw the error "No Braintree merchantAccountId configured" because args doesn't have the required properties.

🔧 Proposed fix

The pluginOptions variable is already available in the processPayment function scope, so you can either:

Option 1: Pass pluginOptions instead of args (recommended - no signature change needed)

-            return processPayment(ctx, gateway, order, amount, metadata.nonce, customerId, options, args);
+            return processPayment(ctx, gateway, order, amount, metadata.nonce, customerId, options);
 async function processPayment(
     ctx: RequestContext,
     gateway: BraintreeGateway,
     order: Order,
     amount: number,
     paymentMethodNonce: any,
     customerId: string | undefined,
     pluginOptions: BraintreePluginOptions,
-    args: any,
 ) {
     const response = await gateway.transaction.sale({
         customerId,
         amount: (amount / 100).toString(10),
         orderId: order.code,
         paymentMethodNonce,
-        merchantAccountId: resolveMerchantAccountId(order.currencyCode, args),
+        merchantAccountId: resolveMerchantAccountId(order.currencyCode, pluginOptions),
         options: {
             submitForSettlement: true,
             storeInVaultOnSuccess: !!customerId,
         },
     });

Option 2: Keep args parameter but pass pluginOptions to the resolver

-        merchantAccountId: resolveMerchantAccountId(order.currencyCode, args),
+        merchantAccountId: resolveMerchantAccountId(order.currencyCode, pluginOptions),

Also applies to: 97-97, 104-104

🤖 Prompt for AI Agents
In packages/payments-plugin/src/braintree/braintree.handler.ts around lines 52,
97 and 104, the call to resolveMerchantAccountId is passing the payment method
args object instead of the plugin options, causing missing merchantAccountId
errors; update those calls to pass the pluginOptions variable (which contains
merchantAccountId and merchantAccountIdByCurrency) as the second argument so
resolveMerchantAccountId receives the correct BraintreePluginOptions and no
signature changes are required.

@blacktscoder
Copy link
Author

I have read the CLA Document and I hereby sign the CLA

github-actions bot added a commit that referenced this pull request Dec 18, 2025
kik0desu pushed a commit to wilshop/vendure that referenced this pull request Dec 29, 2025
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.

Add state to onActivate and onDeactivate sideeffects in Promotion

1 participant