Mobile app payments
The easiest way to start accepting payments on mobile devices using ProcessOut is to use the ProcessOut mobile SDKs. The SDKs will take care of tokenizing your customers’ card numbers, so that you can send those generated tokens to your backend and process payments.
Set up the mobile SDK in your app
Let’s first make sure the ProcessOut SDK is installed and set up on your mobile application.
// The ProcessOut iOS SDK is available on *iOS 8.0+*,
// through http://cocoapods.org/.
//
// To install it, simply add the following line to your Podfile:
// pod 'ProcessOut'
// And then run `pod install`.
// Once installed, in your AppDelegate, configure the ProcessOut iOS SDK:
ProcessOut.Setup(projectId: "proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x")
// The ProcessOut Android SDK is available on *Android SDK 14+*,
// through Jitpack.
//
// To install it, simply add the following line to
// your build file, under the repositories section:
// maven { url 'https://jitpack.io' }
// as well as the following dependency in your build.gradle file:
// compile 'com.github.processout:processout-android:2.+'
// You’ll then be able to import Processout in your code base and
// configure the SDK with your ProcessOut API credentials.
final ProcessOut client = new ProcessOut(
this,
"proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x"
);
Tokenize a card
Once all set up, you’ll be able to tokenize the card numbers. The returned
token is a simple string
.
// First create a card object containing the card details
let card = ProcessOut.Card(cardNumber: "4242424242424242", expMonth: 11, expYear: 19, cvc: nil, name: "NAME")
// And then send the card data to ProcessOut to tokenize
ProcessOut.Tokenize(card: card, metadata: [:], completion: {(token, error) -> Void in
if error != nil {
switch error! {
case .BadRequest(let message, let code):
print(message, code)
case .InternalError:
print("An internal error occured")
case .MissingProjectId:
print("Check your app delegate file")
case .NetworkError:
print("Request could not go through")
}
} else {
// Send token to your backend to charge the customer
print(token!)
}
})
// First create a card object containing the card details
Card card = new Card("Jeremy lejoux", "4242424242424242", 11, 19, "123");
// And then send the card data to ProcessOut to tokenize
client.tokenize(card, new TokenCallback() {
@Override
public void onSuccess(String token) {
// Send the card token to your backend for charging
}
@Override
public void onError(Exception error) {
Log.e("ProcessOut", error.toString());
}
});
3DS2 handlers
3DS2 handlers are native elements that allow you to implement 3DS2 in your application while giving the best experience to your customers.
1— The test handler
The test handler is very simple: it simply emulates the 3DS authentication flow without having the make actual calls to ACS servers (servers of the banks of your customers used to process authentications).
The ProcessOut SDKs provide factories to build the 3DS2 test handler:
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: " + invoicecId)
} 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());
}
}
);
The test handler can basically be used when integrating using ProcessOut’s sandbox, and can also be used to mock tests in your application.
Once you chose to deploy your mobile application to production, you can then start using the official 3DS SDKs from your 3DS Server partners.
2— The handler with the production 3DS SDK
As mentioned previously, the ProcessOut 3DS2 SDK works in a “bring your own SDK” manner. Most 3DS Server providers/partners will provide you with their own certified SDKs, which might also come with specific features you’d like to integrate.
To help in the process, you can find below the 3DS SDKs we officially support:
2.1— Adyen
When making the payment on the Invoice you need to provide the Adyen SDK version to the makeCardPayment
call.
For iOS Adyen provides a method called ADY3DS2SDKVersion()
and for Android it is called getSDKVersion()
. We also allow
you to optionally overwrite the preferred card scheme if the customer’s card supports co-schemes, such as Cartes Bancaire, as demonstrated below.
...
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
);
...
The below code implements the ProcessOut 3DS2 SDK using Adyen’s certified SDK.
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());
}
};