-
Notifications
You must be signed in to change notification settings - Fork 4.2k
fix(connector): [STRIPE] Retrieving Connect Account Id from Mandate Metadata in MITs #8326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
1500c49
71183d8
9b7feb1
92742c6
4c9e5ee
f29d7b1
2edd8d0
44679ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ use std::{collections::HashMap, ops::Deref}; | |
|
||
use api_models::{self, enums as api_enums, payments}; | ||
use common_enums::{enums, AttemptStatus, PaymentChargeType, StripeChargeType}; | ||
use common_types::payments::SplitPaymentsRequest; | ||
use common_utils::{ | ||
collect_missing_value_keys, | ||
errors::CustomResult, | ||
|
@@ -198,7 +199,7 @@ pub struct PaymentIntentRequest { | |
|
||
#[derive(Debug, Eq, PartialEq, Serialize)] | ||
pub struct IntentCharges { | ||
pub application_fee_amount: MinorUnit, | ||
pub application_fee_amount: Option<MinorUnit>, | ||
#[serde( | ||
rename = "transfer_data[destination]", | ||
skip_serializing_if = "Option::is_none" | ||
|
@@ -1668,8 +1669,39 @@ impl TryFrom<(&PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntentRequest | |
fn try_from(data: (&PaymentsAuthorizeRouterData, MinorUnit)) -> Result<Self, Self::Error> { | ||
let item = data.0; | ||
|
||
let mandate_metadata = item | ||
.request | ||
.mandate_id | ||
.as_ref() | ||
.and_then(|mandate_id| mandate_id.mandate_reference_id.as_ref()) | ||
.and_then(|reference_id| match reference_id { | ||
payments::MandateReferenceId::ConnectorMandateId(mandate_data) => { | ||
Some(mandate_data.get_mandate_metadata()) | ||
} | ||
_ => None, | ||
}); | ||
|
||
let (transfer_account_id, charge_type, application_fees) = if let Some(secret_value) = | ||
mandate_metadata.as_ref().and_then(|s| s.as_ref()) | ||
{ | ||
let json_value = secret_value.clone().expose(); | ||
|
||
let parsed: Result<StripeSplitPaymentRequest, _> = serde_json::from_value(json_value); | ||
|
||
match parsed { | ||
Ok(data) => ( | ||
data.transfer_account_id, | ||
data.charge_type, | ||
data.application_fees, | ||
), | ||
Err(_) => (None, None, None), | ||
} | ||
} else { | ||
(None, None, None) | ||
}; | ||
|
||
let payment_method_token = match &item.request.split_payments { | ||
Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment(_)) => { | ||
Some(SplitPaymentsRequest::StripeSplitPayment(_)) => { | ||
match item.payment_method_token.clone() { | ||
Some(PaymentMethodToken::Token(secret)) => Some(secret), | ||
_ => None, | ||
|
@@ -1948,27 +1980,45 @@ impl TryFrom<(&PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntentRequest | |
}; | ||
|
||
let charges = match &item.request.split_payments { | ||
Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment( | ||
stripe_split_payment, | ||
)) => match &stripe_split_payment.charge_type { | ||
PaymentChargeType::Stripe(charge_type) => match charge_type { | ||
StripeChargeType::Direct => Some(IntentCharges { | ||
application_fee_amount: stripe_split_payment.application_fees, | ||
destination_account_id: None, | ||
}), | ||
StripeChargeType::Destination => Some(IntentCharges { | ||
application_fee_amount: stripe_split_payment.application_fees, | ||
destination_account_id: Some( | ||
stripe_split_payment.transfer_account_id.clone(), | ||
), | ||
}), | ||
}, | ||
}, | ||
Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment(_)) | ||
| Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment(_)) | ||
Some(SplitPaymentsRequest::StripeSplitPayment(stripe_split_payment)) => { | ||
match &stripe_split_payment.charge_type { | ||
PaymentChargeType::Stripe(charge_type) => match charge_type { | ||
StripeChargeType::Direct => Some(IntentCharges { | ||
application_fee_amount: stripe_split_payment.application_fees, | ||
destination_account_id: None, | ||
}), | ||
StripeChargeType::Destination => Some(IntentCharges { | ||
application_fee_amount: stripe_split_payment.application_fees, | ||
destination_account_id: Some( | ||
stripe_split_payment.transfer_account_id.clone(), | ||
), | ||
}), | ||
}, | ||
} | ||
} | ||
Some(SplitPaymentsRequest::AdyenSplitPayment(_)) | ||
| Some(SplitPaymentsRequest::XenditSplitPayment(_)) | ||
| None => None, | ||
}; | ||
|
||
let charges_in = if charges.is_none() { | ||
match charge_type { | ||
Some(PaymentChargeType::Stripe(StripeChargeType::Direct)) => Some(IntentCharges { | ||
application_fee_amount: application_fees, // default to 0 if None | ||
destination_account_id: None, | ||
}), | ||
Some(PaymentChargeType::Stripe(StripeChargeType::Destination)) => { | ||
Some(IntentCharges { | ||
application_fee_amount: application_fees, | ||
destination_account_id: transfer_account_id, | ||
}) | ||
} | ||
_ => None, | ||
} | ||
} else { | ||
charges | ||
}; | ||
|
||
let pm = match (payment_method, payment_method_token.clone()) { | ||
(Some(method), _) => Some(Secret::new(method)), | ||
(None, Some(token)) => Some(token), | ||
|
@@ -2009,7 +2059,7 @@ impl TryFrom<(&PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntentRequest | |
payment_method_types, | ||
expand: Some(ExpandableObjects::LatestCharge), | ||
browser_info, | ||
charges, | ||
charges: charges_in, | ||
}) | ||
} | ||
} | ||
|
@@ -2146,6 +2196,87 @@ impl TryFrom<&ConnectorCustomerRouterData> for CustomerRequest { | |
} | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Debug, Clone)] | ||
pub struct StripeSplitPaymentRequest { | ||
pub charge_type: Option<PaymentChargeType>, | ||
pub application_fees: Option<MinorUnit>, | ||
pub transfer_account_id: Option<String>, | ||
} | ||
|
||
impl TryFrom<&PaymentsAuthorizeRouterData> for StripeSplitPaymentRequest { | ||
type Error = error_stack::Report<ConnectorError>; | ||
|
||
fn try_from(item: &PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> { | ||
//extracting mandate metadata from CIT call if CIT call was a Split Payment | ||
let from_metadata = item | ||
.request | ||
.mandate_id | ||
.as_ref() | ||
.and_then(|mandate_id| mandate_id.mandate_reference_id.as_ref()) | ||
.and_then(|reference_id| match reference_id { | ||
payments::MandateReferenceId::ConnectorMandateId(mandate_data) => { | ||
mandate_data.get_mandate_metadata() | ||
} | ||
_ => None, | ||
}) | ||
.and_then(|secret_value| { | ||
let json_value = secret_value.clone().expose(); | ||
serde_json::from_value::<Self>(json_value).ok() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we not ignore error here, instead log the error if not populate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright |
||
}); | ||
|
||
// If the Split Payment Request in MIT mismatches with the metadata from CIT, throw an error | ||
if from_metadata.is_some() && item.request.split_payments.is_some() { | ||
let mut mit_charge_type = None; | ||
let mut mit_application_fees = None; | ||
let mut mit_transfer_account_id = None; | ||
if let Some(SplitPaymentsRequest::StripeSplitPayment(stripe_split_payment)) = | ||
item.request.split_payments.as_ref() | ||
{ | ||
mit_charge_type = Some(stripe_split_payment.charge_type.clone()); | ||
mit_application_fees = stripe_split_payment.application_fees; | ||
mit_transfer_account_id = Some(stripe_split_payment.transfer_account_id.clone()); | ||
} | ||
|
||
if mit_charge_type != from_metadata.as_ref().and_then(|m| m.charge_type.clone()) | ||
|| mit_application_fees != from_metadata.as_ref().and_then(|m| m.application_fees) | ||
|| mit_transfer_account_id | ||
!= from_metadata | ||
.as_ref() | ||
.and_then(|m| m.transfer_account_id.clone()) | ||
{ | ||
let mismatched_fields = ["transfer_account_id", "application_fees", "charge_type"]; | ||
|
||
let field_str = mismatched_fields.join(", "); | ||
return Err(error_stack::Report::from( | ||
ConnectorError::MandatePaymentDataMismatch { fields: field_str }, | ||
)); | ||
} | ||
} | ||
|
||
// If Mandate Metadata from CIT call has something, populate it | ||
let (charge_type, mut transfer_account_id, application_fees) = | ||
if let Some(ref metadata) = from_metadata { | ||
( | ||
metadata.charge_type.clone(), | ||
metadata.transfer_account_id.clone(), | ||
metadata.application_fees, | ||
) | ||
} else { | ||
(None, None, None) | ||
}; | ||
|
||
// If Charge Type is Destination, transfer_account_id need not be appended in headers | ||
if charge_type == Some(PaymentChargeType::Stripe(StripeChargeType::Destination)) { | ||
transfer_account_id = None; | ||
} | ||
Ok(Self { | ||
charge_type, | ||
transfer_account_id, | ||
application_fees, | ||
}) | ||
} | ||
} | ||
|
||
#[derive(Clone, Default, Debug, Eq, PartialEq, Deserialize, Serialize)] | ||
#[serde(rename_all = "snake_case")] | ||
pub enum StripePaymentStatus { | ||
|
@@ -2524,10 +2655,23 @@ where | |
// For backward compatibility payment_method_id & connector_mandate_id is being populated with the same value | ||
let connector_mandate_id = Some(payment_method_id.clone().expose()); | ||
let payment_method_id = Some(payment_method_id.expose()); | ||
|
||
let mandate_metadata: Option<Secret<Value>> = | ||
match item.data.request.get_split_payment_data() { | ||
Some(SplitPaymentsRequest::StripeSplitPayment(stripe_split_data)) => { | ||
Some(Secret::new(serde_json::json!({ | ||
"transfer_account_id": stripe_split_data.transfer_account_id, | ||
"charge_type": stripe_split_data.charge_type, | ||
"application_fees": stripe_split_data.application_fees, | ||
}))) | ||
} | ||
_ => None, | ||
}; | ||
|
||
MandateReference { | ||
connector_mandate_id, | ||
payment_method_id, | ||
mandate_metadata: None, | ||
mandate_metadata, | ||
connector_mandate_request_reference_id: None, | ||
} | ||
}); | ||
|
@@ -4252,10 +4396,7 @@ where | |
T: SplitPaymentData, | ||
{ | ||
let charge_request = request.get_split_payment_data(); | ||
if let Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment( | ||
stripe_split_payment, | ||
)) = charge_request | ||
{ | ||
if let Some(SplitPaymentsRequest::StripeSplitPayment(stripe_split_payment)) = charge_request { | ||
let stripe_charge_response = common_types::payments::StripeChargeResponseData { | ||
charge_id: Some(charge_id), | ||
charge_type: stripe_split_payment.charge_type, | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -290,6 +290,8 @@ pub enum ApiErrorResponse { | |||||
InvalidPlatformOperation, | ||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_45", message = "External vault failed during processing with connector")] | ||||||
ExternalVaultFailed, | ||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_46", message = "Fields like {fields} doesn't match with the ones used during mandate creation")] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved |
||||||
MandatePaymentDataMismatch { fields: String }, | ||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "WE_01", message = "Failed to authenticate the webhook")] | ||||||
WebhookAuthenticationFailed, | ||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "WE_02", message = "Bad request received in webhook")] | ||||||
|
@@ -651,6 +653,9 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon | |||||
Self::ExternalVaultFailed => { | ||||||
AER::BadRequest(ApiError::new("IR", 45, "External Vault failed while processing with connector.", None)) | ||||||
}, | ||||||
Self::MandatePaymentDataMismatch { fields} => { | ||||||
AER::BadRequest(ApiError::new("IR", 46, format!("Fields like {fields} doesn't match with the ones used during mandate creation"), Some(Extra {fields: Some(fields.to_owned()), ..Default::default()}))) //FIXME: error message | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved |
||||||
} | ||||||
|
||||||
Self::WebhookAuthenticationFailed => { | ||||||
AER::Unauthorized(ApiError::new("WE", 1, "Webhook authentication failed", None)) | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -600,6 +600,9 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode { | |
errors::ApiErrorResponse::AddressNotFound => Self::AddressNotFound, | ||
errors::ApiErrorResponse::NotImplemented { .. } => Self::Unauthorized, | ||
errors::ApiErrorResponse::FlowNotSupported { .. } => Self::InternalServerError, | ||
errors::ApiErrorResponse::MandatePaymentDataMismatch { .. } => { | ||
Self::InternalServerError | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be 4xx if we are checking some request data to our internal saved data There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright |
||
} | ||
errors::ApiErrorResponse::PaymentUnexpectedState { | ||
current_flow, | ||
field_name, | ||
|
Uh oh!
There was an error while loading. Please reload this page.