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.

  1. Create a Customer
  2. Add their bank account details so that they can receive money (Create a Destination per customer ID)
  3. Create their first Payment Source (Customer 1 → Customer 2)
  4. Update Payment Source by creating a new beneficiary to pay additional users (Customer 1 → Customer 3)
  5. Create a Payment Intent (Customer 1 → Customer 3)
  6. 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": "ARE",
    "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: "ARE",
  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_OFFACTIVE 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:

  1. 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": "ARE",
        "account_number": "SECOND_BENEFICIARY_ACCOUNT_NB",
        "iban": "SECOND_BENEFICIARY_IBAN",
        "customer_id": "CUSTOMER_ID" //this links the created destination to this specific customer
      }'
    
  2. 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.