mirror of
https://github.com/juspay/hyperswitch.git
synced 2026-03-13 09:02:06 +08:00
feat(core/connector): [SANTANDER] Send back connector refund id in refund response and end_to_end_id in payments response (#11244)
Co-authored-by: Sayak Bhattacharya <sayak.b@juspay.in> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
b78375b646
commit
d70567b5e9
@@ -15247,6 +15247,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ConnectorMetadataResponse": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"santander"
|
||||
],
|
||||
"properties": {
|
||||
"santander": {
|
||||
"$ref": "#/components/schemas/SantanderData"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ConnectorSelection": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -29314,6 +29329,14 @@
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"connector_response_metadata": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ConnectorMetadataResponse"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"feature_metadata": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -30957,6 +30980,14 @@
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"connector_response_metadata": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ConnectorMetadataResponse"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"feature_metadata": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -35706,6 +35737,11 @@
|
||||
"type": "string",
|
||||
"description": "Contains whole connector response",
|
||||
"nullable": true
|
||||
},
|
||||
"connector_refund_id": {
|
||||
"type": "string",
|
||||
"description": "A unique identifier for a payment provided by the connector",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -37263,6 +37299,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SantanderData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"end_to_end_id": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ScaExemptionType": {
|
||||
"type": "string",
|
||||
"description": "SCA Exemptions types available for authentication",
|
||||
|
||||
@@ -10795,6 +10795,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ConnectorMetadataResponse": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"santander"
|
||||
],
|
||||
"properties": {
|
||||
"santander": {
|
||||
"$ref": "#/components/schemas/SantanderData"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ConnectorSelection": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -28169,6 +28184,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SantanderData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"end_to_end_id": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ScaExemptionType": {
|
||||
"type": "string",
|
||||
"description": "SCA Exemptions types available for authentication",
|
||||
|
||||
@@ -7461,6 +7461,11 @@ pub struct PaymentsResponse {
|
||||
#[smithy(value_type = "Option<ConnectorMetadata>")]
|
||||
pub connector_metadata: Option<serde_json::Value>, // This is Value because it is fetched from DB and before putting in DB the type is validated
|
||||
|
||||
/// Returns additional provider-specific metadata for certain connectors
|
||||
#[schema(value_type = Option<ConnectorMetadataResponse>)]
|
||||
#[smithy(value_type = "Option<ConnectorMetadataResponse>")]
|
||||
pub connector_response_metadata: Option<ConnectorMetadataResponse>,
|
||||
|
||||
/// Additional data that might be required by hyperswitch, to enable some specific features.
|
||||
#[schema(value_type = Option<FeatureMetadata>)]
|
||||
#[smithy(value_type = "Option<FeatureMetadata>")]
|
||||
@@ -9834,6 +9839,21 @@ pub struct ConnectorMetadata {
|
||||
pub peachpayments: Option<PeachpaymentsData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, ToSchema, SmithyModel)]
|
||||
#[smithy(namespace = "com.hyperswitch.smithy.types")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ConnectorMetadataResponse {
|
||||
Santander(SantanderData),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, ToSchema, SmithyModel)]
|
||||
#[smithy(namespace = "com.hyperswitch.smithy.types")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SantanderData {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub end_to_end_id: Option<String>,
|
||||
}
|
||||
|
||||
impl ConnectorMetadata {
|
||||
pub fn from_value(
|
||||
value: pii::SecretSerdeValue,
|
||||
|
||||
@@ -351,6 +351,9 @@ pub struct RefundResponse {
|
||||
/// Contains whole connector response
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub raw_connector_response: Option<masking::Secret<String>>,
|
||||
/// A unique identifier for a payment provided by the connector
|
||||
#[smithy(value_type = "Option<String>")]
|
||||
pub connector_refund_id: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
|
||||
@@ -160,6 +160,7 @@ pub struct ImmediateExpirationTime {
|
||||
#[diesel(sql_type = Json)]
|
||||
pub struct ScheduledExpirationTime {
|
||||
/// Expiration time in terms of date, format: YYYY-MM-DD
|
||||
#[serde(with = "common_utils::custom_serde::date_only")]
|
||||
pub date: time::PrimitiveDateTime,
|
||||
/// Days after expiration date for which the QR code remains valid
|
||||
pub validity_after_expiration: Option<u32>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use api_models::payments::{QrCodeInformation, VoucherNextStepData};
|
||||
use api_models::payments::{QrCodeInformation, SantanderData, VoucherNextStepData};
|
||||
use common_enums::{
|
||||
enums, AttemptStatus, BoletoDocumentKind, BoletoPaymentType, ExpiryType, PixKey,
|
||||
};
|
||||
@@ -802,13 +802,16 @@ impl<F, T> TryFrom<ResponseRouterData<F, SantanderPaymentsSyncResponse, T, Payme
|
||||
_ => {
|
||||
let connector_metadata = pix_data
|
||||
.pix
|
||||
.ok_or_else(|| errors::ConnectorError::ParsingFailed)?
|
||||
.first()
|
||||
.as_ref()
|
||||
.and_then(|pix_list| pix_list.first())
|
||||
.map(|pix| {
|
||||
serde_json::json!({
|
||||
"end_to_end_id": pix.end_to_end_id.clone().expose()
|
||||
})
|
||||
});
|
||||
let data = SantanderData {
|
||||
end_to_end_id: Some(pix.end_to_end_id.clone().expose()),
|
||||
};
|
||||
serde_json::to_value(data)
|
||||
.change_context(errors::ConnectorError::ParsingFailed)
|
||||
})
|
||||
.transpose()?;
|
||||
Ok(Self {
|
||||
status: AttemptStatus::from(pix_data.status),
|
||||
response: Ok(PaymentsResponseData::TransactionResponse {
|
||||
@@ -927,7 +930,6 @@ impl<F, T> TryFrom<ResponseRouterData<F, SantanderPaymentsResponse, T, PaymentsR
|
||||
}
|
||||
SantanderPaymentsResponse::Boleto(boleto_data) => {
|
||||
let qr_code_url = if let Some(data) = boleto_data.qr_code_pix.clone() {
|
||||
router_env::logger::debug!("Data to be converted into QR code: {}", data);
|
||||
let qr_image = QrImage::new_from_data(data)
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)?;
|
||||
let url_str = &qr_image.data;
|
||||
@@ -1137,11 +1139,6 @@ fn convert_pix_data_to_value(
|
||||
data: String,
|
||||
variant: Option<ExpiryType>,
|
||||
) -> CustomResult<Option<Value>, errors::ConnectorError> {
|
||||
if router_env::which() != router_env::env::Env::Production {
|
||||
// The data string is the EMV string which is used to generate the QR code. We are generating the QR code and then converting it to a data URL to be sent to the client. This is because the client can directly use the data URL to display the QR code without needing to generate it on their end.
|
||||
// We are logging it because in dev env, we won't be able to scan this QR and complete the payment. We would need to send this EMV to the Santander Support Team to finish the payment on their end so that we can test other flows like PSync/Refund/RSync etc
|
||||
router_env::logger::debug!("Data to be converted into QR code: {}", data);
|
||||
}
|
||||
let image_data = QrImage::new_from_data(data.clone())
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)?;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(all(feature = "v1", feature = "olap"))]
|
||||
use api_models::enums::Connector;
|
||||
#[cfg(feature = "v2")]
|
||||
@@ -1831,6 +1833,31 @@ impl PaymentAttempt {
|
||||
.is_some_and(|unsupported_set| unsupported_set.contains(&pm_type))
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract connector response metadata from the payment attempt metadata based on the connector's configuration
|
||||
pub fn get_connector_response_metadata_from_attempt_metadata(
|
||||
&self,
|
||||
) -> Option<api_models::payments::ConnectorMetadataResponse> {
|
||||
let connector = self
|
||||
.connector
|
||||
.as_deref()
|
||||
.and_then(|s| Connector::from_str(s).ok())?;
|
||||
|
||||
self.connector_metadata
|
||||
.clone()
|
||||
.and_then(|metadata| match connector {
|
||||
Connector::Santander => metadata
|
||||
.parse_value::<api_models::payments::SantanderData>("SantanderData")
|
||||
.map_err(|_| {
|
||||
router_env::logger::warn!(
|
||||
"Failed to parse payment_attempt.connector_metadata to SantanderData"
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
.map(api_models::payments::ConnectorMetadataResponse::Santander),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
||||
@@ -525,6 +525,8 @@ Never share your secret api keys. Keep them guarded and secure.
|
||||
api_models::payments::BankRedirectBilling,
|
||||
api_models::payments::BankRedirectBilling,
|
||||
api_models::payments::ConnectorMetadata,
|
||||
api_models::payments::ConnectorMetadataResponse,
|
||||
api_models::payments::SantanderData,
|
||||
api_models::payments::FeatureMetadata,
|
||||
api_models::payments::ApplepayConnectorMetadataRequest,
|
||||
api_models::payments::SessionTokenInfo,
|
||||
|
||||
@@ -472,6 +472,8 @@ Never share your secret api keys. Keep them guarded and secure.
|
||||
api_models::payments::BankRedirectBilling,
|
||||
api_models::payments::BankRedirectBilling,
|
||||
api_models::payments::ConnectorMetadata,
|
||||
api_models::payments::ConnectorMetadataResponse,
|
||||
api_models::payments::SantanderData,
|
||||
api_models::payments::FeatureMetadata,
|
||||
api_models::payments::SdkType,
|
||||
api_models::payments::ApplepayConnectorMetadataRequest,
|
||||
|
||||
@@ -3995,6 +3995,9 @@ where
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to parse feature metadata")?;
|
||||
|
||||
let connector_response_metadata =
|
||||
payment_attempt.get_connector_response_metadata_from_attempt_metadata();
|
||||
|
||||
let payments_response = api::PaymentsResponse {
|
||||
payment_id: payment_intent.payment_id,
|
||||
merchant_id: payment_intent.merchant_id,
|
||||
@@ -4137,6 +4140,7 @@ where
|
||||
partner_merchant_identifier_details: payment_intent.partner_merchant_identifier_details,
|
||||
payment_method_tokenization_details,
|
||||
installment_options: payment_intent.installment_options,
|
||||
connector_response_metadata,
|
||||
};
|
||||
|
||||
services::ApplicationResponse::JsonWithHeaders((payments_response, headers))
|
||||
@@ -4301,6 +4305,7 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay
|
||||
fn foreign_from((pi, pa): (storage::PaymentIntent, storage::PaymentAttempt)) -> Self {
|
||||
let connector_transaction_id = pa.get_connector_payment_id().map(ToString::to_string);
|
||||
Self {
|
||||
connector_response_metadata: pa.get_connector_response_metadata_from_attempt_metadata(),
|
||||
payment_id: pi.payment_id,
|
||||
merchant_id: pi.merchant_id,
|
||||
status: pi.status,
|
||||
|
||||
@@ -1790,6 +1790,11 @@ impl ForeignFrom<diesel_refund::Refund> for api::RefundResponse {
|
||||
issuer_error_code: refund.issuer_error_code,
|
||||
issuer_error_message: refund.issuer_error_message,
|
||||
raw_connector_response: None,
|
||||
connector_refund_id: refund
|
||||
.connector_refund_id
|
||||
.as_ref()
|
||||
.map(ConnectorTransactionId::get_id)
|
||||
.map(ToOwned::to_owned),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1819,6 +1824,11 @@ impl ForeignFrom<(diesel_refund::Refund, Option<masking::Secret<String>>)> for a
|
||||
issuer_error_code: refund.issuer_error_code,
|
||||
issuer_error_message: refund.issuer_error_message,
|
||||
raw_connector_response,
|
||||
connector_refund_id: refund
|
||||
.connector_refund_id
|
||||
.as_ref()
|
||||
.map(ConnectorTransactionId::get_id)
|
||||
.map(ToOwned::to_owned),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1531,6 +1531,7 @@ mod tests {
|
||||
error_details: None,
|
||||
installment_options: None,
|
||||
state_metadata: None,
|
||||
connector_response_metadata: None,
|
||||
};
|
||||
let content =
|
||||
api_webhooks::OutgoingWebhookContent::PaymentDetails(Box::new(expected_response));
|
||||
|
||||
@@ -482,6 +482,7 @@ async fn payments_create_core() {
|
||||
error_details: None,
|
||||
installment_options: None,
|
||||
state_metadata: None,
|
||||
connector_response_metadata: None,
|
||||
};
|
||||
let expected_response =
|
||||
services::ApplicationResponse::JsonWithHeaders((expected_response, vec![]));
|
||||
@@ -782,6 +783,7 @@ async fn payments_create_core_adyen_no_redirect() {
|
||||
error_details: None,
|
||||
installment_options: None,
|
||||
state_metadata: None,
|
||||
connector_response_metadata: None,
|
||||
},
|
||||
vec![],
|
||||
));
|
||||
|
||||
@@ -242,6 +242,7 @@ async fn payments_create_core() {
|
||||
error_details: None,
|
||||
installment_options: None,
|
||||
state_metadata: None,
|
||||
connector_response_metadata: None,
|
||||
};
|
||||
|
||||
let expected_response =
|
||||
@@ -551,6 +552,7 @@ async fn payments_create_core_adyen_no_redirect() {
|
||||
error_details: None,
|
||||
installment_options: None,
|
||||
state_metadata: None,
|
||||
connector_response_metadata: None,
|
||||
},
|
||||
vec![],
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user