Skip to content

feat(connector): [PeachPayments] Add Cards Flow#9030

Merged
likhinbopanna merged 16 commits intojuspay:mainfrom
peach-payments:peachpayments
Sep 12, 2025
Merged

feat(connector): [PeachPayments] Add Cards Flow#9030
likhinbopanna merged 16 commits intojuspay:mainfrom
peach-payments:peachpayments

Conversation

@BenJanecke
Copy link
Contributor

@BenJanecke BenJanecke commented Aug 22, 2025

Type of Change

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

Description

This PR implements the complete Peachpayments connector integration for Hyperswitch, adding support for payment processing, 3DS authentication, void transactions, and comprehensive validation.

Key features implemented:

  • Complete Peachpayments connector with payment authorization, capture, and void flows
  • 3DS authentication support with enhanced security features
  • Network token handling with TAVV/XID fields for improved authentication
  • ConnectorValidation implementation for request validation
  • Comprehensive error handling and response transformations
  • Configuration and API endpoint management

Additional Changes

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

Motivation and Context

This change adds Peachpayments as a new payment processor option in Hyperswitch, expanding payment gateway options for merchants. The implementation follows the connector integration pattern and includes all necessary features for production use including security validations and 3DS support.

How did you test it?

Postman Test

  1. Payment Connector - Create

Request:

