Skip to content

Adding a New Lender

This guide walks through adding support for a new lender's payment/NACH file.

Step 1: Create YAML Mapping

Create config/lender_mappings/{lender_key}.yml:

yaml
lender_key: newlender
version: 1

columns:
  "Loan Number":              # CSV column header (exact, case-insensitive)
    target: loan_id
    type: text
  "Amount":
    target: amount
    type: numeric
  "Payment Date":
    target: date
    type: date
  "Status":
    target: status
    type: text
  "UTR":
    target: payment_id
    type: text
  "Remarks":
    target: note
    type: text

date_formats:
  date: "%d-%m-%Y"           # Match the actual CSV date format

filters:
  - column: amount
    op: ">"
    value: 0
  - column: status
    op: "in"
    value: ["success", "cleared", "paid"]

defaults:
  lender: newlender

Step 2: Register S3 Config

Add to tasks/flows/s3_processing.py in S3_LENDER_CONFIGS:

python
"newlender": {
    "flow_name": "S3 NewLender Processing Flow",
    "s3_prefix": "newlender/",
    "s3_filename": "NEWLENDER_REPORT.csv",
    "table_name": "RAW_NEWLENDER_REPORT",
},

Step 3: Add dbt Source

Add to dbt/models/raw/sources.yml:

yaml
- name: RAW_NEWLENDER_REPORT

Step 4: Create BASE Model

Create dbt/models/base/BASE_NEWLENDER_REPORT.sql:

sql
{{ config(materialized='incremental') }}

select
    source_file,
    lender,
    trim(loan_id::text) as loan_id,
    amount,
    date as transaction_date,
    trim(status::text) as status,
    charge,
    trim(note::text) as note,
    trim(payment_id::text) as payment_id,
    trim(sub_reference_id::text) as sub_reference_id

from {{ source('raw', 'RAW_NEWLENDER_REPORT') }}

{% if is_incremental() %}
    where source_file not in (select distinct source_file from {{ this }})
{% endif %}

Step 5: Add CTE to CORE_TRANSACTION_DATA

Add a new CTE in dbt/models/core/CORE_TRANSACTION_DATA.sql:

sql
, newlender as (
    select
        null::varchar(36) as uuid,
        md5(
            'newlender' || '|'
            || coalesce(trim(loan_id), '') || '|'
            || coalesce(to_char(transaction_date, 'YYYY-MM-DD'), '') || '|'
            || coalesce(to_char(round(amount::numeric, 2), 'FM999999999999990.00'), '')
        ) as source_key,
        'newlender' as source_lender,
        'nach_csv' as source_type,
        loan_id as loan_external_id,
        amount as transaction_amount,
        transaction_date,
        status as raw_status,
        true as is_success,
        null::text as payment_gateway,
        null::text as external_id,
        'INR' as currency,
        source_file,
        payment_id as utr_payment_id,
        note,
        null::text as recon_source        -- set to 'newlender_nach' if recon needed
    from {{ ref('BASE_NEWLENDER_REPORT') }}
)

Add to the combined CTE:

sql
union all
select * from newlender

Step 6: Create Loan Product in Fineract

If loans from this lender will be created in Fineract, add a loan product and update lender_loan_products.py:

python
LENDER_LOAN_PRODUCT_MAP = {
    ...
    "NEWLENDER": 10,  # Fineract product ID
}

Step 7: Test

  1. Upload a sample CSV via the Ops UI
  2. Check the File Processing Log for success
  3. Run dbt run --select BASE_NEWLENDER_REPORT CORE_TRANSACTION_DATA
  4. Verify records appear in CORE_TRANSACTION_DATA

Checklist

  • [ ] YAML mapping file created
  • [ ] S3 config registered
  • [ ] dbt source added
  • [ ] BASE model created
  • [ ] CORE_TRANSACTION_DATA CTE added
  • [ ] Loan product created (if applicable)
  • [ ] Test file uploaded and verified

Turno Fineract LMS Documentation