refactor(connector): [WorldPay] migrate from modular to standard payment APIs (#6317)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Kashif
2024-10-21 15:29:44 +05:30
committed by GitHub
parent 0dba763c3a
commit 58296ffae6
12 changed files with 531 additions and 268 deletions

1
Cargo.lock generated
View File

@ -6438,6 +6438,7 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
"unidecode", "unidecode",
"url", "url",
"urlencoding",
"utoipa", "utoipa",
"uuid", "uuid",
"validator", "validator",

View File

@ -3416,6 +3416,12 @@ key1="Password"
api_secret="Merchant Identifier" api_secret="Merchant Identifier"
[worldpay.connector_webhook_details] [worldpay.connector_webhook_details]
merchant_secret="Source verification key" 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]] [[worldpay.metadata.apple_pay]]
name="certificate" name="certificate"

View File

@ -2473,6 +2473,12 @@ merchant_secret="Source verification key"
api_key="Username" api_key="Username"
key1="Password" key1="Password"
api_secret="Merchant Identifier" 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]] [[worldpay.metadata.apple_pay]]
name="certificate" name="certificate"

View File

@ -3406,6 +3406,12 @@ key1="Password"
api_secret="Merchant Identifier" api_secret="Merchant Identifier"
[worldpay.connector_webhook_details] [worldpay.connector_webhook_details]
merchant_secret="Source verification key" 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]] [[worldpay.metadata.apple_pay]]
name="certificate" name="certificate"

View File

@ -115,6 +115,7 @@ tracing-futures = { version = "0.2.5", features = ["tokio"] }
unicode-segmentation = "1.11.0" unicode-segmentation = "1.11.0"
unidecode = "0.3.0" unidecode = "0.3.0"
url = { version = "2.5.0", features = ["serde"] } url = { version = "2.5.0", features = ["serde"] }
urlencoding = "2.1.3"
utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order", "time"] } utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order", "time"] }
uuid = { version = "1.8.0", features = ["v4"] } uuid = { version = "1.8.0", features = ["v4"] }
validator = "0.17.0" validator = "0.17.0"

View File

@ -725,6 +725,7 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData {
pub trait PaymentsCaptureRequestData { pub trait PaymentsCaptureRequestData {
fn is_multiple_capture(&self) -> bool; fn is_multiple_capture(&self) -> bool;
fn get_browser_info(&self) -> Result<BrowserInformation, Error>; fn get_browser_info(&self) -> Result<BrowserInformation, Error>;
fn get_capture_method(&self) -> Option<enums::CaptureMethod>;
} }
impl PaymentsCaptureRequestData for types::PaymentsCaptureData { impl PaymentsCaptureRequestData for types::PaymentsCaptureData {
@ -736,6 +737,9 @@ impl PaymentsCaptureRequestData for types::PaymentsCaptureData {
.clone() .clone()
.ok_or_else(missing_field_err("browser_info")) .ok_or_else(missing_field_err("browser_info"))
} }
fn get_capture_method(&self) -> Option<enums::CaptureMethod> {
self.capture_method.to_owned()
}
} }
pub trait RevokeMandateRequestData { pub trait RevokeMandateRequestData {

View File

@ -16,6 +16,7 @@ use self::{requests::*, response::*};
use super::utils::{self as connector_utils, RefundsRequestData}; use super::utils::{self as connector_utils, RefundsRequestData};
use crate::{ use crate::{
configs::settings, configs::settings,
consts,
core::errors::{self, CustomResult}, core::errors::{self, CustomResult},
events::connector_api_logs::ConnectorEvent, events::connector_api_logs::ConnectorEvent,
headers, headers,
@ -64,6 +65,7 @@ where
headers::CONTENT_TYPE.to_string(), headers::CONTENT_TYPE.to_string(),
self.get_content_type().to_string().into(), 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)?; let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
headers.append(&mut api_key); headers.append(&mut api_key);
@ -81,7 +83,7 @@ impl ConnectorCommon for Worldpay {
} }
fn common_get_content_type(&self) -> &'static str { 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 { fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
@ -205,8 +207,9 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
) -> CustomResult<String, errors::ConnectorError> { ) -> CustomResult<String, errors::ConnectorError> {
let connector_payment_id = req.request.connector_transaction_id.clone(); let connector_payment_id = req.request.connector_transaction_id.clone();
Ok(format!( Ok(format!(
"{}payments/authorizations/cancellations/{connector_payment_id}", "{}api/payments/{}/cancellations",
self.base_url(connectors), self.base_url(connectors),
urlencoding::encode(&connector_payment_id),
)) ))
} }
@ -244,15 +247,24 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?; .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response)); event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response); router_env::logger::info!(connector_response=?response);
let optional_correlation_id = res.headers.and_then(|headers| {
headers
.get(consts::WP_CORRELATION_ID)
.and_then(|header_value| header_value.to_str().ok())
.map(|id| id.to_string())
});
Ok(types::PaymentsCancelRouterData { Ok(types::PaymentsCancelRouterData {
status: enums::AttemptStatus::Voided, status: enums::AttemptStatus::from(response.outcome.clone()),
response: Ok(types::PaymentsResponseData::TransactionResponse { response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::foreign_try_from(response.links)?, resource_id: types::ResponseId::foreign_try_from((
response,
Some(data.request.connector_transaction_id.clone()),
))?,
redirection_data: None, redirection_data: None,
mandate_reference: None, mandate_reference: None,
connector_metadata: None, connector_metadata: None,
network_txn_id: None, network_txn_id: None,
connector_response_reference_id: None, connector_response_reference_id: optional_correlation_id,
incremental_authorization_allowed: None, incremental_authorization_allowed: None,
charge_id: None, charge_id: None,
}), }),
@ -306,9 +318,9 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
.get_connector_transaction_id() .get_connector_transaction_id()
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?; .change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
Ok(format!( Ok(format!(
"{}payments/events/{}", "{}api/payments/{}",
self.base_url(connectors), self.base_url(connectors),
connector_payment_id urlencoding::encode(&connector_payment_id),
)) ))
} }
@ -349,6 +361,12 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
event_builder.map(|i| i.set_response_body(&response)); event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response); router_env::logger::info!(connector_response=?response);
let optional_correlation_id = res.headers.and_then(|headers| {
headers
.get(consts::WP_CORRELATION_ID)
.and_then(|header_value| header_value.to_str().ok())
.map(|id| id.to_string())
});
let attempt_status = data.status; let attempt_status = data.status;
let worldpay_status = response.last_event; let worldpay_status = response.last_event;
let status = match (attempt_status, worldpay_status.clone()) { let status = match (attempt_status, worldpay_status.clone()) {
@ -371,7 +389,7 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
mandate_reference: None, mandate_reference: None,
connector_metadata: None, connector_metadata: None,
network_txn_id: None, network_txn_id: None,
connector_response_reference_id: None, connector_response_reference_id: optional_correlation_id,
incremental_authorization_allowed: None, incremental_authorization_allowed: None,
charge_id: None, charge_id: None,
}), }),
@ -403,9 +421,9 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
) -> CustomResult<String, errors::ConnectorError> { ) -> CustomResult<String, errors::ConnectorError> {
let connector_payment_id = req.request.connector_transaction_id.clone(); let connector_payment_id = req.request.connector_transaction_id.clone();
Ok(format!( Ok(format!(
"{}payments/settlements/partials/{}", "{}api/payments/{}/partialSettlements",
self.base_url(connectors), self.base_url(connectors),
connector_payment_id urlencoding::encode(&connector_payment_id),
)) ))
} }
@ -457,15 +475,24 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?; .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response)); event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response); router_env::logger::info!(connector_response=?response);
let optional_correlation_id = res.headers.and_then(|headers| {
headers
.get(consts::WP_CORRELATION_ID)
.and_then(|header_value| header_value.to_str().ok())
.map(|id| id.to_string())
});
Ok(types::PaymentsCaptureRouterData { Ok(types::PaymentsCaptureRouterData {
status: enums::AttemptStatus::Pending, status: enums::AttemptStatus::Pending,
response: Ok(types::PaymentsResponseData::TransactionResponse { response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::foreign_try_from(response.links)?, resource_id: types::ResponseId::foreign_try_from((
response,
Some(data.request.connector_transaction_id.clone()),
))?,
redirection_data: None, redirection_data: None,
mandate_reference: None, mandate_reference: None,
connector_metadata: None, connector_metadata: None,
network_txn_id: None, network_txn_id: None,
connector_response_reference_id: None, connector_response_reference_id: optional_correlation_id,
incremental_authorization_allowed: None, incremental_authorization_allowed: None,
charge_id: None, charge_id: None,
}), }),
@ -514,10 +541,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
_req: &types::PaymentsAuthorizeRouterData, _req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> { ) -> CustomResult<String, errors::ConnectorError> {
Ok(format!( Ok(format!("{}api/payments", self.base_url(connectors)))
"{}cardPayments/customerInitiatedTransactions",
self.base_url(connectors)
))
} }
fn get_request_body( fn get_request_body(
@ -573,12 +597,21 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
event_builder.map(|i| i.set_response_body(&response)); event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response); router_env::logger::info!(connector_response=?response);
let optional_correlation_id = res.headers.and_then(|headers| {
headers
.get(consts::WP_CORRELATION_ID)
.and_then(|header_value| header_value.to_str().ok())
.map(|id| id.to_string())
});
types::RouterData::try_from(types::ResponseRouterData { types::RouterData::foreign_try_from((
response, types::ResponseRouterData {
data: data.clone(), response,
http_code: res.status_code, data: data.clone(),
}) http_code: res.status_code,
},
optional_correlation_id,
))
.change_context(errors::ConnectorError::ResponseHandlingFailed) .change_context(errors::ConnectorError::ResponseHandlingFailed)
} }
@ -631,9 +664,9 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
) -> CustomResult<String, errors::ConnectorError> { ) -> CustomResult<String, errors::ConnectorError> {
let connector_payment_id = req.request.connector_transaction_id.clone(); let connector_payment_id = req.request.connector_transaction_id.clone();
Ok(format!( Ok(format!(
"{}payments/settlements/refunds/partials/{}", "{}api/payments/{}/partialRefunds",
self.base_url(connectors), self.base_url(connectors),
connector_payment_id urlencoding::encode(&connector_payment_id),
)) ))
} }
@ -670,9 +703,19 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?; .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response)); event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response); router_env::logger::info!(connector_response=?response);
let optional_correlation_id = res.headers.and_then(|headers| {
headers
.get(consts::WP_CORRELATION_ID)
.and_then(|header_value| header_value.to_str().ok())
.map(|id| id.to_string())
});
Ok(types::RefundExecuteRouterData { Ok(types::RefundExecuteRouterData {
response: Ok(types::RefundsResponseData { response: Ok(types::RefundsResponseData {
connector_refund_id: ResponseIdStr::try_from(response.links)?.id, connector_refund_id: ResponseIdStr::foreign_try_from((
response,
optional_correlation_id,
))?
.id,
refund_status: enums::RefundStatus::Pending, refund_status: enums::RefundStatus::Pending,
}), }),
..data.clone() ..data.clone()
@ -710,9 +753,9 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> { ) -> CustomResult<String, errors::ConnectorError> {
Ok(format!( Ok(format!(
"{}payments/events/{}", "{}api/payments/{}",
self.base_url(connectors), self.base_url(connectors),
req.request.get_connector_refund_id()? urlencoding::encode(&req.request.get_connector_refund_id()?),
)) ))
} }
@ -813,7 +856,7 @@ impl api::IncomingWebhook for Worldpay {
.parse_struct("WorldpayWebhookTransactionId") .parse_struct("WorldpayWebhookTransactionId")
.change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
Ok(api_models::webhooks::ObjectReferenceId::PaymentId( Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
api::PaymentIdType::ConnectorTransactionId(body.event_details.transaction_reference), api::PaymentIdType::PaymentAttemptId(body.event_details.transaction_reference),
)) ))
} }
@ -829,13 +872,14 @@ impl api::IncomingWebhook for Worldpay {
EventType::Authorized => { EventType::Authorized => {
Ok(api::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess) Ok(api::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess)
} }
EventType::SentForSettlement => Ok(api::IncomingWebhookEvent::PaymentIntentProcessing),
EventType::Settled => Ok(api::IncomingWebhookEvent::PaymentIntentSuccess), EventType::Settled => Ok(api::IncomingWebhookEvent::PaymentIntentSuccess),
EventType::SentForSettlement | EventType::SentForAuthorization => {
Ok(api::IncomingWebhookEvent::PaymentIntentProcessing)
}
EventType::Error | EventType::Expired | EventType::SettlementFailed => { EventType::Error | EventType::Expired | EventType::SettlementFailed => {
Ok(api::IncomingWebhookEvent::PaymentIntentFailure) Ok(api::IncomingWebhookEvent::PaymentIntentFailure)
} }
EventType::Unknown EventType::Unknown
| EventType::SentForAuthorization
| EventType::Cancelled | EventType::Cancelled
| EventType::Refused | EventType::Refused
| EventType::Refunded | EventType::Refunded

View File

@ -1,5 +1,99 @@
use masking::Secret; use masking::Secret;
use serde::{Deserialize, Serialize}; 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<Customer>,
}
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Merchant {
pub entity: Secret<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mcc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_facilitator: Option<PaymentFacilitator>,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Instruction {
pub settlement: Option<AutoSettlement>,
pub method: PaymentMethod,
pub payment_instrument: PaymentInstrument,
pub narrative: InstructionNarrative,
pub value: PaymentValue,
#[serde(skip_serializing_if = "Option::is_none")]
pub debt_repayment: Option<bool>,
}
#[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<Secret<String>>,
pub card_number: cards::CardNumber,
pub expiry_date: ExpiryDate,
#[serde(skip_serializing_if = "Option::is_none")]
pub billing_address: Option<BillingAddress>,
pub cvc: Secret<String>,
}
#[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<Secret<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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub billing_address: Option<BillingAddress>,
}
#[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<i8>,
pub year: Secret<i32>,
}
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct BillingAddress { pub struct BillingAddress {
@ -17,17 +111,6 @@ pub struct BillingAddress {
pub country_code: common_enums::CountryAlpha2, 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<Customer>,
}
#[derive( #[derive(
Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
)] )]
@ -100,89 +183,23 @@ pub struct NetworkToken {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Instruction { pub struct AutoSettlement {
pub request_auto_settlement: RequestAutoSettlement, pub auto: bool,
pub narrative: InstructionNarrative,
pub value: PaymentValue,
pub payment_instrument: PaymentInstrument,
#[serde(skip_serializing_if = "Option::is_none")]
pub debt_repayment: Option<bool>,
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "lowercase")]
pub struct RequestAutoSettlement { pub enum PaymentMethod {
pub enabled: bool, #[default]
Card,
ApplePay,
GooglePay,
} }
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct InstructionNarrative { pub struct InstructionNarrative {
pub line1: String, pub line1: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub line2: Option<String>,
}
#[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 {
#[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<Secret<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub billing_address: Option<BillingAddress>,
pub cvc: Secret<String>,
}
#[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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub billing_address: Option<BillingAddress>,
}
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct ExpiryDate {
pub month: Secret<i8>,
pub year: Secret<i32>,
} }
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
@ -191,16 +208,6 @@ pub struct PaymentValue {
pub currency: api_models::enums::Currency, pub currency: api_models::enums::Currency,
} }
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Merchant {
pub entity: Secret<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mcc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_facilitator: Option<PaymentFacilitator>,
}
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PaymentFacilitator { pub struct PaymentFacilitator {

View File

@ -1,25 +1,97 @@
use error_stack::ResultExt;
use masking::Secret; use masking::Secret;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::requests::*; use super::requests::*;
use crate::{core::errors, types, types::transformers::ForeignTryFrom}; use crate::core::errors;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct WorldpayPaymentsResponse { pub struct WorldpayPaymentsResponse {
pub outcome: Option<PaymentOutcome>, pub outcome: PaymentOutcome,
/// Any risk factors which have been identified for the authorization. This section will not appear if no risks are identified. pub transaction_reference: Option<String>,
#[serde(flatten)]
pub other_fields: WorldpayPaymentResponseFields,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum WorldpayPaymentResponseFields {
AuthorizedResponse(Box<AuthorizedResponse>),
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")] #[serde(skip_serializing_if = "Option::is_none")]
pub risk_factors: Option<Vec<RiskFactorsInner>>, pub payment_instrument: Option<PaymentsResPaymentInstrument>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub issuer: Option<Issuer>, pub issuer: Option<Issuer>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub scheme: Option<PaymentsResponseScheme>, pub scheme: Option<PaymentsResponseScheme>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_instrument: Option<PaymentsResPaymentInstrument>,
#[serde(rename = "_links", skip_serializing_if = "Option::is_none")] #[serde(rename = "_links", skip_serializing_if = "Option::is_none")]
pub links: Option<PaymentLinks>, pub links: Option<SelfLink>,
#[serde(rename = "_actions")]
pub actions: Option<ActionLinks>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>, pub description: Option<String>,
pub risk_factors: Option<Vec<RiskFactorsInner>>,
pub fraud: Option<Fraud>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FraudHighRiskResponse {
pub score: f32,
pub reason: Vec<String>,
}
#[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<RiskFactorsInner>,
pub fraud: Fraud,
#[serde(rename = "threeDS")]
pub three_ds: Option<ThreeDsResponse>,
}
#[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)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -28,16 +100,57 @@ pub enum PaymentOutcome {
#[serde(alias = "authorized", alias = "Authorized")] #[serde(alias = "authorized", alias = "Authorized")]
Authorized, Authorized,
Refused, Refused,
#[serde(alias = "Sent for Settlement")]
SentForSettlement, SentForSettlement,
#[serde(alias = "Sent for Refund")]
SentForRefund, SentForRefund,
FraudHighRisk,
#[serde(alias = "3dsDeviceDataRequired")]
ThreeDsDeviceDataRequired,
ThreeDsChallenged,
SentForCancellation,
#[serde(alias = "3dsAuthenticationFailed")]
ThreeDsAuthenticationFailed,
SentForPartialRefund,
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum RefundOutcome { pub struct SelfLink {
#[serde(alias = "Sent for Refund")] #[serde(rename = "self")]
SentForRefund, 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<ActionLink>,
settle_payment: Option<ActionLink>,
partially_settle_payment: Option<ActionLink>,
refund_payment: Option<ActionLink>,
partiall_refund_payment: Option<ActionLink>,
cancel_payment: Option<ActionLink>,
}
#[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)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -70,40 +183,6 @@ pub enum EventType {
Unknown, Unknown,
} }
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct PaymentLinks {
#[serde(
rename = "cardPayments:events",
skip_serializing_if = "Option::is_none"
)]
pub events: Option<PaymentLink>,
#[serde(
rename = "cardPayments:settle",
skip_serializing_if = "Option::is_none"
)]
pub settle_event: Option<PaymentLink>,
#[serde(
rename = "cardPayments:partialSettle",
skip_serializing_if = "Option::is_none"
)]
pub partial_settle_event: Option<PaymentLink>,
#[serde(
rename = "cardPayments:refund",
skip_serializing_if = "Option::is_none"
)]
pub refund_event: Option<PaymentLink>,
#[serde(
rename = "cardPayments:partialRefund",
skip_serializing_if = "Option::is_none"
)]
pub partial_refund_event: Option<PaymentLink>,
#[serde(
rename = "cardPayments:reverse",
skip_serializing_if = "Option::is_none"
)]
pub reverse_event: Option<PaymentLink>,
}
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct EventLinks { pub struct EventLinks {
#[serde(rename = "payments:events", skip_serializing_if = "Option::is_none")] #[serde(rename = "payments:events", skip_serializing_if = "Option::is_none")]
@ -115,43 +194,51 @@ pub struct PaymentLink {
pub href: String, pub href: String,
} }
fn get_resource_id<T, F>( pub fn get_resource_id<T, F>(
links: Option<PaymentLinks>, response: WorldpayPaymentsResponse,
connector_transaction_id: Option<String>,
transform_fn: F, transform_fn: F,
) -> Result<T, error_stack::Report<errors::ConnectorError>> ) -> Result<T, error_stack::Report<errors::ConnectorError>>
where where
F: Fn(String) -> T, F: Fn(String) -> T,
{ {
let reference_id = links let reference_id = match response.other_fields {
.and_then(|l| l.events) WorldpayPaymentResponseFields::AuthorizedResponse(res) => res
.and_then(|e| e.href.rsplit_once('/').map(|h| h.1.to_string())) .links
.map(transform_fn); .as_ref()
reference_id.ok_or_else(|| { .and_then(|link| link.self_link.href.rsplit_once('/'))
errors::ConnectorError::MissingRequiredField { .map(|(_, h)| urlencoding::decode(h))
field_name: "links.events", .transpose()
} .change_context(errors::ConnectorError::ResponseHandlingFailed)?
.into() .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 struct ResponseIdStr {
pub id: String, pub id: String,
} }
impl TryFrom<Option<PaymentLinks>> for ResponseIdStr {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(links: Option<PaymentLinks>) -> Result<Self, Self::Error> {
get_resource_id(links, |id| Self { id })
}
}
impl ForeignTryFrom<Option<PaymentLinks>> for types::ResponseId {
type Error = error_stack::Report<errors::ConnectorError>;
fn foreign_try_from(links: Option<PaymentLinks>) -> Result<Self, Self::Error> {
get_resource_id(links, Self::ConnectorTransactionId)
}
}
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Issuer { pub struct Issuer {
@ -173,10 +260,10 @@ pub struct PaymentsResPaymentInstrument {
pub payment_instrument_type: Option<String>, pub payment_instrument_type: Option<String>,
pub card_bin: Option<String>, pub card_bin: Option<String>,
pub last_four: Option<String>, pub last_four: Option<String>,
pub category: Option<String>,
pub expiry_date: Option<ExpiryDate>, pub expiry_date: Option<ExpiryDate>,
pub card_brand: Option<String>, pub card_brand: Option<String>,
pub funding_type: Option<String>, pub funding_type: Option<String>,
pub category: Option<String>,
pub issuer_name: Option<String>, pub issuer_name: Option<String>,
pub payment_account_reference: Option<String>, pub payment_account_reference: Option<String>,
} }
@ -231,7 +318,7 @@ pub enum RiskType {
#[derive( #[derive(
Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
)] )]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "lowercase")]
pub enum Detail { pub enum Detail {
#[default] #[default]
Address, Address,

View File

@ -1,11 +1,11 @@
use api_models::payments::Address; use api_models::payments::Address;
use base64::Engine; 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 diesel_models::enums;
use error_stack::ResultExt; use error_stack::ResultExt;
use hyperswitch_connectors::utils::RouterData; use hyperswitch_connectors::utils::RouterData;
use masking::{PeekInterface, Secret}; use masking::{ExposeInterface, PeekInterface, Secret};
use serde::Serialize; use serde::{Deserialize, Serialize};
use super::{requests::*, response::*}; use super::{requests::*, response::*};
use crate::{ use crate::{
@ -45,53 +45,79 @@ impl<T>
}) })
} }
} }
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct WorldpayConnectorMetadataObject {
pub merchant_name: Option<Secret<String>>,
}
impl TryFrom<&Option<pii::SecretSerdeValue>> for WorldpayConnectorMetadataObject {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(meta_data: &Option<pii::SecretSerdeValue>) -> Result<Self, Self::Error> {
let metadata: Self = utils::to_connector_meta_from_secret::<Self>(meta_data.clone())
.change_context(errors::ConnectorError::InvalidConnectorConfig {
config: "metadata",
})?;
Ok(metadata)
}
}
fn fetch_payment_instrument( fn fetch_payment_instrument(
payment_method: domain::PaymentMethodData, payment_method: domain::PaymentMethodData,
billing_address: Option<&Address>, billing_address: Option<&Address>,
auth_type: enums::AuthenticationType,
) -> CustomResult<PaymentInstrument, errors::ConnectorError> { ) -> CustomResult<PaymentInstrument, errors::ConnectorError> {
match payment_method { match payment_method {
domain::PaymentMethodData::Card(card) => Ok(PaymentInstrument::Card(CardPayment { domain::PaymentMethodData::Card(card) => {
payment_type: PaymentType::Card, if auth_type == enums::AuthenticationType::ThreeDs {
expiry_date: ExpiryDate { return Err(errors::ConnectorError::NotImplemented(
month: utils::CardData::get_expiry_month_as_i8(&card)?, "ThreeDS flow through worldpay".to_string(),
year: utils::CardData::get_expiry_year_as_i32(&card)?, )
}, .into());
card_number: card.card_number, }
cvc: card.card_cvc, Ok(PaymentInstrument::Card(CardPayment {
card_holder_name: card.nick_name, payment_type: PaymentType::Plain,
billing_address: if let Some(address) = expiry_date: ExpiryDate {
billing_address.and_then(|addr| addr.address.clone()) month: utils::CardData::get_expiry_month_as_i8(&card)?,
{ year: utils::CardData::get_expiry_year_as_i32(&card)?,
Some(BillingAddress { },
address1: address.line1, card_number: card.card_number,
address2: address.line2, cvc: card.card_cvc,
address3: address.line3, card_holder_name: card.nick_name,
city: address.city, billing_address: if let Some(address) =
state: address.state, billing_address.and_then(|addr| addr.address.clone())
postal_code: address.zip.get_required_value("zip").change_context( {
errors::ConnectorError::MissingRequiredField { field_name: "zip" }, Some(BillingAddress {
)?, address1: address.line1,
country_code: address address2: address.line2,
.country address3: address.line3,
.get_required_value("country_code") city: address.city,
.change_context(errors::ConnectorError::MissingRequiredField { state: address.state,
field_name: "country_code", postal_code: address.zip.get_required_value("zip").change_context(
})?, errors::ConnectorError::MissingRequiredField { field_name: "zip" },
}) )?,
} else { country_code: address
None .country
}, .get_required_value("country_code")
})), .change_context(errors::ConnectorError::MissingRequiredField {
field_name: "country_code",
})?,
})
} else {
None
},
}))
}
domain::PaymentMethodData::Wallet(wallet) => match wallet { domain::PaymentMethodData::Wallet(wallet) => match wallet {
domain::WalletData::GooglePay(data) => { domain::WalletData::GooglePay(data) => {
Ok(PaymentInstrument::Googlepay(WalletPayment { Ok(PaymentInstrument::Googlepay(WalletPayment {
payment_type: PaymentType::Googlepay, payment_type: PaymentType::Encrypted,
wallet_token: Secret::new(data.tokenization_data.token), wallet_token: Secret::new(data.tokenization_data.token),
..WalletPayment::default() ..WalletPayment::default()
})) }))
} }
domain::WalletData::ApplePay(data) => Ok(PaymentInstrument::Applepay(WalletPayment { domain::WalletData::ApplePay(data) => Ok(PaymentInstrument::Applepay(WalletPayment {
payment_type: PaymentType::Applepay, payment_type: PaymentType::Encrypted,
wallet_token: Secret::new(data.payment_data), wallet_token: Secret::new(data.payment_data),
..WalletPayment::default() ..WalletPayment::default()
})), })),
@ -149,6 +175,27 @@ fn fetch_payment_instrument(
} }
} }
impl TryFrom<(enums::PaymentMethod, enums::PaymentMethodType)> for PaymentMethod {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
src: (enums::PaymentMethod, enums::PaymentMethodType),
) -> Result<Self, Self::Error> {
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 impl
TryFrom<( TryFrom<(
&WorldpayRouterData< &WorldpayRouterData<
@ -176,28 +223,44 @@ impl
), ),
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
let (item, entity_id) = req; 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 { Ok(Self {
instruction: Instruction { instruction: Instruction {
request_auto_settlement: RequestAutoSettlement { settlement: item
enabled: item.router_data.request.capture_method .router_data
== Some(enums::CaptureMethod::Automatic), .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(),
item.router_data.auth_type,
)?,
narrative: InstructionNarrative {
line1: merchant_name.expose(),
}, },
value: PaymentValue { value: PaymentValue {
amount: item.amount, amount: item.amount,
currency: item.router_data.request.currency, currency: item.router_data.request.currency,
}, },
narrative: InstructionNarrative {
line1: item
.router_data
.merchant_id
.get_string_repr()
.replace('_', "-"),
..Default::default()
},
payment_instrument: fetch_payment_instrument(
item.router_data.request.payment_method_data.clone(),
item.router_data.get_optional_billing(),
)?,
debt_repayment: None, debt_repayment: None,
}, },
merchant: Merchant { merchant: Merchant {
@ -205,7 +268,6 @@ impl
..Default::default() ..Default::default()
}, },
transaction_reference: item.router_data.connector_request_reference_id.clone(), transaction_reference: item.router_data.connector_request_reference_id.clone(),
channel: Channel::Ecom,
customer: None, customer: None,
}) })
} }
@ -250,9 +312,15 @@ impl From<PaymentOutcome> for enums::AttemptStatus {
fn from(item: PaymentOutcome) -> Self { fn from(item: PaymentOutcome) -> Self {
match item { match item {
PaymentOutcome::Authorized => Self::Authorized, PaymentOutcome::Authorized => Self::Authorized,
PaymentOutcome::Refused => Self::Failure,
PaymentOutcome::SentForSettlement => Self::CaptureInitiated, 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,
} }
} }
} }
@ -295,32 +363,43 @@ impl From<EventType> for enums::RefundStatus {
} }
} }
impl TryFrom<types::PaymentsResponseRouterData<WorldpayPaymentsResponse>> impl
for types::PaymentsAuthorizeRouterData ForeignTryFrom<(
types::PaymentsResponseRouterData<WorldpayPaymentsResponse>,
Option<String>,
)> for types::PaymentsAuthorizeRouterData
{ {
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from( fn foreign_try_from(
item: types::PaymentsResponseRouterData<WorldpayPaymentsResponse>, item: (
types::PaymentsResponseRouterData<WorldpayPaymentsResponse>,
Option<String>,
),
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
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 { Ok(Self {
status: match item.response.outcome { status: enums::AttemptStatus::from(router_data.response.outcome.clone()),
Some(outcome) => enums::AttemptStatus::from(outcome), description,
None => Err(errors::ConnectorError::MissingRequiredField {
field_name: "outcome",
})?,
},
description: item.response.description,
response: Ok(PaymentsResponseData::TransactionResponse { 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, redirection_data: None,
mandate_reference: None, mandate_reference: None,
connector_metadata: None, connector_metadata: None,
network_txn_id: None, network_txn_id: None,
connector_response_reference_id: None, connector_response_reference_id: optional_correlation_id,
incremental_authorization_allowed: None, incremental_authorization_allowed: None,
charge_id: None, charge_id: None,
}), }),
..item.data ..router_data.data
}) })
} }
} }
@ -362,3 +441,21 @@ impl TryFrom<WorldpayWebhookEventType> for WorldpayEventResponse {
}) })
} }
} }
impl ForeignTryFrom<(WorldpayPaymentsResponse, Option<String>)> for ResponseIdStr {
type Error = error_stack::Report<errors::ConnectorError>;
fn foreign_try_from(
item: (WorldpayPaymentsResponse, Option<String>),
) -> Result<Self, Self::Error> {
get_resource_id(item.0, item.1, |id| Self { id })
}
}
impl ForeignTryFrom<(WorldpayPaymentsResponse, Option<String>)> for types::ResponseId {
type Error = error_stack::Report<errors::ConnectorError>;
fn foreign_try_from(
item: (WorldpayPaymentsResponse, Option<String>),
) -> Result<Self, Self::Error> {
get_resource_id(item.0, item.1, Self::ConnectorTransactionId)
}
}

View File

@ -177,3 +177,6 @@ pub const VAULT_DELETE_FLOW_TYPE: &str = "delete_from_vault";
/// Vault Fingerprint fetch flow type /// Vault Fingerprint fetch flow type
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
pub const VAULT_GET_FINGERPRINT_FLOW_TYPE: &str = "get_fingerprint_vault"; pub const VAULT_GET_FINGERPRINT_FLOW_TYPE: &str = "get_fingerprint_vault";
/// Worldpay's unique reference ID for a request TODO: Move to hyperswitch_connectors/constants once Worldpay is moved to connectors crate
pub const WP_CORRELATION_ID: &str = "WP-CorrelationId";

View File

@ -87,6 +87,7 @@ pub mod headers {
pub const X_APP_ID: &str = "x-app-id"; pub const X_APP_ID: &str = "x-app-id";
pub const X_REDIRECT_URI: &str = "x-redirect-uri"; pub const X_REDIRECT_URI: &str = "x-redirect-uri";
pub const X_TENANT_ID: &str = "x-tenant-id"; pub const X_TENANT_ID: &str = "x-tenant-id";
pub const X_WP_API_VERSION: &str = "WP-Api-Version";
} }
pub mod pii { pub mod pii {