---
title: 3DS統合ガイド
slug: eUVb-3ds
createdAt: 2025-07-30T21:22:24.108Z
---

# Forterカートリッジに3DSecureを統合する

3DSの推奨事項と統合するために、商人は **ForterOrder.ds** ファイルを修正して、レスポンスボディに新しい `"recommendation"` フィールドを処理し、追加の3DS認証レスポンスデータをポスト認証リクエストボディに渡す必要があります。

## 1. APIスキーマの更新

専任のForter実装チームとの3DS統合を確認したら、リクエストボディに追加フィールドを含めるようにスキーマを修正し、`payment[0].creditCard` オブジェクトにおいて、事前認証コールでForterから返されるレスポンスオブジェクトに `"recommendations"` キーを公開できるようにします。

実装チームが関連するAPIバージョンを指示し、カートリッジの **cartridges > int\_forter > cartridge > scripts > init > forterServiceInit.js** ファイル内で、どのバージョンを指すべきかを指定できます。デフォルトの `Option .86` を修正することで、 `svc.addHeader("api-version", "Option .86");`

## 2. プリオーソリを有効にする

Forter Cartridge内の `CheckoutServices.js` ファイルに移動します。このファイルへの正確なパスは:
**Cartridges** > **int\_forter\_sfra** > **cartridge** > **controllers** > **CheckoutService.js** です。

このファイルを開き、 **コメントを解除**して、次のコードを *AFTER* `var order = COHelpers.createOrder(currentBasket);` 関数の後に、 *BEFORE* `var handlePaymentResult = COHelpers.handlePayments(order, order.orderNo);` 関数の前に追加します。

:::ExpandableHeading
### Full CheckoutServices.js file with Pre-auth

