refactor(connector): [Klarna] Refactor Authorize call and configs for prod (#4750)

Co-authored-by: Samraat Bansal <samraat.bansal@juspay.in>
This commit is contained in:
Swangi Kumari
2024-05-29 15:39:10 +05:30
committed by GitHub
parent b812e596a1
commit a6570b6a06
17 changed files with 204 additions and 66 deletions

View File

@ -1,8 +1,11 @@
pub mod transformers;
use std::fmt::Debug;
use api_models::enums;
use base64::Engine;
use common_utils::request::RequestContent;
use error_stack::{report, ResultExt};
use masking::PeekInterface;
use transformers as klarna;
use crate::{
@ -51,9 +54,14 @@ impl ConnectorCommon for Klarna {
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
let auth = klarna::KlarnaAuthType::try_from(auth_type)
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
let encoded_api_key = consts::BASE64_ENGINE.encode(format!(
"{}:{}",
auth.username.peek(),
auth.password.peek()
));
Ok(vec![(
headers::AUTHORIZATION.to_string(),
auth.basic_token.into_masked(),
format!("Basic {encoded_api_key}").into_masked(),
)])
}
@ -74,11 +82,13 @@ impl ConnectorCommon for Klarna {
let reason = response
.error_messages
.map(|messages| messages.join(" & "))
.or(response.error_message);
.or(response.error_message.clone());
Ok(types::ErrorResponse {
status_code: res.status_code,
code: response.error_code,
message: consts::NO_ERROR_MESSAGE.to_string(),
message: response
.error_message
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
reason,
attempt_status: None,
connector_transaction_id: None,
@ -86,7 +96,21 @@ impl ConnectorCommon for Klarna {
}
}
impl ConnectorValidation for Klarna {}
impl ConnectorValidation for Klarna {
fn validate_capture_method(
&self,
capture_method: Option<enums::CaptureMethod>,
_pmt: Option<enums::PaymentMethodType>,
) -> CustomResult<(), errors::ConnectorError> {
let capture_method = capture_method.unwrap_or_default();
match capture_method {
enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()),
enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err(
connector_utils::construct_not_supported_error_report(capture_method, self.id()),
),
}
}
}
impl api::Payment for Klarna {}
@ -118,6 +142,22 @@ impl
// Not Implemented (R)
}
fn build_region_specific_endpoint(
base_url: &str,
connector_metadata: &Option<common_utils::pii::SecretSerdeValue>,
) -> CustomResult<String, errors::ConnectorError> {
let klarna_metadata_object =
transformers::KlarnaConnectorMetadataObject::try_from(connector_metadata)?;
let region_based_endpoint = klarna_metadata_object
.klarna_region
.ok_or(errors::ConnectorError::InvalidConnectorConfig {
config: "merchant_connector_account.metadata.region_based_endpoint",
})
.map(String::from)?;
Ok(base_url.replace("{{region_based_endpoint}}", &region_based_endpoint))
}
impl
services::ConnectorIntegration<
api::Session,
@ -147,14 +187,13 @@ impl
fn get_url(
&self,
_req: &types::PaymentsSessionRouterData,
req: &types::PaymentsSessionRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
"{}{}",
self.base_url(connectors),
"payments/v1/sessions"
))
let endpoint =
build_region_specific_endpoint(self.base_url(connectors), &req.connector_meta_data)?;
Ok(format!("{}{}", endpoint, "payments/v1/sessions"))
}
fn get_request_body(
@ -162,7 +201,14 @@ impl
req: &types::PaymentsSessionRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_req = klarna::KlarnaSessionRequest::try_from(req)?;
let connector_router_data = klarna::KlarnaRouterData::try_from((
&self.get_currency_unit(),
req.request.currency,
req.request.amount,
req,
))?;
let connector_req = klarna::KlarnaSessionRequest::try_from(&connector_router_data)?;
// encode only for for urlencoded things.
Ok(RequestContent::Json(Box::new(connector_req)))
}
@ -304,6 +350,8 @@ impl
.payment_method_type
.as_ref()
.ok_or_else(connector_utils::missing_field_err("payment_method_type"))?;
let endpoint =
build_region_specific_endpoint(self.base_url(connectors), &req.connector_meta_data)?;
match payment_method_data {
domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaSdk { token }) => {
@ -313,8 +361,7 @@ impl
common_enums::PaymentMethodType::Klarna,
) => Ok(format!(
"{}payments/v1/authorizations/{}/order",
self.base_url(connectors),
token
endpoint, token
)),
(
common_enums::PaymentExperience::DisplayQrCode
@ -430,9 +477,13 @@ impl
| domain::PaymentMethodData::Upi(_)
| domain::PaymentMethodData::Voucher(_)
| domain::PaymentMethodData::GiftCard(_)
| domain::PaymentMethodData::CardToken(_) => Err(error_stack::report!(
errors::ConnectorError::MismatchedPaymentData
)),
| domain::PaymentMethodData::CardToken(_) => {
Err(report!(errors::ConnectorError::NotImplemented(
connector_utils::get_unimplemented_payment_method_error_message(
req.connector.as_str(),
),
)))
}
}
}

