mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
fix: refund fix with connector_metadata (#330)
This commit is contained in:
@ -354,6 +354,7 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
|
|||||||
}
|
}
|
||||||
errors::ApiErrorResponse::InvalidCardData { data } => Self::InvalidCardType, // Maybe it is better to de generalize this router error
|
errors::ApiErrorResponse::InvalidCardData { data } => Self::InvalidCardType, // Maybe it is better to de generalize this router error
|
||||||
errors::ApiErrorResponse::CardExpired { data } => Self::ExpiredCard,
|
errors::ApiErrorResponse::CardExpired { data } => Self::ExpiredCard,
|
||||||
|
errors::ApiErrorResponse::RefundNotPossible { connector } => Self::RefundFailed,
|
||||||
errors::ApiErrorResponse::RefundFailed { data } => Self::RefundFailed, // Nothing at stripe to map
|
errors::ApiErrorResponse::RefundFailed { data } => Self::RefundFailed, // Nothing at stripe to map
|
||||||
|
|
||||||
errors::ApiErrorResponse::InternalServerError => Self::InternalServerError, // not a stripe code
|
errors::ApiErrorResponse::InternalServerError => Self::InternalServerError, // not a stripe code
|
||||||
|
|||||||
@ -218,6 +218,7 @@ impl<F, T>
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
|
|||||||
@ -429,6 +429,7 @@ impl TryFrom<types::PaymentsCancelResponseRouterData<AdyenCancelResponse>>
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
@ -477,6 +478,7 @@ pub fn get_adyen_response(
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
};
|
};
|
||||||
Ok((status, error, payments_response_data))
|
Ok((status, error, payments_response_data))
|
||||||
}
|
}
|
||||||
@ -542,6 +544,7 @@ pub fn get_redirection_response(
|
|||||||
redirection_data: Some(redirection_data),
|
redirection_data: Some(redirection_data),
|
||||||
redirect: true,
|
redirect: true,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
};
|
};
|
||||||
Ok((status, error, payments_response_data))
|
Ok((status, error, payments_response_data))
|
||||||
}
|
}
|
||||||
@ -635,6 +638,7 @@ impl TryFrom<types::PaymentsCaptureResponseRouterData<AdyenCaptureResponse>>
|
|||||||
redirect: false,
|
redirect: false,
|
||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
amount_captured,
|
amount_captured,
|
||||||
..item.data
|
..item.data
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use common_utils::ext_traits::{Encode, ValueExt};
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -5,6 +6,7 @@ use crate::{
|
|||||||
core::errors,
|
core::errors,
|
||||||
pii::PeekInterface,
|
pii::PeekInterface,
|
||||||
types::{self, api, storage::enums},
|
types::{self, api, storage::enums},
|
||||||
|
utils::OptionExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, PartialEq, Eq)]
|
#[derive(Debug, Serialize, PartialEq, Eq)]
|
||||||
@ -38,21 +40,22 @@ impl TryFrom<&types::ConnectorAuthType> for MerchantAuthentication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, PartialEq)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct CreditCardDetails {
|
struct CreditCardDetails {
|
||||||
card_number: String,
|
card_number: masking::Secret<String, common_utils::pii::CardNumber>,
|
||||||
expiration_date: String,
|
expiration_date: masking::Secret<String>,
|
||||||
card_code: String,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
card_code: Option<masking::Secret<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, PartialEq)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct BankAccountDetails {
|
struct BankAccountDetails {
|
||||||
account_number: String,
|
account_number: masking::Secret<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, PartialEq)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
enum PaymentDetails {
|
enum PaymentDetails {
|
||||||
#[serde(rename = "creditCard")]
|
#[serde(rename = "creditCard")]
|
||||||
CreditCard(CreditCardDetails),
|
CreditCard(CreditCardDetails),
|
||||||
@ -63,6 +66,29 @@ enum PaymentDetails {
|
|||||||
Paypal,
|
Paypal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<api_models::payments::PaymentMethod> for PaymentDetails {
|
||||||
|
fn from(value: api_models::payments::PaymentMethod) -> Self {
|
||||||
|
match value {
|
||||||
|
api::PaymentMethod::Card(ref ccard) => {
|
||||||
|
let expiry_month = ccard.card_exp_month.peek().clone();
|
||||||
|
let expiry_year = ccard.card_exp_year.peek().clone();
|
||||||
|
|
||||||
|
Self::CreditCard(CreditCardDetails {
|
||||||
|
card_number: ccard.card_number.clone(),
|
||||||
|
expiration_date: format!("{expiry_year}-{expiry_month}").into(),
|
||||||
|
card_code: Some(ccard.card_cvc.clone()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
api::PaymentMethod::BankTransfer => Self::BankAccount(BankAccountDetails {
|
||||||
|
account_number: "XXXXX".to_string().into(),
|
||||||
|
}),
|
||||||
|
api::PaymentMethod::PayLater(_) => Self::Klarna,
|
||||||
|
api::PaymentMethod::Wallet(_) => Self::Wallet,
|
||||||
|
api::PaymentMethod::Paypal => Self::Paypal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, PartialEq)]
|
#[derive(Serialize, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct TransactionRequest {
|
struct TransactionRequest {
|
||||||
@ -132,24 +158,7 @@ impl From<enums::CaptureMethod> for AuthorizationType {
|
|||||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for CreateTransactionRequest {
|
impl TryFrom<&types::PaymentsAuthorizeRouterData> for CreateTransactionRequest {
|
||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||||
let payment_details = match item.request.payment_method_data {
|
let payment_details = item.request.payment_method_data.clone().into();
|
||||||
api::PaymentMethod::Card(ref ccard) => {
|
|
||||||
let expiry_month = ccard.card_exp_month.peek().clone();
|
|
||||||
let expiry_year = ccard.card_exp_year.peek().clone();
|
|
||||||
|
|
||||||
PaymentDetails::CreditCard(CreditCardDetails {
|
|
||||||
card_number: ccard.card_number.peek().clone(),
|
|
||||||
expiration_date: format!("{expiry_year}-{expiry_month}"),
|
|
||||||
card_code: ccard.card_cvc.peek().clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
api::PaymentMethod::BankTransfer => PaymentDetails::BankAccount(BankAccountDetails {
|
|
||||||
account_number: "XXXXX".to_string(),
|
|
||||||
}),
|
|
||||||
api::PaymentMethod::PayLater(_) => PaymentDetails::Klarna,
|
|
||||||
api::PaymentMethod::Wallet(_) => PaymentDetails::Wallet,
|
|
||||||
api::PaymentMethod::Paypal => PaymentDetails::Paypal,
|
|
||||||
};
|
|
||||||
let authorization_indicator_type =
|
let authorization_indicator_type =
|
||||||
item.request.capture_method.map(|c| AuthorizationIndicator {
|
item.request.capture_method.map(|c| AuthorizationIndicator {
|
||||||
authorization_indicator: c.into(),
|
authorization_indicator: c.into(),
|
||||||
@ -252,6 +261,7 @@ pub struct TransactionResponse {
|
|||||||
auth_code: String,
|
auth_code: String,
|
||||||
#[serde(rename = "transId")]
|
#[serde(rename = "transId")]
|
||||||
transaction_id: String,
|
transaction_id: String,
|
||||||
|
pub(super) account_number: Option<String>,
|
||||||
pub(super) errors: Option<Vec<ErrorMessage>>,
|
pub(super) errors: Option<Vec<ErrorMessage>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,6 +304,20 @@ impl<F, T>
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let metadata = item
|
||||||
|
.response
|
||||||
|
.transaction_response
|
||||||
|
.account_number
|
||||||
|
.map(|acc_no| {
|
||||||
|
Encode::<'_, PaymentDetails>::encode_to_value(&construct_refund_payment_details(
|
||||||
|
acc_no,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
.change_context(errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "connector_metadata".to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
status,
|
status,
|
||||||
response: match error {
|
response: match error {
|
||||||
@ -305,6 +329,7 @@ impl<F, T>
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: metadata,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
..item.data
|
..item.data
|
||||||
@ -340,32 +365,26 @@ pub struct CreateRefundRequest {
|
|||||||
impl<F> TryFrom<&types::RefundsRouterData<F>> for CreateRefundRequest {
|
impl<F> TryFrom<&types::RefundsRouterData<F>> for CreateRefundRequest {
|
||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||||
let (payment_details, merchant_authentication, transaction_request);
|
let payment_details = item
|
||||||
payment_details = match item.request.payment_method_data {
|
.request
|
||||||
api::PaymentMethod::Card(ref ccard) => {
|
.connector_metadata
|
||||||
let expiry_month = ccard.card_exp_month.peek().clone();
|
.as_ref()
|
||||||
let expiry_year = ccard.card_exp_year.peek().clone();
|
.get_required_value("connector_metadata")
|
||||||
|
.change_context(errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "connector_metadata".to_string(),
|
||||||
|
})?
|
||||||
|
.clone();
|
||||||
|
|
||||||
PaymentDetails::CreditCard(CreditCardDetails {
|
let merchant_authentication = MerchantAuthentication::try_from(&item.connector_auth_type)?;
|
||||||
card_number: ccard.card_number.peek().clone(),
|
|
||||||
expiration_date: format!("{expiry_year}-{expiry_month}"),
|
|
||||||
card_code: ccard.card_cvc.peek().clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
api::PaymentMethod::BankTransfer => PaymentDetails::BankAccount(BankAccountDetails {
|
|
||||||
account_number: "XXXXX".to_string(),
|
|
||||||
}),
|
|
||||||
api::PaymentMethod::PayLater(_) => PaymentDetails::Klarna,
|
|
||||||
api::PaymentMethod::Wallet(_) => PaymentDetails::Wallet,
|
|
||||||
api::PaymentMethod::Paypal => PaymentDetails::Paypal,
|
|
||||||
};
|
|
||||||
|
|
||||||
merchant_authentication = MerchantAuthentication::try_from(&item.connector_auth_type)?;
|
let transaction_request = RefundTransactionRequest {
|
||||||
|
|
||||||
transaction_request = RefundTransactionRequest {
|
|
||||||
transaction_type: TransactionType::Refund,
|
transaction_type: TransactionType::Refund,
|
||||||
amount: item.request.refund_amount,
|
amount: item.request.refund_amount,
|
||||||
payment: payment_details,
|
payment: payment_details
|
||||||
|
.parse_value("PaymentDetails")
|
||||||
|
.change_context(errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "payment_details".to_string(),
|
||||||
|
})?,
|
||||||
currency_code: item.request.currency.to_string(),
|
currency_code: item.request.currency.to_string(),
|
||||||
reference_transaction_id: item.request.connector_transaction_id.clone(),
|
reference_transaction_id: item.request.connector_transaction_id.clone(),
|
||||||
};
|
};
|
||||||
@ -590,6 +609,7 @@ impl<F, Req>
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
status: payment_status,
|
status: payment_status,
|
||||||
..item.data
|
..item.data
|
||||||
@ -610,3 +630,11 @@ pub struct ErrorDetails {
|
|||||||
pub struct AuthorizedotnetErrorResponse {
|
pub struct AuthorizedotnetErrorResponse {
|
||||||
pub error: ErrorDetails,
|
pub error: ErrorDetails,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn construct_refund_payment_details(masked_number: String) -> PaymentDetails {
|
||||||
|
PaymentDetails::CreditCard(CreditCardDetails {
|
||||||
|
card_number: masked_number.into(),
|
||||||
|
expiration_date: "XXXX".to_string().into(),
|
||||||
|
card_code: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -212,6 +212,7 @@ impl<F, T>
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
|
|||||||
@ -220,6 +220,7 @@ impl TryFrom<types::PaymentsResponseRouterData<PaymentsResponse>>
|
|||||||
redirect: redirection_data.is_some(),
|
redirect: redirection_data.is_some(),
|
||||||
redirection_data,
|
redirection_data,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
@ -259,6 +260,7 @@ impl TryFrom<types::PaymentsSyncResponseRouterData<PaymentsResponse>>
|
|||||||
redirect: redirection_data.is_some(),
|
redirect: redirection_data.is_some(),
|
||||||
redirection_data,
|
redirection_data,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
@ -301,6 +303,7 @@ impl TryFrom<types::PaymentsCancelResponseRouterData<PaymentVoidResponse>>
|
|||||||
redirect: false,
|
redirect: false,
|
||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
status: response.into(),
|
status: response.into(),
|
||||||
..item.data
|
..item.data
|
||||||
@ -372,6 +375,7 @@ impl TryFrom<types::PaymentsCaptureResponseRouterData<PaymentCaptureResponse>>
|
|||||||
redirect: false,
|
redirect: false,
|
||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
status,
|
status,
|
||||||
amount_captured,
|
amount_captured,
|
||||||
|
|||||||
@ -234,6 +234,7 @@ impl TryFrom<types::PaymentsResponseRouterData<CybersourcePaymentsResponse>>
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
|
|||||||
@ -131,6 +131,7 @@ impl TryFrom<types::PaymentsResponseRouterData<KlarnaPaymentsResponse>>
|
|||||||
redirect: true,
|
redirect: true,
|
||||||
redirection_data: Some(redirection_data),
|
redirection_data: Some(redirection_data),
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
|
|||||||
@ -156,6 +156,7 @@ impl<F, T>
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
|
|||||||
@ -396,6 +396,7 @@ impl<F, T>
|
|||||||
redirect: redirection_data.is_some(),
|
redirect: redirection_data.is_some(),
|
||||||
redirection_data,
|
redirection_data,
|
||||||
mandate_reference,
|
mandate_reference,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
amount_captured: Some(item.response.amount_received),
|
amount_captured: Some(item.response.amount_received),
|
||||||
..item.data
|
..item.data
|
||||||
@ -445,6 +446,7 @@ impl<F, T>
|
|||||||
redirect: redirection_data.is_some(),
|
redirect: redirection_data.is_some(),
|
||||||
redirection_data,
|
redirection_data,
|
||||||
mandate_reference,
|
mandate_reference,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
|
|||||||
@ -161,6 +161,7 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..data.clone()
|
..data.clone()
|
||||||
})
|
})
|
||||||
@ -249,6 +250,7 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..data.clone()
|
..data.clone()
|
||||||
})
|
})
|
||||||
@ -306,6 +308,7 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..data.clone()
|
..data.clone()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -169,6 +169,7 @@ impl TryFrom<types::PaymentsResponseRouterData<WorldpayPaymentsResponse>>
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
}),
|
}),
|
||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
|
|||||||
@ -107,6 +107,8 @@ pub enum ApiErrorResponse {
|
|||||||
MandateNotFound,
|
MandateNotFound,
|
||||||
#[error(error_type = ErrorType::ValidationError, code = "RE_03", message = "Return URL is not configured and not passed in payments request.")]
|
#[error(error_type = ErrorType::ValidationError, code = "RE_03", message = "Return URL is not configured and not passed in payments request.")]
|
||||||
ReturnUrlUnavailable,
|
ReturnUrlUnavailable,
|
||||||
|
#[error(error_type = ErrorType::ValidationError, code = "RE_03", message = "Refunds not possible through hyperswitch. Please raise Refunds through {connector} dashboard")]
|
||||||
|
RefundNotPossible { connector: String },
|
||||||
#[error(error_type = ErrorType::DuplicateRequest, code = "RE_04", message = "The merchant account with the specified details already exists in our records.")]
|
#[error(error_type = ErrorType::DuplicateRequest, code = "RE_04", message = "The merchant account with the specified details already exists in our records.")]
|
||||||
DuplicateMerchantAccount,
|
DuplicateMerchantAccount,
|
||||||
#[error(error_type = ErrorType::DuplicateRequest, code = "RE_04", message = "The merchant connector account with the specified details already exists in our records.")]
|
#[error(error_type = ErrorType::DuplicateRequest, code = "RE_04", message = "The merchant connector account with the specified details already exists in our records.")]
|
||||||
@ -163,6 +165,7 @@ impl actix_web::ResponseError for ApiErrorResponse {
|
|||||||
| Self::InvalidCardData { .. }
|
| Self::InvalidCardData { .. }
|
||||||
| Self::CardExpired { .. }
|
| Self::CardExpired { .. }
|
||||||
| Self::RefundFailed { .. }
|
| Self::RefundFailed { .. }
|
||||||
|
| Self::RefundNotPossible { .. }
|
||||||
| Self::VerificationFailed { .. }
|
| Self::VerificationFailed { .. }
|
||||||
| Self::PaymentUnexpectedState { .. }
|
| Self::PaymentUnexpectedState { .. }
|
||||||
| Self::MandateValidationFailed { .. } => StatusCode::BAD_REQUEST, // 400
|
| Self::MandateValidationFailed { .. } => StatusCode::BAD_REQUEST, // 400
|
||||||
|
|||||||
@ -178,6 +178,7 @@ async fn payment_response_update_tracker<F: Clone, T>(
|
|||||||
resource_id,
|
resource_id,
|
||||||
redirection_data,
|
redirection_data,
|
||||||
redirect,
|
redirect,
|
||||||
|
connector_metadata,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let connector_transaction_id = match resource_id {
|
let connector_transaction_id = match resource_id {
|
||||||
@ -206,6 +207,7 @@ async fn payment_response_update_tracker<F: Clone, T>(
|
|||||||
.mandate_id
|
.mandate_id
|
||||||
.clone()
|
.clone()
|
||||||
.map(|mandate| mandate.mandate_id),
|
.map(|mandate| mandate.mandate_id),
|
||||||
|
connector_metadata,
|
||||||
};
|
};
|
||||||
|
|
||||||
let connector_response_update = storage::ConnectorResponseUpdate::ResponseUpdate {
|
let connector_response_update = storage::ConnectorResponseUpdate::ResponseUpdate {
|
||||||
|
|||||||
@ -66,6 +66,7 @@ where
|
|||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
redirect: false,
|
redirect: false,
|
||||||
mandate_reference: None,
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
let router_return_url = Some(helpers::create_redirect_url(
|
let router_return_url = Some(helpers::create_redirect_url(
|
||||||
|
|||||||
@ -113,12 +113,13 @@ pub async fn trigger_refund_to_gateway(
|
|||||||
.attach_printable("Transaction in invalid")
|
.attach_printable("Transaction in invalid")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
validator::validate_for_valid_refunds(payment_attempt)?;
|
||||||
|
|
||||||
let router_data = core_utils::construct_refund_router_data(
|
let router_data = core_utils::construct_refund_router_data(
|
||||||
state,
|
state,
|
||||||
&connector_id,
|
&connector_id,
|
||||||
merchant_account,
|
merchant_account,
|
||||||
(payment_attempt.amount, currency),
|
(payment_attempt.amount, currency),
|
||||||
None,
|
|
||||||
payment_intent,
|
payment_intent,
|
||||||
payment_attempt,
|
payment_attempt,
|
||||||
refund,
|
refund,
|
||||||
@ -261,7 +262,6 @@ pub async fn sync_refund_with_gateway(
|
|||||||
&connector_id,
|
&connector_id,
|
||||||
merchant_account,
|
merchant_account,
|
||||||
(payment_attempt.amount, currency),
|
(payment_attempt.amount, currency),
|
||||||
None,
|
|
||||||
payment_intent,
|
payment_intent,
|
||||||
payment_attempt,
|
payment_attempt,
|
||||||
refund,
|
refund,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use error_stack::report;
|
use common_utils::ext_traits::StringExt;
|
||||||
|
use error_stack::{report, ResultExt};
|
||||||
use router_env::{instrument, tracing};
|
use router_env::{instrument, tracing};
|
||||||
use time::PrimitiveDateTime;
|
use time::PrimitiveDateTime;
|
||||||
|
|
||||||
@ -7,7 +8,7 @@ use crate::{
|
|||||||
db::StorageInterface,
|
db::StorageInterface,
|
||||||
logger,
|
logger,
|
||||||
types::storage::{self, enums},
|
types::storage::{self, enums},
|
||||||
utils,
|
utils::{self, OptionExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
@ -135,3 +136,35 @@ pub fn validate_refund_list(limit: Option<i64>) -> CustomResult<i64, errors::Api
|
|||||||
None => Ok(10),
|
None => Ok(10),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validate_for_valid_refunds(
|
||||||
|
payment_attempt: &storage_models::payment_attempt::PaymentAttempt,
|
||||||
|
) -> RouterResult<()> {
|
||||||
|
let connector: api_models::enums::Connector = payment_attempt
|
||||||
|
.connector
|
||||||
|
.clone()
|
||||||
|
.get_required_value("connector")?
|
||||||
|
.parse_enum("connector")
|
||||||
|
.change_context(errors::ApiErrorResponse::IncorrectConnectorNameGiven)?;
|
||||||
|
let payment_method = payment_attempt
|
||||||
|
.payment_method
|
||||||
|
.get_required_value("payment_method")?;
|
||||||
|
utils::when(
|
||||||
|
matches!(
|
||||||
|
(connector, payment_method),
|
||||||
|
(
|
||||||
|
api_models::enums::Connector::Braintree,
|
||||||
|
storage_models::enums::PaymentMethodType::Paypal
|
||||||
|
) | (
|
||||||
|
api_models::enums::Connector::Klarna,
|
||||||
|
storage_models::enums::PaymentMethodType::Klarna
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|| {
|
||||||
|
Err(errors::ApiErrorResponse::RefundNotPossible {
|
||||||
|
connector: connector.to_string(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@ -3,13 +3,13 @@ use std::marker::PhantomData;
|
|||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
use router_env::{instrument, tracing};
|
use router_env::{instrument, tracing};
|
||||||
|
|
||||||
use super::payments::{helpers, PaymentAddress};
|
use super::payments::PaymentAddress;
|
||||||
use crate::{
|
use crate::{
|
||||||
consts,
|
consts,
|
||||||
core::errors::{self, RouterResult},
|
core::errors::{self, RouterResult},
|
||||||
routes::AppState,
|
routes::AppState,
|
||||||
types::{
|
types::{
|
||||||
self, api,
|
self,
|
||||||
storage::{self, enums},
|
storage::{self, enums},
|
||||||
},
|
},
|
||||||
utils::{generate_id, OptionExt, ValueExt},
|
utils::{generate_id, OptionExt, ValueExt},
|
||||||
@ -22,7 +22,6 @@ pub async fn construct_refund_router_data<'a, F>(
|
|||||||
connector_id: &str,
|
connector_id: &str,
|
||||||
merchant_account: &storage::MerchantAccount,
|
merchant_account: &storage::MerchantAccount,
|
||||||
money: (i64, enums::Currency),
|
money: (i64, enums::Currency),
|
||||||
payment_method_data: Option<&'a api::PaymentMethod>,
|
|
||||||
payment_intent: &'a storage::PaymentIntent,
|
payment_intent: &'a storage::PaymentIntent,
|
||||||
payment_attempt: &storage::PaymentAttempt,
|
payment_attempt: &storage::PaymentAttempt,
|
||||||
refund: &'a storage::Refund,
|
refund: &'a storage::Refund,
|
||||||
@ -48,17 +47,6 @@ pub async fn construct_refund_router_data<'a, F>(
|
|||||||
let payment_method_type = payment_attempt
|
let payment_method_type = payment_attempt
|
||||||
.payment_method
|
.payment_method
|
||||||
.get_required_value("payment_method_type")?;
|
.get_required_value("payment_method_type")?;
|
||||||
let payment_method_data = match payment_method_data.cloned() {
|
|
||||||
Some(v) => v,
|
|
||||||
None => {
|
|
||||||
let (pm, _) = helpers::Vault::get_payment_method_data_from_locker(
|
|
||||||
state,
|
|
||||||
&payment_attempt.attempt_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
pm.get_required_value("payment_method_data")?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let router_data = types::RouterData {
|
let router_data = types::RouterData {
|
||||||
flow: PhantomData,
|
flow: PhantomData,
|
||||||
@ -80,11 +68,11 @@ pub async fn construct_refund_router_data<'a, F>(
|
|||||||
amount_captured: payment_intent.amount_captured,
|
amount_captured: payment_intent.amount_captured,
|
||||||
request: types::RefundsData {
|
request: types::RefundsData {
|
||||||
refund_id: refund.refund_id.clone(),
|
refund_id: refund.refund_id.clone(),
|
||||||
payment_method_data,
|
|
||||||
connector_transaction_id: refund.connector_transaction_id.clone(),
|
connector_transaction_id: refund.connector_transaction_id.clone(),
|
||||||
refund_amount: refund.refund_amount,
|
refund_amount: refund.refund_amount,
|
||||||
currency,
|
currency,
|
||||||
amount,
|
amount,
|
||||||
|
connector_metadata: payment_attempt.connector_metadata.clone(),
|
||||||
reason: refund.refund_reason.clone(),
|
reason: refund.refund_reason.clone(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -243,6 +243,7 @@ impl PaymentAttemptInterface for MockDb {
|
|||||||
browser_info: None,
|
browser_info: None,
|
||||||
payment_token: None,
|
payment_token: None,
|
||||||
error_code: payment_attempt.error_code,
|
error_code: payment_attempt.error_code,
|
||||||
|
connector_metadata: None,
|
||||||
};
|
};
|
||||||
payment_attempts.push(payment_attempt.clone());
|
payment_attempts.push(payment_attempt.clone());
|
||||||
Ok(payment_attempt)
|
Ok(payment_attempt)
|
||||||
@ -381,6 +382,7 @@ mod storage {
|
|||||||
browser_info: payment_attempt.browser_info.clone(),
|
browser_info: payment_attempt.browser_info.clone(),
|
||||||
payment_token: payment_attempt.payment_token.clone(),
|
payment_token: payment_attempt.payment_token.clone(),
|
||||||
error_code: payment_attempt.error_code.clone(),
|
error_code: payment_attempt.error_code.clone(),
|
||||||
|
connector_metadata: payment_attempt.connector_metadata.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let field = format!("pa_{}", created_attempt.attempt_id);
|
let field = format!("pa_{}", created_attempt.attempt_id);
|
||||||
|
|||||||
@ -163,6 +163,7 @@ pub enum PaymentsResponseData {
|
|||||||
redirection_data: Option<services::RedirectForm>,
|
redirection_data: Option<services::RedirectForm>,
|
||||||
redirect: bool,
|
redirect: bool,
|
||||||
mandate_reference: Option<String>,
|
mandate_reference: Option<String>,
|
||||||
|
connector_metadata: Option<serde_json::Value>,
|
||||||
},
|
},
|
||||||
SessionResponse {
|
SessionResponse {
|
||||||
session_token: api::SessionToken,
|
session_token: api::SessionToken,
|
||||||
@ -195,7 +196,6 @@ impl ResponseId {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RefundsData {
|
pub struct RefundsData {
|
||||||
pub refund_id: String,
|
pub refund_id: String,
|
||||||
pub payment_method_data: payments::PaymentMethod,
|
|
||||||
pub connector_transaction_id: String,
|
pub connector_transaction_id: String,
|
||||||
pub currency: storage_enums::Currency,
|
pub currency: storage_enums::Currency,
|
||||||
/// Amount for the payment against which this refund is issued
|
/// Amount for the payment against which this refund is issued
|
||||||
@ -203,6 +203,8 @@ pub struct RefundsData {
|
|||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
/// Amount to be refunded
|
/// Amount to be refunded
|
||||||
pub refund_amount: i64,
|
pub refund_amount: i64,
|
||||||
|
/// Arbitrary metadata required for refund
|
||||||
|
pub connector_metadata: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
|||||||
@ -82,15 +82,9 @@ fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
|
|||||||
currency: enums::Currency::USD,
|
currency: enums::Currency::USD,
|
||||||
|
|
||||||
refund_id: uuid::Uuid::new_v4().to_string(),
|
refund_id: uuid::Uuid::new_v4().to_string(),
|
||||||
payment_method_data: types::api::PaymentMethod::Card(types::api::CCard {
|
|
||||||
card_number: Secret::new("4200000000000000".to_string()),
|
|
||||||
card_exp_month: Secret::new("10".to_string()),
|
|
||||||
card_exp_year: Secret::new("2025".to_string()),
|
|
||||||
card_holder_name: Secret::new("John Doe".to_string()),
|
|
||||||
card_cvc: Secret::new("999".to_string()),
|
|
||||||
}),
|
|
||||||
connector_transaction_id: String::new(),
|
connector_transaction_id: String::new(),
|
||||||
refund_amount: 100,
|
refund_amount: 100,
|
||||||
|
connector_metadata: None,
|
||||||
reason: None,
|
reason: None,
|
||||||
},
|
},
|
||||||
payment_method_id: None,
|
payment_method_id: None,
|
||||||
|
|||||||
@ -82,15 +82,9 @@ fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
|
|||||||
amount: 100,
|
amount: 100,
|
||||||
currency: enums::Currency::USD,
|
currency: enums::Currency::USD,
|
||||||
refund_id: uuid::Uuid::new_v4().to_string(),
|
refund_id: uuid::Uuid::new_v4().to_string(),
|
||||||
payment_method_data: types::api::PaymentMethod::Card(types::api::CCard {
|
|
||||||
card_number: Secret::new("5424000000000015".to_string()),
|
|
||||||
card_exp_month: Secret::new("10".to_string()),
|
|
||||||
card_exp_year: Secret::new("2025".to_string()),
|
|
||||||
card_holder_name: Secret::new("John Doe".to_string()),
|
|
||||||
card_cvc: Secret::new("999".to_string()),
|
|
||||||
}),
|
|
||||||
connector_transaction_id: String::new(),
|
connector_transaction_id: String::new(),
|
||||||
refund_amount: 1,
|
refund_amount: 1,
|
||||||
|
connector_metadata: None,
|
||||||
reason: None,
|
reason: None,
|
||||||
},
|
},
|
||||||
response: Err(types::ErrorResponse::default()),
|
response: Err(types::ErrorResponse::default()),
|
||||||
|
|||||||
@ -79,15 +79,9 @@ fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
|
|||||||
amount: 100,
|
amount: 100,
|
||||||
currency: enums::Currency::USD,
|
currency: enums::Currency::USD,
|
||||||
refund_id: uuid::Uuid::new_v4().to_string(),
|
refund_id: uuid::Uuid::new_v4().to_string(),
|
||||||
payment_method_data: types::api::PaymentMethod::Card(api::CCard {
|
|
||||||
card_number: "4242424242424242".to_string().into(),
|
|
||||||
card_exp_month: "10".to_string().into(),
|
|
||||||
card_exp_year: "35".to_string().into(),
|
|
||||||
card_holder_name: "John Doe".to_string().into(),
|
|
||||||
card_cvc: "123".to_string().into(),
|
|
||||||
}),
|
|
||||||
connector_transaction_id: String::new(),
|
connector_transaction_id: String::new(),
|
||||||
refund_amount: 10,
|
refund_amount: 10,
|
||||||
|
connector_metadata: None,
|
||||||
reason: None,
|
reason: None,
|
||||||
},
|
},
|
||||||
response: Err(types::ErrorResponse::default()),
|
response: Err(types::ErrorResponse::default()),
|
||||||
|
|||||||
@ -114,9 +114,9 @@ pub trait ConnectorActions: Connector {
|
|||||||
amount: 100,
|
amount: 100,
|
||||||
currency: enums::Currency::USD,
|
currency: enums::Currency::USD,
|
||||||
refund_id: uuid::Uuid::new_v4().to_string(),
|
refund_id: uuid::Uuid::new_v4().to_string(),
|
||||||
payment_method_data: types::api::PaymentMethod::Card(CCardType::default().0),
|
|
||||||
connector_transaction_id: transaction_id,
|
connector_transaction_id: transaction_id,
|
||||||
refund_amount: 100,
|
refund_amount: 100,
|
||||||
|
connector_metadata: None,
|
||||||
reason: None,
|
reason: None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -137,9 +137,9 @@ pub trait ConnectorActions: Connector {
|
|||||||
amount: 100,
|
amount: 100,
|
||||||
currency: enums::Currency::USD,
|
currency: enums::Currency::USD,
|
||||||
refund_id: uuid::Uuid::new_v4().to_string(),
|
refund_id: uuid::Uuid::new_v4().to_string(),
|
||||||
payment_method_data: types::api::PaymentMethod::Card(CCardType::default().0),
|
|
||||||
connector_transaction_id: transaction_id,
|
connector_transaction_id: transaction_id,
|
||||||
refund_amount: 100,
|
refund_amount: 100,
|
||||||
|
connector_metadata: None,
|
||||||
reason: None,
|
reason: None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -248,9 +248,9 @@ impl Default for PaymentRefundType {
|
|||||||
amount: 1000,
|
amount: 1000,
|
||||||
currency: enums::Currency::USD,
|
currency: enums::Currency::USD,
|
||||||
refund_id: uuid::Uuid::new_v4().to_string(),
|
refund_id: uuid::Uuid::new_v4().to_string(),
|
||||||
payment_method_data: types::api::PaymentMethod::Card(CCardType::default().0),
|
|
||||||
connector_transaction_id: String::new(),
|
connector_transaction_id: String::new(),
|
||||||
refund_amount: 100,
|
refund_amount: 100,
|
||||||
|
connector_metadata: None,
|
||||||
reason: None,
|
reason: None,
|
||||||
};
|
};
|
||||||
Self(data)
|
Self(data)
|
||||||
|
|||||||
@ -38,6 +38,7 @@ pub struct PaymentAttempt {
|
|||||||
pub browser_info: Option<serde_json::Value>,
|
pub browser_info: Option<serde_json::Value>,
|
||||||
pub error_code: Option<String>,
|
pub error_code: Option<String>,
|
||||||
pub payment_token: Option<String>,
|
pub payment_token: Option<String>,
|
||||||
|
pub connector_metadata: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
@ -76,6 +77,7 @@ pub struct PaymentAttemptNew {
|
|||||||
pub browser_info: Option<serde_json::Value>,
|
pub browser_info: Option<serde_json::Value>,
|
||||||
pub payment_token: Option<String>,
|
pub payment_token: Option<String>,
|
||||||
pub error_code: Option<String>,
|
pub error_code: Option<String>,
|
||||||
|
pub connector_metadata: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -116,6 +118,7 @@ pub enum PaymentAttemptUpdate {
|
|||||||
payment_method_id: Option<Option<String>>,
|
payment_method_id: Option<Option<String>>,
|
||||||
redirect: Option<bool>,
|
redirect: Option<bool>,
|
||||||
mandate_id: Option<String>,
|
mandate_id: Option<String>,
|
||||||
|
connector_metadata: Option<serde_json::Value>,
|
||||||
},
|
},
|
||||||
StatusUpdate {
|
StatusUpdate {
|
||||||
status: storage_enums::AttemptStatus,
|
status: storage_enums::AttemptStatus,
|
||||||
@ -147,6 +150,7 @@ pub struct PaymentAttemptUpdateInternal {
|
|||||||
browser_info: Option<serde_json::Value>,
|
browser_info: Option<serde_json::Value>,
|
||||||
payment_token: Option<String>,
|
payment_token: Option<String>,
|
||||||
error_code: Option<String>,
|
error_code: Option<String>,
|
||||||
|
connector_metadata: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PaymentAttemptUpdate {
|
impl PaymentAttemptUpdate {
|
||||||
@ -238,6 +242,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
|
|||||||
payment_method_id,
|
payment_method_id,
|
||||||
redirect,
|
redirect,
|
||||||
mandate_id,
|
mandate_id,
|
||||||
|
connector_metadata,
|
||||||
} => Self {
|
} => Self {
|
||||||
status: Some(status),
|
status: Some(status),
|
||||||
connector,
|
connector,
|
||||||
@ -247,6 +252,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
|
|||||||
modified_at: Some(common_utils::date_time::now()),
|
modified_at: Some(common_utils::date_time::now()),
|
||||||
redirect,
|
redirect,
|
||||||
mandate_id,
|
mandate_id,
|
||||||
|
connector_metadata,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
PaymentAttemptUpdate::ErrorUpdate {
|
PaymentAttemptUpdate::ErrorUpdate {
|
||||||
|
|||||||
@ -217,6 +217,7 @@ diesel::table! {
|
|||||||
browser_info -> Nullable<Jsonb>,
|
browser_info -> Nullable<Jsonb>,
|
||||||
error_code -> Nullable<Varchar>,
|
error_code -> Nullable<Varchar>,
|
||||||
payment_token -> Nullable<Varchar>,
|
payment_token -> Nullable<Varchar>,
|
||||||
|
connector_metadata -> Nullable<Jsonb>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
ALTER TABLE payment_attempt DROP COLUMN connector_metadata;
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE payment_attempt ADD COLUMN connector_metadata JSONB DEFAULT NULL;
|
||||||
Reference in New Issue
Block a user