mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 17:47:54 +08:00
feat(compatibility): add mandates support in stripe compatibility (#897)
Co-authored-by: Sahkal Poddar <sahkal.poddar@juspay.in> Co-authored-by: Sarthak Soni <sarthak.soni@juspay.in> Co-authored-by: Sarthak Soni <76486416+Sarthak1799@users.noreply.github.com> Co-authored-by: Abhishek Marrivagu <abhi.codes10@gmail.com>
This commit is contained in:
@ -47,6 +47,7 @@ locker_decryption_key1 = ""
|
|||||||
locker_decryption_key2 = ""
|
locker_decryption_key2 = ""
|
||||||
vault_encryption_key = ""
|
vault_encryption_key = ""
|
||||||
vault_private_key = ""
|
vault_private_key = ""
|
||||||
|
tunnel_private_key = ""
|
||||||
|
|
||||||
[connectors.supported]
|
[connectors.supported]
|
||||||
wallets = ["klarna", "braintree", "applepay"]
|
wallets = ["klarna", "braintree", "applepay"]
|
||||||
|
|||||||
@ -147,17 +147,17 @@ where
|
|||||||
///
|
///
|
||||||
/// Extending functionalities of `bytes::Bytes`
|
/// Extending functionalities of `bytes::Bytes`
|
||||||
///
|
///
|
||||||
pub trait BytesExt<T> {
|
pub trait BytesExt {
|
||||||
///
|
///
|
||||||
/// Convert `bytes::Bytes` into type `<T>` using `serde::Deserialize`
|
/// Convert `bytes::Bytes` into type `<T>` using `serde::Deserialize`
|
||||||
///
|
///
|
||||||
fn parse_struct<'de>(&'de self, type_name: &str) -> CustomResult<T, errors::ParsingError>
|
fn parse_struct<'de, T>(&'de self, type_name: &str) -> CustomResult<T, errors::ParsingError>
|
||||||
where
|
where
|
||||||
T: Deserialize<'de>;
|
T: Deserialize<'de>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> BytesExt<T> for bytes::Bytes {
|
impl BytesExt for bytes::Bytes {
|
||||||
fn parse_struct<'de>(&'de self, _type_name: &str) -> CustomResult<T, errors::ParsingError>
|
fn parse_struct<'de, T>(&'de self, _type_name: &str) -> CustomResult<T, errors::ParsingError>
|
||||||
where
|
where
|
||||||
T: Deserialize<'de>,
|
T: Deserialize<'de>,
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,6 +20,7 @@ impl StripeApis {
|
|||||||
.service(app::PaymentIntents::server(state.clone()))
|
.service(app::PaymentIntents::server(state.clone()))
|
||||||
.service(app::Refunds::server(state.clone()))
|
.service(app::Refunds::server(state.clone()))
|
||||||
.service(app::Customers::server(state.clone()))
|
.service(app::Customers::server(state.clone()))
|
||||||
.service(app::Webhooks::server(state))
|
.service(app::Webhooks::server(state.clone()))
|
||||||
|
.service(app::Mandates::server(state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use actix_web::{web, Scope};
|
use actix_web::{web, Scope};
|
||||||
|
|
||||||
use super::{customers::*, payment_intents::*, refunds::*, setup_intents::*, webhooks::*};
|
use super::{customers::*, payment_intents::*, refunds::*, setup_intents::*, webhooks::*};
|
||||||
use crate::routes::{self, webhooks};
|
use crate::routes::{self, mandates, webhooks};
|
||||||
|
|
||||||
pub struct PaymentIntents;
|
pub struct PaymentIntents;
|
||||||
|
|
||||||
@ -111,3 +111,13 @@ impl Webhooks {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Mandates;
|
||||||
|
|
||||||
|
impl Mandates {
|
||||||
|
pub fn server(config: routes::AppState) -> Scope {
|
||||||
|
web::scope("/payment_methods")
|
||||||
|
.app_data(web::Data::new(config))
|
||||||
|
.service(web::resource("/{id}/detach").route(web::post().to(mandates::revoke_mandate)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use api_models::payments;
|
use api_models::payments;
|
||||||
use common_utils::{date_time, ext_traits::StringExt, pii as secret};
|
use common_utils::{date_time, ext_traits::StringExt, pii as secret};
|
||||||
use error_stack::ResultExt;
|
use error_stack::{IntoReport, ResultExt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -10,7 +10,7 @@ use crate::{
|
|||||||
pii::{self, Email, PeekInterface},
|
pii::{self, Email, PeekInterface},
|
||||||
types::{
|
types::{
|
||||||
api::{admin, enums as api_enums},
|
api::{admin, enums as api_enums},
|
||||||
transformers::{ForeignFrom, ForeignInto},
|
transformers::{ForeignFrom, ForeignTryFrom},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,6 +59,7 @@ impl From<StripePaymentMethodType> for api_enums::PaymentMethod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Eq, Deserialize, Clone)]
|
#[derive(Default, PartialEq, Eq, Deserialize, Clone)]
|
||||||
pub struct StripePaymentMethodData {
|
pub struct StripePaymentMethodData {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
@ -143,12 +144,32 @@ pub struct StripePaymentIntentRequest {
|
|||||||
pub client_secret: Option<pii::Secret<String>>,
|
pub client_secret: Option<pii::Secret<String>>,
|
||||||
pub payment_method_options: Option<StripePaymentMethodOptions>,
|
pub payment_method_options: Option<StripePaymentMethodOptions>,
|
||||||
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
|
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
|
||||||
|
pub mandate_id: Option<String>,
|
||||||
|
pub off_session: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
|
impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
|
||||||
type Error = error_stack::Report<errors::ApiErrorResponse>;
|
type Error = error_stack::Report<errors::ApiErrorResponse>;
|
||||||
fn try_from(item: StripePaymentIntentRequest) -> errors::RouterResult<Self> {
|
fn try_from(item: StripePaymentIntentRequest) -> errors::RouterResult<Self> {
|
||||||
Ok(Self {
|
let (mandate_options, authentication_type) = match item.payment_method_options {
|
||||||
|
Some(pmo) => {
|
||||||
|
let StripePaymentMethodOptions::Card {
|
||||||
|
request_three_d_secure,
|
||||||
|
mandate_options,
|
||||||
|
}: StripePaymentMethodOptions = pmo;
|
||||||
|
(
|
||||||
|
Option::<payments::MandateData>::foreign_try_from((
|
||||||
|
mandate_options,
|
||||||
|
item.currency.to_owned(),
|
||||||
|
))?,
|
||||||
|
Some(api_enums::AuthenticationType::foreign_from(
|
||||||
|
request_three_d_secure,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => (None, None),
|
||||||
|
};
|
||||||
|
let request = Ok(Self {
|
||||||
payment_id: item.id.map(payments::PaymentIdType::PaymentIntentId),
|
payment_id: item.id.map(payments::PaymentIdType::PaymentIntentId),
|
||||||
amount: item.amount.map(|amount| amount.into()),
|
amount: item.amount.map(|amount| amount.into()),
|
||||||
connector: item.connector,
|
connector: item.connector,
|
||||||
@ -188,16 +209,15 @@ impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
|
|||||||
statement_descriptor_suffix: item.statement_descriptor_suffix,
|
statement_descriptor_suffix: item.statement_descriptor_suffix,
|
||||||
metadata: item.metadata,
|
metadata: item.metadata,
|
||||||
client_secret: item.client_secret.map(|s| s.peek().clone()),
|
client_secret: item.client_secret.map(|s| s.peek().clone()),
|
||||||
authentication_type: item.payment_method_options.map(|pmo| {
|
authentication_type,
|
||||||
let StripePaymentMethodOptions::Card {
|
mandate_data: mandate_options,
|
||||||
request_three_d_secure,
|
|
||||||
} = pmo;
|
|
||||||
|
|
||||||
request_three_d_secure.foreign_into()
|
|
||||||
}),
|
|
||||||
merchant_connector_details: item.merchant_connector_details,
|
merchant_connector_details: item.merchant_connector_details,
|
||||||
|
setup_future_usage: item.setup_future_usage,
|
||||||
|
mandate_id: item.mandate_id,
|
||||||
|
off_session: item.off_session,
|
||||||
..Self::default()
|
..Self::default()
|
||||||
})
|
});
|
||||||
|
request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +394,7 @@ impl From<payments::PaymentsResponse> for StripePaymentIntentResponse {
|
|||||||
created: u64::try_from(date_time::now().assume_utc().unix_timestamp())
|
created: u64::try_from(date_time::now().assume_utc().unix_timestamp())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
method_type: "card".to_string(),
|
method_type: "card".to_string(),
|
||||||
live_mode: false,
|
livemode: false,
|
||||||
},
|
},
|
||||||
error_type: code,
|
error_type: code,
|
||||||
}),
|
}),
|
||||||
@ -391,7 +411,7 @@ pub struct StripePaymentMethod {
|
|||||||
created: u64,
|
created: u64,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
method_type: String,
|
method_type: String,
|
||||||
live_mode: bool,
|
livemode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Eq, PartialEq, Serialize)]
|
#[derive(Default, Eq, PartialEq, Serialize)]
|
||||||
@ -404,7 +424,7 @@ pub struct Charges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Charges {
|
impl Charges {
|
||||||
fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
object: "list",
|
object: "list",
|
||||||
data: vec![],
|
data: vec![],
|
||||||
@ -491,15 +511,83 @@ impl From<payments::PaymentListResponse> for StripePaymentIntentListResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Deserialize, Clone)]
|
#[derive(PartialEq, Eq, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum StripePaymentMethodOptions {
|
pub enum StripePaymentMethodOptions {
|
||||||
Card {
|
Card {
|
||||||
request_three_d_secure: Option<Request3DS>,
|
request_three_d_secure: Option<Request3DS>,
|
||||||
|
mandate_options: Option<MandateOption>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Eq, PartialEq, Serialize, Deserialize, Clone)]
|
#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum StripeMandateType {
|
||||||
|
SingleUse,
|
||||||
|
MultiUse,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Default, Deserialize, Serialize, Debug)]
|
||||||
|
pub struct MandateOption {
|
||||||
|
#[serde(default, with = "common_utils::custom_serde::timestamp::option")]
|
||||||
|
pub accepted_at: Option<time::PrimitiveDateTime>,
|
||||||
|
pub user_agent: Option<String>,
|
||||||
|
pub ip_address: Option<pii::Secret<String, common_utils::pii::IpAddress>>,
|
||||||
|
pub mandate_type: Option<StripeMandateType>,
|
||||||
|
pub amount: Option<i64>,
|
||||||
|
#[serde(default, with = "common_utils::custom_serde::timestamp::option")]
|
||||||
|
pub start_date: Option<time::PrimitiveDateTime>,
|
||||||
|
#[serde(default, with = "common_utils::custom_serde::timestamp::option")]
|
||||||
|
pub end_date: Option<time::PrimitiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ForeignTryFrom<(Option<MandateOption>, Option<String>)> for Option<payments::MandateData> {
|
||||||
|
type Error = error_stack::Report<errors::ApiErrorResponse>;
|
||||||
|
fn foreign_try_from(
|
||||||
|
(mandate_options, currency): (Option<MandateOption>, Option<String>),
|
||||||
|
) -> errors::RouterResult<Self> {
|
||||||
|
let currency = currency
|
||||||
|
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||||
|
field_name: "currency",
|
||||||
|
})
|
||||||
|
.into_report()
|
||||||
|
.and_then(|c| {
|
||||||
|
c.to_uppercase().parse_enum("currency").change_context(
|
||||||
|
errors::ApiErrorResponse::InvalidDataValue {
|
||||||
|
field_name: "currency",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mandate_data = mandate_options.map(|mandate| payments::MandateData {
|
||||||
|
mandate_type: match mandate.mandate_type {
|
||||||
|
Some(item) => match item {
|
||||||
|
StripeMandateType::SingleUse => {
|
||||||
|
payments::MandateType::SingleUse(payments::MandateAmountData {
|
||||||
|
amount: mandate.amount.unwrap_or_default(),
|
||||||
|
currency,
|
||||||
|
start_date: mandate.start_date,
|
||||||
|
end_date: mandate.end_date,
|
||||||
|
metadata: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
StripeMandateType::MultiUse => payments::MandateType::MultiUse(None),
|
||||||
|
},
|
||||||
|
None => api_models::payments::MandateType::MultiUse(None),
|
||||||
|
},
|
||||||
|
customer_acceptance: payments::CustomerAcceptance {
|
||||||
|
acceptance_type: payments::AcceptanceType::Online,
|
||||||
|
accepted_at: mandate.accepted_at,
|
||||||
|
online: Some(payments::OnlineMandate {
|
||||||
|
ip_address: mandate.ip_address.unwrap_or_default(),
|
||||||
|
user_agent: mandate.user_agent.unwrap_or_default(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Ok(mandate_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Eq, PartialEq, Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum Request3DS {
|
pub enum Request3DS {
|
||||||
#[default]
|
#[default]
|
||||||
|
|||||||
@ -28,7 +28,11 @@ pub async fn setup_intents_create(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let create_payment_req: payment_types::PaymentsRequest = payload.into();
|
let create_payment_req: payment_types::PaymentsRequest =
|
||||||
|
match payment_types::PaymentsRequest::try_from(payload) {
|
||||||
|
Ok(req) => req,
|
||||||
|
Err(err) => return api::log_and_return_error_response(err),
|
||||||
|
};
|
||||||
|
|
||||||
wrap::compatibility_api_wrap::<
|
wrap::compatibility_api_wrap::<
|
||||||
_,
|
_,
|
||||||
@ -124,7 +128,11 @@ pub async fn setup_intents_update(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut payload: payment_types::PaymentsRequest = stripe_payload.into();
|
let mut payload: payment_types::PaymentsRequest =
|
||||||
|
match payment_types::PaymentsRequest::try_from(stripe_payload) {
|
||||||
|
Ok(req) => req,
|
||||||
|
Err(err) => return api::log_and_return_error_response(err),
|
||||||
|
};
|
||||||
payload.payment_id = Some(api_types::PaymentIdType::PaymentIntentId(setup_id));
|
payload.payment_id = Some(api_types::PaymentIdType::PaymentIntentId(setup_id));
|
||||||
|
|
||||||
let (auth_type, auth_flow) =
|
let (auth_type, auth_flow) =
|
||||||
@ -179,7 +187,11 @@ pub async fn setup_intents_confirm(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut payload: payment_types::PaymentsRequest = stripe_payload.into();
|
let mut payload: payment_types::PaymentsRequest =
|
||||||
|
match payment_types::PaymentsRequest::try_from(stripe_payload) {
|
||||||
|
Ok(req) => req,
|
||||||
|
Err(err) => return api::log_and_return_error_response(err),
|
||||||
|
};
|
||||||
payload.payment_id = Some(api_types::PaymentIdType::PaymentIntentId(setup_id));
|
payload.payment_id = Some(api_types::PaymentIdType::PaymentIntentId(setup_id));
|
||||||
payload.confirm = Some(true);
|
payload.confirm = Some(true);
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,21 @@
|
|||||||
use api_models::{payments, refunds};
|
use api_models::payments;
|
||||||
|
use common_utils::{date_time, ext_traits::StringExt};
|
||||||
|
use error_stack::ResultExt;
|
||||||
use router_env::logger;
|
use router_env::logger;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
compatibility::stripe::{
|
||||||
|
payment_intents::types as payment_intent, refunds::types as stripe_refunds,
|
||||||
|
},
|
||||||
|
consts,
|
||||||
core::errors,
|
core::errors,
|
||||||
pii::{self, PeekInterface},
|
pii::{self, PeekInterface},
|
||||||
types::api::{self as api_types, enums as api_enums},
|
types::{
|
||||||
|
api::{self as api_types, admin, enums as api_enums},
|
||||||
|
transformers::{ForeignFrom, ForeignTryFrom},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
|
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
|
||||||
@ -108,11 +117,14 @@ impl From<Shipping> for payments::Address {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Eq, Deserialize, Clone)]
|
#[derive(Default, PartialEq, Eq, Deserialize, Clone)]
|
||||||
|
|
||||||
pub struct StripeSetupIntentRequest {
|
pub struct StripeSetupIntentRequest {
|
||||||
pub confirm: Option<bool>,
|
pub confirm: Option<bool>,
|
||||||
pub customer: Option<String>,
|
pub customer: Option<String>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
pub currency: Option<String>,
|
||||||
pub payment_method_data: Option<StripePaymentMethodData>,
|
pub payment_method_data: Option<StripePaymentMethodData>,
|
||||||
pub receipt_email: Option<pii::Email>,
|
pub receipt_email: Option<pii::Email>,
|
||||||
pub return_url: Option<url::Url>,
|
pub return_url: Option<url::Url>,
|
||||||
@ -123,17 +135,46 @@ pub struct StripeSetupIntentRequest {
|
|||||||
pub statement_descriptor_suffix: Option<String>,
|
pub statement_descriptor_suffix: Option<String>,
|
||||||
pub metadata: Option<api_models::payments::Metadata>,
|
pub metadata: Option<api_models::payments::Metadata>,
|
||||||
pub client_secret: Option<pii::Secret<String>>,
|
pub client_secret: Option<pii::Secret<String>>,
|
||||||
|
pub payment_method_options: Option<payment_intent::StripePaymentMethodOptions>,
|
||||||
|
pub payment_method: Option<String>,
|
||||||
|
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<StripeSetupIntentRequest> for payments::PaymentsRequest {
|
impl TryFrom<StripeSetupIntentRequest> for payments::PaymentsRequest {
|
||||||
fn from(item: StripeSetupIntentRequest) -> Self {
|
type Error = error_stack::Report<errors::ApiErrorResponse>;
|
||||||
Self {
|
fn try_from(item: StripeSetupIntentRequest) -> errors::RouterResult<Self> {
|
||||||
|
let (mandate_options, authentication_type) = match item.payment_method_options {
|
||||||
|
Some(pmo) => {
|
||||||
|
let payment_intent::StripePaymentMethodOptions::Card {
|
||||||
|
request_three_d_secure,
|
||||||
|
mandate_options,
|
||||||
|
}: payment_intent::StripePaymentMethodOptions = pmo;
|
||||||
|
(
|
||||||
|
Option::<payments::MandateData>::foreign_try_from((
|
||||||
|
mandate_options,
|
||||||
|
item.currency.to_owned(),
|
||||||
|
))?,
|
||||||
|
Some(api_enums::AuthenticationType::foreign_from(
|
||||||
|
request_three_d_secure,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => (None, None),
|
||||||
|
};
|
||||||
|
let request = Ok(Self {
|
||||||
amount: Some(api_types::Amount::Zero),
|
amount: Some(api_types::Amount::Zero),
|
||||||
currency: Some(api_enums::Currency::default()),
|
|
||||||
capture_method: None,
|
capture_method: None,
|
||||||
amount_to_capture: None,
|
amount_to_capture: None,
|
||||||
confirm: item.confirm,
|
confirm: item.confirm,
|
||||||
customer_id: item.customer,
|
customer_id: item.customer,
|
||||||
|
currency: item
|
||||||
|
.currency
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.to_uppercase().parse_enum("currency"))
|
||||||
|
.transpose()
|
||||||
|
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||||
|
field_name: "currency",
|
||||||
|
})?,
|
||||||
email: item.receipt_email,
|
email: item.receipt_email,
|
||||||
name: item
|
name: item
|
||||||
.billing_details
|
.billing_details
|
||||||
@ -163,8 +204,13 @@ impl From<StripeSetupIntentRequest> for payments::PaymentsRequest {
|
|||||||
statement_descriptor_suffix: item.statement_descriptor_suffix,
|
statement_descriptor_suffix: item.statement_descriptor_suffix,
|
||||||
metadata: item.metadata,
|
metadata: item.metadata,
|
||||||
client_secret: item.client_secret.map(|s| s.peek().clone()),
|
client_secret: item.client_secret.map(|s| s.peek().clone()),
|
||||||
|
setup_future_usage: item.setup_future_usage,
|
||||||
|
merchant_connector_details: item.merchant_connector_details,
|
||||||
|
authentication_type,
|
||||||
|
mandate_data: mandate_options,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
});
|
||||||
|
request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,6 +278,32 @@ impl From<StripePaymentCancelRequest> for payments::PaymentsCancelRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[derive(Default, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct RedirectUrl {
|
||||||
|
pub return_url: Option<String>,
|
||||||
|
pub url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Serialize)]
|
||||||
|
pub struct StripeNextAction {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
stype: payments::NextActionType,
|
||||||
|
redirect_to_url: RedirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_stripe_next_action(
|
||||||
|
next_action: Option<payments::NextAction>,
|
||||||
|
return_url: Option<String>,
|
||||||
|
) -> Option<StripeNextAction> {
|
||||||
|
next_action.map(|n| StripeNextAction {
|
||||||
|
stype: n.next_action_type,
|
||||||
|
redirect_to_url: RedirectUrl {
|
||||||
|
return_url,
|
||||||
|
url: n.redirect_to_url,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Eq, PartialEq, Serialize)]
|
#[derive(Default, Eq, PartialEq, Serialize)]
|
||||||
pub struct StripeSetupIntentResponse {
|
pub struct StripeSetupIntentResponse {
|
||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
@ -241,8 +313,35 @@ pub struct StripeSetupIntentResponse {
|
|||||||
#[serde(with = "common_utils::custom_serde::iso8601::option")]
|
#[serde(with = "common_utils::custom_serde::iso8601::option")]
|
||||||
pub created: Option<time::PrimitiveDateTime>,
|
pub created: Option<time::PrimitiveDateTime>,
|
||||||
pub customer: Option<String>,
|
pub customer: Option<String>,
|
||||||
pub refunds: Option<Vec<refunds::RefundResponse>>,
|
pub refunds: Option<Vec<stripe_refunds::StripeRefundResponse>>,
|
||||||
pub mandate_id: Option<String>,
|
pub mandate_id: Option<String>,
|
||||||
|
pub next_action: Option<StripeNextAction>,
|
||||||
|
pub last_payment_error: Option<LastPaymentError>,
|
||||||
|
pub charges: payment_intent::Charges,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct LastPaymentError {
|
||||||
|
charge: Option<String>,
|
||||||
|
code: Option<String>,
|
||||||
|
decline_code: Option<String>,
|
||||||
|
message: String,
|
||||||
|
param: Option<String>,
|
||||||
|
payment_method: StripePaymentMethod,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
error_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct StripePaymentMethod {
|
||||||
|
#[serde(rename = "id")]
|
||||||
|
payment_method_id: String,
|
||||||
|
object: &'static str,
|
||||||
|
card: Option<StripeCard>,
|
||||||
|
created: u64,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
method_type: String,
|
||||||
|
livemode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<payments::PaymentsResponse> for StripeSetupIntentResponse {
|
impl From<payments::PaymentsResponse> for StripeSetupIntentResponse {
|
||||||
@ -251,11 +350,36 @@ impl From<payments::PaymentsResponse> for StripeSetupIntentResponse {
|
|||||||
object: "setup_intent".to_owned(),
|
object: "setup_intent".to_owned(),
|
||||||
status: StripeSetupStatus::from(resp.status),
|
status: StripeSetupStatus::from(resp.status),
|
||||||
client_secret: resp.client_secret,
|
client_secret: resp.client_secret,
|
||||||
|
charges: payment_intent::Charges::new(),
|
||||||
created: resp.created,
|
created: resp.created,
|
||||||
customer: resp.customer_id,
|
customer: resp.customer_id,
|
||||||
id: resp.payment_id,
|
id: resp.payment_id,
|
||||||
refunds: resp.refunds,
|
refunds: resp
|
||||||
|
.refunds
|
||||||
|
.map(|a| a.into_iter().map(Into::into).collect()),
|
||||||
mandate_id: resp.mandate_id,
|
mandate_id: resp.mandate_id,
|
||||||
|
next_action: into_stripe_next_action(resp.next_action, resp.return_url),
|
||||||
|
last_payment_error: resp.error_code.map(|code| -> LastPaymentError {
|
||||||
|
LastPaymentError {
|
||||||
|
charge: None,
|
||||||
|
code: Some(code.to_owned()),
|
||||||
|
decline_code: None,
|
||||||
|
message: resp
|
||||||
|
.error_message
|
||||||
|
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
|
||||||
|
param: None,
|
||||||
|
payment_method: StripePaymentMethod {
|
||||||
|
payment_method_id: "place_holder_id".to_string(),
|
||||||
|
object: "payment_method",
|
||||||
|
card: None,
|
||||||
|
created: u64::try_from(date_time::now().assume_utc().unix_timestamp())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
method_type: "card".to_string(),
|
||||||
|
livemode: false,
|
||||||
|
},
|
||||||
|
error_type: code,
|
||||||
|
}
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -266,6 +266,7 @@ pub struct Jwekey {
|
|||||||
pub locker_decryption_key2: String,
|
pub locker_decryption_key2: String,
|
||||||
pub vault_encryption_key: String,
|
pub vault_encryption_key: String,
|
||||||
pub vault_private_key: String,
|
pub vault_private_key: String,
|
||||||
|
pub tunnel_private_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Default)]
|
#[derive(Debug, Deserialize, Clone, Default)]
|
||||||
|
|||||||
@ -489,20 +489,22 @@ impl
|
|||||||
types::PaymentsResponseData: Clone,
|
types::PaymentsResponseData: Clone,
|
||||||
{
|
{
|
||||||
let id = data.request.connector_transaction_id.clone();
|
let id = data.request.connector_transaction_id.clone();
|
||||||
let response: transformers::PaymentIntentSyncResponse =
|
let response: transformers::PaymentIntentSyncResponse = match id
|
||||||
match id.get_connector_transaction_id() {
|
.get_connector_transaction_id()
|
||||||
Ok(x) if x.starts_with("set") => res
|
{
|
||||||
.response
|
Ok(x) if x.starts_with("set") => res
|
||||||
.parse_struct("SetupIntentSyncResponse")
|
.response
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed),
|
.parse_struct::<transformers::SetupIntentSyncResponse>("SetupIntentSyncResponse")
|
||||||
Ok(_) => res
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)
|
||||||
.response
|
.map(Into::into),
|
||||||
.parse_struct("PaymentIntentSyncResponse")
|
Ok(_) => res
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed),
|
.response
|
||||||
Err(err) => {
|
.parse_struct("PaymentIntentSyncResponse")
|
||||||
Err(err).change_context(errors::ConnectorError::MissingConnectorTransactionID)
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed),
|
||||||
}
|
Err(err) => {
|
||||||
}?;
|
Err(err).change_context(errors::ConnectorError::MissingConnectorTransactionID)
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
types::RouterData::try_from(types::ResponseRouterData {
|
types::RouterData::try_from(types::ResponseRouterData {
|
||||||
response,
|
response,
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use base64::Engine;
|
|
||||||
use common_utils::{
|
use common_utils::{
|
||||||
ext_traits::{AsyncExt, ByteSliceExt, ValueExt},
|
ext_traits::{AsyncExt, ByteSliceExt, ValueExt},
|
||||||
fp_utils,
|
fp_utils,
|
||||||
};
|
};
|
||||||
// TODO : Evaluate all the helper functions ()
|
// TODO : Evaluate all the helper functions ()
|
||||||
use error_stack::{report, IntoReport, ResultExt};
|
use error_stack::{report, IntoReport, ResultExt};
|
||||||
|
#[cfg(feature = "kms")]
|
||||||
|
use external_services::kms;
|
||||||
|
use josekit::jwe;
|
||||||
use masking::{ExposeOptionInterface, PeekInterface};
|
use masking::{ExposeOptionInterface, PeekInterface};
|
||||||
use router_env::{instrument, tracing};
|
use router_env::{instrument, tracing};
|
||||||
use storage_models::{enums, merchant_account, payment_intent};
|
use storage_models::{enums, merchant_account, payment_intent};
|
||||||
@ -1546,11 +1548,12 @@ impl MerchantConnectorAccountType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_merchant_connector_account(
|
pub async fn get_merchant_connector_account(
|
||||||
db: &dyn StorageInterface,
|
state: &AppState,
|
||||||
merchant_id: &str,
|
merchant_id: &str,
|
||||||
connector_label: &str,
|
connector_label: &str,
|
||||||
creds_identifier: Option<String>,
|
creds_identifier: Option<String>,
|
||||||
) -> RouterResult<MerchantConnectorAccountType> {
|
) -> RouterResult<MerchantConnectorAccountType> {
|
||||||
|
let db = &*state.store;
|
||||||
match creds_identifier {
|
match creds_identifier {
|
||||||
Some(creds_identifier) => {
|
Some(creds_identifier) => {
|
||||||
let mca_config = db
|
let mca_config = db
|
||||||
@ -1560,20 +1563,35 @@ pub async fn get_merchant_connector_account(
|
|||||||
errors::ApiErrorResponse::MerchantConnectorAccountNotFound,
|
errors::ApiErrorResponse::MerchantConnectorAccountNotFound,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let cached_mca = consts::BASE64_ENGINE
|
#[cfg(feature = "kms")]
|
||||||
.decode(mca_config.config.as_bytes())
|
let kms_config = &state.conf.kms;
|
||||||
.into_report()
|
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
|
||||||
.attach_printable(
|
|
||||||
"Failed to decode merchant_connector_details sent in request and then put in cache",
|
|
||||||
)?
|
|
||||||
.parse_struct("MerchantConnectorDetails")
|
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
|
||||||
.attach_printable(
|
|
||||||
"Failed to parse merchant_connector_details sent in request and then put in cache",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(MerchantConnectorAccountType::CacheVal(cached_mca))
|
#[cfg(feature = "kms")]
|
||||||
|
let private_key = kms::get_kms_client(kms_config)
|
||||||
|
.await
|
||||||
|
.decrypt(state.conf.jwekey.tunnel_private_key.to_owned())
|
||||||
|
.await
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable("Error getting tunnel private key")?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "kms"))]
|
||||||
|
let private_key = state.conf.jwekey.tunnel_private_key.to_owned();
|
||||||
|
|
||||||
|
let decrypted_mca = services::decrypt_jwe(mca_config.config.as_str(), services::KeyIdCheck::SkipKeyIdCheck, private_key, jwe::RSA_OAEP_256)
|
||||||
|
.await
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable(
|
||||||
|
"Failed to decrypt merchant_connector_details sent in request and then put in cache",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let res = String::into_bytes(decrypted_mca)
|
||||||
|
.parse_struct("MerchantConnectorDetails")
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable(
|
||||||
|
"Failed to parse merchant_connector_details sent in request and then put in cache",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(MerchantConnectorAccountType::CacheVal(res))
|
||||||
}
|
}
|
||||||
None => db
|
None => db
|
||||||
.find_merchant_connector_account_by_merchant_id_connector_label(
|
.find_merchant_connector_account_by_merchant_id_connector_label(
|
||||||
|
|||||||
@ -44,9 +44,8 @@ where
|
|||||||
connector_id,
|
connector_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
let db = &*state.store;
|
|
||||||
merchant_connector_account = helpers::get_merchant_connector_account(
|
merchant_connector_account = helpers::get_merchant_connector_account(
|
||||||
db,
|
state,
|
||||||
merchant_account.merchant_id.as_str(),
|
merchant_account.merchant_id.as_str(),
|
||||||
&connector_label,
|
&connector_label,
|
||||||
payment_data.creds_identifier.to_owned(),
|
payment_data.creds_identifier.to_owned(),
|
||||||
@ -695,6 +694,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::VerifyRequestDat
|
|||||||
off_session: payment_data.mandate_id.as_ref().map(|_| true),
|
off_session: payment_data.mandate_id.as_ref().map(|_| true),
|
||||||
mandate_id: payment_data.mandate_id.clone(),
|
mandate_id: payment_data.mandate_id.clone(),
|
||||||
setup_mandate_details: payment_data.setup_mandate,
|
setup_mandate_details: payment_data.setup_mandate,
|
||||||
|
return_url: payment_data.payment_intent.return_url,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,8 +30,6 @@ pub async fn construct_refund_router_data<'a, F>(
|
|||||||
refund: &'a storage::Refund,
|
refund: &'a storage::Refund,
|
||||||
creds_identifier: Option<String>,
|
creds_identifier: Option<String>,
|
||||||
) -> RouterResult<types::RefundsRouterData<F>> {
|
) -> RouterResult<types::RefundsRouterData<F>> {
|
||||||
let db = &*state.store;
|
|
||||||
|
|
||||||
let connector_label = helpers::get_connector_label(
|
let connector_label = helpers::get_connector_label(
|
||||||
payment_intent.business_country,
|
payment_intent.business_country,
|
||||||
&payment_intent.business_label,
|
&payment_intent.business_label,
|
||||||
@ -40,7 +38,7 @@ pub async fn construct_refund_router_data<'a, F>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let merchant_connector_account = helpers::get_merchant_connector_account(
|
let merchant_connector_account = helpers::get_merchant_connector_account(
|
||||||
db,
|
state,
|
||||||
merchant_account.merchant_id.as_str(),
|
merchant_account.merchant_id.as_str(),
|
||||||
&connector_label,
|
&connector_label,
|
||||||
creds_identifier,
|
creds_identifier,
|
||||||
@ -234,7 +232,6 @@ pub async fn construct_accept_dispute_router_data<'a>(
|
|||||||
merchant_account: &storage::MerchantAccount,
|
merchant_account: &storage::MerchantAccount,
|
||||||
dispute: &storage::Dispute,
|
dispute: &storage::Dispute,
|
||||||
) -> RouterResult<types::AcceptDisputeRouterData> {
|
) -> RouterResult<types::AcceptDisputeRouterData> {
|
||||||
let db = &*state.store;
|
|
||||||
let connector_id = &dispute.connector;
|
let connector_id = &dispute.connector;
|
||||||
let connector_label = helpers::get_connector_label(
|
let connector_label = helpers::get_connector_label(
|
||||||
payment_intent.business_country,
|
payment_intent.business_country,
|
||||||
@ -243,7 +240,7 @@ pub async fn construct_accept_dispute_router_data<'a>(
|
|||||||
connector_id,
|
connector_id,
|
||||||
);
|
);
|
||||||
let merchant_connector_account = helpers::get_merchant_connector_account(
|
let merchant_connector_account = helpers::get_merchant_connector_account(
|
||||||
db,
|
state,
|
||||||
merchant_account.merchant_id.as_str(),
|
merchant_account.merchant_id.as_str(),
|
||||||
&connector_label,
|
&connector_label,
|
||||||
None,
|
None,
|
||||||
@ -296,7 +293,6 @@ pub async fn construct_submit_evidence_router_data<'a>(
|
|||||||
dispute: &storage::Dispute,
|
dispute: &storage::Dispute,
|
||||||
submit_evidence_request_data: types::SubmitEvidenceRequestData,
|
submit_evidence_request_data: types::SubmitEvidenceRequestData,
|
||||||
) -> RouterResult<types::SubmitEvidenceRouterData> {
|
) -> RouterResult<types::SubmitEvidenceRouterData> {
|
||||||
let db = &*state.store;
|
|
||||||
let connector_id = &dispute.connector;
|
let connector_id = &dispute.connector;
|
||||||
let connector_label = helpers::get_connector_label(
|
let connector_label = helpers::get_connector_label(
|
||||||
payment_intent.business_country,
|
payment_intent.business_country,
|
||||||
@ -305,7 +301,7 @@ pub async fn construct_submit_evidence_router_data<'a>(
|
|||||||
connector_id,
|
connector_id,
|
||||||
);
|
);
|
||||||
let merchant_connector_account = helpers::get_merchant_connector_account(
|
let merchant_connector_account = helpers::get_merchant_connector_account(
|
||||||
db,
|
state,
|
||||||
merchant_account.merchant_id.as_str(),
|
merchant_account.merchant_id.as_str(),
|
||||||
&connector_label,
|
&connector_label,
|
||||||
None,
|
None,
|
||||||
@ -356,7 +352,6 @@ pub async fn construct_upload_file_router_data<'a>(
|
|||||||
connector_id: &str,
|
connector_id: &str,
|
||||||
file_key: String,
|
file_key: String,
|
||||||
) -> RouterResult<types::UploadFileRouterData> {
|
) -> RouterResult<types::UploadFileRouterData> {
|
||||||
let db = &*state.store;
|
|
||||||
let connector_label = helpers::get_connector_label(
|
let connector_label = helpers::get_connector_label(
|
||||||
payment_intent.business_country,
|
payment_intent.business_country,
|
||||||
&payment_intent.business_label,
|
&payment_intent.business_label,
|
||||||
@ -364,7 +359,7 @@ pub async fn construct_upload_file_router_data<'a>(
|
|||||||
connector_id,
|
connector_id,
|
||||||
);
|
);
|
||||||
let merchant_connector_account = helpers::get_merchant_connector_account(
|
let merchant_connector_account = helpers::get_merchant_connector_account(
|
||||||
db,
|
state,
|
||||||
merchant_account.merchant_id.as_str(),
|
merchant_account.merchant_id.as_str(),
|
||||||
&connector_label,
|
&connector_label,
|
||||||
None,
|
None,
|
||||||
@ -418,7 +413,7 @@ pub async fn construct_defend_dispute_router_data<'a>(
|
|||||||
merchant_account: &storage::MerchantAccount,
|
merchant_account: &storage::MerchantAccount,
|
||||||
dispute: &storage::Dispute,
|
dispute: &storage::Dispute,
|
||||||
) -> RouterResult<types::DefendDisputeRouterData> {
|
) -> RouterResult<types::DefendDisputeRouterData> {
|
||||||
let db = &*state.store;
|
let _db = &*state.store;
|
||||||
let connector_id = &dispute.connector;
|
let connector_id = &dispute.connector;
|
||||||
let connector_label = helpers::get_connector_label(
|
let connector_label = helpers::get_connector_label(
|
||||||
payment_intent.business_country,
|
payment_intent.business_country,
|
||||||
@ -427,7 +422,7 @@ pub async fn construct_defend_dispute_router_data<'a>(
|
|||||||
connector_id,
|
connector_id,
|
||||||
);
|
);
|
||||||
let merchant_connector_account = helpers::get_merchant_connector_account(
|
let merchant_connector_account = helpers::get_merchant_connector_account(
|
||||||
db,
|
state,
|
||||||
merchant_account.merchant_id.as_str(),
|
merchant_account.merchant_id.as_str(),
|
||||||
&connector_label,
|
&connector_label,
|
||||||
None,
|
None,
|
||||||
|
|||||||
@ -299,6 +299,7 @@ pub struct VerifyRequestData {
|
|||||||
pub setup_future_usage: Option<storage_enums::FutureUsage>,
|
pub setup_future_usage: Option<storage_enums::FutureUsage>,
|
||||||
pub off_session: Option<bool>,
|
pub off_session: Option<bool>,
|
||||||
pub setup_mandate_details: Option<payments::MandateData>,
|
pub setup_mandate_details: Option<payments::MandateData>,
|
||||||
|
pub return_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|||||||
Reference in New Issue
Block a user