Installation

Xcode 14.3.1 Swift 5.8.1

Swift Package Manager

Select File > Swift Packages > Add Package Dependency... and add a repository URL:

dependencies: [
    // Standard version
    .package(url: "https://repo.paycontrol.org/git/ios/pcsdk.git", from: "6.0.0")
    // Gost version
    .package(url: "https://repo.paycontrol.org/git/ios/pcsdk-gost.git", from: "6.0.0")

]

CocoaPods

Add the following line to your Podfile:

# Standard version
pod 'PCSDKModule', :git => 'https://repo.paycontrol.org/git/ios/pcsdk.git'

# Gost version
pod 'PCSDKGost', :git => 'https://repo.paycontrol.org/git/ios/pcsdk-gost.git'

Manual

Download the latest PCSDKModule or PCSDKGost framework and add it to the project.

Initialization

To inialize PC SDK library you just call PCSDK.initialize(...) method.

If you need to set log level for PC SDK, then you sould call setLogLevel method and set log level.

:::caution Do NOT use .sensitive log level in release builds.
This option will log out keys values and password values into logging system. :::

#if DEBUG
PCSDK.setLogLevel([.debug, .sensitive])
#endif
PCSDK.initialize()

Also, you can provide additional parameters during initialization:

  • databaseURL: The url for storing users data. If it is not set - SDK will use the default URL provided by PCSDK.defaultDatabaseURL property
  • accessGroup: The access group to store new Keychain's keys. If not set, the default access group will be used (app ID or Keychain Access Group).

Detailed: How to use PCSDK in extensions

User registration

User registration process contains followings steps:

  1. Importing personalization data got from the PC Server (via QR, QR + activation code, JSON-value)
  2. Activation the user if needed
  3. Registering PC User on PC Server
  4. Store user's keys in internal storage for futher using

The activation code could be delivered by other channel (SMS, Push Notifications)


// 1. Importing JSON info
let userJSON = ... // User's data from the server
let user = try PCUsersManager.importUser(from: userJSON)

// 2. Activating the user
if !user.isActivated {
    try await PCUsersManager.activate(user: user, using: activationCode)
}

// 3. Registering
try await PCUsersManager.register(
    user: user, // The imported user
    deviceToken: deviceToken // The device token for PUSH notifications
)

// 4. Storing
try await PCUsersManager.store(
    user: user, // The imported user
    name: storingName, // The friendly name to store user
    password: storingPassword // The password to store
)

Transactions

Online


// Getting the apropriate user.
// We are using the first stored user in this example
guard let user = PCUsersManager.users.first
    else { ... }

// Getting the available transactions list for this user
let transactionsList = try await PCTransactionsManager.getTransactionList(for: user)

// Getting transaction's data
// We are using the first transaction in the list in this example
guard let firstTransactionID = transactionsList.first else { ... }

let transaction = try await PCTransactionsManager.getTranaction(
    for: firstTransactionID, 
    user: user
)

// Transaction may contain the binary data
// It is necessary to download it before confirming or declining
if transaction.hasBinaryData {

    try await PCTransactionsManager.getTransactionBinaryData(
        for: transaction,
        user: user
    )

}

// Providing password before processing
if !user.isReadyToSign {
    try await PCUsersManager.submit(password: password, for: user)
}

// Confirming
try await PCTransactionsManager.sign(
    transaction: transaction,
    by: user
)

Offline

If a device is offline, then PC can calculate offline confirmation/declination code for a transaction.

In this case PC SDK can not get the transaction's data from PC Server, and you should use QR-code scanner to get the transaction's data.

Calculated confirmation/declination code should be provided to a user. The user will input this code manually into Application web-site. After this Application can verify it using PC Server API.

Flow will be following


// Scan QR-code with app's QR scanner, get qrValue
let transactionQRValue = ... 

// Validation the value
guard PCSDK.analyzeQR(userJSON) == .transaction
    else { ... }

// Importing the QR-code value
let transaction = try PCTransactionsManager.importTransaction(from: transactionQRValue)

// Choosing the apropriate user to process tranaction
// The user and the transaction must have the identical systemID value
// We use the first apropriate user in this example
guard let user = PCUsersManager.first(where: { $0.systemID == transaction.systemID })
    else { ... }

// Providing password before processing
if !user.isReadyToSign {
     try await PCUsersManager.submit(password: password, for: user)
}

// Confirming
let confirmation = try await PCTransactionManager.signOffline(
    transaction: transaction,
    by: user
)

// Getting the code to confirm the transaction offline
let code = confirmation.confirmationCode

Logging

SDK has an option to use an external logger. The external logger must inherit the PCLoggerProtocolprotocol.

public protocol PCLoggerProtocol {

    func debug(_ message: String, category: PCLoggingCategory)
    func error(_ message: String, category: PCLoggingCategory)
    func sensitive(_ message: String, category: PCLoggingCategory)

}

/// Example logger

final class MyLogger: PCLoggerProtocol {

    func debug(_ message: String, category: PCLoggingCategory) {
        ...
    }

    func error(_ message: String, category: PCLoggingCategory) {
        ...
    }

    func sensitive(_ message: String, category: PCLoggingCategory) {
        #if DEBUG
        // Process sensitive information only in DEBUG mode to keep safe
        // user's sensitive data, e.g. symmetric keys values, personal data etc.
        ... 
        #endif
    }

}

After defining your logger, you must pass it to the method PCSDK.setLogger(...):

let myLogger = MyLogger()
// PCSDK.setLogLevels([.debug, .keys]) <- This should be removed from your code
PCSDK.setLogger(myLogger, options: [.debug, .sensitive])

After that, all messages will be sent to your logger.


Saving to file

There are no internal methods to persist logs in files in SDK. But you can adopt any third-party logger to PCLoggerProtocol protocol.

E.g. you can use CocoaLumberjack. To use it, firstly, confgure CocoaLumberjack's file logger:

import CocoaLumberjack
import CocoaLumberjackSwift

...

let fileLogger = DDFileLogger()
fileLogger.logFileManager.maximumNumberOfLogFiles = 2  // Keeping only 2 files
fileLogger.maximumFileSize = 256 * 1024                // 256kb each one
fileLogger.rollingFrequency = 60 * 60 * 24             // with messages for last 24h
DDLog.add(fileLogger)

Now your logger should just translate messages to CocoaLumberjack's methods:

final class MyLogger: PCLoggerProtocol {

    func debug(_ message: String, category: PCLoggingCategory) {
        DDLogInfo("[SDK][\(category.rawValue)] \(message)")
    }

    func error(_ message: String, category: PCLoggingCategory) {
        DDLogError("[SDK][\(category.rawValue)] \(message)")
    }

    func sensitive(_ message: String, category: PCLoggingCategory) {
        #if DEBUG
        DDLogInfo("[SDK][Sensitive][\(category.rawValue)] \(message)")
        #endif
    }

}

After that you will be able to get persisted logs using CocoaLumberjack`s API:

fileLogger
    .logFileManager
    .sortedLogFileInfos
    .compactMap { (info: DDInfo) -> Data in
        FileManager.default.contents(atPath: info.filePath) // Reading logs content
    }
    .forEach { (logData: Data) in
        ... // Process the log's data
    }

An additional information about using CocoaLumberjack`s you can read at its GitHub page.

Using PCSDK In Extensions

To use PCSDK in extensions it necessarry to have the shared db and the same access group to the app and the extension.

To do that you must use App Groups and initialize the app and the extension in different ways.

The preparations for both the app and the extension


// Making the shared path to the DB
let appGroupIdentifier = ... // The App Group identifier
guard let sharedContainer = FileManager
    .default
    .containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
else { ... }

let sharedDatabaseURL = sharedContainer.appendingPathComponent(PCSDK.defaultDatabaseFileName)

Initializing app and extensions

PCSDK.initialize(
    databaseURL: sharedDatabaseURL,
    accessGroup: appGroupIdentifier
)

Process Transaction by Push notification

PC Server can send push notification when transaction was created.
In this case Application can get User ID and Transaction ID from notification and process only one transaction.

The default iOS push template:

{
  "aps": {
    "alert": "%PUSH_TEXT%",
    "sound": "%PUSH_SOUND%",
    "badge": 1
  },
  "type": "PayControl",
  "userid": "%USER_ID%",
  "transactionid": "%TRANSACTION_ID%"
}

Please, be noticed, that push templates can be changed for your app by PC Server configuration.# PCSDK iOS

Gost

To use GOST algorithms is neccessary to add additional resources to the project from the Resources folder of the repository.

  • The entire locale folder (should be placed to the root of the app bundle)
  • config.ini
  • Localizations folders en.lproj and ru.lproj
  • RndmBioViewController.xib and RndmBioViewControllerIPhone.xib

KYC

Description

KYC is a part of PC Platform which allows to perform remote identification in the mobile application based on verifying client's photos and/or videos alongside with their scanned documents. The remote identification is intended to be performed prior to issuing keys by the PC Server and personalizing the mobile application.

KYC extension interacts with eKYC Connector which receives data from the application and performs the analysis. Refer to this page to find out more about eKYC Connector and the general description of the KYC concept.

The KYC extension initiates a session with initial data (which may be delivered e.g. by QR code) and asks the KYC Connector about the required data for remote identification. The KYC Connector sends the list of necessary media files (which usually includes a short live video and a photo of the passport) and KYC extension, in turn, collects the required sources and sends them to the analysis. After the KYC Connector has verified that the person in a video and on a passport photo is the same, it starts OCR procedure to retrieve the information from the passport. After OCR is done, the mobile application receives a notification, so that the client can confirm that all the fields in the password have been recognized properly. After performing a successful OCR procedure, the KYC Extension requests the list of available options for verifying documents. The client is supposed to choose a preferable way to get ther documents verified. Once verification method is defined, KYC Connector sends the documents for verification and notifies the application about results. Upon a successful verification, the KYC Extension requests the keys from the Connector and the app may be personalized with a retrieved PCUser.

Installation

Manual

Download the latest PCKYCModule.xcframework and add it to the project.

CocoaPods

Add the following line to your Podfile:

pod 'PCKYCModule', :git => 'https://repo.paycontrol.org/git/ios/kyc.git'

Swift Package Manager

Select File > Swift Packages > Add Package Dependency... and add a repository URL:

https://repo.paycontrol.org/git/ios/kyc.git

Carthage

Since Carthage does not support distribution of binary frameworks this integration way is not supported.

Usage

Classes overview

While using KYC Extension in your app, you are going to interact with the following objects:

  • KYCSession - Struct which represents a separate session established with KYC Connector.
  • PCKYCManager - Struct which manages your sessions. The follwoing guide is primarily devoted to showing proper usage of this class.
  • PCKYCError - Enum which represents errors.

Step 1. Add logging

To receive logs from this framework you should set the PCKYCManager.externalLogger parameter. See Logging section for the example of PCLoggerProtocol realisation.

Step 2. Get KYC info

Each session requires configuration which describes analysers and verifiers to use. To determine which analysers and verifiers are available invoke PCKYCManager.getKYCInfo method.

PCKYCManager.getKYCInfo(
    kycConnectorURL: self.connectorURL, // The KYC connector URL
    completion: { result in
        switch result {
            case let .success(kycInfo): ... // KYC info
            case let .failure(error): ...   // Error
        }
    }
)

Step 3. Create a session

To start a session, you call PCKYCManager.startSession() method.


let sessionParameters = KYCSessionParameters(
    analyser: analyser,            // Choosen analyser
    preliminaryVerifier: verifier, // Choosen preliminary verifier
    verifiers: [verifier],         // Verifiers to use
    ocrRetriever: ocrRetriever     // Choosen ocr retriever
)

PCKYCManager.startSession(
    kycInfoJSON: jsonValue,        // The JSON string with KYC connector's data
    deviceToken: deviceToken,      // Device token to receive push notifications
    parameters: sessionParameters, // The session parameters 
    completion: { result in
        switch result {
            case let .success(newSession): ...  // Successfuly created session
            case let .failure(error): ...       // Error
        }
    }
)

Step 4. Get required sources

The first thing to do with a just created session is to determine which types of sources the client will be asked to upload.

// You may get required sources from `session` instance
let requiredSources: Set<PCKYCMediaType> = session.requiredSources

// ... or calling the PCKYCManager's method
PCKYCManager.getRequiredSources(
    session: session,     // The session instance
    completion: { result in
        switch result {
            case let .success(sources): ...  // Sources
            case let .failure(error): ...    // Error
        }
    }
)

Then you should prepare required sources: capture videos, take photos, etc. See the list of available sources.

Step 5. Upload the collected sources

Once the media files are collected, they must be uploaded for analysis.

// Uploading media from url
PCKYCManager.uploadMedia(
    mediaURL: url,          // URL to media file (e.g. video)
    type: mediaType,        // The type of the uploading media
    session: session,       // The session instance
    completion: { result in
        switch result {
            case .success: ...              // Done
            case let .failure(error): ...   // Error
        }
)

// Uploading media from data
PCKYCManager.uploadMedia(
    data: data,             // Binary data of media (e.g. photo)
    extension: "jpg",       // The extension of the media, e.g. "jpg"
    type: mediaType,        // The type of the uploading media
    session: session,       // The session instance
    completion: { result in
        switch result {
            case .success: ...              // Done
            case let .failure(error): ...   // Error
        }
)

Step 6. Check the session status

In your app, you are supposed to check the session status from time to time so as that you are able to handle any changes with the session.

To learn about possible session statuses, refer to the API documentation on PCKYCSession​Status.​Status.

PCKYCManager.getSessionStatus(
    session: session,   // The session instance
    completion: { result in
        switch result {
            case let .success(status): ...  // Session status
            case let .failure(error): ...   // Error
        }
)

Step 7. Download OCR results

After uploading required media files, the KYC connector will perform the analysis. Once, the analysis is successful, you will receive the status .ocrSuccess which means, your passport has been scanned successfully. You are supposed to download scanning results and show it to a client so that they can confirm the correctness of the OCR results.

PCKYCManager.getOCRResults(
    session: session,   // The session instance
    completion: { result in
        switch result {
            case let .success(orcResults): ...  // OCR results [String: Any]
            case let .failure(error): ...       // Error
        }
)

Normally, the result dictionary contains keys with data from the passport: name, dateofbirth, sex, address, number, dateofissue, placeofissue, issuecode.

Step 8. Approve OCR results

After the client has checked the OCR results, their validity must be approved by the application. To approve the OCR results, just call KYCManager.approveOCRResults(session:completion:).

In case the OCR results are invalid, you have to provide an opportunity for a client to notify the KYC Connector about the problem. Call KYCManager.declineOCRResults(session:completion:)

In some cases it is possible that the OCR fails (e.g., the quality of uploaded photo was poor). Then instead of .ocrSuccess the session will come to .ocrFailed. It is also possible that the client rejects OCR results and the session gets its .ocrRejectedByUser status. Your application may behave the same way in both cases: it can restart an OCR procedure. To do this, you must call KYCManager.getRequiredSources(session:completion:) to see which pages of the passport must be uploaded again. Then call KYCManager.uploadMedia(...) to resend required photos.

Step 9. Verify documents validity

Once, the OCR results have been approved by the client, the session status turns to .ocrApprovedByUser. The next step is to get documents verified by government authorities. The KYC Connector can support several ways of documents verification. To find out which ways can be applied, KYCManager.getVerifiers(session:completion:) should be called. The method returns the list of available verifiers in a callback.

After getting the list of supported verifiers you should provide the opportunity to choose the most suitable verifier for your clients. Refer to documentation on KYC Connector to learn more about each verifier.

The following code snippet shows how to start documents verification with a particular verifier:

PCKYCManager.startValidation(
    session: session,   // The session instance
    verifier: verifier, // Verifier to validate
    completion: { result in
        switch result {
            case .success: ...              // Validation started
            case let .failure(error): ...   // Error
        }
)

Step 10. Handle validation

Additional steps may be required after starting validation — providing an additional data or following link to the other app.

You can get and additional data invoking PCKYCManager.getSessionStatus method and getting value of KYCSessionStatus.credentials value.

Step 11. Obtain your keys

Once the documents verification has started you should wait until the session reaches .pckeyReady status. This means, the PC symmetric keys have been issued and you can register them on a device. This is a final step. As a result, you receive PCUser object and pass it to PCUsersManager for further processing.

PCKYCManager.getPCUser(
    session: session,   // The session instance
    completion: { result in
        switch result {
            case let .success(user): ...    // User to register
            case let .failure(error): ...   // Error
        }
)

See Registration to learn about further actions with PCUser object.

NFC via Rutoken

Installation

To use PCSDK with NFC cards install the RutokenModule using following instructions and additional frameworks from the Rutoken's site:

  • rtpkcs11ecp.framework
  • RrPcsc.framework

Also, imported OpenSSL library is required to use RutokenModule.

Manual

Download the latest RutokenModule.xcframework and add it to the project.

CocoaPods

Add the following line to your Podfile:

pod 'RutokenModule', :git => 'https://repo.paycontrol.org/git/ios/rutoken.git'

Swift Package Manager

Select File > Swift Packages > Add Package Dependency... and add a repository URL:

https://repo.paycontrol.org/git/ios/rutoken.git

Carthage

Since Carthage does not support distribution of binary frameworks this integration way is not supported.

Using RutokenModule

... TBD ...

Changelog

6.0.13

  • Added PCUser.activationCodeLength parameter
  • Added PCTransactionsManager.getTransactionStatus(...) method to check transaction status

6.0.7

Version 6.0. Supporting APIv7, GOST algorithms, async/await and more

  • Added support of APIv7
  • Added support of GOST algorithms
  • Internals have been rewritten using the new Swift concurrency with async/await methods and Sendable conformance.

5.5.483

  • Added additional logs at the checking api version step

5.5.482

  • Fixed XML import for APIv1/v2
  • Added PCError.invalidSource error. It appears if import source string is wrong.

5.5.481

  • Fixed submitting password issue when network is offline
  • Added caching of local keypair to avoid extensive logging

5.5.480

  • Moved assigning access group process to initialization method
  • Updated internal finding keypairs methods to avoid working issues of apps extensions

5.5.479

  • Fixed collecting device info for APIv1/v2
  • Updated PCSDK.initialize(...) method - removed mode parameter to use in extensions
  • Added access group migration method

5.5.478

  • Fixed resetting fingerprint
  • Updated collecting memory information

5.5.477

  • Updated BuildInfo.plist version to avoid iOS 13 launch bug

5.5.476

  • Added PCSDK.reset() method to reset all in-memory values.
  • Updated PCSDK.removeAllUsers() implementation. Now it removes all persisted data - users with keys and DB, local encryption keypair and caches.

5.5.475

  • Added Indonesian locale

5.5.475

  • Added Indonesian locale

5.5.474

  • Added PCSDK.buildInfo parameter reflecting BuildInfo.plist values

5.5.473

  • Removed bitcode

5.5.470

  • Updated methods using online credentials
  • Fixed handling APIv1/v2 responces
  • Fixed decoding APIv1/v2 errors

5.5.465

  • Set minimum supported iOS version to 11.0
  • Changed PCParameters name to ExtraParameters to reflect server api

5.5.464

  • Fixed issue with empty public key registration

5.5.463

  • Resetting handles after user deletion
  • Updated server error codes to match server changes

5.5.462

  • Updated user activation to avoid issues with APIv6

5.5.459

  • Added missing errors localization

5.5.458

  • Implemented submit(...) and store(...) methods with callback to support APIv6 online credentials
  • Deprecated old methods
  • Added new error codes
  • Fixed updating user and renew
  • Added default confirmationCodeLength constant

5.5.457

  • Fixed PCServerError switch statements matching

5.5.454

Version 5.5. Supporting APIv6

  • Added support of APIv6
  • Added new method PCUsersManager.activate(...) with callback to support APIv6 online credentials check.
  • Changed PCServerError type to struct.
  • Added new errors of APIv6

5.4.453

  • Managing of handles has been improved

5.4.452

  • Supporting new transaction types
  • Supporting of custom push notifications services and Edna push notifications service
  • Supporting of appExtra parameter in PCTransaction and PCOperation
  • PCUser registration result type has been changed to RegistrationResult which contains appExtra values
  • Internal fixes and improvements

5.4.444

Version 5.4. Support APIv5, fixes and improvements

  • Added support of APIv5
  • Added the PCSDK.clearCaches public method for clearing caches
  • Added the PCUser.reset public method for reseting PCUser's handles
  • Internal fixes and improvements
  • Updated errors descriptions

5.3.441

  • Added the new parameter PCSDK.fixedAPI

5.3.440

  • Fixed the default value of the confirmationCodeLength parameter

5.3.439

  • Improved management and security of handles

5.3.438

  • Fixed the updating of password when using PCUsersManager.store method
  • Fixed the collection of a location data
  • Fixed isReadyToSign parameter
  • Fixed parsing of XML data
  • Fixed installation path
  • Added Chinese localization

5.3.429

  • Added new method for renewing PCUser

5.3.426

Version 5.3. External keys processors, new events, fixes and improvements

  • Added methods to process multiple transactions at once: PCTransactionManager.sign(transactions:user:completion) and PCTransactionManager.decline(transactions:user:completion)
  • Sync methods PCTransactionManager.signOffline and PCTransactionManager.declineOffline have been marked as deprecated. Added new async methods.
  • Refactored internal architecture to support external keys processors via KeysProcessor protocol.
  • PCKYCManager has been extracted to the separate framework
  • Added new events (password_correct and password_changed)
  • Removed deeplinks from sdk
  • Updated dependencies
  • Other minor fixes and improvements

5.2.414

Supporting of Server API v4 has been added:

  • Operations have been added. Operation is generally a bunch of related transactions which can be processed altogether. Refer to documentation on PCOperationsManager and PCOperation for more details.
  • eKYC module has been added. This module provides functionality for remote identification which can be used as a preliminary step before issuing the keys. Refer to documentation on PCKYCManager and PCKYCSession for more details.
  • Key backups have been added. Now, the key can be backed up and restored later (provided that the server supports this functionality and backups are enabled). Refer to documentation on PCUserBackupData and PCUserRestore for more details.
  • Calculation of signature has been extended for CMS, CSR and standard types
  • Parameters type and cmsAuthenticatedAttributes have been added to PCTransaction
  • PCUsersManager.getCertificateInfo method has been added. The transaction type property has been added. Tests has been added.
  • A snippet parameter has been added to PCTransaction
  • The suggestedUserName property has been added to PCUser

Other improvements: - Now you can attach external logger to SDK using PCSDK.setLogger method. - Stream-based confirming, declining and autosigning have been implemented - Downloading binary data to file has been implemented - Errors list has been updated - Target queue has been added to async methods - Other internal optimisations, fixes and improvements have been added