mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 05:17:02 +08:00
feat(connector): [Braintree] Add Authorize, Capture, Void, PSync, Refund, Rsync for Braintree GraphQL API (#1962)
Co-authored-by: AkshayaFoiger <akshaya.shankar@juspay.in>
This commit is contained in:
@ -159,6 +159,7 @@ bitpay.base_url = "https://test.bitpay.com"
|
||||
bluesnap.base_url = "https://sandbox.bluesnap.com/"
|
||||
boku.base_url = "https://$-api4-stage.boku.com"
|
||||
braintree.base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
braintree.secondary_base_url = "https://payments.sandbox.braintree-api.com/graphql"
|
||||
cashtocode.base_url = "https://cluster05.api-test.cashtocode.com"
|
||||
checkout.base_url = "https://api.sandbox.checkout.com/"
|
||||
coinbase.base_url = "https://api.commerce.coinbase.com"
|
||||
@ -301,6 +302,7 @@ stripe = { long_lived_token = false, payment_method = "wallet", payment_method_t
|
||||
checkout = { long_lived_token = false, payment_method = "wallet" }
|
||||
mollie = {long_lived_token = false, payment_method = "card"}
|
||||
stax = { long_lived_token = true, payment_method = "card,bank_debit" }
|
||||
braintree = { long_lived_token = false, payment_method = "card" }
|
||||
|
||||
[dummy_connector]
|
||||
payment_ttl = 172800 # Time to live for dummy connector payment in redis
|
||||
@ -392,3 +394,6 @@ adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamal
|
||||
|
||||
[bank_config.online_banking_thailand]
|
||||
adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank"
|
||||
|
||||
[multiple_api_version_supported_connectors]
|
||||
supported_connectors = ["braintree"]
|
||||
@ -130,6 +130,7 @@ bitpay.base_url = "https://test.bitpay.com"
|
||||
bluesnap.base_url = "https://sandbox.bluesnap.com/"
|
||||
boku.base_url = "https://$-api4-stage.boku.com"
|
||||
braintree.base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
braintree.secondary_base_url = "https://payments.sandbox.braintree-api.com/graphql"
|
||||
cashtocode.base_url = "https://cluster05.api-test.cashtocode.com"
|
||||
checkout.base_url = "https://api.sandbox.checkout.com/"
|
||||
coinbase.base_url = "https://api.commerce.coinbase.com"
|
||||
@ -358,6 +359,7 @@ stripe = { long_lived_token = false, payment_method = "wallet", payment_method_t
|
||||
checkout = { long_lived_token = false, payment_method = "wallet" }
|
||||
stax = { long_lived_token = true, payment_method = "card,bank_debit" }
|
||||
mollie = {long_lived_token = false, payment_method = "card"}
|
||||
braintree = { long_lived_token = false, payment_method = "card" }
|
||||
|
||||
[connector_customer]
|
||||
connector_list = "bluesnap,stax,stripe"
|
||||
@ -398,3 +400,6 @@ merchant_ids_send_payment_id_as_connector_request_id = []
|
||||
|
||||
[payouts]
|
||||
payout_eligibility = true
|
||||
|
||||
[multiple_api_version_supported_connectors]
|
||||
supported_connectors = "braintree"
|
||||
@ -82,6 +82,7 @@ bitpay.base_url = "https://test.bitpay.com"
|
||||
bluesnap.base_url = "https://sandbox.bluesnap.com/"
|
||||
boku.base_url = "https://$-api4-stage.boku.com"
|
||||
braintree.base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
braintree.secondary_base_url = "https://payments.sandbox.braintree-api.com/graphql"
|
||||
cashtocode.base_url = "https://cluster05.api-test.cashtocode.com"
|
||||
checkout.base_url = "https://api.sandbox.checkout.com/"
|
||||
coinbase.base_url = "https://api.commerce.coinbase.com"
|
||||
@ -194,6 +195,7 @@ stripe = { long_lived_token = false, payment_method = "wallet", payment_method_t
|
||||
checkout = { long_lived_token = false, payment_method = "wallet" }
|
||||
mollie = {long_lived_token = false, payment_method = "card"}
|
||||
stax = { long_lived_token = true, payment_method = "card,bank_debit" }
|
||||
braintree = { long_lived_token = false, payment_method = "card" }
|
||||
|
||||
[dummy_connector]
|
||||
payment_ttl = 172800
|
||||
@ -282,3 +284,6 @@ card.debit = {connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay,
|
||||
[connector_customer]
|
||||
connector_list = "stax"
|
||||
payout_connector_list = "wise"
|
||||
|
||||
[multiple_api_version_supported_connectors]
|
||||
supported_connectors = ["braintree"]
|
||||
|
||||
@ -93,6 +93,13 @@ pub struct Settings {
|
||||
pub connector_request_reference_id_config: ConnectorRequestReferenceIdConfig,
|
||||
#[cfg(feature = "payouts")]
|
||||
pub payouts: Payouts,
|
||||
pub multiple_api_version_supported_connectors: MultipleApiVersionSupportedConnectors,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Default)]
|
||||
pub struct MultipleApiVersionSupportedConnectors {
|
||||
#[serde(deserialize_with = "connector_deser")]
|
||||
pub supported_connectors: HashSet<api_models::enums::Connector>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Default)]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,992 @@
|
||||
use error_stack::ResultExt;
|
||||
use masking::Secret;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connector::utils::{self, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData},
|
||||
consts,
|
||||
core::errors,
|
||||
types::{self, api, storage::enums},
|
||||
};
|
||||
|
||||
pub const TOKENIZE_CREDIT_CARD: &str = "mutation tokenizeCreditCard($input: TokenizeCreditCardInput!) { tokenizeCreditCard(input: $input) { clientMutationId paymentMethod { id } } }";
|
||||
pub const CHARGE_CREDIT_CARD_MUTATION: &str = "mutation ChargeCreditCard($input: ChargeCreditCardInput!) { chargeCreditCard(input: $input) { transaction { id legacyId createdAt amount { value currencyCode } status } } }";
|
||||
pub const AUTHORIZE_CREDIT_CARD_MUTATION: &str = "mutation authorizeCreditCard($input: AuthorizeCreditCardInput!) { authorizeCreditCard(input: $input) { 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 REFUND_TRANSACTION_MUTATION: &str = "mutation refundTransaction($input: RefundTransactionInput!) { refundTransaction(input: $input) {clientMutationId refund { id legacyId amount { value currencyCode } status } } }";
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PaymentInput {
|
||||
payment_method_id: String,
|
||||
transaction: TransactionBody,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct VariablePaymentInput {
|
||||
input: PaymentInput,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BraintreePaymentsRequest {
|
||||
query: String,
|
||||
variables: VariablePaymentInput,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BraintreeMeta {
|
||||
merchant_account_id: Option<Secret<String>>,
|
||||
merchant_config_currency: Option<types::storage::enums::Currency>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransactionBody {
|
||||
amount: String,
|
||||
merchant_account_id: Secret<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for BraintreePaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||
let metadata: BraintreeMeta =
|
||||
utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?;
|
||||
utils::validate_currency(item.request.currency, metadata.merchant_config_currency)?;
|
||||
|
||||
match item.request.payment_method_data.clone() {
|
||||
api::PaymentMethodData::Card(_) => {
|
||||
let query = match item.request.is_auto_capture()? {
|
||||
true => CHARGE_CREDIT_CARD_MUTATION.to_string(),
|
||||
false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(),
|
||||
};
|
||||
Ok(Self {
|
||||
query,
|
||||
variables: VariablePaymentInput {
|
||||
input: PaymentInput {
|
||||
payment_method_id: item.get_payment_method_token()?,
|
||||
transaction: TransactionBody {
|
||||
amount: utils::to_currency_base_unit(
|
||||
item.request.amount,
|
||||
item.request.currency,
|
||||
)?,
|
||||
merchant_account_id: metadata.merchant_account_id.ok_or(
|
||||
errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "merchant_account_id",
|
||||
},
|
||||
)?,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
api_models::payments::PaymentMethodData::CardRedirect(_)
|
||||
| api_models::payments::PaymentMethodData::Wallet(_)
|
||||
| api_models::payments::PaymentMethodData::PayLater(_)
|
||||
| api_models::payments::PaymentMethodData::BankRedirect(_)
|
||||
| api_models::payments::PaymentMethodData::BankDebit(_)
|
||||
| api_models::payments::PaymentMethodData::BankTransfer(_)
|
||||
| api_models::payments::PaymentMethodData::Crypto(_)
|
||||
| api_models::payments::PaymentMethodData::MandatePayment
|
||||
| api_models::payments::PaymentMethodData::Reward(_)
|
||||
| api_models::payments::PaymentMethodData::Upi(_)
|
||||
| api_models::payments::PaymentMethodData::Voucher(_)
|
||||
| api_models::payments::PaymentMethodData::GiftCard(_) => {
|
||||
Err(errors::ConnectorError::NotImplemented(
|
||||
utils::get_unimplemented_payment_method_error_message("braintree"),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct AuthResponse {
|
||||
data: DataAuthResponse,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum BraintreeAuthResponse {
|
||||
AuthResponse(Box<AuthResponse>),
|
||||
ErrorResponse(Box<ErrorResponse>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct TransactionAuthChargeResponseBody {
|
||||
id: String,
|
||||
status: BraintreePaymentStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DataAuthResponse {
|
||||
authorize_credit_card: AuthChargeCreditCard,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct AuthChargeCreditCard {
|
||||
transaction: TransactionAuthChargeResponseBody,
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, BraintreeAuthResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, BraintreeAuthResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match item.response {
|
||||
BraintreeAuthResponse::ErrorResponse(error_response) => Ok(Self {
|
||||
response: build_error_response(&error_response.errors, item.http_code),
|
||||
..item.data
|
||||
}),
|
||||
BraintreeAuthResponse::AuthResponse(auth_response) => {
|
||||
let transaction_data = auth_response.data.authorize_credit_card.transaction;
|
||||
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(transaction_data.status.clone()),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||
transaction_data.id.clone(),
|
||||
),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_error_response<T>(
|
||||
response: &[ErrorDetails],
|
||||
http_code: u16,
|
||||
) -> Result<T, types::ErrorResponse> {
|
||||
let error_messages = response
|
||||
.iter()
|
||||
.map(|error| error.message.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let reason = match !error_messages.is_empty() {
|
||||
true => Some(error_messages.join(" ")),
|
||||
false => None,
|
||||
};
|
||||
|
||||
get_error_response(
|
||||
response
|
||||
.get(0)
|
||||
.and_then(|err_details| err_details.extensions.as_ref())
|
||||
.and_then(|extensions| extensions.legacy_code.clone()),
|
||||
response
|
||||
.get(0)
|
||||
.map(|err_details| err_details.message.clone()),
|
||||
reason,
|
||||
http_code,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_error_response<T>(
|
||||
error_code: Option<String>,
|
||||
error_msg: Option<String>,
|
||||
error_reason: Option<String>,
|
||||
http_code: u16,
|
||||
) -> Result<T, types::ErrorResponse> {
|
||||
Err(types::ErrorResponse {
|
||||
code: error_code.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
|
||||
message: error_msg.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
|
||||
reason: error_reason,
|
||||
status_code: http_code,
|
||||
})
|
||||
}
|
||||
|
||||
// Using Auth type from braintree/transformer.rs, need this in later time when we use graphql version
|
||||
// pub struct BraintreeAuthType {
|
||||
// pub(super) auth_header: String,
|
||||
// pub(super) merchant_id: Secret<String>,
|
||||
// }
|
||||
|
||||
// impl TryFrom<&types::ConnectorAuthType> for BraintreeAuthType {
|
||||
// type Error = error_stack::Report<errors::ConnectorError>;
|
||||
// fn try_from(item: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
|
||||
// if let types::ConnectorAuthType::SignatureKey {
|
||||
// api_key: public_key,
|
||||
// key1: merchant_id,
|
||||
// api_secret: private_key,
|
||||
// } = item
|
||||
// {
|
||||
// let auth_key = format!("{}:{}", public_key.peek(), private_key.peek());
|
||||
// let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key));
|
||||
// Ok(Self {
|
||||
// auth_header,
|
||||
// merchant_id: merchant_id.to_owned(),
|
||||
// })
|
||||
// } else {
|
||||
// Err(errors::ConnectorError::FailedToObtainAuthType)?
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum BraintreePaymentStatus {
|
||||
Authorized,
|
||||
Authorizing,
|
||||
AuthorizedExpired,
|
||||
Failed,
|
||||
ProcessorDeclined,
|
||||
GatewayRejected,
|
||||
Voided,
|
||||
Settling,
|
||||
Settled,
|
||||
SettlementPending,
|
||||
SettlementDeclined,
|
||||
SettlementConfirmed,
|
||||
SubmittedForSettlement,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ErrorDetails {
|
||||
pub message: String,
|
||||
pub extensions: Option<AdditionalErrorDetails>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdditionalErrorDetails {
|
||||
pub legacy_code: Option<String>,
|
||||
}
|
||||
|
||||
impl From<BraintreePaymentStatus> for enums::AttemptStatus {
|
||||
fn from(item: BraintreePaymentStatus) -> Self {
|
||||
match item {
|
||||
BraintreePaymentStatus::Settling
|
||||
| BraintreePaymentStatus::Settled
|
||||
| BraintreePaymentStatus::SettlementConfirmed => Self::Charged,
|
||||
BraintreePaymentStatus::Authorizing => Self::Authorizing,
|
||||
BraintreePaymentStatus::AuthorizedExpired => Self::AuthorizationFailed,
|
||||
BraintreePaymentStatus::Failed
|
||||
| BraintreePaymentStatus::GatewayRejected
|
||||
| BraintreePaymentStatus::ProcessorDeclined
|
||||
| BraintreePaymentStatus::SettlementDeclined => Self::Failure,
|
||||
BraintreePaymentStatus::Authorized => Self::Authorized,
|
||||
BraintreePaymentStatus::Voided => Self::Voided,
|
||||
BraintreePaymentStatus::SubmittedForSettlement
|
||||
| BraintreePaymentStatus::SettlementPending => Self::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, BraintreePaymentsResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<
|
||||
F,
|
||||
BraintreePaymentsResponse,
|
||||
T,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match item.response {
|
||||
BraintreePaymentsResponse::ErrorResponse(error_response) => Ok(Self {
|
||||
response: build_error_response(&error_response.errors.clone(), item.http_code),
|
||||
..item.data
|
||||
}),
|
||||
BraintreePaymentsResponse::PaymentsResponse(payment_response) => {
|
||||
let transaction_data = payment_response.data.charge_credit_card.transaction;
|
||||
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(transaction_data.status.clone()),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||
transaction_data.id.clone(),
|
||||
),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PaymentsResponse {
|
||||
data: DataResponse,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum BraintreePaymentsResponse {
|
||||
PaymentsResponse(Box<PaymentsResponse>),
|
||||
ErrorResponse(Box<ErrorResponse>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DataResponse {
|
||||
charge_credit_card: AuthChargeCreditCard,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RefundInputData {
|
||||
amount: String,
|
||||
merchant_account_id: Secret<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BraintreeRefundInput {
|
||||
transaction_id: String,
|
||||
refund: RefundInputData,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
pub struct BraintreeRefundVariables {
|
||||
input: BraintreeRefundInput,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
pub struct BraintreeRefundRequest {
|
||||
query: String,
|
||||
variables: BraintreeRefundVariables,
|
||||
}
|
||||
|
||||
impl<F> TryFrom<&types::RefundsRouterData<F>> for BraintreeRefundRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||
let metadata: BraintreeMeta =
|
||||
utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?;
|
||||
|
||||
utils::validate_currency(item.request.currency, metadata.merchant_config_currency)?;
|
||||
let query = REFUND_TRANSACTION_MUTATION.to_string();
|
||||
let variables = BraintreeRefundVariables {
|
||||
input: BraintreeRefundInput {
|
||||
transaction_id: item.request.connector_transaction_id.clone(),
|
||||
refund: RefundInputData {
|
||||
amount: utils::to_currency_base_unit(
|
||||
item.request.refund_amount,
|
||||
item.request.currency,
|
||||
)?,
|
||||
merchant_account_id: metadata.merchant_account_id.ok_or(
|
||||
errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "merchant_account_id",
|
||||
},
|
||||
)?,
|
||||
},
|
||||
},
|
||||
};
|
||||
Ok(Self { query, variables })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum BraintreeRefundStatus {
|
||||
SettlementPending,
|
||||
Settling,
|
||||
Settled,
|
||||
SubmittedForSettlement,
|
||||
Failed,
|
||||
}
|
||||
|
||||
impl From<BraintreeRefundStatus> for enums::RefundStatus {
|
||||
fn from(item: BraintreeRefundStatus) -> Self {
|
||||
match item {
|
||||
BraintreeRefundStatus::Settled | BraintreeRefundStatus::Settling => Self::Success,
|
||||
BraintreeRefundStatus::SubmittedForSettlement
|
||||
| BraintreeRefundStatus::SettlementPending => Self::Pending,
|
||||
BraintreeRefundStatus::Failed => Self::Failure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct BraintreeRefundTransactionBody {
|
||||
pub id: String,
|
||||
pub status: BraintreeRefundStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct BraintreeRefundTransaction {
|
||||
pub refund: BraintreeRefundTransactionBody,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BraintreeRefundResponseData {
|
||||
pub refund_transaction: BraintreeRefundTransaction,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
|
||||
pub struct RefundResponse {
|
||||
pub data: BraintreeRefundResponseData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum BraintreeRefundResponse {
|
||||
RefundResponse(Box<RefundResponse>),
|
||||
ErrorResponse(Box<ErrorResponse>),
|
||||
}
|
||||
|
||||
impl TryFrom<types::RefundsResponseRouterData<api::Execute, BraintreeRefundResponse>>
|
||||
for types::RefundsRouterData<api::Execute>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::RefundsResponseRouterData<api::Execute, BraintreeRefundResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
response: match item.response {
|
||||
BraintreeRefundResponse::ErrorResponse(error_response) => {
|
||||
build_error_response(&error_response.errors, item.http_code)
|
||||
}
|
||||
BraintreeRefundResponse::RefundResponse(refund_data) => {
|
||||
let refund_data = refund_data.data.refund_transaction.refund;
|
||||
|
||||
Ok(types::RefundsResponseData {
|
||||
connector_refund_id: refund_data.id.clone(),
|
||||
refund_status: enums::RefundStatus::from(refund_data.status.clone()),
|
||||
})
|
||||
}
|
||||
},
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BraintreeRSyncRequest {
|
||||
query: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::RefundSyncRouterData> for BraintreeRSyncRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::RefundSyncRouterData) -> Result<Self, Self::Error> {
|
||||
let metadata: BraintreeMeta =
|
||||
utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?;
|
||||
utils::validate_currency(item.request.currency, metadata.merchant_config_currency)?;
|
||||
let refund_id = item.request.get_connector_refund_id()?;
|
||||
let query = format!("query {{ search {{ refunds(input: {{ id: {{is: \"{}\"}} }}, first: 1) {{ edges {{ node {{ id status createdAt amount {{ value currencyCode }} orderId }} }} }} }} }}",refund_id);
|
||||
|
||||
Ok(Self { query })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RSyncNodeData {
|
||||
id: String,
|
||||
status: BraintreeRefundStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RSyncEdgeData {
|
||||
node: RSyncNodeData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RefundData {
|
||||
edges: Vec<RSyncEdgeData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RSyncSearchData {
|
||||
refunds: RefundData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RSyncResponseData {
|
||||
search: RSyncSearchData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RSyncResponse {
|
||||
data: RSyncResponseData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum BraintreeRSyncResponse {
|
||||
RSyncResponse(Box<RSyncResponse>),
|
||||
ErrorResponse(Box<ErrorResponse>),
|
||||
}
|
||||
|
||||
impl TryFrom<types::RefundsResponseRouterData<api::RSync, BraintreeRSyncResponse>>
|
||||
for types::RefundsRouterData<api::RSync>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::RefundsResponseRouterData<api::RSync, BraintreeRSyncResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match item.response {
|
||||
BraintreeRSyncResponse::ErrorResponse(error_response) => Ok(Self {
|
||||
response: build_error_response(&error_response.errors, item.http_code),
|
||||
..item.data
|
||||
}),
|
||||
BraintreeRSyncResponse::RSyncResponse(rsync_response) => {
|
||||
let edge_data = rsync_response
|
||||
.data
|
||||
.search
|
||||
.refunds
|
||||
.edges
|
||||
.first()
|
||||
.ok_or(errors::ConnectorError::MissingConnectorRefundID)?;
|
||||
let connector_refund_id = &edge_data.node.id;
|
||||
let response = Ok(types::RefundsResponseData {
|
||||
connector_refund_id: connector_refund_id.to_string(),
|
||||
refund_status: enums::RefundStatus::from(edge_data.node.status.clone()),
|
||||
});
|
||||
Ok(Self {
|
||||
response,
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreditCardData {
|
||||
number: cards::CardNumber,
|
||||
expiration_year: Secret<String>,
|
||||
expiration_month: Secret<String>,
|
||||
cvv: Secret<String>,
|
||||
cardholder_name: Secret<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InputData {
|
||||
credit_card: CreditCardData,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
pub struct VariableInput {
|
||||
input: InputData,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
pub struct BraintreeTokenRequest {
|
||||
query: String,
|
||||
variables: VariableInput,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::TokenizationRouterData> for BraintreeTokenRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::TokenizationRouterData) -> Result<Self, Self::Error> {
|
||||
match item.request.payment_method_data.clone() {
|
||||
api::PaymentMethodData::Card(card_data) => {
|
||||
let query = TOKENIZE_CREDIT_CARD.to_string();
|
||||
let input = InputData {
|
||||
credit_card: CreditCardData {
|
||||
number: card_data.card_number,
|
||||
expiration_year: card_data.card_exp_year,
|
||||
expiration_month: card_data.card_exp_month,
|
||||
cvv: card_data.card_cvc,
|
||||
cardholder_name: card_data.card_holder_name,
|
||||
},
|
||||
};
|
||||
Ok(Self {
|
||||
query,
|
||||
variables: VariableInput { input },
|
||||
})
|
||||
}
|
||||
api_models::payments::PaymentMethodData::CardRedirect(_)
|
||||
| api_models::payments::PaymentMethodData::Wallet(_)
|
||||
| api_models::payments::PaymentMethodData::PayLater(_)
|
||||
| api_models::payments::PaymentMethodData::BankRedirect(_)
|
||||
| api_models::payments::PaymentMethodData::BankDebit(_)
|
||||
| api_models::payments::PaymentMethodData::BankTransfer(_)
|
||||
| api_models::payments::PaymentMethodData::Crypto(_)
|
||||
| api_models::payments::PaymentMethodData::MandatePayment
|
||||
| api_models::payments::PaymentMethodData::Reward(_)
|
||||
| api_models::payments::PaymentMethodData::Upi(_)
|
||||
| api_models::payments::PaymentMethodData::Voucher(_)
|
||||
| api_models::payments::PaymentMethodData::GiftCard(_) => {
|
||||
Err(errors::ConnectorError::NotImplemented(
|
||||
utils::get_unimplemented_payment_method_error_message("braintree"),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
pub struct TokenizePaymentMethodData {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TokenizeCreditCardData {
|
||||
payment_method: TokenizePaymentMethodData,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TokenizeCreditCard {
|
||||
tokenize_credit_card: TokenizeCreditCardData,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
pub struct TokenResponse {
|
||||
data: TokenizeCreditCard,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
pub struct ErrorResponse {
|
||||
errors: Vec<ErrorDetails>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum BraintreeTokenResponse {
|
||||
TokenResponse(Box<TokenResponse>),
|
||||
ErrorResponse(Box<ErrorResponse>),
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, BraintreeTokenResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, BraintreeTokenResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
response: match item.response {
|
||||
BraintreeTokenResponse::ErrorResponse(error_response) => {
|
||||
build_error_response(error_response.errors.as_ref(), item.http_code)
|
||||
}
|
||||
|
||||
BraintreeTokenResponse::TokenResponse(token_response) => {
|
||||
Ok(types::PaymentsResponseData::TokenizationResponse {
|
||||
token: token_response
|
||||
.data
|
||||
.tokenize_credit_card
|
||||
.payment_method
|
||||
.id
|
||||
.clone(),
|
||||
})
|
||||
}
|
||||
},
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CaptureTransactionBody {
|
||||
amount: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CaptureInputData {
|
||||
transaction_id: String,
|
||||
transaction: CaptureTransactionBody,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
pub struct VariableCaptureInput {
|
||||
input: CaptureInputData,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
pub struct BraintreeCaptureRequest {
|
||||
query: String,
|
||||
variables: VariableCaptureInput,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsCaptureRouterData> for BraintreeCaptureRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
|
||||
let query = CAPTURE_TRANSACTION_MUTATION.to_string();
|
||||
let variables = VariableCaptureInput {
|
||||
input: CaptureInputData {
|
||||
transaction_id: item.request.connector_transaction_id.clone(),
|
||||
transaction: CaptureTransactionBody {
|
||||
amount: utils::to_currency_base_unit(
|
||||
item.request.amount_to_capture,
|
||||
item.request.currency,
|
||||
)?,
|
||||
},
|
||||
},
|
||||
};
|
||||
Ok(Self { query, variables })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CaptureResponseTransactionBody {
|
||||
id: String,
|
||||
status: BraintreePaymentStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CaptureTransactionData {
|
||||
transaction: CaptureResponseTransactionBody,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CaptureResponseData {
|
||||
capture_transaction: CaptureTransactionData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CaptureResponse {
|
||||
data: CaptureResponseData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum BraintreeCaptureResponse {
|
||||
CaptureResponse(Box<CaptureResponse>),
|
||||
ErrorResponse(Box<ErrorResponse>),
|
||||
}
|
||||
|
||||
impl TryFrom<types::PaymentsCaptureResponseRouterData<BraintreeCaptureResponse>>
|
||||
for types::PaymentsCaptureRouterData
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::PaymentsCaptureResponseRouterData<BraintreeCaptureResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match item.response {
|
||||
BraintreeCaptureResponse::CaptureResponse(capture_data) => {
|
||||
let transaction_data = capture_data.data.capture_transaction.transaction;
|
||||
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(transaction_data.status.clone()),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||
transaction_data.id.clone(),
|
||||
),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
BraintreeCaptureResponse::ErrorResponse(error_data) => Ok(Self {
|
||||
response: build_error_response(&error_data.errors, item.http_code),
|
||||
..item.data
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CancelInputData {
|
||||
transaction_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct VariableCancelInput {
|
||||
input: CancelInputData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BraintreeCancelRequest {
|
||||
query: String,
|
||||
variables: VariableCancelInput,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsCancelRouterData> for BraintreeCancelRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
|
||||
let query = VOID_TRANSACTION_MUTATION.to_string();
|
||||
let variables = VariableCancelInput {
|
||||
input: CancelInputData {
|
||||
transaction_id: item.request.connector_transaction_id.clone(),
|
||||
},
|
||||
};
|
||||
Ok(Self { query, variables })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CancelResponseTransactionBody {
|
||||
id: String,
|
||||
status: BraintreePaymentStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CancelTransactionData {
|
||||
reversal: CancelResponseTransactionBody,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CancelResponseData {
|
||||
reverse_transaction: CancelTransactionData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CancelResponse {
|
||||
data: CancelResponseData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum BraintreeCancelResponse {
|
||||
CancelResponse(Box<CancelResponse>),
|
||||
ErrorResponse(Box<ErrorResponse>),
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, BraintreeCancelResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, BraintreeCancelResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match item.response {
|
||||
BraintreeCancelResponse::ErrorResponse(error_response) => Ok(Self {
|
||||
response: build_error_response(&error_response.errors, item.http_code),
|
||||
..item.data
|
||||
}),
|
||||
BraintreeCancelResponse::CancelResponse(void_response) => {
|
||||
let void_data = void_response.data.reverse_transaction.reversal;
|
||||
|
||||
let transaction_id = void_data.id.clone();
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(void_data.status.clone()),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||
transaction_id.to_string(),
|
||||
),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BraintreePSyncRequest {
|
||||
query: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsSyncRouterData> for BraintreePSyncRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsSyncRouterData) -> Result<Self, Self::Error> {
|
||||
let transaction_id = item
|
||||
.request
|
||||
.connector_transaction_id
|
||||
.get_connector_transaction_id()
|
||||
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
|
||||
let query = format!("query {{ search {{ transactions(input: {{ id: {{is: \"{}\"}} }}, first: 1) {{ edges {{ node {{ id status createdAt amount {{ value currencyCode }} orderId }} }} }} }} }}", transaction_id);
|
||||
Ok(Self { query })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct NodeData {
|
||||
id: String,
|
||||
status: BraintreePaymentStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct EdgeData {
|
||||
node: NodeData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct TransactionData {
|
||||
edges: Vec<EdgeData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SearchData {
|
||||
transactions: TransactionData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PSyncResponseData {
|
||||
search: SearchData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum BraintreePSyncResponse {
|
||||
PSyncResponse(Box<PSyncResponse>),
|
||||
ErrorResponse(Box<ErrorResponse>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PSyncResponse {
|
||||
data: PSyncResponseData,
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, BraintreePSyncResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, BraintreePSyncResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match item.response {
|
||||
BraintreePSyncResponse::ErrorResponse(error_response) => Ok(Self {
|
||||
response: build_error_response(&error_response.errors, item.http_code),
|
||||
..item.data
|
||||
}),
|
||||
BraintreePSyncResponse::PSyncResponse(psync_response) => {
|
||||
let edge_data = psync_response
|
||||
.data
|
||||
.search
|
||||
.transactions
|
||||
.edges
|
||||
.first()
|
||||
.ok_or(errors::ConnectorError::MissingConnectorTransactionID)?;
|
||||
let transaction_id = &edge_data.node.id;
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(edge_data.node.status.clone()),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||
transaction_id.to_string(),
|
||||
),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ use masking::{PeekInterface, Secret};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connector::utils,
|
||||
connector::utils::{self},
|
||||
consts,
|
||||
core::errors,
|
||||
types::{self, api, storage::enums},
|
||||
@ -21,6 +21,7 @@ pub struct PaymentOptions {
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BraintreeMeta {
|
||||
merchant_account_id: Option<Secret<String>>,
|
||||
merchant_config_currency: Option<types::storage::enums::Currency>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Eq, PartialEq)]
|
||||
@ -41,6 +42,10 @@ pub struct BraintreeSessionRequest {
|
||||
impl TryFrom<&types::PaymentsSessionRouterData> for BraintreeSessionRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(_item: &types::PaymentsSessionRouterData) -> Result<Self, Self::Error> {
|
||||
let metadata: BraintreeMeta =
|
||||
utils::to_connector_meta_from_secret(_item.connector_meta_data.clone())?;
|
||||
|
||||
utils::validate_currency(_item.request.currency, metadata.merchant_config_currency)?;
|
||||
Ok(Self {
|
||||
client_token: BraintreeApiVersion {
|
||||
version: "2".to_string(),
|
||||
@ -93,12 +98,14 @@ pub struct CardDetails {
|
||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for BraintreePaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||
let metadata: BraintreeMeta =
|
||||
utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?;
|
||||
|
||||
utils::validate_currency(item.request.currency, metadata.merchant_config_currency)?;
|
||||
let submit_for_settlement = matches!(
|
||||
item.request.capture_method,
|
||||
Some(enums::CaptureMethod::Automatic) | None
|
||||
);
|
||||
let metadata: BraintreeMeta =
|
||||
utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?;
|
||||
let merchant_account_id = metadata.merchant_account_id;
|
||||
let amount = utils::to_currency_base_unit(item.request.amount, item.request.currency)?;
|
||||
let device_data = DeviceData {};
|
||||
@ -354,9 +361,18 @@ pub struct Amount {
|
||||
|
||||
impl<F> TryFrom<&types::RefundsRouterData<F>> for BraintreeRefundRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(_item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||
let metadata: BraintreeMeta =
|
||||
utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?;
|
||||
|
||||
utils::validate_currency(item.request.currency, metadata.merchant_config_currency)?;
|
||||
|
||||
let refund_amount =
|
||||
utils::to_currency_base_unit(item.request.refund_amount, item.request.currency)?;
|
||||
Ok(Self {
|
||||
transaction: Amount { amount: None },
|
||||
transaction: Amount {
|
||||
amount: Some(refund_amount),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1293,3 +1293,21 @@ mod error_code_error_message_tests {
|
||||
assert_eq!(error_code_error_message_none, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_currency(
|
||||
request_currency: types::storage::enums::Currency,
|
||||
merchant_config_currency: Option<types::storage::enums::Currency>,
|
||||
) -> Result<(), errors::ConnectorError> {
|
||||
let merchant_config_currency =
|
||||
merchant_config_currency.ok_or(errors::ConnectorError::NoConnectorMetaData)?;
|
||||
if request_currency != merchant_config_currency {
|
||||
Err(errors::ConnectorError::NotSupported {
|
||||
message: format!(
|
||||
"currency {} is not supported for this merchant account",
|
||||
request_currency
|
||||
),
|
||||
connector: "Braintree",
|
||||
})?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -2483,6 +2483,7 @@ pub fn router_data_type_conversion<F1, F2, Req1, Req2, Res1, Res2>(
|
||||
#[cfg(feature = "payouts")]
|
||||
quote_id: None,
|
||||
test_mode: router_data.test_mode,
|
||||
connector_api_version: router_data.connector_api_version,
|
||||
connector_http_status_code: router_data.connector_http_status_code,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
use std::{fmt::Debug, marker::PhantomData, str::FromStr};
|
||||
|
||||
use common_utils::fp_utils;
|
||||
use diesel_models::{ephemeral_key, payment_attempt::PaymentListFilters};
|
||||
@ -99,6 +99,29 @@ where
|
||||
|
||||
let customer_id = customer.to_owned().map(|customer| customer.customer_id);
|
||||
|
||||
let supported_connector = &state
|
||||
.conf
|
||||
.multiple_api_version_supported_connectors
|
||||
.supported_connectors;
|
||||
let connector_enum = api_models::enums::Connector::from_str(connector_id)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::InvalidConnectorName)
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "connector",
|
||||
})
|
||||
.attach_printable_lazy(|| format!("unable to parse connector name {connector_id:?}"))?;
|
||||
|
||||
let connector_api_version = if supported_connector.contains(&connector_enum) {
|
||||
state
|
||||
.store
|
||||
.find_config_by_key_cached(&format!("connector_api_version_{connector_id}"))
|
||||
.await
|
||||
.map(|value| value.config)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
router_data = types::RouterData {
|
||||
flow: PhantomData,
|
||||
merchant_id: merchant_account.merchant_id.clone(),
|
||||
@ -139,6 +162,7 @@ where
|
||||
quote_id: None,
|
||||
test_mode,
|
||||
payment_method_balance: None,
|
||||
connector_api_version,
|
||||
connector_http_status_code: None,
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::{marker::PhantomData, str::FromStr};
|
||||
|
||||
use api_models::enums::{DisputeStage, DisputeStatus};
|
||||
#[cfg(feature = "payouts")]
|
||||
@ -183,6 +183,7 @@ pub async fn construct_payout_router_data<'a, F>(
|
||||
quote_id: None,
|
||||
test_mode,
|
||||
payment_method_balance: None,
|
||||
connector_api_version: None,
|
||||
connector_http_status_code: None,
|
||||
};
|
||||
|
||||
@ -239,6 +240,29 @@ pub async fn construct_refund_router_data<'a, F>(
|
||||
));
|
||||
let test_mode: Option<bool> = merchant_connector_account.is_test_mode_on();
|
||||
|
||||
let supported_connector = &state
|
||||
.conf
|
||||
.multiple_api_version_supported_connectors
|
||||
.supported_connectors;
|
||||
let connector_enum = api_models::enums::Connector::from_str(connector_id)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::InvalidConnectorName)
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "connector",
|
||||
})
|
||||
.attach_printable_lazy(|| format!("unable to parse connector name {connector_id:?}"))?;
|
||||
|
||||
let connector_api_version = if supported_connector.contains(&connector_enum) {
|
||||
state
|
||||
.store
|
||||
.find_config_by_key_cached(&format!("connector_api_version_{connector_id}"))
|
||||
.await
|
||||
.map(|value| value.config)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let router_data = types::RouterData {
|
||||
flow: PhantomData,
|
||||
merchant_id: merchant_account.merchant_id.clone(),
|
||||
@ -291,6 +315,7 @@ pub async fn construct_refund_router_data<'a, F>(
|
||||
quote_id: None,
|
||||
test_mode,
|
||||
payment_method_balance: None,
|
||||
connector_api_version,
|
||||
connector_http_status_code: None,
|
||||
};
|
||||
|
||||
@ -509,6 +534,7 @@ pub async fn construct_accept_dispute_router_data<'a>(
|
||||
quote_id: None,
|
||||
test_mode,
|
||||
payment_method_balance: None,
|
||||
connector_api_version: None,
|
||||
connector_http_status_code: None,
|
||||
};
|
||||
Ok(router_data)
|
||||
@ -584,6 +610,7 @@ pub async fn construct_submit_evidence_router_data<'a>(
|
||||
#[cfg(feature = "payouts")]
|
||||
quote_id: None,
|
||||
test_mode,
|
||||
connector_api_version: None,
|
||||
connector_http_status_code: None,
|
||||
};
|
||||
Ok(router_data)
|
||||
@ -660,6 +687,7 @@ pub async fn construct_upload_file_router_data<'a>(
|
||||
#[cfg(feature = "payouts")]
|
||||
quote_id: None,
|
||||
test_mode,
|
||||
connector_api_version: None,
|
||||
connector_http_status_code: None,
|
||||
};
|
||||
Ok(router_data)
|
||||
@ -738,6 +766,7 @@ pub async fn construct_defend_dispute_router_data<'a>(
|
||||
#[cfg(feature = "payouts")]
|
||||
quote_id: None,
|
||||
test_mode,
|
||||
connector_api_version: None,
|
||||
connector_http_status_code: None,
|
||||
};
|
||||
Ok(router_data)
|
||||
@ -811,6 +840,7 @@ pub async fn construct_retrieve_file_router_data<'a>(
|
||||
#[cfg(feature = "payouts")]
|
||||
quote_id: None,
|
||||
test_mode,
|
||||
connector_api_version: None,
|
||||
connector_http_status_code: None,
|
||||
};
|
||||
Ok(router_data)
|
||||
|
||||
@ -238,6 +238,10 @@ pub struct RouterData<Flow, Request, Response> {
|
||||
pub preprocessing_id: Option<String>,
|
||||
/// This is the balance amount for gift cards or voucher
|
||||
pub payment_method_balance: Option<PaymentMethodBalance>,
|
||||
|
||||
///for switching between two different versions of the same connector
|
||||
pub connector_api_version: Option<String>,
|
||||
|
||||
/// Contains flow-specific data required to construct a request and send it to the connector.
|
||||
pub request: Request,
|
||||
|
||||
@ -954,6 +958,7 @@ impl<F1, F2, T1, T2> From<(&RouterData<F1, T1, PaymentsResponseData>, T2)>
|
||||
quote_id: data.quote_id.clone(),
|
||||
test_mode: data.test_mode,
|
||||
payment_method_balance: data.payment_method_balance.clone(),
|
||||
connector_api_version: data.connector_api_version.clone(),
|
||||
connector_http_status_code: data.connector_http_status_code,
|
||||
}
|
||||
}
|
||||
@ -1026,6 +1031,7 @@ impl<F1, F2>
|
||||
quote_id: data.quote_id.clone(),
|
||||
test_mode: data.test_mode,
|
||||
payment_method_balance: None,
|
||||
connector_api_version: None,
|
||||
connector_http_status_code: data.connector_http_status_code,
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,6 +89,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
|
||||
quote_id: None,
|
||||
test_mode: None,
|
||||
payment_method_balance: None,
|
||||
connector_api_version: None,
|
||||
connector_http_status_code: None,
|
||||
}
|
||||
}
|
||||
@ -143,6 +144,7 @@ fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
|
||||
quote_id: None,
|
||||
test_mode: None,
|
||||
payment_method_balance: None,
|
||||
connector_api_version: None,
|
||||
connector_http_status_code: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -505,6 +505,7 @@ pub trait ConnectorActions: Connector {
|
||||
quote_id: None,
|
||||
test_mode: None,
|
||||
payment_method_balance: None,
|
||||
connector_api_version: None,
|
||||
connector_http_status_code: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,6 +68,7 @@ bitpay.base_url = "https://test.bitpay.com"
|
||||
bluesnap.base_url = "https://sandbox.bluesnap.com/"
|
||||
boku.base_url = "https://country-api4-stage.boku.com"
|
||||
braintree.base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
braintree.secondary_base_url = "https://payments.sandbox.braintree-api.com/graphql"
|
||||
cashtocode.base_url = "https://cluster05.api-test.cashtocode.com"
|
||||
checkout.base_url = "https://api.sandbox.checkout.com/"
|
||||
coinbase.base_url = "https://api.commerce.coinbase.com"
|
||||
@ -179,6 +180,7 @@ red_pagos = { country = "UY", currency = "UYU" }
|
||||
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" }
|
||||
mollie = {long_lived_token = false, payment_method = "card"}
|
||||
braintree = { long_lived_token = false, payment_method = "card" }
|
||||
|
||||
[dummy_connector]
|
||||
payment_ttl = 172800
|
||||
@ -201,3 +203,6 @@ discord_invite_url = "https://discord.gg/wJZ7DVW8mm"
|
||||
|
||||
[payouts]
|
||||
payout_eligibility = true
|
||||
|
||||
[multiple_api_version_supported_connectors]
|
||||
supported_connectors = ["braintree"]
|
||||
|
||||
Reference in New Issue
Block a user