```javascript
'use strict';

var server = require('server');
server.extend(module.superModule);

var COHelpers      = require('*/cartridge/scripts/checkout/checkoutHelpers');
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');
var ForterConfig   = require('int_forter_mfra/cartridge/scripts/lib/forter/ForterConfig.ds').ForterConfig;

server.replace('PlaceOrder', server.middleware.https, function (req, res, next) {
    var BasketMgr = require('dw/order/BasketMgr');
    var HookMgr = require('dw/system/HookMgr');
    var OrderMgr = require('dw/order/OrderMgr');
    var Resource = require('dw/web/Resource');
    var Transaction = require('dw/system/Transaction');
    var URLUtils = require('dw/web/URLUtils');
    var basketCalculationHelpers = require('*/cartridge/scripts/helpers/basketCalculationHelpers');

    var currentBasket = BasketMgr.getCurrentBasket();

    if (!currentBasket) {
        res.json({
            error: true,
            cartError: true,
            fieldErrors: [],
            serverErrors: [],
            redirectUrl: URLUtils.url('Cart-Show').toString()
        });
        return next();
    }

    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();
    }

    var validationBasketStatus = HookMgr.callHook(
        'app.validate.basket',
        'validateBasket',
        currentBasket,
        false
    );

    if (validationBasketStatus.error) {
        res.json({
            error: true,
            errorMessage: validationBasketStatus.message
        });
        return next();
    }

    // Check to make sure there is a shipping address
    if (currentBasket.defaultShipment.shippingAddress === null) {
        res.json({
            error: true,
            errorStage: {
                stage: 'shipping',
                step: 'address'
            },
            errorMessage: Resource.msg('error.no.shipping.address', 'checkout', null)
        });
        return next();
    }

    // Check to make sure billing address exists
    if (!currentBasket.billingAddress) {
        res.json({
            error: true,
            errorStage: {
                stage: 'payment',
                step: 'billingAddress'
            },
            errorMessage: Resource.msg('error.no.billing.address', 'checkout', null)
        });
        return next();
    }

    // Calculate the basket
    Transaction.wrap(function () {
        basketCalculationHelpers.calculateTotals(currentBasket);
    });

    // Re-validates existing payment instruments
    var validPayment = COHelpers.validatePayment(req, currentBasket);
    if (validPayment.error) {
        res.json({
            error: true,
            errorStage: {
                stage: 'payment',
                step: 'paymentInstrument'
            },
            errorMessage: Resource.msg('error.payment.not.valid', 'checkout', null)
        });
        return next();
    }

    // Re-calculate the payments.
    var calculatedPaymentTransactionTotal = COHelpers.calculatePaymentTransaction(currentBasket);
    if (calculatedPaymentTransactionTotal.error) {
        res.json({
            error: true,
            errorMessage: Resource.msg('error.technical', 'checkout', null)
        });
        return next();
    }

    // Creates a new order.
    var order = COHelpers.createOrder(currentBasket);
    if (!order) {
        res.json({
            error: true,
            errorMessage: Resource.msg('error.technical', 'checkout', null)
        });
        return next();
    }
    // add or uncomment the new pre-auth code here:
    var argOrderValidate = {
                Order: order,
                orderValidateAttemptInput: 1
            },
            forterController = require('int_forter/cartridge/controllers/ForterValidate'),
            forterDecision   = forterController.ValidateOrder(argOrderValidate);
        // in case if no response from Forter, try to call one more time
        if (forterDecision.result === false && forterDecision.orderValidateAttemptInput == 2) {
            var argOrderValidate = {
                    Order: order,
                    orderValidateAttemptInput: 2
                },
                forterController = require('int_forter/cartridge/controllers/ForterValidate'),
                forterDecision   = forterController.ValidateOrder(argOrderValidate);
        }
        
        if (forterDecision.JsonResponseOutput.processorAction == 'void') {
            if (!empty(forterDecision.PlaceOrderError)) {
                return {error : true, forterErrorCode : forterDecision.PlaceOrderError};
            } else {
                return {error : true};
            }
        }  
        // end of pre-auth code

    var handlePaymentResult = COHelpers.handlePayments(order, order.orderNo);     // Handles payment authorization
    if (handlePaymentResult.error) {
        if (ForterConfig.forterShowDeclinedPage == true && !empty(ForterConfig.forterCustomDeclineMessage)) {
            res.json({
                error: true,
                errorMessage: ForterConfig.forterCustomDeclineMessage
            });
        } else {
            res.json({
                error: true,
                errorMessage: Resource.msg('error.technical', 'checkout', null)
            });
        }
        return next();
    }
    
    var fraudDetectionStatus = HookMgr.callHook('app.fraud.detection', 'fraudDetection', currentBasket);
    if (fraudDetectionStatus.status === 'fail') {
        Transaction.wrap(function () { OrderMgr.failOrder(order); });

        // fraud detection failed
        req.session.privacyCache.set('fraudDetectionStatus', true);

        res.json({
            error: true,
            cartError: true,
            redirectUrl: URLUtils.url('Error-ErrorCode', 'err', fraudDetectionStatus.errorCode).toString(),
            errorMessage: Resource.msg('error.technical', 'checkout', null)
        });

        return next();
    }

    // Places the order
    var placeOrderResult = COHelpers.placeOrder(order, fraudDetectionStatus);
    if (placeOrderResult.error) {
        res.json({
            error: true,
            errorMessage: Resource.msg('error.technical', 'checkout', null)
        });
        return next();
    }

    COHelpers.sendConfirmationEmail(order, req.locale.id);

    // Reset usingMultiShip after successful Order placement
    req.session.privacyCache.set('usingMultiShipping', false);

    // TODO: Exposing a direct route to an Order, without at least encoding the orderID
    //  is a serious PII violation.  It enables looking up every customers orders, one at a
    //  time.
    res.json({
        error: false,
        orderID: order.orderNo,
        orderToken: order.orderToken,
        continueUrl: URLUtils.url('Order-Confirm').toString()
    });

    return next();
});


module.exports = server.exports();
```

***
:::

## 3. ビジネスマネージャーを更新する

上記に追加したコードは、あなたの **CheckoutServices.js** ファイルが、決済ゲートウェイへの認証呼び出しが行われる **前に**、注文情報をForterエンドポイントに送信できるようにします。

また、あなたは **Forter Business Manager拡張機能** を更新して、この更新された事前認証フローのためにカスタマイズされたエラーメッセージ（拒否の場合）を表示する必要があります。

## 4. 推奨事項の処理

あなたは、3DSを必要とする注文のForterレスポンスに含まれる新しい `"recommendation"` フィールドを処理するために、Forter SFCCカートリッジにロジックを追加する必要があります。

商人は、Forterカートリッジ（**ForterValidate.js**) およびゲートウェイカートリッジ内で、決定と推奨事項のすべての可能な反復を処理するロジックを含めることを確認する必要があります。これにより、Forterからの事前認証の決定と推奨を受け取った後に、注文が正しくルーティングされることが保証されます。

- の場合は **3DS for regulation**, Forterの事前承認決定（"アクション"）は `"承認"` となり、 populated `"推奨事項"` フィールドが含まれます。
- の場合は **3DS for risk assessment**, Forterの事前承認決定（"アクション"）は `"拒否"` となり、 populated `"推奨事項"` フィールドが含まれます。

あなたのSFCCカートリッジで、 `forterValidate` ファイルに移動し、 **cartridges/int\_forter\_sfra/cartridge/scripts/pipelets/forter/forterValidate.js** のロジックを追加して、推奨事項フィールドが空でない値（""）で populated されている場合の決定と推奨事項の両方を処理します。

推奨される値は次のとおりです:

- `"VERIFICATION_REQUIRED_3DS_CHALLENGE"`
- `""` （Forterが明確な承認のために3DSまたは免除を推奨しない場合）
- `"REQUEST_SCA_EXCLUSION_MOTO"`
- `"REQUEST_SCA_EXCLUSION_PREPAID"`
- `"RECOMMENDATION_REQUEST_SCA_EXCLUSION_ONE_LEG_OUT"`
- `"REQUEST_SCA_EXCLUSION_ONE_LEG_OUT"`
- `"REQUEST_SCA_EXCLUSION_MIT"`
- `"REQUEST_SCA_EXEMPTION_TRA"`
- `"RECOMMENDATION_REQUEST_SCA_EXEMPTION_LOW_VALUE"`
- `"REQUEST_SCA_EXEMPTION_LOW_VALUE"`
- `"REQUEST_SCA_EXEMPTION_CORP"`
- `"RECOMMENDATION_REQUEST_SCA_EXEMPTION_TRUSTED_BENEFICIARY"`
- `"REQUEST_SCA_EXEMPTION_TRUSTED_BENEFICIARY"`

ロジックは、現在のオプションと意思決定およびレスポンスボディフィールドの処理ロジックがある137行目の`validateOrder(args)`関数内で修正する必要があります。

:::ExpandableHeading
### 例：注文の検証(args)の修正

```javascript
    if (fResponse.actionEnum === 'DECLINED' && fResponse.recommendation[0] === undefined) { // 修正に応じて
        if (sitePrefs.forterCancelOrderOnDecline === true) {
            fResponse.processorAction = 'void';
            if (sitePrefs.forterShowDeclinedPage === true && sitePrefs.forterCustomDeclineMessage) {
                resp.PlaceOrderError = {
                    code: sitePrefs.forterCustomDeclineMessage
                };
            }
        } else {
            fResponse.processorAction = 'skipCapture';
        }
    } else if (fResponse.actionEnum === 'NOT_REVIEWED') {
        fResponse.processorAction = 'notReviewed';
    } else if (fResponse.actionEnum === 'APPROVED' && fResponse.recommendation[0] == "") { // 推奨フィールドに推奨がない場合の処理ロジックを追加
        fResponse.processorAction = sitePrefs.forterAutoInvoiceOnApprove ? 'capture' : 'skipCapture';
    } else if (fResponse.actionEnum === 'APPROVED' && fResponse.recommendation[0] != "REQUEST_SCA_EXEMPTION_LOW_VALUE") { //免除のための3DS推奨を処理/保存するロジック
    fResponse.processorAction = sitePrefs.initiate3DSChallenge // initiate3DSChallengeは、あなたのdwサイト 'dw/system/Site' に追加されたカスタム設定です
    } else if (fResponse.actionEnum === 'DECLINED' && fResponse.recommendation[0] != "VERIFICATION_REQUIRED_3DS_CHALLENGE") { //3DS推奨を処理/保存するロジック 
    fResponse.processorAction = sitePrefs.initiate3DSChallenge // initiate3DSChallengeは、あなたのdwサイト 'dw/system/Site' に追加されたカスタム設定です
    }
```
:::

## 5. 支払いゲートウェイカートリッジへのコード修正

上記の例に示されているように、あなたのSFCCサイトの設定を修正する必要があります。**dw > system > Site**新しい`"recommendations"`フィールドをレスポンスボディで処理するために。上記の例では、機能は`initiate3DSChallenge`と呼ばれています。この機能により、あなたのSFCCプラットフォームは推奨を受け入れ、支払いゲートウェイから3DSチャレンジをトリガーすることができるはずです。

各ゲートウェイには異なる3DSチャレンジ要件と構成があります。各マーチャントは、推奨をゲートウェイに統合し、Forterから提供された推奨に基づいて顧客に関連する3DSチャレンジを構成する責任があります。

## 6. 認証後のオプション

2回目の同期認証後の決定を行うには、ゲートウェイからの認証および3DS応答データを含むようにForterOrder.dsファイルのマッピングを更新し、ForterValidate.jsファイルも2回目の認証後のForter決定を処理するようにする必要があります。

認証後の注文ステータス呼び出しを *新しいForter決定（"アクション"）なしで*行う場合、対応する **ForterUpdate.js**ジョブと認証応答のマッピングを修正します `ForterPayment()` 関数内で。

### オプション 1: 認証後の検証呼び出し

ゲートウェイが3DSを正常に開始し、支払いを承認（または拒否）した後、結果はForterカートリッジへのポスト承認呼び出しに含める必要があります。これらのフィールドは、`ForterCreditCard(auth, cc)`関数のマッピングに追加する必要があります。あなたの**ForterOrder.ds**ファイル。このフローは、Forterの支払い推奨（3DSまたはPSD2）を受け取っていない可能性のある商人に適用されますが、プロセッサからの支払い結果をForterのモデルに提供したいと考えています。

:::ExpandableHeading
### Example Modified ForterCreditCard() with 3DS results

```javascript
function ForterCreditCard(auth, cc) {
    var processorResponseCode = '';
    var processorResponseText = '';
    var creditCardExpMonth;

    // format the expiration month. from 1 to 01, etc.
    if (cc.creditCardExpirationMonth.toString().length === 1) {
        creditCardExpMonth = '0' + cc.creditCardExpirationMonth.toString();
    } else {
        creditCardExpMonth = cc.creditCardExpirationMonth.toString();
    }

    this.nameOnCard = cc.creditCardHolder;
    this.cardBrand = cc.creditCardType;

    if (cc.creditCardNumber.substring(0, 6).indexOf('*') > -1) {
        this.bin = session.forms.billing.creditCardFields.cardNumber.value.substring(0, 6); // session.forms.billing.paymentMethods.creditCard.number.value.substring(0, 6);
    } else {
        this.bin = cc.creditCardNumber.substring(0, 6);
    }

    this.lastFourDigits = cc.creditCardNumberLastDigits;
    this.expirationMonth = creditCardExpMonth;
    this.expirationYear = cc.creditCardExpirationYear.toString();

    this.verificationResults = {};
    this.verificationResults.avsFullResult = auth ? auth.avsResultCode.toString() : '';  
    this.verificationResults.cvvResult = auth ? auth.cvvResultCode.toString() : ''; 
    this.verificationResults.authorizationCode = auth ? auth.authCode.toString() : '';

    // ******************************** new 3DS response data ********************************    
    this.verificationResultsauthorizationPolicy =  "3DSecure", //can be hardcoded
    this.verificationResults.cavvResult = auth.body ? replyMessage.decisionEarlyReply.rcode,
    this.verificationResults.eciValue auth.body ? auth.payerAuthValidationReply.eciRaw,
    this.verificationResults.threeDsStatus = auth.payerAuthValidationReply.authenticationMessage || '',
    this.verificationResults.threeDsStatusCode = auth.payerAuthValidationReply.authenticationStatusCodeRaw || '',
    this.verificationResults.threeDsStatusReasonCode = auth.body.status,
    this.liabilityShift = auth.payerAuthValidationReply ? true : false, //should be a boolean value
    threeDsVersion = auth.payerAuthValidationRepl.auth.version
    this.verificationResults.threeDsInteractionMode = "FRICTIONLESS", // modify according to 3ds used
    this.verificationResults.threeDsChallengeCancelCode = replyMessage.decisionEarlyReply.challengeCodeNumeric || '',
    this.verificationResults.authenticationChallengeStartTime = new Number((order.header.Timestamp/1000).toFixed()); , // should be timestamp as int in unix format
    this.verificationResults.authenticationTriggered =  auth.payerAuthValidationReply ? true : false, //should be a boolean value
    this.verificationResults.external3dsVendorPayload =  {}, // optional
    this.verificationResults.external3dsVendorPayloadBeforeChallenge =  {}, // optional 
    this.verificationResults.authenticationMethodType = "THREE_DS", // can be hardcoded if this is the only auth method
    this.verificationResults.authorizationProcessedWith3DS =  auth.payerAuthValidationReply ? true : false, //should be a boolean value
    this.verificationResults.partyExecuted3DS =  "PSP" // add relevant vendor(s)
    // **************************** end of new 3DS response data ***************************
    },
    this.paymentGatewayData = {};
    this.paymentGatewayData.gatewayName = 'AUTHORIZE.NET';                   
    this.paymentGatewayData.gatewayTransactionId = auth ? auth.transId.toString() : '';  

    if (auth && auth.errors.errorCode.length > 0) {                            
        processorResponseCode = auth.errors.errorCode.toString();      
        processorResponseText = auth.errors.errorText.toString();      
    } else if (auth && auth.messages.code.length > 0) {                        
        processorResponseCode = auth.messages.code.toString();         
        processorResponseText = auth.messages.description.toString();  
    }

    this.verificationResults.processorResponseCode = processorResponseCode;
    this.verificationResults.processorResponseText = processorResponseText;
}
```
:::

### オプション2：認証後の注文ステータスコール

**ステップ 1: 認証後のステータス更新スクリプトを含める**&#x20;
以下の例のAuthorize.netスクリプトは、クレジットカード処理を扱うスクリプトを更新する方法を示しており、関連する認証後および3DS注文ステータスの呼び出しをForterに含めるようになっています。デフォルトの2つの注文ステータスは次のとおりです:

- **"処理中"** (ゲートウェイが支払いを承認したとき)
- **"商人によってキャンセルされた"** (ゲートウェイ/プロセッサが支払いを承認していないとき)

以下のコード例は、すべてのSFRAおよびSGの事前認証/ステータス実装に適用され、完全な `postAuthOrderStatusUpdate()` 関数はforterValidate.jsファイルにあります (**cartridges** > **int\_forter\_sfra** > **cartridge** > **scripts** > **pipelets** > **forter** > **forterValidate.js**)

```javascript

    var argOrderUpdate = { 
        orderNumber: orderNumber,
        updateAttempt: 1 
    },
    forterCall = require('*/cartridge/scripts/pipelets/forter/forterValidate'),
    forterDecision = forterCall.postAuthOrderStatusUpdate(argOrderUpdate, "PROCESSING");

    if (forterDecision.result === false && forterDecision.updateAttempt == Option ) { 
        forterDecision.updateAttempt = Option ; 
        forterCall.postAuthOrderStatusUpdate(argOrderUpdate, "PROCESSING");
    }

```

**ステップ 2: ForterUpdate.js ファイルをカスタマイズする**&#x20;
事前認証コードのコメントを解除した後、`CheckoutServices.js`ファイルをカスタマイズして、ゲートウェイの認証と 3DS 応答を Forter へのステータスコールに含めます。`forterUpdate.js`ファイルの正確なパスは、**cartridges** > **int\_forter\_sfra** > **cartridge** > **scripts** > **lib** > **forter** > **dto** > **forterUpdate.js**) です。`ForterPayment(payment, order)` 関数をカスタマイズして、特定のゲートウェイ認証応答をマッピングします。

