From 88aaa4f52bcd08153be3cc944a40df47bb0ac44f Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 26 Sep 2024 16:17:59 +0530 Subject: [PATCH 01/28] feat(worldpay): migrate to v7 --- .../connector_configs/toml/development.toml | 3 +- crates/connector_configs/toml/production.toml | 3 +- crates/connector_configs/toml/sandbox.toml | 3 +- crates/router/src/connector/worldpay.rs | 77 ++++--- .../router/src/connector/worldpay/requests.rs | 54 ++--- .../router/src/connector/worldpay/response.rs | 196 +++++++----------- .../src/connector/worldpay/transformers.rs | 77 +++++-- .../router/tests/connectors/sample_auth.toml | 1 + crates/test_utils/tests/sample_auth.toml | 1 + 9 files changed, 225 insertions(+), 190 deletions(-) diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 007715a92e58..699a414e3f73 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -3384,9 +3384,10 @@ merchant_secret="Source verification key" payment_method_type = "google_pay" [[worldpay.wallet]] payment_method_type = "apple_pay" -[worldpay.connector_auth.BodyKey] +[worldpay.connector_auth.SignatureKey] api_key="Username" key1="Password" +api_secret="Merchant Identifier" [worldpay.connector_webhook_details] merchant_secret="Source verification key" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 5d4e55a361e3..ade28d356994 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -2432,9 +2432,10 @@ merchant_secret="Source verification key" payment_method_type = "google_pay" [[worldpay.wallet]] payment_method_type = "apple_pay" -[worldpay.connector_auth.BodyKey] +[worldpay.connector_auth.SignatureKey] api_key="Username" key1="Password" +api_secret="Merchant Identifier" [[worldpay.metadata.apple_pay]] name="certificate" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index d260970a57fe..be837a9fb515 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -3374,9 +3374,10 @@ merchant_secret="Source verification key" payment_method_type = "google_pay" [[worldpay.wallet]] payment_method_type = "apple_pay" -[worldpay.connector_auth.BodyKey] +[worldpay.connector_auth.SignatureKey] api_key="Username" key1="Password" +api_secret="Merchant Identifier" [worldpay.connector_webhook_details] merchant_secret="Source verification key" diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index 33c9393cede3..dadf12e0e914 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -42,10 +42,16 @@ where req: &types::RouterData, _connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let mut headers = vec![( - headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), - )]; + let mut headers = vec![ + ( + headers::ACCEPT.to_string(), + self.get_content_type().to_string().into(), + ), + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; headers.append(&mut api_key); Ok(headers) @@ -62,7 +68,7 @@ impl ConnectorCommon for Worldpay { } fn common_get_content_type(&self) -> &'static str { - "application/vnd.worldpay.payments-v6+json" + "application/vnd.worldpay.payments-v7+json" } fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { @@ -86,10 +92,14 @@ impl ConnectorCommon for Worldpay { res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - let response: WorldpayErrorResponse = res - .response - .parse_struct("WorldpayErrorResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + router_env::logger::info!(raw_response=?res); + let response = if res.status_code != 404 { + res.response + .parse_struct("WorldpayErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)? + } else { + WorldpayErrorResponse::default() + }; event_builder.map(|i| i.set_error_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -183,7 +193,7 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( - "{}payments/settlements/{}", + "{}payments/authorizations/cancellations/{}", self.base_url(connectors), connector_payment_id )) @@ -361,6 +371,28 @@ impl ConnectorIntegration CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}payments/settlements/partials/{}", + self.base_url(connectors), + connector_payment_id + )) + } + + fn get_request_body( + &self, + req: &types::PaymentsCaptureRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = WorldpayPartialRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( &self, req: &types::PaymentsCaptureRouterData, @@ -374,6 +406,9 @@ impl ConnectorIntegration CustomResult { - let connector_payment_id = req.request.connector_transaction_id.clone(); - Ok(format!( - "{}payments/settlements/{}", - self.base_url(connectors), - connector_payment_id - )) - } - fn get_error_response( &self, res: Response, @@ -463,7 +485,7 @@ impl ConnectorIntegration CustomResult { Ok(format!( - "{}payments/authorizations", + "{}cardPayments/customerInitiatedTransactions", self.base_url(connectors) )) } @@ -479,7 +501,10 @@ impl ConnectorIntegration CustomResult { - let connector_req = WorldpayRefundRequest::try_from(req)?; + let connector_req = WorldpayPartialRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } diff --git a/crates/router/src/connector/worldpay/requests.rs b/crates/router/src/connector/worldpay/requests.rs index 5fb5d75d940b..8b72a8dfd8f5 100644 --- a/crates/router/src/connector/worldpay/requests.rs +++ b/crates/router/src/connector/worldpay/requests.rs @@ -4,29 +4,28 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "camelCase")] pub struct BillingAddress { #[serde(skip_serializing_if = "Option::is_none")] - pub city: Option, + pub address1: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub address2: Option>, - pub postal_code: Secret, - #[serde(skip_serializing_if = "Option::is_none")] - pub state: Option, #[serde(skip_serializing_if = "Option::is_none")] pub address3: Option>, - pub country_code: common_enums::CountryAlpha2, #[serde(skip_serializing_if = "Option::is_none")] - pub address1: Option>, + pub city: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub state: Option, + pub postal_code: Secret, + pub country_code: common_enums::CountryAlpha2, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct WorldpayPaymentsRequest { - #[serde(skip_serializing_if = "Option::is_none")] - pub channel: Option, + pub transaction_reference: String, + pub merchant: Merchant, pub instruction: Instruction, + pub channel: Channel, #[serde(skip_serializing_if = "Option::is_none")] pub customer: Option, - pub merchant: Merchant, - pub transaction_reference: String, } #[derive( @@ -35,6 +34,7 @@ pub struct WorldpayPaymentsRequest { #[serde(rename_all = "camelCase")] pub enum Channel { #[default] + Ecom, Moto, } @@ -101,11 +101,18 @@ pub struct NetworkToken { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Instruction { - #[serde(skip_serializing_if = "Option::is_none")] - pub debt_repayment: Option, - pub value: PaymentValue, + pub request_auto_settlement: RequestAutoSettlement, pub narrative: InstructionNarrative, + pub value: PaymentValue, pub payment_instrument: PaymentInstrument, + #[serde(skip_serializing_if = "Option::is_none")] + pub debt_repayment: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RequestAutoSettlement { + pub enabled: bool, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] @@ -143,16 +150,15 @@ pub enum PaymentType { #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CardPayment { - #[serde(skip_serializing_if = "Option::is_none")] - pub billing_address: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub card_holder_name: Option>, - pub card_expiry_date: CardExpiryDate, - #[serde(skip_serializing_if = "Option::is_none")] - pub cvc: Option>, #[serde(rename = "type")] pub payment_type: PaymentType, pub card_number: cards::CardNumber, + pub expiry_date: ExpiryDate, + #[serde(skip_serializing_if = "Option::is_none")] + pub card_holder_name: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub billing_address: Option, + pub cvc: Secret, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] @@ -174,7 +180,7 @@ pub struct WalletPayment { } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct CardExpiryDate { +pub struct ExpiryDate { pub month: Secret, pub year: Secret, } @@ -188,7 +194,7 @@ pub struct PaymentValue { #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Merchant { - pub entity: String, + pub entity: Secret, #[serde(skip_serializing_if = "Option::is_none")] pub mcc: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -220,7 +226,7 @@ pub struct SubMerchant { } #[derive(Default, Debug, Serialize)] -pub struct WorldpayRefundRequest { +pub struct WorldpayPartialRequest { pub value: PaymentValue, pub reference: String, } diff --git a/crates/router/src/connector/worldpay/response.rs b/crates/router/src/connector/worldpay/response.rs index 14ccbc1fac87..053026df100e 100644 --- a/crates/router/src/connector/worldpay/response.rs +++ b/crates/router/src/connector/worldpay/response.rs @@ -1,33 +1,42 @@ use masking::Secret; use serde::{Deserialize, Serialize}; +use super::requests::*; + use crate::{core::errors, types, types::transformers::ForeignTryFrom}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WorldpayPaymentsResponse { + pub outcome: Option, + /// Any risk factors which have been identified for the authorization. This section will not appear if no risks are identified. #[serde(skip_serializing_if = "Option::is_none")] - pub exemption: Option, + pub risk_factors: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub issuer: Option, - pub outcome: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, + pub scheme: Option, #[serde(skip_serializing_if = "Option::is_none")] pub payment_instrument: Option, - /// Any risk factors which have been identified for the authorization. This section will not appear if no risks are identified. - #[serde(skip_serializing_if = "Option::is_none")] - pub risk_factors: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub scheme: Option, #[serde(rename = "_links", skip_serializing_if = "Option::is_none")] pub links: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub enum Outcome { +pub enum PaymentOutcome { + #[serde(alias = "authorized", alias = "Authorized")] Authorized, Refused, + #[serde(alias = "Sent for Settlement")] + SentForSettlement, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum RefundOutcome { + #[serde(alias = "Sent for Refund")] + SentForRefund, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -41,14 +50,17 @@ pub struct WorldpayEventResponse { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum EventType { + #[serde(alias = "Authorized", alias = "authorized")] Authorized, Cancelled, Charged, + #[serde(rename = "Sent for Refund")] SentForRefund, RefundFailed, Refused, Refunded, Error, + #[serde(rename = "Sent for Settlement")] SentForSettlement, Expired, CaptureFailed, @@ -56,16 +68,38 @@ pub enum EventType { Unknown, } -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct Exemption { - pub result: String, - pub reason: String, -} - #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct PaymentLinks { - #[serde(rename = "payments:events", skip_serializing_if = "Option::is_none")] + #[serde( + rename = "cardPayments:events", + skip_serializing_if = "Option::is_none" + )] pub events: Option, + #[serde( + rename = "cardPayments:settle", + skip_serializing_if = "Option::is_none" + )] + pub settle_event: Option, + #[serde( + rename = "cardPayments:partialSettle", + skip_serializing_if = "Option::is_none" + )] + pub partial_settle_event: Option, + #[serde( + rename = "cardPayments:refund", + skip_serializing_if = "Option::is_none" + )] + pub refund_event: Option, + #[serde( + rename = "cardPayments:partialRefund", + skip_serializing_if = "Option::is_none" + )] + pub partial_refund_event: Option, + #[serde( + rename = "cardPayments:reverse", + skip_serializing_if = "Option::is_none" + )] + pub reverse_event: Option, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] @@ -116,12 +150,6 @@ impl ForeignTryFrom> for types::ResponseId { } } -impl Exemption { - pub fn new(result: String, reason: String) -> Self { - Self { result, reason } - } -} - #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Issuer { @@ -137,103 +165,32 @@ impl Issuer { } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct PaymentsResPaymentInstrument { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub risk_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub card: Option, -} - -impl PaymentsResPaymentInstrument { - pub fn new() -> Self { - Self { - risk_type: None, - card: None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PaymentInstrumentCard { - #[serde(skip_serializing_if = "Option::is_none")] - pub number: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub issuer: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub payment_account_reference: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub country_code: Option, - #[serde(skip_serializing_if = "Option::is_none")] + pub payment_instrument_type: Option, + pub card_bin: Option, + pub last_four: Option, + pub category: Option, + pub expiry_date: Option, + pub card_brand: Option, pub funding_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub brand: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub expiry_date: Option, + pub issuer_name: Option, + pub payment_account_reference: Option, } -impl PaymentInstrumentCard { +impl PaymentsResPaymentInstrument { pub fn new() -> Self { Self { - number: None, - issuer: None, - payment_account_reference: None, - country_code: None, - funding_type: None, - brand: None, + payment_instrument_type: None, + card_bin: None, + last_four: None, + category: None, expiry_date: None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PaymentInstrumentCardExpiryDate { - #[serde(skip_serializing_if = "Option::is_none")] - pub month: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub year: Option>, -} - -impl PaymentInstrumentCardExpiryDate { - pub fn new() -> Self { - Self { - month: None, - year: None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PaymentInstrumentCardIssuer { - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, -} - -impl PaymentInstrumentCardIssuer { - pub fn new() -> Self { - Self { name: None } - } -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PaymentInstrumentCardNumber { - #[serde(skip_serializing_if = "Option::is_none")] - pub bin: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub last4_digits: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub dpan: Option, -} - -impl PaymentInstrumentCardNumber { - pub fn new() -> Self { - Self { - bin: None, - last4_digits: None, - dpan: None, + card_brand: None, + funding_type: None, + issuer_name: None, + payment_account_reference: None, } } } @@ -282,15 +239,12 @@ pub enum Detail { #[derive( Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, )] +#[serde(rename_all = "camelCase")] pub enum Risk { #[default] - #[serde(rename = "not_checked")] NotChecked, - #[serde(rename = "not_matched")] NotMatched, - #[serde(rename = "not_supplied")] NotSupplied, - #[serde(rename = "verificationFailed")] VerificationFailed, } @@ -305,7 +259,7 @@ impl PaymentsResponseScheme { } } -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct WorldpayErrorResponse { pub error_name: String, @@ -313,6 +267,16 @@ pub struct WorldpayErrorResponse { pub validation_errors: Option, } +impl Default for WorldpayErrorResponse { + fn default() -> Self { + Self { + error_name: "Not found".to_string(), + message: "Resource not found".to_string(), + validation_errors: None, + } + } +} + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WorldpayWebhookTransactionId { diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index de82e11548ca..51a69a55f3a1 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -47,11 +47,12 @@ fn fetch_payment_instrument( ) -> CustomResult { match payment_method { domain::PaymentMethodData::Card(card) => Ok(PaymentInstrument::Card(CardPayment { - card_expiry_date: CardExpiryDate { + expiry_date: ExpiryDate { month: utils::CardData::get_expiry_month_as_i8(&card)?, year: utils::CardData::get_expiry_year_as_i32(&card)?, }, card_number: card.card_number, + cvc: card.card_cvc, ..CardPayment::default() })), domain::PaymentMethodData::Wallet(wallet) => match wallet { @@ -120,7 +121,7 @@ fn fetch_payment_instrument( } impl - TryFrom< + TryFrom<( &WorldpayRouterData< &types::RouterData< types::api::payments::Authorize, @@ -128,21 +129,30 @@ impl PaymentsResponseData, >, >, - > for WorldpayPaymentsRequest + &Secret, + )> for WorldpayPaymentsRequest { type Error = error_stack::Report; fn try_from( - item: &WorldpayRouterData< - &types::RouterData< - types::api::payments::Authorize, - PaymentsAuthorizeData, - PaymentsResponseData, + req: ( + &WorldpayRouterData< + &types::RouterData< + types::api::payments::Authorize, + PaymentsAuthorizeData, + PaymentsResponseData, + >, >, - >, + &Secret, + ), ) -> Result { + let (item, entity_id) = req; Ok(Self { instruction: Instruction { + request_auto_settlement: RequestAutoSettlement { + enabled: item.router_data.request.capture_method + == Some(enums::CaptureMethod::Automatic), + }, value: PaymentValue { amount: item.amount, currency: item.router_data.request.currency.to_string(), @@ -161,15 +171,11 @@ impl debt_repayment: None, }, merchant: Merchant { - entity: item - .router_data - .connector_request_reference_id - .clone() - .replace('_', "-"), + entity: entity_id.clone(), ..Default::default() }, transaction_reference: item.router_data.connector_request_reference_id.clone(), - channel: None, + channel: Channel::Ecom, customer: None, }) } @@ -177,17 +183,32 @@ impl pub struct WorldpayAuthType { pub(super) api_key: Secret, + pub(super) entity_id: Secret, } impl TryFrom<&types::ConnectorAuthType> for WorldpayAuthType { type Error = error_stack::Report; fn try_from(auth_type: &types::ConnectorAuthType) -> Result { match auth_type { + // TODO: Remove this later, kept purely for backwards compatability types::ConnectorAuthType::BodyKey { api_key, key1 } => { let auth_key = format!("{}:{}", key1.peek(), api_key.peek()); let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key)); Ok(Self { api_key: Secret::new(auth_header), + entity_id: Secret::new("default".to_string()), + }) + } + types::ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => { + let auth_key = format!("{}:{}", key1.peek(), api_key.peek()); + let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key)); + Ok(Self { + api_key: Secret::new(auth_header), + entity_id: api_secret.clone(), }) } _ => Err(errors::ConnectorError::FailedToObtainAuthType)?, @@ -195,11 +216,12 @@ impl TryFrom<&types::ConnectorAuthType> for WorldpayAuthType { } } -impl From for enums::AttemptStatus { - fn from(item: Outcome) -> Self { +impl From for enums::AttemptStatus { + fn from(item: PaymentOutcome) -> Self { match item { - Outcome::Authorized => Self::Authorized, - Outcome::Refused => Self::Failure, + PaymentOutcome::Authorized => Self::Authorized, + PaymentOutcome::Refused => Self::Failure, + PaymentOutcome::SentForSettlement => Self::CaptureInitiated, } } } @@ -271,11 +293,24 @@ impl TryFrom> } } -impl TryFrom<&types::RefundsRouterData> for WorldpayRefundRequest { +impl TryFrom<&types::PaymentsCaptureRouterData> for WorldpayPartialRequest { + type Error = error_stack::Report; + fn try_from(item: &types::PaymentsCaptureRouterData) -> Result { + Ok(Self { + reference: item.payment_id.clone().replace("_", "-"), + value: PaymentValue { + amount: item.request.amount_to_capture, + currency: item.request.currency.to_string(), + }, + }) + } +} + +impl TryFrom<&types::RefundsRouterData> for WorldpayPartialRequest { type Error = error_stack::Report; fn try_from(item: &types::RefundsRouterData) -> Result { Ok(Self { - reference: item.request.connector_transaction_id.clone(), + reference: item.request.refund_id.clone().replace("_", "-"), value: PaymentValue { amount: item.request.refund_amount, currency: item.request.currency.to_string(), diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index f61770a5bde5..2b20ed4cf167 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -30,6 +30,7 @@ api_key = "Bearer MyApiKey" [worldpay] api_key = "api_key" key1 = "key1" +api_secret = "Merchant Identifier" [payu] api_key = "Bearer MyApiKey" diff --git a/crates/test_utils/tests/sample_auth.toml b/crates/test_utils/tests/sample_auth.toml index 08b24817c24e..c7ec4f12c544 100644 --- a/crates/test_utils/tests/sample_auth.toml +++ b/crates/test_utils/tests/sample_auth.toml @@ -30,6 +30,7 @@ api_key = "Bearer MyApiKey" [worldpay] api_key = "api_key" key1 = "key1" +api_secret = "Merchant Identifier" [payu] api_key = "Bearer MyApiKey" From c5cb6a109c672d8f674d2a0fba430e1360e4b673 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 10:49:05 +0000 Subject: [PATCH 02/28] chore: run formatter --- crates/router/src/connector/worldpay/response.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/router/src/connector/worldpay/response.rs b/crates/router/src/connector/worldpay/response.rs index 053026df100e..1781ae16aca2 100644 --- a/crates/router/src/connector/worldpay/response.rs +++ b/crates/router/src/connector/worldpay/response.rs @@ -2,7 +2,6 @@ use masking::Secret; use serde::{Deserialize, Serialize}; use super::requests::*; - use crate::{core::errors, types, types::transformers::ForeignTryFrom}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] From bbcd0a58abe38deaa5772ef608b7501b368e770b Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 26 Sep 2024 17:41:56 +0530 Subject: [PATCH 03/28] refactor(db): increase connector_refund_id's length to 512 --- crates/diesel_models/src/schema.rs | 6 +++--- crates/router/src/connector/worldpay/response.rs | 2 ++ crates/router/src/connector/worldpay/transformers.rs | 3 ++- .../down.sql | 8 ++++++++ .../up.sql | 8 ++++++++ 5 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/down.sql create mode 100644 migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/up.sql diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 9ff7c958df13..8f54f7fa1fc8 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -773,7 +773,7 @@ diesel::table! { #[max_length = 64] payment_method_id -> Nullable, payment_method -> Nullable, - #[max_length = 128] + #[max_length = 512] connector_transaction_id -> Nullable, capture_method -> Nullable, capture_on -> Nullable, @@ -1144,11 +1144,11 @@ diesel::table! { payment_id -> Varchar, #[max_length = 64] merchant_id -> Varchar, - #[max_length = 128] + #[max_length = 512] connector_transaction_id -> Varchar, #[max_length = 64] connector -> Varchar, - #[max_length = 128] + #[max_length = 512] connector_refund_id -> Nullable, #[max_length = 64] external_reference_id -> Nullable, diff --git a/crates/router/src/connector/worldpay/response.rs b/crates/router/src/connector/worldpay/response.rs index 1781ae16aca2..74f7a30735b1 100644 --- a/crates/router/src/connector/worldpay/response.rs +++ b/crates/router/src/connector/worldpay/response.rs @@ -30,6 +30,8 @@ pub enum PaymentOutcome { Refused, #[serde(alias = "Sent for Settlement")] SentForSettlement, + #[serde(alias = "Sent for Refund")] + SentForRefund, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index 51a69a55f3a1..ff0b4400cf40 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -190,7 +190,7 @@ impl TryFrom<&types::ConnectorAuthType> for WorldpayAuthType { type Error = error_stack::Report; fn try_from(auth_type: &types::ConnectorAuthType) -> Result { match auth_type { - // TODO: Remove this later, kept purely for backwards compatability + // TODO: Remove this later, kept purely for backwards compatibility types::ConnectorAuthType::BodyKey { api_key, key1 } => { let auth_key = format!("{}:{}", key1.peek(), api_key.peek()); let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key)); @@ -222,6 +222,7 @@ impl From for enums::AttemptStatus { PaymentOutcome::Authorized => Self::Authorized, PaymentOutcome::Refused => Self::Failure, PaymentOutcome::SentForSettlement => Self::CaptureInitiated, + PaymentOutcome::SentForRefund => Self::AutoRefunded, } } } diff --git a/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/down.sql b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/down.sql new file mode 100644 index 000000000000..2e3f9771eb04 --- /dev/null +++ b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/down.sql @@ -0,0 +1,8 @@ +ALTER TABLE payment_attempt +ALTER COLUMN connector_transaction_id TYPE VARCHAR(128); + +ALTER TABLE refund +ALTER COLUMN connector_transaction_id TYPE VARCHAR(128); + +ALTER TABLE refund +ALTER COLUMN connector_refund_id TYPE VARCHAR(128); \ No newline at end of file diff --git a/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/up.sql b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/up.sql new file mode 100644 index 000000000000..e57da0ce6634 --- /dev/null +++ b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/up.sql @@ -0,0 +1,8 @@ +ALTER TABLE payment_attempt +ALTER COLUMN connector_transaction_id TYPE VARCHAR(512); + +ALTER TABLE refund +ALTER COLUMN connector_transaction_id TYPE VARCHAR(512); + +ALTER TABLE refund +ALTER COLUMN connector_refund_id TYPE VARCHAR(512); \ No newline at end of file From d0c1637f7b030aa92025a0f73a35d4f078554db7 Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 26 Sep 2024 17:51:23 +0530 Subject: [PATCH 04/28] refactor: re-generate schema_v2 --- crates/diesel_models/src/schema_v2.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index a66286e02dba..de49fb3657fb 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -753,7 +753,7 @@ diesel::table! { #[max_length = 64] payment_method_id -> Nullable, payment_method -> Nullable, - #[max_length = 128] + #[max_length = 512] connector_transaction_id -> Nullable, capture_method -> Nullable, capture_on -> Nullable, @@ -1100,11 +1100,11 @@ diesel::table! { payment_id -> Varchar, #[max_length = 64] merchant_id -> Varchar, - #[max_length = 128] + #[max_length = 512] connector_transaction_id -> Varchar, #[max_length = 64] connector -> Varchar, - #[max_length = 128] + #[max_length = 512] connector_refund_id -> Nullable, #[max_length = 64] external_reference_id -> Nullable, From 97561b7b3834f3ea6191767f94943642b65269ff Mon Sep 17 00:00:00 2001 From: Kashif Date: Fri, 27 Sep 2024 10:43:12 +0530 Subject: [PATCH 05/28] refactor: resolve comments --- crates/router/src/connector/worldpay.rs | 42 +++++++++---- .../router/src/connector/worldpay/requests.rs | 4 +- .../src/connector/worldpay/transformers.rs | 60 ++++++++++++++----- crates/router/src/types/api.rs | 2 +- crates/router/tests/connectors/worldpay.rs | 2 +- 5 files changed, 81 insertions(+), 29 deletions(-) diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index dadf12e0e914..7782c7746f67 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -2,9 +2,12 @@ mod requests; mod response; pub mod transformers; -use std::fmt::Debug; - -use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; +use common_utils::{ + crypto, + ext_traits::ByteSliceExt, + request::RequestContent, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, +}; use diesel_models::enums; use error_stack::ResultExt; use transformers as worldpay; @@ -30,8 +33,18 @@ use crate::{ utils::BytesExt, }; -#[derive(Debug, Clone)] -pub struct Worldpay; +#[derive(Clone)] +pub struct Worldpay { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Worldpay { + pub const fn new() -> &'static Self { + &Self { + amount_converter: &MinorUnitForConnector, + } + } +} impl ConnectorCommonExt for Worldpay where @@ -193,9 +206,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( - "{}payments/authorizations/cancellations/{}", + "{}payments/authorizations/cancellations/{connector_payment_id}", self.base_url(connectors), - connector_payment_id )) } @@ -389,7 +401,12 @@ impl ConnectorIntegration CustomResult { - let connector_req = WorldpayPartialRequest::try_from(req)?; + let amount_to_capture = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + let connector_req = WorldpayPartialRequest::try_from((req, amount_to_capture))?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -498,7 +515,7 @@ impl ConnectorIntegration CustomResult { - let connector_req = WorldpayPartialRequest::try_from(req)?; + let amount_to_refund = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + let connector_req = WorldpayPartialRequest::try_from((req, amount_to_refund))?; Ok(RequestContent::Json(Box::new(connector_req))) } diff --git a/crates/router/src/connector/worldpay/requests.rs b/crates/router/src/connector/worldpay/requests.rs index 8b72a8dfd8f5..301ae64946d1 100644 --- a/crates/router/src/connector/worldpay/requests.rs +++ b/crates/router/src/connector/worldpay/requests.rs @@ -12,7 +12,7 @@ pub struct BillingAddress { #[serde(skip_serializing_if = "Option::is_none")] pub city: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub state: Option, + pub state: Option>, pub postal_code: Secret, pub country_code: common_enums::CountryAlpha2, } @@ -188,7 +188,7 @@ pub struct ExpiryDate { #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct PaymentValue { pub amount: i64, - pub currency: String, + pub currency: api_models::enums::Currency, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index ff0b4400cf40..1a8ba5a97487 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -1,6 +1,9 @@ +use api_models::payments::Address; use base64::Engine; -use common_utils::errors::CustomResult; +use common_utils::{errors::CustomResult, ext_traits::OptionExt, types::MinorUnit}; use diesel_models::enums; +use error_stack::ResultExt; +use hyperswitch_connectors::utils::RouterData; use masking::{PeekInterface, Secret}; use serde::Serialize; @@ -23,37 +26,61 @@ impl TryFrom<( &types::api::CurrencyUnit, types::storage::enums::Currency, - i64, + MinorUnit, T, )> for WorldpayRouterData { type Error = error_stack::Report; fn try_from( - (_currency_unit, _currency, amount, item): ( + (_currency_unit, _currency, minor_amount, item): ( &types::api::CurrencyUnit, types::storage::enums::Currency, - i64, + MinorUnit, T, ), ) -> Result { Ok(Self { - amount, + amount: minor_amount.get_amount_as_i64(), router_data: item, }) } } fn fetch_payment_instrument( payment_method: domain::PaymentMethodData, + billing_address: Option<&Address>, ) -> CustomResult { match payment_method { domain::PaymentMethodData::Card(card) => Ok(PaymentInstrument::Card(CardPayment { + payment_type: PaymentType::Card, expiry_date: ExpiryDate { month: utils::CardData::get_expiry_month_as_i8(&card)?, year: utils::CardData::get_expiry_year_as_i32(&card)?, }, card_number: card.card_number, cvc: card.card_cvc, - ..CardPayment::default() + card_holder_name: card.nick_name, + billing_address: if let Some(address) = + billing_address.and_then(|addr| addr.address.clone()) + { + Some(BillingAddress { + address1: address.line1, + address2: address.line2, + address3: address.line3, + city: address.city, + state: address.state, + postal_code: address.zip.get_required_value("zip").change_context( + errors::ConnectorError::MissingRequiredField { field_name: "zip" }, + )?, + country_code: address + .country + .get_required_value("country_code") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "country_code", + })?, + }) + } else { + None + }, })), domain::PaymentMethodData::Wallet(wallet) => match wallet { domain::WalletData::GooglePay(data) => { @@ -155,7 +182,7 @@ impl }, value: PaymentValue { amount: item.amount, - currency: item.router_data.request.currency.to_string(), + currency: item.router_data.request.currency, }, narrative: InstructionNarrative { line1: item @@ -167,6 +194,7 @@ impl }, payment_instrument: fetch_payment_instrument( item.router_data.request.payment_method_data.clone(), + item.router_data.get_optional_billing(), )?, debt_repayment: None, }, @@ -294,27 +322,29 @@ impl TryFrom> } } -impl TryFrom<&types::PaymentsCaptureRouterData> for WorldpayPartialRequest { +impl TryFrom<(&types::PaymentsCaptureRouterData, MinorUnit)> for WorldpayPartialRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsCaptureRouterData) -> Result { + fn try_from(req: (&types::PaymentsCaptureRouterData, MinorUnit)) -> Result { + let (item, amount) = req; Ok(Self { reference: item.payment_id.clone().replace("_", "-"), value: PaymentValue { - amount: item.request.amount_to_capture, - currency: item.request.currency.to_string(), + amount: amount.get_amount_as_i64(), + currency: item.request.currency, }, }) } } -impl TryFrom<&types::RefundsRouterData> for WorldpayPartialRequest { +impl TryFrom<(&types::RefundsRouterData, MinorUnit)> for WorldpayPartialRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from(req: (&types::RefundsRouterData, MinorUnit)) -> Result { + let (item, amount) = req; Ok(Self { reference: item.request.refund_id.clone().replace("_", "-"), value: PaymentValue { - amount: item.request.refund_amount, - currency: item.request.currency.to_string(), + amount: amount.get_amount_as_i64(), + currency: item.request.currency, }, }) } diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index dd5e47b1005b..20c4fdcdf056 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -465,7 +465,7 @@ impl ConnectorData { Ok(ConnectorEnum::Old(Box::new(&connector::Worldline))) } enums::Connector::Worldpay => { - Ok(ConnectorEnum::Old(Box::new(&connector::Worldpay))) + Ok(ConnectorEnum::Old(Box::new(connector::Worldpay::new()))) } enums::Connector::Mifinity => { Ok(ConnectorEnum::Old(Box::new(connector::Mifinity::new()))) diff --git a/crates/router/tests/connectors/worldpay.rs b/crates/router/tests/connectors/worldpay.rs index d4f6e167ff87..451aa398eb7b 100644 --- a/crates/router/tests/connectors/worldpay.rs +++ b/crates/router/tests/connectors/worldpay.rs @@ -20,7 +20,7 @@ impl utils::Connector for Worldpay { fn get_data(&self) -> types::api::ConnectorData { use router::connector::Worldpay; utils::construct_connector_data_old( - Box::new(&Worldpay), + Box::new(Worldpay::new()), types::Connector::Worldpay, types::api::GetToken::Connector, None, From dbeff462dc932d92a905dcb49cf017948dd1f68a Mon Sep 17 00:00:00 2001 From: Kashif Date: Fri, 27 Sep 2024 12:27:22 +0530 Subject: [PATCH 06/28] refactor: add pm_filters for worldpay and extend Currency enum --- config/deployments/integration_test.toml | 6 ++- config/deployments/production.toml | 6 ++- config/deployments/sandbox.toml | 4 +- config/development.toml | 2 + config/docker_compose.toml | 6 +++ crates/common_enums/src/enums.rs | 52 +++++++++++++++++++++++- crates/currency_conversion/src/types.rs | 12 ++++++ crates/kgraph_utils/src/transformers.rs | 12 ++++++ 8 files changed, 93 insertions(+), 7 deletions(-) diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 3a54c5ef08fc..a4d43353e0f9 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -316,8 +316,10 @@ upi_collect = {country = "IN", currency = "INR"} open_banking_pis = {currency = "EUR,GBP"} [pm_filters.worldpay] -apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" -google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" +debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } [pm_filters.zen] boleto = { country = "BR", currency = "BRL" } diff --git a/config/deployments/production.toml b/config/deployments/production.toml index ec3737a5c64c..b11f5d7e46c1 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -329,8 +329,10 @@ upi_collect = {country = "IN", currency = "INR"} open_banking_pis = {currency = "EUR,GBP"} [pm_filters.worldpay] -apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" -google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" +debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } [pm_filters.zen] boleto = { country = "BR", currency = "BRL" } diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 1c605ef35ac6..82e51488de6c 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -332,7 +332,9 @@ upi_collect = {country = "IN", currency = "INR"} [pm_filters.plaid] open_banking_pis = {currency = "EUR,GBP"} -[pm_filters.worldpay] +[pm_filters] +debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SAR,RSD,SCR,SLL,SG,ST,SBD,SOS,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,TMT,AED,UG,UA,US,UZ,VU,VE,VN,YER,CNY,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SAR,RSD,SCR,SLL,SG,ST,SBD,SOS,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,TMT,AED,UG,UA,US,UZ,VU,VE,VN,YER,CNY,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" diff --git a/config/development.toml b/config/development.toml index 29beed992f6e..4eaffbe10243 100644 --- a/config/development.toml +++ b/config/development.toml @@ -510,6 +510,8 @@ google_pay = { currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } paypal = { currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } [pm_filters.worldpay] +debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } diff --git a/config/docker_compose.toml b/config/docker_compose.toml index e196e123e843..a6284210da2c 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -431,6 +431,12 @@ credit = { currency = "USD" } debit = { currency = "USD" } ach = { currency = "USD" } +[pm_filters.worldpay] +debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } + [bank_config.online_banking_fpx] adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index a9ece7e9c026..b3300d856e65 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -557,6 +557,7 @@ pub enum CallConnectorAction { #[router_derive::diesel_enum(storage_type = "db_enum")] pub enum Currency { AED, + AFN, ALL, AMD, ANG, @@ -576,10 +577,12 @@ pub enum Currency { BOB, BRL, BSD, + BTN, BWP, BYN, BZD, CAD, + CDF, CHF, CLP, CNY, @@ -593,6 +596,7 @@ pub enum Currency { DOP, DZD, EGP, + ERN, ETB, EUR, FJD, @@ -614,6 +618,8 @@ pub enum Currency { ILS, INR, IQD, + IRR, + ISK, JMD, JOD, JPY, @@ -621,6 +627,7 @@ pub enum Currency { KGS, KHR, KMF, + KPW, KRW, KWD, KYD, @@ -667,6 +674,7 @@ pub enum Currency { SAR, SBD, SCR, + SDG, SEK, SGD, SHP, @@ -677,8 +685,11 @@ pub enum Currency { SSP, STN, SVC, + SYP, SZL, THB, + TJS, + TMT, TND, TOP, TRY, @@ -702,6 +713,7 @@ pub enum Currency { YER, ZAR, ZMW, + ZWL, } impl Currency { @@ -755,6 +767,7 @@ impl Currency { pub fn iso_4217(&self) -> &'static str { match *self { Self::AED => "784", + Self::AFN => "971", Self::ALL => "008", Self::AMD => "051", Self::ANG => "532", @@ -774,10 +787,12 @@ impl Currency { Self::BOB => "068", Self::BRL => "986", Self::BSD => "044", + Self::BTN => "064", Self::BWP => "072", Self::BYN => "933", Self::BZD => "084", Self::CAD => "124", + Self::CDF => "976", Self::CHF => "756", Self::CLP => "152", Self::COP => "170", @@ -790,6 +805,7 @@ impl Currency { Self::DOP => "214", Self::DZD => "012", Self::EGP => "818", + Self::ERN => "232", Self::ETB => "230", Self::EUR => "978", Self::FJD => "242", @@ -811,6 +827,8 @@ impl Currency { Self::ILS => "376", Self::INR => "356", Self::IQD => "368", + Self::IRR => "364", + Self::ISK => "352", Self::JMD => "388", Self::JOD => "400", Self::JPY => "392", @@ -818,6 +836,7 @@ impl Currency { Self::KGS => "417", Self::KHR => "116", Self::KMF => "174", + Self::KPW => "408", Self::KRW => "410", Self::KWD => "414", Self::KYD => "136", @@ -865,6 +884,7 @@ impl Currency { Self::SAR => "682", Self::SBD => "090", Self::SCR => "690", + Self::SDG => "938", Self::SEK => "752", Self::SGD => "702", Self::SHP => "654", @@ -875,8 +895,11 @@ impl Currency { Self::SSP => "728", Self::STN => "930", Self::SVC => "222", + Self::SYP => "760", Self::SZL => "748", Self::THB => "764", + Self::TJS => "972", + Self::TMT => "934", Self::TND => "788", Self::TOP => "776", Self::TRY => "949", @@ -899,6 +922,7 @@ impl Currency { Self::YER => "886", Self::ZAR => "710", Self::ZMW => "967", + Self::ZWL => "932", } } @@ -908,6 +932,7 @@ impl Currency { | Self::CLP | Self::DJF | Self::GNF + | Self::IRR | Self::JPY | Self::KMF | Self::KRW @@ -921,6 +946,7 @@ impl Currency { | Self::XOF | Self::XPF => true, Self::AED + | Self::AFN | Self::ALL | Self::AMD | Self::ANG @@ -939,10 +965,12 @@ impl Currency { | Self::BOB | Self::BRL | Self::BSD + | Self::BTN | Self::BWP | Self::BYN | Self::BZD | Self::CAD + | Self::CDF | Self::CHF | Self::CNY | Self::COP @@ -954,6 +982,7 @@ impl Currency { | Self::DOP | Self::DZD | Self::EGP + | Self::ERN | Self::ETB | Self::EUR | Self::FJD @@ -974,11 +1003,13 @@ impl Currency { | Self::ILS | Self::INR | Self::IQD + | Self::ISK | Self::JMD | Self::JOD | Self::KES | Self::KGS | Self::KHR + | Self::KPW | Self::KWD | Self::KYD | Self::KZT @@ -1021,6 +1052,7 @@ impl Currency { | Self::SAR | Self::SBD | Self::SCR + | Self::SDG | Self::SEK | Self::SGD | Self::SHP @@ -1031,8 +1063,11 @@ impl Currency { | Self::SSP | Self::STN | Self::SVC + | Self::SYP | Self::SZL | Self::THB + | Self::TJS + | Self::TMT | Self::TND | Self::TOP | Self::TRY @@ -1048,7 +1083,8 @@ impl Currency { | Self::XCD | Self::YER | Self::ZAR - | Self::ZMW => false, + | Self::ZMW + | Self::ZWL => false, } } @@ -1058,6 +1094,7 @@ impl Currency { true } Self::AED + | Self::AFN | Self::ALL | Self::AMD | Self::AOA @@ -1076,10 +1113,12 @@ impl Currency { | Self::BOB | Self::BRL | Self::BSD + | Self::BTN | Self::BWP | Self::BYN | Self::BZD | Self::CAD + | Self::CDF | Self::CHF | Self::CLP | Self::CNY @@ -1093,6 +1132,7 @@ impl Currency { | Self::DOP | Self::DZD | Self::EGP + | Self::ERN | Self::ETB | Self::EUR | Self::FJD @@ -1113,12 +1153,15 @@ impl Currency { | Self::IDR | Self::ILS | Self::INR + | Self::IRR + | Self::ISK | Self::JMD | Self::JPY | Self::KES | Self::KGS | Self::KHR | Self::KMF + | Self::KPW | Self::KRW | Self::KYD | Self::KZT @@ -1162,6 +1205,7 @@ impl Currency { | Self::SAR | Self::SBD | Self::SCR + | Self::SDG | Self::SEK | Self::SGD | Self::SHP @@ -1172,8 +1216,11 @@ impl Currency { | Self::SSP | Self::STN | Self::SVC + | Self::SYP | Self::SZL | Self::THB + | Self::TJS + | Self::TMT | Self::TOP | Self::TRY | Self::TTD @@ -1194,7 +1241,8 @@ impl Currency { | Self::XOF | Self::YER | Self::ZAR - | Self::ZMW => false, + | Self::ZMW + | Self::ZWL => false, } } diff --git a/crates/currency_conversion/src/types.rs b/crates/currency_conversion/src/types.rs index a84520dca0ad..2001495b2dba 100644 --- a/crates/currency_conversion/src/types.rs +++ b/crates/currency_conversion/src/types.rs @@ -78,6 +78,7 @@ impl ExchangeRates { pub fn currency_match(currency: Currency) -> &'static iso::Currency { match currency { Currency::AED => iso::AED, + Currency::AFN => iso::AFN, Currency::ALL => iso::ALL, Currency::AMD => iso::AMD, Currency::ANG => iso::ANG, @@ -97,10 +98,12 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::BOB => iso::BOB, Currency::BRL => iso::BRL, Currency::BSD => iso::BSD, + Currency::BTN => iso::BTN, Currency::BWP => iso::BWP, Currency::BYN => iso::BYN, Currency::BZD => iso::BZD, Currency::CAD => iso::CAD, + Currency::CDF => iso::CDF, Currency::CHF => iso::CHF, Currency::CLP => iso::CLP, Currency::CNY => iso::CNY, @@ -114,6 +117,7 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::DOP => iso::DOP, Currency::DZD => iso::DZD, Currency::EGP => iso::EGP, + Currency::ERN => iso::ERN, Currency::ETB => iso::ETB, Currency::EUR => iso::EUR, Currency::FJD => iso::FJD, @@ -135,6 +139,8 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::ILS => iso::ILS, Currency::INR => iso::INR, Currency::IQD => iso::IQD, + Currency::IRR => iso::IRR, + Currency::ISK => iso::ISK, Currency::JMD => iso::JMD, Currency::JOD => iso::JOD, Currency::JPY => iso::JPY, @@ -142,6 +148,7 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::KGS => iso::KGS, Currency::KHR => iso::KHR, Currency::KMF => iso::KMF, + Currency::KPW => iso::KPW, Currency::KRW => iso::KRW, Currency::KWD => iso::KWD, Currency::KYD => iso::KYD, @@ -188,6 +195,7 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::SAR => iso::SAR, Currency::SBD => iso::SBD, Currency::SCR => iso::SCR, + Currency::SDG => iso::SDG, Currency::SEK => iso::SEK, Currency::SGD => iso::SGD, Currency::SHP => iso::SHP, @@ -198,9 +206,12 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::SSP => iso::SSP, Currency::STN => iso::STN, Currency::SVC => iso::SVC, + Currency::SYP => iso::SYP, Currency::SZL => iso::SZL, Currency::THB => iso::THB, + Currency::TJS => iso::TJS, Currency::TND => iso::TND, + Currency::TMT => iso::TMT, Currency::TOP => iso::TOP, Currency::TTD => iso::TTD, Currency::TRY => iso::TRY, @@ -222,5 +233,6 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::YER => iso::YER, Currency::ZAR => iso::ZAR, Currency::ZMW => iso::ZMW, + Currency::ZWL => iso::ZWL, } } diff --git a/crates/kgraph_utils/src/transformers.rs b/crates/kgraph_utils/src/transformers.rs index 758a0dd3de03..42044bf09623 100644 --- a/crates/kgraph_utils/src/transformers.rs +++ b/crates/kgraph_utils/src/transformers.rs @@ -333,6 +333,7 @@ impl IntoDirValue for api_enums::Currency { fn into_dir_value(self) -> Result { match self { Self::AED => Ok(dirval!(PaymentCurrency = AED)), + Self::AFN => Ok(dirval!(PaymentCurrency = AFN)), Self::ALL => Ok(dirval!(PaymentCurrency = ALL)), Self::AMD => Ok(dirval!(PaymentCurrency = AMD)), Self::ANG => Ok(dirval!(PaymentCurrency = ANG)), @@ -352,10 +353,12 @@ impl IntoDirValue for api_enums::Currency { Self::BOB => Ok(dirval!(PaymentCurrency = BOB)), Self::BRL => Ok(dirval!(PaymentCurrency = BRL)), Self::BSD => Ok(dirval!(PaymentCurrency = BSD)), + Self::BTN => Ok(dirval!(PaymentCurrency = BTN)), Self::BWP => Ok(dirval!(PaymentCurrency = BWP)), Self::BYN => Ok(dirval!(PaymentCurrency = BYN)), Self::BZD => Ok(dirval!(PaymentCurrency = BZD)), Self::CAD => Ok(dirval!(PaymentCurrency = CAD)), + Self::CDF => Ok(dirval!(PaymentCurrency = CDF)), Self::CHF => Ok(dirval!(PaymentCurrency = CHF)), Self::CLP => Ok(dirval!(PaymentCurrency = CLP)), Self::CNY => Ok(dirval!(PaymentCurrency = CNY)), @@ -369,6 +372,7 @@ impl IntoDirValue for api_enums::Currency { Self::DOP => Ok(dirval!(PaymentCurrency = DOP)), Self::DZD => Ok(dirval!(PaymentCurrency = DZD)), Self::EGP => Ok(dirval!(PaymentCurrency = EGP)), + Self::ERN => Ok(dirval!(PaymentCurrency = ERN)), Self::ETB => Ok(dirval!(PaymentCurrency = ETB)), Self::EUR => Ok(dirval!(PaymentCurrency = EUR)), Self::FJD => Ok(dirval!(PaymentCurrency = FJD)), @@ -390,6 +394,8 @@ impl IntoDirValue for api_enums::Currency { Self::ILS => Ok(dirval!(PaymentCurrency = ILS)), Self::INR => Ok(dirval!(PaymentCurrency = INR)), Self::IQD => Ok(dirval!(PaymentCurrency = IQD)), + Self::IRR => Ok(dirval!(PaymentCurrency = IRR)), + Self::ISK => Ok(dirval!(PaymentCurrency = ISK)), Self::JMD => Ok(dirval!(PaymentCurrency = JMD)), Self::JOD => Ok(dirval!(PaymentCurrency = JOD)), Self::JPY => Ok(dirval!(PaymentCurrency = JPY)), @@ -397,6 +403,7 @@ impl IntoDirValue for api_enums::Currency { Self::KGS => Ok(dirval!(PaymentCurrency = KGS)), Self::KHR => Ok(dirval!(PaymentCurrency = KHR)), Self::KMF => Ok(dirval!(PaymentCurrency = KMF)), + Self::KPW => Ok(dirval!(PaymentCurrency = KPW)), Self::KRW => Ok(dirval!(PaymentCurrency = KRW)), Self::KWD => Ok(dirval!(PaymentCurrency = KWD)), Self::KYD => Ok(dirval!(PaymentCurrency = KYD)), @@ -443,6 +450,7 @@ impl IntoDirValue for api_enums::Currency { Self::SAR => Ok(dirval!(PaymentCurrency = SAR)), Self::SBD => Ok(dirval!(PaymentCurrency = SBD)), Self::SCR => Ok(dirval!(PaymentCurrency = SCR)), + Self::SDG => Ok(dirval!(PaymentCurrency = SDG)), Self::SEK => Ok(dirval!(PaymentCurrency = SEK)), Self::SGD => Ok(dirval!(PaymentCurrency = SGD)), Self::SHP => Ok(dirval!(PaymentCurrency = SHP)), @@ -453,8 +461,11 @@ impl IntoDirValue for api_enums::Currency { Self::SSP => Ok(dirval!(PaymentCurrency = SSP)), Self::STN => Ok(dirval!(PaymentCurrency = STN)), Self::SVC => Ok(dirval!(PaymentCurrency = SVC)), + Self::SYP => Ok(dirval!(PaymentCurrency = SYP)), Self::SZL => Ok(dirval!(PaymentCurrency = SZL)), Self::THB => Ok(dirval!(PaymentCurrency = THB)), + Self::TJS => Ok(dirval!(PaymentCurrency = TJS)), + Self::TMT => Ok(dirval!(PaymentCurrency = TMT)), Self::TND => Ok(dirval!(PaymentCurrency = TND)), Self::TOP => Ok(dirval!(PaymentCurrency = TOP)), Self::TRY => Ok(dirval!(PaymentCurrency = TRY)), @@ -477,6 +488,7 @@ impl IntoDirValue for api_enums::Currency { Self::YER => Ok(dirval!(PaymentCurrency = YER)), Self::ZAR => Ok(dirval!(PaymentCurrency = ZAR)), Self::ZMW => Ok(dirval!(PaymentCurrency = ZMW)), + Self::ZWL => Ok(dirval!(PaymentCurrency = ZWL)), } } } From 5cd1f5e975c867661bfab1199e5f7dd8e8ddc272 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 07:00:14 +0000 Subject: [PATCH 07/28] docs(openapi): re-generate OpenAPI specification --- api-reference-v2/openapi_spec.json | 14 +++++++++++++- api-reference/openapi_spec.json | 14 +++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 99462e95e076..870948ce8f2d 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -5662,6 +5662,7 @@ "description": "The three letter ISO currency code in uppercase. Eg: 'USD' for the United States Dollar.", "enum": [ "AED", + "AFN", "ALL", "AMD", "ANG", @@ -5681,10 +5682,12 @@ "BOB", "BRL", "BSD", + "BTN", "BWP", "BYN", "BZD", "CAD", + "CDF", "CHF", "CLP", "CNY", @@ -5698,6 +5701,7 @@ "DOP", "DZD", "EGP", + "ERN", "ETB", "EUR", "FJD", @@ -5719,6 +5723,8 @@ "ILS", "INR", "IQD", + "IRR", + "ISK", "JMD", "JOD", "JPY", @@ -5726,6 +5732,7 @@ "KGS", "KHR", "KMF", + "KPW", "KRW", "KWD", "KYD", @@ -5772,6 +5779,7 @@ "SAR", "SBD", "SCR", + "SDG", "SEK", "SGD", "SHP", @@ -5782,8 +5790,11 @@ "SSP", "STN", "SVC", + "SYP", "SZL", "THB", + "TJS", + "TMT", "TND", "TOP", "TRY", @@ -5805,7 +5816,8 @@ "XPF", "YER", "ZAR", - "ZMW" + "ZMW", + "ZWL" ] }, "CustomerAcceptance": { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index f5f01c8c49cb..c23936be2f72 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -8985,6 +8985,7 @@ "description": "The three letter ISO currency code in uppercase. Eg: 'USD' for the United States Dollar.", "enum": [ "AED", + "AFN", "ALL", "AMD", "ANG", @@ -9004,10 +9005,12 @@ "BOB", "BRL", "BSD", + "BTN", "BWP", "BYN", "BZD", "CAD", + "CDF", "CHF", "CLP", "CNY", @@ -9021,6 +9024,7 @@ "DOP", "DZD", "EGP", + "ERN", "ETB", "EUR", "FJD", @@ -9042,6 +9046,8 @@ "ILS", "INR", "IQD", + "IRR", + "ISK", "JMD", "JOD", "JPY", @@ -9049,6 +9055,7 @@ "KGS", "KHR", "KMF", + "KPW", "KRW", "KWD", "KYD", @@ -9095,6 +9102,7 @@ "SAR", "SBD", "SCR", + "SDG", "SEK", "SGD", "SHP", @@ -9105,8 +9113,11 @@ "SSP", "STN", "SVC", + "SYP", "SZL", "THB", + "TJS", + "TMT", "TND", "TOP", "TRY", @@ -9128,7 +9139,8 @@ "XPF", "YER", "ZAR", - "ZMW" + "ZMW", + "ZWL" ] }, "CurrentBlockThreshold": { From 8acdb1b1046c8db2f8b7ccda3914f8e1b2cca3ec Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 26 Sep 2024 17:59:44 +0530 Subject: [PATCH 08/28] refactor: remove redundant logs --- crates/router/src/connector/worldpay.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index 7782c7746f67..e6707c9d4259 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -105,8 +105,7 @@ impl ConnectorCommon for Worldpay { res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - router_env::logger::info!(raw_response=?res); - let response = if res.status_code != 404 { + let response = if res.response.len() > 0 { res.response .parse_struct("WorldpayErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)? From 5aab9e9a1f3774c2a593ae0255ce9ebe6f1ba48d Mon Sep 17 00:00:00 2001 From: Kashif Date: Mon, 7 Oct 2024 18:31:09 +0530 Subject: [PATCH 09/28] feat: add domain type for connector_transaction_id for handling id.len > 128 characters --- crates/common_utils/src/types.rs | 122 ++++++++++++++++++ crates/diesel_models/src/capture.rs | 20 ++- crates/diesel_models/src/payment_attempt.rs | 62 +++++++-- crates/diesel_models/src/query/capture.rs | 23 +++- crates/diesel_models/src/refund.rs | 74 +++++++++-- crates/diesel_models/src/schema.rs | 14 +- crates/diesel_models/src/user/sample_data.rs | 5 +- .../src/payments/payment_attempt.rs | 44 +++++-- .../fraud_check/flows/transaction_flow.rs | 7 +- crates/router/src/core/payments.rs | 10 +- .../payments/operations/payment_response.rs | 88 +++++++++---- crates/router/src/core/payments/retry.rs | 30 ++++- .../router/src/core/payments/transformers.rs | 57 +++++--- crates/router/src/core/payments/types.rs | 12 +- crates/router/src/core/refunds.rs | 74 ++++++++--- crates/router/src/core/utils.rs | 10 +- crates/router/src/core/webhooks/incoming.rs | 1 + crates/router/src/db/capture.rs | 1 + crates/router/src/db/refund.rs | 20 ++- .../src/services/kafka/payment_attempt.rs | 8 +- .../services/kafka/payment_attempt_event.rs | 7 +- crates/router/src/services/kafka/refund.rs | 9 +- .../router/src/services/kafka/refund_event.rs | 9 +- crates/router/src/types/api.rs | 5 +- .../src/types/storage/payment_attempt.rs | 1 + crates/router/src/types/transformers.rs | 9 +- crates/router/src/utils/user/sample_data.rs | 18 ++- crates/router/src/workflows/payment_sync.rs | 1 + .../src/mock_db/payment_attempt.rs | 1 + .../src/payments/payment_attempt.rs | 31 ++++- .../down.sql | 9 +- .../up.sql | 9 +- 32 files changed, 641 insertions(+), 150 deletions(-) diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 0cad88bfd4f4..d642d80697a7 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -1247,3 +1247,125 @@ where self.0.to_sql(out) } } + +/// Domain type for connector_transaction_id +/// Maximum length for connector's transaction_id can be 128 characters in HS DB. +/// In case connector's use an identifier whose length exceeds 128 characters, +/// the hash value of such identifiers will be stored as connector_transaction_id. +/// The actual connector's identifier will be stored in a separate column - +/// connector_transaction_data or something with a similar name. +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, AsExpression)] +#[diesel(sql_type = sql_types::Text)] +pub enum ConnectorTransactionId { + /// Actual transaction identifier + TxnId(String), + /// Hashed value of the transaction identifier + HashedData(String), +} + +impl ConnectorTransactionId { + /// Implementation for retrieving the inner identifier + pub fn get_id(&self) -> &String { + match self { + Self::TxnId(id) | Self::HashedData(id) => id, + } + } + + /// Implementation for forming ConnectorTransactionId and an optional string to be used for connector_transaction_id and connector_transaction_data + pub fn form_id_and_data(src: String) -> (Self, Option) { + let txn_id = Self::from(src.clone()); + match txn_id { + Self::TxnId(_) => (txn_id, None), + Self::HashedData(_) => (txn_id, Some(src)), + } + } + + /// Implementation for retrieving + pub fn get_txn_id<'a>( + &'a self, + txn_data: Option<&'a String>, + ) -> Result<&'a String, error_stack::Report> { + match (self, txn_data) { + (Self::TxnId(id), _) => Ok(id), + (Self::HashedData(_), Some(id)) => Ok(id), + (Self::HashedData(id), None) => Err(report!(ValidationError::InvalidValue { + message: "connector_transaction_data is empty for HashedData variant".to_string(), + }) + .attach_printable(format!( + "connector_transaction_data is empty for connector_transaction_id {}", + id + ))), + } + } +} + +impl From for ConnectorTransactionId { + fn from(src: String) -> Self { + // ID already hashed + if src.starts_with("hash_") { + Self::HashedData(src) + // Hash connector's transaction ID + } else if src.len() > 128 { + let hash = blake3::hash(src.as_bytes()); + Self::HashedData(format!("hash_{}", hash.to_hex())) + // Default + } else { + Self::TxnId(src) + } + } +} + +impl Queryable for ConnectorTransactionId +where + DB: Backend, + Self: FromSql, +{ + type Row = Self; + + fn build(row: Self::Row) -> deserialize::Result { + Ok(row) + } +} + +impl FromSql for ConnectorTransactionId +where + DB: Backend, + String: FromSql, +{ + fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result { + let val = String::from_sql(bytes)?; + Ok(Self::from(val)) + } +} + +impl ToSql for ConnectorTransactionId +where + DB: Backend, + String: ToSql, +{ + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result { + match self { + Self::HashedData(id) | Self::TxnId(id) => id.to_sql(out), + } + } +} + +/// Trait for fetching actual or hashed transaction IDs +pub trait ConnectorTransactionIdTrait { + /// Returns an optional connector transaction ID + fn get_optional_connector_transaction_id(&self) -> Option<&String> { + None + } + /// Returns a connector transaction ID + fn get_connector_transaction_id(&self) -> &String { + self.get_optional_connector_transaction_id() + .unwrap_or_else(|| { + static EMPTY_STRING: String = String::new(); + &EMPTY_STRING + }) + } + /// Returns an optional connector refund ID + fn get_optional_connector_refund_id(&self) -> Option<&String> { + self.get_optional_connector_transaction_id() + } +} diff --git a/crates/diesel_models/src/capture.rs b/crates/diesel_models/src/capture.rs index 0b0b86222e21..d6272c34060b 100644 --- a/crates/diesel_models/src/capture.rs +++ b/crates/diesel_models/src/capture.rs @@ -1,4 +1,4 @@ -use common_utils::types::MinorUnit; +use common_utils::types::{ConnectorTransactionId, MinorUnit}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -6,7 +6,7 @@ use time::PrimitiveDateTime; use crate::{enums as storage_enums, schema::captures}; #[derive( - Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Selectable, Serialize, Deserialize, Hash, + Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Selectable, Serialize, Deserialize, )] #[diesel(table_name = captures, primary_key(capture_id), check_for_backend(diesel::pg::Pg))] pub struct Capture { @@ -26,10 +26,11 @@ pub struct Capture { #[serde(with = "common_utils::custom_serde::iso8601")] pub modified_at: PrimitiveDateTime, pub authorized_attempt_id: String, - pub connector_capture_id: Option, + pub connector_capture_id: Option, pub capture_sequence: i16, // reference to the capture at connector side pub connector_response_reference_id: Option, + pub connector_capture_data: Option, } #[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize)] @@ -51,17 +52,19 @@ pub struct CaptureNew { #[serde(with = "common_utils::custom_serde::iso8601")] pub modified_at: PrimitiveDateTime, pub authorized_attempt_id: String, - pub connector_capture_id: Option, + pub connector_capture_id: Option, pub capture_sequence: i16, pub connector_response_reference_id: Option, + pub connector_capture_data: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum CaptureUpdate { ResponseUpdate { status: storage_enums::CaptureStatus, - connector_capture_id: Option, + connector_capture_id: Option, connector_response_reference_id: Option, + connector_capture_data: Option, }, ErrorUpdate { status: storage_enums::CaptureStatus, @@ -79,8 +82,9 @@ pub struct CaptureUpdateInternal { pub error_code: Option, pub error_reason: Option, pub modified_at: Option, - pub connector_capture_id: Option, + pub connector_capture_id: Option, pub connector_response_reference_id: Option, + pub connector_capture_data: Option, } impl CaptureUpdate { @@ -93,6 +97,7 @@ impl CaptureUpdate { modified_at: _, connector_capture_id, connector_response_reference_id, + connector_capture_data, } = self.into(); Capture { status: status.unwrap_or(source.status), @@ -103,6 +108,7 @@ impl CaptureUpdate { connector_capture_id: connector_capture_id.or(source.connector_capture_id), connector_response_reference_id: connector_response_reference_id .or(source.connector_response_reference_id), + connector_capture_data: connector_capture_data.or(source.connector_capture_data), ..source } } @@ -116,11 +122,13 @@ impl From for CaptureUpdateInternal { status, connector_capture_id: connector_transaction_id, connector_response_reference_id, + connector_capture_data, } => Self { status: Some(status), connector_capture_id: connector_transaction_id, modified_at: now, connector_response_reference_id, + connector_capture_data, ..Self::default() }, CaptureUpdate::ErrorUpdate { diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 63ce905f65f2..b54c7f848e08 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -1,4 +1,7 @@ -use common_utils::{id_type, pii, types::MinorUnit}; +use common_utils::{ + id_type, pii, + types::{ConnectorTransactionId, MinorUnit}, +}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -29,7 +32,7 @@ pub struct PaymentAttempt { pub tax_amount: Option, pub payment_method_id: Option, pub payment_method: Option, - pub connector_transaction_id: Option, + pub connector_transaction_id: Option, pub capture_method: Option, #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub capture_on: Option, @@ -83,6 +86,7 @@ pub struct PaymentAttempt { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_transaction_data: Option, } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] @@ -105,7 +109,7 @@ pub struct PaymentAttempt { pub tax_amount: Option, pub payment_method_id: Option, pub payment_method: Option, - pub connector_transaction_id: Option, + pub connector_transaction_id: Option, pub capture_method: Option, #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub capture_on: Option, @@ -159,6 +163,7 @@ pub struct PaymentAttempt { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_transaction_data: Option, } impl PaymentAttempt { @@ -366,7 +371,7 @@ pub enum PaymentAttemptUpdate { ResponseUpdate { status: storage_enums::AttemptStatus, connector: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, authentication_type: Option, payment_method_id: Option, mandate_id: Option, @@ -384,17 +389,19 @@ pub enum PaymentAttemptUpdate { unified_message: Option>, payment_method_data: Option, charge_id: Option, + connector_transaction_data: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, connector: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, payment_method_id: Option, error_code: Option>, error_message: Option>, error_reason: Option>, connector_response_reference_id: Option, updated_by: String, + connector_transaction_data: Option, }, StatusUpdate { status: storage_enums::AttemptStatus, @@ -410,9 +417,10 @@ pub enum PaymentAttemptUpdate { updated_by: String, unified_code: Option>, unified_message: Option>, - connector_transaction_id: Option, + connector_transaction_id: Option, payment_method_data: Option, authentication_type: Option, + connector_transaction_data: Option, }, CaptureUpdate { amount_to_capture: Option, @@ -429,17 +437,19 @@ pub enum PaymentAttemptUpdate { payment_method_id: Option, connector_metadata: Option, preprocessing_step_id: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, connector_response_reference_id: Option, updated_by: String, + connector_transaction_data: Option, }, ConnectorResponse { authentication_data: Option, encoded_data: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, connector: Option, charge_id: Option, updated_by: String, + connector_transaction_data: Option, }, IncrementalAuthorizationAmountUpdate { amount: MinorUnit, @@ -460,7 +470,8 @@ pub enum PaymentAttemptUpdate { updated_by: String, unified_code: Option, unified_message: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, + connector_transaction_data: Option, }, } @@ -471,7 +482,7 @@ pub struct PaymentAttemptUpdateInternal { pub net_amount: Option, pub currency: Option, pub status: Option, - pub connector_transaction_id: Option, + pub connector_transaction_id: Option, pub amount_to_capture: Option, pub connector: Option>, pub authentication_type: Option, @@ -516,6 +527,7 @@ pub struct PaymentAttemptUpdateInternal { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_transaction_data: Option, } impl PaymentAttemptUpdateInternal { @@ -605,6 +617,7 @@ impl PaymentAttemptUpdate { card_network, shipping_cost, order_tax_amount, + connector_transaction_data, } = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source); PaymentAttempt { amount: amount.unwrap_or(source.amount), @@ -660,6 +673,8 @@ impl PaymentAttemptUpdate { card_network: card_network.or(source.card_network), shipping_cost: shipping_cost.or(source.shipping_cost), order_tax_amount: order_tax_amount.or(source.order_tax_amount), + connector_transaction_data: connector_transaction_data + .or(source.connector_transaction_data), ..source } } @@ -738,6 +753,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::AuthenticationTypeUpdate { authentication_type, @@ -792,6 +808,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::ConfirmUpdate { amount, @@ -876,6 +893,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost, order_tax_amount, + connector_transaction_data: None, }, PaymentAttemptUpdate::VoidUpdate { status, @@ -931,6 +949,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::RejectUpdate { status, @@ -987,6 +1006,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::BlocklistUpdate { status, @@ -1043,6 +1063,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::PaymentMethodDetailsUpdate { payment_method_id, @@ -1097,6 +1118,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::ResponseUpdate { status, @@ -1119,6 +1141,7 @@ impl From for PaymentAttemptUpdateInternal { unified_message, payment_method_data, charge_id, + connector_transaction_data, } => Self { status: Some(status), connector: connector.map(Some), @@ -1141,6 +1164,7 @@ impl From for PaymentAttemptUpdateInternal { unified_message, payment_method_data, charge_id, + connector_transaction_data, amount: None, net_amount: None, currency: None, @@ -1183,6 +1207,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, payment_method_data, authentication_type, + connector_transaction_data, } => Self { connector: connector.map(Some), status: Some(status), @@ -1197,6 +1222,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, payment_method_data, authentication_type, + connector_transaction_data, amount: None, net_amount: None, currency: None, @@ -1284,6 +1310,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::UpdateTrackers { payment_token, @@ -1344,6 +1371,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::UnresolvedResponseUpdate { status, @@ -1355,6 +1383,7 @@ impl From for PaymentAttemptUpdateInternal { error_reason, connector_response_reference_id, updated_by, + connector_transaction_data, } => Self { status: Some(status), connector: connector.map(Some), @@ -1366,6 +1395,7 @@ impl From for PaymentAttemptUpdateInternal { error_reason, connector_response_reference_id, updated_by, + connector_transaction_data, amount: None, net_amount: None, currency: None, @@ -1414,6 +1444,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, connector_response_reference_id, updated_by, + connector_transaction_data, } => Self { status: Some(status), payment_method_id, @@ -1423,6 +1454,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, connector_response_reference_id, updated_by, + connector_transaction_data, amount: None, net_amount: None, currency: None, @@ -1519,6 +1551,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::AmountToCaptureUpdate { status, @@ -1574,6 +1607,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::ConnectorResponse { authentication_data, @@ -1582,6 +1616,7 @@ impl From for PaymentAttemptUpdateInternal { connector, updated_by, charge_id, + connector_transaction_data, } => Self { authentication_data, encoded_data, @@ -1590,6 +1625,7 @@ impl From for PaymentAttemptUpdateInternal { modified_at: common_utils::date_time::now(), updated_by, charge_id, + connector_transaction_data, amount: None, net_amount: None, currency: None, @@ -1686,6 +1722,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::AuthenticationUpdate { status, @@ -1743,6 +1780,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, }, PaymentAttemptUpdate::ManualUpdate { status, @@ -1753,6 +1791,7 @@ impl From for PaymentAttemptUpdateInternal { unified_code, unified_message, connector_transaction_id, + connector_transaction_data, } => Self { status, error_code: error_code.map(Some), @@ -1762,10 +1801,11 @@ impl From for PaymentAttemptUpdateInternal { updated_by, unified_code: unified_code.map(Some), unified_message: unified_message.map(Some), + connector_transaction_id, + connector_transaction_data, amount: None, net_amount: None, currency: None, - connector_transaction_id, amount_to_capture: None, connector: None, authentication_type: None, diff --git a/crates/diesel_models/src/query/capture.rs b/crates/diesel_models/src/query/capture.rs index e194dc9f64c6..7111236b7b8e 100644 --- a/crates/diesel_models/src/query/capture.rs +++ b/crates/diesel_models/src/query/capture.rs @@ -1,5 +1,3 @@ -use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; - use super::generics; use crate::{ capture::{Capture, CaptureNew, CaptureUpdate, CaptureUpdateInternal}, @@ -7,6 +5,8 @@ use crate::{ schema::captures::dsl, PgPooledConn, StorageResult, }; +use common_utils::types::ConnectorTransactionIdTrait; +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; impl CaptureNew { pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { @@ -67,3 +67,22 @@ impl Capture { .await } } + +impl ConnectorTransactionIdTrait for Capture { + fn get_optional_connector_transaction_id(&self) -> Option<&String> { + match self + .connector_capture_id + .as_ref() + .map(|capture_id| capture_id.get_txn_id(self.connector_capture_data.as_ref())) + .transpose() + { + Ok(capture_id) => capture_id, + + // In case hashed data is missing from DB, use the hashed ID as connector transaction ID + Err(_) => self + .connector_capture_id + .as_ref() + .map(|txn_id| txn_id.get_id()), + } + } +} diff --git a/crates/diesel_models/src/refund.rs b/crates/diesel_models/src/refund.rs index 42b0ffa620c6..9e850bc9f1f4 100644 --- a/crates/diesel_models/src/refund.rs +++ b/crates/diesel_models/src/refund.rs @@ -1,6 +1,6 @@ use common_utils::{ pii, - types::{ChargeRefunds, MinorUnit}, + types::{ChargeRefunds, ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit}, }; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; @@ -25,9 +25,9 @@ pub struct Refund { pub refund_id: String, //merchant_reference id pub payment_id: common_utils::id_type::PaymentId, pub merchant_id: common_utils::id_type::MerchantId, - pub connector_transaction_id: String, + pub connector_transaction_id: ConnectorTransactionId, pub connector: String, - pub connector_refund_id: Option, + pub connector_refund_id: Option, pub external_reference_id: Option, pub refund_type: storage_enums::RefundType, pub total_amount: MinorUnit, @@ -51,6 +51,8 @@ pub struct Refund { pub merchant_connector_id: Option, pub charges: Option, pub organization_id: common_utils::id_type::OrganizationId, + pub connector_refund_data: Option, + pub connector_transaction_data: Option, } #[derive( @@ -71,9 +73,9 @@ pub struct RefundNew { pub merchant_id: common_utils::id_type::MerchantId, pub internal_reference_id: String, pub external_reference_id: Option, - pub connector_transaction_id: String, + pub connector_transaction_id: ConnectorTransactionId, pub connector: String, - pub connector_refund_id: Option, + pub connector_refund_id: Option, pub refund_type: storage_enums::RefundType, pub total_amount: MinorUnit, pub currency: storage_enums::Currency, @@ -94,17 +96,20 @@ pub struct RefundNew { pub merchant_connector_id: Option, pub charges: Option, pub organization_id: common_utils::id_type::OrganizationId, + pub connector_refund_data: Option, + pub connector_transaction_data: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum RefundUpdate { Update { - connector_refund_id: String, + connector_refund_id: ConnectorTransactionId, refund_status: storage_enums::RefundStatus, sent_to_gateway: bool, refund_error_message: Option, refund_arn: String, updated_by: String, + connector_refund_data: Option, }, MetadataAndReasonUpdate { metadata: Option, @@ -112,17 +117,19 @@ pub enum RefundUpdate { updated_by: String, }, StatusUpdate { - connector_refund_id: Option, + connector_refund_id: Option, sent_to_gateway: bool, refund_status: storage_enums::RefundStatus, updated_by: String, + connector_refund_data: Option, }, ErrorUpdate { refund_status: Option, refund_error_message: Option, refund_error_code: Option, updated_by: String, - connector_refund_id: Option, + connector_refund_id: Option, + connector_refund_data: Option, }, ManualUpdate { refund_status: Option, @@ -135,7 +142,7 @@ pub enum RefundUpdate { #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] #[diesel(table_name = refund)] pub struct RefundUpdateInternal { - connector_refund_id: Option, + connector_refund_id: Option, refund_status: Option, sent_to_gateway: Option, refund_error_message: Option, @@ -145,6 +152,7 @@ pub struct RefundUpdateInternal { refund_error_code: Option, updated_by: String, modified_at: PrimitiveDateTime, + connector_refund_data: Option, } impl RefundUpdateInternal { @@ -160,6 +168,7 @@ impl RefundUpdateInternal { refund_error_code: self.refund_error_code, updated_by: self.updated_by, modified_at: self.modified_at, + connector_refund_data: self.connector_refund_data, ..source } } @@ -175,6 +184,7 @@ impl From for RefundUpdateInternal { refund_error_message, refund_arn, updated_by, + connector_refund_data, } => Self { connector_refund_id: Some(connector_refund_id), refund_status: Some(refund_status), @@ -182,6 +192,7 @@ impl From for RefundUpdateInternal { refund_error_message, refund_arn: Some(refund_arn), updated_by, + connector_refund_data, metadata: None, refund_reason: None, refund_error_code: None, @@ -202,17 +213,20 @@ impl From for RefundUpdateInternal { refund_arn: None, refund_error_code: None, modified_at: common_utils::date_time::now(), + connector_refund_data: None, }, RefundUpdate::StatusUpdate { connector_refund_id, sent_to_gateway, refund_status, updated_by, + connector_refund_data, } => Self { connector_refund_id, sent_to_gateway: Some(sent_to_gateway), refund_status: Some(refund_status), updated_by, + connector_refund_data, refund_error_message: None, refund_arn: None, metadata: None, @@ -226,12 +240,14 @@ impl From for RefundUpdateInternal { refund_error_code, updated_by, connector_refund_id, + connector_refund_data, } => Self { refund_status, refund_error_message, refund_error_code, updated_by, connector_refund_id, + connector_refund_data, sent_to_gateway: None, refund_arn: None, metadata: None, @@ -254,6 +270,7 @@ impl From for RefundUpdateInternal { metadata: None, refund_reason: None, modified_at: common_utils::date_time::now(), + connector_refund_data: None, }, } } @@ -272,6 +289,7 @@ impl RefundUpdate { refund_error_code, updated_by, modified_at: _, + connector_refund_data, } = self.into(); Refund { connector_refund_id: connector_refund_id.or(source.connector_refund_id), @@ -284,6 +302,7 @@ impl RefundUpdate { refund_reason: refund_reason.or(source.refund_reason), updated_by, modified_at: common_utils::date_time::now(), + connector_refund_data: connector_refund_data.or(source.connector_refund_data), ..source } } @@ -292,9 +311,10 @@ impl RefundUpdate { #[derive(Debug, Eq, PartialEq, Deserialize, Serialize)] pub struct RefundCoreWorkflow { pub refund_internal_reference_id: String, - pub connector_transaction_id: String, + pub connector_transaction_id: ConnectorTransactionId, pub merchant_id: common_utils::id_type::MerchantId, pub payment_id: common_utils::id_type::PaymentId, + pub connector_transaction_data: Option, } impl common_utils::events::ApiEventMetric for Refund { @@ -306,6 +326,37 @@ impl common_utils::events::ApiEventMetric for Refund { } } +impl ConnectorTransactionIdTrait for Refund { + fn get_optional_connector_refund_id(&self) -> Option<&String> { + match self + .connector_refund_id + .as_ref() + .map(|refund_id| refund_id.get_txn_id(self.connector_refund_data.as_ref())) + .transpose() + { + Ok(refund_id) => refund_id, + + // In case hashed data is missing from DB, use the hashed ID as connector transaction ID + Err(_) => self + .connector_refund_id + .as_ref() + .map(|txn_id| txn_id.get_id()), + } + } + + fn get_connector_transaction_id(&self) -> &String { + match self + .connector_transaction_id + .get_txn_id(self.connector_transaction_data.as_ref()) + { + Ok(txn_id) => txn_id, + + // In case hashed data is missing from DB, use the hashed ID as connector transaction ID + Err(_) => self.connector_transaction_id.get_id(), + } + } +} + mod tests { #[test] fn test_backwards_compatibility() { @@ -336,7 +387,8 @@ mod tests { "profile_id": null, "updated_by": "admin", "merchant_connector_id": null, - "charges": null + "charges": null, + "connector_transaction_data": null }"#; let deserialized = serde_json::from_str::(serialized_refund); diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 8f54f7fa1fc8..f32f448a5d5f 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -246,6 +246,8 @@ diesel::table! { capture_sequence -> Int2, #[max_length = 128] connector_response_reference_id -> Nullable, + #[max_length = 512] + connector_capture_data -> Nullable, } } @@ -773,7 +775,7 @@ diesel::table! { #[max_length = 64] payment_method_id -> Nullable, payment_method -> Nullable, - #[max_length = 512] + #[max_length = 128] connector_transaction_id -> Nullable, capture_method -> Nullable, capture_on -> Nullable, @@ -844,6 +846,8 @@ diesel::table! { card_network -> Nullable, shipping_cost -> Nullable, order_tax_amount -> Nullable, + #[max_length = 512] + connector_transaction_data -> Nullable, } } @@ -1144,11 +1148,11 @@ diesel::table! { payment_id -> Varchar, #[max_length = 64] merchant_id -> Varchar, - #[max_length = 512] + #[max_length = 128] connector_transaction_id -> Varchar, #[max_length = 64] connector -> Varchar, - #[max_length = 512] + #[max_length = 128] connector_refund_id -> Nullable, #[max_length = 64] external_reference_id -> Nullable, @@ -1180,6 +1184,10 @@ diesel::table! { charges -> Nullable, #[max_length = 32] organization_id -> Varchar, + #[max_length = 512] + connector_refund_data -> Nullable, + #[max_length = 512] + connector_transaction_data -> Nullable, } } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index ddcf6352c52a..5e218a3d7f56 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -2,7 +2,7 @@ use common_enums::{ AttemptStatus, AuthenticationType, CaptureMethod, Currency, PaymentExperience, PaymentMethod, PaymentMethodType, }; -use common_utils::types::MinorUnit; +use common_utils::types::{ConnectorTransactionId, MinorUnit}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -61,7 +61,7 @@ pub struct PaymentAttemptBatchNew { pub mandate_details: Option, pub error_reason: Option, pub connector_response_reference_id: Option, - pub connector_transaction_id: Option, + pub connector_transaction_id: Option, pub multiple_capture_count: Option, pub amount_capturable: MinorUnit, pub updated_by: String, @@ -85,6 +85,7 @@ pub struct PaymentAttemptBatchNew { pub organization_id: common_utils::id_type::OrganizationId, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_transaction_data: Option, } #[allow(dead_code)] diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 003509b20398..9ae8cc4b0293 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -5,7 +5,7 @@ use common_utils::{ id_type, pii, types::{ keymanager::{self, KeyManagerState}, - MinorUnit, + ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit, }, }; use diesel_models::{ @@ -179,7 +179,7 @@ pub struct PaymentAttempt { pub tax_amount: Option, pub payment_method_id: Option, pub payment_method: Option, - pub connector_transaction_id: Option, + pub connector_transaction_id: Option, pub capture_method: Option, #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub capture_on: Option, @@ -231,6 +231,7 @@ pub struct PaymentAttempt { pub organization_id: id_type::OrganizationId, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_transaction_data: Option, } impl PaymentAttempt { @@ -246,6 +247,25 @@ impl PaymentAttempt { } } +impl ConnectorTransactionIdTrait for PaymentAttempt { + fn get_optional_connector_transaction_id(&self) -> Option<&String> { + match self + .connector_transaction_id + .as_ref() + .map(|txn_id| txn_id.get_txn_id(self.connector_transaction_data.as_ref())) + .transpose() + { + Ok(txn_id) => txn_id, + + // In case hashed data is missing from DB, use the hashed ID as connector transaction ID + Err(_) => self + .connector_transaction_id + .as_ref() + .map(|txn_id| txn_id.get_id()), + } + } +} + #[derive(Clone, Debug, Eq, PartialEq)] pub struct PaymentListFilters { pub connector: Vec, @@ -437,7 +457,7 @@ pub enum PaymentAttemptUpdate { ResponseUpdate { status: storage_enums::AttemptStatus, connector: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, authentication_type: Option, payment_method_id: Option, mandate_id: Option, @@ -455,17 +475,19 @@ pub enum PaymentAttemptUpdate { unified_message: Option>, payment_method_data: Option, charge_id: Option, + connector_transaction_data: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, connector: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, payment_method_id: Option, error_code: Option>, error_message: Option>, error_reason: Option>, connector_response_reference_id: Option, updated_by: String, + connector_transaction_data: Option, }, StatusUpdate { status: storage_enums::AttemptStatus, @@ -481,9 +503,10 @@ pub enum PaymentAttemptUpdate { updated_by: String, unified_code: Option>, unified_message: Option>, - connector_transaction_id: Option, + connector_transaction_id: Option, payment_method_data: Option, authentication_type: Option, + connector_transaction_data: Option, }, CaptureUpdate { amount_to_capture: Option, @@ -500,17 +523,19 @@ pub enum PaymentAttemptUpdate { payment_method_id: Option, connector_metadata: Option, preprocessing_step_id: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, connector_response_reference_id: Option, updated_by: String, + connector_transaction_data: Option, }, ConnectorResponse { authentication_data: Option, encoded_data: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, connector: Option, charge_id: Option, updated_by: String, + connector_transaction_data: Option, }, IncrementalAuthorizationAmountUpdate { amount: MinorUnit, @@ -531,7 +556,8 @@ pub enum PaymentAttemptUpdate { updated_by: String, unified_code: Option, unified_message: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, + connector_transaction_data: Option, }, } @@ -1763,6 +1789,7 @@ impl behaviour::Conversion for PaymentAttempt { card_network, order_tax_amount: self.order_tax_amount, shipping_cost: self.shipping_cost, + connector_transaction_data: self.connector_transaction_data, }) } @@ -1840,6 +1867,7 @@ impl behaviour::Conversion for PaymentAttempt { organization_id: storage_model.organization_id, order_tax_amount: storage_model.order_tax_amount, shipping_cost: storage_model.shipping_cost, + connector_transaction_data: storage_model.connector_transaction_data, }) } .await diff --git a/crates/router/src/core/fraud_check/flows/transaction_flow.rs b/crates/router/src/core/fraud_check/flows/transaction_flow.rs index ebd967e75153..4abe37300d8e 100644 --- a/crates/router/src/core/fraud_check/flows/transaction_flow.rs +++ b/crates/router/src/core/fraud_check/flows/transaction_flow.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use common_utils::ext_traits::ValueExt; +use common_utils::{ext_traits::ValueExt, types::ConnectorTransactionIdTrait}; use error_stack::ResultExt; use crate::{ @@ -102,7 +102,10 @@ impl payment_method, error_code: self.payment_attempt.error_code.clone(), error_message: self.payment_attempt.error_message.clone(), - connector_transaction_id: self.payment_attempt.connector_transaction_id.clone(), + connector_transaction_id: self + .payment_attempt + .get_optional_connector_transaction_id() + .cloned(), connector: self.payment_attempt.connector.clone(), }, // self.order_details response: Ok(FraudCheckResponseData::TransactionResponse { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 283e02f975c3..c3254ca97346 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -28,7 +28,7 @@ pub use common_enums::enums::CallConnectorAction; use common_utils::{ ext_traits::{AsyncExt, StringExt}, id_type, pii, - types::{MinorUnit, Surcharge}, + types::{ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit, Surcharge}, }; use diesel_models::{ephemeral_key, fraud_check::FraudCheck}; use error_stack::{report, ResultExt}; @@ -4926,6 +4926,9 @@ pub async fn payments_manual_update( } else { None }; + let (connector_transaction_id, connector_transaction_data) = connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map_or((None, None), |(id, data)| (Some(id), data)); // Update the payment_attempt let attempt_update = storage::PaymentAttemptUpdate::ManualUpdate { status: attempt_status, @@ -4936,6 +4939,7 @@ pub async fn payments_manual_update( unified_code: option_gsm.as_ref().and_then(|gsm| gsm.unified_code.clone()), unified_message: option_gsm.and_then(|gsm| gsm.unified_message), connector_transaction_id, + connector_transaction_data, }; let updated_payment_attempt = state .store @@ -4969,6 +4973,9 @@ pub async fn payments_manual_update( } Ok(services::ApplicationResponse::Json( api_models::payments::PaymentsManualUpdateResponse { + connector_transaction_id: updated_payment_attempt + .get_optional_connector_transaction_id() + .cloned(), payment_id: updated_payment_attempt.payment_id, attempt_id: updated_payment_attempt.attempt_id, merchant_id: updated_payment_attempt.merchant_id, @@ -4976,7 +4983,6 @@ pub async fn payments_manual_update( error_code: updated_payment_attempt.error_code, error_message: updated_payment_attempt.error_message, error_reason: updated_payment_attempt.error_reason, - connector_transaction_id: updated_payment_attempt.connector_transaction_id, }, )) } diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 538f6d70c4f3..b7c59a39e863 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -4,7 +4,9 @@ use async_trait::async_trait; use common_enums::AuthorizationStatus; use common_utils::{ ext_traits::{AsyncExt, Encode}, - types::{keymanager::KeyManagerState, MinorUnit}, + types::{ + keymanager::KeyManagerState, ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit, + }, }; use error_stack::{report, ResultExt}; use futures::FutureExt; @@ -1106,6 +1108,12 @@ async fn payment_response_update_tracker( } } }; + let (connector_transaction_id, connector_transaction_data) = + err.connector_transaction_id.map_or((None, None), |txn_id| { + let (txn_id, txn_data) = + ConnectorTransactionId::form_id_and_data(txn_id); + (Some(txn_id), txn_data) + }); ( None, Some(storage::PaymentAttemptUpdate::ErrorUpdate { @@ -1121,9 +1129,10 @@ async fn payment_response_update_tracker( updated_by: storage_scheme.to_string(), unified_code: Some(Some(unified_code)), unified_message: Some(unified_translated_message), - connector_transaction_id: err.connector_transaction_id, + connector_transaction_id, payment_method_data: additional_payment_method_data, authentication_type: auth_update, + connector_transaction_data, }), ) } @@ -1143,7 +1152,12 @@ async fn payment_response_update_tracker( None }; let field_name = err.field_names; - let connector_transaction_id = err.connector_transaction_id; + let (connector_transaction_id, connector_transaction_data) = + err.connector_transaction_id.map_or((None, None), |txn_id| { + let (txn_id, txn_data) = + ConnectorTransactionId::form_id_and_data(txn_id); + (Some(txn_id), txn_data) + }); ( None, Some(storage::PaymentAttemptUpdate::ErrorUpdate { @@ -1161,6 +1175,7 @@ async fn payment_response_update_tracker( connector_transaction_id, payment_method_data: None, authentication_type: auth_update, + connector_transaction_data, }), ) } @@ -1190,12 +1205,21 @@ async fn payment_response_update_tracker( connector_response_reference_id, .. } => { - let connector_transaction_id = match pre_processing_id.to_owned() { - types::PreprocessingResponseId::PreProcessingId(_) => None, - types::PreprocessingResponseId::ConnectorTransactionId( - connector_txn_id, - ) => Some(connector_txn_id), - }; + let (connector_transaction_id, connector_transaction_data) = + match pre_processing_id.to_owned() { + types::PreprocessingResponseId::PreProcessingId(_) => { + (None, None) + } + types::PreprocessingResponseId::ConnectorTransactionId( + connector_txn_id, + ) => { + let (txn_id, txn_data) = + ConnectorTransactionId::form_id_and_data( + connector_txn_id, + ); + (Some(txn_id), txn_data) + } + }; let preprocessing_step_id = match pre_processing_id { types::PreprocessingResponseId::PreProcessingId( pre_processing_id, @@ -1214,6 +1238,7 @@ async fn payment_response_update_tracker( connector_transaction_id, connector_response_reference_id, updated_by: storage_scheme.to_string(), + connector_transaction_data, }; (None, Some(payment_attempt_update)) @@ -1236,10 +1261,14 @@ async fn payment_response_update_tracker( .payment_intent .request_incremental_authorization, ); - let connector_transaction_id = match resource_id { - types::ResponseId::NoResponseId => None, + let (connector_capture_id, connector_capture_data) = match resource_id { + types::ResponseId::NoResponseId => (None, None), types::ResponseId::ConnectorTransactionId(id) - | types::ResponseId::EncodedData(id) => Some(id), + | types::ResponseId::EncodedData(id) => { + let (txn_id, txn_data) = + ConnectorTransactionId::form_id_and_data(id); + (Some(txn_id), txn_data) + } }; let encoded_data = payment_data.payment_attempt.encoded_data.clone(); @@ -1292,8 +1321,9 @@ async fn payment_response_update_tracker( status: enums::CaptureStatus::foreign_try_from( router_data.status, )?, - connector_capture_id: connector_transaction_id.clone(), + connector_capture_id: connector_capture_id.clone(), connector_response_reference_id, + connector_capture_data: connector_capture_data.clone(), }; let capture_update_list = vec![( multiple_capture_data.get_latest_capture().clone(), @@ -1311,7 +1341,7 @@ async fn payment_response_update_tracker( Some(storage::PaymentAttemptUpdate::ResponseUpdate { status: updated_attempt_status, connector: None, - connector_transaction_id: connector_transaction_id.clone(), + connector_transaction_id: connector_capture_id, authentication_type: auth_update, amount_capturable: router_data .request @@ -1335,6 +1365,7 @@ async fn payment_response_update_tracker( encoded_data, payment_method_data: additional_payment_method_data, charge_id, + connector_transaction_data: connector_capture_data, }), ), }; @@ -1346,11 +1377,16 @@ async fn payment_response_update_tracker( reason, connector_response_reference_id, } => { - let connector_transaction_id = match resource_id { - types::ResponseId::NoResponseId => None, - types::ResponseId::ConnectorTransactionId(id) - | types::ResponseId::EncodedData(id) => Some(id), - }; + let (connector_transaction_id, connector_transaction_data) = + match resource_id { + types::ResponseId::NoResponseId => (None, None), + types::ResponseId::ConnectorTransactionId(id) + | types::ResponseId::EncodedData(id) => { + let (txn_id, txn_data) = + ConnectorTransactionId::form_id_and_data(id); + (Some(txn_id), txn_data) + } + }; ( None, Some(storage::PaymentAttemptUpdate::UnresolvedResponseUpdate { @@ -1366,6 +1402,7 @@ async fn payment_response_update_tracker( error_reason: Some(reason.map(|cd| cd.message)), connector_response_reference_id, updated_by: storage_scheme.to_string(), + connector_transaction_data, }), ) } @@ -1634,7 +1671,11 @@ async fn payment_response_update_tracker( &add_attributes([ ( "connector", - payment_data.payment_attempt.connector.unwrap_or_default(), + payment_data + .payment_attempt + .connector + .clone() + .unwrap_or_default(), ), ( "merchant_id", @@ -1648,12 +1689,15 @@ async fn payment_response_update_tracker( ); Err(error_stack::Report::new( errors::ApiErrorResponse::IntegrityCheckFailed { + connector_transaction_id: payment_data + .payment_attempt + .get_optional_connector_transaction_id() + .cloned(), reason: payment_data .payment_attempt .error_message .unwrap_or_default(), field_names: err.field_names, - connector_transaction_id: payment_data.payment_attempt.connector_transaction_id, }, )) } @@ -1777,7 +1821,7 @@ fn response_to_capture_update( let mut unmapped_captures = vec![]; for (connector_capture_id, capture_sync_response) in response_list { let capture = - multiple_capture_data.get_capture_by_connector_capture_id(connector_capture_id); + multiple_capture_data.get_capture_by_connector_capture_id(&connector_capture_id); if let Some(capture) = capture { capture_update_list.push(( capture.clone(), diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index ad643164f117..4096cf9ac409 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -1,6 +1,9 @@ use std::{str::FromStr, vec::IntoIter}; -use common_utils::{ext_traits::Encode, types::MinorUnit}; +use common_utils::{ + ext_traits::Encode, + types::{ConnectorTransactionId, MinorUnit}, +}; use diesel_models::enums as storage_enums; use error_stack::{report, ResultExt}; use router_env::{ @@ -390,14 +393,19 @@ where .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Could not parse the connector response")?; + let (connector_transaction_id, connector_transaction_data) = match resource_id { + types::ResponseId::NoResponseId => (None, None), + types::ResponseId::ConnectorTransactionId(id) + | types::ResponseId::EncodedData(id) => { + let (txn_id, txn_data) = ConnectorTransactionId::form_id_and_data(id); + (Some(txn_id), txn_data) + } + }; + let payment_attempt_update = storage::PaymentAttemptUpdate::ResponseUpdate { status: router_data.status, connector: None, - connector_transaction_id: match resource_id { - types::ResponseId::NoResponseId => None, - types::ResponseId::ConnectorTransactionId(id) - | types::ResponseId::EncodedData(id) => Some(id), - }, + connector_transaction_id, connector_response_reference_id: payment_data .get_payment_attempt() .connector_response_reference_id @@ -424,6 +432,7 @@ where unified_message: None, payment_method_data: additional_payment_method_data, charge_id, + connector_transaction_data, }; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] @@ -460,6 +469,12 @@ where None }; + let (connector_transaction_id, connector_transaction_data) = error_response + .connector_transaction_id + .clone() + .map(ConnectorTransactionId::form_id_and_data) + .map_or((None, None), |(id, data)| (Some(id), data)); + let payment_attempt_update = storage::PaymentAttemptUpdate::ErrorUpdate { connector: None, error_code: Some(Some(error_response.code.clone())), @@ -470,9 +485,10 @@ where updated_by: storage_scheme.to_string(), unified_code: option_gsm.clone().map(|gsm| gsm.unified_code), unified_message: option_gsm.map(|gsm| gsm.unified_message), - connector_transaction_id: error_response.connector_transaction_id.clone(), + connector_transaction_id, payment_method_data: additional_payment_method_data, authentication_type: auth_update, + connector_transaction_data, }; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 6b84ceb97596..e2587ad43c8f 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -9,7 +9,10 @@ use common_utils::{ consts::X_HS_LATENCY, fp_utils, pii::Email, - types::{AmountConvertor, MinorUnit, StringMajorUnitForConnector}, + types::{ + self as common_utils_type, AmountConvertor, ConnectorTransactionIdTrait, MinorUnit, + StringMajorUnitForConnector, + }, }; use diesel_models::ephemeral_key; use error_stack::{report, ResultExt}; @@ -70,8 +73,8 @@ where let resource_id = match payment_data .payment_attempt - .connector_transaction_id - .clone() + .get_optional_connector_transaction_id() + .cloned() { Some(id) => types::ResponseId::ConnectorTransactionId(id), None => types::ResponseId::NoResponseId, @@ -224,8 +227,8 @@ where let resource_id = match payment_data .payment_attempt - .connector_transaction_id - .clone() + .get_optional_connector_transaction_id() + .cloned() { Some(id) => types::ResponseId::ConnectorTransactionId(id), None => types::ResponseId::NoResponseId, @@ -1061,7 +1064,7 @@ where ))?; Some(PaymentChargeResponse { - charge_id: payment_attempt.charge_id, + charge_id: payment_attempt.charge_id.clone(), charge_type: payment_charges.charge_type, application_fees: payment_charges.fees, transfer_account_id: payment_charges.transfer_account_id, @@ -1140,6 +1143,10 @@ where }) }); + let connector_transaction_id = payment_attempt + .get_optional_connector_transaction_id() + .cloned(); + let payments_response = api::PaymentsResponse { payment_id: payment_intent.payment_id, merchant_id: payment_intent.merchant_id, @@ -1208,7 +1215,7 @@ where connector_request_reference_id_config, &merchant_id, ), - connector_transaction_id: payment_attempt.connector_transaction_id, + connector_transaction_id, frm_message, metadata: payment_intent.metadata, connector_metadata: payment_intent.connector_metadata, @@ -1372,6 +1379,7 @@ pub fn wait_screen_next_steps_check( #[cfg(feature = "v1")] impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::PaymentsResponse { fn foreign_from((pi, pa): (storage::PaymentIntent, storage::PaymentAttempt)) -> Self { + let connector_transaction_id = pa.get_optional_connector_transaction_id().cloned(); Self { payment_id: pi.payment_id, merchant_id: pi.merchant_id, @@ -1394,7 +1402,7 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay setup_future_usage: pi.setup_future_usage, capture_method: pa.capture_method, authentication_type: pa.authentication_type, - connector_transaction_id: pa.connector_transaction_id, + connector_transaction_id, attempt_count: pi.attempt_count, profile_id: pi.profile_id, merchant_connector_id: pa.merchant_connector_id, @@ -1806,7 +1814,11 @@ impl TryFrom> for types::PaymentsSyncData amount, integrity_object: None, mandate_id: payment_data.mandate_id.clone(), - connector_transaction_id: match payment_data.payment_attempt.connector_transaction_id { + connector_transaction_id: match payment_data + .payment_attempt + .get_optional_connector_transaction_id() + .cloned() + { Some(connector_txn_id) => { types::ResponseId::ConnectorTransactionId(connector_txn_id) } @@ -1882,7 +1894,9 @@ impl ConnectorTransactionId for Helcim { Self::connector_transaction_id(self, &payment_attempt.connector_metadata); metadata.map_err(|_| errors::ApiErrorResponse::ResourceIdNotFound) } else { - Ok(payment_attempt.connector_transaction_id) + Ok(payment_attempt + .get_optional_connector_transaction_id() + .cloned()) } } } @@ -2200,14 +2214,21 @@ impl ForeignTryFrom for storage::CaptureUpdate { connector_response_reference_id, .. } => { - let connector_capture_id = match resource_id { - types::ResponseId::ConnectorTransactionId(id) => Some(id), - types::ResponseId::EncodedData(_) | types::ResponseId::NoResponseId => None, + let (connector_capture_id, connector_capture_data) = match resource_id { + types::ResponseId::EncodedData(_) | types::ResponseId::NoResponseId => { + (None, None) + } + types::ResponseId::ConnectorTransactionId(id) => { + let (txn_id, txn_data) = + common_utils_type::ConnectorTransactionId::form_id_and_data(id); + (Some(txn_id), txn_data) + } }; Ok(Self::ResponseUpdate { status: enums::CaptureStatus::foreign_try_from(status)?, connector_capture_id, connector_response_reference_id, + connector_capture_data, }) } types::CaptureSyncResponse::Error { @@ -2278,7 +2299,10 @@ impl TryFrom> for types::CompleteAuthoriz browser_info, email: payment_data.email, payment_method_data: payment_data.payment_method_data.map(From::from), - connector_transaction_id: payment_data.payment_attempt.connector_transaction_id, + connector_transaction_id: payment_data + .payment_attempt + .get_optional_connector_transaction_id() + .cloned(), redirect_response, connector_meta: payment_data.payment_attempt.connector_metadata, complete_authorize_url, @@ -2371,7 +2395,10 @@ impl TryFrom> for types::PaymentsPreProce complete_authorize_url, browser_info, surcharge_details: payment_data.surcharge_details, - connector_transaction_id: payment_data.payment_attempt.connector_transaction_id, + connector_transaction_id: payment_data + .payment_attempt + .get_optional_connector_transaction_id() + .cloned(), redirect_response: None, mandate_id: payment_data.mandate_id, related_transaction_id: None, diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index 66c3eb91f519..8e6b981b8af3 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -4,7 +4,7 @@ use api_models::payment_methods::SurchargeDetailsResponse; use common_utils::{ errors::CustomResult, ext_traits::{Encode, OptionExt}, - types as common_types, + types::{self as common_types, ConnectorTransactionIdTrait}, }; use error_stack::ResultExt; use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; @@ -162,11 +162,13 @@ impl MultipleCaptureData { } pub fn get_capture_by_connector_capture_id( &self, - connector_capture_id: String, + connector_capture_id: &String, ) -> Option<&storage::Capture> { self.all_captures .iter() - .find(|(_, capture)| capture.connector_capture_id == Some(connector_capture_id.clone())) + .find(|(_, capture)| { + capture.get_optional_connector_transaction_id() == Some(connector_capture_id) + }) .map(|(_, capture)| capture) } pub fn get_latest_capture(&self) -> &storage::Capture { @@ -176,14 +178,14 @@ impl MultipleCaptureData { let pending_connector_capture_ids = self .get_pending_captures() .into_iter() - .filter_map(|capture| capture.connector_capture_id.clone()) + .filter_map(|capture| capture.get_optional_connector_transaction_id().cloned()) .collect(); pending_connector_capture_ids } pub fn get_pending_captures_without_connector_capture_id(&self) -> Vec<&storage::Capture> { self.get_pending_captures() .into_iter() - .filter(|capture| capture.connector_capture_id.is_none()) + .filter(|capture| capture.get_optional_connector_transaction_id().is_none()) .collect() } } diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index 7ee74ec5dd5c..5abe609e3008 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use api_models::admin::MerchantConnectorInfo; use common_utils::{ ext_traits::{AsyncExt, ValueExt}, - types::MinorUnit, + types::{ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit}, }; use diesel_models::process_tracker::business_status; use error_stack::{report, ResultExt}; @@ -234,6 +234,7 @@ pub async fn trigger_refund_to_gateway( refund_error_code: Some("NOT_IMPLEMENTED".to_string()), updated_by: storage_scheme.to_string(), connector_refund_id: None, + connector_refund_data: None, }) } errors::ConnectorError::NotSupported { message, connector } => { @@ -245,6 +246,7 @@ pub async fn trigger_refund_to_gateway( refund_error_code: Some("NOT_SUPPORTED".to_string()), updated_by: storage_scheme.to_string(), connector_refund_id: None, + connector_refund_data: None, }) } _ => None, @@ -286,12 +288,18 @@ pub async fn trigger_refund_to_gateway( refund_error_code: Some(err.code), updated_by: storage_scheme.to_string(), connector_refund_id: None, + connector_refund_data: None, }, Ok(response) => { // match on connector integrity checks match router_data_res.integrity_check.clone() { Err(err) => { - let refund_connector_transaction_id = err.connector_transaction_id; + let (refund_connector_transaction_id, connector_refund_data) = + err.connector_transaction_id.map_or((None, None), |txn_id| { + let (refund_id, refund_data) = + ConnectorTransactionId::form_id_and_data(txn_id); + (Some(refund_id), refund_data) + }); metrics::INTEGRITY_CHECK_FAILED.add( &metrics::CONTEXT, 1, @@ -312,6 +320,7 @@ pub async fn trigger_refund_to_gateway( refund_error_code: Some("IE".to_string()), updated_by: storage_scheme.to_string(), connector_refund_id: refund_connector_transaction_id, + connector_refund_data, } } Ok(()) => { @@ -322,13 +331,16 @@ pub async fn trigger_refund_to_gateway( &add_attributes([("connector", connector.connector_name.to_string())]), ) } + let (connector_refund_id, connector_refund_data) = + ConnectorTransactionId::form_id_and_data(response.connector_refund_id); storage::RefundUpdate::Update { - connector_refund_id: response.connector_refund_id, + connector_refund_id, refund_status: response.refund_status, sent_to_gateway: true, refund_error_message: None, refund_arn: "".to_string(), updated_by: storage_scheme.to_string(), + connector_refund_data, } } } @@ -423,7 +435,7 @@ pub async fn refund_retrieve_core( let payment_attempt = db .find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( - &refund.connector_transaction_id, + refund.get_connector_transaction_id(), payment_id, merchant_id, merchant_account.storage_scheme, @@ -578,6 +590,7 @@ pub async fn sync_refund_with_gateway( refund_error_code: Some(error_message.code), updated_by: storage_scheme.to_string(), connector_refund_id: None, + connector_refund_data: None, } } Ok(response) => match router_data_res.integrity_check.clone() { @@ -593,7 +606,13 @@ pub async fn sync_refund_with_gateway( ), ]), ); - let refund_connector_transaction_id = err.connector_transaction_id; + let (refund_connector_transaction_id, connector_refund_data) = err + .connector_transaction_id + .map_or((None, None), |refund_id| { + let (refund_id, refund_data) = + ConnectorTransactionId::form_id_and_data(refund_id); + (Some(refund_id), refund_data) + }); storage::RefundUpdate::ErrorUpdate { refund_status: Some(enums::RefundStatus::ManualReview), refund_error_message: Some(format!( @@ -603,16 +622,22 @@ pub async fn sync_refund_with_gateway( refund_error_code: Some("IE".to_string()), updated_by: storage_scheme.to_string(), connector_refund_id: refund_connector_transaction_id, + connector_refund_data, + } + } + Ok(()) => { + let (connector_refund_id, connector_refund_data) = + ConnectorTransactionId::form_id_and_data(response.connector_refund_id); + storage::RefundUpdate::Update { + connector_refund_id, + refund_status: response.refund_status, + sent_to_gateway: true, + refund_error_message: None, + refund_arn: "".to_string(), + updated_by: storage_scheme.to_string(), + connector_refund_data, } } - Ok(()) => storage::RefundUpdate::Update { - connector_refund_id: response.connector_refund_id, - refund_status: response.refund_status, - sent_to_gateway: true, - refund_error_message: None, - refund_arn: "".to_string(), - updated_by: storage_scheme.to_string(), - }, }, }; @@ -739,15 +764,21 @@ pub async fn validate_and_create_refund( .attach_printable("invalid merchant_id in request")) })?; - let connecter_transaction_id = payment_attempt.clone().connector_transaction_id.ok_or_else(|| { - report!(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Transaction in invalid. Missing field \"connector_transaction_id\" in payment_attempt.") - })?; + let connector_transaction_id = payment_attempt.clone() + .connector_transaction_id + .ok_or_else(|| { + report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Transaction in invalid. Missing field \"connector_transaction_id\" in payment_attempt.") + })?; + + let connector_transaction_id_str = connector_transaction_id + .get_txn_id(payment_attempt.connector_transaction_data.as_ref()) + .change_context(errors::ApiErrorResponse::InternalServerError)?; let all_refunds = db .find_refund_by_merchant_id_connector_transaction_id( merchant_account.get_id(), - &connecter_transaction_id, + &connector_transaction_id_str, merchant_account.storage_scheme, ) .await @@ -793,7 +824,7 @@ pub async fn validate_and_create_refund( external_reference_id: Some(refund_id.clone()), payment_id: req.payment_id, merchant_id: merchant_account.get_id().clone(), - connector_transaction_id: connecter_transaction_id.to_string(), + connector_transaction_id, connector, refund_type: req.refund_type.unwrap_or_default().foreign_into(), total_amount: payment_attempt.amount, @@ -814,6 +845,8 @@ pub async fn validate_and_create_refund( refund_arn: None, updated_by: Default::default(), organization_id: merchant_account.organization_id.clone(), + connector_refund_data: None, + connector_transaction_data: payment_attempt.connector_transaction_data.clone(), }; let refund = match db @@ -1389,7 +1422,7 @@ pub async fn trigger_refund_execute_workflow( let payment_attempt = db .find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( - &refund.connector_transaction_id, + refund.get_connector_transaction_id(), &refund_core.payment_id, &refund.merchant_id, merchant_account.storage_scheme, @@ -1493,6 +1526,7 @@ pub fn refund_to_refund_core_workflow_model( connector_transaction_id: refund.connector_transaction_id.clone(), merchant_id: refund.merchant_id.clone(), payment_id: refund.payment_id.clone(), + connector_transaction_data: refund.connector_transaction_data.clone(), } } diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 607de45215aa..256f0d46061c 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -12,7 +12,7 @@ use common_utils::{crypto::Encryptable, pii::Email}; use common_utils::{ errors::CustomResult, ext_traits::AsyncExt, - types::{keymanager::KeyManagerState, MinorUnit}, + types::{keymanager::KeyManagerState, ConnectorTransactionIdTrait, MinorUnit}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -325,6 +325,8 @@ pub async fn construct_refund_router_data<'a, F>( field_name: "browser_info", })?; + let connector_refund_id = refund.get_optional_connector_refund_id().cloned(); + let router_data = types::RouterData { flow: PhantomData, merchant_id: merchant_account.get_id().clone(), @@ -349,7 +351,7 @@ pub async fn construct_refund_router_data<'a, F>( minor_amount_captured: payment_intent.amount_captured, request: types::RefundsData { refund_id: refund.refund_id.clone(), - connector_transaction_id: refund.connector_transaction_id.clone(), + connector_transaction_id: refund.get_connector_transaction_id().clone(), refund_amount: refund.refund_amount.get_amount_as_i64(), minor_refund_amount: refund.refund_amount, currency, @@ -358,14 +360,14 @@ pub async fn construct_refund_router_data<'a, F>( webhook_url, connector_metadata: payment_attempt.connector_metadata.clone(), reason: refund.refund_reason.clone(), - connector_refund_id: refund.connector_refund_id.clone(), + connector_refund_id: connector_refund_id.clone(), browser_info, charges, integrity_object: None, }, response: Ok(types::RefundsResponseData { - connector_refund_id: refund.connector_refund_id.clone().unwrap_or_default(), + connector_refund_id: connector_refund_id.unwrap_or_default(), refund_status: refund.refund_status, }), access_token: None, diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index af531984a19e..3942c395665f 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -814,6 +814,7 @@ async fn refunds_incoming_webhook_flow( .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) .attach_printable("failed refund status mapping from event type")?, updated_by: merchant_account.storage_scheme.to_string(), + connector_refund_data: None, }; db.update_refund( refund.to_owned(), diff --git a/crates/router/src/db/capture.rs b/crates/router/src/db/capture.rs index 16b18a873d37..cbc27f918d40 100644 --- a/crates/router/src/db/capture.rs +++ b/crates/router/src/db/capture.rs @@ -198,6 +198,7 @@ impl CaptureInterface for MockDb { capture_sequence: capture.capture_sequence, connector_capture_id: capture.connector_capture_id, connector_response_reference_id: capture.connector_response_reference_id, + connector_capture_data: capture.connector_capture_data, }; captures.push(capture.clone()); Ok(capture) diff --git a/crates/router/src/db/refund.rs b/crates/router/src/db/refund.rs index cf092ad410e5..c21e3cf11031 100644 --- a/crates/router/src/db/refund.rs +++ b/crates/router/src/db/refund.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; #[cfg(feature = "olap")] -use common_utils::types::MinorUnit; +use common_utils::types::{ConnectorTransactionIdTrait, MinorUnit}; use diesel_models::{errors::DatabaseError, refund::RefundUpdateInternal}; use hyperswitch_domain_models::refunds; @@ -298,7 +298,9 @@ mod storage { #[cfg(feature = "kv_store")] mod storage { - use common_utils::{ext_traits::Encode, fallback_reverse_lookup_not_found}; + use common_utils::{ + ext_traits::Encode, fallback_reverse_lookup_not_found, types::ConnectorTransactionIdTrait, + }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::refunds; use redis_interface::HsetnxReply; @@ -432,6 +434,8 @@ mod storage { merchant_connector_id: new.merchant_connector_id.clone(), charges: new.charges.clone(), organization_id: new.organization_id.clone(), + connector_refund_data: new.connector_refund_data.clone(), + connector_transaction_data: new.connector_transaction_data.clone(), }; let field = format!( @@ -470,7 +474,8 @@ mod storage { updated_by: storage_scheme.to_string(), }, ]; - if let Some(connector_refund_id) = created_refund.to_owned().connector_refund_id + if let Some(connector_refund_id) = + created_refund.to_owned().get_optional_connector_refund_id() { reverse_lookups.push(storage_types::ReverseLookupNew { sk_id: field.clone(), @@ -921,6 +926,8 @@ impl RefundInterface for MockDb { merchant_connector_id: new.merchant_connector_id, charges: new.charges, organization_id: new.organization_id, + connector_refund_data: new.connector_refund_data, + connector_transaction_data: new.connector_transaction_data, }; refunds.push(refund.clone()); Ok(refund) @@ -937,7 +944,7 @@ impl RefundInterface for MockDb { .iter() .take_while(|refund| { refund.merchant_id == *merchant_id - && refund.connector_transaction_id == connector_transaction_id + && refund.get_connector_transaction_id() == connector_transaction_id }) .cloned() .collect::>()) @@ -995,7 +1002,10 @@ impl RefundInterface for MockDb { .iter() .find(|refund| { refund.merchant_id == *merchant_id - && refund.connector_refund_id == Some(connector_refund_id.to_string()) + && refund + .get_optional_connector_refund_id() + .map(|refund_id| refund_id.as_str()) + == Some(connector_refund_id) && refund.connector == connector }) .cloned() diff --git a/crates/router/src/services/kafka/payment_attempt.rs b/crates/router/src/services/kafka/payment_attempt.rs index b3827450a83b..8a993c50d3dd 100644 --- a/crates/router/src/services/kafka/payment_attempt.rs +++ b/crates/router/src/services/kafka/payment_attempt.rs @@ -1,5 +1,8 @@ // use diesel_models::enums::MandateDetails; -use common_utils::{id_type, types::MinorUnit}; +use common_utils::{ + id_type, + types::{ConnectorTransactionIdTrait, MinorUnit}, +}; use diesel_models::enums as storage_enums; use hyperswitch_domain_models::{ mandates::MandateDetails, payments::payment_attempt::PaymentAttempt, @@ -61,6 +64,7 @@ pub struct KafkaPaymentAttempt<'a> { impl<'a> KafkaPaymentAttempt<'a> { pub fn from_storage(attempt: &'a PaymentAttempt) -> Self { + let connector_transaction_id = attempt.get_optional_connector_transaction_id(); Self { payment_id: &attempt.payment_id, merchant_id: &attempt.merchant_id, @@ -76,7 +80,7 @@ impl<'a> KafkaPaymentAttempt<'a> { tax_amount: attempt.tax_amount, payment_method_id: attempt.payment_method_id.as_ref(), payment_method: attempt.payment_method, - connector_transaction_id: attempt.connector_transaction_id.as_ref(), + connector_transaction_id, capture_method: attempt.capture_method, capture_on: attempt.capture_on.map(|i| i.assume_utc()), confirm: attempt.confirm, diff --git a/crates/router/src/services/kafka/payment_attempt_event.rs b/crates/router/src/services/kafka/payment_attempt_event.rs index 13ab03198198..1d7cc68ee731 100644 --- a/crates/router/src/services/kafka/payment_attempt_event.rs +++ b/crates/router/src/services/kafka/payment_attempt_event.rs @@ -1,5 +1,8 @@ // use diesel_models::enums::MandateDetails; -use common_utils::{id_type, types::MinorUnit}; +use common_utils::{ + id_type, + types::{ConnectorTransactionIdTrait, MinorUnit}, +}; use diesel_models::enums as storage_enums; use hyperswitch_domain_models::{ mandates::MandateDetails, payments::payment_attempt::PaymentAttempt, @@ -77,7 +80,7 @@ impl<'a> KafkaPaymentAttemptEvent<'a> { tax_amount: attempt.tax_amount, payment_method_id: attempt.payment_method_id.as_ref(), payment_method: attempt.payment_method, - connector_transaction_id: attempt.connector_transaction_id.as_ref(), + connector_transaction_id: attempt.get_optional_connector_transaction_id(), capture_method: attempt.capture_method, capture_on: attempt.capture_on.map(|i| i.assume_utc()), confirm: attempt.confirm, diff --git a/crates/router/src/services/kafka/refund.rs b/crates/router/src/services/kafka/refund.rs index c4fb4a43dd08..7eff6f881baf 100644 --- a/crates/router/src/services/kafka/refund.rs +++ b/crates/router/src/services/kafka/refund.rs @@ -1,4 +1,7 @@ -use common_utils::{id_type, types::MinorUnit}; +use common_utils::{ + id_type, + types::{ConnectorTransactionIdTrait, MinorUnit}, +}; use diesel_models::{enums as storage_enums, refund::Refund}; use time::OffsetDateTime; @@ -39,9 +42,9 @@ impl<'a> KafkaRefund<'a> { refund_id: &refund.refund_id, payment_id: &refund.payment_id, merchant_id: &refund.merchant_id, - connector_transaction_id: &refund.connector_transaction_id, + connector_transaction_id: refund.get_connector_transaction_id(), connector: &refund.connector, - connector_refund_id: refund.connector_refund_id.as_ref(), + connector_refund_id: refund.get_optional_connector_refund_id(), external_reference_id: refund.external_reference_id.as_ref(), refund_type: &refund.refund_type, total_amount: &refund.total_amount, diff --git a/crates/router/src/services/kafka/refund_event.rs b/crates/router/src/services/kafka/refund_event.rs index f2150020c974..8f9f77878a10 100644 --- a/crates/router/src/services/kafka/refund_event.rs +++ b/crates/router/src/services/kafka/refund_event.rs @@ -1,4 +1,7 @@ -use common_utils::{id_type, types::MinorUnit}; +use common_utils::{ + id_type, + types::{ConnectorTransactionIdTrait, MinorUnit}, +}; use diesel_models::{enums as storage_enums, refund::Refund}; use time::OffsetDateTime; @@ -40,9 +43,9 @@ impl<'a> KafkaRefundEvent<'a> { refund_id: &refund.refund_id, payment_id: &refund.payment_id, merchant_id: &refund.merchant_id, - connector_transaction_id: &refund.connector_transaction_id, + connector_transaction_id: refund.get_connector_transaction_id(), connector: &refund.connector, - connector_refund_id: refund.connector_refund_id.as_ref(), + connector_refund_id: refund.get_optional_connector_refund_id(), external_reference_id: refund.external_reference_id.as_ref(), refund_type: &refund.refund_type, total_amount: &refund.total_amount, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 20c4fdcdf056..e2d0343d5c37 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -38,6 +38,7 @@ pub mod refunds_v2; use std::{fmt::Debug, str::FromStr}; +use common_utils::types::ConnectorTransactionIdTrait; use error_stack::{report, ResultExt}; pub use hyperswitch_domain_models::router_flow_types::{ access_token_auth::AccessTokenAuth, mandate_revoke::MandateRevoke, @@ -81,7 +82,9 @@ pub trait ConnectorTransactionId: ConnectorCommon + Sync { &self, payment_attempt: hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, ) -> Result, errors::ApiErrorResponse> { - Ok(payment_attempt.connector_transaction_id) + Ok(payment_attempt + .get_optional_connector_transaction_id() + .cloned()) } } diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index 18b6e8cc6f93..a8daf6003a93 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -53,6 +53,7 @@ impl PaymentAttemptExt for PaymentAttempt { capture_sequence, connector_capture_id: None, connector_response_reference_id: None, + connector_capture_data: None, }) } fn get_next_capture_id(&self) -> String { diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 992816a36160..1be01af7daed 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -10,6 +10,7 @@ use common_utils::{ ext_traits::{Encode, StringExt, ValueExt}, fp_utils::when, pii, + types::ConnectorTransactionIdTrait, }; use diesel_models::enums as storage_enums; use error_stack::{report, ResultExt}; @@ -1290,6 +1291,9 @@ impl ForeignTryFrom impl ForeignFrom for payments::PaymentAttemptResponse { fn foreign_from(payment_attempt: storage::PaymentAttempt) -> Self { + let connector_transaction_id = payment_attempt + .get_optional_connector_transaction_id() + .cloned(); Self { attempt_id: payment_attempt.attempt_id, status: payment_attempt.status, @@ -1298,7 +1302,7 @@ impl ForeignFrom for payments::PaymentAttemptResponse { connector: payment_attempt.connector, error_message: payment_attempt.error_reason, payment_method: payment_attempt.payment_method, - connector_transaction_id: payment_attempt.connector_transaction_id, + connector_transaction_id, capture_method: payment_attempt.capture_method, authentication_type: payment_attempt.authentication_type, created_at: payment_attempt.created_at, @@ -1321,6 +1325,7 @@ impl ForeignFrom for payments::PaymentAttemptResponse { impl ForeignFrom for payments::CaptureResponse { fn foreign_from(capture: storage::Capture) -> Self { + let connector_capture_id = capture.get_optional_connector_transaction_id().cloned(); Self { capture_id: capture.capture_id, status: capture.status, @@ -1328,7 +1333,7 @@ impl ForeignFrom for payments::CaptureResponse { currency: capture.currency, connector: capture.connector, authorized_attempt_id: capture.authorized_attempt_id, - connector_capture_id: capture.connector_capture_id, + connector_capture_id, capture_sequence: capture.capture_sequence, error_message: capture.error_message, error_code: capture.error_code, diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 1f7d75fba83d..122989a82cfd 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -2,7 +2,10 @@ use api_models::{ enums::Connector::{DummyConnector4, DummyConnector7}, user::sample_data::SampleDataRequest, }; -use common_utils::{id_type, types::MinorUnit}; +use common_utils::{ + id_type, + types::{ConnectorTransactionId, MinorUnit}, +}; use diesel_models::{user::sample_data::PaymentAttemptBatchNew, RefundNew}; use error_stack::ResultExt; use hyperswitch_domain_models::payments::PaymentIntent; @@ -253,10 +256,12 @@ pub async fn generate_sample_data( tax_details: None, skip_external_tax_calculation: None, }; + let (connector_transaction_id, connector_transaction_data) = + ConnectorTransactionId::form_id_and_data(attempt_id.clone()); let payment_attempt = PaymentAttemptBatchNew { attempt_id: attempt_id.clone(), payment_id: payment_id.clone(), - connector_transaction_id: Some(attempt_id.clone()), + connector_transaction_id: Some(connector_transaction_id), merchant_id: merchant_id.clone(), status: match is_failed_payment { true => common_enums::AttemptStatus::Failure, @@ -333,18 +338,21 @@ pub async fn generate_sample_data( organization_id: org_id.clone(), shipping_cost: None, order_tax_amount: None, + connector_transaction_data, }; let refund = if refunds_count < number_of_refunds && !is_failed_payment { refunds_count += 1; + let (connector_transaction_id, connector_transaction_data) = + ConnectorTransactionId::form_id_and_data(attempt_id.clone()); Some(RefundNew { refund_id: common_utils::generate_id_with_default_len("test"), internal_reference_id: common_utils::generate_id_with_default_len("test"), external_reference_id: None, payment_id: payment_id.clone(), - attempt_id: attempt_id.clone(), + attempt_id, merchant_id: merchant_id.clone(), - connector_transaction_id: attempt_id.clone(), + connector_transaction_id, connector_refund_id: None, description: Some("This is a sample refund".to_string()), created_at, @@ -369,6 +377,8 @@ pub async fn generate_sample_data( merchant_connector_id: payment_attempt.merchant_connector_id.clone(), charges: None, organization_id: org_id.clone(), + connector_refund_data: None, + connector_transaction_data, }) } else { None diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index f34d707b6c83..01a17bc4bc87 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -153,6 +153,7 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { connector_transaction_id: None, payment_method_data: None, authentication_type: None, + connector_transaction_data: None }; payment_data.payment_attempt = db diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 7abde1409919..de5ad5c23e78 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -189,6 +189,7 @@ impl PaymentAttemptInterface for MockDb { profile_id: payment_attempt.profile_id, shipping_cost: payment_attempt.shipping_cost, order_tax_amount: payment_attempt.order_tax_amount, + connector_transaction_data: None, }; payment_attempts.push(payment_attempt.clone()); Ok(payment_attempt) diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 9974393e100b..3a4a398d1b0e 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -538,6 +538,7 @@ impl PaymentAttemptInterface for KVRouterStore { profile_id: payment_attempt.profile_id.clone(), shipping_cost: payment_attempt.shipping_cost, order_tax_amount: payment_attempt.order_tax_amount, + connector_transaction_data: None, }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -670,7 +671,7 @@ impl PaymentAttemptInterface for KVRouterStore { key_str.as_str(), &this.merchant_id, updated_attempt.attempt_id.as_str(), - connector_transaction_id.as_str(), + &connector_transaction_id.get_id(), storage_scheme, ) .await?; @@ -682,7 +683,7 @@ impl PaymentAttemptInterface for KVRouterStore { key_str.as_str(), &this.merchant_id, updated_attempt.attempt_id.as_str(), - connector_transaction_id.as_str(), + &connector_transaction_id.get_id(), storage_scheme, ) .await?; @@ -1604,6 +1605,7 @@ impl DataModelExt for PaymentAttempt { profile_id: self.profile_id, shipping_cost: self.shipping_cost, order_tax_amount: self.order_tax_amount, + connector_transaction_data: self.connector_transaction_data, } } @@ -1675,6 +1677,7 @@ impl DataModelExt for PaymentAttempt { profile_id: storage_model.profile_id, shipping_cost: storage_model.shipping_cost, order_tax_amount: storage_model.order_tax_amount, + connector_transaction_data: storage_model.connector_transaction_data, } } } @@ -2013,6 +2016,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_message, payment_method_data, charge_id, + connector_transaction_data, } => DieselPaymentAttemptUpdate::ResponseUpdate { status, connector, @@ -2034,6 +2038,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_message, payment_method_data, charge_id, + connector_transaction_data, }, Self::UnresolvedResponseUpdate { status, @@ -2045,6 +2050,7 @@ impl DataModelExt for PaymentAttemptUpdate { error_reason, connector_response_reference_id, updated_by, + connector_transaction_data, } => DieselPaymentAttemptUpdate::UnresolvedResponseUpdate { status, connector, @@ -2055,6 +2061,7 @@ impl DataModelExt for PaymentAttemptUpdate { error_reason, connector_response_reference_id, updated_by, + connector_transaction_data, }, Self::StatusUpdate { status, updated_by } => { DieselPaymentAttemptUpdate::StatusUpdate { status, updated_by } @@ -2072,6 +2079,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector_transaction_id, payment_method_data, authentication_type, + connector_transaction_data, } => DieselPaymentAttemptUpdate::ErrorUpdate { connector, status, @@ -2085,6 +2093,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector_transaction_id, payment_method_data, authentication_type, + connector_transaction_data, }, Self::CaptureUpdate { multiple_capture_count, @@ -2103,6 +2112,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector_transaction_id, connector_response_reference_id, updated_by, + connector_transaction_data, } => DieselPaymentAttemptUpdate::PreprocessingUpdate { status, payment_method_id, @@ -2111,6 +2121,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector_transaction_id, connector_response_reference_id, updated_by, + connector_transaction_data, }, Self::RejectUpdate { status, @@ -2139,6 +2150,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector, charge_id, updated_by, + connector_transaction_data, } => DieselPaymentAttemptUpdate::ConnectorResponse { authentication_data, encoded_data, @@ -2146,6 +2158,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector, charge_id, updated_by, + connector_transaction_data, }, Self::IncrementalAuthorizationAmountUpdate { amount, @@ -2176,6 +2189,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_code, unified_message, connector_transaction_id, + connector_transaction_data, } => DieselPaymentAttemptUpdate::ManualUpdate { status, error_code, @@ -2185,6 +2199,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_code, unified_message, connector_transaction_id, + connector_transaction_data, }, } } @@ -2369,6 +2384,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_message, payment_method_data, charge_id, + connector_transaction_data, } => Self::ResponseUpdate { status, connector, @@ -2390,6 +2406,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_message, payment_method_data, charge_id, + connector_transaction_data, }, DieselPaymentAttemptUpdate::UnresolvedResponseUpdate { status, @@ -2401,6 +2418,7 @@ impl DataModelExt for PaymentAttemptUpdate { error_reason, connector_response_reference_id, updated_by, + connector_transaction_data, } => Self::UnresolvedResponseUpdate { status, connector, @@ -2411,6 +2429,7 @@ impl DataModelExt for PaymentAttemptUpdate { error_reason, connector_response_reference_id, updated_by, + connector_transaction_data, }, DieselPaymentAttemptUpdate::StatusUpdate { status, updated_by } => { Self::StatusUpdate { status, updated_by } @@ -2428,6 +2447,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector_transaction_id, payment_method_data, authentication_type, + connector_transaction_data, } => Self::ErrorUpdate { connector, status, @@ -2441,6 +2461,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector_transaction_id, payment_method_data, authentication_type, + connector_transaction_data, }, DieselPaymentAttemptUpdate::CaptureUpdate { amount_to_capture, @@ -2459,6 +2480,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector_transaction_id, connector_response_reference_id, updated_by, + connector_transaction_data, } => Self::PreprocessingUpdate { status, payment_method_id, @@ -2467,6 +2489,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector_transaction_id, connector_response_reference_id, updated_by, + connector_transaction_data, }, DieselPaymentAttemptUpdate::RejectUpdate { status, @@ -2495,6 +2518,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector, charge_id, updated_by, + connector_transaction_data, } => Self::ConnectorResponse { authentication_data, encoded_data, @@ -2502,6 +2526,7 @@ impl DataModelExt for PaymentAttemptUpdate { connector, charge_id, updated_by, + connector_transaction_data, }, DieselPaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { amount, @@ -2532,6 +2557,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_code, unified_message, connector_transaction_id, + connector_transaction_data, } => Self::ManualUpdate { status, error_code, @@ -2541,6 +2567,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_code, unified_message, connector_transaction_id, + connector_transaction_data, }, } } diff --git a/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/down.sql b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/down.sql index 2e3f9771eb04..8008535f877c 100644 --- a/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/down.sql +++ b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/down.sql @@ -1,8 +1,11 @@ ALTER TABLE payment_attempt -ALTER COLUMN connector_transaction_id TYPE VARCHAR(128); +DROP COLUMN IF EXISTS connector_transaction_data; ALTER TABLE refund -ALTER COLUMN connector_transaction_id TYPE VARCHAR(128); +DROP COLUMN IF EXISTS connector_refund_data; ALTER TABLE refund -ALTER COLUMN connector_refund_id TYPE VARCHAR(128); \ No newline at end of file +DROP COLUMN IF EXISTS connector_transaction_data; + +ALTER TABLE captures +DROP COLUMN IF EXISTS connector_capture_data; \ No newline at end of file diff --git a/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/up.sql b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/up.sql index e57da0ce6634..adab6dd3edf7 100644 --- a/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/up.sql +++ b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/up.sql @@ -1,8 +1,11 @@ ALTER TABLE payment_attempt -ALTER COLUMN connector_transaction_id TYPE VARCHAR(512); +ADD COLUMN IF NOT EXISTS connector_transaction_data VARCHAR(512); ALTER TABLE refund -ALTER COLUMN connector_transaction_id TYPE VARCHAR(512); +ADD COLUMN IF NOT EXISTS connector_refund_data VARCHAR(512); ALTER TABLE refund -ALTER COLUMN connector_refund_id TYPE VARCHAR(512); \ No newline at end of file +ADD COLUMN IF NOT EXISTS connector_transaction_data VARCHAR(512); + +ALTER TABLE captures +ADD COLUMN IF NOT EXISTS connector_capture_data VARCHAR(512); \ No newline at end of file From 3984237169ffc1b1edf7ca57c5baf7e376c47648 Mon Sep 17 00:00:00 2001 From: Kashif Date: Mon, 7 Oct 2024 19:42:09 +0530 Subject: [PATCH 10/28] refactor: re-generate schema_v2 --- crates/diesel_models/src/schema_v2.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index b5d6b0db51dc..d23df9bbaa6f 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -255,6 +255,8 @@ diesel::table! { capture_sequence -> Int2, #[max_length = 128] connector_response_reference_id -> Nullable, + #[max_length = 512] + connector_capture_data -> Nullable, } } @@ -817,6 +819,8 @@ diesel::table! { id -> Varchar, shipping_cost -> Nullable, order_tax_amount -> Nullable, + #[max_length = 512] + connector_transaction_data -> Nullable, } } @@ -1094,11 +1098,11 @@ diesel::table! { payment_id -> Varchar, #[max_length = 64] merchant_id -> Varchar, - #[max_length = 512] + #[max_length = 128] connector_transaction_id -> Varchar, #[max_length = 64] connector -> Varchar, - #[max_length = 512] + #[max_length = 128] connector_refund_id -> Nullable, #[max_length = 64] external_reference_id -> Nullable, @@ -1130,6 +1134,10 @@ diesel::table! { charges -> Nullable, #[max_length = 32] organization_id -> Varchar, + #[max_length = 512] + connector_refund_data -> Nullable, + #[max_length = 512] + connector_transaction_data -> Nullable, } } From 33fb8cb3a1fb4a491bb577f2a784992726d82781 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:13:02 +0000 Subject: [PATCH 11/28] chore: run formatter --- crates/diesel_models/src/query/capture.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/diesel_models/src/query/capture.rs b/crates/diesel_models/src/query/capture.rs index 7111236b7b8e..7a32e4109617 100644 --- a/crates/diesel_models/src/query/capture.rs +++ b/crates/diesel_models/src/query/capture.rs @@ -1,3 +1,6 @@ +use common_utils::types::ConnectorTransactionIdTrait; +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; + use super::generics; use crate::{ capture::{Capture, CaptureNew, CaptureUpdate, CaptureUpdateInternal}, @@ -5,8 +8,6 @@ use crate::{ schema::captures::dsl, PgPooledConn, StorageResult, }; -use common_utils::types::ConnectorTransactionIdTrait; -use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; impl CaptureNew { pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { From bdeeb8d002eaca8964ae490652d3c92d4fc882a9 Mon Sep 17 00:00:00 2001 From: Kashif Date: Tue, 8 Oct 2024 10:51:08 +0530 Subject: [PATCH 12/28] refactor: v2 support --- crates/common_utils/src/types.rs | 4 +- crates/diesel_models/src/payment_attempt.rs | 5 +- crates/diesel_models/src/schema_v2.rs | 4 +- .../src/payments/payment_attempt.rs | 31 +++++++++- crates/router/src/connector/worldpay.rs | 2 +- .../fraud_check/flows/transaction_flow.rs | 6 +- crates/router/src/core/payments.rs | 6 +- .../payments/operations/payment_response.rs | 8 +-- .../router/src/core/payments/transformers.rs | 23 ++++---- crates/router/src/core/refunds.rs | 2 +- crates/router/src/db/address.rs | 12 ++-- crates/router/src/db/customers.rs | 20 +++---- crates/router/src/db/mandate.rs | 16 ++--- crates/router/src/db/payment_method.rs | 20 +++---- crates/router/src/db/refund.rs | 28 ++++----- crates/router/src/db/reverse_lookup.rs | 8 +-- crates/router/src/types/transformers.rs | 4 +- crates/storage_impl/src/lookup.rs | 8 +-- .../src/payments/payment_attempt.rs | 58 +++++++++++-------- .../src/payments/payment_intent.rs | 12 ++-- .../src/payouts/payout_attempt.rs | 16 ++--- crates/storage_impl/src/payouts/payouts.rs | 16 ++--- crates/storage_impl/src/redis/kv_store.rs | 8 ++- .../2024-08-28-081721_add_v2_columns/up.sql | 3 +- .../2024-08-28-081847_drop_v1_columns/up.sql | 1 + 25 files changed, 180 insertions(+), 141 deletions(-) diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index d642d80697a7..7569049891a2 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -1302,12 +1302,12 @@ impl ConnectorTransactionId { impl From for ConnectorTransactionId { fn from(src: String) -> Self { // ID already hashed - if src.starts_with("hash_") { + if src.starts_with("hs_hash_") { Self::HashedData(src) // Hash connector's transaction ID } else if src.len() > 128 { let hash = blake3::hash(src.as_bytes()); - Self::HashedData(format!("hash_{}", hash.to_hex())) + Self::HashedData(format!("hs_hash_{}", hash.to_hex())) // Default } else { Self::TxnId(src) diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 542abd448754..3751491cd606 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -66,12 +66,13 @@ pub struct PaymentAttempt { pub organization_id: id_type::OrganizationId, pub card_network: Option, pub payment_method_type_v2: Option, - pub connector_payment_id: Option, + pub connector_payment_id: Option, pub payment_method_subtype: Option, pub routing_result: Option, pub authentication_applied: Option, pub external_reference_id: Option, pub tax_on_surcharge: Option, + pub connector_payment_data: Option, pub id: String, pub shipping_cost: Option, pub order_tax_amount: Option, @@ -228,6 +229,7 @@ pub struct PaymentAttemptNew { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_payment_data: Option, } #[cfg(feature = "v1")] @@ -771,6 +773,7 @@ pub struct PaymentAttemptUpdateInternal { client_version: Option, customer_acceptance: Option, card_network: Option, + connector_payment_data: Option, } #[cfg(feature = "v1")] diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index d23df9bbaa6f..99a4453bba2b 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -815,12 +815,12 @@ diesel::table! { #[max_length = 128] external_reference_id -> Nullable, tax_on_surcharge -> Nullable, + #[max_length = 512] + connector_payment_data -> Nullable, #[max_length = 64] id -> Varchar, shipping_cost -> Nullable, order_tax_amount -> Nullable, - #[max_length = 512] - connector_transaction_data -> Nullable, } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 97edb89be377..0a0a3ea06ff4 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -215,13 +215,14 @@ pub struct PaymentAttempt { pub organization_id: id_type::OrganizationId, pub payment_method_type: Option, pub payment_method_id: Option, - pub connector_payment_id: Option, + pub connector_payment_id: Option, pub payment_method_subtype: Option, pub authentication_applied: Option, pub external_reference_id: Option, pub shipping_cost: Option, pub order_tax_amount: Option, pub id: String, + pub connector_payment_data: Option, } impl PaymentAttempt { @@ -263,7 +264,8 @@ impl PaymentAttempt { #[cfg(feature = "v2")] pub fn get_connector_payment_id(&self) -> Option<&str> { - self.connector_payment_id.as_deref() + self.get_optional_connector_transaction_id() + .map(|x| x.as_str()) } } @@ -365,6 +367,7 @@ impl PaymentAttempt { } } +#[cfg(feature = "v1")] impl ConnectorTransactionIdTrait for PaymentAttempt { fn get_optional_connector_transaction_id(&self) -> Option<&String> { match self @@ -384,6 +387,26 @@ impl ConnectorTransactionIdTrait for PaymentAttempt { } } +#[cfg(feature = "v2")] +impl ConnectorTransactionIdTrait for PaymentAttempt { + fn get_optional_connector_transaction_id(&self) -> Option<&String> { + match self + .connector_payment_id + .as_ref() + .map(|txn_id| txn_id.get_txn_id(self.connector_payment_data.as_ref())) + .transpose() + { + Ok(txn_id) => txn_id, + + // In case hashed data is missing from DB, use the hashed ID as connector payment ID + Err(_) => self + .connector_payment_id + .as_ref() + .map(|txn_id| txn_id.get_id()), + } + } +} + #[derive(Clone, Debug, Eq, PartialEq)] pub struct PaymentListFilters { pub connector: Vec, @@ -1077,6 +1100,7 @@ impl behaviour::Conversion for PaymentAttempt { shipping_cost, order_tax_amount, connector, + connector_payment_data, } = self; Ok(DieselPaymentAttempt { @@ -1134,6 +1158,7 @@ impl behaviour::Conversion for PaymentAttempt { authentication_applied, external_reference_id, connector, + connector_payment_data, }) } @@ -1202,6 +1227,7 @@ impl behaviour::Conversion for PaymentAttempt { authentication_applied: storage_model.authentication_applied, external_reference_id: storage_model.external_reference_id, connector: storage_model.connector, + connector_payment_data: storage_model.connector_payment_data, }) } .await @@ -1269,6 +1295,7 @@ impl behaviour::Conversion for PaymentAttempt { order_tax_amount: self.order_tax_amount, shipping_cost: self.shipping_cost, amount_to_capture: self.amount_to_capture, + connector_payment_data: self.connector_payment_data, }) } } diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index e6707c9d4259..465c64e9a5b7 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -105,7 +105,7 @@ impl ConnectorCommon for Worldpay { res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - let response = if res.response.len() > 0 { + let response = if !res.response.is_empty() { res.response .parse_struct("WorldpayErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)? diff --git a/crates/router/src/core/fraud_check/flows/transaction_flow.rs b/crates/router/src/core/fraud_check/flows/transaction_flow.rs index 4abe37300d8e..5e5cc50ee8d9 100644 --- a/crates/router/src/core/fraud_check/flows/transaction_flow.rs +++ b/crates/router/src/core/fraud_check/flows/transaction_flow.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use common_utils::{ext_traits::ValueExt, types::ConnectorTransactionIdTrait}; +use common_utils::ext_traits::ValueExt; use error_stack::ResultExt; use crate::{ @@ -104,8 +104,8 @@ impl error_message: self.payment_attempt.error_message.clone(), connector_transaction_id: self .payment_attempt - .get_optional_connector_transaction_id() - .cloned(), + .get_connector_payment_id() + .map(ToString::to_string), connector: self.payment_attempt.connector.clone(), }, // self.order_details response: Ok(FraudCheckResponseData::TransactionResponse { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 787dbef6dc5b..6e8c1ecb1322 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -28,7 +28,7 @@ pub use common_enums::enums::CallConnectorAction; use common_utils::{ ext_traits::{AsyncExt, StringExt}, id_type, pii, - types::{ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit, Surcharge}, + types::{ConnectorTransactionId, MinorUnit, Surcharge}, }; use diesel_models::{ephemeral_key, fraud_check::FraudCheck}; use error_stack::{report, ResultExt}; @@ -5075,8 +5075,8 @@ pub async fn payments_manual_update( Ok(services::ApplicationResponse::Json( api_models::payments::PaymentsManualUpdateResponse { connector_transaction_id: updated_payment_attempt - .get_optional_connector_transaction_id() - .cloned(), + .get_connector_payment_id() + .map(ToString::to_string), payment_id: updated_payment_attempt.payment_id, attempt_id: updated_payment_attempt.attempt_id, merchant_id: updated_payment_attempt.merchant_id, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 6a558913d36f..1dd8acd097a8 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -6,9 +6,7 @@ use async_trait::async_trait; use common_enums::AuthorizationStatus; use common_utils::{ ext_traits::{AsyncExt, Encode}, - types::{ - keymanager::KeyManagerState, ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit, - }, + types::{keymanager::KeyManagerState, ConnectorTransactionId, MinorUnit}, }; use error_stack::{report, ResultExt}; use futures::FutureExt; @@ -1810,8 +1808,8 @@ async fn payment_response_update_tracker( errors::ApiErrorResponse::IntegrityCheckFailed { connector_transaction_id: payment_data .payment_attempt - .get_optional_connector_transaction_id() - .cloned(), + .get_connector_payment_id() + .map(ToString::to_string), reason: payment_data .payment_attempt .error_message diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index b8cc1c5fbf6e..5ae6dbfb6bdf 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -9,10 +9,7 @@ use common_utils::{ consts::X_HS_LATENCY, fp_utils, pii::Email, - types::{ - self as common_utils_type, AmountConvertor, ConnectorTransactionIdTrait, MinorUnit, - StringMajorUnitForConnector, - }, + types::{self as common_utils_type, AmountConvertor, MinorUnit, StringMajorUnitForConnector}, }; use diesel_models::ephemeral_key; use error_stack::{report, ResultExt}; @@ -207,8 +204,8 @@ where let resource_id = match payment_data .payment_attempt - .get_optional_connector_transaction_id() - .cloned() + .get_connector_payment_id() + .map(ToString::to_string) { Some(id) => types::ResponseId::ConnectorTransactionId(id), None => types::ResponseId::NoResponseId, @@ -1124,8 +1121,8 @@ where }); let connector_transaction_id = payment_attempt - .get_optional_connector_transaction_id() - .cloned(); + .get_connector_payment_id() + .map(ToString::to_string); let payments_response = api::PaymentsResponse { payment_id: payment_intent.payment_id, @@ -1360,7 +1357,7 @@ pub fn wait_screen_next_steps_check( #[cfg(feature = "v1")] impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::PaymentsResponse { fn foreign_from((pi, pa): (storage::PaymentIntent, storage::PaymentAttempt)) -> Self { - let connector_transaction_id = pa.get_optional_connector_transaction_id().cloned(); + let connector_transaction_id = pa.get_connector_payment_id().map(ToString::to_string); Self { payment_id: pi.payment_id, merchant_id: pi.merchant_id, @@ -2310,8 +2307,8 @@ impl TryFrom> for types::CompleteAuthoriz payment_method_data: payment_data.payment_method_data.map(From::from), connector_transaction_id: payment_data .payment_attempt - .get_optional_connector_transaction_id() - .cloned(), + .get_connector_payment_id() + .map(ToString::to_string), redirect_response, connector_meta: payment_data.payment_attempt.connector_metadata, complete_authorize_url, @@ -2416,8 +2413,8 @@ impl TryFrom> for types::PaymentsPreProce surcharge_details: payment_data.surcharge_details, connector_transaction_id: payment_data .payment_attempt - .get_optional_connector_transaction_id() - .cloned(), + .get_connector_payment_id() + .map(ToString::to_string), redirect_response: None, mandate_id: payment_data.mandate_id, related_transaction_id: None, diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index 0ec466d1e4e3..150b5502cb0a 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -791,7 +791,7 @@ pub async fn validate_and_create_refund( let all_refunds = db .find_refund_by_merchant_id_connector_transaction_id( merchant_account.get_id(), - &connector_transaction_id_str, + connector_transaction_id_str, merchant_account.storage_scheme, ) .await diff --git a/crates/router/src/db/address.rs b/crates/router/src/db/address.rs index 335110248ab3..3a8750feff93 100644 --- a/crates/router/src/db/address.rs +++ b/crates/router/src/db/address.rs @@ -391,11 +391,11 @@ mod storage { let field = format!("add_{}", address_id); Box::pin(db_utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&field), key, - ) + )) .await? .try_into_hget() }, @@ -502,14 +502,14 @@ mod storage { }, }; - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::Hset::( (&field, redis_value), redis_entry, ), key, - ) + )) .await .change_context(errors::StorageError::KVError)? .try_into_hset() @@ -601,7 +601,7 @@ mod storage { }, }; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::HSetNx::( &field, @@ -609,7 +609,7 @@ mod storage { redis_entry, ), key, - ) + )) .await .change_context(errors::StorageError::KVError)? .try_into_hsetnx() diff --git a/crates/router/src/db/customers.rs b/crates/router/src/db/customers.rs index 9cd044f69eb1..5020c238e4a6 100644 --- a/crates/router/src/db/customers.rs +++ b/crates/router/src/db/customers.rs @@ -219,11 +219,11 @@ mod storage { Box::pin(db_utils::try_redis_get_else_try_database_get( // check for ValueNotFound async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&field), key, - ) + )) .await? .try_into_hget() .map(Some) @@ -293,11 +293,11 @@ mod storage { Box::pin(db_utils::try_redis_get_else_try_database_get( // check for ValueNotFound async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&field), key, - ) + )) .await? .try_into_hget() .map(Some) @@ -453,14 +453,14 @@ mod storage { }, }; - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::Hset::( (&field, redis_value), redis_entry, ), key, - ) + )) .await .change_context(errors::StorageError::KVError)? .try_into_hset() @@ -584,11 +584,11 @@ mod storage { let field = format!("cust_{}", customer_id.get_string_repr()); Box::pin(db_utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&field), key, - ) + )) .await? .try_into_hget() }, @@ -773,7 +773,7 @@ mod storage { }; let storage_customer = new_customer.into(); - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::HSetNx::( &field, @@ -781,7 +781,7 @@ mod storage { redis_entry, ), key, - ) + )) .await .change_context(errors::StorageError::KVError)? .try_into_hsetnx() diff --git a/crates/router/src/db/mandate.rs b/crates/router/src/db/mandate.rs index 40e5f88614d5..95733805b436 100644 --- a/crates/router/src/db/mandate.rs +++ b/crates/router/src/db/mandate.rs @@ -114,11 +114,11 @@ mod storage { Box::pin(db_utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&field), key, - ) + )) .await? .try_into_hget() }, @@ -172,11 +172,11 @@ mod storage { Box::pin(db_utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -281,14 +281,14 @@ mod storage { }, }; - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::::Hset( (&field, redis_value), redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hset() @@ -369,7 +369,7 @@ mod storage { .await?; } - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::::HSetNx( &field, @@ -377,7 +377,7 @@ mod storage { redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hsetnx() diff --git a/crates/router/src/db/payment_method.rs b/crates/router/src/db/payment_method.rs index 6e052076799a..e75fb940a35b 100644 --- a/crates/router/src/db/payment_method.rs +++ b/crates/router/src/db/payment_method.rs @@ -210,13 +210,13 @@ mod storage { Box::pin(db_utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet( &lookup.sk_id, ), key, - ) + )) .await? .try_into_hget() }, @@ -348,13 +348,13 @@ mod storage { Box::pin(db_utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet( &lookup.sk_id, ), key, - ) + )) .await? .try_into_hget() }, @@ -500,7 +500,7 @@ mod storage { }, }; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::::HSetNx( &field, @@ -508,7 +508,7 @@ mod storage { redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hsetnx() @@ -597,14 +597,14 @@ mod storage { }, }; - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::::Hset( (&field, redis_value), redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hset() @@ -743,11 +743,11 @@ mod storage { let pattern = "payment_method_id_*"; let redis_fut = async { - let kv_result = kv_wrapper::( + let kv_result = Box::pin(kv_wrapper::( self, KvOperation::::Scan(pattern), key, - ) + )) .await? .try_into_scan(); kv_result.map(|payment_methods| { diff --git a/crates/router/src/db/refund.rs b/crates/router/src/db/refund.rs index c21e3cf11031..41cb3cef5c63 100644 --- a/crates/router/src/db/refund.rs +++ b/crates/router/src/db/refund.rs @@ -361,11 +361,11 @@ mod storage { }; Box::pin(db_utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -496,7 +496,7 @@ mod storage { futures::future::try_join_all(rev_look).await?; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::::HSetNx( &field, @@ -504,7 +504,7 @@ mod storage { redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hsetnx() @@ -565,11 +565,11 @@ mod storage { Box::pin(db_utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::Scan(&pattern), key, - ) + )) .await? .try_into_scan() }, @@ -624,14 +624,14 @@ mod storage { }, }; - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::Hset::( (&field, redis_value), redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hset() @@ -677,11 +677,11 @@ mod storage { }; Box::pin(db_utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -735,11 +735,11 @@ mod storage { }; Box::pin(db_utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -782,11 +782,11 @@ mod storage { }; Box::pin(db_utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::Scan("pa_*_ref_*"), key, - ) + )) .await? .try_into_scan() }, diff --git a/crates/router/src/db/reverse_lookup.rs b/crates/router/src/db/reverse_lookup.rs index d51ed5ed385e..06bd84675b11 100644 --- a/crates/router/src/db/reverse_lookup.rs +++ b/crates/router/src/db/reverse_lookup.rs @@ -120,7 +120,7 @@ mod storage { }, }; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::SetNx(&created_rev_lookup, redis_entry), PartitionKey::CombinationKey { @@ -129,7 +129,7 @@ mod storage { &created_rev_lookup.lookup_id ), }, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&created_rev_lookup.lookup_id))? .try_into_setnx() @@ -168,13 +168,13 @@ mod storage { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { let redis_fut = async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::Get, PartitionKey::CombinationKey { combination: &format!("reverse_lookup_{id}"), }, - ) + )) .await? .try_into_get() }; diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 72bf77793885..d3faaa62a426 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1293,8 +1293,8 @@ impl ForeignTryFrom impl ForeignFrom for payments::PaymentAttemptResponse { fn foreign_from(payment_attempt: storage::PaymentAttempt) -> Self { let connector_transaction_id = payment_attempt - .get_optional_connector_transaction_id() - .cloned(); + .get_connector_payment_id() + .map(ToString::to_string); Self { attempt_id: payment_attempt.attempt_id, status: payment_attempt.status, diff --git a/crates/storage_impl/src/lookup.rs b/crates/storage_impl/src/lookup.rs index 943ef1f36f73..54377e452673 100644 --- a/crates/storage_impl/src/lookup.rs +++ b/crates/storage_impl/src/lookup.rs @@ -97,13 +97,13 @@ impl ReverseLookupInterface for KVRouterStore { }, }; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::SetNx(&created_rev_lookup, redis_entry), PartitionKey::CombinationKey { combination: &format!("reverse_lookup_{}", &created_rev_lookup.lookup_id), }, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&created_rev_lookup.lookup_id))? .try_into_setnx() @@ -140,13 +140,13 @@ impl ReverseLookupInterface for KVRouterStore { storage_enums::MerchantStorageScheme::PostgresOnly => database_call().await, storage_enums::MerchantStorageScheme::RedisKv => { let redis_fut = async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::Get, PartitionKey::CombinationKey { combination: &format!("reverse_lookup_{id}"), }, - ) + )) .await? .try_into_get() }; diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 7859fa7ac4c9..105fe7987c10 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -564,7 +564,7 @@ impl PaymentAttemptInterface for KVRouterStore { self.insert_reverse_lookup(reverse_lookup, storage_scheme) .await?; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::HSetNx( &field, @@ -572,7 +572,7 @@ impl PaymentAttemptInterface for KVRouterStore { redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hsetnx() @@ -636,7 +636,7 @@ impl PaymentAttemptInterface for KVRouterStore { } MerchantStorageScheme::RedisKv => { let key_str = key.to_string(); - let old_connector_transaction_id = &this.connector_transaction_id; + let old_connector_transaction_id = &this.get_connector_payment_id(); let old_preprocessing_id = &this.preprocessing_step_id; let updated_attempt = PaymentAttempt::from_storage_model( payment_attempt @@ -661,7 +661,7 @@ impl PaymentAttemptInterface for KVRouterStore { match ( old_connector_transaction_id, - &updated_attempt.connector_transaction_id, + &updated_attempt.get_connector_payment_id(), ) { (None, Some(connector_transaction_id)) => { add_connector_txn_id_to_reverse_lookup( @@ -669,7 +669,7 @@ impl PaymentAttemptInterface for KVRouterStore { key_str.as_str(), &this.merchant_id, updated_attempt.attempt_id.as_str(), - &connector_transaction_id.get_id(), + connector_transaction_id, storage_scheme, ) .await?; @@ -681,7 +681,7 @@ impl PaymentAttemptInterface for KVRouterStore { key_str.as_str(), &this.merchant_id, updated_attempt.attempt_id.as_str(), - &connector_transaction_id.get_id(), + connector_transaction_id, storage_scheme, ) .await?; @@ -718,11 +718,11 @@ impl PaymentAttemptInterface for KVRouterStore { (_, _) => {} } - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::Hset::((&field, redis_value), redis_entry), key, - ) + )) .await .change_context(errors::StorageError::KVError)? .try_into_hset() @@ -806,7 +806,7 @@ impl PaymentAttemptInterface for KVRouterStore { Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper(self, KvOperation::::HGet(&lookup.sk_id), key).await?.try_into_hget() + Box::pin(kv_wrapper(self, KvOperation::::HGet(&lookup.sk_id), key)).await?.try_into_hget() }, || async {self.router_store.find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id(connector_transaction_id, payment_id, merchant_id, storage_scheme).await}, )) @@ -847,11 +847,11 @@ impl PaymentAttemptInterface for KVRouterStore { let pattern = "pa_*"; let redis_fut = async { - let kv_result = kv_wrapper::( + let kv_result = Box::pin(kv_wrapper::( self, KvOperation::::Scan(pattern), key, - ) + )) .await? .try_into_scan(); kv_result.and_then(|mut payment_attempts| { @@ -906,11 +906,11 @@ impl PaymentAttemptInterface for KVRouterStore { let pattern = "pa_*"; let redis_fut = async { - let kv_result = kv_wrapper::( + let kv_result = Box::pin(kv_wrapper::( self, KvOperation::::Scan(pattern), key, - ) + )) .await? .try_into_scan(); kv_result.and_then(|mut payment_attempts| { @@ -982,11 +982,11 @@ impl PaymentAttemptInterface for KVRouterStore { }; Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -1039,9 +1039,13 @@ impl PaymentAttemptInterface for KVRouterStore { let field = format!("pa_{attempt_id}"); Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper(self, KvOperation::::HGet(&field), key) - .await? - .try_into_hget() + Box::pin(kv_wrapper( + self, + KvOperation::::HGet(&field), + key, + )) + .await? + .try_into_hget() }, || async { self.router_store @@ -1102,11 +1106,11 @@ impl PaymentAttemptInterface for KVRouterStore { }; Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -1191,11 +1195,11 @@ impl PaymentAttemptInterface for KVRouterStore { Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -1245,9 +1249,13 @@ impl PaymentAttemptInterface for KVRouterStore { }; Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper(self, KvOperation::::Scan("pa_*"), key) - .await? - .try_into_scan() + Box::pin(kv_wrapper( + self, + KvOperation::::Scan("pa_*"), + key, + )) + .await? + .try_into_scan() }, || async { self.router_store diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 4c7232f6f3f5..c0b37e071eb7 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -113,7 +113,7 @@ impl PaymentIntentInterface for KVRouterStore { .await .change_context(StorageError::EncryptionError)?; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::::HSetNx( &field, @@ -121,7 +121,7 @@ impl PaymentIntentInterface for KVRouterStore { redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hsetnx() @@ -228,11 +228,11 @@ impl PaymentIntentInterface for KVRouterStore { }, }; - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::::Hset((&field, redis_value), redis_entry), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hset() @@ -316,11 +316,11 @@ impl PaymentIntentInterface for KVRouterStore { let field = payment_id.get_hash_key_for_kv_store(); Box::pin(utils::try_redis_get_else_try_database_get( async { - kv_wrapper::( + Box::pin(kv_wrapper::( self, KvOperation::::HGet(&field), key, - ) + )) .await? .try_into_hget() }, diff --git a/crates/storage_impl/src/payouts/payout_attempt.rs b/crates/storage_impl/src/payouts/payout_attempt.rs index 33d73f99e5f7..81d06a1cbd5f 100644 --- a/crates/storage_impl/src/payouts/payout_attempt.rs +++ b/crates/storage_impl/src/payouts/payout_attempt.rs @@ -115,7 +115,7 @@ impl PayoutAttemptInterface for KVRouterStore { self.insert_reverse_lookup(reverse_lookup, storage_scheme) .await?; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::::HSetNx( &field, @@ -123,7 +123,7 @@ impl PayoutAttemptInterface for KVRouterStore { redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hsetnx() @@ -227,11 +227,11 @@ impl PayoutAttemptInterface for KVRouterStore { _ => {} } - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::::Hset((&field, redis_value), redis_entry), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hset() @@ -284,11 +284,11 @@ impl PayoutAttemptInterface for KVRouterStore { }; Box::pin(utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -345,11 +345,11 @@ impl PayoutAttemptInterface for KVRouterStore { }; Box::pin(utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, diff --git a/crates/storage_impl/src/payouts/payouts.rs b/crates/storage_impl/src/payouts/payouts.rs index 2a49b01458f7..9a94743da3f1 100644 --- a/crates/storage_impl/src/payouts/payouts.rs +++ b/crates/storage_impl/src/payouts/payouts.rs @@ -134,7 +134,7 @@ impl PayoutsInterface for KVRouterStore { }, }; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::::HSetNx( &field, @@ -142,7 +142,7 @@ impl PayoutsInterface for KVRouterStore { redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hsetnx() @@ -208,11 +208,11 @@ impl PayoutsInterface for KVRouterStore { }, }; - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::::Hset((&field, redis_value), redis_entry), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hset() @@ -255,11 +255,11 @@ impl PayoutsInterface for KVRouterStore { let field = format!("po_{payout_id}"); Box::pin(utils::try_redis_get_else_try_database_get( async { - kv_wrapper::( + Box::pin(kv_wrapper::( self, KvOperation::::HGet(&field), key, - ) + )) .await? .try_into_hget() }, @@ -312,11 +312,11 @@ impl PayoutsInterface for KVRouterStore { let field = format!("po_{payout_id}"); Box::pin(utils::try_redis_get_else_try_database_get( async { - kv_wrapper::( + Box::pin(kv_wrapper::( self, KvOperation::::HGet(&field), key, - ) + )) .await? .try_into_hget() .map(Some) diff --git a/crates/storage_impl/src/redis/kv_store.rs b/crates/storage_impl/src/redis/kv_store.rs index 74b1526fe8e8..2fde935b670d 100644 --- a/crates/storage_impl/src/redis/kv_store.rs +++ b/crates/storage_impl/src/redis/kv_store.rs @@ -312,8 +312,12 @@ where Op::Find => MerchantStorageScheme::RedisKv, Op::Update(_, _, Some("postgres_only")) => MerchantStorageScheme::PostgresOnly, Op::Update(partition_key, field, Some(_updated_by)) => { - match kv_wrapper::(store, KvOperation::::HGet(field), partition_key) - .await + match Box::pin(kv_wrapper::( + store, + KvOperation::::HGet(field), + partition_key, + )) + .await { Ok(_) => { metrics::KV_SOFT_KILL_ACTIVE_UPDATE.add(&metrics::CONTEXT, 1, &[]); diff --git a/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql b/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql index 520bbdf6e7e1..136bc550811a 100644 --- a/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql +++ b/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql @@ -41,4 +41,5 @@ ADD COLUMN payment_method_type_v2 VARCHAR, ADD COLUMN routing_result JSONB, ADD COLUMN authentication_applied "AuthenticationType", ADD COLUMN external_reference_id VARCHAR(128), - ADD COLUMN tax_on_surcharge BIGINT; + ADD COLUMN tax_on_surcharge BIGINT, + ADD COLUMN connector_payment_data VARCHAR(512); diff --git a/v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql b/v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql index 55b0b19d0b41..48ae811bf343 100644 --- a/v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql +++ b/v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql @@ -74,6 +74,7 @@ ALTER TABLE payment_attempt DROP COLUMN attempt_id, DROP COLUMN offer_amount, DROP COLUMN payment_method, DROP COLUMN connector_transaction_id, + DROP COLUMN connector_transaction_data, DROP COLUMN capture_method, DROP COLUMN capture_on, DROP COLUMN mandate_id, From 9003400e866bfc6652bbe2c5d9450e50f0067205 Mon Sep 17 00:00:00 2001 From: Kashif Date: Tue, 8 Oct 2024 12:21:09 +0530 Subject: [PATCH 13/28] refactor: rename drop v1 queries for ordering --- .../down.sql | 0 .../up.sql | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename v2_migrations/{2024-08-28-081847_drop_v1_columns => 2024-10-08-081847_drop_v1_columns}/down.sql (100%) rename v2_migrations/{2024-08-28-081847_drop_v1_columns => 2024-10-08-081847_drop_v1_columns}/up.sql (100%) diff --git a/v2_migrations/2024-08-28-081847_drop_v1_columns/down.sql b/v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql similarity index 100% rename from v2_migrations/2024-08-28-081847_drop_v1_columns/down.sql rename to v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql diff --git a/v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql b/v2_migrations/2024-10-08-081847_drop_v1_columns/up.sql similarity index 100% rename from v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql rename to v2_migrations/2024-10-08-081847_drop_v1_columns/up.sql From 6c149d252f4fbf9da2d104fdd580df728a2c14c3 Mon Sep 17 00:00:00 2001 From: Kashif Date: Wed, 9 Oct 2024 09:53:50 +0530 Subject: [PATCH 14/28] refactor(worldpay): form new status based on existing intent and attempt status --- .../src/router_request_types.rs | 4 ++++ crates/router/src/connector/worldpay.rs | 17 +++++++++++++---- .../router/src/connector/worldpay/response.rs | 19 +++++++++++++------ .../src/connector/worldpay/transformers.rs | 7 +++---- crates/router/src/core/payments.rs | 9 +++++++++ .../router/src/core/payments/transformers.rs | 3 +++ 6 files changed, 45 insertions(+), 14 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index d26f1975b71f..03ae5dee03cc 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -1,6 +1,7 @@ pub mod authentication; pub mod fraud_check; use api_models::payments::{Address, RequestSurchargeDetails}; +use common_enums::{AttemptStatus, IntentStatus}; use common_utils::{ consts, errors, ext_traits::OptionExt, @@ -107,6 +108,7 @@ pub struct PaymentsCaptureData { pub browser_info: Option, pub metadata: Option, // This metadata is used to store the metadata shared during the payment intent request. + pub capture_method: Option, // New amount for amount frame work pub minor_payment_amount: MinorUnit, @@ -428,6 +430,8 @@ pub struct PaymentsSyncData { pub amount: MinorUnit, pub integrity_object: Option, + pub attempt_status: AttemptStatus, + pub intent_status: IntentStatus, } #[derive(Debug, Default, Clone)] diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index 465c64e9a5b7..f542d700fb77 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -110,7 +110,7 @@ impl ConnectorCommon for Worldpay { .parse_struct("WorldpayErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)? } else { - WorldpayErrorResponse::default() + WorldpayErrorResponse::default(res.status_code) }; event_builder.map(|i| i.set_error_response_body(&response)); @@ -349,8 +349,17 @@ impl ConnectorIntegration attempt_status, + (enums::AttemptStatus::Pending, _, EventType::Authorized) => attempt_status, + _ => enums::AttemptStatus::from(&worldpay_status), + }; + Ok(types::PaymentsSyncRouterData { - status: enums::AttemptStatus::from(response.last_event), + status, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: data.request.connector_transaction_id.clone(), redirection_data: None, @@ -444,7 +453,7 @@ impl ConnectorIntegration, } -impl Default for WorldpayErrorResponse { - fn default() -> Self { - Self { - error_name: "Not found".to_string(), - message: "Resource not found".to_string(), - validation_errors: None, +impl WorldpayErrorResponse { + pub fn default(status_code: u16) -> Self { + match status_code { + code @ 404 => Self { + error_name: format!("{} Not found", code), + message: "Resource not found".to_string(), + validation_errors: None, + }, + code => Self { + error_name: code.to_string(), + message: "Unknown error".to_string(), + validation_errors: None, + }, } } } diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index 1a8ba5a97487..a14877a29ab8 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -255,8 +255,8 @@ impl From for enums::AttemptStatus { } } -impl From for enums::AttemptStatus { - fn from(value: EventType) -> Self { +impl From<&EventType> for enums::AttemptStatus { + fn from(value: &EventType) -> Self { match value { EventType::Authorized => Self::Authorized, EventType::CaptureFailed => Self::CaptureFailed, @@ -276,12 +276,11 @@ impl From for enums::AttemptStatus { impl From for enums::RefundStatus { fn from(value: EventType) -> Self { match value { - EventType::Refunded => Self::Success, + EventType::Refunded | EventType::SentForRefund => Self::Success, EventType::RefundFailed => Self::Failure, EventType::Authorized | EventType::Cancelled | EventType::Charged - | EventType::SentForRefund | EventType::Refused | EventType::Error | EventType::SentForSettlement diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 6e8c1ecb1322..49f8cf356049 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -5120,6 +5120,7 @@ pub trait OperationSessionGetters { fn get_mandate_connector(&self) -> Option<&MandateConnectorDetails>; fn get_force_sync(&self) -> Option; fn get_capture_method(&self) -> Option; + fn get_intent_status(&self) -> enums::IntentStatus; } pub trait OperationSessionSetters { @@ -5297,6 +5298,10 @@ impl OperationSessionGetters for PaymentData { fn get_capture_method(&self) -> Option { self.payment_intent.capture_method } + + fn get_intent_status(&self) -> enums::IntentStatus { + self.payment_intent.status + } } impl OperationSessionSetters for PaymentData { @@ -5518,6 +5523,10 @@ impl OperationSessionGetters for PaymentIntentData { fn get_capture_method(&self) -> Option { todo!() } + + fn get_intent_status(&self) -> enums::IntentStatus { + self.payment_intent.status + } } #[cfg(feature = "v2")] diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 5ae6dbfb6bdf..3c3eb98c20ee 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1817,6 +1817,8 @@ impl TryFrom> for types::PaymentsSyncData } None => types::ResponseId::NoResponseId, }, + intent_status: payment_data.get_intent_status(), + attempt_status: payment_data.payment_attempt.status, encoded_data: payment_data.payment_attempt.encoded_data, capture_method, connector_meta: payment_data.payment_attempt.connector_metadata, @@ -1953,6 +1955,7 @@ impl TryFrom> for types::PaymentsCaptureD })?; let amount = MinorUnit::from(payment_data.amount); Ok(Self { + capture_method: payment_data.get_capture_method(), amount_to_capture: amount_to_capture.get_amount_as_i64(), // This should be removed once we start moving to connector module minor_amount_to_capture: amount_to_capture, currency: payment_data.currency, From 8cd8aa69a5725c046cffd5a93aca6b1016e068f8 Mon Sep 17 00:00:00 2001 From: Kashif Date: Wed, 9 Oct 2024 10:33:10 +0530 Subject: [PATCH 15/28] refactor: limit hashed connector's txn ID length to 50 characters and update v2_migrations down query --- crates/common_utils/src/consts.rs | 4 ++++ crates/common_utils/src/types.rs | 8 ++++++-- v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs index 00ffb614bb05..21e5866aea00 100644 --- a/crates/common_utils/src/consts.rs +++ b/crates/common_utils/src/consts.rs @@ -154,3 +154,7 @@ pub const MAX_DESCRIPTION_LENGTH: u16 = 255; pub const MAX_STATEMENT_DESCRIPTOR_LENGTH: u16 = 22; /// Payout flow identifier used for performing GSM operations pub const PAYOUT_FLOW_STR: &str = "payout_flow"; + +/// The number of bytes allocated for the hashed connector transaction ID. +/// Total number of characters equals CONNECTOR_TRANSACTION_ID_HASH_BYTES times 2. +pub const CONNECTOR_TRANSACTION_ID_HASH_BYTES: usize = 25; diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 7569049891a2..855dd179b310 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -1306,8 +1306,12 @@ impl From for ConnectorTransactionId { Self::HashedData(src) // Hash connector's transaction ID } else if src.len() > 128 { - let hash = blake3::hash(src.as_bytes()); - Self::HashedData(format!("hs_hash_{}", hash.to_hex())) + let mut hasher = blake3::Hasher::new(); + let mut output = [0u8; consts::CONNECTOR_TRANSACTION_ID_HASH_BYTES]; + hasher.update(src.as_bytes()); + hasher.finalize_xof().fill(&mut output); + let hash = hex::encode(output); + Self::HashedData(format!("hs_hash_{}", hash)) // Default } else { Self::TxnId(src) diff --git a/v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql b/v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql index 65e90b126fb9..2cdb02960339 100644 --- a/v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql +++ b/v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql @@ -76,6 +76,7 @@ ADD COLUMN IF NOT EXISTS attempt_id VARCHAR(64) NOT NULL, ADD COLUMN offer_amount bigint, ADD COLUMN payment_method VARCHAR, ADD COLUMN connector_transaction_id VARCHAR(64), + ADD COLUMN connector_transaction_data VARCHAR(512), ADD COLUMN capture_method "CaptureMethod", ADD COLUMN capture_on TIMESTAMP, ADD COLUMN mandate_id VARCHAR(64), From c7ecd6d9ee083bbf6e77487d5094f02f1bd990ca Mon Sep 17 00:00:00 2001 From: Kashif Date: Mon, 14 Oct 2024 21:16:53 +0530 Subject: [PATCH 16/28] refactor(payment_attempt): restrict connector_transaction_data to diesel_models --- crates/diesel_models/src/payment_attempt.rs | 733 ++++++++++-------- .../src/payments/payment_attempt.rs | 110 +-- crates/router/src/core/payments.rs | 10 +- .../payments/operations/payment_response.rs | 81 +- crates/router/src/core/payments/retry.rs | 30 +- crates/router/src/core/refunds.rs | 20 +- .../src/services/kafka/payment_attempt.rs | 8 +- .../services/kafka/payment_attempt_event.rs | 7 +- crates/router/src/workflows/payment_sync.rs | 1 - .../src/mock_db/payment_attempt.rs | 1 - .../src/payments/payment_attempt.rs | 22 +- 11 files changed, 497 insertions(+), 526 deletions(-) diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 6c1a8d1fec3c..1054832d094d 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -1,6 +1,6 @@ use common_utils::{ id_type, pii, - types::{ConnectorTransactionId, MinorUnit}, + types::{ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit}, }; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; @@ -155,6 +155,46 @@ pub struct PaymentAttempt { pub connector_transaction_data: Option, } +#[cfg(feature = "v1")] +impl ConnectorTransactionIdTrait for PaymentAttempt { + fn get_optional_connector_transaction_id(&self) -> Option<&String> { + match self + .connector_transaction_id + .as_ref() + .map(|txn_id| txn_id.get_txn_id(self.connector_transaction_data.as_ref())) + .transpose() + { + Ok(txn_id) => txn_id, + + // In case hashed data is missing from DB, use the hashed ID as connector transaction ID + Err(_) => self + .connector_transaction_id + .as_ref() + .map(|txn_id| txn_id.get_id()), + } + } +} + +#[cfg(feature = "v2")] +impl ConnectorTransactionIdTrait for PaymentAttempt { + fn get_optional_connector_transaction_id(&self) -> Option<&String> { + match self + .connector_payment_id + .as_ref() + .map(|txn_id| txn_id.get_txn_id(self.connector_payment_data.as_ref())) + .transpose() + { + Ok(txn_id) => txn_id, + + // In case hashed data is missing from DB, use the hashed ID as connector payment ID + Err(_) => self + .connector_payment_id + .as_ref() + .map(|txn_id| txn_id.get_id()), + } + } +} + #[derive(Clone, Debug, Eq, PartialEq, Queryable, Serialize, Deserialize)] pub struct PaymentListFilters { pub connector: Vec, @@ -216,7 +256,6 @@ pub struct PaymentAttemptNew { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, - pub connector_payment_data: Option, } #[cfg(feature = "v1")] @@ -385,7 +424,7 @@ pub enum PaymentAttemptUpdate { ResponseUpdate { status: storage_enums::AttemptStatus, connector: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, authentication_type: Option, payment_method_id: Option, mandate_id: Option, @@ -403,19 +442,17 @@ pub enum PaymentAttemptUpdate { unified_message: Option>, payment_method_data: Option, charge_id: Option, - connector_transaction_data: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, connector: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, payment_method_id: Option, error_code: Option>, error_message: Option>, error_reason: Option>, connector_response_reference_id: Option, updated_by: String, - connector_transaction_data: Option, }, StatusUpdate { status: storage_enums::AttemptStatus, @@ -431,10 +468,9 @@ pub enum PaymentAttemptUpdate { updated_by: String, unified_code: Option>, unified_message: Option>, - connector_transaction_id: Option, + connector_transaction_id: Option, payment_method_data: Option, authentication_type: Option, - connector_transaction_data: Option, }, CaptureUpdate { amount_to_capture: Option, @@ -451,19 +487,17 @@ pub enum PaymentAttemptUpdate { payment_method_id: Option, connector_metadata: Option, preprocessing_step_id: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, connector_response_reference_id: Option, updated_by: String, - connector_transaction_data: Option, }, ConnectorResponse { authentication_data: Option, encoded_data: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, connector: Option, charge_id: Option, updated_by: String, - connector_transaction_data: Option, }, IncrementalAuthorizationAmountUpdate { amount: MinorUnit, @@ -484,8 +518,7 @@ pub enum PaymentAttemptUpdate { updated_by: String, unified_code: Option, unified_message: Option, - connector_transaction_id: Option, - connector_transaction_data: Option, + connector_transaction_id: Option, }, } @@ -2404,59 +2437,65 @@ impl From for PaymentAttemptUpdateInternal { unified_message, payment_method_data, charge_id, - connector_transaction_data, - } => Self { - status: Some(status), - connector: connector.map(Some), - connector_transaction_id, - authentication_type, - payment_method_id, - modified_at: common_utils::date_time::now(), - mandate_id, - connector_metadata, - error_code, - error_message, - payment_token, - error_reason, - connector_response_reference_id, - amount_capturable, - updated_by, - authentication_data, - encoded_data, - unified_code, - unified_message, - payment_method_data, - charge_id, - connector_transaction_data, - amount: None, - net_amount: None, - currency: None, - amount_to_capture: None, - payment_method: None, - cancellation_reason: None, - browser_info: None, - payment_method_type: None, - payment_experience: None, - business_sub_label: None, - straight_through_algorithm: None, - preprocessing_step_id: None, - capture_method: None, - multiple_capture_count: None, - surcharge_amount: None, - tax_amount: None, - merchant_connector_id: None, - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - payment_method_billing_address_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - card_network: None, - shipping_cost: None, - order_tax_amount: None, - }, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + status: Some(status), + connector: connector.map(Some), + connector_transaction_id, + authentication_type, + payment_method_id, + modified_at: common_utils::date_time::now(), + mandate_id, + connector_metadata, + error_code, + error_message, + payment_token, + error_reason, + connector_response_reference_id, + amount_capturable, + updated_by, + authentication_data, + encoded_data, + unified_code, + unified_message, + payment_method_data, + charge_id, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + amount_to_capture: None, + payment_method: None, + cancellation_reason: None, + browser_info: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + preprocessing_step_id: None, + capture_method: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + merchant_connector_id: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + } + } PaymentAttemptUpdate::ErrorUpdate { connector, status, @@ -2470,59 +2509,65 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, payment_method_data, authentication_type, - connector_transaction_data, - } => Self { - connector: connector.map(Some), - status: Some(status), - error_message, - error_code, - modified_at: common_utils::date_time::now(), - error_reason, - amount_capturable, - updated_by, - unified_code, - unified_message, - connector_transaction_id, - payment_method_data, - authentication_type, - connector_transaction_data, - amount: None, - net_amount: None, - currency: None, - amount_to_capture: None, - payment_method: None, - payment_method_id: None, - cancellation_reason: None, - mandate_id: None, - browser_info: None, - payment_token: None, - connector_metadata: None, - payment_method_type: None, - payment_experience: None, - business_sub_label: None, - straight_through_algorithm: None, - preprocessing_step_id: None, - capture_method: None, - connector_response_reference_id: None, - multiple_capture_count: None, - surcharge_amount: None, - tax_amount: None, - merchant_connector_id: None, - authentication_data: None, - encoded_data: None, - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - payment_method_billing_address_id: None, - charge_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - card_network: None, - shipping_cost: None, - order_tax_amount: None, - }, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + connector: connector.map(Some), + status: Some(status), + error_message, + error_code, + modified_at: common_utils::date_time::now(), + error_reason, + amount_capturable, + updated_by, + unified_code, + unified_message, + connector_transaction_id, + payment_method_data, + authentication_type, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + amount_to_capture: None, + payment_method: None, + payment_method_id: None, + cancellation_reason: None, + mandate_id: None, + browser_info: None, + payment_token: None, + connector_metadata: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + preprocessing_step_id: None, + capture_method: None, + connector_response_reference_id: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + merchant_connector_id: None, + authentication_data: None, + encoded_data: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + } + } PaymentAttemptUpdate::StatusUpdate { status, updated_by } => Self { status: Some(status), modified_at: common_utils::date_time::now(), @@ -2646,59 +2691,65 @@ impl From for PaymentAttemptUpdateInternal { error_reason, connector_response_reference_id, updated_by, - connector_transaction_data, - } => Self { - status: Some(status), - connector: connector.map(Some), - connector_transaction_id, - payment_method_id, - modified_at: common_utils::date_time::now(), - error_code, - error_message, - error_reason, - connector_response_reference_id, - updated_by, - connector_transaction_data, - amount: None, - net_amount: None, - currency: None, - amount_to_capture: None, - authentication_type: None, - payment_method: None, - cancellation_reason: None, - mandate_id: None, - browser_info: None, - payment_token: None, - connector_metadata: None, - payment_method_data: None, - payment_method_type: None, - payment_experience: None, - business_sub_label: None, - straight_through_algorithm: None, - preprocessing_step_id: None, - capture_method: None, - multiple_capture_count: None, - surcharge_amount: None, - tax_amount: None, - amount_capturable: None, - merchant_connector_id: None, - authentication_data: None, - encoded_data: None, - unified_code: None, - unified_message: None, - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - payment_method_billing_address_id: None, - charge_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - card_network: None, - shipping_cost: None, - order_tax_amount: None, - }, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + status: Some(status), + connector: connector.map(Some), + connector_transaction_id, + payment_method_id, + modified_at: common_utils::date_time::now(), + error_code, + error_message, + error_reason, + connector_response_reference_id, + updated_by, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + amount_to_capture: None, + authentication_type: None, + payment_method: None, + cancellation_reason: None, + mandate_id: None, + browser_info: None, + payment_token: None, + connector_metadata: None, + payment_method_data: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + preprocessing_step_id: None, + capture_method: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + amount_capturable: None, + merchant_connector_id: None, + authentication_data: None, + encoded_data: None, + unified_code: None, + unified_message: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + } + } PaymentAttemptUpdate::PreprocessingUpdate { status, payment_method_id, @@ -2707,59 +2758,65 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, connector_response_reference_id, updated_by, - connector_transaction_data, - } => Self { - status: Some(status), - payment_method_id, - modified_at: common_utils::date_time::now(), - connector_metadata, - preprocessing_step_id, - connector_transaction_id, - connector_response_reference_id, - updated_by, - connector_transaction_data, - amount: None, - net_amount: None, - currency: None, - amount_to_capture: None, - connector: None, - authentication_type: None, - payment_method: None, - error_message: None, - cancellation_reason: None, - mandate_id: None, - browser_info: None, - payment_token: None, - error_code: None, - payment_method_data: None, - payment_method_type: None, - payment_experience: None, - business_sub_label: None, - straight_through_algorithm: None, - error_reason: None, - capture_method: None, - multiple_capture_count: None, - surcharge_amount: None, - tax_amount: None, - amount_capturable: None, - merchant_connector_id: None, - authentication_data: None, - encoded_data: None, - unified_code: None, - unified_message: None, - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - payment_method_billing_address_id: None, - charge_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - card_network: None, - shipping_cost: None, - order_tax_amount: None, - }, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + status: Some(status), + payment_method_id, + modified_at: common_utils::date_time::now(), + connector_metadata, + preprocessing_step_id, + connector_transaction_id, + connector_response_reference_id, + updated_by, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + amount_to_capture: None, + connector: None, + authentication_type: None, + payment_method: None, + error_message: None, + cancellation_reason: None, + mandate_id: None, + browser_info: None, + payment_token: None, + error_code: None, + payment_method_data: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + error_reason: None, + capture_method: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + amount_capturable: None, + merchant_connector_id: None, + authentication_data: None, + encoded_data: None, + unified_code: None, + unified_message: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + } + } PaymentAttemptUpdate::CaptureUpdate { multiple_capture_count, updated_by, @@ -2879,59 +2936,65 @@ impl From for PaymentAttemptUpdateInternal { connector, updated_by, charge_id, - connector_transaction_data, - } => Self { - authentication_data, - encoded_data, - connector_transaction_id, - connector: connector.map(Some), - modified_at: common_utils::date_time::now(), - updated_by, - charge_id, - connector_transaction_data, - amount: None, - net_amount: None, - currency: None, - status: None, - amount_to_capture: None, - authentication_type: None, - payment_method: None, - error_message: None, - payment_method_id: None, - cancellation_reason: None, - mandate_id: None, - browser_info: None, - payment_token: None, - error_code: None, - connector_metadata: None, - payment_method_data: None, - payment_method_type: None, - payment_experience: None, - business_sub_label: None, - straight_through_algorithm: None, - preprocessing_step_id: None, - error_reason: None, - capture_method: None, - connector_response_reference_id: None, - multiple_capture_count: None, - surcharge_amount: None, - tax_amount: None, - amount_capturable: None, - merchant_connector_id: None, - unified_code: None, - unified_message: None, - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - payment_method_billing_address_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - card_network: None, - shipping_cost: None, - order_tax_amount: None, - }, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + authentication_data, + encoded_data, + connector_transaction_id, + connector: connector.map(Some), + modified_at: common_utils::date_time::now(), + updated_by, + charge_id, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + status: None, + amount_to_capture: None, + authentication_type: None, + payment_method: None, + error_message: None, + payment_method_id: None, + cancellation_reason: None, + mandate_id: None, + browser_info: None, + payment_token: None, + error_code: None, + connector_metadata: None, + payment_method_data: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + preprocessing_step_id: None, + error_reason: None, + capture_method: None, + connector_response_reference_id: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + amount_capturable: None, + merchant_connector_id: None, + unified_code: None, + unified_message: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + } + } PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { amount, amount_capturable, @@ -3054,59 +3117,65 @@ impl From for PaymentAttemptUpdateInternal { unified_code, unified_message, connector_transaction_id, - connector_transaction_data, - } => Self { - status, - error_code: error_code.map(Some), - modified_at: common_utils::date_time::now(), - error_message: error_message.map(Some), - error_reason: error_reason.map(Some), - updated_by, - unified_code: unified_code.map(Some), - unified_message: unified_message.map(Some), - connector_transaction_id, - connector_transaction_data, - amount: None, - net_amount: None, - currency: None, - amount_to_capture: None, - connector: None, - authentication_type: None, - payment_method: None, - payment_method_id: None, - cancellation_reason: None, - mandate_id: None, - browser_info: None, - payment_token: None, - connector_metadata: None, - payment_method_data: None, - payment_method_type: None, - payment_experience: None, - business_sub_label: None, - straight_through_algorithm: None, - preprocessing_step_id: None, - capture_method: None, - connector_response_reference_id: None, - multiple_capture_count: None, - surcharge_amount: None, - tax_amount: None, - amount_capturable: None, - merchant_connector_id: None, - authentication_data: None, - encoded_data: None, - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - payment_method_billing_address_id: None, - charge_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - card_network: None, - shipping_cost: None, - order_tax_amount: None, - }, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + status, + error_code: error_code.map(Some), + modified_at: common_utils::date_time::now(), + error_message: error_message.map(Some), + error_reason: error_reason.map(Some), + updated_by, + unified_code: unified_code.map(Some), + unified_message: unified_message.map(Some), + connector_transaction_id, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + amount_to_capture: None, + connector: None, + authentication_type: None, + payment_method: None, + payment_method_id: None, + cancellation_reason: None, + mandate_id: None, + browser_info: None, + payment_token: None, + connector_metadata: None, + payment_method_data: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + preprocessing_step_id: None, + capture_method: None, + connector_response_reference_id: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + amount_capturable: None, + merchant_connector_id: None, + authentication_data: None, + encoded_data: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + } + } } } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index ecd3e9e71779..6692880c6d41 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -216,14 +216,13 @@ pub struct PaymentAttempt { pub organization_id: id_type::OrganizationId, pub payment_method_type: Option, pub payment_method_id: Option, - pub connector_payment_id: Option, + pub connector_payment_id: Option, pub payment_method_subtype: Option, pub authentication_applied: Option, pub external_reference_id: Option, pub shipping_cost: Option, pub order_tax_amount: Option, pub id: String, - pub connector_payment_data: Option, } impl PaymentAttempt { @@ -259,14 +258,12 @@ impl PaymentAttempt { #[cfg(feature = "v1")] pub fn get_connector_payment_id(&self) -> Option<&str> { - self.get_optional_connector_transaction_id() - .map(|x| x.as_str()) + self.connector_transaction_id.as_deref() } #[cfg(feature = "v2")] pub fn get_connector_payment_id(&self) -> Option<&str> { - self.get_optional_connector_transaction_id() - .map(|x| x.as_str()) + self.connector_payment_id.as_deref() } } @@ -285,7 +282,7 @@ pub struct PaymentAttempt { pub offer_amount: Option, pub payment_method_id: Option, pub payment_method: Option, - pub connector_transaction_id: Option, + pub connector_transaction_id: Option, pub capture_method: Option, #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub capture_on: Option, @@ -335,7 +332,6 @@ pub struct PaymentAttempt { pub customer_acceptance: Option, pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, - pub connector_transaction_data: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)] @@ -503,46 +499,6 @@ impl PaymentAttempt { } } -#[cfg(feature = "v1")] -impl ConnectorTransactionIdTrait for PaymentAttempt { - fn get_optional_connector_transaction_id(&self) -> Option<&String> { - match self - .connector_transaction_id - .as_ref() - .map(|txn_id| txn_id.get_txn_id(self.connector_transaction_data.as_ref())) - .transpose() - { - Ok(txn_id) => txn_id, - - // In case hashed data is missing from DB, use the hashed ID as connector transaction ID - Err(_) => self - .connector_transaction_id - .as_ref() - .map(|txn_id| txn_id.get_id()), - } - } -} - -#[cfg(feature = "v2")] -impl ConnectorTransactionIdTrait for PaymentAttempt { - fn get_optional_connector_transaction_id(&self) -> Option<&String> { - match self - .connector_payment_id - .as_ref() - .map(|txn_id| txn_id.get_txn_id(self.connector_payment_data.as_ref())) - .transpose() - { - Ok(txn_id) => txn_id, - - // In case hashed data is missing from DB, use the hashed ID as connector payment ID - Err(_) => self - .connector_payment_id - .as_ref() - .map(|txn_id| txn_id.get_id()), - } - } -} - #[derive(Clone, Debug, Eq, PartialEq)] pub struct PaymentListFilters { pub connector: Vec, @@ -757,7 +713,7 @@ pub enum PaymentAttemptUpdate { ResponseUpdate { status: storage_enums::AttemptStatus, connector: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, authentication_type: Option, payment_method_id: Option, mandate_id: Option, @@ -775,19 +731,17 @@ pub enum PaymentAttemptUpdate { unified_message: Option>, payment_method_data: Option, charge_id: Option, - connector_transaction_data: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, connector: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, payment_method_id: Option, error_code: Option>, error_message: Option>, error_reason: Option>, connector_response_reference_id: Option, updated_by: String, - connector_transaction_data: Option, }, StatusUpdate { status: storage_enums::AttemptStatus, @@ -803,10 +757,9 @@ pub enum PaymentAttemptUpdate { updated_by: String, unified_code: Option>, unified_message: Option>, - connector_transaction_id: Option, + connector_transaction_id: Option, payment_method_data: Option, authentication_type: Option, - connector_transaction_data: Option, }, CaptureUpdate { amount_to_capture: Option, @@ -823,19 +776,17 @@ pub enum PaymentAttemptUpdate { payment_method_id: Option, connector_metadata: Option, preprocessing_step_id: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, connector_response_reference_id: Option, updated_by: String, - connector_transaction_data: Option, }, ConnectorResponse { authentication_data: Option, encoded_data: Option, - connector_transaction_id: Option, + connector_transaction_id: Option, connector: Option, charge_id: Option, updated_by: String, - connector_transaction_data: Option, }, IncrementalAuthorizationAmountUpdate { net_amount: NetAmount, @@ -856,8 +807,7 @@ pub enum PaymentAttemptUpdate { updated_by: String, unified_code: Option, unified_message: Option, - connector_transaction_id: Option, - connector_transaction_data: Option, + connector_transaction_id: Option, }, } @@ -1037,7 +987,6 @@ impl PaymentAttemptUpdate { unified_message, payment_method_data, charge_id, - connector_transaction_data, } => DieselPaymentAttemptUpdate::ResponseUpdate { status, connector, @@ -1059,7 +1008,6 @@ impl PaymentAttemptUpdate { unified_message, payment_method_data, charge_id, - connector_transaction_data, }, Self::UnresolvedResponseUpdate { status, @@ -1071,7 +1019,6 @@ impl PaymentAttemptUpdate { error_reason, connector_response_reference_id, updated_by, - connector_transaction_data, } => DieselPaymentAttemptUpdate::UnresolvedResponseUpdate { status, connector, @@ -1082,7 +1029,6 @@ impl PaymentAttemptUpdate { error_reason, connector_response_reference_id, updated_by, - connector_transaction_data, }, Self::StatusUpdate { status, updated_by } => { DieselPaymentAttemptUpdate::StatusUpdate { status, updated_by } @@ -1100,7 +1046,6 @@ impl PaymentAttemptUpdate { connector_transaction_id, payment_method_data, authentication_type, - connector_transaction_data, } => DieselPaymentAttemptUpdate::ErrorUpdate { connector, status, @@ -1114,7 +1059,6 @@ impl PaymentAttemptUpdate { connector_transaction_id, payment_method_data, authentication_type, - connector_transaction_data, }, Self::CaptureUpdate { multiple_capture_count, @@ -1133,7 +1077,6 @@ impl PaymentAttemptUpdate { connector_transaction_id, connector_response_reference_id, updated_by, - connector_transaction_data, } => DieselPaymentAttemptUpdate::PreprocessingUpdate { status, payment_method_id, @@ -1142,7 +1085,6 @@ impl PaymentAttemptUpdate { connector_transaction_id, connector_response_reference_id, updated_by, - connector_transaction_data, }, Self::RejectUpdate { status, @@ -1171,7 +1113,6 @@ impl PaymentAttemptUpdate { connector, charge_id, updated_by, - connector_transaction_data, } => DieselPaymentAttemptUpdate::ConnectorResponse { authentication_data, encoded_data, @@ -1179,7 +1120,6 @@ impl PaymentAttemptUpdate { connector, charge_id, updated_by, - connector_transaction_data, }, Self::IncrementalAuthorizationAmountUpdate { net_amount, @@ -1210,7 +1150,6 @@ impl PaymentAttemptUpdate { unified_code, unified_message, connector_transaction_id, - connector_transaction_data, } => DieselPaymentAttemptUpdate::ManualUpdate { status, error_code, @@ -1220,7 +1159,6 @@ impl PaymentAttemptUpdate { unified_code, unified_message, connector_transaction_id, - connector_transaction_data, }, } } @@ -1261,6 +1199,11 @@ impl behaviour::Conversion for PaymentAttempt { .and_then(|card| card.get("card_network")) .and_then(|network| network.as_str()) .map(|network| network.to_string()); + let (connector_transaction_id, connector_transaction_data) = self + .connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); Ok(DieselPaymentAttempt { payment_id: self.payment_id, merchant_id: self.merchant_id, @@ -1276,7 +1219,7 @@ impl behaviour::Conversion for PaymentAttempt { tax_amount: self.net_amount.get_tax_on_surcharge(), payment_method_id: self.payment_method_id, payment_method: self.payment_method, - connector_transaction_id: self.connector_transaction_id, + connector_transaction_id, capture_method: self.capture_method, capture_on: self.capture_on, confirm: self.confirm, @@ -1323,7 +1266,7 @@ impl behaviour::Conversion for PaymentAttempt { profile_id: self.profile_id, organization_id: self.organization_id, card_network, - connector_transaction_data: self.connector_transaction_data, + connector_transaction_data, order_tax_amount: self.net_amount.get_order_tax_amount(), shipping_cost: self.net_amount.get_shipping_cost(), }) @@ -1339,6 +1282,9 @@ impl behaviour::Conversion for PaymentAttempt { Self: Sized, { async { + let connector_transaction_id = storage_model + .get_optional_connector_transaction_id() + .cloned(); Ok::>(Self { payment_id: storage_model.payment_id, merchant_id: storage_model.merchant_id, @@ -1358,7 +1304,7 @@ impl behaviour::Conversion for PaymentAttempt { offer_amount: storage_model.offer_amount, payment_method_id: storage_model.payment_method_id, payment_method: storage_model.payment_method, - connector_transaction_id: storage_model.connector_transaction_id, + connector_transaction_id, capture_method: storage_model.capture_method, capture_on: storage_model.capture_on, confirm: storage_model.confirm, @@ -1403,7 +1349,6 @@ impl behaviour::Conversion for PaymentAttempt { customer_acceptance: storage_model.customer_acceptance, profile_id: storage_model.profile_id, organization_id: storage_model.organization_id, - connector_transaction_data: storage_model.connector_transaction_data, }) } .await @@ -1560,9 +1505,13 @@ impl behaviour::Conversion for PaymentAttempt { shipping_cost, order_tax_amount, connector, - connector_payment_data, } = self; + let (connector_payment_id, connector_payment_data) = connector_payment_id + .map(|txn_id| ConnectorTransactionId::form_id_and_data(txn_id)) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Ok(DieselPaymentAttempt { payment_id, merchant_id, @@ -1632,6 +1581,9 @@ impl behaviour::Conversion for PaymentAttempt { Self: Sized, { async { + let connector_payment_id = storage_model + .get_optional_connector_transaction_id() + .cloned(); Ok::>(Self { payment_id: storage_model.payment_id, merchant_id: storage_model.merchant_id, @@ -1643,7 +1595,7 @@ impl behaviour::Conversion for PaymentAttempt { surcharge_amount: storage_model.surcharge_amount, payment_method_id: storage_model.payment_method_id, payment_method_type: storage_model.payment_method_type_v2, - connector_payment_id: storage_model.connector_payment_id, + connector_payment_id, confirm: storage_model.confirm, authentication_type: storage_model.authentication_type, created_at: storage_model.created_at, @@ -1687,7 +1639,6 @@ impl behaviour::Conversion for PaymentAttempt { authentication_applied: storage_model.authentication_applied, external_reference_id: storage_model.external_reference_id, connector: storage_model.connector, - connector_payment_data: storage_model.connector_payment_data, }) } .await @@ -1755,7 +1706,6 @@ impl behaviour::Conversion for PaymentAttempt { order_tax_amount: self.order_tax_amount, shipping_cost: self.shipping_cost, amount_to_capture: self.amount_to_capture, - connector_payment_data: self.connector_payment_data, }) } } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index e55a80f6e1d8..8ea77df7d2ff 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -28,7 +28,7 @@ pub use common_enums::enums::CallConnectorAction; use common_utils::{ ext_traits::{AsyncExt, StringExt}, id_type, pii, - types::{ConnectorTransactionId, MinorUnit, Surcharge}, + types::{MinorUnit, Surcharge}, }; use diesel_models::{ephemeral_key, fraud_check::FraudCheck}; use error_stack::{report, ResultExt}; @@ -5114,9 +5114,6 @@ pub async fn payments_manual_update( } else { None }; - let (connector_transaction_id, connector_transaction_data) = connector_transaction_id - .map(ConnectorTransactionId::form_id_and_data) - .map_or((None, None), |(id, data)| (Some(id), data)); // Update the payment_attempt let attempt_update = storage::PaymentAttemptUpdate::ManualUpdate { status: attempt_status, @@ -5127,7 +5124,6 @@ pub async fn payments_manual_update( unified_code: option_gsm.as_ref().and_then(|gsm| gsm.unified_code.clone()), unified_message: option_gsm.and_then(|gsm| gsm.unified_message), connector_transaction_id, - connector_transaction_data, }; let updated_payment_attempt = state .store @@ -5161,9 +5157,6 @@ pub async fn payments_manual_update( } Ok(services::ApplicationResponse::Json( api_models::payments::PaymentsManualUpdateResponse { - connector_transaction_id: updated_payment_attempt - .get_connector_payment_id() - .map(ToString::to_string), payment_id: updated_payment_attempt.payment_id, attempt_id: updated_payment_attempt.attempt_id, merchant_id: updated_payment_attempt.merchant_id, @@ -5171,6 +5164,7 @@ pub async fn payments_manual_update( error_code: updated_payment_attempt.error_code, error_message: updated_payment_attempt.error_message, error_reason: updated_payment_attempt.error_reason, + connector_transaction_id: updated_payment_attempt.connector_transaction_id, }, )) } diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index d2be12237df3..34fdf684f2d7 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1208,12 +1208,6 @@ async fn payment_response_update_tracker( } } }; - let (connector_transaction_id, connector_transaction_data) = - err.connector_transaction_id.map_or((None, None), |txn_id| { - let (txn_id, txn_data) = - ConnectorTransactionId::form_id_and_data(txn_id); - (Some(txn_id), txn_data) - }); ( None, Some(storage::PaymentAttemptUpdate::ErrorUpdate { @@ -1229,10 +1223,9 @@ async fn payment_response_update_tracker( updated_by: storage_scheme.to_string(), unified_code: Some(Some(unified_code)), unified_message: Some(unified_translated_message), - connector_transaction_id, + connector_transaction_id: err.connector_transaction_id, payment_method_data: additional_payment_method_data, authentication_type: auth_update, - connector_transaction_data, }), ) } @@ -1252,12 +1245,7 @@ async fn payment_response_update_tracker( None }; let field_name = err.field_names; - let (connector_transaction_id, connector_transaction_data) = - err.connector_transaction_id.map_or((None, None), |txn_id| { - let (txn_id, txn_data) = - ConnectorTransactionId::form_id_and_data(txn_id); - (Some(txn_id), txn_data) - }); + let connector_transaction_id = err.connector_transaction_id; ( None, Some(storage::PaymentAttemptUpdate::ErrorUpdate { @@ -1275,7 +1263,6 @@ async fn payment_response_update_tracker( connector_transaction_id, payment_method_data: None, authentication_type: auth_update, - connector_transaction_data, }), ) } @@ -1305,21 +1292,13 @@ async fn payment_response_update_tracker( connector_response_reference_id, .. } => { - let (connector_transaction_id, connector_transaction_data) = - match pre_processing_id.to_owned() { - types::PreprocessingResponseId::PreProcessingId(_) => { - (None, None) - } - types::PreprocessingResponseId::ConnectorTransactionId( - connector_txn_id, - ) => { - let (txn_id, txn_data) = - ConnectorTransactionId::form_id_and_data( - connector_txn_id, - ); - (Some(txn_id), txn_data) - } - }; + let connector_transaction_id = match pre_processing_id.to_owned() { + types::PreprocessingResponseId::PreProcessingId(_) => None, + + types::PreprocessingResponseId::ConnectorTransactionId( + connector_txn_id, + ) => Some(connector_txn_id), + }; let preprocessing_step_id = match pre_processing_id { types::PreprocessingResponseId::PreProcessingId( pre_processing_id, @@ -1338,7 +1317,6 @@ async fn payment_response_update_tracker( connector_transaction_id, connector_response_reference_id, updated_by: storage_scheme.to_string(), - connector_transaction_data, }; (None, Some(payment_attempt_update)) @@ -1361,14 +1339,10 @@ async fn payment_response_update_tracker( .payment_intent .request_incremental_authorization, ); - let (connector_capture_id, connector_capture_data) = match resource_id { - types::ResponseId::NoResponseId => (None, None), - types::ResponseId::ConnectorTransactionId(id) - | types::ResponseId::EncodedData(id) => { - let (txn_id, txn_data) = - ConnectorTransactionId::form_id_and_data(id); - (Some(txn_id), txn_data) - } + let connector_transaction_id = match resource_id { + types::ResponseId::NoResponseId => None, + types::ResponseId::ConnectorTransactionId(ref id) + | types::ResponseId::EncodedData(ref id) => Some(id), }; let encoded_data = payment_data.payment_attempt.encoded_data.clone(); @@ -1417,6 +1391,16 @@ async fn payment_response_update_tracker( .multiple_capture_data { Some(multiple_capture_data) => { + let (connector_capture_id, connector_capture_data) = + match resource_id { + types::ResponseId::NoResponseId => (None, None), + types::ResponseId::ConnectorTransactionId(id) + | types::ResponseId::EncodedData(id) => { + let (txn_id, txn_data) = + ConnectorTransactionId::form_id_and_data(id); + (Some(txn_id), txn_data) + } + }; let capture_update = storage::CaptureUpdate::ResponseUpdate { status: enums::CaptureStatus::foreign_try_from( router_data.status, @@ -1441,7 +1425,7 @@ async fn payment_response_update_tracker( Some(storage::PaymentAttemptUpdate::ResponseUpdate { status: updated_attempt_status, connector: None, - connector_transaction_id: connector_capture_id, + connector_transaction_id: connector_transaction_id.cloned(), authentication_type: auth_update, amount_capturable: router_data .request @@ -1465,7 +1449,6 @@ async fn payment_response_update_tracker( encoded_data, payment_method_data: additional_payment_method_data, charge_id, - connector_transaction_data: connector_capture_data, }), ), }; @@ -1477,16 +1460,11 @@ async fn payment_response_update_tracker( reason, connector_response_reference_id, } => { - let (connector_transaction_id, connector_transaction_data) = - match resource_id { - types::ResponseId::NoResponseId => (None, None), - types::ResponseId::ConnectorTransactionId(id) - | types::ResponseId::EncodedData(id) => { - let (txn_id, txn_data) = - ConnectorTransactionId::form_id_and_data(id); - (Some(txn_id), txn_data) - } - }; + let connector_transaction_id = match resource_id { + types::ResponseId::NoResponseId => None, + types::ResponseId::ConnectorTransactionId(id) + | types::ResponseId::EncodedData(id) => Some(id), + }; ( None, Some(storage::PaymentAttemptUpdate::UnresolvedResponseUpdate { @@ -1502,7 +1480,6 @@ async fn payment_response_update_tracker( error_reason: Some(reason.map(|cd| cd.message)), connector_response_reference_id, updated_by: storage_scheme.to_string(), - connector_transaction_data, }), ) } diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 76b3643e1021..374c5680685f 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -1,9 +1,6 @@ use std::{str::FromStr, vec::IntoIter}; -use common_utils::{ - ext_traits::Encode, - types::{ConnectorTransactionId, MinorUnit}, -}; +use common_utils::{ext_traits::Encode, types::MinorUnit}; use diesel_models::enums as storage_enums; use error_stack::{report, ResultExt}; use router_env::{ @@ -425,19 +422,14 @@ where .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Could not parse the connector response")?; - let (connector_transaction_id, connector_transaction_data) = match resource_id { - types::ResponseId::NoResponseId => (None, None), - types::ResponseId::ConnectorTransactionId(id) - | types::ResponseId::EncodedData(id) => { - let (txn_id, txn_data) = ConnectorTransactionId::form_id_and_data(id); - (Some(txn_id), txn_data) - } - }; - let payment_attempt_update = storage::PaymentAttemptUpdate::ResponseUpdate { status: router_data.status, connector: None, - connector_transaction_id, + connector_transaction_id: match resource_id { + types::ResponseId::NoResponseId => None, + types::ResponseId::ConnectorTransactionId(id) + | types::ResponseId::EncodedData(id) => Some(id), + }, connector_response_reference_id: payment_data .get_payment_attempt() .connector_response_reference_id @@ -464,7 +456,6 @@ where unified_message: None, payment_method_data: additional_payment_method_data, charge_id, - connector_transaction_data, }; #[cfg(feature = "v1")] @@ -501,12 +492,6 @@ where None }; - let (connector_transaction_id, connector_transaction_data) = error_response - .connector_transaction_id - .clone() - .map(ConnectorTransactionId::form_id_and_data) - .map_or((None, None), |(id, data)| (Some(id), data)); - let payment_attempt_update = storage::PaymentAttemptUpdate::ErrorUpdate { connector: None, error_code: Some(Some(error_response.code.clone())), @@ -517,10 +502,9 @@ where updated_by: storage_scheme.to_string(), unified_code: option_gsm.clone().map(|gsm| gsm.unified_code), unified_message: option_gsm.map(|gsm| gsm.unified_message), - connector_transaction_id, + connector_transaction_id: error_response.connector_transaction_id.clone(), payment_method_data: additional_payment_method_data, authentication_type: auth_update, - connector_transaction_data, }; #[cfg(feature = "v1")] diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index abe1b4a4725f..86211770e0dc 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -777,21 +777,15 @@ pub async fn validate_and_create_refund( .attach_printable("invalid merchant_id in request")) })?; - let connector_transaction_id = payment_attempt.clone() - .connector_transaction_id - .ok_or_else(|| { - report!(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Transaction in invalid. Missing field \"connector_transaction_id\" in payment_attempt.") - })?; - - let connector_transaction_id_str = connector_transaction_id - .get_txn_id(payment_attempt.connector_transaction_data.as_ref()) - .change_context(errors::ApiErrorResponse::InternalServerError)?; + let connector_transaction_id = payment_attempt.clone().connector_transaction_id.ok_or_else(|| { + report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Transaction in invalid. Missing field \"connector_transaction_id\" in payment_attempt.") + })?; let all_refunds = db .find_refund_by_merchant_id_connector_transaction_id( merchant_account.get_id(), - connector_transaction_id_str, + &connector_transaction_id, merchant_account.storage_scheme, ) .await @@ -831,6 +825,8 @@ pub async fn validate_and_create_refund( .clone() .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("No connector populated in payment attempt")?; + let (connector_transaction_id, connector_transaction_data) = + ConnectorTransactionId::form_id_and_data(connector_transaction_id); let refund_create_req = storage::RefundNew { refund_id: refund_id.to_string(), internal_reference_id: utils::generate_id(consts::ID_LENGTH, "refid"), @@ -859,7 +855,7 @@ pub async fn validate_and_create_refund( updated_by: Default::default(), organization_id: merchant_account.organization_id.clone(), connector_refund_data: None, - connector_transaction_data: payment_attempt.connector_transaction_data.clone(), + connector_transaction_data, }; let refund = match db diff --git a/crates/router/src/services/kafka/payment_attempt.rs b/crates/router/src/services/kafka/payment_attempt.rs index 27c609031b21..e585b21d4b2b 100644 --- a/crates/router/src/services/kafka/payment_attempt.rs +++ b/crates/router/src/services/kafka/payment_attempt.rs @@ -1,8 +1,5 @@ // use diesel_models::enums::MandateDetails; -use common_utils::{ - id_type, - types::{ConnectorTransactionIdTrait, MinorUnit}, -}; +use common_utils::{id_type, types::MinorUnit}; use diesel_models::enums as storage_enums; use hyperswitch_domain_models::{ mandates::MandateDetails, payments::payment_attempt::PaymentAttempt, @@ -65,7 +62,6 @@ pub struct KafkaPaymentAttempt<'a> { #[cfg(feature = "v1")] impl<'a> KafkaPaymentAttempt<'a> { pub fn from_storage(attempt: &'a PaymentAttempt) -> Self { - let connector_transaction_id = attempt.get_optional_connector_transaction_id(); Self { payment_id: &attempt.payment_id, merchant_id: &attempt.merchant_id, @@ -81,7 +77,7 @@ impl<'a> KafkaPaymentAttempt<'a> { tax_amount: attempt.net_amount.get_tax_on_surcharge(), payment_method_id: attempt.payment_method_id.as_ref(), payment_method: attempt.payment_method, - connector_transaction_id, + connector_transaction_id: attempt.connector_transaction_id.as_ref(), capture_method: attempt.capture_method, capture_on: attempt.capture_on.map(|i| i.assume_utc()), confirm: attempt.confirm, diff --git a/crates/router/src/services/kafka/payment_attempt_event.rs b/crates/router/src/services/kafka/payment_attempt_event.rs index af689ce04429..cbce46ad9c75 100644 --- a/crates/router/src/services/kafka/payment_attempt_event.rs +++ b/crates/router/src/services/kafka/payment_attempt_event.rs @@ -1,8 +1,5 @@ // use diesel_models::enums::MandateDetails; -use common_utils::{ - id_type, - types::{ConnectorTransactionIdTrait, MinorUnit}, -}; +use common_utils::{id_type, types::MinorUnit}; use diesel_models::enums as storage_enums; use hyperswitch_domain_models::{ mandates::MandateDetails, payments::payment_attempt::PaymentAttempt, @@ -81,7 +78,7 @@ impl<'a> KafkaPaymentAttemptEvent<'a> { tax_amount: attempt.net_amount.get_tax_on_surcharge(), payment_method_id: attempt.payment_method_id.as_ref(), payment_method: attempt.payment_method, - connector_transaction_id: attempt.get_optional_connector_transaction_id(), + connector_transaction_id: attempt.connector_transaction_id.as_ref(), capture_method: attempt.capture_method, capture_on: attempt.capture_on.map(|i| i.assume_utc()), confirm: attempt.confirm, diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index 01a17bc4bc87..f34d707b6c83 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -153,7 +153,6 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { connector_transaction_id: None, payment_method_data: None, authentication_type: None, - connector_transaction_data: None }; payment_data.payment_attempt = db diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index c70baa493aa7..13c75e005c4e 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -181,7 +181,6 @@ impl PaymentAttemptInterface for MockDb { customer_acceptance: payment_attempt.customer_acceptance, organization_id: payment_attempt.organization_id, profile_id: payment_attempt.profile_id, - connector_transaction_data: None, }; payment_attempts.push(payment_attempt.clone()); Ok(payment_attempt) diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 346c79a67412..231f72bd7316 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -1,6 +1,10 @@ #[cfg(feature = "v2")] use common_utils::types::keymanager::KeyManagerState; -use common_utils::{errors::CustomResult, fallback_reverse_lookup_not_found}; +use common_utils::{ + errors::CustomResult, + fallback_reverse_lookup_not_found, + types::{ConnectorTransactionId, ConnectorTransactionIdTrait}, +}; use diesel_models::{ enums::{ MandateAmountData as DieselMandateAmountData, MandateDataType as DieselMandateType, @@ -529,7 +533,6 @@ impl PaymentAttemptInterface for KVRouterStore { customer_acceptance: payment_attempt.customer_acceptance.clone(), organization_id: payment_attempt.organization_id.clone(), profile_id: payment_attempt.profile_id.clone(), - connector_transaction_data: None, }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -1376,6 +1379,11 @@ impl DataModelExt for PaymentAttempt { type StorageModel = DieselPaymentAttempt; fn to_storage_model(self) -> Self::StorageModel { + let (connector_transaction_id, connector_transaction_data) = self + .connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); DieselPaymentAttempt { payment_id: self.payment_id, merchant_id: self.merchant_id, @@ -1392,7 +1400,7 @@ impl DataModelExt for PaymentAttempt { tax_amount: self.net_amount.get_tax_on_surcharge(), payment_method_id: self.payment_method_id, payment_method: self.payment_method, - connector_transaction_id: self.connector_transaction_id, + connector_transaction_id, capture_method: self.capture_method, capture_on: self.capture_on, confirm: self.confirm, @@ -1446,13 +1454,16 @@ impl DataModelExt for PaymentAttempt { customer_acceptance: self.customer_acceptance, organization_id: self.organization_id, profile_id: self.profile_id, - connector_transaction_data: self.connector_transaction_data, + connector_transaction_data, shipping_cost: self.net_amount.get_shipping_cost(), order_tax_amount: self.net_amount.get_order_tax_amount(), } } fn from_storage_model(storage_model: Self::StorageModel) -> Self { + let connector_transaction_id = storage_model + .get_optional_connector_transaction_id() + .cloned(); Self { net_amount: hyperswitch_domain_models::payments::payment_attempt::NetAmount::new( storage_model.amount, @@ -1472,7 +1483,7 @@ impl DataModelExt for PaymentAttempt { offer_amount: storage_model.offer_amount, payment_method_id: storage_model.payment_method_id, payment_method: storage_model.payment_method, - connector_transaction_id: storage_model.connector_transaction_id, + connector_transaction_id, capture_method: storage_model.capture_method, capture_on: storage_model.capture_on, confirm: storage_model.confirm, @@ -1521,7 +1532,6 @@ impl DataModelExt for PaymentAttempt { customer_acceptance: storage_model.customer_acceptance, organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, - connector_transaction_data: storage_model.connector_transaction_data, } } } From 45e1c8c1dc410aa3d19eb943bb5b464274f7dd8e Mon Sep 17 00:00:00 2001 From: Kashif Date: Mon, 14 Oct 2024 21:33:14 +0530 Subject: [PATCH 17/28] refactor: fix clippy v2 suggestions --- .../hyperswitch_domain_models/src/payments/payment_attempt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 6692880c6d41..4c07d3601519 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -1508,7 +1508,7 @@ impl behaviour::Conversion for PaymentAttempt { } = self; let (connector_payment_id, connector_payment_data) = connector_payment_id - .map(|txn_id| ConnectorTransactionId::form_id_and_data(txn_id)) + .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) .unwrap_or((None, None)); From 954fea327c9f05b254d1fe3b661561de5b12e05a Mon Sep 17 00:00:00 2001 From: Kashif Date: Mon, 14 Oct 2024 21:36:21 +0530 Subject: [PATCH 18/28] refactor: add missing down migration for v2 columns --- v2_migrations/2024-08-28-081721_add_v2_columns/down.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql b/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql index cfc769e70e12..519bef65cbba 100644 --- a/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql +++ b/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql @@ -38,4 +38,5 @@ ALTER TABLE payment_attempt DROP COLUMN payment_method_type_v2, DROP COLUMN routing_result, DROP COLUMN authentication_applied, DROP COLUMN external_reference_id, - DROP COLUMN tax_on_surcharge; + DROP COLUMN tax_on_surcharge, + DROP COLUMN connector_payment_data; From 520e54e18bc810fa9fb0ecf2b6b8871652346c56 Mon Sep 17 00:00:00 2001 From: Kashif Date: Tue, 15 Oct 2024 14:53:59 +0530 Subject: [PATCH 19/28] fix: update cypress tests for compatibility with currency changes --- cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js | 2 +- cypress-tests/cypress/e2e/PaymentUtils/Commons.js | 2 +- cypress-tests/cypress/e2e/PaymentUtils/Paybox.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js b/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js index ea7b4f09fd79..c2d1b7483c14 100644 --- a/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js +++ b/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js @@ -1069,7 +1069,7 @@ export const connectorDetails = { status: 400, body: { error: - "Json deserialize error: unknown variant `United`, expected one of `AED`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BWP`, `BYN`, `BZD`, `CAD`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SZL`, `THB`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`", + "Json deserialize error: unknown variant `United`, expected one of `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BTN`, `BWP`, `BYN`, `BZD`, `CAD`, `CDF`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`, `ZWL`", }, }, }, diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js index 284eff5fd1d5..73e54fee916f 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js @@ -1073,7 +1073,7 @@ export const connectorDetails = { status: 400, body: { error: - "Json deserialize error: unknown variant `United`, expected one of `AED`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BWP`, `BYN`, `BZD`, `CAD`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SZL`, `THB`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`", + "Json deserialize error: unknown variant `United`, expected one of `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BTN`, `BWP`, `BYN`, `BZD`, `CAD`, `CDF`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`, `ZWL`", }, }, }, diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js b/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js index c54349b325e8..8fbb8c0b07b7 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js @@ -256,7 +256,7 @@ export const connectorDetails = { status: 400, body: { error: - "Json deserialize error: unknown variant `United`, expected one of `AED`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BWP`, `BYN`, `BZD`, `CAD`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SZL`, `THB`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`", + "Json deserialize error: unknown variant `United`, expected one of `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BTN`, `BWP`, `BYN`, `BZD`, `CAD`, `CDF`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`, `ZWL`", }, }, }, From caeef6fc2cdb65c319cd83f5bd93196b6018db09 Mon Sep 17 00:00:00 2001 From: Kashif Date: Tue, 15 Oct 2024 15:12:00 +0530 Subject: [PATCH 20/28] refactor(connector): [WorldPay] migrate from modular to standard payments APIs --- Cargo.lock | 1 + crates/router/Cargo.toml | 1 + crates/router/src/connector/utils.rs | 4 + crates/router/src/connector/worldpay.rs | 96 ++++++-- .../router/src/connector/worldpay/requests.rs | 197 ++++++++------- .../router/src/connector/worldpay/response.rs | 233 ++++++++++++------ .../src/connector/worldpay/transformers.rs | 125 +++++++--- crates/router/src/lib.rs | 1 + 8 files changed, 430 insertions(+), 228 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26ad619ee0ba..347d4e587979 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6438,6 +6438,7 @@ dependencies = [ "unicode-segmentation", "unidecode", "url", + "urlencoding", "utoipa", "uuid", "validator", diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 5becef839d52..849a3beaaf3d 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -115,6 +115,7 @@ tracing-futures = { version = "0.2.5", features = ["tokio"] } unicode-segmentation = "1.11.0" unidecode = "0.3.0" url = { version = "2.5.0", features = ["serde"] } +urlencoding = "2.1.3" utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order", "time"] } uuid = { version = "1.8.0", features = ["v4"] } validator = "0.17.0" diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index ea19acb76dae..42ca38cece78 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -725,6 +725,7 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { pub trait PaymentsCaptureRequestData { fn is_multiple_capture(&self) -> bool; fn get_browser_info(&self) -> Result; + fn get_capture_method(&self) -> Option; } impl PaymentsCaptureRequestData for types::PaymentsCaptureData { @@ -736,6 +737,9 @@ impl PaymentsCaptureRequestData for types::PaymentsCaptureData { .clone() .ok_or_else(missing_field_err("browser_info")) } + fn get_capture_method(&self) -> Option { + self.capture_method.to_owned() + } } pub trait RevokeMandateRequestData { diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index f542d700fb77..7c4483ddeee6 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -64,6 +64,7 @@ where headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), ), + (headers::X_WP_API_VERSION.to_string(), "2024-06-01".into()), ]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; headers.append(&mut api_key); @@ -81,7 +82,7 @@ impl ConnectorCommon for Worldpay { } fn common_get_content_type(&self) -> &'static str { - "application/vnd.worldpay.payments-v7+json" + "application/json" } fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { @@ -205,8 +206,9 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( - "{}payments/authorizations/cancellations/{connector_payment_id}", + "{}api/payments/{}/cancellations", self.base_url(connectors), + urlencoding::encode(&connector_payment_id), )) } @@ -244,15 +246,24 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( - "{}payments/settlements/partials/{}", + "{}api/payments/{}/partialSettlements", self.base_url(connectors), - connector_payment_id + urlencoding::encode(&connector_payment_id), )) } @@ -452,15 +469,24 @@ impl ConnectorIntegration CustomResult { - Ok(format!( - "{}cardPayments/customerInitiatedTransactions", - self.base_url(connectors) - )) + Ok(format!("{}api/payments", self.base_url(connectors))) } fn get_request_body( @@ -568,12 +591,21 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( - "{}payments/settlements/refunds/partials/{}", + "{}api/payments/{}/partialRefunds", self.base_url(connectors), - connector_payment_id + urlencoding::encode(&connector_payment_id), )) } @@ -665,9 +697,19 @@ impl ConnectorIntegration CustomResult { Ok(format!( - "{}payments/events/{}", + "{}api/payments/{}", self.base_url(connectors), - req.request.get_connector_refund_id()? + urlencoding::encode(&req.request.get_connector_refund_id()?), )) } diff --git a/crates/router/src/connector/worldpay/requests.rs b/crates/router/src/connector/worldpay/requests.rs index 301ae64946d1..208f98934fc7 100644 --- a/crates/router/src/connector/worldpay/requests.rs +++ b/crates/router/src/connector/worldpay/requests.rs @@ -1,5 +1,99 @@ use masking::Secret; use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct WorldpayPaymentsRequest { + pub transaction_reference: String, + pub merchant: Merchant, + pub instruction: Instruction, + #[serde(skip_serializing_if = "Option::is_none")] + pub customer: Option, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Merchant { + pub entity: Secret, + #[serde(skip_serializing_if = "Option::is_none")] + pub mcc: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub payment_facilitator: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Instruction { + pub settlement: Option, + pub method: PaymentMethod, + pub payment_instrument: PaymentInstrument, + pub narrative: InstructionNarrative, + pub value: PaymentValue, + #[serde(skip_serializing_if = "Option::is_none")] + pub debt_repayment: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum PaymentInstrument { + Card(CardPayment), + CardToken(CardToken), + Googlepay(WalletPayment), + Applepay(WalletPayment), +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CardPayment { + #[serde(rename = "type")] + pub payment_type: PaymentType, + #[serde(skip_serializing_if = "Option::is_none")] + pub card_holder_name: Option>, + pub card_number: cards::CardNumber, + pub expiry_date: ExpiryDate, + #[serde(skip_serializing_if = "Option::is_none")] + pub billing_address: Option, + pub cvc: Secret, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CardToken { + #[serde(rename = "type")] + pub payment_type: PaymentType, + pub href: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub cvc: Option>, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WalletPayment { + #[serde(rename = "type")] + pub payment_type: PaymentType, + pub wallet_token: Secret, + #[serde(skip_serializing_if = "Option::is_none")] + pub billing_address: Option, +} + +#[derive( + Clone, Copy, Debug, Eq, Default, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, +)] +#[serde(rename_all = "lowercase")] +pub enum PaymentType { + #[default] + Plain, + Token, + Encrypted, + Checkout, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct ExpiryDate { + pub month: Secret, + pub year: Secret, +} + #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BillingAddress { @@ -17,17 +111,6 @@ pub struct BillingAddress { pub country_code: common_enums::CountryAlpha2, } -#[derive(Clone, Debug, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct WorldpayPaymentsRequest { - pub transaction_reference: String, - pub merchant: Merchant, - pub instruction: Instruction, - pub channel: Channel, - #[serde(skip_serializing_if = "Option::is_none")] - pub customer: Option, -} - #[derive( Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, )] @@ -100,89 +183,25 @@ pub struct NetworkToken { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Instruction { - pub request_auto_settlement: RequestAutoSettlement, - pub narrative: InstructionNarrative, - pub value: PaymentValue, - pub payment_instrument: PaymentInstrument, - #[serde(skip_serializing_if = "Option::is_none")] - pub debt_repayment: Option, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RequestAutoSettlement { - pub enabled: bool, +pub struct AutoSettlement { + pub auto: bool, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct InstructionNarrative { - pub line1: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub line2: Option, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum PaymentInstrument { - Card(CardPayment), - CardToken(CardToken), - Googlepay(WalletPayment), - Applepay(WalletPayment), -} - -#[derive( - Clone, Copy, Debug, Eq, Default, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, -)] -pub enum PaymentType { +#[serde(rename_all = "lowercase")] +pub enum PaymentMethod { #[default] - #[serde(rename = "card/plain")] Card, - #[serde(rename = "card/token")] - CardToken, - #[serde(rename = "card/wallet+googlepay")] - Googlepay, - #[serde(rename = "card/wallet+applepay")] - Applepay, -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CardPayment { - #[serde(rename = "type")] - pub payment_type: PaymentType, - pub card_number: cards::CardNumber, - pub expiry_date: ExpiryDate, - #[serde(skip_serializing_if = "Option::is_none")] - pub card_holder_name: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub billing_address: Option, - pub cvc: Secret, + ApplePay, + GooglePay, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CardToken { - #[serde(rename = "type")] - pub payment_type: PaymentType, - pub href: String, -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct WalletPayment { - #[serde(rename = "type")] - pub payment_type: PaymentType, - pub wallet_token: Secret, +pub struct InstructionNarrative { + pub line1: String, #[serde(skip_serializing_if = "Option::is_none")] - pub billing_address: Option, -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct ExpiryDate { - pub month: Secret, - pub year: Secret, + pub line2: Option, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] @@ -191,16 +210,6 @@ pub struct PaymentValue { pub currency: api_models::enums::Currency, } -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Merchant { - pub entity: Secret, - #[serde(skip_serializing_if = "Option::is_none")] - pub mcc: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub payment_facilitator: Option, -} - #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PaymentFacilitator { diff --git a/crates/router/src/connector/worldpay/response.rs b/crates/router/src/connector/worldpay/response.rs index 82fff5e32137..0c5174c2a696 100644 --- a/crates/router/src/connector/worldpay/response.rs +++ b/crates/router/src/connector/worldpay/response.rs @@ -1,25 +1,96 @@ +use super::requests::*; +use crate::core::errors; +use error_stack::ResultExt; use masking::Secret; use serde::{Deserialize, Serialize}; - -use super::requests::*; -use crate::{core::errors, types, types::transformers::ForeignTryFrom}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WorldpayPaymentsResponse { - pub outcome: Option, - /// Any risk factors which have been identified for the authorization. This section will not appear if no risks are identified. + pub outcome: PaymentOutcome, + pub transaction_reference: String, + #[serde(flatten)] + pub other_fields: WorldpayPaymentResponseFields, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum WorldpayPaymentResponseFields { + AuthorizedResponse(Box), + DDCResponse(DDCResponse), + FraudHighRisk(FraudHighRiskResponse), + RefusedResponse(RefusedResponse), +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthorizedResponse { #[serde(skip_serializing_if = "Option::is_none")] - pub risk_factors: Option>, + pub payment_instrument: Option, #[serde(skip_serializing_if = "Option::is_none")] pub issuer: Option, #[serde(skip_serializing_if = "Option::is_none")] pub scheme: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub payment_instrument: Option, #[serde(rename = "_links", skip_serializing_if = "Option::is_none")] - pub links: Option, + pub links: Option, + #[serde(rename = "_actions")] + pub actions: Option, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, + pub risk_factors: Vec, + pub fraud: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct FraudHighRiskResponse { + pub score: f32, + pub reason: Vec, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RefusedResponse { + pub refusal_description: String, + pub refusal_code: String, + pub risk_factors: Vec, + pub fraud: Fraud, + #[serde(rename = "threeDS")] + pub three_ds: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ThreeDsResponse { + pub outcome: String, + pub issuer_response: IssuerResponse, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum IssuerResponse { + Challenged, + Frictionless, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DDCResponse { + pub device_data_collection: DDCToken, + #[serde(rename = "_actions")] + pub actions: DDCActionLink, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct DDCToken { + pub jwt: String, + pub url: String, + pub bin: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct DDCActionLink { + #[serde(rename = "supply3dsDeviceData")] + supply_ddc_data: ActionLink, + method: String, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -28,16 +99,52 @@ pub enum PaymentOutcome { #[serde(alias = "authorized", alias = "Authorized")] Authorized, Refused, - #[serde(alias = "Sent for Settlement")] SentForSettlement, - #[serde(alias = "Sent for Refund")] SentForRefund, + FraudHighRisk, + #[serde(alias = "3dsDeviceDataRequired")] + ThreeDsDeviceDataRequired, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum RefundOutcome { - #[serde(alias = "Sent for Refund")] - SentForRefund, +pub struct SelfLink { + #[serde(rename = "self")] + pub self_link: SelfLinkInner, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct SelfLinkInner { + pub href: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ActionLinks { + supply_3ds_device_data: Option, + settle_payment: Option, + partially_settle_payment: Option, + refund_payment: Option, + partiall_refund_payment: Option, + cancel_payment: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ActionLink { + pub href: String, + pub method: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Fraud { + pub outcome: FraudOutcome, + pub score: f32, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum FraudOutcome { + LowRisk, + HighRisk, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -69,40 +176,6 @@ pub enum EventType { Unknown, } -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct PaymentLinks { - #[serde( - rename = "cardPayments:events", - skip_serializing_if = "Option::is_none" - )] - pub events: Option, - #[serde( - rename = "cardPayments:settle", - skip_serializing_if = "Option::is_none" - )] - pub settle_event: Option, - #[serde( - rename = "cardPayments:partialSettle", - skip_serializing_if = "Option::is_none" - )] - pub partial_settle_event: Option, - #[serde( - rename = "cardPayments:refund", - skip_serializing_if = "Option::is_none" - )] - pub refund_event: Option, - #[serde( - rename = "cardPayments:partialRefund", - skip_serializing_if = "Option::is_none" - )] - pub partial_refund_event: Option, - #[serde( - rename = "cardPayments:reverse", - skip_serializing_if = "Option::is_none" - )] - pub reverse_event: Option, -} - #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct EventLinks { #[serde(rename = "payments:events", skip_serializing_if = "Option::is_none")] @@ -114,43 +187,51 @@ pub struct PaymentLink { pub href: String, } -fn get_resource_id( - links: Option, +pub fn get_resource_id( + response: WorldpayPaymentsResponse, + connector_transaction_id: Option, transform_fn: F, ) -> Result> where F: Fn(String) -> T, { - let reference_id = links - .and_then(|l| l.events) - .and_then(|e| e.href.rsplit_once('/').map(|h| h.1.to_string())) - .map(transform_fn); - reference_id.ok_or_else(|| { - errors::ConnectorError::MissingRequiredField { - field_name: "links.events", - } - .into() - }) + let reference_id = match response.other_fields { + WorldpayPaymentResponseFields::AuthorizedResponse(res) => res + .links + .as_ref() + .and_then(|link| link.self_link.href.rsplit_once('/')) + .map(|(_, h)| urlencoding::decode(h)) + .transpose() + .change_context(errors::ConnectorError::ResponseHandlingFailed)? + .map(|s| transform_fn(s.into_owned())), + WorldpayPaymentResponseFields::DDCResponse(res) => res + .actions + .supply_ddc_data + .href + .split('/') + .rev() + .nth(1) + .map(urlencoding::decode) + .transpose() + .change_context(errors::ConnectorError::ResponseHandlingFailed)? + .map(|s| transform_fn(s.into_owned())), + WorldpayPaymentResponseFields::FraudHighRisk(_) => None, + WorldpayPaymentResponseFields::RefusedResponse(_) => None, + }; + reference_id + .or_else(|| connector_transaction_id.map(transform_fn)) + .ok_or_else(|| { + errors::ConnectorError::MissingRequiredField { + field_name: "_links.self.href", + } + .into() + }) } pub struct ResponseIdStr { pub id: String, } -impl TryFrom> for ResponseIdStr { - type Error = error_stack::Report; - fn try_from(links: Option) -> Result { - get_resource_id(links, |id| Self { id }) - } -} - -impl ForeignTryFrom> for types::ResponseId { - type Error = error_stack::Report; - fn foreign_try_from(links: Option) -> Result { - get_resource_id(links, Self::ConnectorTransactionId) - } -} - #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Issuer { @@ -172,10 +253,10 @@ pub struct PaymentsResPaymentInstrument { pub payment_instrument_type: Option, pub card_bin: Option, pub last_four: Option, - pub category: Option, pub expiry_date: Option, pub card_brand: Option, pub funding_type: Option, + pub category: Option, pub issuer_name: Option, pub payment_account_reference: Option, } @@ -230,7 +311,7 @@ pub enum RiskType { #[derive( Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, )] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "lowercase")] pub enum Detail { #[default] Address, diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index 06f737268e0e..910a849cf3de 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -51,7 +51,7 @@ fn fetch_payment_instrument( ) -> CustomResult { match payment_method { domain::PaymentMethodData::Card(card) => Ok(PaymentInstrument::Card(CardPayment { - payment_type: PaymentType::Card, + payment_type: PaymentType::Plain, expiry_date: ExpiryDate { month: utils::CardData::get_expiry_month_as_i8(&card)?, year: utils::CardData::get_expiry_year_as_i32(&card)?, @@ -85,13 +85,13 @@ fn fetch_payment_instrument( domain::PaymentMethodData::Wallet(wallet) => match wallet { domain::WalletData::GooglePay(data) => { Ok(PaymentInstrument::Googlepay(WalletPayment { - payment_type: PaymentType::Googlepay, + payment_type: PaymentType::Encrypted, wallet_token: Secret::new(data.tokenization_data.token), ..WalletPayment::default() })) } domain::WalletData::ApplePay(data) => Ok(PaymentInstrument::Applepay(WalletPayment { - payment_type: PaymentType::Applepay, + payment_type: PaymentType::Encrypted, wallet_token: Secret::new(data.payment_data), ..WalletPayment::default() })), @@ -149,6 +149,27 @@ fn fetch_payment_instrument( } } +impl TryFrom<(enums::PaymentMethod, enums::PaymentMethodType)> for PaymentMethod { + type Error = error_stack::Report; + fn try_from( + src: (enums::PaymentMethod, enums::PaymentMethodType), + ) -> Result { + match (src.0, src.1) { + (enums::PaymentMethod::Card, _) => Ok(Self::Card), + (enums::PaymentMethod::Wallet, enums::PaymentMethodType::ApplePay) => { + Ok(Self::ApplePay) + } + (enums::PaymentMethod::Wallet, enums::PaymentMethodType::GooglePay) => { + Ok(Self::GooglePay) + } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("worldpay"), + ) + .into()), + } + } +} + impl TryFrom<( &WorldpayRouterData< @@ -178,14 +199,27 @@ impl let (item, entity_id) = req; Ok(Self { instruction: Instruction { - request_auto_settlement: RequestAutoSettlement { - enabled: item.router_data.request.capture_method - == Some(enums::CaptureMethod::Automatic), - }, - value: PaymentValue { - amount: item.amount, - currency: item.router_data.request.currency, - }, + settlement: item + .router_data + .request + .capture_method + .map(|capture_method| AutoSettlement { + auto: capture_method == enums::CaptureMethod::Automatic, + }), + method: item + .router_data + .request + .payment_method_type + .map(|pmt| PaymentMethod::try_from((item.router_data.payment_method, pmt))) + .transpose()? + .get_required_value("payment_method") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "payment_method", + })?, + payment_instrument: fetch_payment_instrument( + item.router_data.request.payment_method_data.clone(), + item.router_data.get_optional_billing(), + )?, narrative: InstructionNarrative { line1: item .router_data @@ -194,10 +228,10 @@ impl .replace('_', "-"), ..Default::default() }, - payment_instrument: fetch_payment_instrument( - item.router_data.request.payment_method_data.clone(), - item.router_data.get_optional_billing(), - )?, + value: PaymentValue { + amount: item.amount, + currency: item.router_data.request.currency, + }, debt_repayment: None, }, merchant: Merchant { @@ -205,7 +239,6 @@ impl ..Default::default() }, transaction_reference: item.router_data.connector_request_reference_id.clone(), - channel: Channel::Ecom, customer: None, }) } @@ -250,9 +283,10 @@ impl From for enums::AttemptStatus { fn from(item: PaymentOutcome) -> Self { match item { PaymentOutcome::Authorized => Self::Authorized, - PaymentOutcome::Refused => Self::Failure, + PaymentOutcome::Refused | PaymentOutcome::FraudHighRisk => Self::Failure, PaymentOutcome::SentForSettlement => Self::CaptureInitiated, PaymentOutcome::SentForRefund => Self::AutoRefunded, + PaymentOutcome::ThreeDsDeviceDataRequired => Self::DeviceDataCollectionPending, } } } @@ -293,32 +327,43 @@ impl From for enums::RefundStatus { } } -impl TryFrom> - for types::PaymentsAuthorizeRouterData +impl + ForeignTryFrom<( + types::PaymentsResponseRouterData, + Option, + )> for types::PaymentsAuthorizeRouterData { type Error = error_stack::Report; - fn try_from( - item: types::PaymentsResponseRouterData, + fn foreign_try_from( + item: ( + types::PaymentsResponseRouterData, + Option, + ), ) -> Result { + let (router_data, optional_correlation_id) = item; + let description = match router_data.response.other_fields { + WorldpayPaymentResponseFields::AuthorizedResponse(ref res) => res.description.clone(), + WorldpayPaymentResponseFields::DDCResponse(_) + | WorldpayPaymentResponseFields::FraudHighRisk(_) + | WorldpayPaymentResponseFields::RefusedResponse(_) => None, + }; Ok(Self { - status: match item.response.outcome { - Some(outcome) => enums::AttemptStatus::from(outcome), - None => Err(errors::ConnectorError::MissingRequiredField { - field_name: "outcome", - })?, - }, - description: item.response.description, + status: enums::AttemptStatus::from(router_data.response.outcome.clone()), + description, response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::foreign_try_from(item.response.links)?, + resource_id: types::ResponseId::foreign_try_from(( + router_data.response, + optional_correlation_id.clone(), + ))?, redirection_data: None, mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: optional_correlation_id, incremental_authorization_allowed: None, charge_id: None, }), - ..item.data + ..router_data.data }) } } @@ -360,3 +405,21 @@ impl TryFrom for WorldpayEventResponse { }) } } + +impl ForeignTryFrom<(WorldpayPaymentsResponse, Option)> for ResponseIdStr { + type Error = error_stack::Report; + fn foreign_try_from( + item: (WorldpayPaymentsResponse, Option), + ) -> Result { + get_resource_id(item.0, item.1, |id| Self { id }) + } +} + +impl ForeignTryFrom<(WorldpayPaymentsResponse, Option)> for types::ResponseId { + type Error = error_stack::Report; + fn foreign_try_from( + item: (WorldpayPaymentsResponse, Option), + ) -> Result { + get_resource_id(item.0, item.1, Self::ConnectorTransactionId) + } +} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index c1eac13832bf..fd762321242a 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -86,6 +86,7 @@ pub mod headers { pub const X_APP_ID: &str = "x-app-id"; pub const X_REDIRECT_URI: &str = "x-redirect-uri"; pub const X_TENANT_ID: &str = "x-tenant-id"; + pub const X_WP_API_VERSION: &str = "WP-Api-Version"; } pub mod pii { From 7c68125e3d2ec33a97d6b061830b3a5c2f0dde88 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 09:51:55 +0000 Subject: [PATCH 21/28] chore: run formatter --- crates/router/src/connector/worldpay/response.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/router/src/connector/worldpay/response.rs b/crates/router/src/connector/worldpay/response.rs index 0c5174c2a696..5e6648d25037 100644 --- a/crates/router/src/connector/worldpay/response.rs +++ b/crates/router/src/connector/worldpay/response.rs @@ -1,8 +1,9 @@ -use super::requests::*; -use crate::core::errors; use error_stack::ResultExt; use masking::Secret; use serde::{Deserialize, Serialize}; + +use super::requests::*; +use crate::core::errors; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WorldpayPaymentsResponse { From 0d4318a25aa05d95b1653ab45703c7e6a496b3b9 Mon Sep 17 00:00:00 2001 From: Kashif Date: Tue, 15 Oct 2024 19:49:19 +0530 Subject: [PATCH 22/28] fix: update pm_filters for worldpay --- config/deployments/sandbox.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index bf73724b315a..6cc1193ab558 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -339,7 +339,7 @@ upi_collect = {country = "IN", currency = "INR"} [pm_filters.plaid] open_banking_pis = {currency = "EUR,GBP"} -[pm_filters] +[pm_filters.worldpay] debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SAR,RSD,SCR,SLL,SG,ST,SBD,SOS,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,TMT,AED,UG,UA,US,UZ,VU,VE,VN,YER,CNY,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SAR,RSD,SCR,SLL,SG,ST,SBD,SOS,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,TMT,AED,UG,UA,US,UZ,VU,VE,VN,YER,CNY,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" From b0509b0f7bcec00656692f037765d017b03a33ea Mon Sep 17 00:00:00 2001 From: Kashif Date: Wed, 16 Oct 2024 12:02:00 +0530 Subject: [PATCH 23/28] refactor: remove intent_status from ConnectorIntegration data for PSync --- .../src/router_request_types.rs | 3 --- crates/router/src/connector/worldpay.rs | 26 ++++++++++++------- .../router/src/connector/worldpay/response.rs | 21 ++++++++------- .../src/connector/worldpay/transformers.rs | 12 +++++---- crates/router/src/core/payments.rs | 9 ------- .../router/src/core/payments/transformers.rs | 2 -- 6 files changed, 34 insertions(+), 39 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 04612e77e96c..39047f7015a7 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -1,7 +1,6 @@ pub mod authentication; pub mod fraud_check; use api_models::payments::{Address, RequestSurchargeDetails}; -use common_enums::{AttemptStatus, IntentStatus}; use common_utils::{ consts, errors, ext_traits::OptionExt, @@ -441,8 +440,6 @@ pub struct PaymentsSyncData { pub amount: MinorUnit, pub integrity_object: Option, - pub attempt_status: AttemptStatus, - pub intent_status: IntentStatus, } #[derive(Debug, Default, Clone)] diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index f542d700fb77..b5469f12c16d 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -349,12 +349,17 @@ impl ConnectorIntegration attempt_status, - (enums::AttemptStatus::Pending, _, EventType::Authorized) => attempt_status, + let status = match (attempt_status, worldpay_status.clone()) { + ( + enums::AttemptStatus::Authorizing + | enums::AttemptStatus::Authorized + | enums::AttemptStatus::CaptureInitiated + | enums::AttemptStatus::Pending + | enums::AttemptStatus::VoidInitiated, + EventType::Authorized, + ) => attempt_status, _ => enums::AttemptStatus::from(&worldpay_status), }; @@ -821,19 +826,20 @@ impl api::IncomingWebhook for Worldpay { .parse_struct("WorldpayWebhookEventType") .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; match body.event_details.event_type { - EventType::SentForSettlement | EventType::Charged => { - Ok(api::IncomingWebhookEvent::PaymentIntentSuccess) + EventType::Authorized => { + Ok(api::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess) } - EventType::Error | EventType::Expired => { + EventType::SentForSettlement => Ok(api::IncomingWebhookEvent::PaymentIntentProcessing), + EventType::Settled => Ok(api::IncomingWebhookEvent::PaymentIntentSuccess), + EventType::Error | EventType::Expired | EventType::SettlementFailed => { Ok(api::IncomingWebhookEvent::PaymentIntentFailure) } EventType::Unknown - | EventType::Authorized + | EventType::SentForAuthorization | EventType::Cancelled | EventType::Refused | EventType::Refunded | EventType::SentForRefund - | EventType::CaptureFailed | EventType::RefundFailed => Ok(api::IncomingWebhookEvent::EventNotSupported), } } diff --git a/crates/router/src/connector/worldpay/response.rs b/crates/router/src/connector/worldpay/response.rs index 82fff5e32137..dbd596e2e202 100644 --- a/crates/router/src/connector/worldpay/response.rs +++ b/crates/router/src/connector/worldpay/response.rs @@ -51,20 +51,21 @@ pub struct WorldpayEventResponse { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum EventType { - #[serde(alias = "Authorized", alias = "authorized")] + SentForAuthorization, + #[serde(alias = "Authorized")] Authorized, + #[serde(alias = "Sent for Settlement")] + SentForSettlement, + Settled, + SettlementFailed, Cancelled, - Charged, - #[serde(rename = "Sent for Refund")] - SentForRefund, - RefundFailed, - Refused, - Refunded, Error, - #[serde(rename = "Sent for Settlement")] - SentForSettlement, Expired, - CaptureFailed, + Refused, + #[serde(alias = "Sent for Refund")] + SentForRefund, + Refunded, + RefundFailed, #[serde(other)] Unknown, } diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index 06f737268e0e..c5398b83a86f 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -260,10 +260,11 @@ impl From for enums::AttemptStatus { impl From<&EventType> for enums::AttemptStatus { fn from(value: &EventType) -> Self { match value { + EventType::SentForAuthorization => Self::Authorizing, + EventType::SentForSettlement => Self::CaptureInitiated, + EventType::Settled => Self::Charged, EventType::Authorized => Self::Authorized, - EventType::CaptureFailed => Self::CaptureFailed, - EventType::Refused => Self::Failure, - EventType::Charged | EventType::SentForSettlement => Self::Charged, + EventType::Refused | EventType::SettlementFailed => Self::Failure, EventType::Cancelled | EventType::SentForRefund | EventType::RefundFailed @@ -282,12 +283,13 @@ impl From for enums::RefundStatus { EventType::RefundFailed => Self::Failure, EventType::Authorized | EventType::Cancelled - | EventType::Charged + | EventType::Settled | EventType::Refused | EventType::Error | EventType::SentForSettlement + | EventType::SentForAuthorization + | EventType::SettlementFailed | EventType::Expired - | EventType::CaptureFailed | EventType::Unknown => Self::Pending, } } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 908e44db6b2f..68f318958a2c 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -5788,7 +5788,6 @@ pub trait OperationSessionGetters { fn get_mandate_connector(&self) -> Option<&MandateConnectorDetails>; fn get_force_sync(&self) -> Option; fn get_capture_method(&self) -> Option; - fn get_intent_status(&self) -> enums::IntentStatus; } pub trait OperationSessionSetters { @@ -5966,10 +5965,6 @@ impl OperationSessionGetters for PaymentData { fn get_capture_method(&self) -> Option { self.payment_intent.capture_method } - - fn get_intent_status(&self) -> enums::IntentStatus { - self.payment_intent.status - } } impl OperationSessionSetters for PaymentData { @@ -6191,10 +6186,6 @@ impl OperationSessionGetters for PaymentIntentData { fn get_capture_method(&self) -> Option { todo!() } - - fn get_intent_status(&self) -> enums::IntentStatus { - self.payment_intent.status - } } #[cfg(feature = "v2")] diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index a6dfd20c8264..8ad39ffc894c 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1852,8 +1852,6 @@ impl TryFrom> for types::PaymentsSyncData } None => types::ResponseId::NoResponseId, }, - intent_status: payment_data.get_intent_status(), - attempt_status: payment_data.payment_attempt.status, encoded_data: payment_data.payment_attempt.encoded_data, capture_method, connector_meta: payment_data.payment_attempt.connector_metadata, From 5fb6afcae80f7886ca26af52aaa0b592f42b0f82 Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 17 Oct 2024 19:10:01 +0530 Subject: [PATCH 24/28] fix(worldpay): update narrative for worldpay during authorize request --- .../connector_configs/toml/development.toml | 6 +++ crates/connector_configs/toml/production.toml | 6 +++ crates/connector_configs/toml/sandbox.toml | 6 +++ crates/router/src/connector/worldpay.rs | 8 +++- .../router/src/connector/worldpay/requests.rs | 2 - .../router/src/connector/worldpay/response.rs | 9 +++- .../src/connector/worldpay/transformers.rs | 46 ++++++++++++++----- 7 files changed, 67 insertions(+), 16 deletions(-) diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 56c19637e517..78f3c627fcaf 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -3416,6 +3416,12 @@ key1="Password" api_secret="Merchant Identifier" [worldpay.connector_webhook_details] merchant_secret="Source verification key" +[worldpay.metadata.merchant_name] +name="merchant_name" +label="Name of the merchant to de displayed during 3DS challenge" +placeholder="Enter Name of the merchant" +required=true +type="Text" [[worldpay.metadata.apple_pay]] name="certificate" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index c8005087a167..9ca992ecad4b 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -2473,6 +2473,12 @@ merchant_secret="Source verification key" api_key="Username" key1="Password" api_secret="Merchant Identifier" +[worldpay.metadata.merchant_name] +name="merchant_name" +label="Name of the merchant to de displayed during 3DS challenge" +placeholder="Enter Name of the merchant" +required=true +type="Text" [[worldpay.metadata.apple_pay]] name="certificate" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index a1d81f27231c..39421babc873 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -3406,6 +3406,12 @@ key1="Password" api_secret="Merchant Identifier" [worldpay.connector_webhook_details] merchant_secret="Source verification key" +[worldpay.metadata.merchant_name] +name="merchant_name" +label="Name of the merchant to de displayed during 3DS challenge" +placeholder="Enter Name of the merchant" +required=true +type="Text" [[worldpay.metadata.apple_pay]] name="certificate" diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index 09f15778fdd4..42057eebd5ba 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -253,7 +253,7 @@ impl ConnectorIntegration CustomResult { + if req.auth_type == enums::AuthenticationType::ThreeDs { + return Err(errors::ConnectorError::NotImplemented( + "ThreeDS flow through worldpay".to_string(), + ) + .into()); + } let connector_router_data = worldpay::WorldpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, diff --git a/crates/router/src/connector/worldpay/requests.rs b/crates/router/src/connector/worldpay/requests.rs index c617e1f81c31..3d0be891ebb9 100644 --- a/crates/router/src/connector/worldpay/requests.rs +++ b/crates/router/src/connector/worldpay/requests.rs @@ -200,8 +200,6 @@ pub enum PaymentMethod { #[serde(rename_all = "camelCase")] pub struct InstructionNarrative { pub line1: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub line2: Option, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] diff --git a/crates/router/src/connector/worldpay/response.rs b/crates/router/src/connector/worldpay/response.rs index a101a9f4a411..0a7f690c3aa9 100644 --- a/crates/router/src/connector/worldpay/response.rs +++ b/crates/router/src/connector/worldpay/response.rs @@ -8,7 +8,7 @@ use crate::core::errors; #[serde(rename_all = "camelCase")] pub struct WorldpayPaymentsResponse { pub outcome: PaymentOutcome, - pub transaction_reference: String, + pub transaction_reference: Option, #[serde(flatten)] pub other_fields: WorldpayPaymentResponseFields, } @@ -37,7 +37,7 @@ pub struct AuthorizedResponse { pub actions: Option, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, - pub risk_factors: Vec, + pub risk_factors: Option>, pub fraud: Option, } @@ -105,6 +105,11 @@ pub enum PaymentOutcome { FraudHighRisk, #[serde(alias = "3dsDeviceDataRequired")] ThreeDsDeviceDataRequired, + ThreeDsChallenged, + SentForCancellation, + #[serde(alias = "3dsAuthenticationFailed")] + ThreeDsAuthenticationFailed, + SentForPartialRefund, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index b6d10a7638f3..8a676c251a27 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -1,11 +1,11 @@ use api_models::payments::Address; use base64::Engine; -use common_utils::{errors::CustomResult, ext_traits::OptionExt, types::MinorUnit}; +use common_utils::{errors::CustomResult, ext_traits::OptionExt, pii, types::MinorUnit}; use diesel_models::enums; use error_stack::ResultExt; use hyperswitch_connectors::utils::RouterData; -use masking::{PeekInterface, Secret}; -use serde::Serialize; +use masking::{ExposeInterface, PeekInterface, Secret}; +use serde::{Deserialize, Serialize}; use super::{requests::*, response::*}; use crate::{ @@ -45,6 +45,23 @@ impl }) } } + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct WorldpayConnectorMetadataObject { + pub merchant_name: Option>, +} + +impl TryFrom<&Option> for WorldpayConnectorMetadataObject { + type Error = error_stack::Report; + fn try_from(meta_data: &Option) -> Result { + let metadata: Self = utils::to_connector_meta_from_secret::(meta_data.clone()) + .change_context(errors::ConnectorError::InvalidConnectorConfig { + config: "metadata", + })?; + Ok(metadata) + } +} + fn fetch_payment_instrument( payment_method: domain::PaymentMethodData, billing_address: Option<&Address>, @@ -197,6 +214,13 @@ impl ), ) -> Result { let (item, entity_id) = req; + let worldpay_connector_metadata_object: WorldpayConnectorMetadataObject = + WorldpayConnectorMetadataObject::try_from(&item.router_data.connector_meta_data)?; + let merchant_name = worldpay_connector_metadata_object.merchant_name.ok_or( + errors::ConnectorError::InvalidConnectorConfig { + config: "metadata.merchant_name", + }, + )?; Ok(Self { instruction: Instruction { settlement: item @@ -221,12 +245,7 @@ impl item.router_data.get_optional_billing(), )?, narrative: InstructionNarrative { - line1: item - .router_data - .merchant_id - .get_string_repr() - .replace('_', "-"), - ..Default::default() + line1: merchant_name.expose(), }, value: PaymentValue { amount: item.amount, @@ -283,10 +302,15 @@ impl From for enums::AttemptStatus { fn from(item: PaymentOutcome) -> Self { match item { PaymentOutcome::Authorized => Self::Authorized, - PaymentOutcome::Refused | PaymentOutcome::FraudHighRisk => Self::Failure, PaymentOutcome::SentForSettlement => Self::CaptureInitiated, - PaymentOutcome::SentForRefund => Self::AutoRefunded, PaymentOutcome::ThreeDsDeviceDataRequired => Self::DeviceDataCollectionPending, + PaymentOutcome::ThreeDsAuthenticationFailed => Self::AuthenticationFailed, + PaymentOutcome::ThreeDsChallenged => Self::AuthenticationPending, + PaymentOutcome::SentForCancellation => Self::VoidInitiated, + PaymentOutcome::SentForPartialRefund | PaymentOutcome::SentForRefund => { + Self::AutoRefunded + } + PaymentOutcome::Refused | PaymentOutcome::FraudHighRisk => Self::Failure, } } } From e1fcfdec3b01a8e47a07f782895036880e64a67b Mon Sep 17 00:00:00 2001 From: Kashif Date: Fri, 18 Oct 2024 16:34:08 +0530 Subject: [PATCH 25/28] refactor(worldpay): updated transformers --- .../src/query/payment_attempt.rs | 14 +++- crates/router/src/connector/worldpay.rs | 13 +--- .../src/connector/worldpay/transformers.rs | 74 +++++++++++-------- 3 files changed, 59 insertions(+), 42 deletions(-) diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index 85c698248924..0627fa0048a2 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -171,11 +171,23 @@ impl PaymentAttempt { merchant_id: &common_utils::id_type::MerchantId, connector_txn_id: &str, ) -> StorageResult { + let (txn_id, txn_data) = common_utils::types::ConnectorTransactionId::form_id_and_data( + connector_txn_id.to_string(), + ); + let connector_transaction_id = txn_id + .get_txn_id(txn_data.as_ref()) + .change_context(DatabaseError::Others) + .attach_printable_lazy(|| { + format!( + "Failed to retrieve txn_id for ({:?}, {:?})", + txn_id, txn_data + ) + })?; generics::generic_find_one::<::Table, _, _>( conn, dsl::merchant_id .eq(merchant_id.to_owned()) - .and(dsl::connector_transaction_id.eq(connector_txn_id.to_owned())), + .and(dsl::connector_transaction_id.eq(connector_transaction_id.to_owned())), ) .await } diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index 42057eebd5ba..79c63eb4cf97 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -548,12 +548,6 @@ impl ConnectorIntegration CustomResult { - if req.auth_type == enums::AuthenticationType::ThreeDs { - return Err(errors::ConnectorError::NotImplemented( - "ThreeDS flow through worldpay".to_string(), - ) - .into()); - } let connector_router_data = worldpay::WorldpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -861,7 +855,7 @@ impl api::IncomingWebhook for Worldpay { .parse_struct("WorldpayWebhookTransactionId") .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; Ok(api_models::webhooks::ObjectReferenceId::PaymentId( - api::PaymentIdType::ConnectorTransactionId(body.event_details.transaction_reference), + api::PaymentIdType::PaymentAttemptId(body.event_details.transaction_reference), )) } @@ -877,13 +871,14 @@ impl api::IncomingWebhook for Worldpay { EventType::Authorized => { Ok(api::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess) } - EventType::SentForSettlement => Ok(api::IncomingWebhookEvent::PaymentIntentProcessing), EventType::Settled => Ok(api::IncomingWebhookEvent::PaymentIntentSuccess), + EventType::SentForSettlement | EventType::SentForAuthorization => { + Ok(api::IncomingWebhookEvent::PaymentIntentProcessing) + } EventType::Error | EventType::Expired | EventType::SettlementFailed => { Ok(api::IncomingWebhookEvent::PaymentIntentFailure) } EventType::Unknown - | EventType::SentForAuthorization | EventType::Cancelled | EventType::Refused | EventType::Refunded diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index 8a676c251a27..d7a417af8865 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -65,40 +65,49 @@ impl TryFrom<&Option> for WorldpayConnectorMetadataObject fn fetch_payment_instrument( payment_method: domain::PaymentMethodData, billing_address: Option<&Address>, + auth_type: enums::AuthenticationType, ) -> CustomResult { match payment_method { - domain::PaymentMethodData::Card(card) => Ok(PaymentInstrument::Card(CardPayment { - payment_type: PaymentType::Plain, - expiry_date: ExpiryDate { - month: utils::CardData::get_expiry_month_as_i8(&card)?, - year: utils::CardData::get_expiry_year_as_i32(&card)?, - }, - card_number: card.card_number, - cvc: card.card_cvc, - card_holder_name: card.nick_name, - billing_address: if let Some(address) = - billing_address.and_then(|addr| addr.address.clone()) - { - Some(BillingAddress { - address1: address.line1, - address2: address.line2, - address3: address.line3, - city: address.city, - state: address.state, - postal_code: address.zip.get_required_value("zip").change_context( - errors::ConnectorError::MissingRequiredField { field_name: "zip" }, - )?, - country_code: address - .country - .get_required_value("country_code") - .change_context(errors::ConnectorError::MissingRequiredField { - field_name: "country_code", - })?, - }) - } else { - None - }, - })), + domain::PaymentMethodData::Card(card) => { + if auth_type == enums::AuthenticationType::ThreeDs { + return Err(errors::ConnectorError::NotImplemented( + "ThreeDS flow through worldpay".to_string(), + ) + .into()); + } + Ok(PaymentInstrument::Card(CardPayment { + payment_type: PaymentType::Plain, + expiry_date: ExpiryDate { + month: utils::CardData::get_expiry_month_as_i8(&card)?, + year: utils::CardData::get_expiry_year_as_i32(&card)?, + }, + card_number: card.card_number, + cvc: card.card_cvc, + card_holder_name: card.nick_name, + billing_address: if let Some(address) = + billing_address.and_then(|addr| addr.address.clone()) + { + Some(BillingAddress { + address1: address.line1, + address2: address.line2, + address3: address.line3, + city: address.city, + state: address.state, + postal_code: address.zip.get_required_value("zip").change_context( + errors::ConnectorError::MissingRequiredField { field_name: "zip" }, + )?, + country_code: address + .country + .get_required_value("country_code") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "country_code", + })?, + }) + } else { + None + }, + })) + } domain::PaymentMethodData::Wallet(wallet) => match wallet { domain::WalletData::GooglePay(data) => { Ok(PaymentInstrument::Googlepay(WalletPayment { @@ -243,6 +252,7 @@ impl payment_instrument: fetch_payment_instrument( item.router_data.request.payment_method_data.clone(), item.router_data.get_optional_billing(), + item.router_data.auth_type.clone(), )?, narrative: InstructionNarrative { line1: merchant_name.expose(), From a0f086cb7f5b82601eb333ebf73c9d3a53abf896 Mon Sep 17 00:00:00 2001 From: Kashif Date: Fri, 18 Oct 2024 16:46:08 +0530 Subject: [PATCH 26/28] fix: clippy fix --- crates/router/src/connector/worldpay/transformers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index d7a417af8865..a0f2bfd2508b 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -252,7 +252,7 @@ impl payment_instrument: fetch_payment_instrument( item.router_data.request.payment_method_data.clone(), item.router_data.get_optional_billing(), - item.router_data.auth_type.clone(), + item.router_data.auth_type, )?, narrative: InstructionNarrative { line1: merchant_name.expose(), From 956f068992101c5cdeefb550d047a3955e8a03ec Mon Sep 17 00:00:00 2001 From: Kashif Date: Sun, 20 Oct 2024 14:56:23 +0530 Subject: [PATCH 27/28] feat(connector): add 3DS flow for Worldpay --- .../src/router_response_types.rs | 6 + crates/router/src/connector/worldpay.rs | 124 +++++++++ .../router/src/connector/worldpay/requests.rs | 47 ++++ .../router/src/connector/worldpay/response.rs | 116 ++++++--- .../src/connector/worldpay/transformers.rs | 237 +++++++++++++----- crates/router/src/core/payments/flows.rs | 2 - crates/router/src/services/api.rs | 121 +++++++++ 7 files changed, 560 insertions(+), 93 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index eca56b8c866f..6682ac1ad44c 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -163,6 +163,12 @@ pub enum RedirectForm { Mifinity { initialization_token: String, }, + WorldpayDDCForm { + endpoint: url::Url, + method: Method, + form_fields: HashMap, + collection_id: Option, + }, } impl From<(url::Url, Method)> for RedirectForm { diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index 79c63eb4cf97..f5f87d299e4f 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -623,6 +623,113 @@ impl ConnectorIntegration for Worldpay +{ + fn get_headers( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let connector_payment_id = req + .request + .connector_transaction_id + .clone() + .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?; + let stage = match req.status { + enums::AttemptStatus::DeviceDataCollectionPending => "3dsDeviceData".to_string(), + _ => "3dsChallenges".to_string(), + }; + Ok(format!( + "{}api/payments/{connector_payment_id}/{stage}", + self.base_url(connectors), + )) + } + + fn get_request_body( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = WorldpayCompleteAuthorizationRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCompleteAuthorizeType::get_url( + self, req, connectors, + )?) + .headers(types::PaymentsCompleteAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &types::PaymentsCompleteAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: WorldpayPaymentsResponse = res + .response + .parse_struct("WorldpayPaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + let optional_correlation_id = res.headers.and_then(|headers| { + headers + .get("WP-CorrelationId") + .and_then(|header_value| header_value.to_str().ok()) + .map(|id| id.to_string()) + }); + types::RouterData::foreign_try_from(( + types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }, + optional_correlation_id, + )) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + impl api::Refund for Worldpay {} impl api::RefundExecute for Worldpay {} impl api::RefundSync for Worldpay {} @@ -899,3 +1006,20 @@ impl api::IncomingWebhook for Worldpay { Ok(Box::new(psync_body)) } } + +impl services::ConnectorRedirectResponse for Worldpay { + fn get_flow_type( + &self, + _query_params: &str, + _json_payload: Option, + action: services::PaymentAction, + ) -> CustomResult { + match action { + services::PaymentAction::CompleteAuthorize => Ok(enums::CallConnectorAction::Trigger), + services::PaymentAction::PSync + | services::PaymentAction::PaymentAuthenticateCompleteAuthorize => { + Ok(enums::CallConnectorAction::Avoid) + } + } + } +} diff --git a/crates/router/src/connector/worldpay/requests.rs b/crates/router/src/connector/worldpay/requests.rs index 3d0be891ebb9..b0fa85a64c36 100644 --- a/crates/router/src/connector/worldpay/requests.rs +++ b/crates/router/src/connector/worldpay/requests.rs @@ -31,6 +31,8 @@ pub struct Instruction { pub value: PaymentValue, #[serde(skip_serializing_if = "Option::is_none")] pub debt_repayment: Option, + #[serde(rename = "threeDS")] + pub three_ds: Option, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -187,6 +189,44 @@ pub struct AutoSettlement { pub auto: bool, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ThreeDSRequest { + #[serde(rename = "type")] + pub three_ds_type: String, + pub mode: String, + pub device_data: ThreeDSRequestDeviceData, + pub challenge: ThreeDSRequestChallenge, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ThreeDSRequestDeviceData { + pub accept_header: String, + pub user_agent_header: String, + pub browser_language: Option, + pub browser_screen_width: Option, + pub browser_screen_height: Option, + pub browser_color_depth: Option, + pub time_zone: Option, + pub browser_java_enabled: Option, + pub browser_javascript_enabled: Option, + pub channel: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ThreeDSRequestChannel { + Browser, + Native, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ThreeDSRequestChallenge { + pub return_url: String, +} + #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum PaymentMethod { @@ -237,3 +277,10 @@ pub struct WorldpayPartialRequest { pub value: PaymentValue, pub reference: String, } + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorldpayCompleteAuthorizationRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub collection_reference: Option, +} diff --git a/crates/router/src/connector/worldpay/response.rs b/crates/router/src/connector/worldpay/response.rs index 0a7f690c3aa9..5e436b19ed07 100644 --- a/crates/router/src/connector/worldpay/response.rs +++ b/crates/router/src/connector/worldpay/response.rs @@ -1,6 +1,7 @@ use error_stack::ResultExt; use masking::Secret; use serde::{Deserialize, Serialize}; +use url::Url; use super::requests::*; use crate::core::errors; @@ -10,7 +11,7 @@ pub struct WorldpayPaymentsResponse { pub outcome: PaymentOutcome, pub transaction_reference: Option, #[serde(flatten)] - pub other_fields: WorldpayPaymentResponseFields, + pub other_fields: Option, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -20,13 +21,13 @@ pub enum WorldpayPaymentResponseFields { DDCResponse(DDCResponse), FraudHighRisk(FraudHighRiskResponse), RefusedResponse(RefusedResponse), + ThreeDsChallenged(ThreeDsChallengedResponse), } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AuthorizedResponse { - #[serde(skip_serializing_if = "Option::is_none")] - pub payment_instrument: Option, + pub payment_instrument: PaymentsResPaymentInstrument, #[serde(skip_serializing_if = "Option::is_none")] pub issuer: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -67,6 +68,34 @@ pub struct ThreeDsResponse { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +pub struct ThreeDsChallengedResponse { + pub authentication: AuthenticationResponse, + pub challenge: ThreeDsChallenge, + #[serde(rename = "_actions")] + pub actions: CompleteThreeDsActionLink, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AuthenticationResponse { + pub version: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ThreeDsChallenge { + pub reference: String, + pub url: Url, + pub jwt: Secret, + pub payload: Secret, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CompleteThreeDsActionLink { + #[serde(rename = "complete3dsChallenge")] + pub complete_three_ds_challenge: ActionLink, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub enum IssuerResponse { Challenged, Frictionless, @@ -82,16 +111,15 @@ pub struct DDCResponse { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DDCToken { - pub jwt: String, - pub url: String, - pub bin: String, + pub jwt: Secret, + pub url: Url, + pub bin: Secret, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DDCActionLink { #[serde(rename = "supply3dsDeviceData")] supply_ddc_data: ActionLink, - method: String, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -105,11 +133,32 @@ pub enum PaymentOutcome { FraudHighRisk, #[serde(alias = "3dsDeviceDataRequired")] ThreeDsDeviceDataRequired, - ThreeDsChallenged, SentForCancellation, #[serde(alias = "3dsAuthenticationFailed")] ThreeDsAuthenticationFailed, SentForPartialRefund, + #[serde(alias = "3dsChallenged")] + ThreeDsChallenged, + #[serde(alias = "3dsUnavailable")] + ThreeDsUnavailable, +} + +impl std::fmt::Display for PaymentOutcome { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Authorized => write!(f, "authorized"), + Self::Refused => write!(f, "refused"), + Self::SentForSettlement => write!(f, "sentForSettlement"), + Self::SentForRefund => write!(f, "sentForRefund"), + Self::FraudHighRisk => write!(f, "fraudHighRisk"), + Self::ThreeDsDeviceDataRequired => write!(f, "3dsDeviceDataRequired"), + Self::SentForCancellation => write!(f, "sentForCancellation"), + Self::ThreeDsAuthenticationFailed => write!(f, "3dsAuthenticationFailed"), + Self::SentForPartialRefund => write!(f, "sentForPartialRefund"), + Self::ThreeDsChallenged => write!(f, "3dsChallenged"), + Self::ThreeDsUnavailable => write!(f, "3dsUnavailable"), + } + } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -202,30 +251,33 @@ pub fn get_resource_id( where F: Fn(String) -> T, { - let reference_id = match response.other_fields { - WorldpayPaymentResponseFields::AuthorizedResponse(res) => res - .links - .as_ref() - .and_then(|link| link.self_link.href.rsplit_once('/')) - .map(|(_, h)| urlencoding::decode(h)) - .transpose() - .change_context(errors::ConnectorError::ResponseHandlingFailed)? - .map(|s| transform_fn(s.into_owned())), - WorldpayPaymentResponseFields::DDCResponse(res) => res - .actions - .supply_ddc_data - .href - .split('/') - .rev() - .nth(1) - .map(urlencoding::decode) - .transpose() - .change_context(errors::ConnectorError::ResponseHandlingFailed)? - .map(|s| transform_fn(s.into_owned())), - WorldpayPaymentResponseFields::FraudHighRisk(_) => None, - WorldpayPaymentResponseFields::RefusedResponse(_) => None, - }; - reference_id + let optional_reference_id = response + .other_fields + .as_ref() + .and_then(|other_fields| match other_fields { + WorldpayPaymentResponseFields::AuthorizedResponse(res) => res + .links + .as_ref() + .and_then(|link| link.self_link.href.rsplit_once('/').map(|(_, h)| h)), + WorldpayPaymentResponseFields::DDCResponse(res) => { + res.actions.supply_ddc_data.href.split('/').nth_back(1) + } + WorldpayPaymentResponseFields::ThreeDsChallenged(res) => res + .actions + .complete_three_ds_challenge + .href + .split('/') + .nth_back(1), + WorldpayPaymentResponseFields::FraudHighRisk(_) + | WorldpayPaymentResponseFields::RefusedResponse(_) => None, + }) + .map(|href| { + urlencoding::decode(href) + .map(|s| transform_fn(s.into_owned())) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + }) + .transpose()?; + optional_reference_id .or_else(|| connector_transaction_id.map(transform_fn)) .ok_or_else(|| { errors::ConnectorError::MissingRequiredField { diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index a0f2bfd2508b..a28d3bff7ed4 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -1,17 +1,20 @@ +use std::collections::HashMap; + use api_models::payments::Address; use base64::Engine; use common_utils::{errors::CustomResult, ext_traits::OptionExt, pii, types::MinorUnit}; use diesel_models::enums; use error_stack::ResultExt; -use hyperswitch_connectors::utils::RouterData; +use hyperswitch_connectors::utils::{PaymentsAuthorizeRequestData, RouterData}; use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use super::{requests::*, response::*}; use crate::{ - connector::utils, + connector::utils::{self, AddressData}, consts, core::errors, + services, types::{ self, domain, transformers::ForeignTryFrom, PaymentsAuthorizeData, PaymentsResponseData, }, @@ -65,49 +68,40 @@ impl TryFrom<&Option> for WorldpayConnectorMetadataObject fn fetch_payment_instrument( payment_method: domain::PaymentMethodData, billing_address: Option<&Address>, - auth_type: enums::AuthenticationType, ) -> CustomResult { match payment_method { - domain::PaymentMethodData::Card(card) => { - if auth_type == enums::AuthenticationType::ThreeDs { - return Err(errors::ConnectorError::NotImplemented( - "ThreeDS flow through worldpay".to_string(), - ) - .into()); - } - Ok(PaymentInstrument::Card(CardPayment { - payment_type: PaymentType::Plain, - expiry_date: ExpiryDate { - month: utils::CardData::get_expiry_month_as_i8(&card)?, - year: utils::CardData::get_expiry_year_as_i32(&card)?, - }, - card_number: card.card_number, - cvc: card.card_cvc, - card_holder_name: card.nick_name, - billing_address: if let Some(address) = - billing_address.and_then(|addr| addr.address.clone()) - { - Some(BillingAddress { - address1: address.line1, - address2: address.line2, - address3: address.line3, - city: address.city, - state: address.state, - postal_code: address.zip.get_required_value("zip").change_context( - errors::ConnectorError::MissingRequiredField { field_name: "zip" }, - )?, - country_code: address - .country - .get_required_value("country_code") - .change_context(errors::ConnectorError::MissingRequiredField { - field_name: "country_code", - })?, - }) - } else { - None - }, - })) - } + domain::PaymentMethodData::Card(card) => Ok(PaymentInstrument::Card(CardPayment { + payment_type: PaymentType::Plain, + expiry_date: ExpiryDate { + month: utils::CardData::get_expiry_month_as_i8(&card)?, + year: utils::CardData::get_expiry_year_as_i32(&card)?, + }, + card_number: card.card_number, + cvc: card.card_cvc, + card_holder_name: billing_address.and_then(|address| address.get_optional_full_name()), + billing_address: if let Some(address) = + billing_address.and_then(|addr| addr.address.clone()) + { + Some(BillingAddress { + address1: address.line1, + address2: address.line2, + address3: address.line3, + city: address.city, + state: address.state, + postal_code: address.zip.get_required_value("zip").change_context( + errors::ConnectorError::MissingRequiredField { field_name: "zip" }, + )?, + country_code: address + .country + .get_required_value("country_code") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "country_code", + })?, + }) + } else { + None + }, + })), domain::PaymentMethodData::Wallet(wallet) => match wallet { domain::WalletData::GooglePay(data) => { Ok(PaymentInstrument::Googlepay(WalletPayment { @@ -230,6 +224,53 @@ impl config: "metadata.merchant_name", }, )?; + let three_ds = match item.router_data.auth_type { + enums::AuthenticationType::ThreeDs => { + let browser_info = item + .router_data + .request + .browser_info + .clone() + .get_required_value("browser_info") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "browser_info", + })?; + let accept_header = browser_info + .accept_header + .get_required_value("accept_header") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "accept_header", + })?; + let user_agent_header = browser_info + .user_agent + .get_required_value("user_agent") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "user_agent", + })?; + Some(ThreeDSRequest { + three_ds_type: "integrated".to_string(), + mode: "always".to_string(), + device_data: ThreeDSRequestDeviceData { + accept_header, + user_agent_header, + browser_language: browser_info.language.clone(), + browser_screen_width: browser_info.screen_width, + browser_screen_height: browser_info.screen_height, + browser_color_depth: browser_info + .color_depth + .map(|depth| depth.to_string()), + time_zone: browser_info.time_zone.map(|tz| tz.to_string()), + browser_java_enabled: browser_info.java_enabled, + browser_javascript_enabled: browser_info.java_script_enabled, + channel: Some(ThreeDSRequestChannel::Browser), + }, + challenge: ThreeDSRequestChallenge { + return_url: item.router_data.request.get_complete_authorize_url()?, + }, + }) + } + _ => None, + }; Ok(Self { instruction: Instruction { settlement: item @@ -252,7 +293,6 @@ impl payment_instrument: fetch_payment_instrument( item.router_data.request.payment_method_data.clone(), item.router_data.get_optional_billing(), - item.router_data.auth_type, )?, narrative: InstructionNarrative { line1: merchant_name.expose(), @@ -262,6 +302,7 @@ impl currency: item.router_data.request.currency, }, debt_repayment: None, + three_ds, }, merchant: Merchant { entity: entity_id.clone(), @@ -321,6 +362,7 @@ impl From for enums::AttemptStatus { Self::AutoRefunded } PaymentOutcome::Refused | PaymentOutcome::FraudHighRisk => Self::Failure, + PaymentOutcome::ThreeDsUnavailable => Self::AuthenticationFailed, } } } @@ -363,42 +405,105 @@ impl From for enums::RefundStatus { } } -impl +impl ForeignTryFrom<( - types::PaymentsResponseRouterData, + types::ResponseRouterData, Option, - )> for types::PaymentsAuthorizeRouterData + )> for types::RouterData { type Error = error_stack::Report; fn foreign_try_from( item: ( - types::PaymentsResponseRouterData, + types::ResponseRouterData, Option, ), ) -> Result { let (router_data, optional_correlation_id) = item; - let description = match router_data.response.other_fields { - WorldpayPaymentResponseFields::AuthorizedResponse(ref res) => res.description.clone(), - WorldpayPaymentResponseFields::DDCResponse(_) - | WorldpayPaymentResponseFields::FraudHighRisk(_) - | WorldpayPaymentResponseFields::RefusedResponse(_) => None, + let (description, redirection_data) = router_data + .response + .other_fields + .as_ref() + .map(|other_fields| match other_fields { + WorldpayPaymentResponseFields::AuthorizedResponse(res) => { + (res.description.clone(), None) + } + WorldpayPaymentResponseFields::DDCResponse(res) => ( + None, + Some(services::RedirectForm::WorldpayDDCForm { + endpoint: res.device_data_collection.url.clone(), + method: common_utils::request::Method::Post, + collection_id: Some("SessionId".to_string()), + form_fields: HashMap::from([ + ( + "Bin".to_string(), + res.device_data_collection.bin.clone().expose(), + ), + ( + "JWT".to_string(), + res.device_data_collection.jwt.clone().expose(), + ), + ]), + }), + ), + WorldpayPaymentResponseFields::ThreeDsChallenged(res) => ( + None, + Some(services::RedirectForm::Form { + endpoint: res.challenge.url.to_string(), + method: common_utils::request::Method::Post, + form_fields: HashMap::from([( + "JWT".to_string(), + res.challenge.jwt.clone().expose(), + )]), + }), + ), + WorldpayPaymentResponseFields::FraudHighRisk(_) + | WorldpayPaymentResponseFields::RefusedResponse(_) => (None, None), + }) + .unwrap_or((None, None)); + let worldpay_status = router_data.response.outcome.clone(); + let optional_reason = match worldpay_status { + PaymentOutcome::ThreeDsAuthenticationFailed => { + Some("3DS authentication failed from issuer".to_string()) + } + PaymentOutcome::ThreeDsUnavailable => { + Some("3DS authentication unavailable from issuer".to_string()) + } + PaymentOutcome::FraudHighRisk => { + Some("Transaction marked as high risk by Worldpay".to_string()) + } + PaymentOutcome::Refused => Some("Transaction refused by issuer".to_string()), + _ => None, }; - Ok(Self { - status: enums::AttemptStatus::from(router_data.response.outcome.clone()), - description, - response: Ok(PaymentsResponseData::TransactionResponse { + let status = enums::AttemptStatus::from(worldpay_status.clone()); + let response = optional_reason.map_or( + Ok(PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::foreign_try_from(( router_data.response, optional_correlation_id.clone(), ))?, - redirection_data: None, + redirection_data, mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: optional_correlation_id, + connector_response_reference_id: optional_correlation_id.clone(), incremental_authorization_allowed: None, charge_id: None, }), + |reason| { + Err(types::ErrorResponse { + code: worldpay_status.to_string(), + message: reason.clone(), + reason: Some(reason), + status_code: router_data.http_code, + attempt_status: Some(status), + connector_transaction_id: optional_correlation_id, + }) + }, + ); + Ok(Self { + status, + description, + response, ..router_data.data }) } @@ -459,3 +564,17 @@ impl ForeignTryFrom<(WorldpayPaymentsResponse, Option)> for types::Respo get_resource_id(item.0, item.1, Self::ConnectorTransactionId) } } + +impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for WorldpayCompleteAuthorizationRequest { + type Error = error_stack::Report; + fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result { + let params = item + .request + .redirect_response + .as_ref() + .and_then(|redirect_response| redirect_response.params.as_ref()) + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; + serde_urlencoded::from_str::(params.peek()) + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + } +} diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index eae41c91e4e7..d113fca70897 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -241,7 +241,6 @@ default_imp_for_complete_authorize!( connector::Wise, connector::Wellsfargo, connector::Wellsfargopayout, - connector::Worldpay, connector::Zen, connector::Zsl ); @@ -476,7 +475,6 @@ default_imp_for_connector_redirect_response!( connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise, - connector::Worldpay, connector::Zsl ); diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 579a4ac4ffd0..b8cb9385197e 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1809,6 +1809,127 @@ pub fn build_redirection_form( } } + RedirectForm::WorldpayDDCForm { + endpoint, + method, + form_fields, + collection_id, + } => maud::html! { + (maud::DOCTYPE) + html { + meta name="viewport" content="width=device-width, initial-scale=1"; + head { + (PreEscaped(r##" + + "##)) + } + + body style="background-color: #ffffff; padding: 20px; font-family: Arial, Helvetica, Sans-Serif;" { + div id="loader1" class="lottie" style="height: 150px; display: block; position: relative; margin-left: auto; margin-right: auto;" { "" } + (PreEscaped(r#""#)) + (PreEscaped(r#" + + "#)) + h3 style="text-align: center;" { "Please wait while we process your payment..." } + + script { + (PreEscaped(format!( + r#" + function submitCollectionReference(collectionReference) {{ + var redirectUrl = window.location.origin + window.location.pathname.replace(/payments\/redirect\/(\w+)\/(\w+)\/\w+/, "payments/$1/$2/redirect/complete/worldpay"); + try {{ + if (typeof collectionReference === "string" && collectionReference.length > 0) {{ + var url = new URL(redirectUrl); + url.searchParams.append("collectionReference", collectionReference); + window.location.replace(url.toString()); + }} else {{ + window.location.replace(redirectUrl); + }} + }} catch (error) {{ + window.location.replace(redirectUrl); + }} + }} + var allowedHost = "{}"; + var collectionField = "{}"; + window.addEventListener("message", function(event) {{ + if (event.origin === allowedHost) {{ + try {{ + var data = JSON.parse(event.data); + if (collectionField.length > 0) {{ + var collectionReference = data[collectionField]; + return submitCollectionReference(collectionReference); + }} else {{ + console.error("Collection field not found in event data (" + collectionField + ")"); + }} + }} catch (error) {{ + console.error("Error parsing event data: ", error); + }} + }} else {{ + console.error("Invalid origin: " + event.origin, "Expected origin: " + allowedHost); + }} + + submitCollectionReference(""); + }}); + + // Redirect within 8 seconds if no collection reference is received + window.setTimeout(submitCollectionReference, 8000); + "#, + endpoint.host_str().map_or(endpoint.as_ref().split('/').take(3).collect::>().join("/"), |host| format!("{}://{}", endpoint.scheme(), host)), + collection_id.clone().unwrap_or("".to_string()))) + ) + } + + iframe + style="display: none;" + srcdoc=( + maud::html! { + (maud::DOCTYPE) + html { + body { + form action=(PreEscaped(endpoint.to_string())) method=(method.to_string()) #payment_form { + @for (field, value) in form_fields { + input type="hidden" name=(field) value=(value); + } + } + (PreEscaped(format!(r#" + + "#))) + } + } + }.into_string() + ) + {} + } + } + }, } } From ca86c379a60cee8dc4434c106913aafad0f860ba Mon Sep 17 00:00:00 2001 From: Kashif Date: Mon, 21 Oct 2024 12:18:48 +0530 Subject: [PATCH 28/28] refactor(connector): update payments response type for worldpay --- .../router/src/connector/worldpay/response.rs | 20 ++----------------- crates/router/src/services/api.rs | 16 +++++++++++---- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/crates/router/src/connector/worldpay/response.rs b/crates/router/src/connector/worldpay/response.rs index 5e436b19ed07..edc3c26948fa 100644 --- a/crates/router/src/connector/worldpay/response.rs +++ b/crates/router/src/connector/worldpay/response.rs @@ -308,8 +308,8 @@ impl Issuer { #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PaymentsResPaymentInstrument { - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub payment_instrument_type: Option, + #[serde(rename = "type")] + pub payment_instrument_type: String, pub card_bin: Option, pub last_four: Option, pub expiry_date: Option, @@ -320,22 +320,6 @@ pub struct PaymentsResPaymentInstrument { pub payment_account_reference: Option, } -impl PaymentsResPaymentInstrument { - pub fn new() -> Self { - Self { - payment_instrument_type: None, - card_bin: None, - last_four: None, - category: None, - expiry_date: None, - card_brand: None, - funding_type: None, - issuer_name: None, - payment_account_reference: None, - } - } -} - #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RiskFactorsInner { diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index b8cb9385197e..0d84ba9c30f4 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1854,12 +1854,20 @@ pub fn build_redirection_form( (PreEscaped(format!( r#" function submitCollectionReference(collectionReference) {{ - var redirectUrl = window.location.origin + window.location.pathname.replace(/payments\/redirect\/(\w+)\/(\w+)\/\w+/, "payments/$1/$2/redirect/complete/worldpay"); + var redirectPathname = window.location.pathname.replace(/payments\/redirect\/(\w+)\/(\w+)\/\w+/, "payments/$1/$2/redirect/complete/worldpay"); + var redirectUrl = window.location.origin + redirectPathname; try {{ if (typeof collectionReference === "string" && collectionReference.length > 0) {{ - var url = new URL(redirectUrl); - url.searchParams.append("collectionReference", collectionReference); - window.location.replace(url.toString()); + var form = document.createElement("form"); + form.action = redirectPathname; + form.method = "GET"; + var input = document.createElement("input"); + input.type = "hidden"; + input.name = "collectionReference"; + input.value = collectionReference; + form.appendChild(input); + document.body.appendChild(form); + form.submit();; }} else {{ window.location.replace(redirectUrl); }}