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
REFUNDwebhook - 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_NOTPAIDimmediately removes the paid status from the ordertrackOrderChange()creates a permanent record in the order historyAdyenLogs.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:
- Removes gift-card payment instruments from the basket
- Clears Adyen-specific basket data using
clearForms.clearAdyenBasketData() - Nullifies session privacy variables that track gift-card responses and balances
- 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:
- Location:
src/cartridges/int_adyen_webhooks/cartridge/eventHandlers/__tests__/REFUND.test.js - Validates that
setPaymentStatus(),trackOrderChange(), and logging all execute correctly
Partial refund testing:
- Location:
src/cartridges/int_adyen_SFRA/cartridge/adyen/scripts/partialPayments/__tests__/cancelPartialPaymentOrder.test.js - Tests both success paths (resultCode
RECEIVED) and failure handling (AdyenError throwing)
Manual sandbox verification:
- Configure the Adyen webhook URL to point at your SFCC endpoint
- Trigger a full refund from the Adyen dashboard and confirm the order status changes to Not Paid
- Create a basket with a gift-card, simulate a payment failure (e.g.,
resultCode = REFUSED), and verify thatcancelPartialPaymentOrderHelperrestores the gift-card balance and clears session data
Summary
- Full refunds are processed via the
REFUND.jswebhook handler, which sets orders toPAYMENT_STATUS_NOTPAIDand creates audit trails - Cancel/Refund ambiguity is handled by
CANCEL_OR_REFUND.jsusing identical logic but distinct logging for compliance tracking - Partial refunds require
cancelPartialPaymentOrderHelperto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →