Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(request_body): Added FRM data in payment request #7615

Merged
merged 8 commits into from
Mar 28, 2025

Conversation

Sweta-Kumari-Sharma
Copy link
Contributor

@Sweta-Kumari-Sharma Sweta-Kumari-Sharma commented Mar 25, 2025

Type of Change

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

Description

Added FRM data in payment request for connector Adyen

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?

Screenshot 2025-03-26 at 6 00 22 PM
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_1tUvTWvHj4yX8wc3kkROOR01P8ikqRzDkcj3ZUdcpLmTjhkuW6f7LPj1s7RhO3Nc' \
--data-raw '{
    "amount": 100,
    "currency": "USD",
    "confirm": true,
    "payment_link": false,
    "capture_on": "2029-09-10T10:11:12Z",
    "amount_to_capture": 100,
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+1",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
    "payment_method_data": {
        "card": {
            "card_number": "4917610000000000",
            "card_exp_month": "03",
            "card_exp_year": "30",
            "card_cvc": "737",
            "card_network": "Visa"
        },
        "billing": {
            "address": {
                "line1": "1467",
                "line2": "CA",
                "city": "San Fransico",
                "state": "California",
                "zip": "94122",
                "country": "US",
                "first_name": "Sweta",
                "last_name": "Sharma"
            },
            "email": "[email protected]",
            "phone": {
                "number": "9999999999",
                "country_code": "+91"
            }
        }
    },
    "email": "[email protected]",
    "payment_method": "card",
    "payment_method_type": "credit",
    "browser_info": {
        "color_depth": 24,
        "java_enabled": true,
        "java_script_enabled": true,
        "language": "en-GB",
        "screen_height": 720,
        "screen_width": 1280,
        "time_zone": -330,
        "ip_address": "208.127.127.193",
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0"
    },
    "metadata": {
        "device_fingerprint": "abc123devicefingerprint2",
        "riskdata.basket.item1.itemID": "1234567890",
        "riskdata.basket.item1.productTitle": "Wireless Bluetooth Headphones",
        "riskdata.basket.item1.amountPerItem": "79.99",
        "riskdata.basket.item1.currency": "USD",
        "riskdata.basket.item1.upc": "012345678901",
        "riskdata.basket.item1.brand": "BrandX",
        "riskdata.basket.item1.manufacturer": "BrandX Manufacturing Inc.",
        "riskdata.basket.item1.category": "Electronics",
        "riskdata.basket.item1.quantity": "1",
        "riskdata.basket.item1.color": "Black",
        "riskdata.basket.item1.size": "One Size",
        "riskdata.deviceCountry": "US",
        "riskdata.houseNumberorName": "1234",
        "riskdata.accountCreationDate": "2015-08-01",
        "riskdata.affiliateChannel": "Online Store",
        "riskdata.avgOrderValue": "120.50",
        "riskdata.deliveryMethod": "Standard Shipping",
        "riskdata.emailName": "swetasharma",
        "riskdata.emailDomain": "example.com",
        "riskdata.lastOrderDate": "2024-12-25",
        "riskdata.merchantReference": "ORD12345XYZ",
        "riskdata.paymentMethod": "Visa",
        "riskdata.promotionName": "Holiday Sale",
        "riskdata.secondaryPhoneNumber": "9876543210",
        "riskdata.timefromLogintoOrder": "15 minutes",
        "riskdata.totalSessionTime": "45 minutes",
        "riskdata.totalAuthorizedAmountInLast30Days": "500.00",
        "riskdata.totalOrderQuantity": "10",
        "riskdata.totalLifetimeValue": "1500.00",
        "riskdata.visitsMonth": "8",
        "riskdata.visitsWeek": "3",
        "riskdata.visitsYear": "30",
        "riskdata.shipToName": "Sweta Sharma",
        "riskdata.first8charactersofAddressLine1Zip": "94122",
        "riskdata.affiliateOrder": false
    },
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "CA",
            "zip": "94122",
            "state": "California",
            "first_name": "Sweta",
            "last_name": "Sharma"
        },
        "phone": {
            "number": "9999999999",
            "country_code": "+91"
        },
        "email": "[email protected]"
    }
}'

Response

