diff --git a/connector-template/mod.rs b/connector-template/mod.rs index 3a66db6682..75553b4ee2 100644 --- a/connector-template/mod.rs +++ b/connector-template/mod.rs @@ -70,6 +70,13 @@ impl ConnectorCommon for {{project-name | downcase | pascal_case}} { "{{project-name | downcase}}" } + fn get_currency_unit(&self) -> api::CurrencyUnit { + todo!() + // TODO! Check connector documentation, on which unit they are processing the currency. + // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, + // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + } + fn common_get_content_type(&self) -> &'static str { "application/json" } @@ -150,7 +157,14 @@ impl } fn get_request_body(&self, req: &types::PaymentsAuthorizeRouterData) -> CustomResult, errors::ConnectorError> { - let req_obj = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}PaymentsRequest::try_from(req)?; + let connector_router_data = + {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let req_obj = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}PaymentsRequest::try_from(&connector_router_data)?; let {{project-name | downcase}}_req = types::RequestBody::log_and_get_request_body(&req_obj, utils::Encode::<{{project-name | downcase}}::{{project-name | downcase | pascal_case}}PaymentsRequest>::encode_to_string_of_json) .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some({{project-name | downcase}}_req)) @@ -361,7 +375,14 @@ impl } fn get_request_body(&self, req: &types::RefundsRouterData) -> CustomResult, errors::ConnectorError> { - let req_obj = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RefundRequest::try_from(req)?; + let connector_router_data = + {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.refund_amount, + req, + ))?; + let req_obj = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RefundRequest::try_from(&connector_router_data)?; let {{project-name | downcase}}_req = types::RequestBody::log_and_get_request_body(&req_obj, utils::Encode::<{{project-name | downcase}}::{{project-name | downcase | pascal_case}}RefundRequest>::encode_to_string_of_json) .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some({{project-name | downcase}}_req)) diff --git a/connector-template/transformers.rs b/connector-template/transformers.rs index ac51bb5e38..3ed53a906a 100644 --- a/connector-template/transformers.rs +++ b/connector-template/transformers.rs @@ -1,6 +1,37 @@ use serde::{Deserialize, Serialize}; use masking::Secret; -use crate::{connector::utils::PaymentsAuthorizeRequestData,core::errors,types::{self,api, storage::enums}}; +use crate::{connector::utils::{PaymentsAuthorizeRequestData},core::errors,types::{self,api, storage::enums}}; + +//TODO: Fill the struct with respective fields +pub struct {{project-name | downcase | pascal_case}}RouterData { + pub amount: i64, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for {{project-name | downcase | pascal_case}}RouterData +{ + type Error = error_stack::Report; + fn try_from( + (_currency_unit, _currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Ok(Self { + amount, + router_data: item, + }) + } +} //TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, Eq, PartialEq)] @@ -19,10 +50,10 @@ pub struct {{project-name | downcase | pascal_case}}Card { complete: bool, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for {{project-name | downcase | pascal_case}}PaymentsRequest { +impl TryFrom<&{{project-name | downcase | pascal_case}}RouterData<&types::PaymentsAuthorizeRouterData>> for {{project-name | downcase | pascal_case}}PaymentsRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { - match item.request.payment_method_data.clone() { + fn try_from(item: &{{project-name | downcase | pascal_case}}RouterData<&types::PaymentsAuthorizeRouterData>) -> Result { + match item.router_data.request.payment_method_data.clone() { api::PaymentMethodData::Card(req_card) => { let card = {{project-name | downcase | pascal_case}}Card { name: req_card.card_holder_name, @@ -30,10 +61,10 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for {{project-name | downcase expiry_month: req_card.card_exp_month, expiry_year: req_card.card_exp_year, cvc: req_card.card_cvc, - complete: item.request.is_auto_capture()?, + complete: item.router_data.request.is_auto_capture()?, }; Ok(Self { - amount: item.request.amount, + amount: item.amount.to_owned(), card, }) } @@ -113,11 +144,11 @@ pub struct {{project-name | downcase | pascal_case}}RefundRequest { pub amount: i64 } -impl TryFrom<&types::RefundsRouterData> for {{project-name | downcase | pascal_case}}RefundRequest { +impl TryFrom<&{{project-name | downcase | pascal_case}}RouterData<&types::RefundsRouterData>> for {{project-name | downcase | pascal_case}}RefundRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from(item: &{{project-name | downcase | pascal_case}}RouterData<&types::RefundsRouterData>) -> Result { Ok(Self { - amount: item.request.refund_amount, + amount: item.amount.to_owned(), }) } } diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs index 1f8c3f3b5e..8e4e1b03ca 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/router/src/connector/braintree.rs @@ -74,6 +74,10 @@ impl ConnectorCommon for Braintree { "braintree" } + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + } + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { connectors.braintree.base_url.as_ref() } @@ -425,10 +429,19 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { let connector_api_version = &req.connector_api_version.clone(); + let connector_router_data = + braintree_graphql_transformers::BraintreeRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount_to_capture, + req, + ))?; match self.is_braintree_graphql_version(connector_api_version) { true => { let connector_request = - braintree_graphql_transformers::BraintreeCaptureRequest::try_from(req)?; + braintree_graphql_transformers::BraintreeCaptureRequest::try_from( + &connector_router_data, + )?; let braintree_req = types::RequestBody::log_and_get_request_body( &connector_request, @@ -736,10 +749,19 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { let connector_api_version = &req.connector_api_version; + let connector_router_data = + braintree_graphql_transformers::BraintreeRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; match self.is_braintree_graphql_version(connector_api_version) { true => { let connector_request = - braintree_graphql_transformers::BraintreePaymentsRequest::try_from(req)?; + braintree_graphql_transformers::BraintreePaymentsRequest::try_from( + &connector_router_data, + )?; let braintree_payment_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, @@ -1026,10 +1048,19 @@ impl ConnectorIntegration, ) -> CustomResult, errors::ConnectorError> { let connector_api_version = &req.connector_api_version; + let connector_router_data = + braintree_graphql_transformers::BraintreeRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.refund_amount, + req, + ))?; match self.is_braintree_graphql_version(connector_api_version) { true => { let connector_request = - braintree_graphql_transformers::BraintreeRefundRequest::try_from(req)?; + braintree_graphql_transformers::BraintreeRefundRequest::try_from( + connector_router_data, + )?; let braintree_refund_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, @@ -1310,11 +1341,20 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, ) -> CustomResult, errors::ConnectorError> { + let connector_router_data = + braintree_graphql_transformers::BraintreeRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; let connector_api_version = &req.connector_api_version; match self.is_braintree_graphql_version(connector_api_version) { true => { let connector_request = - braintree_graphql_transformers::BraintreePaymentsRequest::try_from(req)?; + braintree_graphql_transformers::BraintreePaymentsRequest::try_from( + &connector_router_data, + )?; let braintree_payment_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, diff --git a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs index 913c32014a..fc6a057606 100644 --- a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs +++ b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs @@ -18,6 +18,37 @@ pub const CAPTURE_TRANSACTION_MUTATION: &str = "mutation captureTransaction($inp 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)] +pub struct BraintreeRouterData { + pub amount: String, + pub router_data: T, +} + +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for BraintreeRouterData +{ + type Error = error_stack::Report; + fn try_from( + (currency_unit, currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; + Ok(Self { + amount, + router_data: item, + }) + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct PaymentInput { @@ -56,16 +87,23 @@ pub struct TransactionBody { merchant_account_id: Secret, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for BraintreePaymentsRequest { +impl TryFrom<&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>> + for BraintreePaymentsRequest +{ type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { + fn try_from( + item: &BraintreeRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { let metadata: BraintreeMeta = - utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?; - utils::validate_currency(item.request.currency, metadata.merchant_config_currency)?; + utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())?; + utils::validate_currency( + item.router_data.request.currency, + metadata.merchant_config_currency, + )?; - match item.request.payment_method_data.clone() { + match item.router_data.request.payment_method_data.clone() { api::PaymentMethodData::Card(_) => { - if item.is_three_ds() { + if item.router_data.is_three_ds() { Ok(Self::CardThreeDs(BraintreeClientTokenRequest::try_from( metadata, )?)) @@ -94,10 +132,14 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for BraintreePaymentsRequest { } } -impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for BraintreePaymentsRequest { +impl TryFrom<&BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>> + for BraintreePaymentsRequest +{ type Error = error_stack::Report; - fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result { - match item.request.payment_method_data.clone() { + fn try_from( + item: &BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { Some(api::PaymentMethodData::Card(_)) => { Ok(Self::Card(CardPaymentRequest::try_from(item)?)) } @@ -537,22 +579,24 @@ pub struct BraintreeRefundRequest { variables: BraintreeRefundVariables, } -impl TryFrom<&types::RefundsRouterData> for BraintreeRefundRequest { +impl TryFrom>> for BraintreeRefundRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from( + item: BraintreeRouterData<&types::RefundsRouterData>, + ) -> Result { let metadata: BraintreeMeta = - utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?; + utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())?; - utils::validate_currency(item.request.currency, metadata.merchant_config_currency)?; + utils::validate_currency( + item.router_data.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(), + transaction_id: item.router_data.request.connector_transaction_id.clone(), refund: RefundInputData { - amount: utils::to_currency_base_unit( - item.request.refund_amount, - item.request.currency, - )?, + amount: item.amount, merchant_account_id: metadata.merchant_account_id.ok_or( errors::ConnectorError::MissingRequiredField { field_name: "merchant_account_id", @@ -928,18 +972,17 @@ pub struct BraintreeCaptureRequest { variables: VariableCaptureInput, } -impl TryFrom<&types::PaymentsCaptureRouterData> for BraintreeCaptureRequest { +impl TryFrom<&BraintreeRouterData<&types::PaymentsCaptureRouterData>> for BraintreeCaptureRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsCaptureRouterData) -> Result { + fn try_from( + item: &BraintreeRouterData<&types::PaymentsCaptureRouterData>, + ) -> Result { let query = CAPTURE_TRANSACTION_MUTATION.to_string(); let variables = VariableCaptureInput { input: CaptureInputData { - transaction_id: item.request.connector_transaction_id.clone(), + transaction_id: item.router_data.request.connector_transaction_id.clone(), transaction: CaptureTransactionBody { - amount: utils::to_currency_base_unit( - item.request.amount_to_capture, - item.request.currency, - )?, + amount: item.amount.to_owned(), }, }, }; @@ -1231,14 +1274,20 @@ impl TryFrom for BraintreeClientTokenRequest { } } -impl TryFrom<(&types::PaymentsAuthorizeRouterData, BraintreeMeta)> for CardPaymentRequest { +impl + TryFrom<( + &BraintreeRouterData<&types::PaymentsAuthorizeRouterData>, + BraintreeMeta, + )> for CardPaymentRequest +{ type Error = error_stack::Report; fn try_from( - payment_info: (&types::PaymentsAuthorizeRouterData, BraintreeMeta), + (item, metadata): ( + &BraintreeRouterData<&types::PaymentsAuthorizeRouterData>, + BraintreeMeta, + ), ) -> Result { - let item = payment_info.0; - let metadata = payment_info.1; - let query = match item.request.is_auto_capture()? { + let query = match item.router_data.request.is_auto_capture()? { true => CHARGE_CREDIT_CARD_MUTATION.to_string(), false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(), }; @@ -1246,17 +1295,14 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, BraintreeMeta)> for CardPayme query, variables: VariablePaymentInput { input: PaymentInput { - payment_method_id: match item.get_payment_method_token()? { + payment_method_id: match item.router_data.get_payment_method_token()? { types::PaymentMethodToken::Token(token) => token, types::PaymentMethodToken::ApplePayDecrypt(_) => { Err(errors::ConnectorError::InvalidWalletToken)? } }, transaction: TransactionBody { - amount: utils::to_currency_base_unit( - item.request.amount, - item.request.currency, - )?, + amount: item.amount.to_owned(), merchant_account_id: metadata.merchant_account_id.ok_or( errors::ConnectorError::MissingRequiredField { field_name: "merchant_account_id", @@ -1269,15 +1315,22 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, BraintreeMeta)> for CardPayme } } -impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for CardPaymentRequest { +impl TryFrom<&BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>> + for CardPaymentRequest +{ type Error = error_stack::Report; - fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result { + fn try_from( + item: &BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + ) -> Result { let metadata: BraintreeMeta = - utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?; - utils::validate_currency(item.request.currency, metadata.merchant_config_currency)?; + utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())?; + utils::validate_currency( + item.router_data.request.currency, + metadata.merchant_config_currency, + )?; let payload_data = utils::PaymentsCompleteAuthorizeRequestData::get_redirect_response_payload( - &item.request, + &item.router_data.request, )? .expose(); let redirection_response: BraintreeRedirectionResponse = @@ -1293,21 +1346,19 @@ impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for CardPaymentRequest .change_context(errors::ConnectorError::MissingConnectorRedirectionPayload { field_name: "three_ds_data", })?; - let query = - match utils::PaymentsCompleteAuthorizeRequestData::is_auto_capture(&item.request)? { - true => CHARGE_CREDIT_CARD_MUTATION.to_string(), - false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(), - }; + let query = match utils::PaymentsCompleteAuthorizeRequestData::is_auto_capture( + &item.router_data.request, + )? { + true => CHARGE_CREDIT_CARD_MUTATION.to_string(), + false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(), + }; Ok(Self { query, variables: VariablePaymentInput { input: PaymentInput { payment_method_id: three_ds_data.nonce, transaction: TransactionBody { - amount: utils::to_currency_base_unit( - item.request.amount, - item.request.currency, - )?, + amount: item.amount.to_owned(), merchant_account_id: metadata.merchant_account_id.ok_or( errors::ConnectorError::MissingRequiredField { field_name: "merchant_account_id", diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 7f6bbdc30c..d7f0cf6d40 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -994,6 +994,33 @@ pub fn to_currency_base_unit_from_optional_amount( } } +pub fn get_amount_as_string( + currency_unit: &types::api::CurrencyUnit, + amount: i64, + currency: diesel_models::enums::Currency, +) -> Result> { + let amount = match currency_unit { + types::api::CurrencyUnit::Minor => amount.to_string(), + types::api::CurrencyUnit::Base => to_currency_base_unit(amount, currency)?, + }; + Ok(amount) +} + +pub fn get_amount_as_f64( + currency_unit: &types::api::CurrencyUnit, + amount: i64, + currency: diesel_models::enums::Currency, +) -> Result> { + let amount = match currency_unit { + types::api::CurrencyUnit::Base => to_currency_base_unit_asf64(amount, currency)?, + types::api::CurrencyUnit::Minor => u32::try_from(amount) + .into_report() + .change_context(errors::ConnectorError::ParsingFailed)? + .into(), + }; + Ok(amount) +} + pub fn to_currency_base_unit( amount: i64, currency: diesel_models::enums::Currency, @@ -1001,7 +1028,7 @@ pub fn to_currency_base_unit( currency .to_currency_base_unit(amount) .into_report() - .change_context(errors::ConnectorError::RequestEncodingFailed) + .change_context(errors::ConnectorError::ParsingFailed) } pub fn to_currency_lower_unit( @@ -1050,7 +1077,7 @@ pub fn to_currency_base_unit_asf64( currency .to_currency_base_unit_asf64(amount) .into_report() - .change_context(errors::ConnectorError::RequestEncodingFailed) + .change_context(errors::ConnectorError::ParsingFailed) } pub fn str_to_f32(value: &str, serializer: S) -> Result diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 23c9f850da..77df9122d4 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -47,10 +47,20 @@ pub trait ConnectorTransactionId: ConnectorCommon + Sync { } } +pub enum CurrencyUnit { + Base, + Minor, +} + pub trait ConnectorCommon { /// Name of the connector (in lowercase). fn id(&self) -> &'static str; + /// Connector accepted currency unit as either "Base" or "Minor" + fn get_currency_unit(&self) -> CurrencyUnit { + CurrencyUnit::Minor // Default implementation should be remove once it is implemented in all connectors + } + /// HTTP header used for authorization. fn get_auth_header( &self,