View File

@ -1,11 +1,13 @@
use api_models::payments;
use error_stack::report;
use common_utils::pii;
use error_stack::{report, ResultExt};
use masking::{ExposeInterface, Secret};
use serde::{Deserialize, Serialize};
use crate::{
connector::utils::{self, PaymentsAuthorizeRequestData, RouterData},
core::errors,
types::{self, storage::enums},
types::{self, storage::enums, transformers::ForeignFrom},
};
#[derive(Debug, Serialize)]
@ -32,16 +34,50 @@ impl<T> TryFrom<(&types::api::CurrencyUnit, enums::Currency, i64, T)> for Klarna
}
}
#[derive(Default, Debug, Serialize)]
pub struct KlarnaPaymentsRequest {
order_lines: Vec<OrderLines>,
order_amount: i64,
purchase_country: String,
purchase_currency: enums::Currency,
merchant_reference1: String,
#[derive(Debug, Serialize, Deserialize)]
pub struct KlarnaConnectorMetadataObject {
pub klarna_region: Option<KlarnaEndpoint>,
}
#[derive(Default, Debug, Deserialize, Serialize)]
#[derive(Debug, Serialize, Deserialize)]
pub enum KlarnaEndpoint {
Europe,
NorthAmerica,
Oceania,
}
impl From<KlarnaEndpoint> for String {
fn from(endpoint: KlarnaEndpoint) -> Self {
Self::from(match endpoint {
KlarnaEndpoint::Europe => "",
KlarnaEndpoint::NorthAmerica => "-na",
KlarnaEndpoint::Oceania => "-oc",
})
}
}
impl TryFrom<&Option<pii::SecretSerdeValue>> for KlarnaConnectorMetadataObject {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(meta_data: &Option<pii::SecretSerdeValue>) -> Result<Self, Self::Error> {
let metadata: Self = utils::to_connector_meta_from_secret::<Self>(meta_data.clone())
.change_context(errors::ConnectorError::InvalidConnectorConfig {
config: "metadata",
})?;
Ok(metadata)
}
}
#[derive(Default, Debug, Serialize)]
pub struct KlarnaPaymentsRequest {
auto_capture: bool,
order_lines: Vec<OrderLines>,
order_amount: i64,
purchase_country: enums::CountryAlpha2,
purchase_currency: enums::Currency,
merchant_reference1: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct KlarnaPaymentsResponse {
order_id: String,
fraud_status: KlarnaFraudStatus,
@ -50,9 +86,8 @@ pub struct KlarnaPaymentsResponse {
#[derive(Debug, Serialize)]
pub struct KlarnaSessionRequest {
intent: KlarnaSessionIntent,
purchase_country: String,
purchase_country: enums::CountryAlpha2,
purchase_currency: enums::Currency,
locale: String,
order_amount: i64,
order_lines: Vec<OrderLines>,
}
@ -60,20 +95,25 @@ pub struct KlarnaSessionRequest {
#[derive(Deserialize, Serialize, Debug)]
pub struct KlarnaSessionResponse {
pub client_token: Secret<String>,
pub session_id: Secret<String>,
pub session_id: String,
}
impl TryFrom<&types::PaymentsSessionRouterData> for KlarnaSessionRequest {
impl TryFrom<&KlarnaRouterData<&types::PaymentsSessionRouterData>> for KlarnaSessionRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsSessionRouterData) -> Result<Self, Self::Error> {
let request = &item.request;
fn try_from(
item: &KlarnaRouterData<&types::PaymentsSessionRouterData>,
) -> Result<Self, Self::Error> {
let request = &item.router_data.request;
match request.order_details.clone() {
Some(order_details) => Ok(Self {
intent: KlarnaSessionIntent::Buy,
purchase_country: "US".to_string(),
purchase_country: request.country.ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "billing.address.country",
},
)?,
purchase_currency: request.currency,
order_amount: request.amount,
locale: "en-US".to_string(),
order_amount: item.amount,
order_lines: order_details
.iter()
.map(|data| OrderLines {
@ -85,7 +125,7 @@ impl TryFrom<&types::PaymentsSessionRouterData> for KlarnaSessionRequest {
.collect(),
}),
None => Err(report!(errors::ConnectorError::MissingRequiredField {
field_name: "product_name",
field_name: "order_details",
})),
}
}
@ -104,7 +144,7 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<KlarnaSessionResponse>>
session_token: types::api::SessionToken::Klarna(Box::new(
payments::KlarnaSessionTokenResponse {
session_token: response.client_token.clone().expose(),
session_id: response.session_id.clone().expose(),
session_id: response.session_id.clone(),
},
)),
}),
@ -122,9 +162,9 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsAuthorizeRouterData>> for KlarnaP
let request = &item.router_data.request;
match request.order_details.clone() {
Some(order_details) => Ok(Self {
purchase_country: "US".to_string(),
purchase_country: item.router_data.get_billing_country()?,
purchase_currency: request.currency,
order_amount: request.amount,
order_amount: item.amount,
order_lines: order_details
.iter()
.map(|data| OrderLines {
@ -134,10 +174,11 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsAuthorizeRouterData>> for KlarnaP
total_amount: i64::from(data.quantity) * (data.amount),
})
.collect(),
merchant_reference1: item.router_data.connector_request_reference_id.clone(),
merchant_reference1: Some(item.router_data.connector_request_reference_id.clone()),
auto_capture: request.is_auto_capture()?,
}),
None => Err(report!(errors::ConnectorError::MissingRequiredField {
field_name: "product_name"
field_name: "order_details"
})),
}
}
@ -163,11 +204,15 @@ impl TryFrom<types::PaymentsResponseRouterData<KlarnaPaymentsResponse>>
incremental_authorization_allowed: None,
charge_id: None,
}),
status: item.response.fraud_status.into(),
status: enums::AttemptStatus::foreign_from((
item.response.fraud_status,
item.data.request.is_auto_capture()?,
)),
..item.data
})
}
}
#[derive(Debug, Serialize)]
pub struct OrderLines {
name: String,
@ -186,15 +231,17 @@ pub enum KlarnaSessionIntent {
}
pub struct KlarnaAuthType {
pub basic_token: Secret<String>,
pub username: Secret<String>,
pub password: Secret<String>,
}
impl TryFrom<&types::ConnectorAuthType> for KlarnaAuthType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
if let types::ConnectorAuthType::HeaderKey { api_key } = auth_type {
if let types::ConnectorAuthType::BodyKey { api_key, key1 } = auth_type {
Ok(Self {
basic_token: api_key.to_owned(),
username: key1.to_owned(),
password: api_key.to_owned(),
})
} else {
Err(errors::ConnectorError::FailedToObtainAuthType.into())
@ -202,19 +249,26 @@ impl TryFrom<&types::ConnectorAuthType> for KlarnaAuthType {
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum KlarnaFraudStatus {
Accepted,
#[default]
Pending,
Rejected,
}
impl From<KlarnaFraudStatus> for enums::AttemptStatus {
fn from(item: KlarnaFraudStatus) -> Self {
match item {
KlarnaFraudStatus::Accepted => Self::Charged,
impl ForeignFrom<(KlarnaFraudStatus, bool)> for enums::AttemptStatus {
fn foreign_from((klarna_status, is_auto_capture): (KlarnaFraudStatus, bool)) -> Self {
match klarna_status {
KlarnaFraudStatus::Accepted => {
if is_auto_capture {
Self::Charged
} else {
Self::Authorized
}
}
KlarnaFraudStatus::Pending => Self::Authorizing,
KlarnaFraudStatus::Rejected => Self::Failure,
}
}
}

View File

@ -1947,6 +1947,7 @@ pub(crate) fn validate_auth_and_metadata_type(
}
api_enums::Connector::Klarna => {
klarna::transformers::KlarnaAuthType::try_from(val)?;
klarna::transformers::KlarnaConnectorMetadataObject::try_from(connector_meta_data)?;
Ok(())
}
api_enums::Connector::Mollie => {