Enable Peer to Peer (P2P) Payments
Overview
This guide will follow the journey for a single customer. From signing up and adding their own Destination, to creating a beneficiary and transferring money to a second and third user within your platform.
- Create a Customer
- Add their bank account details so that they can receive money (Create a Destination per customer ID)
- Create their first Payment Source (Customer 1 → Customer 2)
- Update Payment Source by creating a new beneficiary to pay additional users (Customer 1 → Customer 3)
- Create a Payment Intent (Customer 1 → Customer 3)
- Initiate Payment
Create a Destination
What is a Destination
A Destination is a bank account that you want other users to be able to transfer funds to.
Default Destination
Lean will create a default destination on your behalf - for P2P use cases this destination should not be used. The default destination will be your company's corporate account.
Creating a Customer Destination
While onboarding a customer, you should call the destinations API to create a new Destination resource for the customer to enable payments to their nominated account:
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": "Fulan AlFulani",
"name": "Fulan AlFulani",
"bank_identifier": "LEANMB1",
"address": "123 Fake St.",
"city": "Dubai",
"country": "AER",
"account_number": "008223921",
"iban": "",
"customer_id": "CUSTOMER_ID" //this links the created destination to this specific customer
}'
{
id: "266733cc-6f46-4e87-af7e-9bb46b99601d",
display_name: "Fulan AlFulani",
name: "Fulan AlFulani",
bank_identifier: "LEANMB1",
address: "123 Fake St.",
city: "Dubai",
country: "AER",
account_number: "008223921",
iban: "",
customer_id: "CUSTOMER_ID"
}
Create a Payment Source
Connecting a customer using the LinkSDK's connect
function, creates a Payment Source and adds a Beneficiary at once. A Beneficiary is the link between a Payment Source and a Payment Destination.
To enable P2P - you will be adding multiple beneficiaries to a single Payment Source, which maps out all of the payment channels that a customer has.
Example:



Front-end
Integrate Lean's LinkSDK - and call the .connect()
method.
Lean.connect({
app_token: "YOUR_APP_TOKEN",
customer_id: "CUSTOMER_ID",
permissions: ["payments"],
payment_destination_id: "SEBASTIANS_DESTINATION_ID", //make sure to include the beneficiary's destination ID or else the default destination (Company's CMA account) will be linked
sandbox: true
})
Handle Webhooks
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": "LEANMB1",
"bank_name": "Lean Mock Bank"
},
"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",
"payment_source_bank_identifier": "LEANMB1",
"status": "AWAITING_BENEFICIARY_COOL_OFF"
},
"message": "A beneficiary was added for a payment source.",
"timestamp": "2021-02-23T13:23:27.448337Z"
}
For P2P 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:
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'
{
"id": "d8d7a9b3-c401-49f6-81a4-386d8398aa2c",
"customer_id": "6db94cb7-3a02-4a57-a03a-e2a92ce86406",
"app_id": "2c9280887230f322017231b408cf0007",
"bank_identifier": "LEANMB1",
"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 your own beneficiaries array within the payment_source
.
Beneficiary updates
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.
Add another beneficiary
At this point we have created a customer and enabled payments between two users on your platform.
The next step is to:
-
Create a new payment destination for this customer reflecting the new beneficiary details
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": "SECOND_BENEFICIARY_DISPLAY_NAME", "name": "SECOND_BENEFICIARY_NAME", "bank_identifier": "SECOND_BENEFICIARY_BANK_IDENTIFIER", "address": "123 Fake St.", "city": "Dubai", "country": "AER", "account_number": "SECOND_BENEFICIARY_ACCOUNT_NB", "iban": "SECOND_BENEFICIARY_IBAN", "customer_id": "CUSTOMER_ID" //this links the created destination to this specific customer }'
-
Then create a new beneficiary using the LinkSDK's
createBeneficiary
function and link it to the existing Payment Source to allow our customer to transfer funds to two friends - this is of course a repeatable process to n number of contacts that a customer has on your application.



Once your customer has selected that they'd like to make payments to Leah above from their existing ENBD Payment Source - you will again use the LinkSDK to manage this connection:
Lean.createBeneficiary({
app_token: "2c9a80897169b1dd01716a0339e30003",
customer_id: "d57a03bc-ef9d-460b-8fa6-3b17e425326c",
payment_source_id: "88d7aefe-aae6-4eb3-a314-6e60e73fda35",
payment_destination_id: "SECOND_BENEFICIARY_PAYMENT_DESTINATION_ID",
sandbox: "true",
});
This will trigger the Link SDK which will again ask for the end user's permission to add their new contact as a beneficiary to their bank account and handling any challenges asked by the bank.
Upon successfully receiving authorization and therefore successfully 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",
"payment_source_bank_identifier": "LEANMB1",
"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.
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": "LEANMB1",
"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.


When your user clicks Pay Leah a call to your backend should be initiated expecting a payment_intent_id
as the response.
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": "AED",
"payment_destination_id": "LEAHS_DESTINATION_ID",
"customer_id": "CUSTOMER_ID",
"description": "YOUR_TRANSACTION_REFERENCE"
}'
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 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.
Updated about 2 months ago