How to Handle Refund Processing Including Partial Refunds in Adyen Salesforce Commerce Cloud

The Adyen SFCC integration processes full refunds via webhook handlers that mark orders as NOTPAID, while partial refunds for gift-card orders require calling cancelPartialPaymentOrderHelper to restore balances and clean up session data.

The adyen/adyen-salesforce-commerce-cloud repository provides a comprehensive refund architecture that handles both standard refunds and complex partial-payment scenarios involving gift cards. Understanding how to handle refund processing including partial refunds ensures that your Commerce Cloud storefront maintains accurate order states and financial records when customers request money back. This guide examines the webhook-driven refund flow, the specific handlers for different notification types, and the helper functions that manage gift-card reversals.

Understanding the Refund Flow Architecture

The integration adopts a webhook-driven architecture where Adyen pushes notification events to your SFCC endpoints. This design ensures that refund statuses remain synchronized between Adyen’s platform and your Commerce Cloud order management system.

Webhook Routing and Event Handling

When Adyen processes a refund or cancellation, it dispatches notifications to src/cartridges/int_adyen_webhooks/cartridge/controllers/webhook/. The router inspects the eventCode field and delegates to the appropriate handler in src/cartridges/int_adyen_webhooks/cartridge/eventHandlers/.

The system distinguishes between three primary refund scenarios:

  • Full refunds triggered by the REFUND webhook
  • Ambiguous cancel/refund operations handled by CANCEL_OR_REFUND
  • Partial payment reversals for gift-card transactions managed by cancelPartialPaymentOrderHelper

Full Refund Processing

For standard refunds, the REFUND.js handler receives the order object and updates the payment status immediately. This ensures that the order appears as "Not Paid" in Business Manager, preventing accidental fulfillment of refunded orders.

Cancel or Refund Processing

The CANCEL_OR_REFUND webhook type handles scenarios where Adyen cannot distinguish between a cancellation and a refund (common in certain payment methods). The handler logic mirrors the standard refund process but logs the specific event type for audit trails.

Implementing Full Refunds with the REFUND Handler

The REFUND.js handler in src/cartridges/int_adyen_webhooks/cartridge/eventHandlers/ provides the core logic for processing full refund notifications. When executed, it performs three critical operations: updating the order status, recording an audit trail, and writing system logs.

// src/cartridges/int_adyen_webhooks/cartridge/eventHandlers/REFUND.js
const Order = require('dw/order/Order');
const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');

function handle({ order }) {
    // Mark the order as NOTPAID (full amount is no longer captured)
    order.setPaymentStatus(Order.PAYMENT_STATUS_NOTPAID);
    
    // Record a note for audit purposes
    order.trackOrderChange('REFUND notification received');
    
    // Write an info log (visible in Business Manager)
    AdyenLogs.info_log(`Order ${order.orderNo} was refunded.`);
}

module.exports = { handle };

Key implementation details:

  • Order.PAYMENT_STATUS_NOTPAID immediately removes the paid status from the order
  • trackOrderChange() creates a permanent record in the order history
  • AdyenLogs.info_log() ensures visibility in Business Manager log files

The handler is covered by unit tests in src/cartridges/int_adyen_webhooks/cartridge/eventHandlers/__tests__/REFUND.test.js, which verify that all three operations execute correctly when the webhook fires.

Handling Cancel or Refund Scenarios

The CANCEL_OR_REFUND.js handler addresses edge cases where payment methods blur the line between cancellations and refunds. Located in the same directory as the standard refund handler, it executes identical status updates but differentiates the audit trail.

// src/cartridges/int_adyen_webhooks/cartridge/eventHandlers/CANCEL_OR_REFUND.js
const Order = require('dw/order/Order');
const AdyenLogs = require('*/cartridge/adyen/logs/adyenCustomLogs');

function handle({ order }) {
    order.setPaymentStatus(Order.PAYMENT_STATUS_NOTPAID);
    order.trackOrderChange('CANCEL_OR_REFUND notification received');
    AdyenLogs.info_log(`Order ${order.orderNo} was cancelled or refunded.`);
}

module.exports = { handle };

Both handlers ensure that regardless of whether Adyen sends a REFUND or CANCEL_OR_REFUND notification, your SFCC order reflects the financial reality that funds have been returned to the customer.

Processing Partial Refunds for Gift Card Orders

Partial refunds present unique complexity when orders use split payment methods—specifically when customers combine gift cards with Adyen payment instruments. In these scenarios, the integration must cancel the partial-payment order on Adyen’s side to restore gift-card balances.

The Partial Payment Cancellation Helper

The cancelPartialPaymentOrderHelper function in src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/partialPayments/cancelPartialPaymentOrder.js encapsulates the logic for reversing partial payments. It extracts the Adyen order reference from basket.custom.partialPaymentOrderData, calls the cancellation API, and cleans up local session data.

// src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/partialPayments/cancelPartialPaymentOrder.js
function cancelPartialPaymentOrderHelper(basket) {
    // Guard clause – only run if a partial-payment order exists
    if (!basket || !basket.custom.partialPaymentOrderData) {
        return null;
    }

    // Extract the Adyen order reference stored in the basket
    const { order } = JSON.parse(basket.custom.partialPaymentOrderData);

    // Build the request for Adyen
    const cancelOrderRequest = {
        merchantAccount: AdyenConfigs.getAdyenMerchantAccount(),
        order,
    };

    // Execute the API call
    const response = adyenCheckout.doCancelPartialPaymentOrderCall(cancelOrderRequest);

    // On success – clean up basket and session
    if (response.resultCode === constants.RESULTCODES.RECEIVED) {
        Transaction.wrap(() => {
            collections.forEach(basket.getPaymentInstruments(), (item) => {
                if (item.custom.adyenPartialPaymentsOrder) {
                    basket.removePaymentInstrument(item);
                }
            });
            clearForms.clearAdyenBasketData(basket);
        });
        session.privacy.giftCardResponse = null;
        session.privacy.partialPaymentAmounts = null;
        session.privacy.giftCardBalance = null;
    } else {
        // Unexpected result – raise a custom error
        throw new AdyenError(`received resultCode ${response.resultCode}`);
    }

    return response;
}

This helper performs four critical cleanup operations:

  1. Removes gift-card payment instruments from the basket
  2. Clears Adyen-specific basket data using clearForms.clearAdyenBasketData()
  3. Nullifies session privacy variables that track gift-card responses and balances
  4. Returns the API response for upstream error handling

Integration in Checkout Flow

The helper integrates into the checkout pipeline within src/cartridges/int_adyen_SFRA/cartridge/controllers/middlewares/checkout_services/placeOrder.js. When payment authorization fails, the system checks for gift-card usage and triggers the cancellation helper to prevent orphaned holds on customer gift-card balances.

// src/cartridges/int_adyen_SFRA/cartridge/controllers/middlewares/checkout_services/placeOrder.js
if (handlePaymentResult.error) {
    // Cancel partial payment order to refund gift cards if applicable
    let basketDataCleared = false;
    if (giftCardsAdded) {
        try {
            cancelPartialPaymentOrderHelper(currentBasket);
            basketDataCleared = true;
        } catch (error) {
            AdyenLogs.error_log('Failed to cancel partial payment order on payment failure:', error);
        }
    }
    // Continue with order failure handling...
}

The same helper function is exposed through a public controller endpoint (cancelPartialPaymentOrder) that client-side JavaScript in giftcards/index.js can invoke via window.cancelPartialPaymentOrderUrl when users manually cancel checkout.

Testing Your Refund Implementation

The repository includes comprehensive unit tests to validate refund behavior before deployment.

Full refund testing:

Partial refund testing:

Manual sandbox verification:

  1. Configure the Adyen webhook URL to point at your SFCC endpoint
  2. Trigger a full refund from the Adyen dashboard and confirm the order status changes to Not Paid
  3. Create a basket with a gift-card, simulate a payment failure (e.g., resultCode = REFUSED), and verify that cancelPartialPaymentOrderHelper restores the gift-card balance and clears session data

Summary

  • Full refunds are processed via the REFUND.js webhook handler, which sets orders to PAYMENT_STATUS_NOTPAID and creates audit trails
  • Cancel/Refund ambiguity is handled by CANCEL_OR_REFUND.js using identical logic but distinct logging for compliance tracking
  • Partial refunds require cancelPartialPaymentOrderHelper to call Adyen's cancellation API and restore gift-card balances
  • Checkout integration automatically triggers partial refund cleanup when payment authorization fails in placeOrder.js
  • Session cleanup is mandatory for partial refunds to prevent data leakage between checkout attempts

Frequently Asked Questions

How does the Adyen SFCC integration distinguish between full and partial refunds?

Full refunds are triggered by Adyen webhook notifications (REFUND or CANCEL_OR_REFUND) that invoke handlers in int_adyen_webhooks, while partial refunds for gift-card orders are handled proactively by cancelPartialPaymentOrderHelper in the checkout flow. The helper checks for basket.custom.partialPaymentOrderData to determine if a partial payment exists before attempting cancellation.

What happens to the order status when a refund webhook is received?

The order status changes to NOTPAID immediately upon receipt of a valid refund webhook. According to the source code in REFUND.js, the handler calls order.setPaymentStatus(Order.PAYMENT_STATUS_NOTPAID) before recording the change note and writing the info log.

Can customers reuse gift cards after a partial payment cancellation?

Yes, once cancelPartialPaymentOrderHelper successfully executes, the gift-card balance is restored on Adyen's platform and the local payment instrument is removed from the basket. The helper clears session.privacy.giftCardBalance and related variables, making the gift card available for immediate reuse.

Where should I look if refunds are not updating order statuses in Business Manager?

First, verify that the webhook endpoint in int_adyen_webhooks is receiving notifications and routing them to the correct handler. Check AdyenLogs entries for the string "Order {orderNo} was refunded" to confirm the REFUND.js handler executed. If processing partial refunds, ensure cancelPartialPaymentOrderHelper is being called and that basket.custom.partialPaymentOrderData contains valid JSON with the Adyen order reference.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →