fix: refund fix with connector_metadata (#330)

This commit is contained in:
Nishant Joshi
2023-01-10 19:24:19 +05:30
committed by GitHub
parent ca994f6a3c
commit 9ad56703c4
28 changed files with 163 additions and 91 deletions

View File

@ -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

View File

@ -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
}) })

View File

@ -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

View File

@ -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,
})
}

View File

@ -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
}) })

View File

@ -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,

View File

@ -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
}) })

View File

@ -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
}) })

View File

@ -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
}) })

View File

@ -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
}) })

View File

@ -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()
}) })

View File

@ -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
}) })

View File

@ -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

View File

@ -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 {

View File

@ -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(

View File

@ -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,

View File

@ -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(())
}

View File

@ -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(),
}, },

View File

@ -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);

View File

@ -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)]

View File

@ -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,

View File

@ -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()),

View File

@ -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()),

View File

@ -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)

View File

@ -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 {

View File

@ -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>,
} }
} }

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
ALTER TABLE payment_attempt DROP COLUMN connector_metadata;

View File

@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE payment_attempt ADD COLUMN connector_metadata JSONB DEFAULT NULL;