diff --git a/crates/integrations/connector-integration/src/connectors/maya.rs b/crates/integrations/connector-integration/src/connectors/maya.rs index 759bebbfb0..3d357e9937 100644 --- a/crates/integrations/connector-integration/src/connectors/maya.rs +++ b/crates/integrations/connector-integration/src/connectors/maya.rs @@ -17,7 +17,7 @@ use domain_types::{ types::Connectors, }; use error_stack::ResultExt; -use hyperswitch_masking::{ExposeInterface, Maskable}; +use hyperswitch_masking::{ExposeInterface, Maskable, Secret}; use interfaces::{ api::ConnectorCommon, connector_integration_v2::ConnectorIntegrationV2, connector_types, decode::BodyDecoding, @@ -213,7 +213,9 @@ impl Conn network_advice_code: None, network_error_message: None, typed_connector_response: typed, - raw_connector_response: None, + raw_connector_response: Some(Secret::new( + String::from_utf8_lossy(&res.response).to_string(), + )), raw_connector_request: None, typed_connector_request: None, }) @@ -634,7 +636,7 @@ impl fn process_redirect_response( &self, _request: &RequestDetails, - _connector_feature_data: Option<&hyperswitch_masking::Secret>, + _connector_feature_data: Option<&Secret>, ) -> CustomResult { // Maya is a redirect-only connector. The redirect body carries no // meaningful payment state; final status is confirmed via PSync or diff --git a/crates/integrations/connector-integration/src/connectors/maya/transformers.rs b/crates/integrations/connector-integration/src/connectors/maya/transformers.rs index e330b6eb1b..b89fb28a1a 100644 --- a/crates/integrations/connector-integration/src/connectors/maya/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/maya/transformers.rs @@ -1,4 +1,7 @@ -use common_utils::{types::FloatMajorUnit, Method}; +use common_utils::{ + types::{AmountConvertor, FloatMajorUnit, StringMajorUnit, StringMajorUnitForConnector}, + Method, +}; use domain_types::{ connector_flow::{Authorize, PSync, RSync, Refund, Void}, connector_types::{ @@ -10,10 +13,11 @@ use domain_types::{ payment_method_data::{PaymentMethodData, PaymentMethodDataTypes, WalletData}, router_data::ConnectorSpecificConfig, router_data_v2::RouterDataV2, + router_request_types::{PaymentSynIntegrityObject, RefundIntegrityObject}, router_response_types::RedirectForm, }; use error_stack::ResultExt; -use hyperswitch_masking::{PeekInterface, Secret}; +use hyperswitch_masking::Secret; use serde::{Deserialize, Serialize}; use url::Url; @@ -86,8 +90,6 @@ pub struct MayaPaymentsRequest { pub total_amount: MayaTotalAmount, pub redirect_url: MayaRedirectUrl, pub request_reference_number: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub user_id: Option, } /// `totalAmount` object carried by most Maya request bodies (`value` + `currency`, @@ -251,6 +253,27 @@ pub struct MayaWebhookBody { pub can_void: Option, #[serde(default)] pub can_refund: Option, + /// Whether Maya has captured funds for this payment. Present on the + /// payment-inquiry response but not on every webhook event, so optional. + #[serde(default)] + pub can_capture: Option, + /// Whether the payment has been paid. Returned by the payment-inquiry + /// endpoint; absent on some webhook events, so optional. + #[serde(default)] + pub is_paid: Option, + /// Processed amount returned by Maya. The API reference documents this as a + /// JSON number, but Maya's live payment response actually serializes it as a + /// decimal string (e.g. `"100"`), so it is modeled as `StringMajorUnit` and + /// fed through the money framework into the PSync integrity check. + #[serde(default)] + pub amount: Option, + /// ISO 4217 currency echoed by Maya on the payment object. + #[serde(default)] + pub currency: Option, + #[serde(default)] + pub created_at: Option, + #[serde(default)] + pub updated_at: Option, #[serde(default)] pub metadata: Option, } @@ -446,12 +469,6 @@ impl TryFrom; fn try_from(item: ResponseRouterData) -> Result { + let raw_connector_response = serde_json::to_string(&item.response).ok().map(Secret::new); + let redirect_url = Url::parse(&item.response.redirect_url).change_context( ConnectorError::response_deserialization_failed_with_context( item.http_code, @@ -513,6 +531,7 @@ impl TryFrom> type Error = error_stack::Report; fn try_from(item: ResponseRouterData) -> Result { + let raw_connector_response = serde_json::to_string(&item.response).ok().map(Secret::new); + let connector_request_reference_id = item .router_data .resource_common_data @@ -534,6 +555,22 @@ impl TryFrom> let payment = item.response; + // Maya returns the processed amount/currency on the payment object. Run them + // through the money framework and hand them to the PSync integrity check, + // but only when both are present (Maya omits them for e.g. expired payments). + let integrity_object = match (payment.amount.clone(), payment.currency) { + (Some(amount), Some(currency)) => { + let amount = StringMajorUnitForConnector + .convert_back(amount, currency) + .change_context(crate::utils::response_handling_fail_for_connector( + item.http_code, + "maya", + ))?; + Some(PaymentSynIntegrityObject { amount, currency }) + } + _ => None, + }; + let settlement_status = maya_settlement_status(payment.can_void, payment.can_refund); let status = common_enums::AttemptStatus::from(payment.status.clone()); @@ -567,10 +604,15 @@ impl TryFrom> splits: None, status_code: item.http_code, }), + request: PaymentsSyncData { + integrity_object, + ..item.router_data.request + }, resource_common_data: PaymentFlowData { status, settlement_status, raw_connector_status: Some(raw_connector_status), + raw_connector_response, ..item.router_data.resource_common_data }, ..item.router_data @@ -584,6 +626,8 @@ impl TryFrom> type Error = error_stack::Report; fn try_from(item: ResponseRouterData) -> Result { + let raw_connector_response = serde_json::to_string(&item.response).ok().map(Secret::new); + let status = common_enums::AttemptStatus::from(item.response.status.clone()); let void_id = item.response.id; let payment_id = item.response.payment; @@ -608,6 +652,7 @@ impl TryFrom> message: None, reason: None, }), + raw_connector_response, ..item.router_data.resource_common_data }, ..item.router_data @@ -658,6 +703,14 @@ pub struct MayaRefundResponse { pub status: MayaRefundStatus, #[serde(default)] pub reason: Option, + /// Refunded amount echoed by Maya. Unlike the payment object's numeric + /// `amount`, Maya's refund response serializes it as a JSON string, so it is + /// modeled as `StringMajorUnit` and fed through the money framework. + #[serde(default)] + pub amount: Option, + /// ISO 4217 currency echoed by Maya on the refund response. + #[serde(default)] + pub currency: Option, #[serde(default)] pub request_reference_number: Option, #[serde(default)] @@ -726,6 +779,8 @@ impl TryFrom> type Error = error_stack::Report; fn try_from(item: ResponseRouterData) -> Result { + let raw_connector_response = serde_json::to_string(&item.response).ok().map(Secret::new); + let refund_status = common_enums::RefundStatus::from(item.response.status.clone()); let raw_connector_status = RawConnectorStatus { @@ -734,6 +789,24 @@ impl TryFrom> reason: None, }; + // Feed Maya's echoed refund amount/currency through the money framework into + // the refund integrity check, but only when Maya returns both. + let integrity_object = match (item.response.amount.clone(), item.response.currency) { + (Some(amount), Some(currency)) => { + let refund_amount = StringMajorUnitForConnector + .convert_back(amount, currency) + .change_context(crate::utils::response_handling_fail_for_connector( + item.http_code, + "maya", + ))?; + Some(RefundIntegrityObject { + refund_amount, + currency, + }) + } + _ => None, + }; + Ok(Self { response: Ok(RefundsResponseData { connector_refund_id: item.response.id, @@ -741,8 +814,13 @@ impl TryFrom> status_code: item.http_code, acquirer_reference_number: None, }), + request: RefundsData { + integrity_object, + ..item.router_data.request + }, resource_common_data: RefundFlowData { raw_connector_status: Some(raw_connector_status), + raw_connector_response, ..item.router_data.resource_common_data }, ..item.router_data @@ -758,6 +836,8 @@ impl TryFrom> fn try_from( item: ResponseRouterData, ) -> Result { + let raw_connector_response = serde_json::to_string(&item.response).ok().map(Secret::new); + let refund_status = common_enums::RefundStatus::from(item.response.status.clone()); let raw_connector_status = RawConnectorStatus { @@ -775,6 +855,7 @@ impl TryFrom> }), resource_common_data: RefundFlowData { raw_connector_status: Some(raw_connector_status), + raw_connector_response, ..item.router_data.resource_common_data }, ..item.router_data