Skip to content

refactor(connector): [paysafe] introduce PaymentMethodToken flow#10541

Merged
likhinbopanna merged 12 commits intomainfrom
connector/paysafe-flow-correction
Dec 18, 2025
Merged

refactor(connector): [paysafe] introduce PaymentMethodToken flow#10541
likhinbopanna merged 12 commits intomainfrom
connector/paysafe-flow-correction

Conversation

@pixincreate
Copy link
Member

@pixincreate pixincreate commented Dec 4, 2025

Type of Change

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

Description

this pr is aimed at refactoring the paysafe connector integration. the mail goal of this pr is to replace preProcessing flow with paymentMethodToken flow. however, introduction of paymentMethodToken flow in paysafe will break 3ds transactions due to below given reasons:

  • paymentMethodToken flow is not granular (yet). this will get called before authorize
  • there is no way to restrict authorize or paymentMethodToken flow for a specific authentication_type

hence, this pr introduces the paymentMethodToken flow to replace the preProcessing flow but it is intentionally made to not work just so that it does not affect the existing integration.

Important

ucs will be bumped once the corresponding fix for paysafe is merged in ucs

Additional Changes

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

Motivation and Context

preProcessing flow is wrong. we get a token in the response. we should be using paymentMethodToken flow instead for no3ds cards and applepay.

for 3ds cards, authorize flow is expected since that flow tokenizes and authenticates the user.

How did you test it?

existing no 3ds and applepay payment methods should not be affected:

Cards -- No3DS
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Accept-Language: ja' \
--header 'api-key: dev_QWGWxp0RIAorCkaF3imVYTrHkwVot1caWoGMzPjtrmhMiQwZMGD3BB71B6GbltMF' \
--data-raw '{
	"amount": 4000,
	"currency": "USD",
	"confirm": true,
	"capture_method": "automatic",
	"capture_on": "2022-09-10T10:11:12Z",
	"amount_to_capture": 4000,
	"customer_id": "customer",
	"email": "guesjhvghht@example.com",
	"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_data": {
		"card": {
			"card_number": "4000000000001091",
			"card_exp_month": "10",
			"card_exp_year": "30",
			"card_holder_name": "Joseph",
			"card_cvc": "111"
		}
	},
	"billing": {
		"address": {
			"line1": "1467",
			"line2": "Harrison Street",
			"line3": "Harrison Street",
			"city": "San Fransico",
			"state": "California",
			"zip": "94122",
			"country": "US",
			"first_name": "PiX",
			"last_name": "THE"
		}
	},
	"shipping": {
		"address": {
			"line1": "1467",
			"line2": "Harrison Street",
			"line3": "Harrison Street",
			"city": "San Fransico",
			"state": "California",
			"zip": "94122",
			"country": "US",
			"first_name": "PiX"
		}
	},
	"statement_descriptor_name": "joseph",
	"statement_descriptor_suffix": "JS",
	"metadata": {
		"udf1": "value1",
		"new_customer": "true",
		"login_date": "2019-09-10T10:11:12Z"
	},
	"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"
	}
}'
{
	"payment_id": "pay_AEYIlTwKhUaMQyyVWdNe",
	"merchant_id": "postman_merchant_GHAction_1765193570",
	"status": "succeeded",
	"amount": 4000,
	"net_amount": 4000,
	"shipping_cost": null,
	"amount_capturable": 0,
	"amount_received": 4000,
	"connector": "paysafe",
	"client_secret": "pay_AEYIlTwKhUaMQyyVWdNe_secret_9F3PF1GaxtFS24131yU9",
	"created": "2025-12-08T11:32:57.896Z",
	"modified_at": "2025-12-08T11:32:59.498Z",
	"currency": "USD",
	"customer_id": "customer",
	"customer": {
		"id": "customer",
		"name": null,
		"email": "guesjhvghht@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": "automatic",
	"payment_method": "card",
	"payment_method_data": {
		"card": {
			"last4": "1091",
			"card_type": "CREDIT",
			"card_network": "Visa",
			"card_issuer": "INTL HDQTRS-CENTER OWNED",
			"card_issuing_country": "UNITEDSTATES",
			"card_isin": "400000",
			"card_extended_bin": null,
			"card_exp_month": "10",
			"card_exp_year": "30",
			"card_holder_name": "Joseph",
			"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": "PiX",
			"last_name": "THE",
			"origin_zip": null
		},
		"phone": null,
		"email": null
	},
	"order_details": null,
	"email": "guesjhvghht@example.com",
	"name": null,
	"phone": "999999999",
	"return_url": "https://duck.com/",
	"authentication_type": "no_three_ds",
	"statement_descriptor_name": "joseph",
	"statement_descriptor_suffix": "JS",
	"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": "customer",
		"created_at": 1765193577,
		"expires": 1765197177,
		"secret": "epk_03767cafb717462cbcc04e7fdba0f1b8"
	},
	"manual_retry_allowed": null,
	"connector_transaction_id": "b4f7dcbf-e26d-4551-b15b-61be7c143740",
	"frm_message": null,
	"metadata": {
		"udf1": "value1",
		"login_date": "2019-09-10T10:11:12Z",
		"new_customer": "true"
	},
	"connector_metadata": null,
	"feature_metadata": {
		"redirect_response": null,
		"search_tags": null,
		"apple_pay_recurring_details": null,
		"gateway_system": "direct"
	},
	"reference_id": null,
	"payment_link": null,
	"profile_id": "pro_1bgv1EqLZpII2LhrqZ6v",
	"surcharge_details": null,
	"attempt_count": 1,
	"merchant_decision": null,
	"merchant_connector_id": "mca_5JI6HUfzajWqeytwQWPc",
	"incremental_authorization_allowed": false,
	"authorization_count": null,
	"incremental_authorizations": null,
	"external_authentication_details": null,
	"external_3ds_authentication_attempted": false,
	"expires_on": "2025-12-08T11:47:57.896Z",
	"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_channel": null,
	"payment_method_id": null,
	"network_transaction_id": null,
	"payment_method_status": null,
	"updated": "2025-12-08T11:32:59.498Z",
	"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
}
ApplePay -- Predecrypt
curl --location 'http://localhost:8080/payments/pay_ndmQWvahx8Qe4z8sQ2tJ/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_STjgg0pk4mC7zRANJ022bUG5RLcPb0a6Q72g2glOZyhYu4L0n3pUPl3aUwMqAjcQ' \
--data '{
	"confirm": true,
	"payment_method": "wallet",
	"payment_method_type": "apple_pay",
	"payment_method_data": {
		"wallet": {
			"apple_pay": {
				"payment_data": {
					
					"application_primary_account_number": "5204245253050839",
					"application_expiration_month": "01",
					
					"application_expiration_year": "30",
					"payment_data": {
						"online_payment_cryptogram": "A/...AA=",
						"eci_indicator": "1"
					}
				},
			"payment_method": {
					"display_name": "Visa 4228",
					
					"network": "Mastercard",
					"type": "debit"
				},
				"transaction_identifier": "da...2f"
			}
		},
		"billing": null
	},
	"browser_info": {
		"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
		"accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
		"language": "nl-NL",
		"color_depth": 24,
		"screen_height": 723,
		"screen_width": 1536,
		"time_zone": 0,
		"java_enabled": true,
		"java_script_enabled": true,
		"ip_address": "127.0.0.1"
	},
	"billing": {
		"address": {
			"line1": "1467",
			"line2": "Harrison Street",
			"line3": "Harrison Street",
			"city": "San Fransico",
			"state": "California",
			"zip": "94122",
			"country": "US",
			"first_name": "John",
			"last_name": "Doe"
		}
	},
	"shipping": {
		"address": {
			"line1": "1467",
			"line2": "Harrison Street",
			"line3": "Harrison Street",
			"city": "San Fransico",
			"state": "California",
			"zip": "94122",
			"country": "US",
			"first_name": "John",
			"last_name": "Doe"
		}
	}
}'
{
	"payment_id": "pay_ndmQWvahx8Qe4z8sQ2tJ",
	"merchant_id": "postman_merchant_GHAction_1765211283",
	"status": "succeeded",
	"amount": 1000,
	"net_amount": 1000,
	"shipping_cost": null,
	"amount_capturable": 0,
	"amount_received": 1000,
	"connector": "paysafe",
	"client_secret": "pay_ndmQWvahx8Qe4z8sQ2tJ_secret_2xWyC4bezvWdFfs78JfF",
	"created": "2025-12-08T16:28:18.010Z",
	"modified_at": "2025-12-08T16:28:23.374Z",
	"currency": "USD",
	"customer_id": "StripeCustomer",
	"customer": {
		"id": "StripeCustomer",
		"name": "John Doe",
		"email": "likhin.bopanna@gmail.com",
		"phone": "999999999",
		"phone_country_code": "+65"
	},
	"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": "automatic",
	"payment_method": "wallet",
	"payment_method_data": {
		"wallet": {
			"apple_pay": {
				"last4": "4228",
				"card_network": "Mastercard",
				"type": "debit",
				"card_exp_month": "01",
				"card_exp_year": "30"
			}
		},
		"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": "John",
			"last_name": "Doe",
			"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": "John",
			"last_name": "Doe",
			"origin_zip": null
		},
		"phone": null,
		"email": null
	},
	"order_details": null,
	"email": "likhin.bopanna@gmail.com",
	"name": "John Doe",
	"phone": "999999999",
	"return_url": "https://duck.com/",
	"authentication_type": "three_ds",
	"statement_descriptor_name": "joseph",
	"statement_descriptor_suffix": "JS",
	"next_action": null,
	"cancellation_reason": null,
	"error_code": null,
	"error_message": null,
	"unified_code": null,
	"unified_message": null,
	"payment_experience": null,
	"payment_method_type": "apple_pay",
	"connector_label": "paysafe_US_default",
	"business_country": "US",
	"business_label": "default",
	"business_sub_label": null,
	"allowed_payment_method_types": null,
	"ephemeral_key": null,
	"manual_retry_allowed": null,
	"connector_transaction_id": "a0813723-4573-461d-81a3-9d961246f9d7",
	"frm_message": null,
	"metadata": {
		"udf1": "value1",
		"login_date": "2019-09-10T10:11:12Z",
		"new_customer": "true"
	},
	"connector_metadata": null,
	"feature_metadata": {
		"redirect_response": null,
		"search_tags": null,
		"apple_pay_recurring_details": null,
		"gateway_system": "direct"
	},
	"reference_id": null,
	"payment_link": null,
	"profile_id": "pro_T9unWs1Yhy7L9ySqL9mT",
	"surcharge_details": null,
	"attempt_count": 1,
	"merchant_decision": null,
	"merchant_connector_id": "mca_czCRiYiJLertOMihXZJM",
	"incremental_authorization_allowed": false,
	"authorization_count": null,
	"incremental_authorizations": null,
	"external_authentication_details": null,
	"external_3ds_authentication_attempted": false,
	"expires_on": "2025-12-08T16:43:18.010Z",
	"fingerprint": null,
	"browser_info": {
		"os_type": null,
		"referer": null,
		"language": "nl-NL",
		"time_zone": 0,
		"ip_address": "127.0.0.1",
		"os_version": null,
		"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
		"color_depth": 24,
		"device_model": null,
		"java_enabled": true,
		"screen_width": 1536,
		"accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
		"screen_height": 723,
		"accept_language": "en",
		"java_script_enabled": true
	},
	"payment_channel": null,
	"payment_method_id": null,
	"network_transaction_id": null,
	"payment_method_status": null,
	"updated": "2025-12-08T16:28:23.374Z",
	"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": null,
	"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
}
ApplePay -- Hyperswitch Decryption
curl --location 'http://localhost:8080/payments/pay_MLJwKrnIOAvjENNCvWWN/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_STjgg0pk4mC7zRANJ022bUG5RLcPb0a6Q72g2glOZyhYu4L0n3pUPl3aUwMqAjcQ' \
--data '{
	"confirm": true,
	"payment_method": "wallet",
	"payment_method_type": "apple_pay",
	"payment_method_data": {
		"wallet": {
			"apple_pay": {
				"payment_data": "eyJk...0=",
				"payment_method": {
					"display_name": "Visa 4228",
					"network": "Visa",
					"type": "debit"
				},
				"transaction_identifier": "da...2f"
			}
		},
		"billing": null
	},
	"browser_info": {
		"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
		"accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
		"language": "nl-NL",
		"color_depth": 24,
		"screen_height": 723,
		"screen_width": 1536,
		"time_zone": 0,
		"java_enabled": true,
		"java_script_enabled": true,
		"ip_address": "127.0.0.1"
	},
	"billing": {
		"address": {
			"line1": "1467",
			"line2": "Harrison Street",
			"line3": "Harrison Street",
			"city": "San Fransico",
			"state": "California",
			"zip": "94122",
			"country": "US",
			"first_name": "John",
			"last_name": "Doe"
		}
	},
	"shipping": {
		"address": {
			"line1": "1467",
			"line2": "Harrison Street",
			"line3": "Harrison Street",
			"city": "San Fransico",
			"state": "California",
			"zip": "94122",
			"country": "US",
			"first_name": "John",
			"last_name": "Doe"
		}
	}
}'
{
	"payment_id": "pay_MLJwKrnIOAvjENNCvWWN",
	"merchant_id": "postman_merchant_GHAction_1765211283",
	"status": "succeeded",
	"amount": 1000,
	"net_amount": 1000,
	"shipping_cost": null,
	"amount_capturable": 0,
	"amount_received": 1000,
	"connector": "paysafe",
	"client_secret": "pay_MLJwKrnIOAvjENNCvWWN_secret_plfLi20sU6m92EhAGz78",
	"created": "2025-12-08T16:29:58.814Z",
	"modified_at": "2025-12-08T16:30:02.799Z",
	"currency": "USD",
	"customer_id": "StripeCustomer",
	"customer": {
		"id": "StripeCustomer",
		"name": "John Doe",
		"email": "likhin.bopanna@gmail.com",
		"phone": "999999999",
		"phone_country_code": "+65"
	},
	"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": "automatic",
	"payment_method": "wallet",
	"payment_method_data": {
		"wallet": {
			"apple_pay": {
				"last4": "4228",
				"card_network": "Visa",
				"type": "debit",
				"card_exp_month": "01",
				"card_exp_year": "2030"
			}
		},
		"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": "John",
			"last_name": "Doe",
			"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": "John",
			"last_name": "Doe",
			"origin_zip": null
		},
		"phone": null,
		"email": null
	},
	"order_details": null,
	"email": "likhin.bopanna@gmail.com",
	"name": "John Doe",
	"phone": "999999999",
	"return_url": "https://duck.com/",
	"authentication_type": "three_ds",
	"statement_descriptor_name": "joseph",
	"statement_descriptor_suffix": "JS",
	"next_action": null,
	"cancellation_reason": null,
	"error_code": null,
	"error_message": null,
	"unified_code": null,
	"unified_message": null,
	"payment_experience": null,
	"payment_method_type": "apple_pay",
	"connector_label": "paysafe_US_default",
	"business_country": "US",
	"business_label": "default",
	"business_sub_label": null,
	"allowed_payment_method_types": null,
	"ephemeral_key": null,
	"manual_retry_allowed": null,
	"connector_transaction_id": "f773089f-722b-49fd-b4db-1396acef0645",
	"frm_message": null,
	"metadata": {
		"udf1": "value1",
		"login_date": "2019-09-10T10:11:12Z",
		"new_customer": "true"
	},
	"connector_metadata": null,
	"feature_metadata": {
		"redirect_response": null,
		"search_tags": null,
		"apple_pay_recurring_details": null,
		"gateway_system": "direct"
	},
	"reference_id": null,
	"payment_link": null,
	"profile_id": "pro_T9unWs1Yhy7L9ySqL9mT",
	"surcharge_details": null,
	"attempt_count": 1,
	"merchant_decision": null,
	"merchant_connector_id": "mca_czCRiYiJLertOMihXZJM",
	"incremental_authorization_allowed": false,
	"authorization_count": null,
	"incremental_authorizations": null,
	"external_authentication_details": null,
	"external_3ds_authentication_attempted": false,
	"expires_on": "2025-12-08T16:44:58.814Z",
	"fingerprint": null,
	"browser_info": {
		"os_type": null,
		"referer": null,
		"language": "nl-NL",
		"time_zone": 0,
		"ip_address": "127.0.0.1",
		"os_version": null,
		"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
		"color_depth": 24,
		"device_model": null,
		"java_enabled": true,
		"screen_width": 1536,
		"accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
		"screen_height": 723,
		"accept_language": "en",
		"java_script_enabled": true
	},
	"payment_channel": null,
	"payment_method_id": null,
	"network_transaction_id": null,
	"payment_method_status": null,
	"updated": "2025-12-08T16:30:02.799Z",
	"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": null,
	"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
}
cypress image

three 3ds transactions do not return redirect url. request has been raised to paysafe team. the same is reproducible on sandbox too.

it might be because, the card number that the documentation points to is 10/25 which is expired and it has never been updated since then.

Checklist

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

@semanticdiff-com
Copy link

semanticdiff-com bot commented Dec 4, 2025

@codecov
Copy link

codecov bot commented Dec 4, 2025

Codecov Report

❌ Patch coverage is 0.47847% with 416 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@a738f7e). Learn more about missing BASE report.

Files with missing lines Patch % Lines
..._connectors/src/connectors/paysafe/transformers.rs 0.00% 228 Missing ⚠️
...s/hyperswitch_connectors/src/connectors/paysafe.rs 0.00% 168 Missing ⚠️
crates/hyperswitch_connectors/src/utils.rs 0.00% 9 Missing ⚠️
...erswitch_domain_models/src/router_request_types.rs 25.00% 6 Missing ⚠️
...src/core/unified_connector_service/transformers.rs 0.00% 3 Missing ⚠️
crates/router/src/core/payment_methods.rs 0.00% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #10541   +/-   ##
=======================================
  Coverage        ?    6.42%           
=======================================
  Files           ?     1262           
  Lines           ?   315994           
  Branches        ?        0           
=======================================
  Hits            ?    20292           
  Misses          ?   295702           
  Partials        ?        0           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pixincreate pixincreate changed the title refactor: replace preprocessing flow for paymentmethodtoken flow refactor(connector): [paysafe] replace preprocessing flow for paymentmethodtoken flow Dec 4, 2025
@pixincreate pixincreate changed the title refactor(connector): [paysafe] replace preprocessing flow for paymentmethodtoken flow refactor(connector): [paysafe] replace PreProcessing flow for PaymentMethodToken flow Dec 4, 2025
@pixincreate pixincreate self-assigned this Dec 8, 2025
@pixincreate pixincreate added A-connector-integration Area: Connector integration S-waiting-on-review Status: This PR has been implemented and needs to be reviewed labels Dec 8, 2025
@pixincreate pixincreate changed the title refactor(connector): [paysafe] replace PreProcessing flow for PaymentMethodToken flow refactor(connector): [paysafe] introduce PaymentMethodToken flow Dec 8, 2025
@pixincreate pixincreate force-pushed the connector/paysafe-flow-correction branch from f4b29e3 to 6a09ca3 Compare December 11, 2025 09:37
@pixincreate pixincreate marked this pull request as ready for review December 11, 2025 10:47
Copilot AI review requested due to automatic review settings December 11, 2025 10:47
@pixincreate pixincreate requested review from a team as code owners December 11, 2025 10:47
Copy link
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 refactors the Paysafe connector integration to introduce the PaymentMethodToken flow as a replacement for the deprecated preProcessing flow. However, the implementation is intentionally incomplete to avoid breaking existing 3DS transactions until the upstream unified connector service (UCS) supports granular flow control.

Key changes include:

  • Added router_return_url and capture_method fields to PaymentMethodTokenizationData struct
  • Implemented new PaymentMethodToken flow alongside existing PreProcessing flow for Paysafe
  • Updated UCS dependency version from f43fbfdb to e2cb5b7004

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/router/Cargo.toml Updated UCS client and cards dependency versions
crates/hyperswitch_interfaces/Cargo.toml Updated UCS client dependency version
crates/external_services/Cargo.toml Updated UCS client dependency version
Cargo.lock Updated dependency lock hashes for UCS packages
crates/hyperswitch_domain_models/src/router_request_types.rs Added router_return_url and capture_method fields to PaymentMethodTokenizationData struct and updated TryFrom implementations
crates/router/tests/connectors/utils.rs Initialized new fields in test data structures
crates/router/tests/connectors/stax.rs Initialized new fields in multiple test cases
crates/router/tests/connectors/square.rs Initialized new fields in multiple test cases
crates/router/src/core/unified_connector_service/transformers.rs Added new fields to various request structures and refactored metadata conversion to use helper function
crates/router/src/core/payments/flows/setup_mandate_flow.rs Added Box::pin wrapper to payment_setup_mandate call
crates/router/src/core/payment_methods.rs Initialized new fields in tokenization flow creation
crates/hyperswitch_connectors/src/utils.rs Added get_router_return_url and is_customer_initiated_mandate_payment trait methods
crates/hyperswitch_connectors/src/connectors/paysafe.rs Implemented PaymentMethodToken flow ConnectorIntegration and moved PreProcessing flow to maintain both implementations
crates/hyperswitch_connectors/src/connectors/paysafe/transformers.rs Added TryFrom implementations for TokenizationRouterData, refactored Apple Pay data handling, and updated mandate data retrieval logic
Comments suppressed due to low confidence (1)

