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:
A Destination is a bank account that you want your customer to be able transfer money into.
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).
Call the Destinations endpoint in the Payments API to create a new Destination resource:
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": ""}'
{"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.
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.
GET https://sandbox.leantech.me/payments/v1/destinations
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"}'
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
Integrate Lean's LinkSDK - and call the createPaymentSource()
method.
Lean.createPaymentSource({app_token: "YOUR_APP_TOKEN",customer_id: "CUSTOMER_ID",payment_destination_id: "PAYMENT_DESTINATION_ID"})
In the above call, the payment_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.
Upon creation of a Payment Source - Lean will send two webhooks:
payment_source.created
{"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
{"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.
When a payment_source.beneficiary.created is received, your backend should check to see if the payment_source_id already exists in your database:
Fetch the newly created Payment Source and save it to your database
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
{"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"}]}
Append the new beneficiary from the webhook's payload
to the beneficiaries array within the payment_source
When the status of a beneficiary changes (for example from AWAITING_BENEFICIARY_COOL_OFF
→ ACTIVE
a new webhook will be sent:
{"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.
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.
To do this - upon clicking the 'Update Account' link in the above UI you should initialise the following call:
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:
{"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:
{"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"}]}
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.
When your user clicks to initiate a transfer a call to your backend should be initiated expecting a payment_intent_id
as the response.
onClick={() => callMyApi(amount, currency, customer_id, payment_destination_id)}
Your backend creates the Payment Intent
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.
Once an initiation has been completed, you will receive a payment.created webhook to your Webhook URL:
{"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"}
Payment Initiations can have three final statuses on the Lean platform.
REJECTED_BY_BANK / FAILED: The bank rejected the payment initiation. This could be for a number of reasons such as insufficient balance to complete the transaction, or a connectivity failure.
PENDING_WITH_BANK: The bank accepted the initiation, but didn’t provide a transaction reference number. Pending payments may be processed by the bank at a future date and require manual reconciliation.
ACCEPTED_BY_BANK: The bank accepted the initiation and provided a transaction reference number. Transactions that are ACCEPTED_BY_BANK should not be treated as completed payments until the funds have been reconciled in your account. There are a rare number of circumstances when a delay may occur between a bank accepting a payment and the funds reaching your account and/or circumstances which may cause the payment to fail. For example, a bank may request additional KYC checks or there may be insufficient funds available for the transaction after the bank transfer fees are applied. We recommend that funds are reconciled in your account before making them available to the customer.