Authorizing a payment

When you authorize the payment of an Invoice, you confirm that the customer has enough money in their bank account to settle the payment and then reserve that money (ie, stop it from being used to pay for anything else). It is only when you capture the payment in a separate stage that you confirm and book the transfer of money (which will typically take a few days to complete). Note that you can capture a payment up to 7 days after authorization, which may be useful for fraud prevention or other checks. You can also specify an automatic delayed capture for a payment at the time of authorization.

Client-side and server-side authorization

For Cardholder Initiated Transactions (CITs), you must authorize a payment with 3DS on the client side after the customer has provided their payment details. This is necessary to comply with EU legislation on Strong Customer Authentication (SCA).

Merchant Initiated Transactions (MITs) are authorized from the server side. This is an optional step if you want to take payment on an MIT immediately (ie, capturing the payment will implicitly authorize it first). However, you can still authorize explicitly if you want to perform checks first and then capture later. Note that banks will usually only approve an MIT if it was initially set up with a CIT (which requires client-side authorization, as described above).

Client-side authorization

You must already have an Invoice ID and a payment source (a card token or a customer token) to authorize a payment.

First, see the page about setting up the APIs for details of how to install and access the Smart Router libraries for your platform.

Use the ProcessOut.makeCardPayment() function to authorize a payment in your web page or mobile app, as shown in the example below. The payment source token (passed as a parameter to makeCardPayment()) can either be a one-time card token or a Customer token that represents stored card details. See the page about saving a token to capture future payments to learn how to create a Customer token.

Note that the handler object used in the mobile code samples is described in the next section. Also, we use the Adyen 3DS2 SDK as an example here but you can just as easily use an SDK from any other PSP (see below)

function processoutCardTokenized(token) {
    // make sure `invoiceID` generated from
    // your backend is available in this scope
    client.makeCardPayment(invoiceID, token, {
        authorize_only: false, // set to true if you don’t want to capture directly
        // If you want to offer the customer a preferred scheme
        // to pay on (for example, if the customer's card supports
        // co-schemes such as carte bancaire)
        preferred_scheme: "carte bancaire"
    }, function(iv) {
        var field   = document.createElement("input");
        field.type  = "hidden";
        field.name  = "invoice_id";
        field.value = iv;

        // Enable back the button
        document.getElementById("paymentBtn").disabled = false;

        // We add the invoice_id input so that it’s sent back to the server.
        // The only thing left to do is to submit the form
        formElement.appendChild(field);
        formElement.submit();
    }, function(err) {
        document.getElementById("errors").innerHTML = err.message;
    });
}
var authorizationRequest = AuthorizationRequest(
    source: "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ",
    invoiceID: "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT"
);

// Here, we are using the Adyen 3DS2 SDK as an example, but
// ProcessOut is agnostic about the SDK you use to handle
// 3DS2.
authorizationRequest.thirdPartySDKVersion = ThreeDS2Service.ADY3DS2SDKVersion()

// optional
authorizationRequest.preferredScheme = "carte bancaire"

ProcessOut.makeCardPayment(
    authorizationRequest,
    handler,
    with: self
);
AuthorizationRequest authorizationRequest = new AuthorizationRequest(
    "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ", // source
    "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT" // invoiceId
);

// Here, we are using the Adyen 3DS2 SDK as an example, but
// ProcessOut is agnostic about the SDK you use to handle
// 3DS2.
authorizationRequest.setThirdPartySDKVersion(ThreeDS2Service.getSDKVersion())

// optional
authorizationRequest.setPreferredScheme("carte bancaire")

client.makeCardPayment(
    authorizationRequest,
    handler
);

You can supply a number of options to ProcessOut.makeCardPayment() with the options object in JavaScript and with the AuthorizationRequest object in Swift and Java. The available options are shown in the table below.

authorize_only
Set to true if you want to authorize payment without capturing. Note that you must capture payment on the server if you use this option.
allow_fallback_to_sale
This flag must be used in conjunction with authorize_only. Set to true in order to immediately perform a capture if the PSP does not support authorizing and capturing payments separately.
auto_capture_at
Date and time in ISO 8601 format specifying when to capture the payment (see Automatic capture after a delay below).
capture_amount
Amount of money to capture when partial captures are available (see Capturing an invoice in the API reference for details). Note that this only applies if you are also using the auto_capture_at option.
preferred_scheme
Card scheme or co-scheme that should get priority if it is available.

3DS2 handlers for mobile apps

Most PSPs have their own certified SDKs for 3DS2 in mobile apps but they all have equivalent features.

To abstract the details of specific 3DS2 SDKs, we define a ThreeDSHandler object that supplies the functionality in a consistent way (this is the handler parameter in the code samples above). The object is specified as an interface in Java and as a protocol in Swift. We officially support our own implementations of ThreeDSHandler for a simple test handler and for the Adyen service. For other providers, you can create your own ThreeDSHandler implementation using our code as a starting point. See the Adyen example below and also see our GitHub repositories for the definition of ThreeDSHandler on Android and iOS.

The ThreeDSTestHandler class emulates the normal 3DS authentication flow but does not actually make any calls to a real Access Control Server (ACS). It is mainly useful during development in our sandbox testing environment.

The example below shows how you can create an instance of ThreeDSTestHandler to pass as the handler parameter of makeCardPayment().

let handler = ProcessOut.createThreeDSTestHandler(viewController: self, completion: { (invoiceId, error) in
    if invoiceId != nil {
        // The authentication was done successfully, you can send
        // back the invoice ID to your backend to validate the payment
        print("invoice: " + invoiceId)
    } else {
        // An error occurred
        print(error)
    }
})
final ThreeDSHandler handler = ProcessOut.createDefaultTestHandler(
    MainActivity.this,
    new ProcessOut.ThreeDSHandlerTestCallback() {
        @Override
        public void onSuccess(String invoiceId) {
            // The authentication was done successfully, you can send
            // back the invoice ID to your backend to validate the payment
            Log.d("PROCESSOUT", "invoice: " + invoiceId);
        }

        @Override
        public void onError(Exception error) {
            // An error occurred
            Log.e("PROCESSOUT", error.toString());
        }
    }
);

For Adyen, you must add the version number of the Adyen SDK to the AuthorizationRequest object when you make the call to makeCardPayment(). Adyen provides a method in its ThreeDS2Service class for this purpose, which is called ADY3DS2SDKVersion() in Swift and getSDKVersion() in Java. The example below also demonstrates how you can change the preferred card scheme (for example, to “carte bancaire”) if the customer’s card supports co-schemes.

...

var authorizationRequest = AuthorizationRequest(
    source:"card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ",
    invoiceID: "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT"
);

// optional
authorizationRequest.thirdPartySDKVersion = ThreeDS2Service.ADY3DS2SDKVersion()
authorizationRequest.preferredScheme = "carte bancaire"

ProcessOut.makeCardPayment(
    authorizationRequest,
    handler,
    with: self
);

...
...

AuthorizationRequest authorizationRequest = new AuthorizationRequest(
    "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ", // source
    "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT" // invoiceId
);

// optional
authorizationRequest.setThirdPartySDKVersion(ThreeDS2Service.getSDKVersion())
authorizationRequest.setPreferredScheme("carte bancaire")

client.makeCardPayment(
    authorizationRequest,
    handler
);

...

Finally, the code below shows how to implement ThreeDSHandler with your own class. Pass an instance of the class as the handler parameter to makeCardPayment().

import ProcessOut
import Adyen3DS2

class AdyenThreeDSHandler: ThreeDSHandler {
    var transaction: ADYTransaction?

    public func doPresentWebView(webView: ProcessOutWebView) {
        // Called when a web challenge is required. Present 
        // the webview to the user
    }

    func doFingerprint(directoryServerData: DirectoryServerData, completion: @escaping (ThreeDSFingerprintResponse) -> Void) {
        let parameters = ADYServiceParameters()
        parameters.directoryServerIdentifier = directoryServerData.directoryServerID
        parameters.directoryServerPublicKey = directoryServerData.directoryServerPublicKey

        ADYService.service(with: parameters, appearanceConfiguration: nil, completionHandler: {(service: ADYService) -> Void
            in
            do {
                self.transaction = try service.transaction(withMessageVersion: directoryServerData.messageVersion)
                    let authReqParams = self.transaction!.authenticationRequestParameters
                    if let sdkEphemPubKeyData = authReqParams.sdkEphemeralPublicKey.data(using: .utf8) {
                        let sdkEphemPubKey = try JSONDecoder().decode(ThreeDSFingerprintResponse.SDKEphemPubKey.self, from: sdkEphemPubKeyData)
                        let fingerprintResponse = ThreeDSFingerprintResponse(
                            sdkEncData: authReqParams.deviceInformation,
                            sdkAppID: authReqParams.sdkApplicationIdentifier,
                            sdkEphemPubKey: sdkEphemPubKey,
                            sdkReferenceNumber: authReqParams.sdkReferenceNumber,
                            sdkTransID: authReqParams.sdkTransactionIdentifier
                        )

                        completion(fingerprintResponse)
                    } else {
                        print("Could not encode sdkEphem")
                    }
            } catch {
                print(error)
            }
        })
    }

    func doChallenge(authentificationData: AuthentificationChallengeData, completion: @escaping (Bool) -> Void) {
        let challengeParameters = ADYChallengeParameters(
            serverTransactionIdentifier: authentificationData.threeDSServerTransID, 
            acsTransactionIdentifier: authentificationData.acsTransID, 
            acsReferenceNumber: authentificationData.acsReferenceNumber, 
            acsSignedContent: authentificationData.acsSignedContent
        )

        transaction?.performChallenge(with: challengeParameters, completionHandler: { (result, error) in
            if result != nil{
                completion(true)
            } else {
                completion(false)
            }
        })
    }

    func onSuccess(invoiceId: String) {
        // The authentication was done successfully, you can send
        // back the invoice ID to your backend to validate the payment
        print("SUCCESS:" + invoiceId)
    }

    func onError(error: ProcessOutException) {
        // An error occurred
        print(error)
    }
}
import com.adyen.threeds2.AuthenticationRequestParameters;
import com.adyen.threeds2.ChallengeStatusReceiver;
import com.adyen.threeds2.CompletionEvent;
import com.adyen.threeds2.ProtocolErrorEvent;
import com.adyen.threeds2.RuntimeErrorEvent;
import com.adyen.threeds2.ThreeDS2Service;
import com.adyen.threeds2.Transaction;
import com.adyen.threeds2.exception.SDKAlreadyInitializedException;
import com.adyen.threeds2.exception.SDKNotInitializedException;
import com.adyen.threeds2.parameters.ChallengeParameters;
import com.adyen.threeds2.parameters.ConfigParameters;
import com.adyen.threeds2.util.AdyenConfigParameters;

// ...

final ThreeDSHandler handler = new ThreeDSHandler() {
    private Transaction mTransaction;

    @Override
    public void doPresentWebView(ProcessOutWebView webView) {
        // Called when a web challenge is required. Present 
        // the webview to the user
    }

    @Override
    public void doFingerprint(DirectoryServerData dsdata, DoFingerprintCallback callback) {
        ConfigParameters config = new AdyenConfigParameters.Builder(
            dsdata.getDirectoryServerID(), 
            dsdata.getDirectoryServerPublicKey()
        ).build();
        try {
            ThreeDS2Service.INSTANCE.initialize(MainActivity.this, config, null, null);
            mTransaction = ThreeDS2Service.INSTANCE.createTransaction(null, dsdata.getMessageVersion());
            AuthenticationRequestParameters t = mTransaction.getAuthenticationRequestParameters();
            ThreeDSFingerprintResponse gatewayRequest = new ThreeDSFingerprintResponse(
                t.getDeviceData(), 
                t.getSDKAppID(), 
                new Gson().fromJson(t.getSDKEphemeralPublicKey(), SDKEPhemPubKey.class), 
                t.getSDKReferenceNumber(), 
                t.getSDKTransactionID()
            );
            callback.continueCallback(gatewayRequest);
        } catch (SDKAlreadyInitializedException e) {
            e.printStackTrace();
        } catch (SDKNotInitializedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doChallenge(AuthenticationChallengeData authenticateData, final DoChallengeCallback callback) {
        ChallengeParameters challengeParameters = new ChallengeParameters();
        challengeParameters.set3DSServerTransactionID(authenticateData.getThreeDSServerTransID());
        challengeParameters.setAcsTransactionID(authenticateData.getAcsTransID());
        challengeParameters.setAcsRefNumber(authenticateData.getAcsReferenceNumber());
        challengeParameters.setAcsSignedContent(authenticateData.getAcsSignedContent());

        mTransaction.doChallenge(MainActivity.this, challengeParameters, new ChallengeStatusReceiver() {
            @Override
            public void completed(CompletionEvent completionEvent) {
                callback.success();
            }

            @Override
            public void cancelled() {
                // Cancelled by the user or the App.
                callback.error();
            }

            @Override
            public void timedout() {
                // The user didn't submit the challenge within the given time, 5 minutes in this case.
                callback.error();
            }

            @Override
            public void protocolError(ProtocolErrorEvent protocolErrorEvent) {
                // An error occurred.
                callback.error();
            }

            @Override
            public void runtimeError(RuntimeErrorEvent runtimeErrorEvent) {
                // An error occurred.
                callback.error();
            }
        }, 5);
    }

    @Override
    public void onSuccess(String invoiceId) {
        // The authentication was done successfully, you can send
        // back the invoice ID to your backend to validate the payment
        Log.d("PROCESSOUT", "SUCCESS:" + invoiceId);
    }

    @Override
    public void onError(Exception error) {
        // An error occurred
        Log.e("PROCESSOUT", error.toString());
    }
};

Server-side authorization

You must have a stored Customer token to authorize payment on an Invoice from the server. Call Invoice.authorize() with the token, as shown below:

curl -X POST https://api.processout.com/invoices/iv_lEZFFcQg5WwpKcolBrlzioeeFnnuPmyE/authorize \
    -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
    -d source=card_Tpu6ZOCDu1tnDKp0kTnPOcVDMUbW7dTU
invoice.authorize("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ").then(
    function(transaction) {
        //

    }, function(err) {
        // The invoice could not be authorized
    });
transaction = invoice.authorize("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ")
transaction = invoice.authorize("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ")
$transaction = $invoice->authorize("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ");
tr, err := iv.Authorize("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ")

Invoice.authorize() returns a Transaction object if the payment is successful or an error code or null value if it isn’t. The status field of the Transaction is set to authorized to distinguish it from the completed transaction that results from a capture. (See the Transaction section of the API reference for a list of all possible status codes.)

Note that once you have authorized payment on the Invoice object, you do not need to supply the Customer token during the capture stage.

Automatic capture after a delay

You can pass an auto_capture_at option to ProcessOut.makeCardPayment() and Invoice.authorize() to set a date and time for an automatic capture. The value is a date and time in ISO 8601 format.

curl -X POST https://api.processout.com/invoices/iv_lEZFFcQg5WwpKcolBrlzioeeFnnuPmyE/authorize \
    -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
    -d source=card_Tpu6ZOCDu1tnDKp0kTnPOcVDMUbW7dTU \
    -d auto_capture_at="2022-10-02T15:00:00Z"
invoice.authorize("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ", {
    "auto_capture_at": "2022-10-02T15:00:00Z"
}).then(
    function(transaction) {
        //

    }, function(err) {
        // The invoice could not be authorized
    });
transaction = invoice.authorize("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ", {
    "auto_capture_at": "2022-10-02T15:00:00Z"
})
transaction = invoice.authorize("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ", (
    auto_capture_at: "2022-10-02T15:00:00Z"
))
$transaction = $invoice->authorize("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ", array(
    "auto_capture_at" => "2022-10-02T15:00:00Z"
));
tr, _ := iv.Authorize("card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ", InvoiceAuthorizeParameters{
    AutoCaptureAt: "2022-10-02T15:00:00Z",
})

Incrementing an invoice that is already authorized

You can use incremental authorizations to increase the amount that is authorized for payment on an invoice. Some Payment Service Providers (PSPs) do not support this operation, so you should check that the incremental field of your Invoice object is set to true. Also, the original payment must already be authorized but not yet captured. Check that the status field of the transaction is set to authorized to be sure.

You can increment an invoice on either the client or the server using the code shown below.

Server code

// authorize
curl -X POST https://api.processout.com/invoices/iv_MgeLS2Rr3ZGwjqOvDvYSuWx7ce88luXl/authorize \
    -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
    -d incremental=true \
    -d source={customer or token}

// increment authorization
curl -X POST https://api.processout.com/invoices/iv_MgeLS2Rr3ZGwjqOvDvYSuWx7ce88luXl/increment_authorization \
    -u test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x:key_sandbox_mah31RDFqcDxmaS7MvhDbJfDJvjtsFTB \
    -d amount="10.00"
// authorize
invoice.authorize("source",{"incremental" : true}).then(
    function(transaction) {
        // The invoice was authorized and returned a transaction
    }, function(err) {
        // The invoice could not be authorized
    });

// increment authorization
invoice.incrementAuthorization("iv_MgeLS2Rr3ZGwjqOvDvYSuWx7ce88luXl","10.00").then(
    function(transaction) {
        // The authorization amount was incremented and returned a transaction

    }, function(err) {
        // The The authorization amount could not be incremented
    });
// authorize
transaction = invoice.authorize("source",{"incremental" : true})

// increment authorization
transaction = invoice.increment_authorization("iv_MgeLS2Rr3ZGwjqOvDvYSuWx7ce88luXl","10.00")
// authorize
transaction = invoice.authorize("source",{"incremental" => true})

// increment authorization
transaction = invoice.increment_authorization("iv_MgeLS2Rr3ZGwjqOvDvYSuWx7ce88luXl","10.00")
// authorize
$transaction = $invoice->authorize("source",array("incremental"=>true);

// increment authorization
$transaction = $invoice->incrementAuthorization("10.00");
// authorize
opt := InvoiceAuthorizeParameters{
    Invoice: &Invoice{
        Incremental: true,
    },
}
tr, _ := iv.Authorize("source",opt)

// increment authorization
tr, _ := iv.IncrementAuthorization("10.00")

Client code

client.incrementAuthorizationAmount("iv_MgeLS2Rr3ZGwjqOvDvYSuWx7ce88luXl",1,
    function (inv) {
        // Increment was successful.
    }, function (err) {
        // An error occurred.
    });
ProcessOut.incrementAuthorizationAmount(invoiceId: "iv_MgeLS2Rr3ZGwjqOvDvYSuWx7ce88luXl", amount: 1, handler: handler)
p.incrementAuthorizationAmount("iv_MgeLS2Rr3ZGwjqOvDvYSuWx7ce88luXl", 1, handler);

Next steps

Once you have authorized the payment, the final step is to capture it on the server.