mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(braintree): Sessions flow for braintree and klarna (#121)
This commit is contained in:
@ -61,6 +61,8 @@ base_url = "https://api.stripe.com/"
|
||||
[connectors.braintree]
|
||||
base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
|
||||
[connectors.klarna]
|
||||
base_url = "https://api-na.playground.klarna.com/"
|
||||
|
||||
[scheduler]
|
||||
stream = "SCHEDULER_STREAM"
|
||||
|
||||
@ -113,6 +113,15 @@ base_url = "https://api.stripe.com/"
|
||||
[connectors.braintree]
|
||||
base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
|
||||
[connectors.klarna]
|
||||
base_url = "https://api-na.playground.klarna.com/"
|
||||
|
||||
# This data is used to call respective connectors for wallets and cards
|
||||
[connectors.supported]
|
||||
wallets = ["klarna","braintree"]
|
||||
cards = ["stripe","adyen","authorizedotnet","checkout","braintree"]
|
||||
|
||||
|
||||
# Scheduler settings provides a point to modify the behaviour of scheduler flow.
|
||||
# It defines the the streams/queues name and configuration as well as event selection variables
|
||||
[scheduler]
|
||||
|
||||
@ -67,3 +67,11 @@ base_url = "https://api.stripe.com/"
|
||||
|
||||
[connectors.braintree]
|
||||
base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
|
||||
[connectors.klarna]
|
||||
base_url = "https://api-na.playground.klarna.com/"
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna","braintree"]
|
||||
cards = ["stripe","adyen","authorizedotnet","checkout","braintree"]
|
||||
|
||||
|
||||
@ -532,13 +532,8 @@ impl From<PaymentsStartRequest> for PaymentsResponse {
|
||||
|
||||
impl From<PaymentsSessionRequest> for PaymentsResponse {
|
||||
fn from(item: PaymentsSessionRequest) -> Self {
|
||||
let payment_id = match item.payment_id {
|
||||
PaymentIdType::PaymentIntentId(id) => Some(id),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Self {
|
||||
payment_id,
|
||||
payment_id: Some(item.payment_id),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@ -664,11 +659,12 @@ pub struct PaymentsRetrieveRequest {
|
||||
pub struct ConnectorSessionToken {
|
||||
pub connector_name: String,
|
||||
pub session_token: String,
|
||||
pub session_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, serde::Deserialize, Clone)]
|
||||
pub struct PaymentsSessionRequest {
|
||||
pub payment_id: PaymentIdType,
|
||||
pub payment_id: String,
|
||||
pub client_secret: String,
|
||||
}
|
||||
|
||||
|
||||
@ -85,3 +85,20 @@ pub mod iso8601 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// https://github.com/serde-rs/serde/issues/994#issuecomment-316895860
|
||||
|
||||
pub mod json_string {
|
||||
use serde::de::{self, Deserialize, DeserializeOwned, Deserializer};
|
||||
use serde_json;
|
||||
|
||||
/// Deserialize a string which is in json format
|
||||
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let j = String::deserialize(deserializer)?;
|
||||
serde_json::from_str(&j).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,6 +112,7 @@ pub struct Connectors {
|
||||
pub checkout: ConnectorParams,
|
||||
pub stripe: ConnectorParams,
|
||||
pub braintree: ConnectorParams,
|
||||
pub klarna: ConnectorParams,
|
||||
pub supported: SupportedConnectors,
|
||||
}
|
||||
|
||||
@ -173,7 +174,8 @@ impl Settings {
|
||||
.try_parsing(true)
|
||||
.separator("__")
|
||||
.list_separator(",")
|
||||
.with_list_parse_key("redis.cluster_urls"),
|
||||
.with_list_parse_key("redis.cluster_urls")
|
||||
.with_list_parse_key("connectors.supported.wallets"),
|
||||
)
|
||||
.build()?;
|
||||
|
||||
|
||||
@ -3,9 +3,10 @@ pub mod adyen;
|
||||
pub mod authorizedotnet;
|
||||
pub mod braintree;
|
||||
pub mod checkout;
|
||||
pub mod klarna;
|
||||
pub mod stripe;
|
||||
|
||||
pub use self::{
|
||||
aci::Aci, adyen::Adyen, authorizedotnet::Authorizedotnet, braintree::Braintree,
|
||||
checkout::Checkout, stripe::Stripe,
|
||||
checkout::Checkout, klarna::Klarna, stripe::Stripe,
|
||||
};
|
||||
|
||||
@ -61,7 +61,99 @@ impl
|
||||
types::PaymentsResponseData,
|
||||
> for Braintree
|
||||
{
|
||||
//TODO: implement sessions flow
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let mut headers = vec![
|
||||
(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::PaymentsSessionType::get_content_type(self).to_string(),
|
||||
),
|
||||
(headers::X_ROUTER.to_string(), "test".to_string()),
|
||||
(headers::X_API_VERSION.to_string(), "6".to_string()),
|
||||
(headers::ACCEPT.to_string(), "application/json".to_string()),
|
||||
];
|
||||
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
||||
headers.append(&mut api_key);
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
"application/json"
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
connectors: Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let auth_type = braintree::BraintreeAuthType::try_from(&req.connector_auth_type)
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
Ok(format!(
|
||||
"{}/merchants/{}/client_token",
|
||||
self.base_url(connectors),
|
||||
auth_type.merchant_account,
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
connectors: Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let request = Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsSessionType::get_url(self, req, connectors)?)
|
||||
.headers(types::PaymentsSessionType::get_headers(self, req)?)
|
||||
.body(types::PaymentsSessionType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
);
|
||||
|
||||
logger::debug!(session_request=?request);
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Bytes,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
let response: braintree::ErrorResponse = res
|
||||
.parse_struct("Error Response")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
Ok(ErrorResponse {
|
||||
code: consts::NO_ERROR_CODE.to_string(),
|
||||
message: response.api_error_response.message,
|
||||
reason: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
_req: &types::PaymentsSessionRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsSessionRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsSessionRouterData, errors::ConnectorError> {
|
||||
logger::debug!(payment_session_response_braintree=?res);
|
||||
let response: braintree::BraintreeSessionTokenResponse = res
|
||||
.response
|
||||
.parse_struct("braintree SessionTokenReponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PreVerify for Braintree {}
|
||||
|
||||
@ -154,12 +154,54 @@ impl<F, T>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||
impl<F, T>
|
||||
TryFrom<
|
||||
types::ResponseRouterData<F, BraintreeSessionTokenResponse, T, types::PaymentsResponseData>,
|
||||
> for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<
|
||||
F,
|
||||
BraintreeSessionTokenResponse,
|
||||
T,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(types::RouterData {
|
||||
response: Ok(types::PaymentsResponseData::SessionResponse {
|
||||
session_token: item.response.client_token.value.authorization_fingerprint,
|
||||
session_id: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BraintreePaymentsResponse {
|
||||
transaction: TransactionResponse,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthorizationFingerprint {
|
||||
authorization_fingerprint: String,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClientToken {
|
||||
#[serde(with = "common_utils::custom_serde::json_string")]
|
||||
pub value: AuthorizationFingerprint,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BraintreeSessionTokenResponse {
|
||||
pub client_token: ClientToken,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransactionResponse {
|
||||
|
||||
241
crates/router/src/connector/klarna.rs
Normal file
241
crates/router/src/connector/klarna.rs
Normal file
@ -0,0 +1,241 @@
|
||||
#![allow(dead_code)]
|
||||
mod transformers;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use bytes::Bytes;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use transformers as klarna;
|
||||
|
||||
use crate::{
|
||||
configs::settings::Connectors,
|
||||
core::errors::{self, CustomResult},
|
||||
headers,
|
||||
services::{self, logger},
|
||||
types::{
|
||||
self,
|
||||
api::{self, ConnectorCommon},
|
||||
ErrorResponse, Response,
|
||||
},
|
||||
utils::{self, BytesExt},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Klarna;
|
||||
|
||||
impl api::ConnectorCommon for Klarna {
|
||||
fn id(&self) -> &'static str {
|
||||
"klarna"
|
||||
}
|
||||
|
||||
fn common_get_content_type(&self) -> &'static str {
|
||||
"application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
fn base_url(&self, connectors: Connectors) -> String {
|
||||
connectors.klarna.base_url
|
||||
}
|
||||
|
||||
fn get_auth_header(
|
||||
&self,
|
||||
auth_type: &types::ConnectorAuthType,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let auth: klarna::KlarnaAuthType = auth_type
|
||||
.try_into()
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
Ok(vec![(headers::AUTHORIZATION.to_string(), auth.basic_token)])
|
||||
}
|
||||
}
|
||||
|
||||
impl api::Payment for Klarna {}
|
||||
|
||||
impl api::PaymentAuthorize for Klarna {}
|
||||
impl api::PaymentSync for Klarna {}
|
||||
impl api::PaymentVoid for Klarna {}
|
||||
impl api::PaymentCapture for Klarna {}
|
||||
impl api::PaymentSession for Klarna {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Session,
|
||||
types::PaymentsSessionData,
|
||||
types::PaymentsResponseData,
|
||||
> for Klarna
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let mut header = vec![
|
||||
(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::PaymentsAuthorizeType::get_content_type(self).to_string(),
|
||||
),
|
||||
(headers::X_ROUTER.to_string(), "test".to_string()),
|
||||
];
|
||||
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
||||
header.append(&mut api_key);
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsSessionRouterData,
|
||||
connectors: Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}{}",
|
||||
self.base_url(connectors),
|
||||
"payments/v1/sessions"
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
// encode only for for urlencoded things.
|
||||
let klarna_req = utils::Encode::<klarna::KlarnaSessionRequest>::convert_and_encode(req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
logger::debug!(klarna_payment_logs=?klarna_req);
|
||||
Ok(Some(klarna_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
connectors: Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsSessionType::get_url(self, req, connectors)?)
|
||||
.headers(types::PaymentsSessionType::get_headers(self, req)?)
|
||||
.header(headers::X_ROUTER, "test")
|
||||
.body(types::PaymentsSessionType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsSessionRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsSessionRouterData, errors::ConnectorError> {
|
||||
let response: klarna::KlarnaSessionResponse = res
|
||||
.response
|
||||
.parse_struct("KlarnaPaymentsResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Bytes,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
let response: klarna::KlarnaErrorResponse = res
|
||||
.parse_struct("KlarnaErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
Ok(ErrorResponse {
|
||||
code: response.error_code,
|
||||
message: response.error_messages.join(" & "),
|
||||
reason: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PreVerify for Klarna {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Verify,
|
||||
types::VerifyRequestData,
|
||||
types::PaymentsResponseData,
|
||||
> for Klarna
|
||||
{
|
||||
// TODO: Critical Implement
|
||||
}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Capture,
|
||||
types::PaymentsCaptureData,
|
||||
types::PaymentsResponseData,
|
||||
> for Klarna
|
||||
{
|
||||
// Not Implemented (R)
|
||||
}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
for Klarna
|
||||
{
|
||||
// Not Implemented (R)
|
||||
}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Authorize,
|
||||
types::PaymentsAuthorizeData,
|
||||
types::PaymentsResponseData,
|
||||
> for Klarna
|
||||
{
|
||||
//Not Implemented (R)
|
||||
}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Void,
|
||||
types::PaymentsCancelData,
|
||||
types::PaymentsResponseData,
|
||||
> for Klarna
|
||||
{
|
||||
}
|
||||
|
||||
impl api::Refund for Klarna {}
|
||||
impl api::RefundExecute for Klarna {}
|
||||
impl api::RefundSync for Klarna {}
|
||||
|
||||
impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
|
||||
for Klarna
|
||||
{
|
||||
}
|
||||
|
||||
impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
|
||||
for Klarna
|
||||
{
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl api::IncomingWebhook for Klarna {
|
||||
fn get_webhook_object_reference_id(
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
|
||||
fn get_webhook_event_type(
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
|
||||
fn get_webhook_resource_object(
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
}
|
||||
|
||||
impl services::ConnectorRedirectResponse for Klarna {}
|
||||
121
crates/router/src/connector/klarna/transformers.rs
Normal file
121
crates/router/src/connector/klarna/transformers.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
core::errors,
|
||||
types::{self, storage::enums},
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Serialize)]
|
||||
pub struct KlarnaPaymentsRequest {}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct KlarnaSessionRequest {
|
||||
intent: KlarnaSessionIntent,
|
||||
purchase_country: String,
|
||||
purchase_currency: enums::Currency,
|
||||
locale: String,
|
||||
order_amount: i32,
|
||||
order_lines: Vec<OrderLines>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct KlarnaSessionResponse {
|
||||
pub client_token: String,
|
||||
pub session_id: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&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;
|
||||
Ok(Self {
|
||||
intent: KlarnaSessionIntent::Buy,
|
||||
purchase_country: "US".to_string(),
|
||||
purchase_currency: request.currency,
|
||||
order_amount: request.amount,
|
||||
locale: "en-US".to_string(),
|
||||
order_lines: vec![OrderLines {
|
||||
name: "Battery Power Pack".to_string(),
|
||||
quantity: 1,
|
||||
unit_price: request.amount,
|
||||
total_amount: request.amount,
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<types::PaymentsSessionResponseRouterData<KlarnaSessionResponse>>
|
||||
for types::PaymentsSessionRouterData
|
||||
{
|
||||
type Error = error_stack::Report<errors::ParsingError>;
|
||||
fn try_from(
|
||||
item: types::PaymentsSessionResponseRouterData<KlarnaSessionResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let response = &item.response;
|
||||
Ok(types::RouterData {
|
||||
response: Ok(types::PaymentsResponseData::SessionResponse {
|
||||
session_id: Some(response.session_id.clone()),
|
||||
session_token: response.client_token.clone(),
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct OrderLines {
|
||||
name: String,
|
||||
quantity: u64,
|
||||
unit_price: i32,
|
||||
total_amount: i32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum KlarnaSessionIntent {
|
||||
Buy,
|
||||
Tokenize,
|
||||
BuyAndTokenize,
|
||||
}
|
||||
|
||||
pub struct KlarnaAuthType {
|
||||
pub basic_token: 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 {
|
||||
Ok(Self {
|
||||
basic_token: api_key.to_string(),
|
||||
})
|
||||
} else {
|
||||
Err(errors::ConnectorError::FailedToObtainAuthType.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum KlarnaPaymentStatus {
|
||||
Succeeded,
|
||||
Failed,
|
||||
#[default]
|
||||
Processing,
|
||||
}
|
||||
|
||||
impl From<KlarnaPaymentStatus> for enums::AttemptStatus {
|
||||
fn from(item: KlarnaPaymentStatus) -> Self {
|
||||
match item {
|
||||
KlarnaPaymentStatus::Succeeded => enums::AttemptStatus::Charged,
|
||||
KlarnaPaymentStatus::Failed => enums::AttemptStatus::Failure,
|
||||
KlarnaPaymentStatus::Processing => enums::AttemptStatus::Authorizing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct KlarnaErrorResponse {
|
||||
pub error_code: String,
|
||||
pub error_messages: Vec<String>,
|
||||
}
|
||||
@ -398,16 +398,19 @@ where
|
||||
CallConnectorAction::Trigger,
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await?;
|
||||
.await?; //FIXME: remove this error propogation
|
||||
|
||||
match res.response {
|
||||
Ok(connector_response) => {
|
||||
if let types::PaymentsResponseData::SessionResponse { session_token } =
|
||||
connector_response
|
||||
if let types::PaymentsResponseData::SessionResponse {
|
||||
session_token,
|
||||
session_id,
|
||||
} = connector_response
|
||||
{
|
||||
payment_data
|
||||
.sessions_token
|
||||
.push(api::ConnectorSessionToken {
|
||||
session_id,
|
||||
connector_name,
|
||||
session_token,
|
||||
});
|
||||
@ -558,6 +561,7 @@ pub fn should_call_connector<Op: Debug, F: Clone>(
|
||||
enums::IntentStatus::RequiresCapture
|
||||
)
|
||||
}
|
||||
"PaymentSession" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,6 +115,14 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest>
|
||||
.attach_printable("Database error when finding connector response")
|
||||
})?;
|
||||
|
||||
let customer_details = payments::CustomerDetails {
|
||||
customer_id: payment_intent.customer_id.clone(),
|
||||
name: None,
|
||||
email: None,
|
||||
phone: None,
|
||||
phone_country_code: None,
|
||||
};
|
||||
|
||||
Ok((
|
||||
Box::new(self),
|
||||
PaymentData {
|
||||
@ -137,7 +145,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest>
|
||||
sessions_token: vec![],
|
||||
connector_response,
|
||||
},
|
||||
None,
|
||||
Some(customer_details),
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -174,10 +182,7 @@ impl<F: Send + Clone> ValidateRequest<F, api::PaymentsSessionRequest> for Paymen
|
||||
operations::ValidateResult<'a>,
|
||||
)> {
|
||||
//paymentid is already generated and should be sent in the request
|
||||
let given_payment_id = request
|
||||
.payment_id
|
||||
.get_payment_intent_id()
|
||||
.change_context(errors::ApiErrorResponse::PaymentNotFound)?;
|
||||
let given_payment_id = request.payment_id.clone();
|
||||
|
||||
Ok((
|
||||
Box::new(self),
|
||||
|
||||
@ -459,8 +459,11 @@ impl<F: Clone> TryFrom<PaymentData<F>> for types::PaymentsCancelData {
|
||||
impl<F: Clone> TryFrom<PaymentData<F>> for types::PaymentsSessionData {
|
||||
type Error = errors::ApiErrorResponse;
|
||||
|
||||
fn try_from(_payment_data: PaymentData<F>) -> Result<Self, Self::Error> {
|
||||
Ok(Self {})
|
||||
fn try_from(payment_data: PaymentData<F>) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
amount: payment_data.amount.into(),
|
||||
currency: payment_data.currency,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -59,6 +59,9 @@ impl Payments {
|
||||
.app_data(web::Data::new(state))
|
||||
.service(web::resource("").route(web::post().to(payments_create)))
|
||||
.service(web::resource("/list").route(web::get().to(payments_list)))
|
||||
.service(
|
||||
web::resource("/session_tokens").route(web::get().to(payments_connector_session)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/{payment_id}")
|
||||
.route(web::get().to(payments_retrieve))
|
||||
@ -75,9 +78,6 @@ impl Payments {
|
||||
web::resource("/{payment_id}/{merchant_id}/response/{connector}")
|
||||
.route(web::get().to(payments_response)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/session_tokens").route(web::get().to(payments_connector_session)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -36,6 +36,9 @@ pub type PaymentsCancelResponseRouterData<R> =
|
||||
ResponseRouterData<api::Void, R, PaymentsCancelData, PaymentsResponseData>;
|
||||
pub type PaymentsSyncResponseRouterData<R> =
|
||||
ResponseRouterData<api::PSync, R, PaymentsSyncData, PaymentsResponseData>;
|
||||
pub type PaymentsSessionResponseRouterData<R> =
|
||||
ResponseRouterData<api::Session, R, PaymentsSessionData, PaymentsResponseData>;
|
||||
|
||||
pub type RefundsResponseRouterData<F, R> =
|
||||
ResponseRouterData<F, R, RefundsData, RefundsResponseData>;
|
||||
|
||||
@ -51,6 +54,8 @@ pub type RefundExecuteType =
|
||||
dyn services::ConnectorIntegration<api::Execute, RefundsData, RefundsResponseData>;
|
||||
pub type RefundSyncType =
|
||||
dyn services::ConnectorIntegration<api::RSync, RefundsData, RefundsResponseData>;
|
||||
pub type PaymentsSessionType =
|
||||
dyn services::ConnectorIntegration<api::Session, PaymentsSessionData, PaymentsResponseData>;
|
||||
|
||||
pub type VerifyRouterData = RouterData<api::Verify, VerifyRequestData, PaymentsResponseData>;
|
||||
|
||||
@ -118,12 +123,15 @@ pub struct PaymentsCancelData {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaymentsSessionData {
|
||||
//TODO: Add the fields here as required
|
||||
pub amount: i32,
|
||||
pub currency: storage_enums::Currency,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Debug)]
|
||||
pub struct PaymentsSessionResponseData {
|
||||
pub client_token: Option<String>,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectorSessionToken {
|
||||
pub connector_name: String,
|
||||
pub session_id: Option<String>,
|
||||
pub session_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -137,6 +145,19 @@ pub struct VerifyRequestData {
|
||||
pub setup_mandate_details: Option<payments::MandateData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaymentsTransactionResponse {
|
||||
pub resource_id: ResponseId,
|
||||
pub redirection_data: Option<services::RedirectForm>,
|
||||
pub redirect: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaymentsSessionResponse {
|
||||
pub session_id: Option<String>,
|
||||
pub session_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PaymentsResponseData {
|
||||
TransactionResponse {
|
||||
@ -147,6 +168,7 @@ pub enum PaymentsResponseData {
|
||||
},
|
||||
SessionResponse {
|
||||
session_token: String,
|
||||
session_id: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@ impl ConnectorData {
|
||||
let connector_name = types::Connector::from_str(name)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::InvalidConnectorName)
|
||||
.attach_printable_lazy(|| format!("unable to parse connector name {:?}", connector))
|
||||
.attach_printable_lazy(|| format!("unable to parse connector name {connector:?}"))
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
Ok(ConnectorData {
|
||||
connector,
|
||||
@ -102,6 +102,7 @@ impl ConnectorData {
|
||||
"checkout" => Ok(Box::new(&connector::Checkout)),
|
||||
"authorizedotnet" => Ok(Box::new(&connector::Authorizedotnet)),
|
||||
"braintree" => Ok(Box::new(&connector::Braintree)),
|
||||
"klarna" => Ok(Box::new(&connector::Klarna)),
|
||||
_ => Err(report!(errors::UnexpectedError)
|
||||
.attach_printable(format!("invalid connector name: {connector_name}")))
|
||||
.change_context(errors::ConnectorError::InvalidConnectorName)
|
||||
|
||||
@ -7,6 +7,7 @@ pub enum Connector {
|
||||
Aci,
|
||||
Authorizedotnet,
|
||||
Braintree,
|
||||
Klarna,
|
||||
#[default]
|
||||
Dummy,
|
||||
}
|
||||
|
||||
@ -57,3 +57,11 @@ base_url = "http://stripe-mock:12111/"
|
||||
|
||||
[connectors.braintree]
|
||||
base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
|
||||
[connectors.klarna]
|
||||
base_url = "https://api-na.playground.klarna.com/"
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna","braintree"]
|
||||
cards = ["stripe","adyen","authorizedotnet","checkout","braintree"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user