Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
233 changes: 226 additions & 7 deletions crates/hyperswitch_connectors/src/connectors/paypal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1053,33 +1053,117 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
res.response
.parse_struct("paypal PaypalAuthResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

// Extract amount and currency for integrity check
let (response_amount, response_currency) = match &response {
PaypalAuthResponse::PaypalOrdersResponse(resp) => {
let purchase_unit = resp.purchase_units.first().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "purchase_units[0]",
},
)?;
// For orders response, we need to get amount from the payments collection
// Try to get from authorizations first, then captures
let amount = if let Some(authorizations) = &purchase_unit.payments.authorizations {
if let Some(auth) = authorizations.first() {
&auth.amount
} else {
return Err(errors::ConnectorError::MissingRequiredField {
field_name: "authorizations[0]",
}
.into());
}
} else if let Some(captures) = &purchase_unit.payments.captures {
if let Some(capture) = captures.first() {
&capture.amount
} else {
return Err(errors::ConnectorError::MissingRequiredField {
field_name: "captures[0]",
}
.into());
}
} else {
return Err(errors::ConnectorError::MissingRequiredField {
field_name: "payments.authorizations or payments.captures",
}
.into());
};
(amount.value.clone(), amount.currency_code.to_string())
}
PaypalAuthResponse::PaypalRedirectResponse(_) => {
// For redirect responses, we don't have amount/currency in the response
// Use the original request values for integrity check
(
connector_utils::convert_amount(
self.amount_converter,
data.request.minor_amount,
data.request.currency,
)?,
data.request.currency.to_string(),
)
}
PaypalAuthResponse::PaypalThreeDsResponse(_) => {
// For 3DS responses, we don't have amount/currency in the response
// Use the original request values for integrity check
(
connector_utils::convert_amount(
self.amount_converter,
data.request.minor_amount,
data.request.currency,
)?,
data.request.currency.to_string(),
)
}
};

let response_integrity_object = connector_utils::get_authorise_integrity_object(
self.amount_converter,
response_amount,
response_currency,
)?;

match response {
PaypalAuthResponse::PaypalOrdersResponse(response) => {
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);

RouterData::try_from(ResponseRouterData {
let new_router_data = RouterData::try_from(ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
});

new_router_data.map(|mut router_data| {
router_data.request.integrity_object = Some(response_integrity_object);
router_data
})
}
PaypalAuthResponse::PaypalRedirectResponse(response) => {
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
RouterData::try_from(ResponseRouterData {
let new_router_data = RouterData::try_from(ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
});

new_router_data.map(|mut router_data| {
router_data.request.integrity_object = Some(response_integrity_object);
router_data
})
}
PaypalAuthResponse::PaypalThreeDsResponse(response) => {
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
RouterData::try_from(ResponseRouterData {
let new_router_data = RouterData::try_from(ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
});

new_router_data.map(|mut router_data| {
router_data.request.integrity_object = Some(response_integrity_object);
router_data
})
}
}
Expand Down Expand Up @@ -1563,16 +1647,95 @@ impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Pay
.response
.parse_struct("paypal SyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

// Extract amount and currency for integrity check
let (response_amount, response_currency) = match &response {
paypal::PaypalSyncResponse::PaypalOrdersSyncResponse(resp) => {
let purchase_unit = resp.purchase_units.first().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "purchase_units[0]",
},
)?;
// For orders sync response, we need to get amount from the payments collection
// Try to get from authorizations first, then captures
let amount = if let Some(authorizations) = &purchase_unit.payments.authorizations {
if let Some(auth) = authorizations.first() {
&auth.amount
} else {
return Err(errors::ConnectorError::MissingRequiredField {
field_name: "authorizations[0]",
}
.into());
}
} else if let Some(captures) = &purchase_unit.payments.captures {
if let Some(capture) = captures.first() {
&capture.amount
} else {
return Err(errors::ConnectorError::MissingRequiredField {
field_name: "captures[0]",
}
.into());
}
} else {
return Err(errors::ConnectorError::MissingRequiredField {
field_name: "payments.authorizations or payments.captures",
}
.into());
};
(amount.value.clone(), amount.currency_code.to_string())
}
paypal::PaypalSyncResponse::PaypalPaymentsSyncResponse(resp) => (
resp.amount.value.clone(),
resp.amount.currency_code.to_string(),
),
paypal::PaypalSyncResponse::PaypalRedirectSyncResponse(_) => {
// For redirect sync responses, we don't have amount/currency in the response
// Use the original request values for integrity check
(
connector_utils::convert_amount(
self.amount_converter,
data.request.amount,
data.request.currency,
)?,
data.request.currency.to_string(),
)
}
paypal::PaypalSyncResponse::PaypalThreeDsSyncResponse(_) => {
// For 3DS sync responses, we don't have amount/currency in the response
// Use the original request values for integrity check
(
connector_utils::convert_amount(
self.amount_converter,
data.request.amount,
data.request.currency,
)?,
data.request.currency.to_string(),
)
}
};

let response_integrity_object = connector_utils::get_sync_integrity_object(
self.amount_converter,
response_amount,
response_currency,
)?;

event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
RouterData::foreign_try_from((

let new_router_data = RouterData::foreign_try_from((
ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
},
data.request.payment_experience,
))
));

new_router_data.map(|mut router_data| {
router_data.request.integrity_object = Some(response_integrity_object);
router_data
})
}

fn get_error_response(
Expand Down Expand Up @@ -1820,12 +1983,40 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Paypal
res.response
.parse_struct("paypal RefundResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

// Extract amount and currency for integrity check
let (response_amount, response_currency) = if let Some(amount) = &response.amount {
(amount.value.clone(), amount.currency_code.to_string())
} else {
// If no amount in response, use the original request values
(
connector_utils::convert_amount(
self.amount_converter,
data.request.minor_refund_amount,
data.request.currency,
)?,
data.request.currency.to_string(),
)
};

let response_integrity_object = connector_utils::get_refund_integrity_object(
self.amount_converter,
response_amount,
response_currency,
)?;

event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
RouterData::try_from(ResponseRouterData {

let new_router_data = RouterData::try_from(ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
});

new_router_data.map(|mut router_data| {
router_data.request.integrity_object = Some(response_integrity_object);
router_data
})
}

Expand Down Expand Up @@ -1887,12 +2078,40 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Paypal {
.response
.parse_struct("paypal RefundSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

// Extract amount and currency for integrity check
let (response_amount, response_currency) = if let Some(amount) = &response.amount {
(amount.value.clone(), amount.currency_code.to_string())
} else {
// If no amount in response, use the original request values
(
connector_utils::convert_amount(
self.amount_converter,
data.request.minor_refund_amount,
data.request.currency,
)?,
data.request.currency.to_string(),
)
};

let response_integrity_object = connector_utils::get_refund_integrity_object(
self.amount_converter,
response_amount,
response_currency,
)?;

event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
RouterData::try_from(ResponseRouterData {

let new_router_data = RouterData::try_from(ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
});

new_router_data.map(|mut router_data| {
router_data.request.integrity_object = Some(response_integrity_object);
router_data
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1712,7 +1712,7 @@ pub(crate) fn get_order_status(

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentsCollectionItem {
amount: OrderAmount,
pub amount: OrderAmount,
expiration_time: Option<String>,
id: String,
final_capture: Option<bool>,
Expand All @@ -1721,8 +1721,8 @@ pub struct PaymentsCollectionItem {

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct PaymentsCollection {
authorizations: Option<Vec<PaymentsCollectionItem>>,
captures: Option<Vec<PaymentsCollectionItem>>,
pub authorizations: Option<Vec<PaymentsCollectionItem>>,
pub captures: Option<Vec<PaymentsCollectionItem>>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -1824,7 +1824,7 @@ pub struct PaypalOrdersResponse {
id: String,
intent: PaypalPaymentIntent,
status: PaypalOrderStatus,
purchase_units: Vec<PurchaseUnitItem>,
pub purchase_units: Vec<PurchaseUnitItem>,
payment_source: Option<PaymentSourceItemResponse>,
}

Expand Down Expand Up @@ -1872,7 +1872,7 @@ pub enum PaypalSyncResponse {
pub struct PaypalPaymentsSyncResponse {
id: String,
status: PaypalPaymentStatus,
amount: OrderAmount,
pub amount: OrderAmount,
invoice_id: Option<String>,
supplementary_data: PaypalSupplementaryData,
}
Expand Down Expand Up @@ -2907,7 +2907,7 @@ impl From<RefundStatus> for storage_enums::RefundStatus {
pub struct RefundResponse {
id: String,
status: RefundStatus,
amount: Option<OrderAmount>,
pub amount: Option<OrderAmount>,
}

impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>> for RefundsRouterData<Execute> {
Expand All @@ -2929,6 +2929,7 @@ impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>> for RefundsRout
pub struct RefundSyncResponse {
id: String,
status: RefundStatus,
pub amount: Option<OrderAmount>,
}

impl TryFrom<RefundsResponseRouterData<RSync, RefundSyncResponse>> for RefundsRouterData<RSync> {
Expand Down Expand Up @@ -3364,6 +3365,7 @@ impl TryFrom<(PaypalRefundWebhooks, PaypalWebhookEventType)> for RefundSyncRespo
id: webhook_body.id,
status: RefundStatus::try_from(webhook_event)
.attach_printable("Could not find suitable webhook event")?,
amount: None, // Webhook doesn't contain amount information
})
}
}
Expand Down
Loading
Loading