Skip to content

feat(core): Add TLID through Adyen payment flows and API response#12278

Open
ayush22667 wants to merge 21 commits into
mainfrom
feat/add-tlid-support-for-adyen
Open

feat(core): Add TLID through Adyen payment flows and API response#12278
ayush22667 wants to merge 21 commits into
mainfrom
feat/add-tlid-support-for-adyen

Conversation

@ayush22667
Copy link
Copy Markdown
Contributor

@ayush22667 ayush22667 commented May 13, 2026

Type of Change

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

Description

Builds on top of PR #12260.Use actual TLID values through the payment flows for Adyen.

Changes

Adyen connector

  • CIT response: extract additionalData.transactionLinkId from Adyen and store on payment_attempt and payment_methods
  • MIT request: read TLID from MandateReferenceId inside get_additional_data() and send as additionalData.transactionLinkId to Adyen

MIT routing

  • NTWithNTIRef and new CardWithNTIRef structs carry transaction_link_id alongside NTID
  • ActionType::CardWithNetworkTransactionId uses CardWithNTIRef struct
  • IsNtWithNtiFlow::NtWithNtiSupported carries CardWithNTIRef
  • is_network_token_with_network_transaction_id_flow: TLID added to match tuple — same pattern as network_transaction_id

API surface

  • PaymentsResponse exposes network_transaction_link_id
  • PaymentMethodTokenizationDetails exposes network_transaction_link_id
  • ConfirmUpdate passes network_transaction_link_id from payment attempt

Additional Changes

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

Motivation and Context

Mastercard mandates TLID for all recurring/MIT transactions from Oct 23, 2026. Unlike Trace ID, TLID has no static fallback — missing it risks issuer declines and scheme fines.

Tracking: https://github.com/juspay/hyperswitch-cloud/issues/15984

1. 2. CIT - Create Payment with setup_future_usage (saves card + TLID)

Request

curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'api-key: [REDACTED]
--header 'Accept: */*' \
--data '{
    "amount": 10000,
    "currency": "USD",
    "confirm": true,
    "customer_id": "cus_jJRKhCItHJsWIlGDTGoi",
    "description": "TLID test - CIT with setup_future_usage",
    "setup_future_usage": "off_session",
    "payment_method": "card",
    "payment_method_type": "credit",
    "payment_method_data": {
        "card": {
            "card_number": "5100290029002909",
            "card_exp_month": "03",
            "card_exp_year": "30",
            "card_cvc": "737",
            "card_holder_name": "Test User"
        }
    },
    "customer_acceptance": {
        "acceptance_type": "online",
        "accepted_at": "2026-06-01T10:00:00Z",
        "online": {
            "ip_address": "192.0.2.1",
            "user_agent": "Mozilla/5.0"
        }
    },
    "browser_info": {
        "language": "en-US",
        "time_zone": -330,
        "ip_address": "127.0.0.1",
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "color_depth": 32,
        "java_enabled": true,
        "screen_width": 1728,
        "screen_height": 1117,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "java_script_enabled": true
    },
    "return_url": "https://example.com/return"
}'

Response