:::ExpandableHeading
### Example ForterCreditCard() auth function

````text
```javascript

function ForterCreditCard(auth, cc) {
    var processorResponseCode = '';
    var processorResponseText = '';
    var creditCardExpMonth;

    // format the expiration month. from 1 to 01, etc.
    if (cc.creditCardExpirationMonth.toString().length === 1) {
        creditCardExpMonth = '0' + cc.creditCardExpirationMonth.toString();
    } else {
        creditCardExpMonth = cc.creditCardExpirationMonth.toString();
    }

    this.nameOnCard = cc.creditCardHolder;
    this.cardBrand = cc.creditCardType;

    if (cc.creditCardNumber.substring(0, 6).indexOf('*') > -1) {
        this.bin = session.forms.billing.creditCardFields.cardNumber.value.substring(0, 6); // session.forms.billing.paymentMethods.creditCard.number.value.substring(0, 6);
    } else {
        this.bin = cc.creditCardNumber.substring(0, 6);
    }

    this.lastFourDigits = cc.creditCardNumberLastDigits;
    this.expirationMonth = creditCardExpMonth;
    this.expirationYear = cc.creditCardExpirationYear.toString();

    this.verificationResults = {};
// ******************************** new 3DS response data ********************************    
    this.verificationResultsauthorizationPolicy =  "3DSecure", //can be hardcoded
    this.verificationResults.cavvResult = auth.body ? replyMessage.decisionEarlyReply.rcode,
    this.verificationResults.eciValue auth.body ? auth.payerAuthValidationReply.eciRaw,
    this.threeDsStatus = auth.payerAuthValidationReply.authenticationMessage || '',
    this.threeDsStatusCode = auth.payerAuthValidationReply.authenticationStatusCodeRaw || '',
    this.threeDsStatusReasonCode = auth.body.status,
    this.liabilityShift = auth.payerAuthValidationReply ? true : false, //should be a boolean value
    this.threeDsVersion = auth.payerAuthValidationRepl.auth.version
    this.threeDsInteractionMode = "FRICTIONLESS", // modify according to 3ds used
    this.threeDsChallengeCancelCode = replyMessage.decisionEarlyReply.challengeCodeNumeric || '',
    this.authenticationChallengeStartTime = new Number((order.header.Timestamp/1000).toFixed()); , // should be timestamp as int in unix format
    this.authenticationTriggered =  auth.payerAuthValidationReply ? true : false, //should be a boolean value
    this.external3dsVendorPayload =  {}, // optional
    this.external3dsVendorPayloadBeforeChallenge =  {}, // optional 
    this.authenticationMethodType = "THREE_DS", // can be hardcoded if this is the only auth method
    this.authorizationProcessedWith3DS =  auth.payerAuthValidationReply ? true : false, //should be a boolean value
    this.partyExecuted3DS =  "PSP"

    this.verificationResults.avsFullResult = auth ? auth.avsResultCode.toString() : '';    
    this.verificationResults.cvvResult = auth ? auth.cvvResultCode.toString() : ''; 
    this.verificationResults.authorizationCode = auth ? auth.authCode.toString() : '';    
    this.paymentGatewayData = {};
    this.paymentGatewayData.gatewayName = 'AUTHORIZE.NET';                
    this.paymentGatewayData.gatewayTransactionId = auth ? auth.transId.toString() : '';  

    if (auth && auth.errors.errorCode.length > 0) {                            
        processorResponseCode = auth.errors.errorCode.toString();      
        processorResponseText = auth.errors.errorText.toString();      
    } else if (auth && auth.messages.code.length > 0) {                        
        processorResponseCode = auth.messages.code.toString();         
        processorResponseText = auth.messages.description.toString();  
    }

    this.verificationResults.processorResponseCode = processorResponseCode;
    this.verificationResults.processorResponseText = processorResponseText;
    }   
};
````
:::
