How to Handle Manual Payment Reviews and Fraud Detection in Adyen Salesforce Commerce Cloud

The Adyen Salesforce Commerce Cloud integration automatically flags high-risk transactions for manual review when Adyen returns an AMBER fraud result, then processes merchant accept or reject decisions through dedicated webhook handlers while providing a pluggable fraud detection hook for pre-order risk checks.

The adyen/adyen-salesforce-commerce-cloud repository implements a two-layered security architecture to handle manual payment reviews and fraud detection. This approach leverages Adyen's risk engine to pause suspicious orders for human oversight while enabling merchants to inject custom fraud logic during the checkout flow before any payment authorization occurs.

Detecting Manual Review Triggers in Webhook Handlers

When Adyen's fraud engine identifies a transaction requiring human oversight, it returns the AMBER fraud result type. The integration detects this status in the AUTHORISATION webhook handler and halts automatic order placement until a merchant manually reviews the case in the Adyen Customer Area.

Identifying AMBER Status in AUTHORISATION.js

The webhook processor at src/cartridges/int_adyen_webhooks/cartridge/eventHandlers/AUTHORISATION.js (lines 25-44) inspects the additionalData.fraudResultType field to identify transactions flagged for manual review:

// excerpt from lines 25-44
const fraudResultType = webhookData['additionalData.fraudResultType'];
// ...
if (fraudResultType === constants.FRAUD_STATUS_AMBER) {
    order.trackOrderChange(
        'Order sent for manual review in Adyen Customer Area',
    );
} else {
    // normal success path → place order, etc.
}

The constant FRAUD_STATUS_AMBER is defined in src/cartridges/int_adyen_webhooks/cartridge/utils/constants.js at line 23:

FRAUD_STATUS_AMBER: 'AMBER',

When this condition triggers, the order remains in CREATED status and webhook processing stops immediately. No order confirmation is sent to the shopper until the merchant intervenes in the Adyen dashboard.

Processing Accept and Reject Decisions

Once a merchant reviews the transaction, Adyen sends follow-up webhooks that the integration handles through specific event handlers in the int_adyen_webhooks cartridge.

MANUAL_REVIEW_ACCEPT.js

Located at src/cartridges/int_adyen_webhooks/cartridge/eventHandlers/MANUAL_REVIEW_ACCEPT.js, this handler executes when a merchant approves a flagged transaction:

order.trackOrderChange(
    'Manual review is accepted in Adyen Customer Area, placing the order',
);
handleSuccessfulAuthorisation(order, result);

This invokes handleSuccessfulAuthorisation to transition the order to PAID status, trigger OMS export, and send the order confirmation email.

MANUAL_REVIEW_REJECT.js

Located at src/cartridges/int_adyen_webhooks/cartridge/eventHandlers/MANUAL_REVIEW_REJECT.js, this handler processes rejection decisions:

order.trackOrderChange(
    'Manual review is not accepted in Adyen Customer Area, failing the order',
);
order.setPaymentStatus(Order.PAYMENT_STATUS_NOTPAID);
Transaction.wrap(() => {
    OrderMgr.failOrder(order, false);
});

The order is explicitly set to NOTPAID and failed within a Transaction.wrap() block, ensuring atomic database updates while returning the shopper to the checkout error flow.

Implementing Pre-Order Fraud Detection

Beyond Adyen's post-authorisation risk checks, the integration supports custom fraud detection during checkout through a pluggable hook architecture that aborts suspicious transactions before payment processing begins.

The Fraud Detection Hook Architecture

The checkout controller at src/cartridges/int_adyen_SFRA/cartridge/controllers/middlewares/checkout_services/placeOrder.js implements fraud checking at two critical points (lines 56-66 and 200-207):

