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

To authorize a payment, you need an existing invoice ID and a payment source. The payment source can either be:

Web

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

Then use the ProcessOut.makeCardPayment() function to authorize a payment in your web page as shown in the example 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;
    });
}

iOS

First, you should obtain and set up the SDK in your code using the instructions for iOS.

Ensure your invoice has a valid return_url assigned that points back to your application. Doing so enables the SDK to pass control back to your application.

  • If you rely on scheme-based deep links, refer to Apple’s article on Defining a custom URL scheme for your app. Alternatively, when creating the authorization request, you can set the webAuthenticationCallback property with a custom scheme that matches return_url.
  • For applications that support iOS 17.4, you can use universal links. In this case, the return_url host specified in the invoice must be associated with your app via associated domains (see Apple’s article on associated domains for more details). Additionally, when creating the authorization request, set the webAuthenticationCallback to an object that matches against HTTPS URLs.

Then to authorize a payment use the ProcessOut.shared.invoices.authorizeInvoice() method, as shown in the example below.

let request = POInvoiceAuthorizationRequest(
    invoiceId: "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT",
    source: "card_1jSEVrx7oaRta1KEdxoMWbiGkK2MijrZ",
    webAuthenticationCallback: .customScheme("processout") // optional
)

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

Note that the threeDSService object used in example above is described in the next section.

Android

First, you should obtain and set up the SDK in your code using the instructions for Android.

Ensure your invoice has a valid return_url assigned that points back to your application. Doing so enables the SDK to pass control back to your application. URL must follow the your.application.id://processout/return format.

Then to authorize a payment use the ProcessOut.instance.invoices.authorizeInvoice() method, as shown in the example below.

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.
    }

Note that the threeDSService object used in example above is described in the next section.

You can supply a number of options when authorizing invoice. 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.
save_sourceSet this property to true if you want us to save the payment source by creating a customer token during the authorization. Ensure that client_secret is also set for this operation.
client_secretA secret key associated with the client making the request. The client secret is returned in the X-ProcessOut-Client-Secret header when creating or retrieving an invoice.

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 interface PO3DSService in Kotlin and protocol PO3DS2Service in Swift that supply the functionality in a consistent way (this is the threeDSService parameter in the code samples above).

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 the POTest3DSService to pass as the threeDSService parameter of authorizeInvoice().

import ProcessOutUI

...

try await ProcessOut.shared.invoices.authorizeInvoice(
    request: request, threeDSService: POTest3DSService()
)
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
)

Checkout 3DS SDK integration

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 Checkout-based PO3DSService and use it.

import ProcessOut
import ProcessOutCheckout3DS

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

    func checkout3DSService(
        _ service: POCheckout3DSService,
        configurationWith parameters: ThreeDS2ServiceConfiguration.ConfigParameters
    ) -> ThreeDS2ServiceConfiguration? {
        // If you don’t want to customize 3DS2 SDK appearance or behavior
        // you can omit this method as there is a default implementation. For example
        // the code below changes default timeout to 10 minutes.
        .init(configParameters: parameters, challengeTimeout: 500)
    }

    func checkout3DSService(
        _ service: POCheckout3DSService,
        shouldContinueWith warnings: Set<Warning>
    ) async -> Bool {
        // The default implementation ignores all warnings. You can change
        // that and for example abort 3DS2 on jailbroken devices.
        !warnings.contains(.jailbroken)
    }
}

// 2. Create the delegate.
let threeDSServiceDelegate = POCheckout3DSServiceDelegate()

// 3. Create the 3DS service.
let threeDSService = POCheckout3DSService(
    // Delegate is strongly referenced by service so make sure you are not
    // creating any retain cycles.
    delegate: threeDSServiceDelegate, environment: .sandbox
)
...
// 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