Success and Error handling

The majority of the Client APIs are request based calls returning completion handlers in response. Successful requests made to the Client SDK are returned in an onSuccess completion handler:

onSuccess

The success block takes two forms:

returning an empty block

    () -> Void

returning an object e.g.

    (_ user : [Transaction]) -> Void

Failures from an SDK API call are flagged using error completion handlers. The biid SDK relies heavily on error handling and as such errors should not be ignored on any SDK calls as they represent all the ways that an API can fail.

There are six different types of exceptions that can be handled by the SDK. Each is discussed in detail below:

  • No Connection Error

  • Invalid User State Error

  • Validation Error

  • Invalid Token Error

  • Client Error

  • SDK Process Specific Exceptions

No Connection error

A noConnection error is returned if a network request fails. Reasons for failure can include:

  • No active network available
  • No matching certificate found for the pinned endpoint (see Certificate Pinning).

Invalid User State Error

Most useful interactions with the SDK require the user to have certified their device i.e. a signed certificate exists on the device. This is represented by the user status of CERTIFIED. A user with this status can create, sign, accept, confirm and reject different types of transactions.

However there are several status iterations that the SDK has to go through to get this status see Certificate Process for more information.

For example if a user tries to sign a transaction and they do not have the status of CERTIFIED, they will receive a UserNotCertifiedError.

UserNotCertifiedError itself is a subclass of an InvalidUserStateError. These types of errors are a hint that the user is in the incorrect state to perform the action that was requested. These errors are very useful in that they are somewhat unavoidable in wider user journeys. User journeys which are interrupted by these errors can be caught and corrected easily.

Once an invalid state is caught the best practice is to perform a requestUser() call. The result of this call will always give the current user status.

It is recommended that the application implementing the SDK should be built in a reactive way with the requestUser() call at its center. For example, during on-boarding a user will need to be taken through the registration, verification and accreditation phases before certification (generally in that order). These type of errors detect and inform the developer that something was missed or that the user status has changed unexpectedly and the user status should be requested and acted on.

These types of errors can be caught specifically using subclasses of the InvalidUserStateError. For example you can catch a UserNotRegisteredError if the user is not registered. Alternatively the InvalidUserStateError can be caught for all invalid states and the direct cause retrieved by accessing the .invalidState property. In the previous example this would return .notRegistered. In either case the error handler should always send the user through a journey that requests the user status and pushes the user to the correct flows to resolve the incorrect state.

Invalid states can happen for a number of reasons, for example; a result of a fraud check done by a payment provider may in extreme cases cause the user’s certificate to be revoked. This will take the user from the CERTIFIED status to the UNCERTIFIED status, which means the certificate has been revoked a removed from the device and the user will need to go through the Certification Process again (calling either CertifyUserAtLevel1() or CertifyUser()). In another example, the user may change their telephone number, this will mean the user will need to re-verify their telephone number and will receive the status of UNVERIFIED. In this case the user should be pushed through to the Phone Verification process.

The invalid state exceptions do not necessarily give you the current status of the user so somewhere in resolution journey of this error, a requestUser() call should me made to retrieve the actual current status, and react to it.

The InvalidUserStateError and its subclasses all contain a traceId. As the exception is easily recoverable there is no need to display this traceId to the user, however it can be logged if the developer wishes for error resolution and analytical purposes for example.

All InvalidUserStateErrors are logged into elastic search, and are addressable via the .traceID property of the error.

Available InvalidUserStateError subclasses:

Validation Errors

Validation errors are represented by one type of error, a ValidationException. These errors are returned by API’s which accept user input and which need to be validated. They are very common and need to be handled correctly. When input validation performed by the platform fails, thie error will be returned with an error message from the server.

A single ValidationError is capable of encapsulating multiple errors from a single error. Each ValidationError will contain a collection of field name and errors messages for each validation that failed. A field name is either a user field name (e.g. registration field name, see Registration) as defined in the back office or a parameter name of the biid SDK method.

For example if a user tries to complete a registration user journey and calls the registerUser() method, and some of the user fields are incorrectly entered or are invalid, this error will be returned. Each error can be referenced back to the registration field in question using the keys in the errors property of the ValidationError. The messages in this collections are designed to be directly displayed to the user and are configurable and localisable in the back office.

Often ValidationError do not contain a traceID as they are not logged in elastic search due to their frequency and self correcting nature.

Invalid Token Errors

Invalid Token Exceptions are represented by a single exception; an InvalidTokenException. These exceptions are thrown when the token that is generated by the SDK is invalid. The following could cause this to happen:

  • Token expiry – The token currently being used by the SDK has expired.

  • Scope change – The token is no longer valid because the access permissions, scope or grant have changed.

  • OAuth token verification failure – A token or credential used in a claim has expired, or has been revoked.

There are only two methods for resolving this exception: re-authorisation or re-authentication.

Token expiry and scope changes are mostly resolved by requesting a new token, this is the most common cause of this exception and the SDK handles most of this on behalf of the developer. However all other causes of an InvalidTokenException require the user to re-authenticate to the SDK, which in addition could mean the user needs to re-enter credentials. This second type of exception is very rare and generally once a user has entered credentials into an application and acquired a token which is subssequently passed in to the authenticate() method of the SDK, the resultant token lasts for the length of the session.

For tokens that have expired or changed scope the SDK will reauthorize (request a new token) on the developers behalf. The exception will still be thrown post reauthorization but the exception gives the developer a resolution hint to fix the problem.

It is possible to check the error’s Action property to determine what action should be taken to resolve the error:

Swift3

    ...
    onInvalidToken: { (error) in
        if error.action == InvalidTokenError.Action.login {
            // authenticate again to get a new access token
        } else { // InvalidTokenError.Action.retry
           // retry failing call
        }
    }
    ...

ObjC

     ...
     onInvalidToken:^(InvalidTokenError * _Nonnull error) {
         switch (error.action) {
             case InvalidTokenErrorCodeLogin:
                 // Stuff
                 break;

             case InvalidTokenErrorCodeRetry:
                 // Stuff
                 break;

             default:
                 break;
         }
     }
     ...

InvalidTokenException with RETRY : Simply call the method that threw this exception again. The new token will be used this time.

InvalidTokenException with LOGIN : This is a more serious invalid token exception and the user may have the re-authenticate with the calling application as there is likely a problem with the supplied OAuth token.

Documentation

Client errors

ClientErrors are returned when an error occurs with a biid client method itself. Client errors are represented by a single type of error; the ClientError. These errors have two purposes:

  1. To aid developers whilst integrating the SDK into an application, wrapping error conditions or errors returned from the server during setup.
  2. To encapsulate any unknown errors that happen. The iOS core SDK will never crash an application as it traps all unknown errors and returns a ClientError.

ClientError are not recoverable and as such should not be seen in a production application in normal cases. All production applications have user journeys that should display screens to the user in the event that something unknown or unexpected happens. For example if some critical API goes down due to an outage, these are the error which will be returned. An ‘unknown error’ user journey should be shown to the user here and the appropriate messages displayed.

NB ClientError returned with an error exception of clientNotSupported is returned when the version of the SDK in not compatible with the version of the platform it is connecting to. These can be returned from any API but do not need to be explicitly caught.

ClientErrors contain three properties to help identity the error:

  • error : The error represented by a ClientException
  • details : An optional string providing additional information where relevant
  • traceID : a unique ID generated per API call. Thethe traceID of the error is very useful. The traceId is a unique code which identifies the specific error instance to the user at the time the error occurred. This error code could also be physically shown to the user, this would help third line support identify the problem if the user reports the code to first line support. This code can be re-called from elastic search within the biid platform (see TraceID)

Documentation

SDK Process Specific Exceptions

The last type of errors that can be returned from the SDK are specific to the process that is being called. These range from the phone validation processes, the Certification Process, to transaction handling or security processes.

These types of errors are returned for very different reasons and should be considered when calling the relevant functions.

Listed below are some important examples of these errors. These errors all have a traceId and are logged to elastic search.

Verification Process Errors:

Certification Process Errors:

Security Errors:

Throwing Calls

A small number of API calls throw errors and should be called with a try or do-catch block in Swift or with an NSError in ObjC e.g.

Swift3

    do {
        try SDK.getClient.initialize()
    }
    catch (let exception) {
        // Handle exception
    }

ObjC

    NSError *error;
    [biidCoreSDK.getClient initialize:&error];
    if (error) {
        // Handle error
    }

TraceID

Each API request generates a unique ID (called a trace_ID) which is then associated with the request. This allows the request to be tracked though the back end micro-services. This id has been exposed via the traceID property in all Error Models models as an aid to debugging any issues with the call i.e. it can be used to find the request in the back end error logs.

The traceID for the request can be accessed as follows:

Swift3

    ...
    onInvalidToken: { (error) in
        let traceID = error.traceID
    }
    ...

ObjC

     ...
     onInvalidToken:^(InvalidTokenError * _Nonnull error) {
        NSString *errorMessageAndTraceID = error.description
     }
     ...

Note: In Objective-C the traceID (if set) is wrapped into the NSError description along with the error message.

e.g. ["urlName": "Not Found."] (traceID: QqTmrct8ph)