// Lines 56-66 – abort if a previous fraud check already failed
if (req.session.privacyCache.get('fraudDetectionStatus')) {
    res.json({
        error: true,
        cartError: true,
        redirectUrl: URLUtils.url('Error-ErrorCode', 'err', '01').toString(),
        errorMessage: Resource.msg('error.technical', 'checkout', null)
    });
    return next();
}

// Lines 200-207 – invoke the hook for the current basket
var fraudDetectionStatus = hooksHelper(
    'app.fraud.detection',
    'fraudDetection',
    currentBasket,
    require('*/cartridge/scripts/hooks/fraudDetection').fraudDetection
);
if (fraudDetectionStatus.status === 'fail') {
    Transaction.wrap(() => { OrderMgr.failOrder(order, true); });
    req.session.privacyCache.set('fraudDetectionStatus', true);
    // ...
}

The hooksHelper utility dynamically routes to the implementation specified in scripts/hooks/fraudDetection.js without requiring modifications to core controller logic.

Custom Fraud Logic Implementation

Merchants can override the default stub by creating src/cartridges/app_adyen_SFRA/cartridge/scripts/hooks/fraudDetection.js:

'use strict';
module.exports = {
    fraudDetection: function (basket) {
        // Example: block orders over $5,000
        var threshold = 500000; // in minor units (e.g. cents)
        if (basket.totalGrossPrice.value > threshold) {
            return { status: 'fail', errorCode: '01' };
        }
        return { status: 'success' };
    }
};

This implementation executes automatically during the placeOrder middleware flow, allowing validation of basket totals, shipping addresses, device fingerprints, or third-party risk service integrations.

Summary

  • AMBER Detection: The AUTHORISATION.js webhook handler (lines 25-44) detects FRAUD_STATUS_AMBER from Adyen's risk engine and pauses order placement by tracking the change and stopping processing, leaving orders in CREATED status.
  • Manual Review Handlers: Dedicated MANUAL_REVIEW_ACCEPT.js and MANUAL_REVIEW_REJECT.js handlers finalize orders based on merchant decisions, invoking handleSuccessfulAuthorisation for accepts or OrderMgr.failOrder for rejects.
  • Pre-Order Protection: The placeOrder.js controller (lines 56-66, 200-207) executes the app.fraud.detection hook before payment authorization, enabling custom risk rules to fail orders via Transaction.wrap().
  • Transaction Safety: All order state changes execute within Transaction.wrap() wrappers to ensure database consistency and prevent partial order creation.

Frequently Asked Questions

What triggers a manual payment review in the Adyen Salesforce Commerce Cloud integration?

Adyen's fraud risk engine returns an AMBER result type for transactions requiring human oversight. The AUTHORISATION.js webhook handler detects this via the additionalData.fraudResultType field and pauses the order by calling order.trackOrderChange(), leaving the order in CREATED status until the merchant accepts or rejects it in the Adyen Customer Area.

How does the integration handle accepted manual reviews?

When a merchant clicks Accept in the Adyen dashboard, Adyen sends a MANUAL_REVIEW_ACCEPT webhook. The MANUAL_REVIEW_ACCEPT.js handler executes handleSuccessfulAuthorisation(order, result), which transitions the order to PAID status, triggers OMS export, and sends the order confirmation email to the shopper.

Can merchants add custom fraud checks before the payment is processed?

Yes. The integration includes a pluggable app.fraud.detection hook invoked by placeOrder.js during checkout (lines 200-207). Merchants can implement custom logic in scripts/hooks/fraudDetection.js to validate basket values, shipping addresses, or device fingerprints, returning { status: 'fail' } to abort the order via OrderMgr.failOrder() before any payment authorization occurs.

What happens to the order when a manual review is rejected?

The MANUAL_REVIEW_REJECT.js handler sets the payment status to NOTPAID using order.setPaymentStatus(Order.PAYMENT_STATUS_NOTPAID), then fails the order within a transaction wrapper via OrderMgr.failOrder(order, false). This prevents order confirmation and returns the shopper to the checkout error page with a technical error message.

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 →