Skip to content

feat: invoke_ddc next action handling#1493

Open
AbhishekChorotiya wants to merge 4 commits intomainfrom
feat/invoke_ddc_next_action-new
Open

feat: invoke_ddc next action handling#1493
AbhishekChorotiya wants to merge 4 commits intomainfrom
feat/invoke_ddc_next_action-new

Conversation

@AbhishekChorotiya
Copy link
Copy Markdown
Contributor

@AbhishekChorotiya AbhishekChorotiya commented Apr 10, 2026

Type of Change

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

Description

Closes #1494

Adds support for the invoke_ddc next action type in the payment confirmation flow. When the backend returns next_action.type = "invoke_ddc", the SDK now performs Device Data Collection (DDC) via a hidden iframe before redirecting the user.

How it works

  1. Backend triggers DDC: After payment confirmation, if next_action.type is invoke_ddc, the SDK reads ddc_data containing iframe_url and timeout_ms from the response.
  2. Hidden iframe injection: A hidden iframe is created pointing to the DDC iframe_url. The iframe performs device fingerprinting/data collection in the background.
  3. Message-based completion: The SDK listens for a postMessage from the iframe containing a next_action with type = "redirect_to_url" and a redirect URL. On receipt, it cleans up and redirects the user.
  4. Timeout handling: If no response is received within timeout_ms (default 30s), the flow fails gracefully with an error message.
  5. Cleanup: All resources (iframe, event listener, timeout) are cleaned up on completion, failure, or timeout.

Test card:

card": {
            "card_number": "4000000000001091",
            "card_exp_month": "12",
            "card_exp_year": "2028",
            "card_cvc": "152"
        }

payment data:

const paymentData = {
  currency: "USD",
  amount: 2999,
  order_details: [
    {
      product_name: "Apple iPhone 15",
      quantity: 1,
      amount: 2999,
    },
  ],
  confirm: false,
  capture_method: "automatic",
  authentication_type: "three_ds",
  customer_id: "hyperswitch_sdk_demo_id",
  email: "hyperswitch_sdk_demo_id@gmail.com",
  request_external_three_ds_authentication: false,
  description: "Hello this is description",
  shipping: {
    address: {
      line1: "1467",
      line2: "Harrison Street",
      line3: "Harrison Street",
      city: "San Fransico",
      state: "California",
      zip: "94122",
      country: "US",
      first_name: "joseph",
      last_name: "Doe",
    },
    phone: {
      number: "8056594427",
      country_code: "+91",
    },
  },
  metadata: {
    udf1: "value1",
    new_customer: "true",
    login_date: "2019-09-10T10:11:12Z",
  },
  billing: {
    address: {
      line1: "1467",
      line2: "Harrison Street",
      line3: "Harrison Street",
      city: "San Fransico",
      state: "California",
      zip: "94122",
      country: "US",
      first_name: "joseph",
      last_name: "Doe",
    },
    phone: {
      number: "8056594427",
      country_code: "+91",
    },
  },
}

/confirm response

{
    "payment_id": "pay_WCfhA6iyvYKHWLxNx7Mr",
    "merchant_id": "merchant_1775476418",
    "status": "requires_customer_action",
    "amount": 2999,
    "net_amount": 2999,
    "shipping_cost": null,
    "amount_capturable": 2999,
    "amount_received": null,
    "processor_merchant_id": "merchant_1775476418",
    "initiator": null,
    "sdk_authorization": "cHJvZmlsZV9pZD1wcm9fQldTSktLTFE2ekIzcmZiT1NHazcscHVibGlzaGFibGVfa2V5PXBrX2Rldl9iZWU5Yjk0ZjcwZmU0NTkwYTFjMDQ2OWYyZGYzN2Y4YyxjbGllbnRfc2VjcmV0PXBheV9XQ2ZoQTZpeXZZS0hXTHhOeDdNcl9zZWNyZXRfcmlEUlV0elh1TjluUHRCVTUwRzUsY3VzdG9tZXJfaWQ9aHlwZXJzd2l0Y2hfc2RrX2RlbW9faWQscGF5bWVudF9pZD1wYXlfV0NmaEE2aXl2WUtIV0x4Tng3TXI=",
    "connector": "worldpayxml",
    "state_metadata": null,
    "client_secret": "pay_WCfhA6iyvYKHWLxNx7Mr_secret_riDRUtzXuN9nPtBU50G5",
    "created": "2026-04-13T10:30:29.034Z",
    "modified_at": "2026-04-13T10:32:33.014Z",
    "currency": "USD",
    "customer_id": "hyperswitch_sdk_demo_id",
    "customer": {
        "id": "hyperswitch_sdk_demo_id",
        "name": null,
        "email": "hyperswitch_sdk_demo_id@gmail.com",
        "phone": null,
        "phone_country_code": null,
        "customer_document_details": null
    },
    "description": "Hello this is description",
    "refunds": null,
    "disputes": null,
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": null,
    "off_session": null,
    "capture_on": null,
    "capture_method": "automatic",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "1091",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "400000",
            "card_extended_bin": null,
            "card_exp_month": "12",
            "card_exp_year": "2028",
            "card_holder_name": "joseph Doe",
            "payment_checks": null,
            "authentication_data": null,
            "auth_code": null
        },
        "billing": {
            "address": {
                "city": null,
                "country": null,
                "line1": null,
                "line2": null,
                "line3": null,
                "zip": null,
                "state": null,
                "first_name": "joseph",
                "last_name": "Doe",
                "origin_zip": null
            },
            "phone": null,
            "email": null
        }
    },
    "payment_token": "token_JlxynRL1PiEiDeO7p94S",
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": null
    },
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": null
    },
    "order_details": [
        {
            "sku": null,
            "upc": null,
            "brand": null,
            "amount": 2999,
            "category": null,
            "quantity": 1,
            "tax_rate": null,
            "product_id": null,
            "description": null,
            "product_name": "Apple iPhone 15",
            "product_type": null,
            "sub_category": null,
            "total_amount": null,
            "commodity_code": null,
            "unit_of_measure": null,
            "product_img_link": null,
            "product_tax_code": null,
            "total_tax_amount": null,
            "requires_shipping": null,
            "unit_discount_amount": null
        }
    ],
    "email": "hyperswitch_sdk_demo_id@gmail.com",
    "name": null,
    "phone": null,
    "return_url": "http://localhost:9060/",
    "authentication_type": "three_ds",
    "statement_descriptor_name": null,
    "statement_descriptor_suffix": null,
    "next_action": {
        "type": "invoke_ddc",
        "ddc_data": {
            "iframe_url": "http://localhost:8080/payments/redirect/pay_WCfhA6iyvYKHWLxNx7Mr/merchant_1775476418/pay_WCfhA6iyvYKHWLxNx7Mr_1",
            "timeout_ms": 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": null,
    "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": null,
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "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
    },
    "reference_id": null,
    "payment_link": null,
    "profile_id": "pro_BWSJKKLQ6zB3rfbOSGk7",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_lmOoLJtKDq6eJQw5rZ3m",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2026-04-13T10:45:29.034Z",
    "fingerprint": null,
    "browser_info": {
        "os_type": "macOS",
        "referer": null,
        "language": "en-US",
        "time_zone": -330,
        "ip_address": "::1",
        "os_version": "10.15.7",
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
        "color_depth": 24,
        "device_model": "Macintosh",
        "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,
        "accept_language": "en-US,en;q=0.9",
        "java_script_enabled": true
    },
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2026-04-13T10:32:33.014Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "extended_authorization_last_applied_at": null,
    "request_extended_authorization": null,
    "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": null,
    "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": null,
    "installment_options": null,
    "installment_data": null
}

How did you test it?

Tested locally

Screen.Recording.2026-04-13.at.3.41.25.PM.mov

Checklist

  • I ran npm run re:build
  • I reviewed submitted code
  • I added unit tests for my changes where possible

@semanticdiff-com
Copy link
Copy Markdown

Review changes with  SemanticDiff

@juspay juspay deleted a comment from github-actions bot Apr 10, 2026
@AbhishekChorotiya AbhishekChorotiya force-pushed the feat/invoke_ddc_next_action-new branch from 6dacb60 to 1ec4034 Compare April 10, 2026 02:35
@AbhishekChorotiya AbhishekChorotiya changed the title feat: add DDC (Device Data Collection) next action handling feat: invoke_ddc next action handling Apr 10, 2026
Comment on lines +192 to +194
ddc_data: json
->getOptionalDict("ddc_data")
->Option.map(ddcDict => {
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.

Instead of extracting the ddc_data inline here, please create a separate helper function getDdcData

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.

feat: support DDC (Device Data Collection) next action in payment flow

2 participants