{
    "payment_id": "pay_4RPSha2liSpcRUJNA6cj",
    "merchant_id": "merchant_1742923962",
    "status": "requires_customer_action",
    "amount": 100,
    "net_amount": 100,
    "shipping_cost": null,
    "amount_capturable": 100,
    "amount_received": null,
    "connector": "adyen",
    "client_secret": "pay_4RPSha2liSpcRUJNA6cj_secret_oaNtTvAFxiHxCk0GxifB",
    "created": "2025-03-26T06:24:55.256Z",
    "currency": "USD",
    "customer_id": null,
    "customer": {
        "id": null,
        "name": "John Doe",
        "email": "[email protected]",
        "phone": "999999999",
        "phone_country_code": "+1"
    },
    "description": "Its my first payment request",
    "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": "0000",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "491761",
            "card_extended_bin": null,
            "card_exp_month": "03",
            "card_exp_year": "30",
            "card_holder_name": "Sweta Sharma",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": {
            "address": {
                "city": "San Fransico",
                "country": "US",
                "line1": "1467",
                "line2": "CA",
                "line3": null,
                "zip": "94122",
                "state": "California",
                "first_name": "Sweta",
                "last_name": "Sharma"
            },
            "phone": {
                "number": "9999999999",
                "country_code": "+91"
            },
            "email": "[email protected]"
        }
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "CA",
            "line3": null,
            "zip": "94122",
            "state": "California",
            "first_name": "Sweta",
            "last_name": "Sharma"
        },
        "phone": {
            "number": "9999999999",
            "country_code": "+91"
        },
        "email": "[email protected]"
    },
    "billing": null,
    "order_details": null,
    "email": null,
    "name": null,
    "phone": null,
    "return_url": "https://google.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": null,
    "statement_descriptor_suffix": null,
    "next_action": {
        "type": "redirect_to_url",
        "redirect_to_url": "http://localhost:8080/payments/redirect/pay_4RPSha2liSpcRUJNA6cj/merchant_1742923962/pay_4RPSha2liSpcRUJNA6cj_1"
    },
    "cancellation_reason": null,
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": 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,
    "ephemeral_key": null,
    "manual_retry_allowed": null,
    "connector_transaction_id": "G8Z7RX4G8BCDCQV5",
    "frm_message": null,
    "metadata": {
         "device_fingerprint": "abc123devicefingerprint2",
        "riskdata.basket.item1.itemID": "1234567890",
        "riskdata.basket.item1.productTitle": "Wireless Bluetooth Headphones",
        "riskdata.basket.item1.amountPerItem": "79.99",
        "riskdata.basket.item1.currency": "USD",
        "riskdata.basket.item1.upc": "012345678901",
        "riskdata.basket.item1.brand": "BrandX",
        "riskdata.basket.item1.manufacturer": "BrandX Manufacturing Inc.",
        "riskdata.basket.item1.category": "Electronics",
        "riskdata.basket.item1.quantity": "1",
        "riskdata.basket.item1.color": "Black",
        "riskdata.basket.item1.size": "One Size",
        "riskdata.deviceCountry": "US",
        "riskdata.houseNumberorName": "1234",
        "riskdata.accountCreationDate": "2015-08-01",
        "riskdata.affiliateChannel": "Online Store",
        "riskdata.avgOrderValue": "120.50",
        "riskdata.deliveryMethod": "Standard Shipping",
        "riskdata.emailName": "swetasharma",
        "riskdata.emailDomain": "example.com",
        "riskdata.lastOrderDate": "2024-12-25",
        "riskdata.merchantReference": "ORD12345XYZ",
        "riskdata.paymentMethod": "Visa",
        "riskdata.promotionName": "Holiday Sale",
        "riskdata.secondaryPhoneNumber": "9876543210",
        "riskdata.timefromLogintoOrder": "15 minutes",
        "riskdata.totalSessionTime": "45 minutes",
        "riskdata.totalAuthorizedAmountInLast30Days": "500.00",
        "riskdata.totalOrderQuantity": "10",
        "riskdata.totalLifetimeValue": "1500.00",
        "riskdata.visitsMonth": "8",
        "riskdata.visitsWeek": "3",
        "riskdata.visitsYear": "30",
        "riskdata.shipToName": "Sweta Sharma",
        "riskdata.first8charactersofAddressLine1Zip": "94122",
        "riskdata.affiliateOrder": false
    },
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "G8Z7RX4G8BCDCQV5",
    "payment_link": null,
    "profile_id": "pro_9AiAESD5MrUBnko9THxG",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_vtrNcsyy5demMast3m1e",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-03-26T06:39:55.256Z",
    "fingerprint": null,
    "browser_info": {
        "language": "en-GB",
        "time_zone": -330,
        "ip_address": "208.127.127.193",
        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0",
        "color_depth": 24,
        "java_enabled": true,
        "screen_width": 1280,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "screen_height": 720,
        "java_script_enabled": true
    },
    "payment_method_id": null,
    "payment_method_status": null,
    "updated": "2025-03-26T06:24:57.280Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "capture_before": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null,
    "card_discovery": "manual",
    "issuer_error_code": null,
    "issuer_error_message": null
}

Checklist

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

@Sweta-Kumari-Sharma Sweta-Kumari-Sharma added A-connector-integration Area: Connector integration C-refactor Category: Refactor labels Mar 25, 2025
@Sweta-Kumari-Sharma Sweta-Kumari-Sharma self-assigned this Mar 25, 2025
@Sweta-Kumari-Sharma Sweta-Kumari-Sharma requested a review from a team as a code owner March 25, 2025 07:33
Copy link

semanticdiff-com bot commented Mar 25, 2025

Review changes with  SemanticDiff

Changed Files
File Status
  crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs  2% smaller

visits_year: Option<String>,
ship_to_name: Option<String>,
first8charactersof_address_line1_zip: Option<String>,
affiliate_order: Option<String>,
Copy link
Contributor

Choose a reason for hiding this comment

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

This value should be bool

Copy link
Contributor Author

Choose a reason for hiding this comment

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

affiliate_order is already bool, rest fields should be string only, right?

Comment on lines 184 to 185
#[serde(rename = "itemID")]
item_id: Option<String>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#[serde(rename = "itemID")]
item_id: Option<String>,
item_i_d: Option<String>,

#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Basket {
basket: Vec<OrderLines>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn;t the hierarchy here be riskdata.basket.item1.itemID?
Where is item1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@Sweta-Kumari-Sharma Sweta-Kumari-Sharma changed the title [REFACTOR] Added FRM data in payment request refactor(request_body): Added FRM data in payment request Mar 26, 2025
impl ForeignTryFrom<serde_json::Value> for RiskData {
type Error = error_stack::Report<errors::ConnectorError>;

fn foreign_try_from(metadata: serde_json::Value) -> Result<Self, Self::Error> {
Copy link
Contributor

@deepanshu-iiitu deepanshu-iiitu Mar 26, 2025

Choose a reason for hiding this comment

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

Do not use foreign try from here, instead create a function like get_risk_data

Also the return type for that function can be Option

Comment on lines 2649 to 2743
let device_country = risk_data_value
.get("device_country")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let house_numberor_name = risk_data_value
.get("house_numberor_name")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let account_creation_date = risk_data_value
.get("account_creation_date")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let affiliate_channel = risk_data_value
.get("affiliate_channel")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let avg_order_value = risk_data_value
.get("avg_order_value")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let delivery_method = risk_data_value
.get("delivery_method")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let email_name = risk_data_value
.get("email_name")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let email_domain = risk_data_value
.get("email_domain")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let last_order_date = risk_data_value
.get("last_order_date")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let merchant_reference = risk_data_value
.get("merchant_reference")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let payment_method = risk_data_value
.get("payment_method")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let promotion_name = risk_data_value
.get("promotion_name")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let secondary_phone_number = risk_data_value
.get("secondary_phone_number")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let timefrom_loginto_order = risk_data_value
.get("timefrom_loginto_order")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let total_session_time = risk_data_value
.get("total_session_time")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let total_authorized_amount_in_last30_days = risk_data_value
.get("total_authorized_amount_in_last30_days")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let total_order_quantity = risk_data_value
.get("total_order_quantity")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let total_lifetime_value = risk_data_value
.get("total_lifetime_value")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let visits_month = risk_data_value
.get("visits_month")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let visits_week = risk_data_value
.get("visits_week")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let visits_year = risk_data_value
.get("visits_year")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let ship_to_name = risk_data_value
.get("ship_to_name")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let first8charactersof_address_line1_zip = risk_data_value
.get("first8charactersof_address_line1_zip")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let affiliate_order = risk_data_value
.get("affiliate_order")
.and_then(|v| v.as_bool());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let device_country = risk_data_value
.get("device_country")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let house_numberor_name = risk_data_value
.get("house_numberor_name")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let account_creation_date = risk_data_value
.get("account_creation_date")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let affiliate_channel = risk_data_value
.get("affiliate_channel")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let avg_order_value = risk_data_value
.get("avg_order_value")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let delivery_method = risk_data_value
.get("delivery_method")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let email_name = risk_data_value
.get("email_name")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let email_domain = risk_data_value
.get("email_domain")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let last_order_date = risk_data_value
.get("last_order_date")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let merchant_reference = risk_data_value
.get("merchant_reference")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let payment_method = risk_data_value
.get("payment_method")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let promotion_name = risk_data_value
.get("promotion_name")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let secondary_phone_number = risk_data_value
.get("secondary_phone_number")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let timefrom_loginto_order = risk_data_value
.get("timefrom_loginto_order")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let total_session_time = risk_data_value
.get("total_session_time")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let total_authorized_amount_in_last30_days = risk_data_value
.get("total_authorized_amount_in_last30_days")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let total_order_quantity = risk_data_value
.get("total_order_quantity")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let total_lifetime_value = risk_data_value
.get("total_lifetime_value")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let visits_month = risk_data_value
.get("visits_month")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let visits_week = risk_data_value
.get("visits_week")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let visits_year = risk_data_value
.get("visits_year")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let ship_to_name = risk_data_value
.get("ship_to_name")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let first8charactersof_address_line1_zip = risk_data_value
.get("first8charactersof_address_line1_zip")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let affiliate_order = risk_data_value
.get("affiliate_order")
.and_then(|v| v.as_bool());
device_country: get_str("device_country"),
house_numberor_name: get_str("house_numberor_name"),
account_creation_date: get_str("account_creation_date"),
affiliate_channel: get_str("affiliate_channel"),
avg_order_value: get_str("avg_order_value"),
delivery_method: get_str("delivery_method"),
email_name: get_str("email_name"),
email_domain: get_str("email_domain"),
last_order_date: get_str("last_order_date"),
merchant_reference: get_str("merchant_reference"),
payment_method: get_str("payment_method"),
promotion_name: get_str("promotion_name"),
secondary_phone_number: get_str("secondary_phone_number"),
timefrom_loginto_order: get_str("timefrom_loginto_order"),
total_session_time: get_str("total_session_time"),
total_authorized_amount_in_last30_days: get_str("total_authorized_amount_in_last30_days"),
total_order_quantity: get_str("total_order_quantity"),
total_lifetime_value: get_str("total_lifetime_value"),
visits_month: get_str("visits_month"),
visits_week: get_str("visits_week"),
visits_year: get_str("visits_year"),
ship_to_name: get_str("ship_to_name"),
first8charactersof_address_line1_zip: get_str("first8charactersof_address_line1_zip"),
affiliate_order: get_bool("affiliate_order"),

impl ForeignTryFrom<serde_json::Value> for Option<Secret<String>> {
type Error = error_stack::Report<errors::ConnectorError>;

fn foreign_try_from(metadata: serde_json::Value) -> Result<Self, Self::Error> {
Copy link
Contributor

Choose a reason for hiding this comment

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

do not use foreign_try_from

Comment on lines 181 to 210
#[serde_with::skip_serializing_none]
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ItemData {
item_i_d: Option<String>,
product_title: Option<String>,
amount_per_item: Option<String>,
currency: Option<String>,
upc: Option<String>,
brand: Option<String>,
manufacturer: Option<String>,
category: Option<String>,
quantity: Option<String>,
color: Option<String>,
size: Option<String>,
}

#[serde_with::skip_serializing_none]
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Basket {
basket: Option<Item>,
}

#[serde_with::skip_serializing_none]
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Item {
item1: Option<ItemData>,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

These structs can be removed now.

swangi-kumari
swangi-kumari previously approved these changes Mar 27, 2025
@likhinbopanna likhinbopanna added this pull request to the merge queue Mar 28, 2025
Merged via the queue into main with commit b00deb9 Mar 28, 2025
17 of 20 checks passed
@likhinbopanna likhinbopanna deleted the adyen-add-fields branch March 28, 2025 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-connector-integration Area: Connector integration C-refactor Category: Refactor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants