Getting Started
This document provides instructions for embedding PC SDK in Android applications. In order to understand better how to work with PC we recommend you to read the following documents as well:
- Architecture and functionalty presentation
- Architecture and functionalty
The PC SDK contains native libraries for Android
operating system. These libraries allow to implement all functionality of PC right in your mobile App.
Project Integration
Adding dependency to gradle-script
PC SDK library for Android can be integrated into your project as maven-dependency using SafeTech or Airome repository.
To add PC SDK you need to add external maven repository to top-level gradle script as in the example below.
// Sample of project-level build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenCentral()
maven { url "https://maven.google.com" }
// PC Libraries
maven { url "https://repo.paycontrol.org/android/maven" }
}
}
The next step is to add PC SDK into your project as a dependency. Latest version can be discovered in the repository.
If you want to support GOST algorithms for signing and encryption, you should refer to pcsdk-gost
artifact. If support of GOST algorithms is not essential for you, it is better to refer to original artifact called pcsdk
. Usage of PCSDK with GOST support leads to increase in your final app size of around 18 Mb.
// Dependency in your module-level build.gradle (without supporting GOST algorithms)
implementation 'tech.paycon.sdk.v5:pcsdk:6.0.<LATEST_VERSION>'
// Dependency in your module-level build.gradle (with supporting GOST algorithms)
implementation 'tech.paycon.sdk.v5:pcsdk-gost:6.0.<LATEST_VERSION>'
Configuring Android manifest
To use all of the features of PC SDK (like device data collection, device scoring, etc.) PCSDK adds the follwoing permissions to AndroidManifest.xml
.
<!-- For ensuring internet connection to interact with PC Server -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- For collecting SIM info and sending it to PC Server -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- For collecting device network settings and sending it to PC Server -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- For obtaining device name from Bluetooth settings for devices running Android 11 or lower -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30"/>
<!-- For obtaining device name from Bluetooth settings for devices running Android 12 or igher -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
Note that these permissions are automatically added in your app when merging manifests from your app and PCSDK.
You also need to provide a permission to gather device location by yourself to support sharing device location with server.
<!-- For tracking device location and sending it to PC Server (approximate coordinates) -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- For tracking device location and sending it to PC Server (exact coordinates) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Note that in order to gather device location you are supposed to provide either
android.permission.ACCESS_COARSE_LOCATION
orandroid.permission.ACCESS_FINE_LOCATION
but not both of them. Consider thatandroid.permission.ACCESS_COARSE_LOCATION
provides an approximate location which is based on data from network connection whereasandroid.permission.ACCESS_FINE_LOCATION
supplies more exact coordinates (based on both GPS and network connection) but consumes more power.
Basic Scenarios
SDK Initalization
To initialize PC SDK library you need to call static method PCSDK.init(Context context)
passing the
context of your application. Your are expected to initialize PC SDK in UI thread in either onCreate()
method of your app or
onCreate()
method of your launcher activity before calling any other methods from PCSDK
.
If you want to receive logs in LogCat from the PC SDK, you also should call PCSDK.setLogLevel()
method (can be called before init
) with desired log level.
Possible log levels are:
PCSDK.PC_NO_LOGGING
- does not put any data to LogCatPCSDK.PC_LOG_DEBUG
- provides sufficient data to LogCat to debug your app behavior when interacting with PC SDKPCSDK.PC_LOG_VERBOSE
- provides extended information about what is happening inside the SDK. Consider using it for debug purposes only.PCSDK.PC_LOG_KEY_VALUES
- logs values of private keys and passwords. Can be combined with other levels. This log level is for debugging purposes only. Here is an example of initialization inApplication
'sonCreate()
method:
@Override
public void onCreate() {
super.onCreate();
PCSDK.setLogLevel(PCSDK.PC_LOG_DEBUG);
PCSDK.init(this);
}
Your application must use one instance of PCSDK
class during operating.
Registering users
Process of user registration includes followings steps:
- Getting the personalization data from the PC Server (via QR code, QR code + activation code, or JSON-value)
seeArchitecture and functionality
document for more details on this. - Registering a PC User on PC Server. During this process PC SDK generates needed key sets
- Storing user's keys in internal storage for further usage
Step 1. Import PCUser from an appropriate source
First of all, you should construct a PCUser
object (also referenced as key
) from String
value which contains either JSON extracted from QR-code or JSON delivered to your app with usage of PC Server API.
PCUser user = PCUsersManager.importUser(source);
if (user == null) {
Log.e(TAG, "Failed to import PCUser");
return;
}
// PCUser imported successfully - continue registration
Step 2. Check if PCUser requires activation
When delivering personalization data to your clients your infrastructure may use different methods (via QR, QR + activation code, JSON-value) to deliver the key data to the mobile app.
The following snippet demonstrates how to import key data to a valid PCUser
object:
PCUser user = PCUsersManager.importUser(source);
if (!user.isActivated()) {
// PCUser requires activation, get activation code according to your rules
String activationCode = getActivationCode();
// Activate the PCUser (that is, decrypt the key values)
PCUsersManager.activate(user, activationCode, new PCActivateCallback() {
@Override
public void success() {
// PCUser was activated successfully
}
@Override
public void error(@NonNull PCAbstractError error) {
// Activation failed
}
});
} else {
// No activation is required
}
Step 3. Check for information intended to be collected
Your infrastructure might be configured in such a way that some information about the device is collected when interacting with a PC Server. Some of the information requires granting additional permissions on the devices running Android 6 and newer.
You had better check the required permissions when adding a new PCUser to the device. Consider the following code snippet:
ArrayList<String> requiredPermissions = new ArrayList<>();
if (user.isCollectDeviceInfo() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// For this PCUser the server expects data about the device including the device name.
// Device name is obtained from Bluetooth settings and for devices rinnung Android 12
// or newer the permission to access Bluetooth must be asked in runtime
requiredPermissions.add(Manifest.permission.BLUETOOTH_CONNECT);
}
if (user.isCollectSIMInfo()) {
// For this PCUser the server expects data about a SIM card to be collected and registered
requiredPermissions.add(Manifest.permission.READ_PHONE_STATE);
}
if (mUser.isCollectLocation()) {
// For this PCUser the server expects a device location to be collected and registered
// Note: you can use Manifest.permission.ACCESS_COARSE_LOCATION instead but you will lose precision
requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
// Now request required permissions. It might also be a good idea to check in advance if they are already granted
if (requiredPermissions.size() > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String[] permissionsArray = new String[requiredPermissions.size()];
requestPermissions(requiredPermissions.toArray(permissionsArray), REQUEST_PERMISSIONS_CODE);
}
NOTE: If necessary permissions are not granted, the required information will not be collected. This does not result in any errors or misbehavior and process of interaction with PC Server remains intact.
Step 4. Save the PCUser object into permanent storage
Now, you can save the PCUser into storage. Each PCUser object is stored in your app's shared preferences and referenced by a unique name which is supposed to be entered by a client of your app.
Besides, the keys which are used to confirm transactions (HMAC key which is used to calculate a confirmation code and private key used to generate ECDSA-signature) are always additionally protected with a client-password and hardware encryption layer (available from Android 4.4).
So, the process of saving a key will look in this way:
// Prompt a unique name for the key from your client or generate it in your app
String keyName = promptKeyName();
// Prompt a password to protect the key
String password = promptPassword();
// Save the key to storage
int result = PCUsersManager.store(user, keyName, password, new PCStoreCallback() {
@Override
public void success() {
// PCUser is now saved to the storage and can be found in the list of the keys returned by PCUsersManager.listStorage()
}
@Override
public void error(@NonNull PCAbstractError error) {
// PCUser was not saved - check the error code and handle the error
}
});
NOTE: You have to check the password policy required by a particular PCUser before prompting a password from a client. Call
PCUser.getPasswordPolicy()
to find out which requirements must be met.
- Result0
means that the client is allowed to keep the key without a password protection. In this case you should generate a random or constant password and pass it toPCUsersManager.store(...)
. The app-generated password must comply with password policy1
. In this particular case, it is your responsibility to save the password and submit it when confirming transactions.
- Result1
stands for weak password policy which wants client to use at least 6 chars to create a password.
- Result2
requires submitting passwords which include lower- and upper-case letters and at least 8-chars in length.
- Result3
implies the strong policy and accepts passwords comprised of digits, lower- and upper-case letters and at least 8-chars in length.NOTE: The PC SDK does not store submitted passwords anywhere. The client is supposed to remember their password.
NOTE: Instead of directly submitting a password you can design your app so that it uses biometrics mechanisms of authentication (e.g. fingerprint sensor) and generates passwords based on biometrics data. When implementing this functionality take into account that the app must comply with the flag indicated by
PCUser.isDenyStoreWithOSProtection()
.
Consider also, that adding a new fingerprint on a smartphone invalidates all the previously created fingerprints, hence you had better provide a chance to your clients to use a password instead.
Step 5. Registering the key on the PC Server
The final step is to let the server know that the key is installed on the device. When registering a PCUser on the server, you send a device token to get push-notifications. Besides, the key pair is generated and the public key is sent to the server while the private key is encrypted with password and hardware encryption layer. You use PCUsersManager.register()
to registered the key on the server side.
PCUsersManager.register(user, pushToken, new PCNetCallback() {
@Override
public void success() {
// The PCUser has been registered sucessfully. Now it can be used to confirm transactions.
}
@Override
public void error(PCNetError pcNetError) {
// An error occured while registration. Some errors are not critical while some are.
// Call pcNetError.getType() to check the type of the error in order to handle it properly
// E.g. PCNetError.PCIS_ERROR_NO_CONNECTION means connectivity problems - you can
// give the client another try to complete registration.
// PCNetError.PCIS_ERROR_FINGERPRINT_ALREADY_SET means that this PCUser was already registered
// from another device, so it cannot be registered again.
// Refer to documentation on PCNetError and PCUsersManager classes to investigate
// the possible errors and issues.
}
});
NOTE: You can pass
null
as apushToken
if the push-token is not available. Then, the device will be registered but the client will not receive any notifications about new transactions.
You can also obtain a push token later and callPCUsersManager.register()
again with a new token. Consider also calling it each time, the push token is updated.NOTE: By default, FCM service is used to send notifications but you can also use HMS service for Huawei devices. Consider calling
PCUsersManager.register(PCUser user, String pushID, PCPushServiceType pushServiceType, PCNetCallback callback)
withPCPushServiceType.HMS
in this case. You also can usePCPushServiceType.Firebase
to show explicitly that you want use a Firebase token but it is usually redundant.
Overall key registration flow
The overall process of registration which considers all the possible ways of app personalization may have the following structure:
PCUser user = null;
// 1. Import PCUser in appropriate way
if (appUsesQRcode) {
PCSDK.PCQRType qrCodeType = PCSDK.analyzeQRValue(qrCodeSource);
if (qrCodeType == PCQRType.PCUser) {
user = PCUsersManager.importUser(qrCodeSource);
}
} else if (appUsesJSON) {
user = PCUsersManager.importUser(json);
}
if (user == null) {
// Import failed - handle a error
return;
}
// 2. Check if PCUser requires to be activated
if (!user.isActivated()) {
String activationCode = getActivationCode();
int result = PCUsersManager.activate(user, activationCode, new PCActivateCallback() {
@Override
public void success() {
// PCUser was activated successfully. Go to step 3.
}
@Override
public void error(@NonNull PCAbstractError error) {
// Activation failed, activation code was wrong - ask the client to resubmit it
}
});
}
// 3. Check if some permissions must be granted
if (user.isCollectSIMInfo()) {
// Ask for Manifest.permission.READ_PHONE_STATE
}
if (mUser.isCollectLocation()) {
// Ask for either Manifest.permission.ACCESS_COARSE_LOCATION or Manifest.permission.ACCESS_FINE_LOCATION
}
// 4. Store the key
int result = PCUsersManager.store(user, keyName, password, new PCStoreCallback() {
@Override
public void success() {
// PCUser is now saved to the storage. Go to step 5
}
@Override
public void error(@NonNull PCAbstractError error) {
// PCUser was not saved - check the error code and handle the error
}
});
// 5. Register PCUser
PCUsersManager.register(user, pushToken, new PCNetCallback() {
@Override
public void success() {
// The PCUser has been registered successfully
}
@Override
public void error(PCNetError pcNetError) {
// An error occurred while registration. handle it in a proper way
}
});
Further considerations
The process of registration described above can be simplified if your infrastructure uses a particular way of delivering personalization information (e.g. only QR-codes without an activation code). If you know in advance what kind of password policy is used, it also makes the logic less complex.
In your app you also can switch steps to adjust them for your logic. E.g. you can call PCUsersManager.register(...)
before storing a PCUser into the storage and call PCUsersManager.store(...)
only in case the registration was successful.
Process transactions
The overall scenario of processing a transaction includes the following stages:
- Call
PCUsersManager.listStorage()
to obtain a list of users from the storage. - Filter the list of users for which transactions will be loaded.
- Call
PCTransactionsManager.getTransactionList()
for each desiredPCUser
to find out if there are any transactions to be processed. - Call
PCTransactionsManager.getTransaction()
to get transaction by its identifier. Consider also callingPCTransactionsManager.getTransactionBinaryData()
for transactions which contain attachments. - Display loaded transactions to your client in a desired way with buttons for confirmation and declination.
- Handle events from clicking the confirmation/declination buttons:
- Check if password must be submitted before processing transaction with method
PCUser.isReadyToSign()
; - Prompt the password if required and call
PCUsersManager.submitPassword(...)
to submit it and unlock the keys; - Call either
PCTransactionsManager.sign()
orPCTransactionsManager.decline()
to sign or decline the transaction.
- Check if password must be submitted before processing transaction with method
Getting transaction data
This code snippet demonstrates how to download available transactions and get ready to display them.
for (PCUser user: PCUsersManager.listStorage()) {
// Filter list of PUsers in a desired way
if (user.getUserId().equals(TARGET_USER_ID) || user.getName().equals(TARGET_KEY_NAME)) {
// Load list of transactions for the user
PCTransactionsManager.getTransactionList(user, new PCListTransactionsCallback() {
@Override
public void success(String[] strings) {
// List of transactions is loaded. It might be empty
if (strings.length > 0) {
// List is not empty - load transactions
for (String transactionId: strings) {
// Load each transaction by its id
PCTransactionsManager.getTransaction(user,
transactionId,
new PCGetTransactionCallback() {
@Override
public void success(PCTransaction pcTransaction) {
// Transaction is loaded. Check if it has an attachment
if (pcTransaction.hasBinaryData()) {
PCTransactionsManager.getTransactionBinaryData(
user,
pcTransaction,
new PCGetTransactionBinaryDataCallback() {
@Override
public void success(PCTransaction pcTransaction) {
// Transaction is loaded with its attachment and
// ready to be displayed
}
@Override
public void error(@Nullable PCError pcError,
@Nullable PCNetError pcNetError) {
// An error occurred while downloading attachment
}
@Override
public void onProgressChanged(int loaded, int total) {
// Useful for displaying progress of downloading
}
});
} else {
// No attachment for this transaction - display it immediately
}
}
@Override
public void error(PCNetError pcNetError) {
// Transaction is not loaded - handle an error
}
});
}
}
}
@Override
public void error(PCNetError pcNetError) {
// List of transactions is not loaded = handle an error
}
});
}
}
NOTE: Both
PCTransactionsManager.getTransactionList()
andPCTransactionsManager.getTransaction()
are always light-weight methods which result in several kilobytes of the network traffic whereasPCTransactionsManager.getTransactionBinaryData()
might seriously affect the traffic when downloading heavy attachments.
Displaying transaction data
Before transaction can be confirmed or declined it should be shown to a client. To show the transaction in more user-friendly way consider the following methods that you can use when working with a particular PCTransaction
:
PCTransaction.getTransactionText()
- returns the text of transaction. Might benull
if the transaction contains an attachment only;PCTransaction.getSnippet()
- returns short description of the transaction which comes in handy when displaying a transaction in the list. Might benull
.PCTransaction.getStoredBinaryData()
- returnsjava.io.File
pointing to binary attachment (which is PDF document for example). Might be null if the transaction does not contain any attachment. The attachment stored in app's cache directory and eliminated as soon as the transaction is processed and every time the SDK is initialized.PCTransaction.getTextRenderType()
- returns type of transaction text. It makes sense to call this method ifPCTransaction.getTransactionText()
returns non-null and non-empty value to render the text in a proper way. This method returns eithernull
orPCRenderType.raw
for plain-text transactions andPCRenderType.markdown
if the text of the transaction follows markdown syntax.PCTransaction.getSnippetRenderType()
- the similar method for the transaction snippet.
NOTE: Depending on how your infrastructure is configured, you probably will not need all these methods to be triggered (e.g. if you know exactly that snippets or attachments are not used). Refer to documentation on PCTransaction
to find out other methods that might come in handy in particular cases.
Confirm or decline the transaction
As soon as you have downloaded and displayed transaction data, it can be confirmed through sign
method or declined with decline
method of PCTransactionsManager
class. However, before calling these methods you should check whether the password must be submitted or an error might occur. The sample confirmation flow looks like the following:
// 1. Check if the password is required to sign or decline the transaction
public void tryProcessTransaction() {
if (!user.isReadyToSign()) {
// Password is required.
// Depensing on your app configuration, the password may be:
// - Generated by the app itself
// - Directly submitted by the client
// - Generated with usage of Fingerprint of Face ID
String password = requestPassword();
int result = PCUsersManager.submitPassword(user, password, new PCSubmitPasswordCallback() {
@Override
public void success() {
// Now the transaction can be processed
processTransaction();
}
@Override
public void error(@NonNull PCAbstractError error) {
// An error occurred - handle it in a proper way
}
});
} else {
// Transaction can be processed without submitting a password
// This occurs when the password has already been submitted recently
processTransaction();
}
}
// 2. Sign or decline a transaction
public void processTransaction() {
if (confirmationButtonPressed) {
PCTransactionsManager.sign(user, pcTransaction, new PCSignCallback() {
@Override
public void error(PCError pcError, PCNetError pcNetError, PCConfirmation pcConfirmation) {
// Handle the error if the transaction was not signed
}
@Override
public void success() {
// Notify the client that the transaction has been signed successfully
}
});
} else {
PCTransactionsManager.decline(user, transaction, new PCDeclineCallback() {
@Override
public void error(PCError pcError, PCNetError pcNetError, PCDeclination pcDeclination) {
// Handle the error if the transaction was not declined
}
@Override
public void success() {
// Notify the client that the transaction has been declined successfully
}
});
}
}
NOTE: if the transaction contains an attachment but it was not downloaded with
PCTransactionsManager.getTransactionBinaryData()
, then the transaction will not be processed.
Getting a transaction from a push notification
PC Server can send a push notification when a transaction was created.
In this case the application can get an identifier of target PCUser
and identifier of target PCTransaction
from the notification payload and process the transaction.
Default push notification template is the following:
Default android push template (PC Server sends 2 push notifications)
{
"to":"%DEVICE_TOKEN%",
"notification":{
"tag":"%USER_ID%",
"title":"%PUSH_TITLE%",
"body":"%PUSH_TEXT%",
"icon":"pc_push",
"sound":"default"
},
"data":{
"type":"PayControl_v2"
}
}
{
"to":"%DEVICE_TOKEN%",
"data":{
"type":"PayControl_v2",
"userid":"%USER_ID%",
"transactionid":"%TRANSACTION_ID%"
}
}
Please, consider that push templates can be changed for your app by PC Server configuration.
Offline confirmation and declination
If the device is offline, then PC SDK can calculate offline confirmation/declination code for a transaction.
In this case PC SDK cannot get the transaction data from the PC Server, hence you should use QR code scanner to get the transaction data (this option is unavailable for transactions with attachments as they obviously cannot be embedded in a QR code).
Calculated confirmation/declination code should be displayed to a client. The client is supposed to input this code manually into the Application web-site. After this the Application can verify it using PC Server API.
The following example demonstrates how to confirm or decline a transaction i offline mode.
// scan QR code with app's QR code scanner
PCSDK.PCQRType qrType = PCSDK.analyzeQR(qrValue);
if (qrType != PCSDK.PCQRType.PCTransaction) {
// This is not a QR code with transaction data, show an error or skip it
return;
}
// Import the transaction
PCTransaction transaction = PCTransactionsManager.importTransaction(qrValue);
// Make sure, the transaction was imported correctly
if (transaction == null) {
// Notify about the error
return;
}
// Find a PCUser for who the transaction is intended
PCUser targetUser = null;
for (PCUser user: PCUsersManager.listStorage()) {
if (user.getUserId().equalsIgnoreCase(transaction.getUserId()) {
targetUser = user;
break;
}
}
if (targetUser == null) {
// There is no such a user in the storage. Notify about the error
return;
}
// Check if password must be submitted
if (!targetUser.isReadyToSign()) {
if (targetUser.areOnlineCredentialsRequired()) {
// You need to perform an online request to submit a password.
// Offline confirmation is not available.
return;
}
// Password is required.
// Depending on your app configuration, the password may be:
// - Generated by the app itself
// - Directly submitted by the client
// - Generated with usage of Fingerprint of Face ID
String password = requestPassword();
int result = PCUsersManager.submitPassword(user, password);
if (result != PCError.PC_ERROR_OK) {
// An error occurred - handle it in a proper way
return;
}
}
// Process the transaction
if (confirmationButtonPressed) {
// Confirm the transaction
PCConfirmation confirmation = PCTransactionsManager.signOffline(toConfirmUser, transaction);
// Show confirmation code to the client by calling confirmation.getShortConfirmCode()
} else {
// Decline the transaction
PCDeclination declination = PCTransactionsManager.declineOffline(toConfirmUser, transaction);
// Show declination code to the client by calling declination.getShortDeclineCode()
}
Online and Offline transaction processing can be combined in the result scenarios.
Moreover, PCTransactionsManager.sign()
and PCTransactionsManager.decline()
return PCConfirmation
and PCDeclination
respectively in case of any error during PC Server interaction so that you can use the mechanism of offline confirmation or declination in this case.
Working with operations
Operation is presented by the instance of PCOperation
and includes several transactions as a list of PCTransaction
instances. That is, the operation is a group of transactions that can be processed all together within one request resulting in smaller amounts of network traffic and faster calculations.
The algorithm of processing operations can include the following steps:
- Calling
PCUsersManager.listStorage()
to obtain a list of users from the storage and filtering the list. - Calling
PCOperationsManager.getOperationList()
for each desiredPCUser
to find out if there are any operations to be processed. - If there are any operations available, each operation can be retrieved by
PCOperationsManager.getOperation()
. The constructedPCOperation
will contain data about all the transactions which comprise the operation. However, the binary data for each transaction must be downloaded separately. - For each transaction listed by
PCOperation.getTransactions()
check if the transaction requires binary data to be downloaded by callingPCTransaction.hasBinaryData()
andPCTransactionsManager.getTransactionBinaryData()
. - Display the operation in a desired way. See Displaying transaction data for details on proper methods to render transactions. Apart from transactions data, the operation description (accessible by
PCOperation.getDescription()
) should be displayed. - Operations support partial processing, that is the client can decide to process some transactions immediately, while the decision to process other transactions can be postponed. So, the next step is to offer the interface to allow the client decide which transactions they want to process now and call
PCTransaction.processOperation()
passing the list of transactions to be confirmed and the list of transactions to be declined. - If there are some transactions left unprocessed, let the client process them later.
Getting operation data
The following code snippet demonstrates how to properly retrieve PCOperation
from the PC Server, so that it can be displayed and processed correctly.
/*
* STEP 1. Find target PCUser according to logic of your application
*/
final PCUser targetUser = PCUsersManager.getById(TARGET_USER_ID);
/*
* STEP 2. Load list of operations for the user
*/
PCOperationsManager.getOperationList(targetUser, new PCListOperationsCallback() {
@Override
public void success(String[] strings) {
// List of operations loaded successfully
if (strings.length == 0) {
// The list is empty
return;
}
/*
* STEP 3. Load operation data
*/
// Assume, we want to show first PCOperation
PCOperationsManager.getOperation(targetUser, strings[0], new PCGetOperationCallback() {
@Override
public void success(PCOperation pcOperation) {
// PCOperation is loaded and can be rendered
// Extract and show operation description
String desc = pcOperation.getDescription();
// Extract and show list of transactions
List<PCTransaction> transactions = pcOperation.getTransactions();
/*
* STEP 4. Load attachments for transactions. You can load attachments immediately
* before displaying the operation, or you can do it step by step. The main idea
* is that the attachment for a particular transaction must be loaded (and shown)
* before this transaction can be processed
*/
for (PCTransaction transaction: transactions) {
if (transaction.hasBinaryData()) {
PCTransactionsManager.getTransactionBinaryData(
targetUser,
transaction,
new PCGetTransactionBinaryDataCallback() {
@Override
public void success(PCTransaction pcTransaction) {
// Now this transaction is fully loaded and can be
// processed
}
@Override
public void error(@Nullable PCError pcError,
@Nullable PCNetError pcNetError) {
// Handle a error
}
});
} else {
// No need to download an attachment
}
}
}
@Override
public void error(PCNetError pcNetError) {
// Handle a error
}
});
}
@Override
public void error(PCNetError pcNetError) {
// Handle a error
}
});
In order to process the operation, the list of transactions to be confirmed and the list of transactions to be declined must be specified. Each list might be empty. but not both of them. The lists must not intersect as well.
Processing an operation
The following code snippet demonstrates how the PCOperation
may be processed. Here, we assume that the attachments have been already downloaded for all the transactions intended to be processed.
final PCUser pcUser = PCUsersManager.getById(TARGET_USER_ID);
final PCOperation pcOperation = getTargetOperation();
/*
* STEP 1. Allow the client to specify the transactions to be confirmed and/or declined.
* Each transaction must be a reference to an object from the list returned by
* pcOperation.getTransactions()
*/
List<PCTransaction> transactionsToConfirm = getTransactionsSelectedForConfirmation();
List<PCTransaction> transactionsToDecline = getTransactionsSelectedForDeclination();
/*
* STEP 2. Check whether the password is required
*/
if (!pcUser.isReadyToSign()) {
String password = submittedPassword();
int result = PCUsersManager.submitPassword(pcUser, password, new PCSubmitPasswordCallback() {
@Override
public void success() {
// Go to STEP 3
}
@Override
public void error(@NonNull PCAbstractError error) {
// Some error occurred. Probably, the password is incorrect
// Handle the error
}
});
}
/*
* STEP 3. Process the operation
*/
int initialOperationSize = pcOperation.getTransactions().getSize();
PCOperationsManager.processOperation(
pcUser,
pcOperation,
transactionsToConfirm,
transactionsToDecline,
new PCProcessOperationCallback() {
@Override
public void result(@NonNull PCOperation operation,
@NonNull List<PCConfirmationResult> confirmationResults,
@NonNull List<PCDeclinationResult> declinationResults) {
// Generally, the operation has been processed
// However, the results must be analyzed to find out whether all the transactions have been processed successfully.
// A quick check that number of processed transactions coincides with the number of transactions intended to be processed
if (initialOperationSize - operation.getTransactions().getSize()
== transactionsToConfirm.getSize() + transactionsToDecline.getSize()) {
// Everything is alright, all transactions have been processed
// ...
} else {
// Some transaction has failed, search for errors
for (PCConfirmationResult result: confirmationResults) {
if (result.getError() != null) {
// Some transaction has not been confirmed
// You can process the error and call result.getTransaction() to find out which transaction has failed
}
}
for (PCDeclinationResult result: declinationResults) {
if (result.getError() != null) {
// Some transaction has not been declined
// You can process the error and call result.getTransaction() to find out which transaction has failed
}
}
}
}
@Override
public void error(@NonNull PCAbstractError error) {
// Some critical error has occurred. No one transaction has been confirmed
}
});
Logging in a website
PC server and PC SDK provide functionality to log on a website using a QR code instead of submitting any usernames or passwords. The detailed description of this feature can be found here.
PC SDK provides a simple class PCLogin
which is responsible for scanning QR code and performing logging into a website.
The sequence of actions which are performed on a mobile device is usually the following:
- The application scans QR code for logging in and its content is passed to PC SDK to initiate the process.
- PC SDK analyzes the QR code validity (checks that it has not expired yet and at least one
PCUser
is stored on the device for which the system ID is the same as specified in the QR code). Provided the QR code is valid, PC SDK suggest the list ofPCUser
objects that can be used to log in. - The client chooses a desired
PCUser
and thePCLogin
downloads the transaction which must be confirmed to log in. The transaction normally contains operating system name, browser name and other details about the device which is used to log into the website. - Client confirms the transaction and after the information between PC Server and the website is synced, the client is authenticated.
The following code sample demonstrates the above-mentioned process.
/*
* STEP 1. Import data from scanned QR code
*/
PCLogin.importFromJson(qrSource, new PCLogin.GetLoginDataCallback() {
@Override
public void error(PCError pcError) {
// Some error occurred. Probably, the QR code has expired or no
// appropriate PCUser found in storage
}
@Override
public void success(PCLogin pcLogin) {
/*
* STEP 2. Get the list of PCUsers which are eligible to perform logging in. Allow client to
* pick up the desired key if there are several possible variants
*/
List<PCUser> users = pcLogin.suggestUsers();
// Assume, we prefer the first key
PCUser targetUser = users.get(0);
/*
* STEP 3. Request transaction for logging in and display it in your app.
* You should display it as a regular transaction with both options to confirm and decline
*/
pcLogin.requestLoginTransaction(targetUser, new PCLogin.GetLoginTransactionCallback() {
@Override
public void error(PCError pcError, PCNetError pcNetError) {
// An error has occurred. Handle it
}
@Override
public void success(PCTransaction pcTransaction) {
/*
* STEP 4. Check if the password must be submitted
*/
if (!targetUser.isReadyToSign()) {
String password = submittedPassword();
int result = PCUsersManager.submitPassword(targetUser, password, new PCSubmitPasswordCallback() {
@Override
public void success() {
// Go to STEP 5
}
@Override
public void error(@NonNull PCAbstractError error) {
// Some error occurred. Probably, the password is incorrect
// Handle the error
}
});
}
/*
* STEP 5. Confirm or decline the transaction depending on what the client
* has selected
*/
if (clientDecidedToConfirm) {
pcLogin.confirm(new PCGeneralCallback() {
@Override
public void success() {
// The transaction is confirmed. The user will be authenticated
// shortly on the website
}
@Override
public void error(@Nullable PCError pcError, @Nullable PCNetError pcNetError) {
// Handle the error
}
});
} else {
pcLogin.decline(new PCGeneralCallback() {
@Override
public void success() {
// Transaction is declined. Authentication will be rejected
}
@Override
public void error(@Nullable PCError pcError, @Nullable PCNetError pcNetError) {
// Handle the error
}
});
}
}
});
}
});
NOTE: Although the method
PCLogin.importFromJson()
uses callback for providing results instead of returned value, it is still a synchronized method.NOTE: Although you can confirm or decline login transaction in a regular way using either
PCTransactionsManager.sign()
orPCTransactionsManager.decline()
, you are encouraged to usepcLogin.confirm()
andpcLogin.decline()
instead.
Advanced usage
This section contains brief information about other functionality of PC SDK which also may come in handy.
Multiple confirmation and declination
If your application is desined in such a way that the client can confirm or decline several transactions at once, you had better not use a loop with single calls of PCTransactionsManager.sign(PCUser user, PCTransaction transaction, final PCSignCallback callback)
but call a designated method for multiple transaction processing like the following:
ArrayList<PCTransaction> mTransactionsToProcess = new ArrayList();
// Consider, mTransactionsToProcess is somehow filled
PCTransactionsManager.sign(
mUser,
mTransactionsToProcess,
PCRegularKeysProcessor.getInstance(),
new PCMultipleConfirmationCallback() {
@Override
public void onNextResult(@NonNull PCConfirmationResult result) {
// Called every time, a transaction has been processed.
// If result.getError() is null, the transaction has been confirmed
// successfully. Otherwise, examine the error code for the reason
// of failure.
}
@Override
public void error(@NonNull PCAbstractError error) {
// An error has occurred prior to any transaction has been confirmed
}
});
NOTE: You can use the similar
PCTransactionsManager.decline()
method to perform multiple declination.
Keys backup and restoring
In case, some happens with the device and the client has to change it, they will lost their PC keys. The mobile app backup will not resolve this problem and the client will have to personalize the mobile app again.
Fortunately, Pay Control provides the mechanism to backup and restore PCUser
objects so that it can be securely transferred from one device to another (or can be reinstalled on the same device).
Check if the key can be backed up and restored
When the key backup is done and it is to be restored, the PC Server runs a procedure called Remote update. This feature can be disabled for your system, so to ensure the key can be backed up, run PCUser.isRemoteUpdateEnabled()
first.
Creating a backup
You can create a key backup with just calling one method PCUserRestore.createBackupData()
which requires a PCUser
instance to be backed up and a password to encrypt the backup content. This password will not be saved anywhere, so that the client must memorize it or save in a secure way.
Here is a sample code snippet that demonstrates how a backup can be created.
// user is an instance of PCUser and password is a password entered by a client.
// Password must at least 6 chars in length
PCUserRestore.createBackupData(user, password, new PCUserBackupCallback() {
@Override
public void success(@NonNull PCUserBackupData pcUserBackupData) {
// Backup is created successfully
}
@Override
public void error(@Nullable PCError pcError, @Nullable PCNetError pcNetError) {
// Backup was not created. Handle a error
}
});
Storing a backup
The created backup contains sensitive data which is used to restore the keys. Although, it is encrypted with a user-provided password, it must be stored in a secure place. The best way to store it is in serialized form. You can serialize the PCUserBackupData
object by calling PCUserBackupData.toJson()
. The output JSON-string can then be used to restore a backup. The good place to store this string is a text file in a secure area of the client's Google Drive. However you are free to specify your own ways to store clients' key backups.
Restoring a backup
Once the client wants to restore a backup, it can be easily done by calling PCUserRestore.restoreBackup()
with a target PCUserBackupData
instance and a password as arguments.
Here is an example of how a backup should be restored.
// serializedBackup is a previously created JSON-string
PCUserBackupData backupData = PCUserBackupData.fromJson(serializedBackup);
PCUserRestore.restoreBackup(backupData, password, new PCRestoreCallback() {
@Override
public void success(@NonNull PCUser pcUser) {
// PCUser is restored successfully.
// After the PCUser is restored it must be saved on the device and new key pair must
// be registered on the PC Server
}
@Override
public void error(@Nullable PCError pcError, @Nullable PCNetError pcNetError) {
// Backup was not restored. Handle an error
}
});
NOTE: After the backup is restored, you should follow all the standard actions related to mobile app personalization (like if the key was imported form a QR code). Refer to Registering users for further details. Some steps will be omitted though (e.g. key importing and key activation).
Task management
To perform network requests to a PC Server the SDK creates a task for each request. You normally receive the results via specified callbacks. The callbacks are invoked shortly for the majority of the requests provided that the internet connection is not too poor.
However, some network requests may involve more intensive data exchange. For instance, PCTransactionsManager.getTransactionBinaryData()
might generate a heavy traffic when a large attachment is being downloaded. If the client decides to quit a screen while an attachment is being downloaded, the connection will not be closed automatically. So, you have to close the connection from the code.
Each callback that involves internet connection has default empty implementations of onTaskCreated(PCTask task)
and onTaskCancelled()
. You can override them to control the tasks manually.
The following example demonstrates how a task can be canceled manually.
PCTransactionsManager.getTransactionBinaryData(user, transaction, new PCGetTransactionBinaryDataCallback() {
@Override
public void success(PCTransaction pcTransaction) {
}
@Override
public void error(@Nullable PCError pcError, @Nullable PCNetError pcNetError) {
}
@Override
public void onTaskCreated(@NonNull PCTask task) {
// This method is called once the internet connection is established
// Call this method if you want to interrupt the connection immediately
// e.g. if the client decides to quit the screen
PCTasksManager.cancel(task);
}
@Override
public void onTaskCancelled() {
// This method is invoked when the task is canceled. You can override
// it if you want to perform some specific actions at this step.
}
});
Fine-tuning
Once the SDK is initialized, some default settings are applied to ensure the recommended behavior. However, there are some handy methods that can change the default behavior for some special cases.
Password resubmitting after app has been in background
Once the password was submitted for a particular PCUser
via PCUsersManager.submitPassword(...)
it is not required to be resubmitted until the app is running. By default, if the app was in a background state for less than 15 seconds, the password is still not required again to confirm transactions. You can change this behavior by calling PCSDK.setHandlesBackgroundTimeout(...)
at any moment after SDK is initialized.
PCSDK.init(getApplicationContext());
// Allows the app to be in background up to 30 seconds with no need to resubmit password
PCSDK.setHandlesBackgroundTimeout(30_000);
Callbacks execution
By default, all the callbacks are executed in a main thread regardless the thread from which corresponding PCSDK methods were invoked. If you want callbacks to be invoked from the same thread where you pass this callbacks to PCSDK methods, you can use PCSDK.setCallbacksInvokedInMainThread()
and pass false
:
PCSDK.setCallbacksInvokedInMainThread(false);