feat(core): Hyperswitch <|> UCS Mandate flow integration (#8738)

Co-authored-by: Aishwariyaa Anand <aishwariyaa.anand@Aishwariyaa-Anand-C3PGW02T6Y.local>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Aishwariyaa Anand
2025-07-28 13:17:25 +05:30
committed by GitHub
parent 20049d52fa
commit f94f39ef0c
8 changed files with 773 additions and 203 deletions

View File

@ -20,6 +20,7 @@ use crate::{
unified_connector_service::{
build_unified_connector_service_auth_metadata,
handle_unified_connector_service_response_for_payment_authorize,
handle_unified_connector_service_response_for_payment_repeat,
},
},
logger,
@ -515,51 +516,23 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
merchant_connector_account: domain::MerchantConnectorAccountTypeDetails,
merchant_context: &domain::MerchantContext,
) -> RouterResult<()> {
let client = state
.grpc_client
.unified_connector_service_client
.clone()
.ok_or(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to fetch Unified Connector Service client")?;
let payment_authorize_request =
payments_grpc::PaymentServiceAuthorizeRequest::foreign_try_from(self)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to construct Payment Authorize Request")?;
let connector_auth_metadata = build_unified_connector_service_auth_metadata(
merchant_connector_account,
merchant_context,
)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to construct request metadata")?;
let response = client
.payment_authorize(
payment_authorize_request,
connector_auth_metadata,
state.get_grpc_headers(),
if self.request.mandate_id.is_some() {
call_unified_connector_service_repeat_payment(
self,
state,
merchant_connector_account,
merchant_context,
)
.await
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to authorize payment")?;
let payment_authorize_response = response.into_inner();
let (status, router_data_response) =
handle_unified_connector_service_response_for_payment_authorize(
payment_authorize_response.clone(),
} else {
call_unified_connector_service_authorize(
self,
state,
merchant_connector_account,
merchant_context,
)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to deserialize UCS response")?;
self.status = status;
self.response = router_data_response;
self.raw_connector_response = payment_authorize_response
.raw_connector_response
.map(Secret::new);
Ok(())
.await
}
}
}
@ -846,3 +819,115 @@ async fn process_capture_flow(
router_data.response = Ok(updated_response);
Ok(router_data)
}
async fn call_unified_connector_service_authorize(
router_data: &mut types::RouterData<
api::Authorize,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
>,
state: &SessionState,
#[cfg(feature = "v1")] merchant_connector_account: helpers::MerchantConnectorAccountType,
#[cfg(feature = "v2")] merchant_connector_account: domain::MerchantConnectorAccountTypeDetails,
merchant_context: &domain::MerchantContext,
) -> RouterResult<()> {
let client = state
.grpc_client
.unified_connector_service_client
.clone()
.ok_or(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to fetch Unified Connector Service client")?;
let payment_authorize_request =
payments_grpc::PaymentServiceAuthorizeRequest::foreign_try_from(router_data)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to construct Payment Authorize Request")?;
let connector_auth_metadata =
build_unified_connector_service_auth_metadata(merchant_connector_account, merchant_context)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to construct request metadata")?;
let response = client
.payment_authorize(
payment_authorize_request,
connector_auth_metadata,
state.get_grpc_headers(),
)
.await
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to authorize payment")?;
let payment_authorize_response = response.into_inner();
let (status, router_data_response) =
handle_unified_connector_service_response_for_payment_authorize(
payment_authorize_response.clone(),
)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to deserialize UCS response")?;
router_data.status = status;
router_data.response = router_data_response;
router_data.raw_connector_response = payment_authorize_response
.raw_connector_response
.map(Secret::new);
Ok(())
}
async fn call_unified_connector_service_repeat_payment(
router_data: &mut types::RouterData<
api::Authorize,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
>,
state: &SessionState,
#[cfg(feature = "v1")] merchant_connector_account: helpers::MerchantConnectorAccountType,
#[cfg(feature = "v2")] merchant_connector_account: domain::MerchantConnectorAccountTypeDetails,
merchant_context: &domain::MerchantContext,
) -> RouterResult<()> {
let client = state
.grpc_client
.unified_connector_service_client
.clone()
.ok_or(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to fetch Unified Connector Service client")?;
let payment_repeat_request =
payments_grpc::PaymentServiceRepeatEverythingRequest::foreign_try_from(router_data)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to construct Payment Authorize Request")?;
let connector_auth_metadata =
build_unified_connector_service_auth_metadata(merchant_connector_account, merchant_context)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to construct request metadata")?;
let response = client
.payment_repeat(
payment_repeat_request,
connector_auth_metadata,
state.get_grpc_headers(),
)
.await
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to authorize payment")?;
let payment_repeat_response = response.into_inner();
let (status, router_data_response) =
handle_unified_connector_service_response_for_payment_repeat(
payment_repeat_response.clone(),
)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to deserialize UCS response")?;
router_data.status = status;
router_data.response = router_data_response;
router_data.raw_connector_response = payment_repeat_response
.raw_connector_response
.map(Secret::new);
Ok(())
}

View File

@ -1,19 +1,25 @@
use async_trait::async_trait;
use common_types::payments as common_payments_types;
use error_stack::ResultExt;
use router_env::logger;
use unified_connector_service_client::payments as payments_grpc;
use super::{ConstructFlowSpecificData, Feature};
use crate::{
core::{
errors::{ConnectorErrorExt, RouterResult},
errors::{ApiErrorResponse, ConnectorErrorExt, RouterResult},
mandate,
payments::{
self, access_token, customers, helpers, tokenization, transformers, PaymentData,
},
unified_connector_service::{
build_unified_connector_service_auth_metadata,
handle_unified_connector_service_response_for_payment_register,
},
},
routes::SessionState,
services,
types::{self, api, domain},
types::{self, api, domain, transformers::ForeignTryFrom},
};
#[cfg(feature = "v1")]
@ -200,6 +206,62 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
_ => Ok((None, true)),
}
}
async fn call_unified_connector_service<'a>(
&mut self,
state: &SessionState,
#[cfg(feature = "v1")] merchant_connector_account: helpers::MerchantConnectorAccountType,
#[cfg(feature = "v2")]
merchant_connector_account: domain::MerchantConnectorAccountTypeDetails,
merchant_context: &domain::MerchantContext,
) -> RouterResult<()> {
let client = state
.grpc_client
.unified_connector_service_client
.clone()
.ok_or(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to fetch Unified Connector Service client")?;
let payment_register_request =
payments_grpc::PaymentServiceRegisterRequest::foreign_try_from(self)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to construct Payment Setup Mandate Request")?;
let connector_auth_metadata = build_unified_connector_service_auth_metadata(
merchant_connector_account,
merchant_context,
)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to construct request metadata")?;
let response = client
.payment_setup_mandate(
payment_register_request,
connector_auth_metadata,
state.get_grpc_headers(),
)
.await
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to Setup Mandate payment")?;
let payment_register_response = response.into_inner();
let (status, router_data_response) =
handle_unified_connector_service_response_for_payment_register(
payment_register_response.clone(),
)
.change_context(ApiErrorResponse::InternalServerError)
.attach_printable("Failed to deserialize UCS response")?;
self.status = status;
self.response = router_data_response;
// UCS does not return raw connector response for setup mandate right now
// self.raw_connector_response = payment_register_response
// .raw_connector_response
// .map(Secret::new);
Ok(())
}
}
impl mandate::MandateBehaviour for types::SetupMandateRequestData {

View File

@ -10,7 +10,7 @@ use hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAcco
use hyperswitch_domain_models::{
merchant_context::MerchantContext,
router_data::{ConnectorAuthType, ErrorResponse, RouterData},
router_response_types::{PaymentsResponseData, RedirectForm},
router_response_types::PaymentsResponseData,
};
use masking::{ExposeInterface, PeekInterface, Secret};
use unified_connector_service_client::payments::{
@ -238,85 +238,8 @@ pub fn handle_unified_connector_service_response_for_payment_authorize(
> {
let status = AttemptStatus::foreign_try_from(response.status())?;
let connector_response_reference_id =
response.response_ref_id.as_ref().and_then(|identifier| {
identifier
.id_type
.clone()
.and_then(|id_type| match id_type {
payments_grpc::identifier::IdType::Id(id) => Some(id),
payments_grpc::identifier::IdType::EncodedData(encoded_data) => {
Some(encoded_data)
}
payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None,
})
});
let transaction_id = response.transaction_id.as_ref().and_then(|id| {
id.id_type.clone().and_then(|id_type| match id_type {
payments_grpc::identifier::IdType::Id(id) => Some(id),
payments_grpc::identifier::IdType::EncodedData(encoded_data) => Some(encoded_data),
payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None,
})
});
let router_data_response = match status {
AttemptStatus::Charged |
AttemptStatus::Authorized |
AttemptStatus::AuthenticationPending |
AttemptStatus::DeviceDataCollectionPending |
AttemptStatus::Started |
AttemptStatus::AuthenticationSuccessful |
AttemptStatus::Authorizing |
AttemptStatus::ConfirmationAwaited |
AttemptStatus::Pending => Ok(PaymentsResponseData::TransactionResponse {
resource_id: match transaction_id.as_ref() {
Some(transaction_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(transaction_id.clone()),
None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId,
},
redirection_data: Box::new(
response
.redirection_data
.clone()
.map(RedirectForm::foreign_try_from)
.transpose()?
),
mandate_reference: Box::new(None),
connector_metadata: None,
network_txn_id: response.network_txn_id.clone(),
connector_response_reference_id,
incremental_authorization_allowed: response.incremental_authorization_allowed,
charges: None,
}),
AttemptStatus::AuthenticationFailed
| AttemptStatus::AuthorizationFailed
| AttemptStatus::Unresolved
| AttemptStatus::Failure => Err(ErrorResponse {
code: response.error_code().to_owned(),
message: response.error_message().to_owned(),
reason: Some(response.error_message().to_owned()),
status_code: 500,
attempt_status: Some(status),
connector_transaction_id: connector_response_reference_id,
network_decline_code: None,
network_advice_code: None,
network_error_message: None,
}),
AttemptStatus::RouterDeclined |
AttemptStatus::CodInitiated |
AttemptStatus::Voided |
AttemptStatus::VoidInitiated |
AttemptStatus::CaptureInitiated |
AttemptStatus::VoidFailed |
AttemptStatus::AutoRefunded |
AttemptStatus::PartialCharged |
AttemptStatus::PartialChargedAndChargeable |
AttemptStatus::PaymentMethodAwaited |
AttemptStatus::CaptureFailed |
AttemptStatus::IntegrityFailure => return Err(UnifiedConnectorServiceError::NotImplemented(format!(
"AttemptStatus {status:?} is not implemented for Unified Connector Service"
)).into()),
};
let router_data_response =
Result::<PaymentsResponseData, ErrorResponse>::foreign_try_from(response)?;
Ok((status, router_data_response))
}
@ -329,75 +252,36 @@ pub fn handle_unified_connector_service_response_for_payment_get(
> {
let status = AttemptStatus::foreign_try_from(response.status())?;
let connector_response_reference_id =
response.response_ref_id.as_ref().and_then(|identifier| {
identifier
.id_type
.clone()
.and_then(|id_type| match id_type {
payments_grpc::identifier::IdType::Id(id) => Some(id),
payments_grpc::identifier::IdType::EncodedData(encoded_data) => {
Some(encoded_data)
}
payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None,
})
});
let router_data_response = match status {
AttemptStatus::Charged |
AttemptStatus::Authorized |
AttemptStatus::AuthenticationPending |
AttemptStatus::DeviceDataCollectionPending |
AttemptStatus::Started |
AttemptStatus::AuthenticationSuccessful |
AttemptStatus::Authorizing |
AttemptStatus::ConfirmationAwaited |
AttemptStatus::Pending => Ok(
PaymentsResponseData::TransactionResponse {
resource_id: match connector_response_reference_id.as_ref() {
Some(connector_response_reference_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(connector_response_reference_id.clone()),
None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId,
},
redirection_data: Box::new(
None
),
mandate_reference: Box::new(None),
connector_metadata: None,
network_txn_id: response.network_txn_id.clone(),
connector_response_reference_id,
incremental_authorization_allowed: None,
charges: None,
}
),
AttemptStatus::AuthenticationFailed
| AttemptStatus::AuthorizationFailed
| AttemptStatus::Failure => Err(ErrorResponse {
code: response.error_code().to_owned(),
message: response.error_message().to_owned(),
reason: Some(response.error_message().to_owned()),
status_code: 500,
attempt_status: Some(status),
connector_transaction_id: connector_response_reference_id,
network_decline_code: None,
network_advice_code: None,
network_error_message: None,
}),
AttemptStatus::RouterDeclined |
AttemptStatus::CodInitiated |
AttemptStatus::Voided |
AttemptStatus::VoidInitiated |
AttemptStatus::CaptureInitiated |
AttemptStatus::VoidFailed |
AttemptStatus::AutoRefunded |
AttemptStatus::PartialCharged |
AttemptStatus::PartialChargedAndChargeable |
AttemptStatus::Unresolved |
AttemptStatus::PaymentMethodAwaited |
AttemptStatus::CaptureFailed |
AttemptStatus::IntegrityFailure => return Err(UnifiedConnectorServiceError::NotImplemented(format!(
"AttemptStatus {status:?} is not implemented for Unified Connector Service"
)).into()),
};
let router_data_response =
Result::<PaymentsResponseData, ErrorResponse>::foreign_try_from(response)?;
Ok((status, router_data_response))
}
pub fn handle_unified_connector_service_response_for_payment_register(
response: payments_grpc::PaymentServiceRegisterResponse,
) -> CustomResult<
(AttemptStatus, Result<PaymentsResponseData, ErrorResponse>),
UnifiedConnectorServiceError,
> {
let status = AttemptStatus::foreign_try_from(response.status())?;
let router_data_response =
Result::<PaymentsResponseData, ErrorResponse>::foreign_try_from(response)?;
Ok((status, router_data_response))
}
pub fn handle_unified_connector_service_response_for_payment_repeat(
response: payments_grpc::PaymentServiceRepeatEverythingResponse,
) -> CustomResult<
(AttemptStatus, Result<PaymentsResponseData, ErrorResponse>),
UnifiedConnectorServiceError,
> {
let status = AttemptStatus::foreign_try_from(response.status())?;
let router_data_response =
Result::<PaymentsResponseData, ErrorResponse>::foreign_try_from(response)?;
Ok((status, router_data_response))
}

View File

@ -6,9 +6,11 @@ use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use external_services::grpc_client::unified_connector_service::UnifiedConnectorServiceError;
use hyperswitch_domain_models::{
router_data::RouterData,
router_flow_types::payments::{Authorize, PSync},
router_request_types::{AuthenticationData, PaymentsAuthorizeData, PaymentsSyncData},
router_data::{ErrorResponse, RouterData},
router_flow_types::payments::{Authorize, PSync, SetupMandate},
router_request_types::{
AuthenticationData, PaymentsAuthorizeData, PaymentsSyncData, SetupMandateRequestData,
},
router_response_types::{PaymentsResponseData, RedirectForm},
};
use masking::{ExposeInterface, PeekInterface};
@ -167,6 +169,443 @@ impl ForeignTryFrom<&RouterData<Authorize, PaymentsAuthorizeData, PaymentsRespon
}
}
impl ForeignTryFrom<&RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>>
for payments_grpc::PaymentServiceRegisterRequest
{
type Error = error_stack::Report<UnifiedConnectorServiceError>;
fn foreign_try_from(
router_data: &RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
) -> Result<Self, Self::Error> {
let currency = payments_grpc::Currency::foreign_try_from(router_data.request.currency)?;
let payment_method = router_data
.request
.payment_method_type
.map(|payment_method_type| {
build_unified_connector_service_payment_method(
router_data.request.payment_method_data.clone(),
payment_method_type,
)
})
.transpose()?;
let address = payments_grpc::PaymentAddress::foreign_try_from(router_data.address.clone())?;
let auth_type = payments_grpc::AuthenticationType::foreign_try_from(router_data.auth_type)?;
let browser_info = router_data
.request
.browser_info
.clone()
.map(payments_grpc::BrowserInformation::foreign_try_from)
.transpose()?;
let setup_future_usage = router_data
.request
.setup_future_usage
.map(payments_grpc::FutureUsage::foreign_try_from)
.transpose()?;
let customer_acceptance = router_data
.request
.customer_acceptance
.clone()
.map(payments_grpc::CustomerAcceptance::foreign_try_from)
.transpose()?;
Ok(Self {
request_ref_id: Some(Identifier {
id_type: Some(payments_grpc::identifier::IdType::Id(
router_data.connector_request_reference_id.clone(),
)),
}),
currency: currency.into(),
payment_method,
minor_amount: router_data.request.amount,
email: router_data
.request
.email
.clone()
.map(|e| e.expose().expose()),
customer_name: router_data
.request
.customer_name
.clone()
.map(|customer_name| customer_name.peek().to_owned()),
connector_customer_id: router_data
.request
.customer_id
.as_ref()
.map(|id| id.get_string_repr().to_string()),
address: Some(address),
auth_type: auth_type.into(),
enrolled_for_3ds: false,
authentication_data: None,
metadata: router_data
.request
.metadata
.as_ref()
.map(|secret| secret.peek())
.and_then(|val| val.as_object()) //secret
.map(|map| {
map.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect::<HashMap<String, String>>()
})
.unwrap_or_default(),
return_url: router_data.request.router_return_url.clone(),
webhook_url: router_data.request.webhook_url.clone(),
complete_authorize_url: router_data.request.complete_authorize_url.clone(),
access_token: None,
session_token: None,
order_tax_amount: None,
order_category: None,
merchant_order_reference_id: None,
shipping_cost: router_data
.request
.shipping_cost
.map(|cost| cost.get_amount_as_i64()),
setup_future_usage: setup_future_usage.map(|s| s.into()),
off_session: router_data.request.off_session,
request_incremental_authorization: router_data
.request
.request_incremental_authorization,
request_extended_authorization: None,
customer_acceptance,
browser_info,
payment_experience: None,
})
}
}
impl ForeignTryFrom<&RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>>
for payments_grpc::PaymentServiceRepeatEverythingRequest
{
type Error = error_stack::Report<UnifiedConnectorServiceError>;
fn foreign_try_from(
router_data: &RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>,
) -> Result<Self, Self::Error> {
let currency = payments_grpc::Currency::foreign_try_from(router_data.request.currency)?;
let mandate_reference = match &router_data.request.mandate_id {
Some(mandate) => match &mandate.mandate_reference_id {
Some(api_models::payments::MandateReferenceId::ConnectorMandateId(
connector_mandate_id,
)) => Some(payments_grpc::MandateReference {
mandate_id: connector_mandate_id.get_connector_mandate_id(),
}),
_ => {
return Err(UnifiedConnectorServiceError::MissingRequiredField {
field_name: "connector_mandate_id",
}
.into())
}
},
None => {
return Err(UnifiedConnectorServiceError::MissingRequiredField {
field_name: "connector_mandate_id",
}
.into())
}
};
Ok(Self {
request_ref_id: Some(Identifier {
id_type: Some(payments_grpc::identifier::IdType::Id(
router_data.connector_request_reference_id.clone(),
)),
}),
mandate_reference,
amount: router_data.request.amount,
currency: currency.into(),
minor_amount: router_data.request.amount,
merchant_order_reference_id: router_data.request.merchant_order_reference_id.clone(),
metadata: router_data
.request
.metadata
.as_ref()
.and_then(|val| val.as_object())
.map(|map| {
map.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect::<HashMap<String, String>>()
})
.unwrap_or_default(),
webhook_url: router_data.request.webhook_url.clone(),
})
}
}
impl ForeignTryFrom<payments_grpc::PaymentServiceAuthorizeResponse>
for Result<PaymentsResponseData, ErrorResponse>
{
type Error = error_stack::Report<UnifiedConnectorServiceError>;
fn foreign_try_from(
response: payments_grpc::PaymentServiceAuthorizeResponse,
) -> Result<Self, Self::Error> {
let status = AttemptStatus::foreign_try_from(response.status())?;
let connector_response_reference_id =
response.response_ref_id.as_ref().and_then(|identifier| {
identifier
.id_type
.clone()
.and_then(|id_type| match id_type {
payments_grpc::identifier::IdType::Id(id) => Some(id),
payments_grpc::identifier::IdType::EncodedData(encoded_data) => {
Some(encoded_data)
}
payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None,
})
});
let transaction_id = response.transaction_id.as_ref().and_then(|id| {
id.id_type.clone().and_then(|id_type| match id_type {
payments_grpc::identifier::IdType::Id(id) => Some(id),
payments_grpc::identifier::IdType::EncodedData(encoded_data) => Some(encoded_data),
payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None,
})
});
let response = if response.error_code.is_some() {
Err(ErrorResponse {
code: response.error_code().to_owned(),
message: response.error_message().to_owned(),
reason: Some(response.error_message().to_owned()),
status_code: 500, //TODO: To be handled once UCS sends proper status codes
attempt_status: Some(status),
connector_transaction_id: connector_response_reference_id,
network_decline_code: None,
network_advice_code: None,
network_error_message: None,
})
} else {
Ok(PaymentsResponseData::TransactionResponse {
resource_id: match transaction_id.as_ref() {
Some(transaction_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(transaction_id.clone()),
None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId,
},
redirection_data: Box::new(
response
.redirection_data
.clone()
.map(RedirectForm::foreign_try_from)
.transpose()?
),
mandate_reference: Box::new(None),
connector_metadata: None,
network_txn_id: response.network_txn_id.clone(),
connector_response_reference_id,
incremental_authorization_allowed: response.incremental_authorization_allowed,
charges: None,
})
};
Ok(response)
}
}
impl ForeignTryFrom<payments_grpc::PaymentServiceGetResponse>
for Result<PaymentsResponseData, ErrorResponse>
{
type Error = error_stack::Report<UnifiedConnectorServiceError>;
fn foreign_try_from(
response: payments_grpc::PaymentServiceGetResponse,
) -> Result<Self, Self::Error> {
let status = AttemptStatus::foreign_try_from(response.status())?;
let connector_response_reference_id =
response.response_ref_id.as_ref().and_then(|identifier| {
identifier
.id_type
.clone()
.and_then(|id_type| match id_type {
payments_grpc::identifier::IdType::Id(id) => Some(id),
payments_grpc::identifier::IdType::EncodedData(encoded_data) => {
Some(encoded_data)
}
payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None,
})
});
let response = if response.error_code.is_some() {
Err(ErrorResponse {
code: response.error_code().to_owned(),
message: response.error_message().to_owned(),
reason: Some(response.error_message().to_owned()),
status_code: 500, //TODO: To be handled once UCS sends proper status codes
attempt_status: Some(status),
connector_transaction_id: connector_response_reference_id,
network_decline_code: None,
network_advice_code: None,
network_error_message: None,
})
} else {
Ok(PaymentsResponseData::TransactionResponse {
resource_id: match connector_response_reference_id.as_ref() {
Some(connector_response_reference_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(connector_response_reference_id.clone()),
None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId,
},
redirection_data: Box::new(
None
),
mandate_reference: Box::new(None),
connector_metadata: None,
network_txn_id: response.network_txn_id.clone(),
connector_response_reference_id,
incremental_authorization_allowed: None,
charges: None,
}
)
};
Ok(response)
}
}
impl ForeignTryFrom<payments_grpc::PaymentServiceRegisterResponse>
for Result<PaymentsResponseData, ErrorResponse>
{
type Error = error_stack::Report<UnifiedConnectorServiceError>;
fn foreign_try_from(
response: payments_grpc::PaymentServiceRegisterResponse,
) -> Result<Self, Self::Error> {
let status = AttemptStatus::foreign_try_from(response.status())?;
let connector_response_reference_id =
response.response_ref_id.as_ref().and_then(|identifier| {
identifier
.id_type
.clone()
.and_then(|id_type| match id_type {
payments_grpc::identifier::IdType::Id(id) => Some(id),
payments_grpc::identifier::IdType::EncodedData(encoded_data) => {
Some(encoded_data)
}
payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None,
})
});
let response = if response.error_code.is_some() {
Err(ErrorResponse {
code: response.error_code().to_owned(),
message: response.error_message().to_owned(),
reason: Some(response.error_message().to_owned()),
status_code: 500, //TODO: To be handled once UCS sends proper status codes
attempt_status: Some(status),
connector_transaction_id: connector_response_reference_id,
network_decline_code: None,
network_advice_code: None,
network_error_message: None,
})
} else {
Ok(PaymentsResponseData::TransactionResponse {
resource_id: response.registration_id.as_ref().and_then(|identifier| {
identifier
.id_type
.clone()
.and_then(|id_type| match id_type {
payments_grpc::identifier::IdType::Id(id) => Some(
hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(id),
),
payments_grpc::identifier::IdType::EncodedData(encoded_data) => Some(
hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(encoded_data),
),
payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None,
})
}).unwrap_or(hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId),
redirection_data: Box::new(
response
.redirection_data
.clone()
.map(RedirectForm::foreign_try_from)
.transpose()?
),
mandate_reference: Box::new(
response.mandate_reference.map(|grpc_mandate| {
hyperswitch_domain_models::router_response_types::MandateReference {
connector_mandate_id: grpc_mandate.mandate_id,
payment_method_id: None,
mandate_metadata: None,
connector_mandate_request_reference_id: None,
}
})
),
connector_metadata: None,
network_txn_id: response.network_txn_id,
connector_response_reference_id,
incremental_authorization_allowed: response.incremental_authorization_allowed,
charges: None,
})
};
Ok(response)
}
}
impl ForeignTryFrom<payments_grpc::PaymentServiceRepeatEverythingResponse>
for Result<PaymentsResponseData, ErrorResponse>
{
type Error = error_stack::Report<UnifiedConnectorServiceError>;
fn foreign_try_from(
response: payments_grpc::PaymentServiceRepeatEverythingResponse,
) -> Result<Self, Self::Error> {
let status = AttemptStatus::foreign_try_from(response.status())?;
let connector_response_reference_id =
response.response_ref_id.as_ref().and_then(|identifier| {
identifier
.id_type
.clone()
.and_then(|id_type| match id_type {
payments_grpc::identifier::IdType::Id(id) => Some(id),
payments_grpc::identifier::IdType::EncodedData(encoded_data) => {
Some(encoded_data)
}
payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None,
})
});
let transaction_id = response.transaction_id.as_ref().and_then(|id| {
id.id_type.clone().and_then(|id_type| match id_type {
payments_grpc::identifier::IdType::Id(id) => Some(id),
payments_grpc::identifier::IdType::EncodedData(encoded_data) => Some(encoded_data),
payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None,
})
});
let response = if response.error_code.is_some() {
Err(ErrorResponse {
code: response.error_code().to_owned(),
message: response.error_message().to_owned(),
reason: Some(response.error_message().to_owned()),
status_code: 500, //TODO: To be handled once UCS sends proper status codes
attempt_status: Some(status),
connector_transaction_id: transaction_id,
network_decline_code: None,
network_advice_code: None,
network_error_message: None,
})
} else {
Ok(PaymentsResponseData::TransactionResponse {
resource_id: match transaction_id.as_ref() {
Some(transaction_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(transaction_id.clone()),
None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId,
},
redirection_data: Box::new(None),
mandate_reference: Box::new(None),
connector_metadata: None,
network_txn_id: response.network_txn_id.clone(),
connector_response_reference_id,
incremental_authorization_allowed: None,
charges: None,
})
};
Ok(response)
}
}
impl ForeignTryFrom<common_enums::Currency> for payments_grpc::Currency {
type Error = error_stack::Report<UnifiedConnectorServiceError>;
@ -480,3 +919,48 @@ impl ForeignTryFrom<payments_grpc::HttpMethod> for Method {
}
}
}
impl ForeignTryFrom<storage_enums::FutureUsage> for payments_grpc::FutureUsage {
type Error = error_stack::Report<UnifiedConnectorServiceError>;
fn foreign_try_from(future_usage: storage_enums::FutureUsage) -> Result<Self, Self::Error> {
match future_usage {
storage_enums::FutureUsage::OnSession => Ok(Self::OnSession),
storage_enums::FutureUsage::OffSession => Ok(Self::OffSession),
}
}
}
impl ForeignTryFrom<common_types::payments::CustomerAcceptance>
for payments_grpc::CustomerAcceptance
{
type Error = error_stack::Report<UnifiedConnectorServiceError>;
fn foreign_try_from(
customer_acceptance: common_types::payments::CustomerAcceptance,
) -> Result<Self, Self::Error> {
let acceptance_type = match customer_acceptance.acceptance_type {
common_types::payments::AcceptanceType::Online => payments_grpc::AcceptanceType::Online,
common_types::payments::AcceptanceType::Offline => {
payments_grpc::AcceptanceType::Offline
}
};
let online_mandate_details =
customer_acceptance
.online
.map(|online| payments_grpc::OnlineMandate {
ip_address: online.ip_address.map(|ip| ip.peek().to_string()),
user_agent: online.user_agent,
});
Ok(Self {
acceptance_type: acceptance_type.into(),
accepted_at: customer_acceptance
.accepted_at
.map(|dt| dt.assume_utc().unix_timestamp())
.unwrap_or_default(),
online_mandate_details,
})
}
}