curl --location 'http://localhost:8080/account/merchant_1757610148/connectors' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_SFgSgDzIk5bWAMdai9p76lBeX4bvrRV15SEqDicJ7OhCG6KRzOTljGHUZxpKnZN6' \
--data-raw '{
    "connector_type": "payment_processor",
    "connector_name": "peachpayments",
    "connector_account_details": {
        "auth_type": "BodyKey",
        "api_key": API KEY,
        "key1": KEY1
        
    },
    "test_mode": true,
    "disabled": false,
    "payment_methods_enabled": [
        {
            "payment_method": "card",
            "payment_method_types": [
                {
                    "payment_method_type": "credit",
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                {
                    "payment_method_type": "debit",
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        }
    ],
    "connector_webhook_details": {
        "merchant_secret": "MyWebhookSecret"
    },
    "metadata": {
        "client_merchant_reference_id": CLIENT MERCHANT REFERENCE ID,
        "name": NAME,
        "mcc": MCC,
        "phone": "27726599258",
        "email": "hello@example.com",
        "mobile": "27726599258",
        "address": "35 Brickfield Rd",
        "city": "Cape Town",
        "postalCode": "7925",
        "regionCode": "WC",
        "merchantType": "sub",
        "websiteUrl": "example.com",
        "route": ROUTE,
        "mid": MID,
        "tid": TID,
        "subMid": SUBMID,
        "visaPaymentFacilitatorId": VISAPAYMENTFACILITATORID,
        "masterCardPaymentFacilitatorId": MASTERCARDPAYMENTFACILITATORID
    },
    "business_country": "US",
    "business_label": "default",
    "additional_merchant_data": null,
    "status": "active",
    "pm_auth_config": null
}'

Response:

{
    "connector_type": "payment_processor",
    "connector_name": "peachpayments",
    "connector_label": "peachpayments_US_default",
    "merchant_connector_id": "mca_ef4ONOxde7RCQQfvhHJJ",
    "profile_id": "pro_5BUfDMTQyz5EbHajw6SE",
    "connector_account_details": {
        "auth_type": "BodyKey",
        "api_key": "********************",
        "key1": "********************************"
    },
    "payment_methods_enabled": [
        {
            "payment_method": "card",
            "payment_method_types": [
                {
                    "payment_method_type": "credit",
                    "payment_experience": null,
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "accepted_currencies": null,
                    "accepted_countries": null,
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                {
                    "payment_method_type": "debit",
                    "payment_experience": null,
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "accepted_currencies": null,
                    "accepted_countries": null,
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        }
    ],
    "connector_webhook_details": {
        "merchant_secret": "MyWebhookSecret",
        "additional_secret": null
    },
    "metadata": {
        "client_merchant_reference_id": CLIENT MERCHANT REFERENCE ID,
        "name": NAME,
        "mcc": MCC,
        "phone": "27726599258",
        "email": "hello@example.com",
        "mobile": "27726599258",
        "address": "35 Brickfield Rd",
        "city": "Cape Town",
        "postalCode": "7925",
        "regionCode": "WC",
        "merchantType": "sub",
        "websiteUrl": "example.com",
        "route": ROUTE,
        "mid": MID,
        "tid": TID,
        "subMid": SUBMID,
        "visaPaymentFacilitatorId": VISAPAYMENTFACILITATORID,
        "masterCardPaymentFacilitatorId": MASTERCARDPAYMENTFACILITATORID
    },
    "test_mode": true,
    "disabled": false,
    "frm_configs": null,
    "business_country": "US",
    "business_label": "default",
    "business_sub_label": null,
    "applepay_verified_domains": null,
    "pm_auth_config": null,
    "status": "active",
    "additional_merchant_data": null,
    "connector_wallets_details": null
}
  1. Payments - Create

Request:

curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_SFgSgDzIk5bWAMdai9p76lBeX4bvrRV15SEqDicJ7OhCG6KRzOTljGHUZxpKnZN6' \
--data-raw '
{
    "amount": 35308,
    "currency": "USD",
    "confirm": true,
    "capture_method": "manual",
    "customer_id": "StripeCustomer",
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+1",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "return_url": "https://duck.com",
    "payment_method": "card",
    "payment_method_type": "credit",
    "payment_method_data": {
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "10",
            "card_exp_year": "25",
            "card_holder_name": "joseph Doe",
            "card_cvc": "123"
        }
    },
    "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"
        }
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX"
        }
    },
    "browser_info": {
        "color_depth": 24,
        "java_enabled": true,
        "java_script_enabled": true,
        "language": "en-US",
        "screen_height": 848,
        "screen_width": 1312,
        "time_zone": -330,
        "ip_address": "65.1.52.128",
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15",
        "os_type": "macOS",
        "os_version": "10.15.7",
        "device_model": "Macintosh",
        "accept_language": "en"
    }
}'

Response:

{
    "payment_id": "pay_2POhxvMBaEDy88vz9cwX",
    "merchant_id": "merchant_1757610148",
    "status": "requires_capture",
    "amount": 35308,
    "net_amount": 35308,
    "shipping_cost": null,
    "amount_capturable": 35308,
    "amount_received": null,
    "connector": "peachpayments",
    "client_secret": "pay_2POhxvMBaEDy88vz9cwX_secret_1BIiQY7RkIeBsyqU7YRF",
    "created": "2025-09-11T17:13:57.373Z",
    "currency": "USD",
    "customer_id": "StripeCustomer",
    "customer": {
        "id": "StripeCustomer",
        "name": "John Doe",
        "email": "guest@example.com",
        "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": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "4242",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "424242",
            "card_extended_bin": null,
            "card_exp_month": "10",
            "card_exp_year": "25",
            "card_holder_name": "joseph Doe",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": null,
            "origin_zip": null
        },
        "phone": null,
        "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": null,
        "email": null
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://duck.com/",
    "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,
    "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": {
        "customer_id": "StripeCustomer",
        "created_at": 1757610837,
        "expires": 1757614437,
        "secret": "epk_826cdebaa0dc4fb197d4a229381cff54"
    },
    "manual_retry_allowed": null,
    "connector_transaction_id": "884be477-1865-4f40-b452-ae792e21a6d5",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "gateway_system": "direct"
    },
    "reference_id": "884be477-1865-4f40-b452-ae792e21a6d5",
    "payment_link": null,
    "profile_id": "pro_5BUfDMTQyz5EbHajw6SE",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_ef4ONOxde7RCQQfvhHJJ",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-09-11T17:28:57.373Z",
    "fingerprint": null,
    "browser_info": {
        "os_type": "macOS",
        "language": "en-US",
        "time_zone": -330,
        "ip_address": "65.1.52.128",
        "os_version": "10.15.7",
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15",
        "color_depth": 24,
        "device_model": "Macintosh",
        "java_enabled": true,
        "screen_width": 1312,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "screen_height": 848,
        "accept_language": "en",
        "java_script_enabled": true
    },
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2025-09-11T17:13:59.291Z",
    "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",
    "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
}
  1. Payments - Capture

Request:

curl --location 'http://localhost:8080/payments/pay_2POhxvMBaEDy88vz9cwX/capture' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_SFgSgDzIk5bWAMdai9p76lBeX4bvrRV15SEqDicJ7OhCG6KRzOTljGHUZxpKnZN6' \
--data '
{
    "amount_to_capture": 35308,
    "statement_descriptor_name": "Joseph",
    "statement_descriptor_suffix": "JS"
    
}'

Response:

{
    "payment_id": "pay_2POhxvMBaEDy88vz9cwX",
    "merchant_id": "merchant_1757610148",
    "status": "succeeded",
    "amount": 35308,
    "net_amount": 35308,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 35308,
    "connector": "peachpayments",
    "client_secret": "pay_2POhxvMBaEDy88vz9cwX_secret_1BIiQY7RkIeBsyqU7YRF",
    "created": "2025-09-11T17:13:57.373Z",
    "currency": "USD",
    "customer_id": "StripeCustomer",
    "customer": {
        "id": "StripeCustomer",
        "name": "John Doe",
        "email": "guest@example.com",
        "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": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "4242",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "424242",
            "card_extended_bin": null,
            "card_exp_month": "10",
            "card_exp_year": "25",
            "card_holder_name": "joseph Doe",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": null,
            "origin_zip": null
        },
        "phone": null,
        "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": null,
        "email": null
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://duck.com/",
    "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,
    "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": "884be477-1865-4f40-b452-ae792e21a6d5",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "gateway_system": "direct"
    },
    "reference_id": "884be477-1865-4f40-b452-ae792e21a6d5",
    "payment_link": null,
    "profile_id": "pro_5BUfDMTQyz5EbHajw6SE",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_ef4ONOxde7RCQQfvhHJJ",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-09-11T17:28:57.373Z",
    "fingerprint": null,
    "browser_info": {
        "os_type": "macOS",
        "language": "en-US",
        "time_zone": -330,
        "ip_address": "65.1.52.128",
        "os_version": "10.15.7",
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15",
        "color_depth": 24,
        "device_model": "Macintosh",
        "java_enabled": true,
        "screen_width": 1312,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "screen_height": 848,
        "accept_language": "en",
        "java_script_enabled": true
    },
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2025-09-11T17:15:17.517Z",
    "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",
    "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
}
  1. Payments - Retrieve

Request:

curl --location 'http://localhost:8080/payments/pay_2POhxvMBaEDy88vz9cwX?force_sync=true' \
--header 'Accept: application/json' \
--header 'api-key: dev_SFgSgDzIk5bWAMdai9p76lBeX4bvrRV15SEqDicJ7OhCG6KRzOTljGHUZxpKnZN6'

Response:

{
    "payment_id": "pay_2POhxvMBaEDy88vz9cwX",
    "merchant_id": "merchant_1757610148",
    "status": "succeeded",
    "amount": 35308,
    "net_amount": 35308,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 35308,
    "connector": "peachpayments",
    "client_secret": "pay_2POhxvMBaEDy88vz9cwX_secret_1BIiQY7RkIeBsyqU7YRF",
    "created": "2025-09-11T17:13:57.373Z",
    "currency": "USD",
    "customer_id": "StripeCustomer",
    "customer": {
        "id": "StripeCustomer",
        "name": "John Doe",
        "email": "guest@example.com",
        "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": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "4242",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "424242",
            "card_extended_bin": null,
            "card_exp_month": "10",
            "card_exp_year": "25",
            "card_holder_name": "joseph Doe",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": null,
            "origin_zip": null
        },
        "phone": null,
        "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": null,
        "email": null
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://duck.com/",
    "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,
    "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": "884be477-1865-4f40-b452-ae792e21a6d5",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "gateway_system": "direct"
    },
    "reference_id": "884be477-1865-4f40-b452-ae792e21a6d5",
    "payment_link": null,
    "profile_id": "pro_5BUfDMTQyz5EbHajw6SE",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_ef4ONOxde7RCQQfvhHJJ",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-09-11T17:28:57.373Z",
    "fingerprint": null,
    "browser_info": {
        "os_type": "macOS",
        "language": "en-US",
        "time_zone": -330,
        "ip_address": "65.1.52.128",
        "os_version": "10.15.7",
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15",
        "color_depth": 24,
        "device_model": "Macintosh",
        "java_enabled": true,
        "screen_width": 1312,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "screen_height": 848,
        "accept_language": "en",
        "java_script_enabled": true
    },
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2025-09-11T17:15:35.325Z",
    "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",
    "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
}
  1. Payments - Cancel (with different paymentID)

Request:

curl --location 'http://localhost:8080/payments/pay_jflZ4TICqo9uLRFDrbtc/cancel' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_SFgSgDzIk5bWAMdai9p76lBeX4bvrRV15SEqDicJ7OhCG6KRzOTljGHUZxpKnZN6' \
--data '{
  "cancellation_reason": "requested_by_customer"
}'

