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.

If you are using the ProcessOut mobile SDKs, you need to pass deep links in the return_url property in the invoice and invoice_return_url in the customer token. It enables the SDKs to pass control back to your application. Deep links for Android must follow the your.application.id://processout/return format.

First, see the page about setting up your environment 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, ProcessOut.shared.invoices.authorizeInvoice() on iOS, or ProcessOut.instance.invoices.authorizeInvoice() on Android, as shown in the example below. The payment source token passed as a parameter to the method 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 threeDSService object used in the mobile code samples is described in the next section.

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;
    });
}
let request = POInvoiceAuthorizationRequest(
    invoiceId: "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT",
    source: "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ"
)

try await ProcessOut.shared.invoices.authorizeInvoice(
    request: request, threeDSService: threeDSService
)
val request = POInvoiceAuthorizationRequest(
    invoiceId = "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT",
    source = "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ"
)

ProcessOut.instance.invoices.authorizeInvoice(
    request = request,
    threeDSService = threeDSService
)

// Subscribe to collect the result in the coroutine’s scope.
ProcessOut.instance.invoices.authorizeInvoiceResult
    .collect { result ->
        // Handle the result.
    }

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

ParameterDescription
authorize_onlySet to true if you want to authorize payment without capturing. Note that you must capture the payment on the server if you use this option.
auto_capture_atDate and time in ISO 8601 format specifying when to capture the payment (see automatic capture after a delay below).
capture_amountAmount 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_schemeCard scheme or co-scheme that should get priority if it is available.
incrementalSet to true to indicate that the authorization may be incremented in the future (see below).
invoice_detail_idsCan be used to to provide specific ids to indicate which of items provided in invoice details list are subject to capture.
override_mac_blockingAllows to specify if transaction blocking due to MasterCard Merchant Advice Code should be applied or not. Defaults to false.
initial_scheme_transaction_idAllows to specify which scheme ID to use for subsequent CIT/MITs if applicable. ProcessOut sets this automatically by default.
allow_fallback_to_saleSetting this property to true allows to fall back to a sale payment if separation between authorization and capture is not available.

3-D Secure 2 handlers for mobile apps

Most PSPs have their own certified SDKs for 3-D Secure 2 (3DS2) in mobile apps but they all have equivalent features. To abstract the details of specific 3DS2 SDKs, we define a PO3DSService object that supplies the functionality in a consistent way (this is the threeDSService parameter in the code samples above). The object is specified as an interface in Kotlin and as a protocol in Swift.

We officially support our own implementation POTest3DSService of PO3DSService that 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 POTest3DSService
to pass as the threeDSService parameter of authorizeInvoice().

let service = POTest3DSService(returnUrl: ...) // Same URL as set in the invoice.

// Pass a view controller that the service can use to present its UI when needed.
service.viewController = viewController
ProcessOut.instance.invoices.authorizeInvoice(
    request = request,
    threeDSService = POTest3DSService(
        activity = this,
        customTabLauncher = customTabLauncher,
        returnUrl = "your.application.id://processout/return"
    )
)

With some PSPs such as Adyen you must add the version number of the 3DS2 SDK to the POInvoiceAuthorizationRequest object when you make the call to authorizeInvoice().

The example below demonstrates how you can do that and also change the preferred card scheme (for example, to carte bancaire) if the customer's card supports co-schemes.

...

let request = POInvoiceAuthorizationRequest(
    ...
    preferredScheme: "carte bancaire", // Optional
    thirdPartySdkVersion: "<VERSION>" 
)

try await ProcessOut.shared.invoices.authorizeInvoice(
    request: request, threeDSService: threeDSService
)

...
val request = POInvoiceAuthorizationRequest(
    ...
    thirdPartySdkVersion = "<VERSION>",
    preferredScheme = "carte bancaire" // Optional.
)

ProcessOut.instance.invoices.authorizeInvoice(
    request = request,
    threeDSService = threeDSService
)

We also provide a built-in 3DS2 SDK that is available in separate ProcessOutCheckout3DS module on both iOS and Android.

The example below shows how you can create an instance of PO3DSService with POCheckout3DSServiceBuilder and use it.

import ProcessOut
import ProcessOutCheckout3DS

// 1. Define the 3DS service delegate.
final class Checkout3DSServiceDelegate: POCheckout3DSServiceDelegate {

    // Needed to handle redirects.
    unowned var viewController: UIViewController!

    func configuration(
        with parameters: ThreeDS2ServiceConfiguration.ConfigParameters
    ) -> Checkout3DS.ThreeDS2ServiceConfiguration {
        // If you don’t want to customize 3DS2 SDK appearance or behavior
        // you can omit this method as there is a default one. For example
        // the code below changes default timeout to 10 minutes.
        ThreeDS2ServiceConfiguration(
            configParameters: parameters, challengeTimeout: 600
        )
    }

    func shouldContinue(
        with warnings: Set<Checkout3DS.Warning>,
        completion: @escaping (Bool) -> Void
    ) {
        // The default implementation ignores all warnings. You can change
        // that and for example abort 3DS2 on jailbroken devices.
        completion(!warnings.contains(.jailbroken))
    }

    func handle(
        redirect: PO3DSRedirect,
        completion: @escaping (Result<String, POFailure>) -> Void
    ) {
        // The simplest way to handle redirect is to create a view controller
        // using `PO3DSRedirectViewControllerBuilder` and present to the user.
        let viewController = PO3DSRedirectViewControllerBuilder()
            .with(returnUrl: ...)
            .with(redirect: redirect)
            .with(completion: { [weak self] result in
                self?.viewController.dismiss(animated: true)
                completion(result)
            })
            .build()
        self.viewController.present(viewController, animated: true)
    }

}

// 2. Create the delegate.
let threeDSServiceDelegate = POCheckout3DSServiceDelegate()
delegate.viewController = self // or any other view 

// 3. Create the 3DS service.
let threeDSService = POCheckout3DSServiceBuilder
    // Delegate is strongly referenced by service so make sure you are not
    // creating any retain cycles.
    .with(threeDSServiceDelegate)
    // The next call is optional, environment is set to .production by
    // default.
    .with(environment: .sandbox) 
    .build()
...
// 1. Initialize PO3DSRedirectCustomTabLauncher in the onCreate() method of Activity or Fragment.

private lateinit var customTabLauncher: PO3DSRedirectCustomTabLauncher

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    customTabLauncher = PO3DSRedirectCustomTabLauncher.create(from = this)
}

// 2. Implement POCheckout3DSServiceDelegate and pass the launcher.

class Checkout3DSServiceDelegate(
    private val activity: Activity,
    private val customTabLauncher: PO3DSRedirectCustomTabLauncher
) : POCheckout3DSServiceDelegate {

    override fun configuration(parameters: ConfigParameters): ThreeDS2ServiceConfiguration {
        return ThreeDS2ServiceConfiguration(
            context = activity,
            configParameters = parameters,
            // Optional properties.
            locale = Locale.UK,
            uiCustomization = UICustomization(),
            appUri = Uri.parse("https://my-app-url.com"),
            challengeTimeout = 300
        )
    }

    override fun shouldContinue(warnings: Set<Warning>, callback: (Boolean) -> Unit) {
        // The default implementation ignores all warnings.
        // As an example we can define to continue the flow
        // only if there are no warnings or all warnings have low severity.
        callback(warnings.all { it.severity == Severity.LOW })
    }

    override fun handle(redirect: PO3DSRedirect, callback: (ProcessOutResult<String>) -> Unit) {
        customTabLauncher.launch(
            redirect = redirect,
            returnUrl = "your.application.id://processout/return",
            callback = callback
        )
    }

    // Optionally implement service lifecycle callbacks for logs or custom logic.

    override fun willCreateAuthenticationRequest(configuration: PO3DS2Configuration) {}

    override fun didCreateAuthenticationRequest(result: ProcessOutResult<PO3DS2AuthenticationRequest>) {}

    override fun willHandle(challenge: PO3DS2Challenge) {}

    override fun didHandle3DS2Challenge(result: ProcessOutResult<Boolean>) {}
}

// 3. Create the 3DS service.
val threeDSService = POCheckout3DSService.Builder(
    activity = this,
    delegate = Checkout3DSServiceDelegate(activity = this, customTabLauncher)
)   // Optional parameter, by default Environment.PRODUCTION.
    .with(environment = Environment.SANDBOX)
    .build()

For other providers, you can create your own PO3DSService implementation using our code as a starting point. See our GitHub repositories with the definition of the 3DS2 SDK integration for Android and iOS.

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 server-side using the code shown below.

// 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
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")
// 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
    });

Bypassing unsupported PSP features


Not every PSP supports the same set of features. For example, some of them may not be able to increment authorization. If you mark the invoice as incremental and include such PSP in the routing rules, it may turn out that the gateway chosen by the Smart Router cannot execute the payment.

The unsupported_feature_bypass object on the invoice contains flags used to avoid such situations. By setting the appropriate flags you can indicate that the payment should be executed in a way that bypasses PSP limitations. For example, by setting the flag unsupported_feature_bypass.incremental_authorization to true, ProcessOut will fall back to non-incremental authorization if the chosen gateway does not support incremental ones.
Once the transaction is authorized, you'll be able to discover that no incremental authorization action is possible and thus adapt your flow.

Next steps

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


What’s Next