{
    "payment_id": "pay_fPwcoyNJXjvmxGxbj1mp",
    "merchant_id": "merchant_1777896241",
    "status": "succeeded",
    "amount": 10000,
    "net_amount": 10000,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 10000,
    "processor_merchant_id": "merchant_1777896241",
    "initiator": null,
    "sdk_authorization": "[REDACTED]",
    "connector": "adyen",
    "state_metadata": null,
    "client_secret": "[REDACTED]",
    "created": "2026-05-13T13:06:17.623Z",
    "modified_at": "2026-05-13T13:06:19.303Z",
    "currency": "USD",
    "customer_id": "cus_jJRKhCItHJsWIlGDTGoi",
    "customer": {
        "id": "cus_jJRKhCItHJsWIlGDTGoi",
        "name": "John Doe",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "customer_document_details": null
    },
    "description": "TLID test - CIT with setup_future_usage",
    "refunds": null,
    "disputes": null,
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": "off_session",
    "off_session": null,
    "capture_on": null,
    "capture_method": null,
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "2909",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "510029",
            "card_extended_bin": null,
            "card_exp_month": "03",
            "card_exp_year": "30",
            "card_holder_name": "Test User",
            "payment_checks": null,
            "authentication_data": null,
            "auth_code": "097737"
        },
        "billing": null
    },
    "payment_token": "[REDACTED]",
    "shipping": null,
    "billing": null,
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://example.com/return",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": null,
    "statement_descriptor_suffix": null,
    "next_action": null,
    "cancellation_reason": null,
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": null,
    "error_details": null,
    "payment_experience": null,
    "payment_method_type": "credit",
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "manual_retry_allowed": null,
    "connector_transaction_id": "V38GVQ6DBM6NXJV5",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "connector_response_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "pix_additional_details": null,
        "boleto_additional_details": null,
        "pix_automatico_additional_details": null,
        "finix_additional_details": null
    },
    "reference_id": "pay_fPwcoyNJXjvmxGxbj1mp_1",
    "payment_link": null,
    "profile_id": "pro_MynyLskWiSBYHJHRqjOK",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_ra4dOXyEJDb9HtB1NzGP",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2026-05-13T13:21:17.622Z",
    "fingerprint": null,
    "browser_info": {
        "language": "en-US",
        "time_zone": -330,
        "ip_address": "127.0.0.1",
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "color_depth": 32,
        "java_enabled": true,
        "screen_width": 1728,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "screen_height": 1117,
        "java_script_enabled": true
    },
    "payment_channel": null,
    "payment_method_id": "pm_GkBwjMU64evtaoXQvP6U",
    "network_transaction_id": "HAPTDUS7D0513",
    "network_transaction_link_id": "LrugTut99Cgunxf3HJOT2H",
    "payment_method_status": "active",
    "updated": "2026-05-13T13:06:19.303Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "extended_authorization_last_applied_at": null,
    "request_extended_authorization": "[REDACTED]",
    "capture_before": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": "FPCGJP3M6HJ6TNV5",
    "card_discovery": "manual",
    "force_3ds_challenge": false,
    "force_3ds_challenge_trigger": false,
    "issuer_error_code": null,
    "issuer_error_message": null,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null,
    "enable_partial_authorization": "[REDACTED]",
    "enable_overcapture": null,
    "is_overcapture_enabled": null,
    "network_details": null,
    "is_stored_credential": null,
    "mit_category": null,
    "billing_descriptor": null,
    "tokenization": null,
    "partner_merchant_identifier_details": null,
    "payment_method_tokenization_details": {
        "payment_method_id": "pm_GkBwjMU64evtaoXQvP6U",
        "payment_method_status": "active",
        "psp_tokenization": false,
        "network_tokenization": false,
        "network_transaction_id": "HAPTDUS7D0513",
        "network_transaction_link_id": "LrugTut99Cgunxf3HJOT2H",
        "is_eligible_for_mit_payment": true
    },
    "installment_options": null,
    "installment_data": null
}
2. 4. MIT - External Vault (merchant supplies NTID + TLID directly)

Request

curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'api-key: [REDACTED]
--header 'Accept: */*' \
--data '{
    "amount": 10000,
    "currency": "USD",
    "confirm": true,
    "customer_id": "cus_jJRKhCItHJsWIlGDTGoi",
    "description": "TLID test - External vault MIT with NTID + TLID",
    "payment_method": "card",
    "payment_method_type": "credit",
    "recurring_details": {
        "type": "network_transaction_id_and_card_details",
        "data": {
            "card_number": "5100290029002909",
            "card_exp_month": "03",
            "card_exp_year": "30",
            "card_holder_name": "Test User",
            "card_network": "Mastercard",
            "network_transaction_id": "HAPTDUS7D0513",
            "transaction_link_id": "LrugTut99Cgunxf3HJOT2H"
        }
    },
    "return_url": "https://example.com/return"
}'

Response

{
    "payment_id": "pay_MeZlSH4mQWDwKCYB9w72",
    "merchant_id": "merchant_1777896241",
    "status": "succeeded",
    "amount": 10000,
    "net_amount": 10000,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 10000,
    "processor_merchant_id": "merchant_1777896241",
    "initiator": null,
    "sdk_authorization": "[REDACTED]",
    "connector": "adyen",
    "state_metadata": null,
    "client_secret": "[REDACTED]",
    "created": "2026-05-13T13:07:15.489Z",
    "modified_at": "2026-05-13T13:07:16.217Z",
    "currency": "USD",
    "customer_id": "cus_jJRKhCItHJsWIlGDTGoi",
    "customer": {
        "id": "cus_jJRKhCItHJsWIlGDTGoi",
        "name": "John Doe",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+65",
        "customer_document_details": null
    },
    "description": "TLID test - External vault MIT with NTID + TLID",
    "refunds": null,
    "disputes": null,
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": null,
    "off_session": null,
    "capture_on": null,
    "capture_method": null,
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "2909",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "510029",
            "card_extended_bin": null,
            "card_exp_month": "03",
            "card_exp_year": "30",
            "card_holder_name": "Test User",
            "payment_checks": null,
            "authentication_data": null,
            "auth_code": null
        },
        "billing": null
    },
    "payment_token": "[REDACTED]",
    "shipping": null,
    "billing": null,
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://example.com/return",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": null,
    "statement_descriptor_suffix": null,
    "next_action": null,
    "cancellation_reason": null,
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": null,
    "error_details": null,
    "payment_experience": null,
    "payment_method_type": "credit",
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "manual_retry_allowed": null,
    "connector_transaction_id": "RHXM6HJ4VZ7LD3W5",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "connector_response_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "pix_additional_details": null,
        "boleto_additional_details": null,
        "pix_automatico_additional_details": null,
        "finix_additional_details": null
    },
    "reference_id": "pay_MeZlSH4mQWDwKCYB9w72_1",
    "payment_link": null,
    "profile_id": "pro_MynyLskWiSBYHJHRqjOK",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_ra4dOXyEJDb9HtB1NzGP",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2026-05-13T13:22:15.489Z",
    "fingerprint": null,
    "browser_info": null,
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": "U0UYLZML90513",
    "network_transaction_link_id": "w1uAjFSDzwnkwDIiqULt4a",
    "payment_method_status": null,
    "updated": "2026-05-13T13:07:16.217Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "extended_authorization_last_applied_at": null,
    "request_extended_authorization": "[REDACTED]",
    "capture_before": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null,
    "card_discovery": "manual",
    "force_3ds_challenge": false,
    "force_3ds_challenge_trigger": false,
    "issuer_error_code": null,
    "issuer_error_message": null,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null,
    "enable_partial_authorization": "[REDACTED]",
    "enable_overcapture": null,
    "is_overcapture_enabled": null,
    "network_details": null,
    "is_stored_credential": true,
    "mit_category": null,
    "billing_descriptor": null,
    "tokenization": null,
    "partner_merchant_identifier_details": null,
    "payment_method_tokenization_details": null,
    "installment_options": null,
    "installment_data": null
}

Testing

  • cargo check passes
  • Adyen CIT response: network_transaction_link_id returned in payments API response
  • Adyen MIT (external vault): additionalData.transactionLinkId present in connector request
  • Adyen MIT (PSP token): Adyen vault handles TLID internally — no explicit send needed

ayush22667 and others added 14 commits May 12, 2026 17:17
…card TLID

Adds structural scaffolding for Mastercard Transaction Link Identifier (TLID)
support. TLID is mandatory for all Mastercard recurring/MIT transactions from
Oct 23, 2026. Missing TLID risks issuer declines (no static fallback unlike
Trace ID which has a static fallback).

Changes:
- DB migrations: add network_transaction_link_id to payment_attempt and payment_methods
- Diesel schema (schema.rs, schema_v2.rs) updated with new column
- Domain models and storage impl structs extended with new field
- PaymentsResponseData::TransactionResponse extended with network_txn_link_id
- MandateReferenceId::NetworkMandateId converted from String to NetworkMandateIdRef
  struct (carries NTID + TLID together)
- NetworkTokenWithNTIRef extended with transaction_link_id field
- RecurringDetails API input structs extended with optional transaction_link_id field
- create_payment_method trait/impl updated with new parameter
- All connector TransactionResponse constructions include network_txn_link_id: None
- All values default to None in this PR; Adyen extraction and forwarding in follow-up

Tracking: juspay/hyperswitch-cloud#15941

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Picks up from PR #12260 (structural plumbing). This commit wires actual
TLID values through the payment flows:

- Adyen CIT: extract transactionLinkId from additionalData response and
  store on payment_attempt and payment_methods
- Adyen MIT: read TLID from MandateReferenceId in get_additional_data()
  and send as additionalData.transactionLinkId to Adyen
- NTWithNTIRef and CardWithNTIRef structs carry transaction_link_id
- ActionType::CardWithNetworkTransactionId now uses CardWithNTIRef struct
- IsNtWithNtiFlow::NtWithNtiSupported now carries CardWithNTIRef
- TLID populated in is_network_token_with_network_transaction_id_flow
  via match tuple (same pattern as network_transaction_id)
- PaymentsResponse exposes network_transaction_link_id field
- PaymentMethodTokenizationDetails exposes network_transaction_link_id
- ConfirmUpdate passes network_transaction_link_id from payment attempt

Mandatory for Mastercard recurring/MIT from Oct 23, 2026.
No fallback — missing TLID risks issuer declines.

Tracking: juspay/hyperswitch-cloud#15984

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@ayush22667 ayush22667 requested review from a team as code owners May 13, 2026 13:08
@semanticdiff-com
Copy link
Copy Markdown

semanticdiff-com Bot commented May 13, 2026

@ayush22667 ayush22667 changed the title feat(core): Wire TLID through Adyen payment flows and API response feat(core):Add TLID through Adyen payment flows and API response May 13, 2026
@ayush22667 ayush22667 self-assigned this May 13, 2026
@ayush22667 ayush22667 added the S-test-ready Status: This PR is ready for cypress-tests label May 13, 2026
@hyperswitch-bot hyperswitch-bot Bot added the M-api-contract-changes Metadata: This PR involves API contract changes label May 13, 2026
@ayush22667 ayush22667 changed the title feat(core):Add TLID through Adyen payment flows and API response feat(core): Add TLID through Adyen payment flows and API response May 13, 2026
@github-actions github-actions Bot removed the S-test-ready Status: This PR is ready for cypress-tests label May 14, 2026
Comment thread crates/router/src/core/payments.rs
Comment thread crates/router/src/core/payments.rs
@ayush22667 ayush22667 added the S-test-ready Status: This PR is ready for cypress-tests label May 14, 2026
Comment thread crates/router/src/core/payments/transformers.rs
swangi-kumari
swangi-kumari previously approved these changes May 14, 2026
Sakilmostak
Sakilmostak previously approved these changes May 14, 2026
hrithikesh026
hrithikesh026 previously approved these changes May 15, 2026
Base automatically changed from feat/add-tlid-support to main May 16, 2026 07:21
@bernard-eugine bernard-eugine dismissed stale reviews from hrithikesh026, Sakilmostak, and swangi-kumari May 16, 2026 07:21

The base branch was changed.

@ayush22667 ayush22667 requested review from a team as code owners May 16, 2026 07:21
@hyperswitch-bot hyperswitch-bot Bot added the M-database-changes Metadata: This PR involves database schema changes label May 16, 2026
@hyperswitch-bot hyperswitch-bot Bot removed the M-database-changes Metadata: This PR involves database schema changes label May 16, 2026
@ayush22667 ayush22667 requested review from Sakilmostak, hrithikesh026 and swangi-kumari and removed request for a team May 16, 2026 08:10
@swangi-kumari swangi-kumari requested a review from Copilot May 16, 2026 08:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR wires Mastercard TLID (Transaction Link Identifier) end-to-end for Adyen recurring flows: extracting TLID from Adyen CIT responses, persisting it through internal recurring/MIT routing structures, and exposing it via the Payments API response surface (including tokenization details).

Changes:

  • Adyen: extract additionalData.transactionLinkId into connector TransactionResponse, and forward TLID on MIT as additionalData.transactionLinkId when present in MandateReferenceId.
  • Core routing: propagate TLID alongside NTID through MIT action/routing types (CardWithNTIRef, NTWithNTIRef) and mandate reference construction.
  • API & docs: expose network_transaction_link_id on PaymentsResponse and PaymentMethodTokenizationDetails, and update OpenAPI v1 spec.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
crates/router/tests/payments2.rs Updates expected test structs to include network_transaction_link_id field.
crates/router/tests/payments.rs Updates expected test structs to include network_transaction_link_id field.
crates/router/src/db/events.rs Updates event DB test struct to include network_transaction_link_id.
crates/router/src/core/payments/transformers.rs Maps TLID into API responses and tokenization details (also includes a list-response conversion path).
crates/router/src/core/payments/operations/payment_update.rs Preserves TLID when updating payment attempts (avoids unintentionally clearing it).
crates/router/src/core/payments.rs Carries TLID through MIT routing/action types and mandate reference construction.
crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs Extracts TLID from Adyen responses and forwards TLID in Adyen MIT requests via additionalData.
crates/api_models/src/payments.rs Adds network_transaction_link_id to API response models with documentation.
api-reference/v1/openapi_spec_v1.json Documents/exposes network_transaction_link_id in OpenAPI v1 schema.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 4515 to +4516
network_transaction_id: None,
network_transaction_link_id: None,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

M-api-contract-changes Metadata: This PR involves API contract changes S-test-ready Status: This PR is ready for cypress-tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants