External Authorisation and Balance

Introduction

This documentation outlines Shaype’s external balance and external authorisation proposition. It will act as an overview of how the functionality works and any key considerations.

We use REST API, because it is the standard way of integration with the clients in our company. Endpoints, field names and types are largely influenced by existing hay-adaptor B2B Operations API and Notification event webhooks API for consistency.

OpenAPI Spec v3.0 used as API description format, because it’s one of the latest and well wide spread versions supported, according to OpenAPI.Tools website. It should allow both API parties to use code generation tools like OpenAPI Generator to automate client and server creation from the spec.

Balances

There must be a single source of truth for account balance information that is used for the authorisation of any transactions on the Shaype managed payment channels.​

This can be held and managed either by Shaype or by the Client.​

In each case there are differences in what information is held, how Shaype payment authorisation operates and how the Client performs additional activity on the Shaype accounts.

External Client Authorisation​ Flows

External Auth – Funds Deposit

External Auth – Funds Withdrawal

Shaype applied Limits

  • ATM_WITHDRAWAL_PER_DAY​
  • TOP_UP_PER_DAY​
  • CARD_PAYMENTS_DAILY​
  • PAYMENT_TO_ACCOUNT_NUMBER​
  • SINGLE_CARD_TRANSACTION​
  • TOTAL_SPEND_PER_YEAR​

API Design

Authorise a card hold

POST /holds - This would create a new card authorisation hold. The client would approve it or reject it with an error code.

request body:

{  
  "id": "1e07aaff-b93d-4e55-9f65-0b10d8923b68",  
  "accountId": "afd68d5a-2f3e-4166-97db-d5db72b76fca",  
  "cardId": "b373b5da-6917-4c8a-bd77-fd5eb8208774",  
  "amount": {  
    "amount": "-12.43",  
    "currency": "USD"  
  }  
}

response:

200 OK

470 - The request contained valid data and was understood by the server, but the server is refusing action due to internal validity, status or limit checks or similar failure of request to the 3rd party necessary to fulfil client request.

{  
  "errorCode": "REFUSED_MAX_BALANCE_EXCEEDED",  
  "reason": "string"  
}

500 Internal error

Update a hold amount

PATCH /holds/{holdId} - this would be used to update a hold amount

request body:

{  
  "amount": {  
    "amount": "-56.87",  
    "currency": "USD"  
  }  
}

response:

200 OK

470 - The request contained valid data and was understood by the server, but the server is refusing action due to internal validity, status or limit checks or similar failure of request to the 3rd party necessary to fulfil client request.

{  
  "errorCode": "REFUSED_MAX_BALANCE_EXCEEDED",  
  "reason": "string"  
}

500 Internal error

Authorise a transaction

POST /transactions - this would create a new transaction.

request body:

{  
  "id": "1e07aaff-b93d-4e55-9f65-0b10d8923b68",  
  "accountId": "afd68d5a-2f3e-4166-97db-d5db72b76fca",  
  "amount": {  
    "amount": "12.43",  
    "currency": "USD"  
  }  
}

response:

200 OK

470 - The request contained valid data and was understood by the server, but the server is refusing action due to internal validity, status or limit checks or similar failure of request to the 3rd party necessary to fulfil client request.

{  
  "errorCode": "REFUSED_MAX_BALANCE_EXCEEDED",  
  "reason": "string"  
}

500 Internal error

Response codes

Shaype requests clients to use different response code to describe the outcome of the hold/transaction authorisation. In order to keep the current flow in balance-manager working, these are the possible outcomes that would need to be covered by the client:

  • REFUSED_MAX_BALANCE_EXCEEDED,
  • REFUSED_NOT_ENOUGH_FUNDS,

These can occur after checking min or max balance in balance manager, which Shaype wouldn’t be able to provide in this case as the balance is held by the client.

Security

Network level

HTTP-over-TLS (HTTPS) RFC2818 MUST be used for the connection.

List the IP addresses requests will be coming from. Allows the consumer to hide endpoints from the rest of the internet and avoid random scanning and probing as well as targeted attacks.

Application level

Asymmetric Key Signature. This consists of splitting a secret key into a private and public part. The private key will remain with the sender (Shaype) and will be used to sign the body of the request. The signature will be added in the headers of the request. The public key is shared with the receiver, who will use this public keys to run a verifier and check that the signature is valid.

This offers message integrity checks which a simple token doesn’t. Asymmetric keys also offers non-repudiation, it cannot be denied that it came from the sender as only they have the private key.

Shaype will use SHA256WithRSA signature algorithm with 2048 bits as it offers high security and fast signature.

A key id will be provided from Shaype in the request as a header, this can be used to retrieve the public key from our Authentication API.

The public key can be used to verify the signature in the request.

Example response of the authentication-manager endpoint to retrieve public keys:

{  
  "keys": [  
    {  
      "kty": "RSA",  
      "alg": "RS256",  
      "kid": "HCm7EWcCmpCgXFKTksFSNEx7axjn_FuutbnfKyy-69o",  
      "use": "sig",  
      "e": "AQAB",  
      "n": "abcdefghijklmnopqrstuvwxyzx4885U8770YwRvf6NM3qM4kroGUhPbfqyocPs6rjtYO5BbhJQvVWOHb4XrUb8ZK-F4CfAHU4hDXEbgSoZQjAbFVBeprks5wCwKpWHsxF9K00e4HhsuqwlHEOcbZXZG5mmCNJSZNtIqoC1a1KFij7HqjHxwR7uQjlEq0jH_eDue0rdlaI9UxYE9IQxYKr1QLPkd39o4URPLNMkHbNzXy90gMEF43jIVfWBFDMQ--SrbtvHMC2HN10NeHTc2iH_6nGxzfMMDuQKpRavOsVGp_7RcWtDBRbWdkIeY9StncmK2XnA_GzCajxII7sV6-w"  
    }  
  ]  
}

Example code implementation:

//Create transaction json  
Transaction transaction = new Transaction()  
        .transactionId(UUID.randomUUID().toString())  
        .accountId(UUID.randomUUID().toString())  
        .amount(new CurrencyAmount().amount("12345").currency("AED"));

String transactionStr = new ObjectMapper().writeValueAsString(transaction);

//Verify signature  
Signature publicSignature = Signature.getInstance("SHA256withRSA");  
publicSignature.initVerify(keyPair.getPublic());  
publicSignature.update(transactionStr.getBytes(UTF_8));  
byte\[] signatureBytes = Base64.getDecoder().decode(signatureFromRequest);  
boolean verify = publicSignature.verify(signatureBytes);

Replay prevention

Each request comes with unique idempotency key. If the same request is retried it will have the same idempotency key. Idempotency key is part of the message signature. Client is advised to implement idempotent API, that means to ensure the message with the same idempotency key is processed only once.

If client is unable to implement idempotent API, then there is another option. Each request comes with the timestamp. Timestamp is part of the message signature. Timestamp allows client to define acceptable timeframe in which requests must be processed (i.e., 3-5 seconds). If request comes outside of acceptable window, then it’s ignored. For this approach to work client has to ensure server clocks are not skewed.

Notifications

Transaction type CARD_TRANSACTION is used for:

  • new hold
  • hold increase
  • Transactions without holds

Transaction type CARD_TRANSACTION_SETTLED is used after the hold is accepted.

CARD_TRANSACTION_REFUND is used for:

  • hold decrease
  • OCT
  • reversal
  • refund

In Transaction Event we also have fields accountBalances and updatedBalance which we won’t be able to populate.

For authorisation hold the isPending flag is true, for settled transaction is false.

Non-functional Requirements

  • Requests per second (RPS) - 10

Latency:

  • We are given 1.5 seconds by GPS to respond to external balance client authorisation requests
  • 0.4 seconds is the roundtrip between London (our processing centre) and Sydney
  • 0.2 seconds our processing time
  • 0.4 is a buffer (reserved time)
  • 0.5 seconds for Client to reply. Doesn’t include network roundtrip of 0.4 seconds mentioned above

request to client authorisation API

  • connection timeout 0.4 second
  • request timeout 0.5 second (time after establishing the connection)
  • total request timeout is 0.9 seconds including both establishing the connection and receiving the response