Response:

{
    "payment_id": "pay_jflZ4TICqo9uLRFDrbtc",
    "merchant_id": "merchant_1757610148",
    "status": "cancelled",
    "amount": 35308,
    "net_amount": 35308,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": null,
    "connector": "peachpayments",
    "client_secret": "pay_jflZ4TICqo9uLRFDrbtc_secret_qDuIgBJH7F07APir1zg8",
    "created": "2025-09-11T17:04:35.524Z",
    "currency": "USD",
    "customer_id": "StripeCustomer",
    "customer": {
        "id": "StripeCustomer",
        "name": "John Doe",
        "email": "guest@example.com",
        "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": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "4242",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "424242",
            "card_extended_bin": null,
            "card_exp_month": "10",
            "card_exp_year": "25",
            "card_holder_name": "joseph Doe",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": null,
            "origin_zip": null
        },
        "phone": null,
        "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": null,
        "email": null
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://duck.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": null,
    "statement_descriptor_suffix": null,
    "next_action": null,
    "cancellation_reason": "requested_by_customer",
    "error_code": null,
    "error_message": null,
    "unified_code": "UE_9000",
    "unified_message": "Something went wrong",
    "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": "0ca6ad99-d749-4a9e-96df-95fa761681ad",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "gateway_system": "direct"
    },
    "reference_id": "0ca6ad99-d749-4a9e-96df-95fa761681ad",
    "payment_link": null,
    "profile_id": "pro_5BUfDMTQyz5EbHajw6SE",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_ef4ONOxde7RCQQfvhHJJ",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-09-11T17:19:35.524Z",
    "fingerprint": null,
    "browser_info": {
        "os_type": "macOS",
        "language": "en-US",
        "time_zone": -330,
        "ip_address": "65.1.52.128",
        "os_version": "10.15.7",
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15",
        "color_depth": 24,
        "device_model": "Macintosh",
        "java_enabled": true,
        "screen_width": 1312,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "screen_height": 848,
        "accept_language": "en",
        "java_script_enabled": true
    },
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2025-09-11T17:05:06.519Z",
    "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",
    "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": {
        "network_advice_code": 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

@BenJanecke BenJanecke requested review from a team as code owners August 22, 2025 08:28
@semanticdiff-com
Copy link

semanticdiff-com bot commented Aug 22, 2025

@BenJanecke BenJanecke changed the title WIP - PeachPayments Connector WIP - feature: PeachPayments Connector Aug 22, 2025
_req: &PaymentsAuthorizeRouterData,
connectors: &Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!("{}/transactions", self.base_url(connectors)))
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
Ok(format!("{}/transactions", self.base_url(connectors)))
Ok(format!("{}transactions", self.base_url(connectors)))

Can we make this change across all flows since the base url already has a training / at the end?

paystack.base_url = "https://api.paystack.co"
payu.base_url = "https://secure.snd.payu.com/"
peachpayments.base_url = "https://apitest.bankint.ppay.io/v/1"
placetopay.base_url = "https://test.placetopay.com/rest/gateway"
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
placetopay.base_url = "https://test.placetopay.com/rest/gateway"
placetopay.base_url = "https://test.placetopay.com/rest/gateway/"

Can we add a trailing / since the same is done in deployments/production.toml file as well ?
Can we do the similar change for deployments/sandbox.toml, deployments/docker_compose.toml?

#[serde(rename = "sendDateTime")]
pub send_date_time: String,
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of adding #[serde(rename = "chargeMethod")] on top of every field we can look at adding #[serde(rename_all = "camelCase")] at the top of the struct?
Can we do the same across all structs?


#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct AmountDetails {
pub amount: i64,
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
pub amount: i64,
pub amount: MinorUnit,

Can we use custom user defined data type for Amount Fields?

);

Ok(Self {
payment_method: "ecommerce_card_payment_only".to_string(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we make this field's type a enum and add ecommerce_card_payment_only as an enum variant?

Comment on lines +419 to +404
impl TryFrom<&ConnectorAuthType> for PeachpaymentsAuthType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
match auth_type {
ConnectorAuthType::BodyKey { api_key, key1: tenant_id } => {
Ok(Self {
api_key: api_key.to_owned(),
tenant_id: tenant_id.to_owned(),
})
},
ConnectorAuthType::MultiAuthKey {
api_key,
key1: tenant_id,
..
} => {
Ok(Self {
api_key: api_key.to_owned(),
tenant_id: tenant_id.to_owned(),
})
},
_ => {
Err(errors::ConnectorError::FailedToObtainAuthType.into())
}
}
}
}
Copy link
Contributor

@bsayak03 bsayak03 Aug 29, 2025

Choose a reason for hiding this comment

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

Suggested change
impl TryFrom<&ConnectorAuthType> for PeachpaymentsAuthType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
match auth_type {
ConnectorAuthType::BodyKey { api_key, key1: tenant_id } => {
Ok(Self {
api_key: api_key.to_owned(),
tenant_id: tenant_id.to_owned(),
})
},
ConnectorAuthType::MultiAuthKey {
api_key,
key1: tenant_id,
..
} => {
Ok(Self {
api_key: api_key.to_owned(),
tenant_id: tenant_id.to_owned(),
})
},
_ => {
Err(errors::ConnectorError::FailedToObtainAuthType.into())
}
}
}
}
impl TryFrom<&ConnectorAuthType> for PeachpaymentsAuthType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
if let ConnectorAuthType::BodyKey { api_key, key1 } = auth_type {
Ok(Self {
api_key: api_key.clone(),
tenant_id: key1.clone(),
})
} else {
Err(errors::ConnectorError::FailedToObtainAuthType)?
}
}
}

Can we eliminate the handling of MultiAuthKey since the connector will always have a type of BodyKey?


#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ResponseCode {
pub value: String,
Copy link
Contributor

Choose a reason for hiding this comment

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

can we make this type a enum and include variants like 00?

} else {
Ok(PaymentsResponseData::TransactionResponse {
resource_id: ResponseId::ConnectorTransactionId(item.response.transaction_id.clone()),
redirection_data: Box::new(None),
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we please get to know in case of 3ds payments, how would we get the redirect link for the customer to authenticate?


// Extract card details from refund_connector_metadata
let card = if let Some(ref connector_metadata) = item.router_data.request.refund_connector_metadata {
let metadata_value = connector_metadata.clone().expose();
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we get to know why card details are again needed for Refund?

Comment on lines +966 to +901
impl TryFrom<ErrorResponse> for PeachpaymentsErrorResponse {
type Error = error_stack::Report<errors::ConnectorError>;

fn try_from(error_response: ErrorResponse) -> Result<Self, Self::Error> {
Ok(Self {
error_ref: error_response.code,
message: error_response.message,
error_details: serde_json::json!({}),
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

can we get to know where is this try_from being used?

@Anurag-05-prog Anurag-05-prog requested review from a team as code owners September 9, 2025 08:10
@Anurag-05-prog Anurag-05-prog force-pushed the peachpayments branch 5 times, most recently from 4eb9d07 to 0a7dc52 Compare September 9, 2025 12:00
BenJanecke and others added 8 commits September 9, 2025 18:58
…tion and API support

- Added Peachpayments base URL to configuration files (development, example, docker compose, integration test, sandbox).
- Introduced Peachpayments connector in the codebase, including payment and refund handling.
- Implemented necessary transformers and request/response structures for Peachpayments.
- Updated connector enums and validation to include Peachpayments.
- Added tests for Peachpayments integration to ensure functionality.
…card networks, add amexId, remove defaults

- Restrict supported card networks to only Mastercard, Visa, AmericanExpress across all config files
- Add amexId field to routing configuration for AmEx transactions with optional serialization
- Remove default values from transformer structs - omit keys when values not present instead
- Update merchant_type to Select field with options: iso, sub
- Update routing_route to Select field with options: exipay_emulator, absa_base24, nedbank_postbridge
- Make MerchantInformation fields optional except client_merchant_reference_id
- Make CardDetails cardholder_name and cvv optional for security compliance
- Ensure routing_route is required in connector metadata with proper validation
- Apply changes consistently across development, production, and sandbox configurations
…ments connector

- Add PeachpaymentsVoidRequest struct with payment_method, send_date_time, and failure_reason fields
- Implement TryFrom for PaymentsCancelRouterData with proper ISO 8601 timestamp generation
- Add complete void flow implementation in main connector with proper request/response handling
- Support POST /transactions/{transactionId}/void endpoint as per POC examples
- Use "timeout" as default failure_reason to match script behavior
- Reuse existing PeachpaymentsPaymentsResponse for void response handling

This brings the connector up to standard with all required flows: create, confirm, refund, sync, and void.
Comprehensive improvements to 3D Secure implementation:

• Enhanced ThreeDSData struct with all required fields:
  - Added tavv field for Token Authentication Verification Value
  - Added xid field for Transaction Identifier
  - Improved field handling with proper optional serialization

• Improved authentication status mapping:
  - ECI "05"/"06" → "Y" (fully authenticated)
  - ECI "07" → "A" (attempted authentication)
  - Other ECI values → "N" (not authenticated)

• Better 3DS data extraction:
  - Enhanced CAVV validation and handling
  - Proper version string truncation for API compatibility
  - Improved ds_trans_id extraction with fallback logic

• Comprehensive field validation:
  - All fields properly handle None values
  - skip_serializing_if applied consistently
  - Default ECI value "07" for attempted authentication

Limitations documented:
- Hyperswitch AuthenticationData struct missing tavv/xid fields
- Code structured to accept these fields when available
- Enhanced error handling for missing authentication data
…peachpayments

- Updated comments to accurately reflect that tavv and xid are network token fields
- tavv: Token Authentication Verification Value (network tokens)
- xid: Legacy 3DS 1.x/network token Transaction Identifier
- These fields are part of PeachPayments API spec but not standard 3DS fields
- Added proper validation for capture methods (Manual, Automatic, SequentialAutomatic)
- Added validation for payment methods (Card only for now)
- Added validation for payment method types (Credit, Debit)
- Provides clear error messages for unsupported features
- Follows Hyperswitch connector validation patterns
@Anurag-05-prog Anurag-05-prog removed the request for review from a team September 9, 2025 14:03
@Anurag-05-prog Anurag-05-prog linked an issue Sep 11, 2025 that may be closed by this pull request
2 tasks
@Anurag-05-prog Anurag-05-prog changed the title WIP - feature: PeachPayments Connector feat(connector): [PeachPayments] Add Cards Flow Sep 11, 2025
@Anurag-05-prog Anurag-05-prog removed the request for review from a team September 11, 2025 17:10
[[peachpayments.debit]]
payment_method_type = "Visa"
[[peachpayments.debit]]
payment_method_type = "AmericanExpress"
Copy link
Contributor

Choose a reason for hiding this comment

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

just making sure we don't want to include any other card networks for now?

Copy link
Contributor

Choose a reason for hiding this comment

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

Confirmed with the Peachpayments team, they support Mastercard Visa and AmericanExpress.

required=true
type="Text"


Copy link
Contributor

Choose a reason for hiding this comment

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

please remove the extra whitespaces

paypal.base_url = "https://api-m.paypal.com/"
paysafe.base_url = "https://api.paysafe.com/paymenthub/"
paystack.base_url = "https://api.paystack.co"
peachpayments.base_url = "https://api.bankint.peachpayments.com"
Copy link
Contributor

Choose a reason for hiding this comment

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

This url already exists right ?

ShankarSinghC
ShankarSinghC previously approved these changes Sep 11, 2025
// Truncate version to match API spec (e.g., "2.2.0" -> "2.2")
if version_str.len() > 3 && version_str.chars().nth(3) == Some('.')
{
version_str[..3].to_string()
Copy link
Contributor

Choose a reason for hiding this comment

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

using [..3] assumes a strict format (e.g., "2.2.0"). If version strings change,code may panic -

we can use something safer like split

let mut parts = v.to_string().split('.');
        match (parts.next(), parts.next()) {
            (Some(major), Some(minor)) => format!("{}.{}", major, minor),
            _ => v.to_string(), // fallback if format unexpected
        }

.map(|reason| {
FailureReason::from_str(reason)
})
.transpose()?
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Right now FromStr always returns Ok, so the Result / transpose()? isn’t really needed. We could make a helper like FailureReason::from_str(reason) that directly returns a FailureReason (defaulting to Timeout), which would make this block easier to read.

dgeee13
dgeee13 previously approved these changes Sep 11, 2025
@hyperswitch-bot hyperswitch-bot bot added the M-api-contract-changes Metadata: This PR involves API contract changes label Sep 11, 2025
@likhinbopanna likhinbopanna added this pull request to the merge queue Sep 12, 2025
Merged via the queue into juspay:main with commit f3635a2 Sep 12, 2025
17 of 21 checks passed
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Add Peachpayments Connector - Cards Flow

6 participants