Feat(connector): [BRAINTREE] Implement Card Mandates (#5204)

This commit is contained in:
awasthi21
2024-07-05 13:37:46 +05:30
committed by GitHub
parent 00f9ed4cae
commit 1904ffad88
13 changed files with 246 additions and 49 deletions

View File

@ -135,8 +135,8 @@ connectors_with_delayed_session_response = "trustpay,payme" # List of connec
bank_debit.ach.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit bank_debit.ach.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit
bank_debit.becs.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit bank_debit.becs.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit
bank_debit.sepa.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit bank_debit.sepa.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit
card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" # Mandate supported payment method type and connector for card card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card
card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" # Mandate supported payment method type and connector for card card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card
pay_later.klarna.connector_list = "adyen" # Mandate supported payment method type and connector for pay_later pay_later.klarna.connector_list = "adyen" # Mandate supported payment method type and connector for pay_later
wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" # Mandate supported payment method type and connector for wallets wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" # Mandate supported payment method type and connector for wallets
wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" # Mandate supported payment method type and connector for wallets wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" # Mandate supported payment method type and connector for wallets

View File

@ -135,8 +135,8 @@ enabled = false
bank_debit.ach.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit bank_debit.ach.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit
bank_debit.becs.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit bank_debit.becs.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit
bank_debit.sepa.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit bank_debit.sepa.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit
card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" # Mandate supported payment method type and connector for card card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card
card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" # Mandate supported payment method type and connector for card card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card
pay_later.klarna.connector_list = "adyen" # Mandate supported payment method type and connector for pay_later pay_later.klarna.connector_list = "adyen" # Mandate supported payment method type and connector for pay_later
wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" # Mandate supported payment method type and connector for wallets wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" # Mandate supported payment method type and connector for wallets
wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" # Mandate supported payment method type and connector for wallets wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" # Mandate supported payment method type and connector for wallets

View File

@ -135,8 +135,8 @@ enabled = true
bank_debit.ach.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit bank_debit.ach.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit
bank_debit.becs.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit bank_debit.becs.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit
bank_debit.sepa.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit bank_debit.sepa.connector_list = "gocardless" # Mandate supported payment method type and connector for bank_debit
card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" # Mandate supported payment method type and connector for card card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card
card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" # Mandate supported payment method type and connector for card card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card
pay_later.klarna.connector_list = "adyen" # Mandate supported payment method type and connector for pay_later pay_later.klarna.connector_list = "adyen" # Mandate supported payment method type and connector for pay_later
wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" # Mandate supported payment method type and connector for wallets wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" # Mandate supported payment method type and connector for wallets
wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" # Mandate supported payment method type and connector for wallets wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" # Mandate supported payment method type and connector for wallets

View File

@ -410,8 +410,6 @@ google_pay = { currency = "USD" }
[pm_filters.braintree] [pm_filters.braintree]
paypal = { currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" } paypal = { currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" }
credit = { not_available_flows = { capture_method = "manual" } }
debit = { not_available_flows = { capture_method = "manual" } }
[pm_filters.helcim] [pm_filters.helcim]
credit = { currency = "USD" } credit = { currency = "USD" }
@ -536,8 +534,8 @@ pay_later.klarna = { connector_list = "adyen" }
wallet.google_pay = { connector_list = "stripe,adyen,cybersource,bankofamerica" } wallet.google_pay = { connector_list = "stripe,adyen,cybersource,bankofamerica" }
wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica" } wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica" }
wallet.paypal = { connector_list = "adyen" } wallet.paypal = { connector_list = "adyen" }
card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" }
card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" }
bank_debit.ach = { connector_list = "gocardless" } bank_debit.ach = { connector_list = "gocardless" }
bank_debit.becs = { connector_list = "gocardless" } bank_debit.becs = { connector_list = "gocardless" }
bank_debit.sepa = { connector_list = "gocardless" } bank_debit.sepa = { connector_list = "gocardless" }

View File

@ -43,26 +43,29 @@ pub struct MandateData {
pub mandate_type: Option<MandateDataType>, pub mandate_type: Option<MandateDataType>,
} }
#[derive(Default, Eq, PartialEq, Debug, Clone)] #[derive(Default, Eq, PartialEq, Debug, Clone, serde::Deserialize)]
pub struct CustomerAcceptance { pub struct CustomerAcceptance {
/// Type of acceptance provided by the /// Type of acceptance provided by the
pub acceptance_type: AcceptanceType, pub acceptance_type: AcceptanceType,
/// Specifying when the customer acceptance was provided /// Specifying when the customer acceptance was provided
#[serde(with = "common_utils::custom_serde::iso8601::option")]
pub accepted_at: Option<PrimitiveDateTime>, pub accepted_at: Option<PrimitiveDateTime>,
/// Information required for online mandate generation /// Information required for online mandate generation
pub online: Option<OnlineMandate>, pub online: Option<OnlineMandate>,
} }
#[derive(Default, Debug, PartialEq, Eq, Clone)] #[derive(Default, Debug, PartialEq, Eq, Clone, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AcceptanceType { pub enum AcceptanceType {
Online, Online,
#[default] #[default]
Offline, Offline,
} }
#[derive(Default, Eq, PartialEq, Debug, Clone)] #[derive(Default, Eq, PartialEq, Debug, Clone, serde::Deserialize)]
pub struct OnlineMandate { pub struct OnlineMandate {
/// Ip address of the customer machine from which the mandate was created /// Ip address of the customer machine from which the mandate was created
#[serde(skip_deserializing)]
pub ip_address: Option<Secret<String, pii::IpAddress>>, pub ip_address: Option<Secret<String, pii::IpAddress>>,
/// The user-agent of the customer's browser /// The user-agent of the customer's browser
pub user_agent: String, pub user_agent: String,

View File

@ -347,7 +347,7 @@ pub struct CompleteAuthorizeData {
pub connector_meta: Option<serde_json::Value>, pub connector_meta: Option<serde_json::Value>,
pub complete_authorize_url: Option<String>, pub complete_authorize_url: Option<String>,
pub metadata: Option<pii::SecretSerdeValue>, pub metadata: Option<pii::SecretSerdeValue>,
pub customer_acceptance: Option<mandates::CustomerAcceptance>,
// New amount for amount frame work // New amount for amount frame work
pub minor_amount: MinorUnit, pub minor_amount: MinorUnit,
} }

View File

@ -15,6 +15,7 @@ use self::transformers as braintree;
use super::utils::{self as connector_utils, PaymentsAuthorizeRequestData}; use super::utils::{self as connector_utils, PaymentsAuthorizeRequestData};
use crate::{ use crate::{
configs::settings, configs::settings,
connector::utils::PaymentMethodDataType,
consts, consts,
core::{ core::{
errors::{self, CustomResult}, errors::{self, CustomResult},
@ -175,6 +176,15 @@ impl ConnectorValidation for Braintree {
), ),
} }
} }
fn validate_mandate_payment(
&self,
pm_type: Option<types::storage::enums::PaymentMethodType>,
pm_data: domain::payments::PaymentMethodData,
) -> CustomResult<(), errors::ConnectorError> {
let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]);
connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id())
}
} }
impl api::Payment for Braintree {} impl api::Payment for Braintree {}

View File

@ -5,11 +5,14 @@ use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
use crate::{ use crate::{
connector::utils::{self, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData}, connector::utils::{
self, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData,
RefundsRequestData, RouterData,
},
consts, consts,
core::errors, core::errors,
services, services,
types::{self, api, domain, storage::enums}, types::{self, api, domain, storage::enums, MandateReference},
unimplemented_payment_method, unimplemented_payment_method,
}; };
@ -20,6 +23,8 @@ pub const AUTHORIZE_CREDIT_CARD_MUTATION: &str = "mutation authorizeCreditCard($
pub const CAPTURE_TRANSACTION_MUTATION: &str = "mutation captureTransaction($input: CaptureTransactionInput!) { captureTransaction(input: $input) { clientMutationId transaction { id legacyId amount { value currencyCode } status } } }"; pub const CAPTURE_TRANSACTION_MUTATION: &str = "mutation captureTransaction($input: CaptureTransactionInput!) { captureTransaction(input: $input) { clientMutationId transaction { id legacyId amount { value currencyCode } status } } }";
pub const VOID_TRANSACTION_MUTATION: &str = "mutation voidTransaction($input: ReverseTransactionInput!) { reverseTransaction(input: $input) { clientMutationId reversal { ... on Transaction { id legacyId amount { value currencyCode } status } } } }"; pub const VOID_TRANSACTION_MUTATION: &str = "mutation voidTransaction($input: ReverseTransactionInput!) { reverseTransaction(input: $input) { clientMutationId reversal { ... on Transaction { id legacyId amount { value currencyCode } status } } } }";
pub const REFUND_TRANSACTION_MUTATION: &str = "mutation refundTransaction($input: RefundTransactionInput!) { refundTransaction(input: $input) {clientMutationId refund { id legacyId amount { value currencyCode } status } } }"; pub const REFUND_TRANSACTION_MUTATION: &str = "mutation refundTransaction($input: RefundTransactionInput!) { refundTransaction(input: $input) {clientMutationId refund { id legacyId amount { value currencyCode } status } } }";
pub const AUTHORIZE_AND_VAULT_CREDIT_CARD_MUTATION: &str="mutation authorizeCreditCard($input: AuthorizeCreditCardInput!) { authorizeCreditCard(input: $input) { transaction { id status createdAt paymentMethod { id } } } }";
pub const CHARGE_AND_VAULT_TRANSACTION_MUTATION: &str ="mutation ChargeCreditCard($input: ChargeCreditCardInput!) { chargeCreditCard(input: $input) { transaction { id status createdAt paymentMethod { id } } } }";
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct BraintreeRouterData<T> { pub struct BraintreeRouterData<T> {
@ -58,11 +63,18 @@ pub struct CardPaymentRequest {
variables: VariablePaymentInput, variables: VariablePaymentInput,
} }
#[derive(Debug, Serialize)]
pub struct MandatePaymentRequest {
query: String,
variables: VariablePaymentInput,
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum BraintreePaymentsRequest { pub enum BraintreePaymentsRequest {
Card(CardPaymentRequest), Card(CardPaymentRequest),
CardThreeDs(BraintreeClientTokenRequest), CardThreeDs(BraintreeClientTokenRequest),
Mandate(MandatePaymentRequest),
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -84,11 +96,69 @@ impl TryFrom<&Option<pii::SecretSerdeValue>> for BraintreeMeta {
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TransactionBody { pub struct RegularTransactionBody {
amount: String, amount: String,
merchant_account_id: Secret<String>, merchant_account_id: Secret<String>,
} }
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VaultTransactionBody {
amount: String,
merchant_account_id: Secret<String>,
vault_payment_method_after_transacting: TransactionTiming,
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum TransactionBody {
Regular(RegularTransactionBody),
Vault(VaultTransactionBody),
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionTiming {
when: String,
}
impl
TryFrom<(
&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>,
String,
BraintreeMeta,
)> for MandatePaymentRequest
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
(item, connector_mandate_id, metadata): (
&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>,
String,
BraintreeMeta,
),
) -> Result<Self, Self::Error> {
let (query, transaction_body) = (
match item.router_data.request.is_auto_capture()? {
true => CHARGE_CREDIT_CARD_MUTATION.to_string(),
false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(),
},
TransactionBody::Regular(RegularTransactionBody {
amount: item.amount.to_owned(),
merchant_account_id: metadata.merchant_account_id,
}),
);
Ok(Self {
query,
variables: VariablePaymentInput {
input: PaymentInput {
payment_method_id: connector_mandate_id.into(),
transaction: transaction_body,
},
},
})
}
}
impl TryFrom<&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>> impl TryFrom<&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>>
for BraintreePaymentsRequest for BraintreePaymentsRequest
{ {
@ -105,7 +175,6 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>>
item.router_data.request.currency, item.router_data.request.currency,
Some(metadata.merchant_config_currency), Some(metadata.merchant_config_currency),
)?; )?;
match item.router_data.request.payment_method_data.clone() { match item.router_data.request.payment_method_data.clone() {
domain::PaymentMethodData::Card(_) => { domain::PaymentMethodData::Card(_) => {
if item.router_data.is_three_ds() { if item.router_data.is_three_ds() {
@ -116,6 +185,18 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>>
Ok(Self::Card(CardPaymentRequest::try_from((item, metadata))?)) Ok(Self::Card(CardPaymentRequest::try_from((item, metadata))?))
} }
} }
domain::PaymentMethodData::MandatePayment => {
let connector_mandate_id = item.router_data.request.connector_mandate_id().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "connector_mandate_id",
},
)?;
Ok(Self::Mandate(MandatePaymentRequest::try_from((
item,
connector_mandate_id,
metadata,
))?))
}
domain::PaymentMethodData::CardRedirect(_) domain::PaymentMethodData::CardRedirect(_)
| domain::PaymentMethodData::Wallet(_) | domain::PaymentMethodData::Wallet(_)
| domain::PaymentMethodData::PayLater(_) | domain::PaymentMethodData::PayLater(_)
@ -123,7 +204,6 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>>
| domain::PaymentMethodData::BankDebit(_) | domain::PaymentMethodData::BankDebit(_)
| domain::PaymentMethodData::BankTransfer(_) | domain::PaymentMethodData::BankTransfer(_)
| domain::PaymentMethodData::Crypto(_) | domain::PaymentMethodData::Crypto(_)
| domain::PaymentMethodData::MandatePayment
| domain::PaymentMethodData::Reward | domain::PaymentMethodData::Reward
| domain::PaymentMethodData::RealTimePayment(_) | domain::PaymentMethodData::RealTimePayment(_)
| domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Upi(_)
@ -194,9 +274,16 @@ pub enum BraintreeCompleteAuthResponse {
} }
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
struct PaymentMethodInfo {
id: Secret<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionAuthChargeResponseBody { pub struct TransactionAuthChargeResponseBody {
id: String, id: String,
status: BraintreePaymentStatus, status: BraintreePaymentStatus,
payment_method: Option<PaymentMethodInfo>,
} }
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
@ -242,7 +329,12 @@ impl<F>
response: Ok(types::PaymentsResponseData::TransactionResponse { response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id),
redirection_data: None, redirection_data: None,
mandate_reference: None, mandate_reference: transaction_data.payment_method.as_ref().map(|pm| {
MandateReference {
connector_mandate_id: Some(pm.id.clone().expose()),
payment_method_id: None,
}
}),
connector_metadata: None, connector_metadata: None,
network_txn_id: None, network_txn_id: None,
connector_response_reference_id: None, connector_response_reference_id: None,
@ -426,7 +518,12 @@ impl<F>
response: Ok(types::PaymentsResponseData::TransactionResponse { response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id),
redirection_data: None, redirection_data: None,
mandate_reference: None, mandate_reference: transaction_data.payment_method.as_ref().map(|pm| {
MandateReference {
connector_mandate_id: Some(pm.id.clone().expose()),
payment_method_id: None,
}
}),
connector_metadata: None, connector_metadata: None,
network_txn_id: None, network_txn_id: None,
connector_response_reference_id: None, connector_response_reference_id: None,
@ -490,7 +587,12 @@ impl<F>
response: Ok(types::PaymentsResponseData::TransactionResponse { response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id),
redirection_data: None, redirection_data: None,
mandate_reference: None, mandate_reference: transaction_data.payment_method.as_ref().map(|pm| {
MandateReference {
connector_mandate_id: Some(pm.id.clone().expose()),
payment_method_id: None,
}
}),
connector_metadata: None, connector_metadata: None,
network_txn_id: None, network_txn_id: None,
connector_response_reference_id: None, connector_response_reference_id: None,
@ -536,7 +638,12 @@ impl<F>
response: Ok(types::PaymentsResponseData::TransactionResponse { response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id),
redirection_data: None, redirection_data: None,
mandate_reference: None, mandate_reference: transaction_data.payment_method.as_ref().map(|pm| {
MandateReference {
connector_mandate_id: Some(pm.id.clone().expose()),
payment_method_id: None,
}
}),
connector_metadata: None, connector_metadata: None,
network_txn_id: None, network_txn_id: None,
connector_response_reference_id: None, connector_response_reference_id: None,
@ -1327,9 +1434,31 @@ impl
BraintreeMeta, BraintreeMeta,
), ),
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
let query = match item.router_data.request.is_auto_capture()? { let (query, transaction_body) = if item.router_data.request.is_mandate_payment() {
true => CHARGE_CREDIT_CARD_MUTATION.to_string(), (
false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(), match item.router_data.request.is_auto_capture()? {
true => CHARGE_AND_VAULT_TRANSACTION_MUTATION.to_string(),
false => AUTHORIZE_AND_VAULT_CREDIT_CARD_MUTATION.to_string(),
},
TransactionBody::Vault(VaultTransactionBody {
amount: item.amount.to_owned(),
merchant_account_id: metadata.merchant_account_id,
vault_payment_method_after_transacting: TransactionTiming {
when: "ALWAYS".to_string(),
},
}),
)
} else {
(
match item.router_data.request.is_auto_capture()? {
true => CHARGE_CREDIT_CARD_MUTATION.to_string(),
false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(),
},
TransactionBody::Regular(RegularTransactionBody {
amount: item.amount.to_owned(),
merchant_account_id: metadata.merchant_account_id,
}),
)
}; };
Ok(Self { Ok(Self {
query, query,
@ -1341,10 +1470,7 @@ impl
unimplemented_payment_method!("Apple Pay", "Simplified", "Braintree"), unimplemented_payment_method!("Apple Pay", "Simplified", "Braintree"),
)?, )?,
}, },
transaction: TransactionBody { transaction: transaction_body,
amount: item.amount.to_owned(),
merchant_account_id: metadata.merchant_account_id,
},
}, },
}, },
}) })
@ -1367,11 +1493,10 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>>
item.router_data.request.currency, item.router_data.request.currency,
Some(metadata.merchant_config_currency), Some(metadata.merchant_config_currency),
)?; )?;
let payload_data = let payload_data = PaymentsCompleteAuthorizeRequestData::get_redirect_response_payload(
utils::PaymentsCompleteAuthorizeRequestData::get_redirect_response_payload( &item.router_data.request,
&item.router_data.request, )?
)? .expose();
.expose();
let redirection_response: BraintreeRedirectionResponse = serde_json::from_value( let redirection_response: BraintreeRedirectionResponse = serde_json::from_value(
payload_data, payload_data,
) )
@ -1384,21 +1509,39 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>>
.change_context(errors::ConnectorError::MissingConnectorRedirectionPayload { .change_context(errors::ConnectorError::MissingConnectorRedirectionPayload {
field_name: "three_ds_data", field_name: "three_ds_data",
})?; })?;
let query = match utils::PaymentsCompleteAuthorizeRequestData::is_auto_capture(
&item.router_data.request, let (query, transaction_body) = if item.router_data.request.is_mandate_payment() {
)? { (
true => CHARGE_CREDIT_CARD_MUTATION.to_string(), match item.router_data.request.is_auto_capture()? {
false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(), true => CHARGE_AND_VAULT_TRANSACTION_MUTATION.to_string(),
false => AUTHORIZE_AND_VAULT_CREDIT_CARD_MUTATION.to_string(),
},
TransactionBody::Vault(VaultTransactionBody {
amount: item.amount.to_owned(),
merchant_account_id: metadata.merchant_account_id,
vault_payment_method_after_transacting: TransactionTiming {
when: "ALWAYS".to_string(),
},
}),
)
} else {
(
match item.router_data.request.is_auto_capture()? {
true => CHARGE_CREDIT_CARD_MUTATION.to_string(),
false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(),
},
TransactionBody::Regular(RegularTransactionBody {
amount: item.amount.to_owned(),
merchant_account_id: metadata.merchant_account_id,
}),
)
}; };
Ok(Self { Ok(Self {
query, query,
variables: VariablePaymentInput { variables: VariablePaymentInput {
input: PaymentInput { input: PaymentInput {
payment_method_id: three_ds_data.nonce, payment_method_id: three_ds_data.nonce,
transaction: TransactionBody { transaction: transaction_body,
amount: item.amount.to_owned(),
merchant_account_id: metadata.merchant_account_id,
},
}, },
}, },
}) })

View File

@ -1017,6 +1017,7 @@ pub trait PaymentsCompleteAuthorizeRequestData {
fn get_email(&self) -> Result<Email, Error>; fn get_email(&self) -> Result<Email, Error>;
fn get_redirect_response_payload(&self) -> Result<pii::SecretSerdeValue, Error>; fn get_redirect_response_payload(&self) -> Result<pii::SecretSerdeValue, Error>;
fn get_complete_authorize_url(&self) -> Result<String, Error>; fn get_complete_authorize_url(&self) -> Result<String, Error>;
fn is_mandate_payment(&self) -> bool;
} }
impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData {
@ -1046,6 +1047,17 @@ impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData {
.clone() .clone()
.ok_or_else(missing_field_err("complete_authorize_url")) .ok_or_else(missing_field_err("complete_authorize_url"))
} }
fn is_mandate_payment(&self) -> bool {
((self.customer_acceptance.is_some() || self.setup_mandate_details.is_some())
&& self.setup_future_usage.map_or(false, |setup_future_usage| {
setup_future_usage == storage_enums::FutureUsage::OffSession
}))
|| self
.mandate_id
.as_ref()
.and_then(|mandate_ids| mandate_ids.mandate_reference_id.as_ref())
.is_some()
}
} }
pub trait PaymentsSyncRequestData { pub trait PaymentsSyncRequestData {

View File

@ -610,6 +610,21 @@ pub async fn get_token_pm_type_mandate_details(
) )
} }
} else { } else {
let payment_method_info = payment_method_id
.async_map(|payment_method_id| async move {
state
.store
.find_payment_method(
&payment_method_id,
merchant_account.storage_scheme,
)
.await
.to_not_found_response(
errors::ApiErrorResponse::PaymentMethodNotFound,
)
})
.await
.transpose()?;
( (
request.payment_token.to_owned(), request.payment_token.to_owned(),
request.payment_method, request.payment_method,
@ -617,7 +632,7 @@ pub async fn get_token_pm_type_mandate_details(
None, None,
None, None,
None, None,
None, payment_method_info,
) )
} }
} }

View File

@ -11,7 +11,10 @@ use crate::{
core::{ core::{
errors::{self, CustomResult, RouterResult, StorageErrorExt}, errors::{self, CustomResult, RouterResult, StorageErrorExt},
mandate::helpers as m_helpers, mandate::helpers as m_helpers,
payments::{self, helpers, operations, CustomerDetails, PaymentAddress, PaymentData}, payments::{
self, helpers, operations, CustomerAcceptance, CustomerDetails, PaymentAddress,
PaymentData,
},
utils as core_utils, utils as core_utils,
}, },
db::StorageInterface, db::StorageInterface,
@ -86,7 +89,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co
})?; })?;
let recurring_details = request.recurring_details.clone(); let recurring_details = request.recurring_details.clone();
let customer_acceptance = request.customer_acceptance.clone().map(From::from);
payment_attempt = db payment_attempt = db
.find_payment_attempt_by_payment_id_merchant_id_attempt_id( .find_payment_attempt_by_payment_id_merchant_id_attempt_id(
@ -127,6 +129,19 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co
&payment_intent.customer_id, &payment_intent.customer_id,
) )
.await?; .await?;
let customer_acceptance: Option<CustomerAcceptance> = request
.customer_acceptance
.clone()
.map(From::from)
.or(payment_method_info
.clone()
.map(|pm| {
pm.customer_acceptance
.parse_value::<CustomerAcceptance>("CustomerAcceptance")
})
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to deserialize to CustomerAcceptance")?);
let token = token.or_else(|| payment_attempt.payment_token.clone()); let token = token.or_else(|| payment_attempt.payment_token.clone());
if let Some(payment_method) = payment_method { if let Some(payment_method) = payment_method {

View File

@ -1790,6 +1790,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::CompleteAuthoriz
connector_meta: payment_data.payment_attempt.connector_metadata, connector_meta: payment_data.payment_attempt.connector_metadata,
complete_authorize_url, complete_authorize_url,
metadata: payment_data.payment_intent.metadata, metadata: payment_data.payment_intent.metadata,
customer_acceptance: payment_data.customer_acceptance,
}) })
} }
} }

View File

@ -281,8 +281,8 @@ pay_later.klarna = {connector_list = "adyen"}
wallet.google_pay = {connector_list = "stripe,adyen,bankofamerica"} wallet.google_pay = {connector_list = "stripe,adyen,bankofamerica"}
wallet.apple_pay = {connector_list = "stripe,adyen,bankofamerica"} wallet.apple_pay = {connector_list = "stripe,adyen,bankofamerica"}
wallet.paypal = {connector_list = "adyen"} wallet.paypal = {connector_list = "adyen"}
card.credit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica"} card.credit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree"}
card.debit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica"} card.debit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree"}
bank_debit.ach = { connector_list = "gocardless"} bank_debit.ach = { connector_list = "gocardless"}
bank_debit.becs = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"}
bank_debit.sepa = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"}