Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(connector): add 3DS flow for Worldpay #6374

Merged
merged 36 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
88aaa4f
feat(worldpay): migrate to v7
kashif-m Sep 26, 2024
c5cb6a1
chore: run formatter
hyperswitch-bot[bot] Sep 26, 2024
bbcd0a5
refactor(db): increase connector_refund_id's length to 512
kashif-m Sep 26, 2024
d0c1637
refactor: re-generate schema_v2
kashif-m Sep 26, 2024
97561b7
refactor: resolve comments
kashif-m Sep 27, 2024
dbeff46
refactor: add pm_filters for worldpay and extend Currency enum
kashif-m Sep 27, 2024
5cd1f5e
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Sep 27, 2024
8acdb1b
refactor: remove redundant logs
kashif-m Sep 26, 2024
5aab9e9
feat: add domain type for connector_transaction_id for handling id.le…
kashif-m Oct 7, 2024
5268636
Merge remote-tracking branch 'origin/main' into worldpay-updates
kashif-m Oct 7, 2024
3984237
refactor: re-generate schema_v2
kashif-m Oct 7, 2024
33fb8cb
chore: run formatter
hyperswitch-bot[bot] Oct 7, 2024
bdeeb8d
refactor: v2 support
kashif-m Oct 8, 2024
9003400
refactor: rename drop v1 queries for ordering
kashif-m Oct 8, 2024
6c149d2
refactor(worldpay): form new status based on existing intent and atte…
kashif-m Oct 9, 2024
8cd8aa6
refactor: limit hashed connector's txn ID length to 50 characters and…
kashif-m Oct 9, 2024
6b678af
Merge remote-tracking branch 'origin/main' into worldpay-updates
kashif-m Oct 10, 2024
c7ecd6d
refactor(payment_attempt): restrict connector_transaction_data to die…
kashif-m Oct 14, 2024
8f81329
Merge remote-tracking branch 'origin/main' into worldpay-updates
kashif-m Oct 14, 2024
45e1c8c
refactor: fix clippy v2 suggestions
kashif-m Oct 14, 2024
954fea3
refactor: add missing down migration for v2 columns
kashif-m Oct 14, 2024
520e54e
fix: update cypress tests for compatibility with currency changes
kashif-m Oct 15, 2024
caeef6f
refactor(connector): [WorldPay] migrate from modular to standard paym…
kashif-m Oct 15, 2024
7c68125
chore: run formatter
hyperswitch-bot[bot] Oct 15, 2024
0d4318a
fix: update pm_filters for worldpay
kashif-m Oct 15, 2024
f9986e7
Merge remote-tracking branch 'origin/main' into worldpay-updates
kashif-m Oct 16, 2024
b0509b0
refactor: remove intent_status from ConnectorIntegration data for PSync
kashif-m Oct 16, 2024
f8a7c16
Merge remote-tracking branch 'origin/worldpay-updates' into worldpay-…
kashif-m Oct 16, 2024
2771e7d
Merge remote-tracking branch 'origin/main' into worldpay-wallets-api-…
kashif-m Oct 17, 2024
5fb6afc
fix(worldpay): update narrative for worldpay during authorize request
kashif-m Oct 17, 2024
e1fcfde
refactor(worldpay): updated transformers
kashif-m Oct 18, 2024
f43eff1
Merge remote-tracking branch 'origin/main' into worldpay-wallets-api-…
kashif-m Oct 18, 2024
a0f086c
fix: clippy fix
kashif-m Oct 18, 2024
956f068
feat(connector): add 3DS flow for Worldpay
kashif-m Oct 20, 2024
ca86c37
refactor(connector): update payments response type for worldpay
kashif-m Oct 21, 2024
5f2d078
Merge remote-tracking branch 'origin/main' into worldpay-3ds
kashif-m Oct 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion crates/diesel_models/src/query/payment_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,23 @@ impl PaymentAttempt {
merchant_id: &common_utils::id_type::MerchantId,
connector_txn_id: &str,
) -> StorageResult<Self> {
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::<<Self as HasTable>::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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ pub enum RedirectForm {
Mifinity {
initialization_token: String,
},
WorldpayDDCForm {
endpoint: url::Url,
method: Method,
form_fields: HashMap<String, String>,
collection_id: Option<String>,
},
}

impl From<(url::Url, Method)> for RedirectForm {
Expand Down
124 changes: 124 additions & 0 deletions crates/router/src/connector/worldpay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,113 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
}
}

impl api::PaymentsCompleteAuthorize for Worldpay {}
impl
ConnectorIntegration<
api::CompleteAuthorize,
types::CompleteAuthorizeData,
types::PaymentsResponseData,
> for Worldpay
{
fn get_headers(
&self,
req: &types::PaymentsCompleteAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, 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<String, errors::ConnectorError> {
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<RequestContent, errors::ConnectorError> {
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<Option<services::Request>, 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<types::PaymentsCompleteAuthorizeRouterData, errors::ConnectorError> {
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<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
}

impl api::Refund for Worldpay {}
impl api::RefundExecute for Worldpay {}
impl api::RefundSync for Worldpay {}
Expand Down Expand Up @@ -900,3 +1007,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<serde_json::Value>,
action: services::PaymentAction,
) -> CustomResult<enums::CallConnectorAction, errors::ConnectorError> {
match action {
services::PaymentAction::CompleteAuthorize => Ok(enums::CallConnectorAction::Trigger),
services::PaymentAction::PSync
| services::PaymentAction::PaymentAuthenticateCompleteAuthorize => {
Ok(enums::CallConnectorAction::Avoid)
}
}
}
}
47 changes: 47 additions & 0 deletions crates/router/src/connector/worldpay/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub struct Instruction {
pub value: PaymentValue,
#[serde(skip_serializing_if = "Option::is_none")]
pub debt_repayment: Option<bool>,
#[serde(rename = "threeDS")]
pub three_ds: Option<ThreeDSRequest>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -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<String>,
pub browser_screen_width: Option<u32>,
pub browser_screen_height: Option<u32>,
pub browser_color_depth: Option<String>,
pub time_zone: Option<String>,
pub browser_java_enabled: Option<bool>,
pub browser_javascript_enabled: Option<bool>,
pub channel: Option<ThreeDSRequestChannel>,
}

#[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 {
Expand Down Expand Up @@ -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<String>,
}
Loading
Loading