Enable Peer to Peer (P2P) Payments

Lean Data API illustration

Overview

The flow for Peer to Peer payments

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.

  1. Create a Customer
  2. Add their bank account details so that they can receive money (Create a Destination)
  3. Create their first Payment Source (Customer 1 → Customer 2)
  4. Update Payment Source to pay additional users (Customer 1 → Customer 3)
  5. Create a Payment Intent (Customer 1 → Customer 3)
  6. Initiate Payment

Create a Customer

bash

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

During the onboarding of your users you should create a new Customer object.


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:

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": "Fulan AlFulani",
"name": "Fulan AlFulani",
"bank_identifier": "ENBD_UAE",
"address": "123 Fake St.",
"city": "Dubai",
"country": "AER",
"account_number": "008223921",
"iban": "",
"customer_id": "CUSTOMER_ID"
}'

Response

bash

{
id: "266733cc-6f46-4e87-af7e-9bb46b99601d"
display_name: "Fulan AlFulani",
name: "Fulan AlFulani",
bank_identifier: "ENBD_UAE",
address: "123 Fake St.",
city: "Dubai",
country: "AER",
account_number: "008223921",
iban: "",
customer_id: "CUSTOMER_ID"
}

You should store the ID of this Destination resource in your database against the customer_id generated previously - This Destination ID is a store for when another user on your platform wants to transfer money to the user who has signed up.


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.

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

create payment sourcecreate payment sourcecreate payment source

Front-end

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

bash

Lean.createPaymentSource({
app_token: "YOUR_APP_TOKEN",
customer_id: "CUSTOMER_ID",
payment_destination_id: "SEBASTIANS_DESTINATION_ID"
})

Handle Webhooks

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

payment_source.created

bash

{
"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

bash

{
"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 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

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 your own 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.


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 update a 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.

update payment sourceupdate payment sourceupdate payment source

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:

javascript

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

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 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.

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 payment intentcreate payment intent

When your user clicks Pay Leah 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": "AED",
"payment_destination_id": "LEAHS_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 an initiation has been completed, you will receive a payment.created webhook 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"
}

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.