crates/hyperswitch_connectors/src/connectors/paysafe/transformers.rs:1381

  • There is significant code duplication in the TryFrom implementations for PaysafeApplepayPayment. The implementations for PaymentsPreProcessingRouterData (lines 1175-1280) and TokenizationRouterData (lines 1282-1381) are nearly identical, with only minor differences in how currency is accessed. This duplication makes the code harder to maintain. Consider extracting the common logic into a helper function or using a macro to reduce duplication.
impl
    TryFrom<(
        &ApplePayWalletData,
        &PaysafeRouterData<&PaymentsPreProcessingRouterData>,
    )> for PaysafeApplepayPayment
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (wallet_data, item): (
            &ApplePayWalletData,
            &PaysafeRouterData<&PaymentsPreProcessingRouterData>,
        ),
    ) -> Result<Self, Self::Error> {
        let apple_pay_payment_token = PaysafeApplePayPaymentToken {
            token: PaysafeApplePayToken {
                payment_data: if let Ok(PaymentMethodToken::ApplePayDecrypt(ref token)) =
                    item.router_data.get_payment_method_token()
                {
                    PaysafeApplePayPaymentData::Decrypted(PaysafeApplePayDecryptedDataWrapper {
                        decrypted_data: get_apple_pay_decrypt_data(
                            token,
                            item.router_data
                                .request
                                .currency
                                .ok_or_else(missing_field_err("currency"))?,
                            item.amount,
                        )?,
                    })
                } else {
                    match &wallet_data.payment_data {
                        ApplePayPaymentData::Decrypted(applepay_predecrypt_data) => {
                            PaysafeApplePayPaymentData::Decrypted(
                                PaysafeApplePayDecryptedDataWrapper {
                                    decrypted_data: get_apple_pay_decrypt_data(
                                        applepay_predecrypt_data,
                                        item.router_data
                                            .request
                                            .currency
                                            .ok_or_else(missing_field_err("currency"))?,
                                        item.amount,
                                    )?,
                                },
                            )
                        }
                        ApplePayPaymentData::Encrypted(applepay_encrypt_data) => {
                            let decoded_data = base64::prelude::BASE64_STANDARD
                                .decode(applepay_encrypt_data)
                                .change_context(errors::ConnectorError::InvalidDataFormat {
                                    field_name: "apple_pay_encrypted_data",
                                })?;

                            let apple_pay_token: DecryptedApplePayTokenData =
                                serde_json::from_slice(&decoded_data).change_context(
                                    errors::ConnectorError::InvalidDataFormat {
                                        field_name: "apple_pay_token_json",
                                    },
                                )?;

                            PaysafeApplePayPaymentData::Encrypted(PaysafeApplePayEncryptedData {
                                data: apple_pay_token.data,
                                signature: apple_pay_token.signature,
                                header: PaysafeApplePayHeader {
                                    public_key_hash: apple_pay_token.header.public_key_hash,
                                    ephemeral_public_key: apple_pay_token
                                        .header
                                        .ephemeral_public_key,
                                    transaction_id: apple_pay_token.header.transaction_id,
                                },
                                version: apple_pay_token.version,
                            })
                        }
                    }
                },
                payment_method: PaysafeApplePayPaymentMethod {
                    display_name: Secret::new(wallet_data.payment_method.display_name.clone()),
                    network: Secret::new(wallet_data.payment_method.network.clone()),
                    method_type: Secret::new(wallet_data.payment_method.pm_type.clone()),
                },
                transaction_identifier: wallet_data.transaction_identifier.clone(),
            },
            billing_contact: Some(PaysafeApplePayBillingContact {
                address_lines: vec![
                    item.router_data.get_optional_billing_line1(),
                    item.router_data.get_optional_billing_line2(),
                ],
                postal_code: item.router_data.get_billing_zip()?,
                country_code: item.router_data.get_billing_country()?,
                country: None,
                family_name: None,
                given_name: None,
                locality: None,
                phonetic_family_name: None,
                phonetic_given_name: None,
                sub_administrative_area: None,
                administrative_area: None,
                sub_locality: None,
            }),
        };

        Ok(Self {
            label: None,
            request_billing_address: Some(false),
            apple_pay_payment_token,
        })
    }
}

impl
    TryFrom<(
        &ApplePayWalletData,
        &PaysafeRouterData<&TokenizationRouterData>,
    )> for PaysafeApplepayPayment
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (wallet_data, item): (
            &ApplePayWalletData,
            &PaysafeRouterData<&TokenizationRouterData>,
        ),
    ) -> Result<Self, Self::Error> {
        let apple_pay_payment_token = PaysafeApplePayPaymentToken {
            token: PaysafeApplePayToken {
                payment_data: if let Ok(PaymentMethodToken::ApplePayDecrypt(ref token)) =
                    item.router_data.get_payment_method_token()
                {
                    PaysafeApplePayPaymentData::Decrypted(PaysafeApplePayDecryptedDataWrapper {
                        decrypted_data: get_apple_pay_decrypt_data(
                            token,
                            item.router_data.request.currency,
                            item.amount,
                        )?,
                    })
                } else {
                    match &wallet_data.payment_data {
                        ApplePayPaymentData::Decrypted(applepay_predecrypt_data) => {
                            PaysafeApplePayPaymentData::Decrypted(
                                PaysafeApplePayDecryptedDataWrapper {
                                    decrypted_data: get_apple_pay_decrypt_data(
                                        applepay_predecrypt_data,
                                        item.router_data.request.currency,
                                        item.amount,
                                    )?,
                                },
                            )
                        }
                        ApplePayPaymentData::Encrypted(applepay_encrypt_data) => {
                            let decoded_data = base64::prelude::BASE64_STANDARD
                                .decode(applepay_encrypt_data)
                                .change_context(errors::ConnectorError::InvalidDataFormat {
                                    field_name: "apple_pay_encrypted_data",
                                })?;

                            let apple_pay_token: DecryptedApplePayTokenData =
                                serde_json::from_slice(&decoded_data).change_context(
                                    errors::ConnectorError::InvalidDataFormat {
                                        field_name: "apple_pay_token_json",
                                    },
                                )?;

                            PaysafeApplePayPaymentData::Encrypted(PaysafeApplePayEncryptedData {
                                data: apple_pay_token.data,
                                signature: apple_pay_token.signature,
                                header: PaysafeApplePayHeader {
                                    public_key_hash: apple_pay_token.header.public_key_hash,
                                    ephemeral_public_key: apple_pay_token
                                        .header
                                        .ephemeral_public_key,
                                    transaction_id: apple_pay_token.header.transaction_id,
                                },
                                version: apple_pay_token.version,
                            })
                        }
                    }
                },
                payment_method: PaysafeApplePayPaymentMethod {
                    display_name: Secret::new(wallet_data.payment_method.display_name.clone()),
                    network: Secret::new(wallet_data.payment_method.network.clone()),
                    method_type: Secret::new(wallet_data.payment_method.pm_type.clone()),
                },
                transaction_identifier: wallet_data.transaction_identifier.clone(),
            },
            billing_contact: Some(PaysafeApplePayBillingContact {
                address_lines: vec![
                    item.router_data.get_optional_billing_line1(),
                    item.router_data.get_optional_billing_line2(),
                ],
                postal_code: item.router_data.get_billing_zip()?,
                country_code: item.router_data.get_billing_country()?,
                country: None,
                family_name: None,
                given_name: None,
                locality: None,
                phonetic_family_name: None,
                phonetic_given_name: None,
                sub_administrative_area: None,
                administrative_area: None,
                sub_locality: None,
            }),
        };

        Ok(Self {
            label: None,
            request_billing_address: Some(false),
            apple_pay_payment_token,
        })
    }
}

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

}
}

impl TryFrom<&PaysafeRouterData<&TokenizationRouterData>> for PaysafePaymentHandleRequest {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you try to reuse existing transformers logic that is already available for PreProcessing.

Copy link
Member Author

Choose a reason for hiding this comment

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

hrithikesh026
hrithikesh026 previously approved these changes Dec 15, 2025
…aysafe-flow-correction

* 'main' of github.com:juspay/hyperswitch: (47 commits)
  fix: implement is_pre_authentication_flow_required function for nmi (#10692)
  chore(version): 2025.12.17.0
  fix: Corrected the mapping of metadata fields in HS<>UCS tunnel (#10626)
  fix(users): Check if the inviter role info entity type is greater than invitee (#10667)
  feat(payouts): [WorldpayWPG] Implement fast access feature of worldpaywpg for payouts (#10647)
  fix(oidc): registration of oidc paths (#10678)
  feat(connector): [Peach Payments] Add Pre-Auth Flow With Full Reversal (#10590)
  fix(payouts): add fallback for names for payout via Psp token (#10502)
  feat(euclid): support for issuer_country based routing (#10638)
  feat: implement granular authentication flow for UCS gateway interfaces (#10622)
  feat(euclid): support for transaction_initiator based routing (#10658)
  feat(gsm): add standardised error fields to GSM models and database schema (#10600)
  feat(payments): change lookup for find gsm code and message (#10585)
  feat: Added Paypal Post Auth Flow for HS<>UCS tunnel (#10640)
  feat: Added PreAuthenticate Flow for Nmi HS<>UCS tunnel (#10632)
  chore(version): 2025.12.16.0
  feat: implement generic Locker api handler (#10242)
  fix(user-role): add entity type validation in user role lineage queries (#10608)
  ci: add API and database Schema validation workflow (#9710)
  feat(oidc): Add OIDC infrastructure and discovery endpoints (#10145)
  ...
Copy link
Contributor

@Sakilmostak Sakilmostak left a comment

Choose a reason for hiding this comment

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

looks good at payment-methods

@likhinbopanna likhinbopanna added this pull request to the merge queue Dec 18, 2025
@pixincreate pixincreate removed the S-waiting-on-review Status: This PR has been implemented and needs to be reviewed label Dec 18, 2025
Merged via the queue into main with commit 0dd61de Dec 18, 2025
30 of 31 checks passed
@likhinbopanna likhinbopanna deleted the connector/paysafe-flow-correction branch December 18, 2025 17:33
pixincreate added a commit that referenced this pull request Dec 22, 2025
…pm-metadata

* 'main' of github.com:juspay/hyperswitch:
  chore(version): 2025.12.22.0
  feat(braintree): add UCS wallet support for PaypalSdk, ApplePayThirdPartySdk, and GooglePayThirdPartySdk (#10513)
  Fix: WorldpayVantiv Cypress fix (#10656)
  feat(connector): Add Apple Pay HS-Decryption support for Braintree (#10734)
  ci(cypress): add bank redirect flow onlinebankingfpx for fiuu (#10642)
  chore(version): 2025.12.19.0
  refactor(connector): [paysafe] introduce `PaymentMethodToken` flow (#10541)
  fix(connectors): add 3ds validations for connector for card specific only (#10560)
  fix(router): Prevent panic when masking non-ASCII strings (#10682)
  feat(authentication): add domain models for authentication and support kafka filters in dashboard (#10446)
  fix(docker): increase RUST_MIN_STACK size to handle stack overflow (#10730)
  fix(api): align ApiEvent status_code with HTTP response when proxy_connector_http_status_code enabled (#10680)
  feat(core): [Network Token] Passing Network Token in payments request (#9975)
  feat(core): Bumped UCS Client dependency to bring latest changes (#10641)
  ci(cypress): Update cypress shadow mode rollout configs (#10689)
pixincreate added a commit that referenced this pull request Dec 22, 2025
…rmers

* 'main' of github.com:juspay/hyperswitch: (67 commits)
  refactor: Introduce PreAuth for redsys HS<>UCS tunnel (#10727)
  chore(version): 2025.12.22.0
  feat(braintree): add UCS wallet support for PaypalSdk, ApplePayThirdPartySdk, and GooglePayThirdPartySdk (#10513)
  Fix: WorldpayVantiv Cypress fix (#10656)
  feat(connector): Add Apple Pay HS-Decryption support for Braintree (#10734)
  ci(cypress): add bank redirect flow onlinebankingfpx for fiuu (#10642)
  chore(version): 2025.12.19.0
  refactor(connector): [paysafe] introduce `PaymentMethodToken` flow (#10541)
  fix(connectors): add 3ds validations for connector for card specific only (#10560)
  fix(router): Prevent panic when masking non-ASCII strings (#10682)
  feat(authentication): add domain models for authentication and support kafka filters in dashboard (#10446)
  fix(docker): increase RUST_MIN_STACK size to handle stack overflow (#10730)
  fix(api): align ApiEvent status_code with HTTP response when proxy_connector_http_status_code enabled (#10680)
  feat(core): [Network Token] Passing Network Token in payments request (#9975)
  feat(core): Bumped UCS Client dependency to bring latest changes (#10641)
  ci(cypress): Update cypress shadow mode rollout configs (#10689)
  chore(version): 2025.12.18.0
  feat(payment-methods): Add support for guest checkout flow in payment method service (#10487)
  feat(connector): [NMI] Implement Apple Pay - hyperswitch decryption flow (#10686)
  fix(connector): [bluesnap] pass `connector_request_ref_id` instead of `payment_id` (#10653)
  ...
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants