# Transaction Patterns - Transfer
Mojaloop Third Party API
# Table Of Contents
- Preface
1.1 Conventions Used in This Document
1.2 Document Version Information
1.3 References - Introduction
2.1 Third Party API Specification - Transfers
3.1 Discovery
3.2 Agreement
3.3 Transfer - Request TransactionRequest Status
- Error Conditions
5.1 Bad Payee Lookup
5.2 Bad Thirdparty Transaction Request
5.3 Downstream FSPIOP-API Failure
5.4 Invalid Signed Challenge
5.5 Thirdparty Transaction Request Timeout - Appendix
6.1 Deriving the Challenge
# 1. Preface
This section contains information about how to use this document.
# 1.1. Conventions Used in This Document
The following conventions are used in this document to identify the specified types of information.
Type of Information | Convention | Example |
---|---|---|
Elements of the API, such as resources | Boldface | /authorization |
Variables | Italics with in angle brackets | {ID} |
Glossary terms | Italics on first occurrence; defined in Glossary | The purpose of the API is to enable interoperable financial transactions between a Payer (a payer of electronic funds in a payment transaction) located in one FSP (an entity that provides a digital financial service to an end user) and a Payee (a recipient of electronic funds in a payment transaction) located in another FSP. |
Library documents | Italics | User information should, in general, not be used by API deployments; the security measures detailed in API Signature and API Encryption should be used instead. |
# 1.2. Document Version Information
Version | Date | Change Description |
---|---|---|
1.0 | 2021-10-03 | Initial Version |
# 1.3. References
The following references are used in this specification:
Reference | Description | Version | Link |
---|---|---|---|
Ref. 1 | Open API for FSP Interoperability | 1.1 | API Definition v1.1 (opens new window) |
# 2. Introduction
This document introduces the transaction patterns supported by the Third Party API relating to the initiation of a Transaction Request from a PISP.
The API design and architectural style of this API are based on Section 3 (opens new window) of Ref 1. above.
# 2.1 Third Party API Specification
The Mojaloop Third Party API Specification includes the following documents:
- Data Models
- Transaction Patterns - Linking
- Transaction Patterns - Transfer
- Third Party Open API Definition - DFSP
- Third Party Open API Definition - PISP
# 3. Transfers
Transfers is broken down into the separate sections:
Discovery: PISP looks up the Payee Party to send funds to
Agreement PISP confirms the Payee Party, and looks up the terms of the transaction. If the User accepts the terms of the transaction, they sign the transaction with the credential established in the Linking API flow
Transfer The Payer DFSP initiates the transaction, and informs the PISP of the transaction result.
# 3.1 Discovery
In this phase, a user enters the identifier of the user they wish to send funds to. The PISP executes a GET /parties/{Type}/{ID}** (or GET /parties/{Type}/{ID}/{SubId}) call and awaits a callback from the Mojaloop switch. Section 6.3 (opens new window) of Ref 1. above describes the /parties resource in detail.
If the GET /parties/{Type}/{ID} request is successful, the PISP will receive a PUT /parties callback from the Mojaloop switch. The PISP then confirms the Payee with their user.
Should the PISP receive a PUT /parties/{Type}/{ID}/error (or PUT /parties/{Type}/{ID}/{SubId}/error) callback, the PISP should display the relevant error to their user.
# 3.2 Agreement
# 3.2.1 Thirdparty Transaction Request
Upon confirming the details of the Payee with their user, the PISP asks the user to enter the amount
of funds they wish to send to the Payee, and whether or not they wish the Payee to receive that amount, or they wish to send that amount (amountType
field).
If the User has linked more than 1 account with the PISP application, the PISP application can ask the user to choose an account they wish to send funds from. Upon confirming the source of funds account, the PISP can determine:
- the
FSPIOP-Destination
as the DFSP who the User's account is linked with - The
payer
field of the POST /thirdpartyRequests/transactions request body. ThepartyIdType
isTHIRD_PARTY_LINK
, thefspId
is the fspId of the DFSP who issued the link, and thepartyIdentifier
is theaccountId
specified in the POST /consents#scopes body.
See Grant Consent for more information.
The PISP then generates a random transactionRequestId
of type UUID (see RFC 4122 UUID (opens new window)).
Upon receiving the POST /thirdpartyRequests/transactions call from the PISP, the DFSP performs some validation such as:
- Determine that the
payer
identifier exists, and is one that was issued by this DFSP to the PISP specified in theFSPIOP-Source
. - Confirms that the
Consent
that is identified by thepayer
identifier exists, and is valid. - Confirm that the User's account is active and holds enough funds to complete the transaction.
- Any other validation that the DFSP wishes to do.
Should this validation succeed, the DFSP will generate a unique transactionId
for the request, and call PUT /thirdpartyRequests/transactions/{ID} with this transactionId
and a transactionRequestState
of RECEIVED
.
This call informs the PISP that the Thirdparty Transaction Request was accepted, and informs them of the final transactionId
to watch for at a later date.
If the above validation fail, the DFSP should send a PUT /thirdpartyRequests/transactions/{ID}/error call to the PISP, with an error message communicating the failure to the PISP. See Error Codes for more information.
# 3.2.2 Thirdparty Authorization Request
The Payer DFSP (that is, the institution sending funds at the request of the PISP) may then issue a quotation request (POST /quotes) to the Payee DFSP (that is, the institution receiving the funds). Upon receiving the PUT /quotes/{ID} callback from the Payee DFSP, the Payer DFSP needs to confirm the details of the transaction with the PISP.
They use the API call POST /thirdpartyRequests/authorizations. The request body is populated with the following fields:
transactionRequestId
- the original id of the POST /thirdpartyRequests/transactions. Used by the PISP to correlate an Authorization Request to a Thirdparty Transaction RequestauthorizationRequestId
- a random UUID generated by the DFSP to identify this Thirdparty Authorization Requestchallenge
- the challenge is aBinaryString
which will be signed by the private key on the User's device. While the challenge could be a random string, we recommend that it be derived from something meaningful to the actors involved in the transaction, that can't be predicted ahead of time by the PISP. See Section 4.1 for an example of how the challenge could be derived.transactionType
thetransactionType
field from the original POST /thirdpartyRequests/transactions request
# 3.2.3 Signed Authorization
Upon receiving the POST /thirdpartyRequests/authorizations request from the Payer DFSP, the PISP presents the terms of the proposed transaction to the user, and asks them if they want to proceed.
The results of the authorization request are returned to the DFSP via the PUT /thirdpartyRequests/authorizations/{ID}, where
the {ID} is the authorizationRequestId
.
If the user rejects the transaction, the following is the payload sent in PUT /thirdpartyRequests/authorizations/{ID}:
{
"responseType": "REJECTED"
}
Should the user accept the transaction, the payload will depend on the credentialType
of the Consent.credential
:
If
FIDO
, the PISP asks the user to complete the FIDO Assertion (opens new window) flow to sign the challenge. ThesignedPayload.fidoSignedPayload
is theFIDOPublicKeyCredentialAssertion
returned from the FIDO Assertion process. See 3.2.3.1 Signing the Challenge FIDOIf
GENERIC
, the private key created during the credential registration process is used to sign the challenge. See 3.2.3.2 Signing the Challenge with a GENERIC Credential
# 3.2.3.1 Signing the Challenge FIDO
For a FIDO
credentialType
, the PISP asks the user to complete the FIDO Assertion (opens new window) flow to sign the challenge. The signedPayload.value
is the PublicKeyCredential
(opens new window) returned from the FIDO Assertion process, where the ArrayBuffer
s are parsed as base64 encoded utf-8 strings. As a PublicKeyCredential
is the response of both the FIDO Attestation and Assertion, we define the following interface: FIDOPublicKeyCredentialAssertion
:
FIDOPublicKeyCredentialAssertion {
"id": "string",
"rawId": "string - base64 encoded utf-8",
"response": {
"authenticatorData": "string - base64 encoded utf-8",
"clientDataJSON": "string - base64 encoded utf-8",
"signature": "string - base64 encoded utf-8",
"userHandle": "string - base64 encoded utf-8",
},
"type": "public-key"
}
The final payload of the PUT /thirdpartyRequests/authorizations/{ID} is then:
{
"responseType": "ACCEPTED",
"signedPayload": {
"signedPayloadType": "FIDO",
"fidoSignedPayload": FIDOPublicKeyCredentialAssertion
}
}
# 3.2.3.2 Signing the Challenge with a GENERIC Credential
For a GENERIC
credential, the PISP will perform the following steps:
- Given the inputs:
challenge
(authorizationRequest.challenge
) as a base64 encoded utf-8 stringprivatekey
(stored by the PISP when creating the credential), as a base64 encoded utf-8 string- SHA256() is a one way hash function, as defined in RFC6234 (opens new window)
- sign(data, key) is a signature function that takes some data and a private key to produce a signature
- Let
challengeHash
be the result of applying the SHA256() function over thechallenge
- Let
signature
be the result of applying the sign() function to thechallengeHash
andprivateKey
The response from the PISP to the DFSP then uses this signature as the signedPayload.genericSignedPayload
field:
The final payload of the PUT /thirdpartyRequests/authorizations/{ID} is then:
{
"responseType": "ACCEPTED",
"signedPayload": {
"signedPayloadType": "GENERIC",
"genericSignedPayload": "utf-8 base64 encoded signature"
}
}
# 3.2.4 Validate Authorization
Note: If the DFSP uses a self-hosted authorization service, this step can be skipped.
The DFSP now needs to check that challenge has been signed correctly, and by the private key that corresponds to the
public key that is attached to the Consent
object.
The DFSP uses the API call POST /thirdpartyRequests/verifications, the body of which is comprised of:
verificationRequestId
- A UUID created by the DFSP to identify this verification request.challenge
- The same challenge that was sent to the PISP in 3.2.2 Thirdparty Authorization RequestconsentId
- TheconsentId
of the Consent resource that contains the credential public key with which to verify this transaction.signedPayloadType
- The type of the SignedPayload, depending on the type of credential registered by the PISPfidoValue
orgenericValue
- The corresponding field from the PUT /thirdpartyRequests/authorizations request body from the PISP. The DFSP must lookup theconsentId
based on thepayer
details of theThirdpartyTransactionRequest
.
# 3.3 Transfer
Upon validating the signed challenge, the DFSP can go ahead and initiate a standard Mojaloop Transaction using the FSPIOP API.
After receiving the PUT /transfers/{ID} call from the switch, the DFSP looks up the ThirdpartyTransactionRequestId for the given transfer, and sends a PATCH /thirdpartyRequests/transactions/{ID} call to the PISP.
Upon receiving this callback, the PISP knows that the transfer has completed successfully, and can inform their user.
# 4. Request TransactionRequest Status
A PISP can issue a GET /thirdpartyRequests/transactions/{ID} to find the status of a transaction request.
PISP issues a GET /thirdpartyRequests/transactions/{ID}
Switch validates request and responds with
202 Accepted
Switch looks up the endpoint for
dfspa
for forwards to DFSP ADFSPA validates the request and responds with
202 Accepted
DFSP looks up the transaction request based on its
transactionRequestId
- If it can't be found, it calls PUT /thirdpartyRequests/transactions/{ID}/error to the Switch, with a relevant error message
DFSP Ensures that the
FSPIOP-Source
header matches that of the originator of the POST /thirdpartyRequests/transactions- If it does not match, it calls PUT /thirdpartyRequests/transactions/{ID}/error to the Switch, with a relevant error message
DFSP calls PUT /thirdpartyRequests/transactions/{ID} with the following request body:
{ transactionRequestState: TransactionRequestState }
Where
transactionId
is the DFSP-generated id of the transaction, andTransactionRequestState
isRECEIVED
,PENDING
,ACCEPTED
,REJECTED
, as defined in 7.5.10 TransactionRequestState (opens new window) of the API DefinitionSwitch validates request and responds with
200 OK
Switch looks up the endpoint for
pispa
for forwards to PISPPISP validates the request and responds with
200 OK
# 5. Error Conditions
After the PISP initiates the Thirdparty Transaction Request with POST /thirdpartyRequests/transactions, the DFSP must send either a PUT /thirdpartyRequests/transactions/{ID}/error or PATCH /thirdpartyRequests/transactions/{ID} callback to inform the PISP of a final status to the Thirdparty Transaction Request.
- PATCH /thirdpartyRequests/transactions/{ID} is used to inform the PISP of the final status of the Thirdparty Transaction Request. This could be either a Thirdparty Transaction Request that was rejected by the user, or a Thirdparty Transaction Request that was approved and resulted in a successful transfer of funds.
- PUT /thirdpartyRequests/transactions/{ID}/error is used to inform the PISP of a failed Thirdparty Transaction Request.
- If a PISP doesn't receive either of the above callbacks within the
expiration
DateTime specified in the POST /thirdpartyRequests/transactions, it can assume the Thirdparty Transaction Request failed, and inform their user accordingly
# 5.1 Unsuccessful Payee Lookup
When the PISP performs a Payee lookup (GET /parties/{Type}/{ID}), they may receive the callback PUT /parties/{Type}/{ID}/error.
See 6.3.4 Parties Error Callbacks (opens new window) of the FSPIOP API Definition for details on how to interpret use this error callback.
In this case, the PISP may wish to display an error message to their user informing them to try a different identifier, or try again at a later stage.
# 5.2 Bad Thirdparty Transaction Request
When the DFSP receives the POST /thirdpartyRequests/transactions request from the PISP, the following processing or validation errors may occur:
- The
payer.partyIdType
orpayer.partyIdentifier
is not valid, or not linked with a valid Consent that the DFSP knows about - The user's account identified by
payer.partyIdentifier
doesn't have enough funds to complete the transaction - The currency specified by
amount.currency
is not a currency that the user's account transacts in payee.partyIdInfo.fspId
is not set - it's an optional property, but payee fspId will be required to properly address quote request- Any other checks or verifications of the transaction request on the DFSP's side fail
In this case, the DFSP must inform the PISP of the failure by sending a PUT /thirdpartyRequests/transactions/{ID}/error callback to the PISP.
The PISP can then inform their user of the failure, and can ask them to restart the Thirdparty Transaction request if desired.
# 5.3 Downstream FSPIOP-API Failure
The DFSP may not want to (or may not be able to) expose details about downstream failures in the FSPIOP API to PISPs.
For example, before issuing a POST /thirdpartyRequests/authorizations to the PISP, if the POST /quotes call with the Payee FSP fails, the DFSP sends a PUT /thirdpartyRequests/transactions/{ID}/error callback to the PISP.
Another example is where the POST /transfers request fails:
# 5.4 Invalid Signed Challenge
After receiving a POST /thirdpartyRequests/authorizations call from the DFSP, the PISP asks the user to sign the challenge
using the credential that was registered during the account linking flow.
The signed challenge is returned to the DFSP with the call PUT /thirdpartyRequest/authorizations/{ID}.
The DFSP either:
- Performs validation of the signed challenge itself
- Queries the Auth-Service with the thirdpartyRequests/verifications resource to check the validity of the signed challenge against the publicKey registered for the Consent.
Should the signed challenge be invalid, the DFSP sends a PUT /thirdpartyRequests/transactions/{ID}/error callback to the PISP.
# Case 1: DFSP self-verifies the signed challenge
# Case 2: DFSP uses the hub-hosted Auth-Service to check the validity of the signed challenge against the registered credential.
# 5.5 Thirdparty Transaction Request Timeout
If a PISP doesn't receive either of the above callbacks within the expiration
DateTime specified in the POST /thirdpartyRequests/transactions, it can assume the Thirdparty Transaction Request failed, and inform their user accordingly.
# 6. Appendix
# 6.1 Deriving the Challenge
- Let
quote
be the value of the response body from the PUT /quotes/{ID}_ call_ - Let the function
CJSON()
be the implementation of a Canonical JSON to string, as specified in RFC-8785 - Canonical JSON format (opens new window) - Let the function
SHA256()
be the implementation of a SHA-256 one way hash function, as specified in RFC-6234 (opens new window) - The DFSP must generate the value
jsonString
from the output ofCJSON(quote)
- The
challenge
is the value ofSHA256(jsonString)