Skip to content

Commit

Permalink
feat(connector): Mandates for alternate payment methods via Stripe (#…
Browse files Browse the repository at this point in the history
…1041)

Co-authored-by: Manoj Ghorela <[email protected]>
Co-authored-by: Arjun Karthik <[email protected]>
  • Loading branch information
3 people authored May 8, 2023
1 parent fa8683a commit 64721b8
Show file tree
Hide file tree
Showing 32 changed files with 1,068 additions and 601 deletions.
4 changes: 2 additions & 2 deletions config/development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@ credit = { currency = "USD" }
debit = { currency = "USD" }

[tokenization]
stripe = { long_lived_token = false, payment_method = "wallet" }
checkout = { long_lived_token = false, payment_method = "wallet" }
stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } }
checkout = { long_lived_token = false, payment_method = "wallet"}

[connector_customer]
connector_list = "bluesnap, stripe"
Expand Down
8 changes: 7 additions & 1 deletion crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,16 @@ pub struct MandateIds {

#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)]
pub enum MandateReferenceId {
ConnectorMandateId(String), // mandate_id send by connector
ConnectorMandateId(ConnectorMandateReferenceId), // mandate_id send by connector
NetworkMandateId(String), // network_txns_id send by Issuer to connector, Used for PG agnostic mandate txns
}

#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConnectorMandateReferenceId {
pub connector_mandate_id: Option<String>,
pub payment_method_id: Option<String>,
}

impl MandateIds {
pub fn new(mandate_id: String) -> Self {
Self {
Expand Down
32 changes: 32 additions & 0 deletions crates/router/src/configs/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,26 @@ pub struct DummyConnector {
pub struct PaymentMethodTokenFilter {
#[serde(deserialize_with = "pm_deser")]
pub payment_method: HashSet<storage_models::enums::PaymentMethod>,
pub payment_method_type: Option<PaymentMethodTypeTokenFilter>,
pub long_lived_token: bool,
}

#[derive(Debug, Deserialize, Clone, Default)]
#[serde(
deny_unknown_fields,
tag = "type",
content = "list",
rename_all = "snake_case"
)]
pub enum PaymentMethodTypeTokenFilter {
#[serde(deserialize_with = "pm_type_deser")]
EnableOnly(HashSet<storage_models::enums::PaymentMethodType>),
#[serde(deserialize_with = "pm_type_deser")]
DisableOnly(HashSet<storage_models::enums::PaymentMethodType>),
#[default]
AllAccepted,
}

fn pm_deser<'a, D>(
deserializer: D,
) -> Result<HashSet<storage_models::enums::PaymentMethod>, D::Error>
Expand All @@ -125,6 +142,21 @@ where
.map_err(D::Error::custom)
}

fn pm_type_deser<'a, D>(
deserializer: D,
) -> Result<HashSet<storage_models::enums::PaymentMethodType>, D::Error>
where
D: Deserializer<'a>,
{
let value = <String>::deserialize(deserializer)?;
value
.trim()
.split(',')
.map(storage_models::enums::PaymentMethodType::from_str)
.collect::<Result<_, _>>()
.map_err(D::Error::custom)
}

#[derive(Debug, Deserialize, Clone, Default)]
pub struct BankRedirectConfig(
pub HashMap<api_models::enums::PaymentMethodType, ConnectorBankNames>,
Expand Down
13 changes: 9 additions & 4 deletions crates/router/src/connector/adyen/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;

use crate::{
connector::utils::{self, CardData, PaymentsAuthorizeRequestData, RouterData},
connector::utils::{
self, CardData, MandateReferenceData, PaymentsAuthorizeRequestData, RouterData,
},
consts,
core::errors,
pii::{self, Email, Secret},
Expand Down Expand Up @@ -1104,10 +1106,10 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, MandateReferenceId)>
let additional_data = get_additional_data(item);
let return_url = item.request.get_return_url()?;
let payment_method = match mandate_ref_id {
MandateReferenceId::ConnectorMandateId(connector_mandate_id) => {
MandateReferenceId::ConnectorMandateId(connector_mandate_ids) => {
let adyen_mandate = AdyenMandate {
payment_type: PaymentType::Scheme,
stored_payment_method_id: connector_mandate_id,
stored_payment_method_id: connector_mandate_ids.get_connector_mandate_id()?,
};
Ok::<AdyenPaymentMethod<'_>, Self::Error>(AdyenPaymentMethod::Mandate(Box::new(
adyen_mandate,
Expand Down Expand Up @@ -1435,7 +1437,10 @@ pub fn get_adyen_response(
let mandate_reference = response
.additional_data
.as_ref()
.and_then(|additional_data| additional_data.recurring_detail_reference.to_owned());
.map(|data| types::MandateReference {
connector_mandate_id: data.recurring_detail_reference.to_owned(),
payment_method_id: None,
});
let network_txn_id = response
.additional_data
.and_then(|additional_data| additional_data.network_tx_reference);
Expand Down
8 changes: 6 additions & 2 deletions crates/router/src/connector/globalpay/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ fn get_payment_response(
pm.card
.as_ref()
.and_then(|card| card.brand_reference.to_owned())
.map(|id| types::MandateReference {
connector_mandate_id: Some(id),
payment_method_id: None,
})
});
match status {
enums::AttemptStatus::Failure => Err(ErrorResponse {
Expand Down Expand Up @@ -374,8 +378,8 @@ fn get_mandate_details(item: &types::PaymentsAuthorizeRouterData) -> Result<Mand
let connector_mandate_id = item.request.mandate_id.as_ref().and_then(|mandate_ids| {
match mandate_ids.mandate_reference_id.clone() {
Some(api_models::payments::MandateReferenceId::ConnectorMandateId(
connector_mandate_id,
)) => Some(connector_mandate_id),
connector_mandate_ids,
)) => connector_mandate_ids.connector_mandate_id,
_ => None,
}
});
Expand Down
10 changes: 7 additions & 3 deletions crates/router/src/connector/multisafepay/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,8 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for MultisafepayPaymentsReques
.clone()
.and_then(|mandate_ids| match mandate_ids.mandate_reference_id {
Some(api_models::payments::MandateReferenceId::ConnectorMandateId(
connector_mandate_id,
)) => Some(connector_mandate_id),
connector_mandate_ids,
)) => connector_mandate_ids.connector_mandate_id,
_ => None,
}),
days_active: Some(30),
Expand Down Expand Up @@ -479,7 +479,11 @@ impl<F, T>
.response
.data
.payment_details
.and_then(|payment_details| payment_details.recurring_id),
.and_then(|payment_details| payment_details.recurring_id)
.map(|id| types::MandateReference {
connector_mandate_id: Some(id),
payment_method_id: None,
}),
connector_metadata: None,
network_txn_id: None,
}),
Expand Down
9 changes: 8 additions & 1 deletion crates/router/src/connector/nexinets/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,14 @@ impl<F, T>
}
_ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
};
let mandate_reference = item.response.payment_instrument.payment_instrument_id;
let mandate_reference = item
.response
.payment_instrument
.payment_instrument_id
.map(|id| types::MandateReference {
connector_mandate_id: Some(id),
payment_method_id: None,
});
Ok(Self {
status: enums::AttemptStatus::foreign_from((
transaction.status.clone(),
Expand Down
6 changes: 5 additions & 1 deletion crates/router/src/connector/nuvei/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1143,7 +1143,11 @@ where
redirection_data,
mandate_reference: response
.payment_option
.and_then(|po| po.user_payment_option_id),
.and_then(|po| po.user_payment_option_id)
.map(|id| types::MandateReference {
connector_mandate_id: Some(id),
payment_method_id: None,
}),
// we don't need to save session token for capture, void flow so ignoring if it is not present
connector_metadata: if let Some(token) = response.session_token {
Some(
Expand Down
10 changes: 7 additions & 3 deletions crates/router/src/connector/payeezy/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ fn get_transaction_type_and_stored_creds(
let connector_mandate_id = item.request.mandate_id.as_ref().and_then(|mandate_ids| {
match mandate_ids.mandate_reference_id.clone() {
Some(api_models::payments::MandateReferenceId::ConnectorMandateId(
connector_mandate_id,
)) => Some(connector_mandate_id),
connector_mandate_ids,
)) => connector_mandate_ids.connector_mandate_id,
_ => None,
}
});
Expand Down Expand Up @@ -341,7 +341,11 @@ impl<F, T>
let mandate_reference = item
.response
.stored_credentials
.map(|credentials| credentials.cardbrand_original_transaction_id);
.map(|credentials| credentials.cardbrand_original_transaction_id)
.map(|id| types::MandateReference {
connector_mandate_id: Some(id),
payment_method_id: None,
});
let status = enums::AttemptStatus::foreign_from((
item.response.transaction_status,
item.response.transaction_type,
Expand Down
47 changes: 26 additions & 21 deletions crates/router/src/connector/stripe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,29 +489,33 @@ impl
types::PaymentsResponseData: Clone,
{
let id = data.request.connector_transaction_id.clone();
let response: transformers::PaymentIntentSyncResponse = match id
.get_connector_transaction_id()
{
Ok(x) if x.starts_with("set") => res
.response
.parse_struct::<transformers::SetupIntentSyncResponse>("SetupIntentSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)
.map(Into::into),
Ok(_) => res
.response
.parse_struct("PaymentIntentSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed),
match id.get_connector_transaction_id() {
Ok(x) if x.starts_with("set") => {
let response: stripe::SetupIntentResponse = res
.response
.parse_struct("SetupIntentSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}
Ok(_) => {
let response: stripe::PaymentIntentSyncResponse = res
.response
.parse_struct("PaymentIntentSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}
Err(err) => {
Err(err).change_context(errors::ConnectorError::MissingConnectorTransactionID)
}
}?;

types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
}

fn get_error_response(
Expand Down Expand Up @@ -798,7 +802,8 @@ impl
&self,
req: &types::RouterData<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let stripe_req = utils::Encode::<stripe::SetupIntentRequest>::convert_and_url_encode(req)
let req = stripe::SetupIntentRequest::try_from(req)?;
let stripe_req = utils::Encode::<stripe::SetupIntentRequest>::url_encode(&req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(stripe_req))
}
Expand Down
Loading

0 comments on commit 64721b8

Please sign in to comment.