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.