feat(mandates): recurring payment support for bank redirect and bank debit payment method for stripe (#1119)

Co-authored-by: pixincreate@work <69745008+pixincreate@users.noreply.github.com>
This commit is contained in:
Hrithikesh
2023-07-17 18:19:31 +05:30
committed by GitHub
parent de2d9bd059
commit 14c2d72509
20 changed files with 385 additions and 103 deletions

View File

@ -593,10 +593,11 @@ impl
x
)),
Ok(x) => Ok(format!(
"{}{}/{}",
"{}{}/{}{}",
self.base_url(connectors),
"v1/payment_intents",
x
x,
"?expand[0]=latest_charge" //updated payment_id(if present) reside inside latest_charge field
)),
x => x.change_context(errors::ConnectorError::MissingConnectorTransactionID),
}
@ -1883,7 +1884,8 @@ impl services::ConnectorRedirectResponse for Stripe {
.map_or(
payments::CallConnectorAction::Trigger,
|status| match status {
transformers::StripePaymentStatus::Failed => {
transformers::StripePaymentStatus::Failed
| transformers::StripePaymentStatus::Pending => {
payments::CallConnectorAction::Trigger
}
_ => payments::CallConnectorAction::StatusUpdate {

View File

@ -70,19 +70,24 @@ pub enum Auth3ds {
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
#[serde(
rename_all = "snake_case",
tag = "mandate_data[customer_acceptance][type]"
)]
pub enum StripeMandateType {
Online,
Online {
#[serde(rename = "mandate_data[customer_acceptance][online][ip_address]")]
ip_address: Secret<String, pii::IpAddress>,
#[serde(rename = "mandate_data[customer_acceptance][online][user_agent]")]
user_agent: String,
},
Offline,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
pub struct StripeMandateRequest {
#[serde(rename = "mandate_data[customer_acceptance][type]")]
pub mandate_type: StripeMandateType,
#[serde(rename = "mandate_data[customer_acceptance][online][ip_address]")]
pub ip_address: Secret<String, pii::IpAddress>,
#[serde(rename = "mandate_data[customer_acceptance][online][user_agent]")]
pub user_agent: String,
#[serde(flatten)]
mandate_type: StripeMandateType,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
@ -115,6 +120,8 @@ pub struct PaymentIntentRequest {
pub payment_method_options: Option<StripePaymentMethodOptions>, // For mandate txns using network_txns_id, needs to be validated
pub setup_future_usage: Option<enums::FutureUsage>,
pub off_session: Option<bool>,
#[serde(rename = "payment_method_types[0]")]
pub payment_method_types: Option<StripePaymentMethodType>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
@ -137,8 +144,6 @@ pub struct SetupIntentRequest {
#[derive(Debug, Eq, PartialEq, Serialize)]
pub struct StripeCardData {
#[serde(rename = "payment_method_types[]")]
pub payment_method_types: StripePaymentMethodType,
#[serde(rename = "payment_method_data[type]")]
pub payment_method_data_type: StripePaymentMethodType,
#[serde(rename = "payment_method_data[card][number]")]
@ -154,8 +159,6 @@ pub struct StripeCardData {
}
#[derive(Debug, Eq, PartialEq, Serialize)]
pub struct StripePayLaterData {
#[serde(rename = "payment_method_types[]")]
pub payment_method_types: StripePaymentMethodType,
#[serde(rename = "payment_method_data[type]")]
pub payment_method_data_type: StripePaymentMethodType,
}
@ -277,8 +280,6 @@ fn get_bank_name(
#[derive(Debug, Eq, PartialEq, Serialize)]
pub struct StripeBankRedirectData {
#[serde(rename = "payment_method_types[]")]
pub payment_method_types: StripePaymentMethodType,
#[serde(rename = "payment_method_data[type]")]
pub payment_method_data_type: StripePaymentMethodType,
// Required only for eps and ideal
@ -403,8 +404,6 @@ pub enum BankDebitData {
#[derive(Debug, Eq, PartialEq, Serialize)]
pub struct StripeBankDebitData {
#[serde(rename = "payment_method_types[]")]
pub payment_method_types: StripePaymentMethodType,
#[serde(flatten)]
pub bank_specific_data: BankDebitData,
}
@ -459,16 +458,12 @@ pub struct ApplepayPayment {
#[derive(Debug, Eq, PartialEq, Serialize)]
pub struct AlipayPayment {
#[serde(rename = "payment_method_types[]")]
pub payment_method_types: StripePaymentMethodType,
#[serde(rename = "payment_method_data[type]")]
pub payment_method_data_type: StripePaymentMethodType,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
pub struct WechatpayPayment {
#[serde(rename = "payment_method_types[]")]
pub payment_method_types: StripePaymentMethodType,
#[serde(rename = "payment_method_data[type]")]
pub payment_method_data_type: StripePaymentMethodType,
#[serde(rename = "payment_method_options[wechat_pay][client]")]
@ -520,6 +515,36 @@ pub enum StripePaymentMethodType {
Multibanco,
}
impl TryFrom<enums::PaymentMethodType> for StripePaymentMethodType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(value: enums::PaymentMethodType) -> Result<Self, Self::Error> {
match value {
enums::PaymentMethodType::Credit => Ok(Self::Card),
enums::PaymentMethodType::Debit => Ok(Self::Card),
enums::PaymentMethodType::Klarna => Ok(Self::Klarna),
enums::PaymentMethodType::Affirm => Ok(Self::Affirm),
enums::PaymentMethodType::AfterpayClearpay => Ok(Self::AfterpayClearpay),
enums::PaymentMethodType::Eps => Ok(Self::Eps),
enums::PaymentMethodType::Giropay => Ok(Self::Giropay),
enums::PaymentMethodType::Ideal => Ok(Self::Ideal),
enums::PaymentMethodType::Sofort => Ok(Self::Sofort),
enums::PaymentMethodType::ApplePay => Ok(Self::ApplePay),
enums::PaymentMethodType::Ach => Ok(Self::Ach),
enums::PaymentMethodType::Sepa => Ok(Self::Sepa),
enums::PaymentMethodType::Becs => Ok(Self::Becs),
enums::PaymentMethodType::Bacs => Ok(Self::Bacs),
enums::PaymentMethodType::BancontactCard => Ok(Self::Bancontact),
enums::PaymentMethodType::WeChatPay => Ok(Self::Wechatpay),
enums::PaymentMethodType::AliPay => Ok(Self::Alipay),
enums::PaymentMethodType::Przelewy24 => Ok(Self::Przelewy24),
_ => Err(errors::ConnectorError::NotImplemented(
"this payment method for stripe".to_string(),
)
.into()),
}
}
}
#[derive(Debug, Eq, PartialEq, Serialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum BankTransferType {
@ -873,6 +898,7 @@ impl TryFrom<&payments::BankRedirectData> for StripeBillingAddress {
billing_details, ..
} => Ok(Self {
name: billing_details.billing_name.clone(),
email: billing_details.email.clone(),
..Self::default()
}),
payments::BankRedirectData::Przelewy24 {
@ -883,14 +909,39 @@ impl TryFrom<&payments::BankRedirectData> for StripeBillingAddress {
}),
payments::BankRedirectData::BancontactCard {
billing_details, ..
} => {
let billing_details = billing_details.as_ref().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "billing_details",
},
)?;
Ok(Self {
name: Some(
billing_details
.billing_name
.as_ref()
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "billing_details.billing_name",
})?
.to_owned(),
),
email: Some(
billing_details
.email
.as_ref()
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "billing_details.email",
})?
.to_owned(),
),
..Self::default()
})
}
payments::BankRedirectData::Sofort {
billing_details, ..
} => Ok(Self {
name: billing_details
.as_ref()
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "bancontact_card.billing_name",
})?
.billing_name
.clone(),
name: billing_details.billing_name.clone(),
email: billing_details.email.clone(),
..Self::default()
}),
_ => Ok(Self::default()),
@ -996,7 +1047,6 @@ fn create_stripe_payment_method(
};
Ok((
StripePaymentMethodData::Card(StripeCardData {
payment_method_types: StripePaymentMethodType::Card,
payment_method_data_type: StripePaymentMethodType::Card,
payment_method_data_card_number: card_details.card_number.clone(),
payment_method_data_card_exp_month: card_details.card_exp_month.clone(),
@ -1023,7 +1073,6 @@ fn create_stripe_payment_method(
Ok((
StripePaymentMethodData::PayLater(StripePayLaterData {
payment_method_types: stripe_pm_type,
payment_method_data_type: stripe_pm_type,
}),
stripe_pm_type,
@ -1038,7 +1087,6 @@ fn create_stripe_payment_method(
Ok((
StripePaymentMethodData::BankRedirect(StripeBankRedirectData {
payment_method_types: pm_type,
payment_method_data_type: pm_type,
bank_name,
bank_specific_data,
@ -1063,7 +1111,6 @@ fn create_stripe_payment_method(
payments::WalletData::WeChatPay(_) => Ok((
StripePaymentMethodData::Wallet(StripeWallet::WechatpayPayment(WechatpayPayment {
client: WechatClient::Web,
payment_method_types: StripePaymentMethodType::Wechatpay,
payment_method_data_type: StripePaymentMethodType::Wechatpay,
})),
StripePaymentMethodType::Wechatpay,
@ -1071,7 +1118,6 @@ fn create_stripe_payment_method(
)),
payments::WalletData::AliPayRedirect(_) => Ok((
StripePaymentMethodData::Wallet(StripeWallet::AlipayPayment(AlipayPayment {
payment_method_types: StripePaymentMethodType::Alipay,
payment_method_data_type: StripePaymentMethodType::Alipay,
})),
StripePaymentMethodType::Alipay,
@ -1091,7 +1137,6 @@ fn create_stripe_payment_method(
let (pm_type, bank_debit_data, billing_address) = get_bank_debit_data(bank_debit_data);
let pm_data = StripePaymentMethodData::BankDebit(StripeBankDebitData {
payment_method_types: pm_type,
bank_specific_data: bank_debit_data,
});
@ -1232,7 +1277,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
};
let mut payment_method_options = None;
let (mut payment_data, payment_method, mandate, billing_address) = {
let (mut payment_data, payment_method, mandate, billing_address, payment_method_types) = {
match item
.request
.mandate_id
@ -1246,6 +1291,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
connector_mandate_ids.payment_method_id,
connector_mandate_ids.connector_mandate_id,
StripeBillingAddress::default(),
None,
),
Some(api_models::payments::MandateReferenceId::NetworkMandateId(
network_transaction_id,
@ -1257,7 +1303,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
network_transaction_id: Secret::new(network_transaction_id),
}),
});
(None, None, None, StripeBillingAddress::default())
(None, None, None, StripeBillingAddress::default(), None)
}
_ => {
let (payment_method_data, payment_method_type, billing_address) =
@ -1273,11 +1319,26 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
&payment_method_type,
)?;
(Some(payment_method_data), None, None, billing_address)
(
Some(payment_method_data),
None,
None,
billing_address,
Some(payment_method_type),
)
}
}
};
//if payment_method is Some(), That means, it is a recurring mandate payment.
//payment_method_types will not be not be available in router_data.request. But stripe requires that field.
//here we will get that field from router_data.recurring_mandate_payment_data
let payment_method_types = if payment_method.is_some() {
//if recurring payment get payment_method_type
get_payment_method_type_for_saved_payment_method_payment(item).map(Some)?
} else {
payment_method_types
};
payment_data = match item.request.payment_method_data {
payments::PaymentMethodData::Wallet(payments::WalletData::ApplePay(_)) => Some(
StripePaymentMethodData::Wallet(StripeWallet::ApplepayPayment(ApplepayPayment {
@ -1300,25 +1361,62 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
.and_then(|mandate_details| {
mandate_details
.customer_acceptance
.as_ref()?
.online
.as_ref()
.map(|online_details| {
Ok::<_, error_stack::Report<errors::ConnectorError>>(StripeMandateRequest {
mandate_type: StripeMandateType::Online,
ip_address: online_details
.ip_address
.clone()
.get_required_value("ip_address")
.change_context(errors::ConnectorError::MissingRequiredField {
field_name: "ip_address",
})?,
user_agent: online_details.user_agent.to_owned(),
})
.map(|customer_acceptance| {
Ok::<_, error_stack::Report<errors::ConnectorError>>(
match customer_acceptance.acceptance_type {
payments::AcceptanceType::Online => {
let online_mandate = customer_acceptance
.online
.clone()
.get_required_value("online")
.change_context(
errors::ConnectorError::MissingRequiredField {
field_name: "online",
},
)?;
StripeMandateRequest {
mandate_type: StripeMandateType::Online {
ip_address: online_mandate
.ip_address
.get_required_value("ip_address")
.change_context(
errors::ConnectorError::MissingRequiredField {
field_name: "ip_address",
},
)?,
user_agent: online_mandate.user_agent,
},
}
}
payments::AcceptanceType::Offline => StripeMandateRequest {
mandate_type: StripeMandateType::Offline,
},
},
)
})
})
.transpose()?;
.transpose()?
.or_else(|| {
//stripe requires us to send mandate_data while making recurring payment through saved bank debit
if payment_method.is_some() {
//check if payment is done through saved payment method
match &payment_method_types {
//check if payment method is bank debit
Some(
StripePaymentMethodType::Ach
| StripePaymentMethodType::Sepa
| StripePaymentMethodType::Becs
| StripePaymentMethodType::Bacs,
) => Some(StripeMandateRequest {
mandate_type: StripeMandateType::Offline,
}),
_ => None,
}
} else {
None
}
});
Ok(Self {
amount: item.request.amount, //hopefully we don't loose some cents here
currency: item.request.currency.to_string(), //we need to copy the value and not transfer ownership
@ -1345,10 +1443,38 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
setup_mandate_details,
off_session: item.request.off_session,
setup_future_usage: item.request.setup_future_usage,
payment_method_types,
})
}
}
fn get_payment_method_type_for_saved_payment_method_payment(
item: &types::PaymentsAuthorizeRouterData,
) -> Result<StripePaymentMethodType, error_stack::Report<errors::ConnectorError>> {
let stripe_payment_method_type = match item.recurring_mandate_payment_data.clone() {
Some(recurring_payment_method_data) => {
match recurring_payment_method_data.payment_method_type {
Some(payment_method_type) => StripePaymentMethodType::try_from(payment_method_type),
None => Err(errors::ConnectorError::MissingRequiredField {
field_name: "payment_method_type",
}
.into()),
}
}
None => Err(errors::ConnectorError::MissingRequiredField {
field_name: "recurring_mandate_payment_data",
}
.into()),
}?;
match stripe_payment_method_type {
//Stripe converts Ideal, Bancontact & Sofort Bank redirect methods to Sepa direct debit and attaches to the customer for future usage
StripePaymentMethodType::Ideal
| StripePaymentMethodType::Bancontact
| StripePaymentMethodType::Sofort => Ok(StripePaymentMethodType::Sepa),
_ => Ok(stripe_payment_method_type),
}
}
impl TryFrom<&types::VerifyRouterData> for SetupIntentRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::VerifyRouterData) -> Result<Self, Self::Error> {
@ -1552,6 +1678,19 @@ pub struct PaymentIntentSyncResponse {
#[serde(flatten)]
payment_intent_fields: PaymentIntentResponse,
pub last_payment_error: Option<LastPaymentError>,
pub latest_charge: Option<StripeCharge>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct StripeCharge {
pub id: String,
pub payment_method_details: Option<StripePaymentMethodDetailsResponse>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct StripeBankRedirectDetails {
#[serde(rename = "generated_sepa_debit")]
attached_payment_method: Option<String>,
}
impl Deref for PaymentIntentSyncResponse {
@ -1562,6 +1701,45 @@ impl Deref for PaymentIntentSyncResponse {
}
}
#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum StripePaymentMethodDetailsResponse {
//only ideal, sofort and bancontact is supported by stripe for recurring payment in bank redirect
Ideal {
ideal: StripeBankRedirectDetails,
},
Sofort {
sofort: StripeBankRedirectDetails,
},
Bancontact {
bancontact: StripeBankRedirectDetails,
},
//other payment method types supported by stripe. To avoid deserialization error.
Blik,
Eps,
Fpx,
Giropay,
#[serde(rename = "p24")]
Przelewy24,
Card,
Klarna,
Affirm,
AfterpayClearpay,
ApplePay,
#[serde(rename = "us_bank_account")]
Ach,
#[serde(rename = "sepa_debit")]
Sepa,
#[serde(rename = "au_becs_debit")]
Becs,
#[serde(rename = "bacs_debit")]
Bacs,
#[serde(rename = "wechat_pay")]
Wechatpay,
Alipay,
}
#[derive(Deserialize)]
pub struct SetupIntentSyncResponse {
#[serde(flatten)]
@ -1582,6 +1760,7 @@ impl From<SetupIntentSyncResponse> for PaymentIntentSyncResponse {
Self {
payment_intent_fields: value.setup_intent_fields.into(),
last_payment_error: value.last_payment_error,
latest_charge: None,
}
}
}
@ -1772,7 +1951,21 @@ impl<F, T>
let mandate_reference = item.response.payment_method.clone().map(|pm| {
types::MandateReference::foreign_from((
item.response.payment_method_options.clone(),
pm,
match item.response.latest_charge.clone() {
Some(charge) => match charge.payment_method_details {
Some(StripePaymentMethodDetailsResponse::Bancontact { bancontact }) => {
bancontact.attached_payment_method.unwrap_or(pm)
}
Some(StripePaymentMethodDetailsResponse::Ideal { ideal }) => {
ideal.attached_payment_method.unwrap_or(pm)
}
Some(StripePaymentMethodDetailsResponse::Sofort { sofort }) => {
sofort.attached_payment_method.unwrap_or(pm)
}
_ => pm,
},
None => pm,
},
))
});
let error_res =
@ -2583,7 +2776,6 @@ impl
enums::AuthenticationType::NoThreeDs => Auth3ds::Automatic,
};
StripeCardData {
payment_method_types: StripePaymentMethodType::Card,
payment_method_data_type: StripePaymentMethodType::Card,
payment_method_data_card_number: ccard.card_number.clone(),
payment_method_data_card_exp_month: ccard.card_exp_month.clone(),
@ -2593,12 +2785,10 @@ impl
}
})),
api::PaymentMethodData::PayLater(_) => Ok(Self::PayLater(StripePayLaterData {
payment_method_types: pm_type,
payment_method_data_type: pm_type,
})),
api::PaymentMethodData::BankRedirect(_) => {
Ok(Self::BankRedirect(StripeBankRedirectData {
payment_method_types: pm_type,
payment_method_data_type: pm_type,
bank_name: None,
bank_specific_data: None,
@ -2620,14 +2810,12 @@ impl
payments::WalletData::WeChatPayRedirect(_) => {
let wallet_info = StripeWallet::WechatpayPayment(WechatpayPayment {
client: WechatClient::Web,
payment_method_types: StripePaymentMethodType::Wechatpay,
payment_method_data_type: StripePaymentMethodType::Wechatpay,
});
Ok(Self::Wallet(wallet_info))
}
payments::WalletData::AliPayRedirect(_) => {
let wallet_info = StripeWallet::AlipayPayment(AlipayPayment {
payment_method_types: StripePaymentMethodType::Alipay,
payment_method_data_type: StripePaymentMethodType::Alipay,
});
Ok(Self::Wallet(wallet_info))
@ -2636,10 +2824,9 @@ impl
_ => Err(errors::ConnectorError::InvalidWallet.into()),
},
api::PaymentMethodData::BankDebit(bank_debit_data) => {
let (pm_type, bank_data, _) = get_bank_debit_data(&bank_debit_data);
let (_pm_type, bank_data, _) = get_bank_debit_data(&bank_debit_data);
Ok(Self::BankDebit(StripeBankDebitData {
payment_method_types: pm_type,
bank_specific_data: bank_data,
}))
}

View File

@ -1,3 +1,4 @@
use api_models::payments;
use common_utils::{ext_traits::Encode, pii};
use diesel_models::enums as storage_enums;
use error_stack::{report, ResultExt};
@ -123,6 +124,18 @@ pub async fn get_customer_mandates(
}
}
fn get_insensitive_payment_method_data_if_exists<F, FData>(
router_data: &types::RouterData<F, FData, types::PaymentsResponseData>,
) -> Option<payments::PaymentMethodData>
where
FData: MandateBehaviour,
{
match &router_data.request.get_payment_method_data() {
api_models::payments::PaymentMethodData::Card(_) => None,
_ => Some(router_data.request.get_payment_method_data()),
}
}
pub async fn mandate_procedure<F, FData>(
state: &AppState,
mut resp: types::RouterData<F, FData, types::PaymentsResponseData>,
@ -209,6 +222,7 @@ where
pm_id.get_required_value("payment_method_id")?,
mandate_ids,
network_txn_id,
get_insensitive_payment_method_data_if_exists(&resp),
)? {
let connector = new_mandate_data.connector.clone();
logger::debug!("{:?}", new_mandate_data);

View File

@ -1006,7 +1006,6 @@ where
.to_domain()?
.make_pm_data(state, &mut payment_data, validate_result.storage_scheme)
.await?;
payment_data.payment_method_data = payment_method_data;
TokenizationAction::SkipConnectorTokenization
}
@ -1036,7 +1035,6 @@ where
.to_domain()?
.make_pm_data(state, &mut payment_data, validate_result.storage_scheme)
.await?;
payment_data.payment_method_data = payment_method_data;
(payment_data, TokenizationAction::SkipConnectorTokenization)
}
@ -1091,10 +1089,16 @@ where
pub creds_identifier: Option<String>,
pub pm_token: Option<String>,
pub connector_customer_id: Option<String>,
pub recurring_mandate_payment_data: Option<RecurringMandatePaymentData>,
pub ephemeral_key: Option<ephemeral_key::EphemeralKey>,
pub redirect_response: Option<api_models::payments::RedirectResponse>,
}
#[derive(Debug, Default, Clone)]
pub struct RecurringMandatePaymentData {
pub payment_method_type: Option<storage_enums::PaymentMethodType>, //required for making recurring payment using saved payment method through stripe
}
#[derive(Debug, Default)]
pub struct CustomerDetails {
pub customer_id: Option<String>,

View File

@ -273,6 +273,7 @@ pub async fn get_token_pm_type_mandate_details(
Option<storage_enums::PaymentMethod>,
Option<storage_enums::PaymentMethodType>,
Option<api::MandateData>,
Option<payments::RecurringMandatePaymentData>,
Option<String>,
)> {
match mandate_type {
@ -287,16 +288,23 @@ pub async fn get_token_pm_type_mandate_details(
request.payment_method_type,
Some(setup_mandate),
None,
None,
))
}
Some(api::MandateTransactionType::RecurringMandateTransaction) => {
let (token_, payment_method_, payment_method_type_, mandate_connector) =
get_token_for_recurring_mandate(state, request, merchant_account).await?;
let (
token_,
payment_method_,
recurring_mandate_payment_data,
payment_method_type_,
mandate_connector,
) = get_token_for_recurring_mandate(state, request, merchant_account).await?;
Ok((
token_,
payment_method_,
payment_method_type_.or(request.payment_method_type),
None,
recurring_mandate_payment_data,
mandate_connector,
))
}
@ -306,6 +314,7 @@ pub async fn get_token_pm_type_mandate_details(
request.payment_method_type,
request.mandate_data.clone(),
None,
None,
)),
}
}
@ -317,6 +326,7 @@ pub async fn get_token_for_recurring_mandate(
) -> RouterResult<(
Option<String>,
Option<storage_enums::PaymentMethod>,
Option<payments::RecurringMandatePaymentData>,
Option<storage_enums::PaymentMethodType>,
Option<String>,
)> {
@ -355,6 +365,7 @@ pub async fn get_token_for_recurring_mandate(
.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;
let token = Uuid::new_v4().to_string();
let payment_method_type = payment_method.payment_method_type;
if let diesel_models::enums::PaymentMethod::Card = payment_method.payment_method {
let _ = cards::get_lookup_key_from_locker(state, &token, &payment_method).await?;
if let Some(payment_method_from_request) = req.payment_method {
@ -372,6 +383,9 @@ pub async fn get_token_for_recurring_mandate(
Ok((
Some(token),
Some(payment_method.payment_method),
Some(payments::RecurringMandatePaymentData {
payment_method_type,
}),
payment_method.payment_method_type,
Some(mandate.connector),
))
@ -379,6 +393,9 @@ pub async fn get_token_for_recurring_mandate(
Ok((
None,
Some(payment_method.payment_method),
Some(payments::RecurringMandatePaymentData {
payment_method_type,
}),
payment_method.payment_method_type,
Some(mandate.connector),
))
@ -1612,6 +1629,7 @@ pub fn check_if_operation_confirm<Op: std::fmt::Debug>(operations: Op) -> bool {
format!("{operations:?}") == "PaymentConfirm"
}
#[allow(clippy::too_many_arguments)]
pub fn generate_mandate(
merchant_id: String,
connector: String,
@ -1620,6 +1638,7 @@ pub fn generate_mandate(
payment_method_id: String,
connector_mandate_id: Option<pii::SecretSerdeValue>,
network_txn_id: Option<String>,
payment_method_data_option: Option<api_models::payments::PaymentMethodData>,
) -> CustomResult<Option<storage::MandateNew>, errors::ApiErrorResponse> {
match (setup_mandate_details, customer) {
(Some(data), Some(cus)) => {
@ -1646,7 +1665,12 @@ pub fn generate_mandate(
.map(masking::Secret::new),
)
.set_customer_user_agent(customer_acceptance.get_user_agent())
.set_customer_accepted_at(Some(customer_acceptance.get_accepted_at()));
.set_customer_accepted_at(Some(customer_acceptance.get_accepted_at()))
.set_metadata(payment_method_data_option.map(|payment_method_data| {
pii::SecretSerdeValue::new(
serde_json::to_value(payment_method_data).unwrap_or_default(),
)
}));
Ok(Some(
match data.mandate_type.get_required_value("mandate_type")? {
@ -1661,8 +1685,9 @@ pub fn generate_mandate(
.set_mandate_amount(Some(data.amount))
.set_mandate_currency(Some(data.currency))
.set_start_date(data.start_date)
.set_end_date(data.end_date)
.set_metadata(data.metadata),
.set_end_date(data.end_date),
// .set_metadata(data.metadata),
// we are storing PaymentMethodData in metadata of mandate
None => &mut new_mandate,
}
.set_mandate_type(storage_enums::MandateType::MultiUse)
@ -2160,6 +2185,7 @@ pub fn router_data_type_conversion<F1, F2, Req1, Req2, Res1, Res2>(
customer_id: router_data.customer_id,
connector_customer: router_data.connector_customer,
preprocessing_id: router_data.preprocessing_id,
recurring_mandate_payment_data: router_data.recurring_mandate_payment_data,
connector_request_reference_id: router_data.connector_request_reference_id,
test_mode: router_data.test_mode,
}

View File

@ -159,6 +159,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsCancelRequest>
creds_identifier,
pm_token: None,
connector_customer_id: None,
recurring_mandate_payment_data: None,
ephemeral_key: None,
redirect_response: None,
},

View File

@ -164,6 +164,7 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, api::PaymentsCaptu
creds_identifier,
pm_token: None,
connector_customer_id: None,
recurring_mandate_payment_data: None,
ephemeral_key: None,
redirect_response: None,
},

View File

@ -72,14 +72,20 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co
"confirm",
)?;
let (token, payment_method, payment_method_type, setup_mandate, mandate_connector) =
helpers::get_token_pm_type_mandate_details(
state,
request,
mandate_type.clone(),
merchant_account,
)
.await?;
let (
token,
payment_method,
payment_method_type,
setup_mandate,
recurring_mandate_payment_data,
mandate_connector,
) = helpers::get_token_pm_type_mandate_details(
state,
request,
mandate_type.clone(),
merchant_account,
)
.await?;
let browser_info = request
.browser_info
@ -225,6 +231,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co
creds_identifier: None,
pm_token: None,
connector_customer_id: None,
recurring_mandate_payment_data,
ephemeral_key: None,
redirect_response,
},

View File

@ -107,14 +107,20 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
.setup_future_usage
.or(payment_intent.setup_future_usage);
let (token, payment_method, payment_method_type, setup_mandate, mandate_connector) =
helpers::get_token_pm_type_mandate_details(
state,
request,
mandate_type.clone(),
merchant_account,
)
.await?;
let (
token,
payment_method,
payment_method_type,
setup_mandate,
recurring_mandate_payment_data,
mandate_connector,
) = helpers::get_token_pm_type_mandate_details(
state,
request,
mandate_type.clone(),
merchant_account,
)
.await?;
let browser_info = request
.browser_info
@ -281,6 +287,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
creds_identifier,
pm_token: None,
connector_customer_id: None,
recurring_mandate_payment_data,
ephemeral_key: None,
redirect_response: None,
},

View File

@ -64,14 +64,20 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
.get_payment_intent_id()
.change_context(errors::ApiErrorResponse::PaymentNotFound)?;
let (token, payment_method, payment_method_type, setup_mandate, mandate_connector) =
helpers::get_token_pm_type_mandate_details(
state,
request,
mandate_type,
merchant_account,
)
.await?;
let (
token,
payment_method,
payment_method_type,
setup_mandate,
recurring_mandate_payment_data,
mandate_connector,
) = helpers::get_token_pm_type_mandate_details(
state,
request,
mandate_type,
merchant_account,
)
.await?;
let customer_details = helpers::get_customer_details_from_request(request);
@ -260,6 +266,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
creds_identifier,
pm_token: None,
connector_customer_id: None,
recurring_mandate_payment_data,
ephemeral_key,
redirect_response: None,
},

View File

@ -185,6 +185,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::VerifyRequest> for Paym
creds_identifier,
pm_token: None,
connector_customer_id: None,
recurring_mandate_payment_data: None,
ephemeral_key: None,
redirect_response: None,
},

View File

@ -177,6 +177,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest>
creds_identifier,
pm_token: None,
connector_customer_id: None,
recurring_mandate_payment_data: None,
ephemeral_key: None,
redirect_response: None,
},

View File

@ -149,6 +149,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f
creds_identifier: None,
pm_token: None,
connector_customer_id: None,
recurring_mandate_payment_data: None,
ephemeral_key: None,
redirect_response: None,
},

View File

@ -321,6 +321,7 @@ async fn get_tracker_for_sync<
creds_identifier,
pm_token: None,
connector_customer_id: None,
recurring_mandate_payment_data: None,
ephemeral_key: None,
redirect_response: None,
},

View File

@ -79,14 +79,20 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
"update",
)?;
let (token, payment_method, payment_method_type, setup_mandate, mandate_connector) =
helpers::get_token_pm_type_mandate_details(
state,
request,
mandate_type.clone(),
merchant_account,
)
.await?;
let (
token,
payment_method,
payment_method_type,
setup_mandate,
recurring_mandate_payment_data,
mandate_connector,
) = helpers::get_token_pm_type_mandate_details(
state,
request,
mandate_type.clone(),
merchant_account,
)
.await?;
payment_intent = db
.find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme)
@ -329,6 +335,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
creds_identifier,
pm_token: None,
connector_customer_id: None,
recurring_mandate_payment_data,
ephemeral_key: None,
redirect_response: None,
},

View File

@ -126,6 +126,7 @@ where
reference_id: None,
payment_method_token: payment_data.pm_token,
connector_customer: payment_data.connector_customer_id,
recurring_mandate_payment_data: payment_data.recurring_mandate_payment_data,
connector_request_reference_id: core_utils::get_connector_request_reference_id(
&state.conf,
&merchant_account.merchant_id,

View File

@ -113,6 +113,7 @@ pub async fn construct_refund_router_data<'a, F>(
reference_id: None,
payment_method_token: None,
connector_customer: None,
recurring_mandate_payment_data: None,
preprocessing_id: None,
connector_request_reference_id: get_connector_request_reference_id(
&state.conf,
@ -307,6 +308,7 @@ pub async fn construct_accept_dispute_router_data<'a>(
payment_method_token: None,
connector_customer: None,
customer_id: None,
recurring_mandate_payment_data: None,
preprocessing_id: None,
connector_request_reference_id: get_connector_request_reference_id(
&state.conf,
@ -375,6 +377,7 @@ pub async fn construct_submit_evidence_router_data<'a>(
payment_method_token: None,
connector_customer: None,
customer_id: None,
recurring_mandate_payment_data: None,
preprocessing_id: None,
connector_request_reference_id: get_connector_request_reference_id(
&state.conf,
@ -444,6 +447,7 @@ pub async fn construct_upload_file_router_data<'a>(
payment_method_token: None,
connector_customer: None,
customer_id: None,
recurring_mandate_payment_data: None,
preprocessing_id: None,
connector_request_reference_id: get_connector_request_reference_id(
&state.conf,
@ -515,6 +519,7 @@ pub async fn construct_defend_dispute_router_data<'a>(
payment_method_token: None,
customer_id: None,
connector_customer: None,
recurring_mandate_payment_data: None,
preprocessing_id: None,
connector_request_reference_id: get_connector_request_reference_id(
&state.conf,
@ -584,6 +589,7 @@ pub async fn construct_retrieve_file_router_data<'a>(
session_token: None,
reference_id: None,
payment_method_token: None,
recurring_mandate_payment_data: None,
preprocessing_id: None,
connector_request_reference_id: IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_DISPUTE_FLOW
.to_string(),

View File

@ -20,7 +20,10 @@ use masking::Secret;
use self::{api::payments, storage::enums as storage_enums};
pub use crate::core::payments::PaymentAddress;
use crate::{core::errors, services};
use crate::{
core::{errors, payments::RecurringMandatePaymentData},
services,
};
pub type PaymentsAuthorizeRouterData =
RouterData<api::Authorize, PaymentsAuthorizeData, PaymentsResponseData>;
@ -194,6 +197,7 @@ pub struct RouterData<Flow, Request, Response> {
pub session_token: Option<String>,
pub reference_id: Option<String>,
pub payment_method_token: Option<String>,
pub recurring_mandate_payment_data: Option<RecurringMandatePaymentData>,
pub preprocessing_id: Option<String>,
/// Contains flow-specific data required to construct a request and send it to the connector.
@ -820,6 +824,7 @@ impl<F1, F2, T1, T2> From<(&RouterData<F1, T1, PaymentsResponseData>, T2)>
payment_method_token: None,
preprocessing_id: None,
connector_customer: data.connector_customer.clone(),
recurring_mandate_payment_data: data.recurring_mandate_payment_data.clone(),
connector_request_reference_id: data.connector_request_reference_id.clone(),
test_mode: data.test_mode,
}

View File

@ -79,6 +79,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
reference_id: None,
payment_method_token: None,
connector_customer: None,
recurring_mandate_payment_data: None,
preprocessing_id: None,
connector_request_reference_id: uuid::Uuid::new_v4().to_string(),
test_mode: None,
@ -125,6 +126,7 @@ fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
reference_id: None,
payment_method_token: None,
connector_customer: None,
recurring_mandate_payment_data: None,
preprocessing_id: None,
connector_request_reference_id: uuid::Uuid::new_v4().to_string(),
test_mode: None,

View File

@ -402,6 +402,7 @@ pub trait ConnectorActions: Connector {
reference_id: None,
payment_method_token: None,
connector_customer: None,
recurring_mandate_payment_data: None,
preprocessing_id: None,
connector_request_reference_id: uuid::Uuid::new_v4().to_string(),
test_mode: None,