Make payments to different destinations

Lean Data API illustration

Overview

Multi-destination integrations should be used by clients who wish to integrate the Lean platform to direct customer funds to different locations based on their own business logic.

Examples where this is useful:

  1. Sending payments in different currencies to their relevant currency accounts.
  2. Splitting payments between multiple banks to reduce risk.
  3. Taking advantage of better rates between banks.
  4. Top up specific customer stored value accounts.

Add your Destination Account(s)

What is a Destination

A Destination is a bank account that you want your customer to be able transfer money into.

Default Destination

Lean will create a default beneficiary on your behalf, this will be used when a destination account is not specified (see Create Payment Intent section of this guide).

Create a new Destination

Call the Destinations endpoint in the Payments API to create a new Destination resource:

Call

bash

curl -X POST 'https://sandbox.leantech.me/payments/v1/destinations' \
--header 'Content-Type: application/json' \
--header 'lean-app-token: YOUR_APP_TOKEN' \
--data-raw '{
"display_name": "Acme USD Account",
"name": "Acme Inc.",
"bank_identifier": "ENBD_UAE",
"address": "",
"city": "",
"country": "",
"account_number": "",
"iban": ""
}'

Response

bash

{
"id": "266733cc-6f46-4e87-af7e-9bb46b99601d"
"display_name": "Acme USD Account",
"name": "Acme Inc.",
"bank_identifier": "ENBD_UAE",
"address": "",
"city": "",
"country": "",
"account_number": "",
"iban": ""
}

You should store the ID of this Destination resource - as it will be used to determine where you route customer funds to in later stages.

Fetch a list of all destinations

If you would like to retrieve a list of all created Destinations in your application, you can make a GET request to the Destinations endpoint.

bash

GET https://sandbox.leantech.me/payments/v1/destinations

Create a Customer

bash

curl -X POST 'https://sandbox.leantech.me/payments/v1/customer' \
--header 'Content-Type: application/json' \
--header 'lean-app-token: YOUR_APP_TOKEN' \
--data-raw '{
"app_user_id": "IDENTIFIER_FOR_CUSTOMER"
}'

Create a payment source

Creating a Payment Source also creates a Beneficiary. A Beneficiary is the link between a Payment Source and a Payment Destination.

You can use custom business logic to determine whether you want this first beneficiary to be linked to your default account or a specific Destination.

Example

create a payment source

Integrate Lean's LinkSDK - and call the createPaymentSource() method.

javascript

Lean.createPaymentSource({
app_token: "YOUR_APP_TOKEN",
customer_id: "CUSTOMER_ID",
destination_id: "DESTINATION_ID"
})

In the above call, the destination_id is an optional parameter. If a destination is not provided, Lean will fallback to setting up the payment source with your default destination.


Processing Webhooks

Upon creation of a Payment Source - Lean will send two webhooks:

payment_source.created

json

{
"type": "payment_source.created",
"payload": {
"id": "93845ad9-5cd9-4a78-b66a-07b95497d573",
"customer_id": "781fc40c-746e-4627-a441-8eff6888826a",
"status": "AWAITING_BENEFICIARY_COOL_OFF", // NOTE: This is the status of the beneficiary that was added when the payment source was created. It is only still here for reverse compatibility.
"bank_identifier": "ENBD_UAE",
"bank_name": "Emirates NBD"
},
"message": "A payment source was pre-authorized by your customer.",
"timestamp": "2021-02-23T13:23:27.448337Z"
}

payment_source.beneficiary.created

json

{
"type": "payment_source.beneficiary.created",
"payload": {
"id": "776616c4-fb97-4ad6-ba1e-8cfdad436672",
"payment_source_id": "93845ad9-5cd9-4a78-b66a-07b95497d573",
"payment_destination_id": "0ddff041-b30e-41fb-aef0-dc1772e9706f",
"customer_id": "781fc40c-746e-4627-a441-8eff6888826a",
"status": "AWAITING_BENEFICIARY_COOL_OFF"
},
"message": "A beneficiary was added for a payment source.",
"timestamp": "2021-02-23T13:23:27.448337Z"
}

For multi-beneficiary use cases we suggest that you don't use the payment_source.created webhook (although you should still reply with a 200 response to acknowledge the webhook was received. In future iterations we will allow you to disable webhooks you do not listen to.

Processing the beneficiary.created webhook

When a payment_source.beneficiary.created is received, your backend should check to see if the payment_source_id already exists in your database:

If the payment source does not exist in your database

Fetch the newly created Payment Source and save it to your database

bash

curl -X GET 'sandbox.leantech.me/customers/v1/<customer_id>/payment_sources/<payment_source_id>' \
--header 'Content-Type: application/json' \
--header 'lean-app-token: YOUR_APP_TOKEN'

Response

json

{
"id": "d8d7a9b3-c401-49f6-81a4-386d8398aa2c",
"customer_id": "6db94cb7-3a02-4a57-a03a-e2a92ce86406",
"app_id": "2c9280887230f322017231b408cf0007",
"bank_identifier": "ENBD_UAE",
"beneficiaries": [
{
"id": "26891A75-9406-4E06-97BA-F4D6DA664F3C",
"payment_destination_id": "f044c18b-3a51-49a2-bd3f-a585679d8885"
"status": "AWAITING_BENEFICIARY_COOL_OFF",
"beneficiary_cool_off_expiry": "2021-02-13T13:20:57.674299Z"
}
]
"accounts": [
{
"id": "9acde98e-9d19-495d-934c-b2ff966262c8",
"account_id": "aaadbc4e-96a4-420f-ba01-597fa63502ee",
"account_name": "CURRENT ACCOUNT",
"account_number": "1015528734001",
"iban": "AE340260001015528734001",
"balance": 35681.15,
"currency": "AED",
"balance_last_updated": "2021-02-12T13:20:23.83922Z"
}, {
"id": "1457d23a-c1f8-4aee-bc6f-2a253b2e617f",
"account_id": "69f22112-e95d-427e-8bd1-eb58b4db60ba",
"account_name": "CURRENCY PASSPORT SAVINGS AC",
"account_number": "0315528734002",
"iban": "AE790260000315528734002",
"balance": 80871.00,
"currency": "USD",
"balance_last_updated": "2021-02-12T13:20:57.370659Z"
}
]
}

If the payment source exists in your database

Append the new beneficiary from the webhook's payload to the beneficiaries array within the payment_source

Beneficiary updates

When the status of a beneficiary changes (for example from AWAITING_BENEFICIARY_COOL_OFFACTIVE a new webhook will be sent:

json

{
"type": "payment_source.beneficiary.updated",
"payload": {
"id": "26891A75-9406-4E06-97BA-F4D6DA664F3C", //this is the beneficiary_id
"payment_source_id": "93845ad9-5cd9-4a78-b66a-07b95497d573",
"payment_destination_id": "0ddff041-b30e-41fb-aef0-dc1772e9706f",
"customer_id": "781fc40c-746e-4627-a441-8eff6888826a",
"status": "ACTIVE"
},
"message": "A beneficiary was updated for a payment source.",
"timestamp": "2021-02-23T13:23:27.448337Z"
}

You can patch the changes and notify your customer of the change in status from this webhook.


Updating Payment Sources

Notice in the Payment Source added above there is a USD and an AED account within the payment source.

By implementing a UI which allows a user to specify which account to initiate a payment from - you can route these accounts to the correct location.

update a payment source

To do this - upon clicking the 'Update Account' link in the above UI you should initialise the following call:

javascript

Lean.updatePaymentSource({
app_token: "YOUR_APP_TOKEN",
payment_source_id: "PAYMENT_SOURCE_ID",
payment_destination_id: "PAYMENT_DESTINATION_ID"
customer_id: "CUSTOMER_ID"
})

This will trigger the Link SDK which will ask for the end user's permission to add your USD payment_destination as a beneficiary to their bank account and handling any challenges asked by the bank. Upon successfully receiving authorization and therefore succesfully creating a beneficiary, your backend will receive the following payment_source.beneficiary.created webhook:

json

{
"type": "payment_source.beneficiary.created",
"payload": {
"id": "a7868957-ae07-452b-bcaf-f604cff6aafc",
"payment_source_id": "93845ad9-5cd9-4a78-b66a-07b95497d573"
"payment_destination_id": "6783014a-301a-4b8a-b279-ace56ec359f9",
"customer_id": "781fc40c-746e-4627-a441-8eff6888826a",
"status": "AWAITING_BENEFICIARY_COOL_OFF",
},
"message": "A beneficiary was added for a payment source.",
"timestamp": "2021-02-23T13:23:27.448337Z"
}

The status field will reflect whether or not the beneficiary requires a beneficiary cool off, if the beneficiary does require the cool off period, Lean will send you a payment_source.beneficiary.updated webhook with the updated status as seen before. At this point, you will be able to make payments from this payment source.

In this instance - the USD account would be added to the list of beneficiaries. When the customer completes the linking process - another payment_source.beneficiary.created webhook will be sent.

Your final Payment Source object should now look like this:

json

{
"id": "d8d7a9b3-c401-49f6-81a4-386d8398aa2c",
"customer_id": "6db94cb7-3a02-4a57-a03a-e2a92ce86406",
"app_id": "2c9280887230f322017231b408cf0007",
"bank_identifier": "ENBD_UAE",
"beneficiaries": [
{
"id": "26891A75-9406-4E06-97BA-F4D6DA664F3C",
"payment_destination_id": "f044c18b-3a51-49a2-bd3f-a585679d8885"
"status": "ACTIVE",
"beneficiary_cool_off_expiry": "2021-02-13T13:20:57.674299Z"
}, {
"id": "f6da1dd8-075c-4a3a-b84c-d846f68cd619",
"payment_destination_id": "3589d342-d340-49d6-b4db-506491e94acc"
"status": "AWAITING_BENEFICIARY_COOL_OFF",
"beneficiary_cool_off_expiry": "2021-02-14T13:20:57.674299Z"
}
]
"accounts": [
{
"id": "9acde98e-9d19-495d-934c-b2ff966262c8",
"account_id": "aaadbc4e-96a4-420f-ba01-597fa63502ee",
"account_name": "CURRENT ACCOUNT",
"account_number": "1015528734001",
"iban": "AE340260001015528734001",
"balance": 35681.15,
"currency": "AED",
"balance_last_updated": "2021-02-12T13:20:23.83922Z"
}, {
"id": "1457d23a-c1f8-4aee-bc6f-2a253b2e617f",
"account_id": "69f22112-e95d-427e-8bd1-eb58b4db60ba",
"account_name": "CURRENCY PASSPORT SAVINGS AC",
"account_number": "0315528734002",
"iban": "AE790260000315528734002",
"balance": 80871.00,
"currency": "USD",
"balance_last_updated": "2021-02-12T13:20:57.370659Z"
}
]
}

Create a Payment Intent

Now that your user is able to transact with multiple accounts from a single payment source - the next step is to create a Payment Intent.

create a payment intent

When your user clicks to initiate a transfer a call to your backend should be initiated expecting a payment_intent_id as the response.

javascript

onClick={() => callMyApi(amount, currency, customer_id, payment_destination_id)}

Your backend creates the Payment Intent

bash

curl -X POST 'https://sandbox.leantech.me/payments/v1/intents' \
--header 'Content-Type: application/json' \
--header 'lean-app-token: YOUR_APP_TOKEN' \
--data-raw '{
"amount": 1000,
"currency": "USD",
"payment_destination_id": "USD_DESTINATION_ID",
"customer_id": "CUSTOMER_ID"
}'

and returns the payment_intent_id received in the response back to your front-end this response should immediately trigger the next call detailed below.


Initiate Payment

Once your payment_intent_id has been received you can now initialise the LinkSDK again. To ensure the correct account is used, you should also pass in the id parameter for the account selected - this prevents the user from changing the source account inside the LinkSDK and takes them straight to confirming the payment.

javascript

lean.pay({
app_token: "YOUR_APP_TOKEN",
payment_intent_id: "RETRIEVED_APP_TOKEN"
account_id: "SELECTED_PAYMENT_SOURCE.ACCOUNTS[SELECTED_ACCOUNT_INDEX].ID"
})

On a successful payment, a payment.created webhook with an ACCEPTED_BY_BANK status will be sent to your webhook URL:

json

{
"payload": {
"id": "81f686b5-0cf3-4ee1-9250-9f8dae030a1d",
"customer_id": "e52d8b7e-7c4d-40af-b851-90ab5c061028",
"intent_id": "88f73a6d-f017-4fc6-85bf-2cc2804c2a4a",
"status": "ACCEPTED_BY_BANK",
"amount": 10.12,
"currency": "AED"
},
"type": "payment.created",
"message": "A payment object has been created.",
"timestamp": "2021-03-24T12:50:47.521324Z"
}

If the bank rejects the payment a payment.created webhook with a FAILED status will be sent to your webhook URL:

json

{
"payload": {
"id": "1d408310-273b-4e54-931b-8b423eea5856",
"customer_id": "e52d8b7e-7c4d-40af-b851-90ab5c061028",
"intent_id": "977f1c9a-508f-4218-80c2-1f32f37c62c1",
"status": "FAILED",
"amount": 11.00,
"currency": "AED"
},
"type": "payment.created",
"message": "A payment object has been created.",
"timestamp": "2021-03-18T13:43:15.837276Z"
}