Skip to content

feat(connector): [Truelayer] Implementation - #2047

Open
THE-CORE-BUG wants to merge 2 commits into
mainfrom
Truelayer-payouts
Open

feat(connector): [Truelayer] Implementation#2047
THE-CORE-BUG wants to merge 2 commits into
mainfrom
Truelayer-payouts

Conversation

@THE-CORE-BUG

@THE-CORE-BUG THE-CORE-BUG commented Aug 3, 2026

Copy link
Copy Markdown

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

Integrating Payout Connector

Additional Changes

  • This PR modifies the API contract
  • This PR modifies the database schema
  • This PR modifies application configuration/environment variables

Motivation and Context

How did you test it?

MCA:

curl --location 'localhost:8080/account/merchant_1785763808/connectors' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_QX29uQkk8sbM3t1flDICvnCT1fSCfmTAJJDWa1okXRUDv4Ou6MONpF1ln6ntdv2N' \
--data '{
    "connector_type": "payout_processor",
    "connector_name": "truelayer",
    "connector_label":"test123",
    "connector_account_details": {
        "auth_type": "BodyKey",
        "api_key": "",
        "key1": ""
    },
    "test_mode": true,
    "payment_methods_enabled": [
        {
            "payment_method": "bank_transfer",
            "payment_method_types": [
                {
                    "payment_method_type": "open_banking",
                    "payment_experience": null,
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        }
    ],
    "metadata": {
        "kid": "",
        "merchant_account_id": "",
        "account_holder_name": "",
        "private_key": ""
    }
}'

Request:

{
    "connector_type": "payout_processor",
    "connector_name": "truelayer",
    "connector_label":"test123",
    "connector_account_details": {
        "auth_type": "BodyKey",
        "api_key": "",
        "key1": ""
    },
    "test_mode": true,
    "payment_methods_enabled": [
        {
            "payment_method": "bank_transfer",
            "payment_method_types": [
                {
                    "payment_method_type": "open_banking",
                    "payment_experience": null,
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        }
    ],
    "metadata": {
        "kid": "",
        "merchant_account_id": "",
        "account_holder_name": "",
        "private_key": ""
    }
}

Response:


{
    "payout_id": "payout_cKr0bTnTXU60KbtN63XD",
    "merchant_id": "merchant_1771820627",
    "merchant_order_reference_id": null,
    "amount": 100,
    "currency": "GBP",
    "connector": "truelayer",
    "payout_type": "bank_redirect",
    "payout_method_data": {
        "bank_redirect": {
            "account_holder_name": "test",
            "iban": "GB32CLRB04066800012315"
        }
    },
    "billing": {
        "address": {
            "city": "Los Angeles",
            "country": "NL",
            "line1": "123 Main St",
            "line2": "Apt 4B",
            "line3": null,
            "zip": "90001",
            "state": "CA",
            "first_name": "John",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": null,
        "email": null
    },
    "auto_fulfill": true,
    "customer_id": "cus_DjLrHOdflVXD4cF8Oby2",
    "customer": {
        "id": "cus_DjLrHOdflVXD4cF8Oby2",
        "name": "John Doe",
        "email": "payout_cer1@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "customer_document_details": null
    },
    "client_secret": "payout_payout_cKr0bTnTXU60KbtN63XD_secret_qqSMZ2w5uqnF6H0dbNhH",
    "return_url": null,
    "business_country": null,
    "business_label": null,
    "description": "Its my first payout request",
    "entity_type": "Individual",
    "recurring": false,
    "metadata": {},
    "merchant_connector_id": "mca_hZgHKnlTZMk951R7sKiy",
    "status": "initiated",
    "error_message": null,
    "error_code": null,
    "profile_id": "pro_BwuYDrAWLSYfsgBqjoy3",
    "created": "2026-02-23T05:58:23.564Z",
    "connector_transaction_id": "37fbea44-8723-49b1-acbc-3ad7c49a4986",
    "priority": null,
    "payout_link": null,
    "email": "payout_cer1@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "unified_code": null,
    "unified_message": null,
    "payout_method_id": null
}

Payouts-Create:


curl --location 'localhost:8080/payouts/create' \
--header 'Content-Type: application/json' \
--header 'api-key: ' \
--data-raw '{
    "amount": 1000,
    "currency": "GBP",
    "customer_id": "12358",
    "name": "Test Hyperswitch",
    "email": "payout1@example.com",
    "description": "Truelayer legacy BT test",
    "payout_type": "bank",
    "connector": ["truelayer"],
    "payout_method_data": {
        "bank_transfer": {
            "payout_method_type": "open_banking",
            "account_holder_name": "Test Hyperswitch",
            "iban": "GB33BUKB20201555555555"
        }
    },
    "billing": {
        "address": {
            "line1": "Main street 1", "city": "London", "state": "London",
            "zip": "EC1A 1BB", "country": "GB",
            "first_name": "Test", "last_name": "Hyperswitch"
        },
        "phone": { "number": "709876543", "country_code": "+44" },
        "email": "abc@gmail.com"
    },
    "customer": { "id": "12358", "name": "Test Hyperswitch", "email": "payout1@example.com" },
    "entity_type": "Individual",
    "confirm": true,
    "auto_fulfill": false
}'

Payouts-FulFill:

curl --location 'localhost:8080/payouts/payout_chhQzEv5oV267XeKrGuC/fulfill' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_QVbOlpchBqypxy08pvwXLc2fBCsTLVheaMSG6MlUZNpuoJJDNZTqeUEo2dsRPQKc' \
--data '{ "payout_id": "payout_chhQzEv5oV267XeKrGuC" }'

Payouts-Sync:

curl --location 'localhost:8080/payouts/payout_chhQzEv5oV267XeKrGuC?force_sync=true' \
--header 'api-key: dev_QVbOlpchBqypxy08pvwXLc2fBCsTLVheaMSG6MlUZNpuoJJDNZTqeUEo2dsRPQKc'

Webhooks:


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible

@THE-CORE-BUG
THE-CORE-BUG requested review from a team as code owners August 3, 2026 13:46
@THE-CORE-BUG THE-CORE-BUG changed the title Integrating Truelayer payouts feat(connector): [Truelayer] Implementation Aug 7, 2026
Comment on lines +60 to +63
_ => Err(IntegrationError::FailedToObtainAuthType {
context: Default::default(),
}
.into()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add addtional context

Comment on lines +81 to +94
IntegrationError::MissingRequiredField {
field_name: "merchant_account_id",
context: Default::default(),
},
)?,
private_key: auth
.private_key
.ok_or(IntegrationError::MissingRequiredField {
field_name: "private_key",
context: Default::default(),
})?,
kid: auth.kid.ok_or(IntegrationError::MissingRequiredField {
field_name: "kid",
context: Default::default(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

additional context

Comment on lines +476 to +485
Err(ErrorResponse {
code: reason.clone(),
message: reason.clone(),
reason: Some(reason),
attempt_status: None,
connector_transaction_id: Some(connector_payout_id),
status_code: http_code,
network_advice_code: None,
network_decline_code: None,
network_error_message: None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a TrueLayer payout in failed is never actually delivered

TruelayerPayoutStatus::Pending
| TruelayerPayoutStatus::AuthorizationRequired
| TruelayerPayoutStatus::Authorizing => Self::Pending,
TruelayerPayoutStatus::Authorized | TruelayerPayoutStatus::Executed => Self::Success,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ince confirm that we should mark authorized as success

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirming — this mirrors the existing hyperswitch implementation exactly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants