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:
- A one-time card token.
- A customer token representing stored card details. See the page about saving a token to capture future payments to learn how to create a customer token.
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 matchesreturn_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 thewebAuthenticationCallback
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.
Parameter | Description |
---|---|
authorize_only | Set 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_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. |
incremental | Set to true to indicate that the authorization may be incremented in the future (see below). |
invoice_detail_ids | Can be used to to provide specific ids to indicate which of items provided in invoice details list are subject to capture. |
override_mac_blocking | Allows to specify if transaction blocking due to MasterCard Merchant Advice Code should be applied or not. Defaults to false. |
initial_scheme_transaction_id | Allows to specify which scheme ID to use for subsequent CIT/MITs if applicable. ProcessOut sets this automatically by default. |
allow_fallback_to_sale | Setting this property to true allows to fall back to a sale payment if separation between authorization and capture is not available. |
save_source | Set 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_secret | A 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.
Updated 5 days ago