mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 03:13:56 +08:00
feat(klarna): wallet payment through klarna (#182)
This commit is contained in:
@ -14,6 +14,11 @@ pub enum PaymentOp {
|
|||||||
Confirm,
|
Confirm,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
pub struct Metadata {
|
||||||
|
pub order_details: OrderDetails,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct PaymentsRequest {
|
pub struct PaymentsRequest {
|
||||||
@ -210,10 +215,30 @@ pub struct CCard {
|
|||||||
pub card_cvc: Secret<String>,
|
pub card_cvc: Secret<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct PayLaterData {
|
#[serde(rename_all = "snake_case")]
|
||||||
pub billing_email: String,
|
pub enum KlarnaRedirectIssuer {
|
||||||
pub country: String,
|
Stripe,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum KlarnaSdkIssuer {
|
||||||
|
Klarna,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum PayLaterData {
|
||||||
|
KlarnaRedirect {
|
||||||
|
issuer_name: KlarnaRedirectIssuer,
|
||||||
|
billing_email: String,
|
||||||
|
billing_country: String,
|
||||||
|
},
|
||||||
|
KlarnaSdk {
|
||||||
|
issuer_name: KlarnaSdkIssuer,
|
||||||
|
token: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
@ -704,7 +729,22 @@ pub struct PaymentsRetrieveRequest {
|
|||||||
pub connector: Option<String>,
|
pub connector: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, serde::Deserialize, Clone)]
|
#[derive(Debug, serde::Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum SupportedWallets {
|
||||||
|
Paypal,
|
||||||
|
ApplePay,
|
||||||
|
Klarna,
|
||||||
|
Gpay,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
pub struct OrderDetails {
|
||||||
|
pub product_name: String,
|
||||||
|
pub quantity: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct PaymentsSessionRequest {
|
pub struct PaymentsSessionRequest {
|
||||||
pub payment_id: String,
|
pub payment_id: String,
|
||||||
pub client_secret: String,
|
pub client_secret: String,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
mod transformers;
|
mod transformers;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use api_models::payments as api_payments;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use error_stack::{IntoReport, ResultExt};
|
use error_stack::{IntoReport, ResultExt};
|
||||||
use transformers as klarna;
|
use transformers as klarna;
|
||||||
@ -27,7 +27,7 @@ impl api::ConnectorCommon for Klarna {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn common_get_content_type(&self) -> &'static str {
|
fn common_get_content_type(&self) -> &'static str {
|
||||||
"application/x-www-form-urlencoded"
|
"application/json"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
|
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
|
||||||
@ -99,7 +99,7 @@ impl
|
|||||||
// encode only for for urlencoded things.
|
// encode only for for urlencoded things.
|
||||||
let klarna_req = utils::Encode::<klarna::KlarnaSessionRequest>::convert_and_encode(req)
|
let klarna_req = utils::Encode::<klarna::KlarnaSessionRequest>::convert_and_encode(req)
|
||||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||||
logger::debug!(klarna_payment_logs=?klarna_req);
|
logger::debug!(klarna_session_request_logs=?klarna_req);
|
||||||
Ok(Some(klarna_req))
|
Ok(Some(klarna_req))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +113,6 @@ impl
|
|||||||
.method(services::Method::Post)
|
.method(services::Method::Post)
|
||||||
.url(&types::PaymentsSessionType::get_url(self, req, connectors)?)
|
.url(&types::PaymentsSessionType::get_url(self, req, connectors)?)
|
||||||
.headers(types::PaymentsSessionType::get_headers(self, req)?)
|
.headers(types::PaymentsSessionType::get_headers(self, req)?)
|
||||||
.header(headers::X_ROUTER, "test")
|
|
||||||
.body(types::PaymentsSessionType::get_request_body(self, req)?)
|
.body(types::PaymentsSessionType::get_request_body(self, req)?)
|
||||||
.build(),
|
.build(),
|
||||||
))
|
))
|
||||||
@ -124,9 +123,10 @@ impl
|
|||||||
data: &types::PaymentsSessionRouterData,
|
data: &types::PaymentsSessionRouterData,
|
||||||
res: types::Response,
|
res: types::Response,
|
||||||
) -> CustomResult<types::PaymentsSessionRouterData, errors::ConnectorError> {
|
) -> CustomResult<types::PaymentsSessionRouterData, errors::ConnectorError> {
|
||||||
|
logger::debug!(klarna_session_response_logs=?res);
|
||||||
let response: klarna::KlarnaSessionResponse = res
|
let response: klarna::KlarnaSessionResponse = res
|
||||||
.response
|
.response
|
||||||
.parse_struct("KlarnaPaymentsResponse")
|
.parse_struct("KlarnaSessionResponse")
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
types::RouterData::try_from(types::ResponseRouterData {
|
types::RouterData::try_from(types::ResponseRouterData {
|
||||||
response,
|
response,
|
||||||
@ -140,6 +140,7 @@ impl
|
|||||||
&self,
|
&self,
|
||||||
res: Bytes,
|
res: Bytes,
|
||||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
logger::debug!(klarna_session_error_logs=?res);
|
||||||
let response: klarna::KlarnaErrorResponse = res
|
let response: klarna::KlarnaErrorResponse = res
|
||||||
.parse_struct("KlarnaErrorResponse")
|
.parse_struct("KlarnaErrorResponse")
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
@ -187,7 +188,108 @@ impl
|
|||||||
types::PaymentsResponseData,
|
types::PaymentsResponseData,
|
||||||
> for Klarna
|
> for Klarna
|
||||||
{
|
{
|
||||||
//Not Implemented (R)
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsAuthorizeRouterData,
|
||||||
|
) -> 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::PaymentsAuthorizeRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
let payment_method_data = &req.request.payment_method_data;
|
||||||
|
match payment_method_data {
|
||||||
|
api_payments::PaymentMethod::PayLater(api_payments::PayLaterData::KlarnaSdk {
|
||||||
|
token,
|
||||||
|
..
|
||||||
|
}) => Ok(format!(
|
||||||
|
"{}payments/v1/authorizations/{}/order",
|
||||||
|
self.base_url(connectors),
|
||||||
|
token
|
||||||
|
)),
|
||||||
|
_ => Err(error_stack::report!(
|
||||||
|
errors::ConnectorError::NotImplemented(
|
||||||
|
"We only support wallet payments through klarna".to_string(),
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request_body(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsAuthorizeRouterData,
|
||||||
|
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||||
|
let klarna_req = utils::Encode::<klarna::KlarnaPaymentsRequest>::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::PaymentsAuthorizeRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||||
|
Ok(Some(
|
||||||
|
services::RequestBuilder::new()
|
||||||
|
.method(services::Method::Post)
|
||||||
|
.url(&types::PaymentsAuthorizeType::get_url(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.headers(types::PaymentsAuthorizeType::get_headers(self, req)?)
|
||||||
|
.body(types::PaymentsAuthorizeType::get_request_body(self, req)?)
|
||||||
|
.build(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_response(
|
||||||
|
&self,
|
||||||
|
data: &types::PaymentsAuthorizeRouterData,
|
||||||
|
res: types::Response,
|
||||||
|
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
|
||||||
|
logger::debug!(klarna_raw_response=?res);
|
||||||
|
let response: klarna::KlarnaPaymentsResponse = 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<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
logger::debug!(klarna_error_response=?res);
|
||||||
|
let response: klarna::KlarnaErrorResponse = res
|
||||||
|
.parse_struct("KlarnaErrorResponse")
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
Ok(types::ErrorResponse {
|
||||||
|
code: response.error_code,
|
||||||
|
message: response.error_messages.join(" & "),
|
||||||
|
reason: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl
|
impl
|
||||||
|
|||||||
@ -1,13 +1,26 @@
|
|||||||
|
use error_stack::{report, IntoReport, ResultExt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::errors,
|
core::errors,
|
||||||
|
services,
|
||||||
types::{self, storage::enums},
|
types::{self, storage::enums},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize)]
|
#[derive(Default, Debug, Serialize)]
|
||||||
pub struct KlarnaPaymentsRequest {}
|
pub struct KlarnaPaymentsRequest {
|
||||||
|
order_lines: Vec<OrderLines>,
|
||||||
|
order_amount: i64,
|
||||||
|
purchase_country: String,
|
||||||
|
purchase_currency: enums::Currency,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Deserialize)]
|
||||||
|
pub struct KlarnaPaymentsResponse {
|
||||||
|
order_id: String,
|
||||||
|
redirection_url: String,
|
||||||
|
}
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct KlarnaSessionRequest {
|
pub struct KlarnaSessionRequest {
|
||||||
intent: KlarnaSessionIntent,
|
intent: KlarnaSessionIntent,
|
||||||
@ -28,19 +41,24 @@ impl TryFrom<&types::PaymentsSessionRouterData> for KlarnaSessionRequest {
|
|||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
fn try_from(item: &types::PaymentsSessionRouterData) -> Result<Self, Self::Error> {
|
fn try_from(item: &types::PaymentsSessionRouterData) -> Result<Self, Self::Error> {
|
||||||
let request = &item.request;
|
let request = &item.request;
|
||||||
Ok(Self {
|
match request.order_details.clone() {
|
||||||
intent: KlarnaSessionIntent::Buy,
|
Some(order_details) => Ok(Self {
|
||||||
purchase_country: "US".to_string(),
|
intent: KlarnaSessionIntent::Buy,
|
||||||
purchase_currency: request.currency,
|
purchase_country: "US".to_string(),
|
||||||
order_amount: request.amount,
|
purchase_currency: request.currency,
|
||||||
locale: "en-US".to_string(),
|
order_amount: request.amount,
|
||||||
order_lines: vec![OrderLines {
|
locale: "en-US".to_string(),
|
||||||
name: "Battery Power Pack".to_string(),
|
order_lines: vec![OrderLines {
|
||||||
quantity: 1,
|
name: order_details.product_name,
|
||||||
unit_price: request.amount,
|
quantity: order_details.quantity,
|
||||||
total_amount: request.amount,
|
unit_price: request.amount,
|
||||||
}],
|
total_amount: request.amount,
|
||||||
})
|
}],
|
||||||
|
}),
|
||||||
|
None => Err(report!(errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "product_name".to_string()
|
||||||
|
})),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,16 +82,71 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<KlarnaSessionResponse>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
impl TryFrom<&types::PaymentsAuthorizeRouterData> for KlarnaPaymentsRequest {
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||||
|
let request = &item.request;
|
||||||
|
match request.order_details.clone() {
|
||||||
|
Some(order_details) => Ok(Self {
|
||||||
|
purchase_country: "US".to_string(),
|
||||||
|
purchase_currency: request.currency,
|
||||||
|
order_amount: request.amount,
|
||||||
|
order_lines: vec![OrderLines {
|
||||||
|
name: order_details.product_name,
|
||||||
|
quantity: order_details.quantity,
|
||||||
|
unit_price: request.amount,
|
||||||
|
total_amount: request.amount,
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
None => Err(report!(errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "product_name".to_string()
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<types::PaymentsResponseRouterData<KlarnaPaymentsResponse>>
|
||||||
|
for types::PaymentsAuthorizeRouterData
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<errors::ParsingError>;
|
||||||
|
fn try_from(
|
||||||
|
item: types::PaymentsResponseRouterData<KlarnaPaymentsResponse>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
let response = &item.response;
|
||||||
|
let url = Url::parse(&response.redirection_url)
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ParsingError)
|
||||||
|
.attach_printable("Could not parse the redirection data")?;
|
||||||
|
let redirection_data = services::RedirectForm {
|
||||||
|
url: url.to_string(),
|
||||||
|
method: services::Method::Get,
|
||||||
|
form_fields: std::collections::HashMap::from_iter(
|
||||||
|
url.query_pairs()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_string())),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
|
resource_id: types::ResponseId::ConnectorTransactionId(item.response.order_id),
|
||||||
|
redirect: true,
|
||||||
|
redirection_data: Some(redirection_data),
|
||||||
|
mandate_reference: None,
|
||||||
|
}),
|
||||||
|
..item.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
pub struct OrderLines {
|
pub struct OrderLines {
|
||||||
name: String,
|
name: String,
|
||||||
quantity: u64,
|
quantity: u16,
|
||||||
unit_price: i64,
|
unit_price: i64,
|
||||||
total_amount: i64,
|
total_amount: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub enum KlarnaSessionIntent {
|
pub enum KlarnaSessionIntent {
|
||||||
Buy,
|
Buy,
|
||||||
Tokenize,
|
Tokenize,
|
||||||
|
|||||||
@ -176,14 +176,25 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
api::PaymentMethod::BankTransfer => StripePaymentMethodData::Bank,
|
api::PaymentMethod::BankTransfer => StripePaymentMethodData::Bank,
|
||||||
api::PaymentMethod::PayLater(ref klarna_data) => {
|
api::PaymentMethod::PayLater(ref pay_later_data) => match pay_later_data {
|
||||||
StripePaymentMethodData::Klarna(StripeKlarnaData {
|
api_models::payments::PayLaterData::KlarnaRedirect {
|
||||||
|
billing_email,
|
||||||
|
billing_country,
|
||||||
|
..
|
||||||
|
} => StripePaymentMethodData::Klarna(StripeKlarnaData {
|
||||||
payment_method_types: "klarna".to_string(),
|
payment_method_types: "klarna".to_string(),
|
||||||
payment_method_data_type: "klarna".to_string(),
|
payment_method_data_type: "klarna".to_string(),
|
||||||
billing_email: klarna_data.billing_email.clone(),
|
billing_email: billing_email.to_string(),
|
||||||
billing_country: klarna_data.country.clone(),
|
billing_country: billing_country.to_string(),
|
||||||
})
|
}),
|
||||||
}
|
api_models::payments::PayLaterData::KlarnaSdk { .. } => Err(
|
||||||
|
error_stack::report!(errors::ApiErrorResponse::NotImplemented)
|
||||||
|
.attach_printable(
|
||||||
|
"Stripe does not support klarna sdk payments".to_string(),
|
||||||
|
)
|
||||||
|
.change_context(errors::ParsingError),
|
||||||
|
)?,
|
||||||
|
},
|
||||||
api::PaymentMethod::Wallet(_) => StripePaymentMethodData::Wallet,
|
api::PaymentMethod::Wallet(_) => StripePaymentMethodData::Wallet,
|
||||||
api::PaymentMethod::Paypal => StripePaymentMethodData::Paypal,
|
api::PaymentMethod::Paypal => StripePaymentMethodData::Paypal,
|
||||||
}),
|
}),
|
||||||
@ -253,7 +264,7 @@ impl TryFrom<&types::VerifyRouterData> for SetupIntentRequest {
|
|||||||
let metadata_txn_uuid = Uuid::new_v4().to_string();
|
let metadata_txn_uuid = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
let payment_data: StripePaymentMethodData =
|
let payment_data: StripePaymentMethodData =
|
||||||
(item.request.payment_method_data.clone(), item.auth_type).into();
|
(item.request.payment_method_data.clone(), item.auth_type).try_into()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
confirm: true,
|
confirm: true,
|
||||||
@ -751,10 +762,13 @@ pub struct StripeWebhookObjectId {
|
|||||||
pub data: StripeWebhookDataId,
|
pub data: StripeWebhookDataId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(api::PaymentMethod, enums::AuthenticationType)> for StripePaymentMethodData {
|
impl TryFrom<(api::PaymentMethod, enums::AuthenticationType)> for StripePaymentMethodData {
|
||||||
fn from((pm_data, auth_type): (api::PaymentMethod, enums::AuthenticationType)) -> Self {
|
type Error = error_stack::Report<errors::ParsingError>;
|
||||||
|
fn try_from(
|
||||||
|
(pm_data, auth_type): (api::PaymentMethod, enums::AuthenticationType),
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
match pm_data {
|
match pm_data {
|
||||||
api::PaymentMethod::Card(ref ccard) => Self::Card({
|
api::PaymentMethod::Card(ref ccard) => Ok(Self::Card({
|
||||||
let payment_method_auth_type = match auth_type {
|
let payment_method_auth_type = match auth_type {
|
||||||
enums::AuthenticationType::ThreeDs => Auth3ds::Any,
|
enums::AuthenticationType::ThreeDs => Auth3ds::Any,
|
||||||
enums::AuthenticationType::NoThreeDs => Auth3ds::Automatic,
|
enums::AuthenticationType::NoThreeDs => Auth3ds::Automatic,
|
||||||
@ -768,16 +782,27 @@ impl From<(api::PaymentMethod, enums::AuthenticationType)> for StripePaymentMeth
|
|||||||
payment_method_data_card_cvc: ccard.card_cvc.clone(),
|
payment_method_data_card_cvc: ccard.card_cvc.clone(),
|
||||||
payment_method_auth_type,
|
payment_method_auth_type,
|
||||||
}
|
}
|
||||||
}),
|
})),
|
||||||
api::PaymentMethod::BankTransfer => Self::Bank,
|
api::PaymentMethod::BankTransfer => Ok(Self::Bank),
|
||||||
api::PaymentMethod::PayLater(ref klarna_data) => Self::Klarna(StripeKlarnaData {
|
api::PaymentMethod::PayLater(pay_later_data) => match pay_later_data {
|
||||||
payment_method_types: "klarna".to_string(),
|
api_models::payments::PayLaterData::KlarnaRedirect {
|
||||||
payment_method_data_type: "klarna".to_string(),
|
billing_email,
|
||||||
billing_email: klarna_data.billing_email.clone(),
|
billing_country: country,
|
||||||
billing_country: klarna_data.country.clone(),
|
..
|
||||||
}),
|
} => Ok(Self::Klarna(StripeKlarnaData {
|
||||||
api::PaymentMethod::Wallet(_) => Self::Wallet,
|
payment_method_types: "klarna".to_string(),
|
||||||
api::PaymentMethod::Paypal => Self::Paypal,
|
payment_method_data_type: "klarna".to_string(),
|
||||||
|
billing_email,
|
||||||
|
billing_country: country,
|
||||||
|
})),
|
||||||
|
api_models::payments::PayLaterData::KlarnaSdk { .. } => Err(error_stack::report!(
|
||||||
|
errors::ApiErrorResponse::NotImplemented
|
||||||
|
)
|
||||||
|
.attach_printable("Stripe does not support klarna sdk payments".to_string())
|
||||||
|
.change_context(errors::ParsingError))?,
|
||||||
|
},
|
||||||
|
api::PaymentMethod::Wallet(_) => Ok(Self::Wallet),
|
||||||
|
api::PaymentMethod::Paypal => Ok(Self::Paypal),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -144,7 +144,7 @@ pub trait Domain<F: Clone, R>: Send + Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait UpdateTracker<F, D, R>: Send {
|
pub trait UpdateTracker<F, D, Req>: Send {
|
||||||
async fn update_trackers<'b>(
|
async fn update_trackers<'b>(
|
||||||
&'b self,
|
&'b self,
|
||||||
db: &dyn StorageInterface,
|
db: &dyn StorageInterface,
|
||||||
@ -152,7 +152,7 @@ pub trait UpdateTracker<F, D, R>: Send {
|
|||||||
payment_data: D,
|
payment_data: D,
|
||||||
customer: Option<Customer>,
|
customer: Option<Customer>,
|
||||||
storage_scheme: enums::MerchantStorageScheme,
|
storage_scheme: enums::MerchantStorageScheme,
|
||||||
) -> RouterResult<(BoxedOperation<'b, F, R>, D)>
|
) -> RouterResult<(BoxedOperation<'b, F, Req>, D)>
|
||||||
where
|
where
|
||||||
F: 'b + Send;
|
F: 'b + Send;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -509,6 +509,7 @@ impl PaymentCreate {
|
|||||||
billing_address_id,
|
billing_address_id,
|
||||||
statement_descriptor_name: request.statement_descriptor_name.clone(),
|
statement_descriptor_name: request.statement_descriptor_name.clone(),
|
||||||
statement_descriptor_suffix: request.statement_descriptor_suffix.clone(),
|
statement_descriptor_suffix: request.statement_descriptor_suffix.clone(),
|
||||||
|
metadata: request.metadata.clone(),
|
||||||
..storage::PaymentIntentNew::default()
|
..storage::PaymentIntentNew::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -157,11 +157,11 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsSessionRequest> for
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn update_trackers<'b>(
|
async fn update_trackers<'b>(
|
||||||
&'b self,
|
&'b self,
|
||||||
_db: &dyn StorageInterface,
|
db: &dyn StorageInterface,
|
||||||
_payment_id: &api::PaymentIdType,
|
_payment_id: &api::PaymentIdType,
|
||||||
payment_data: PaymentData<F>,
|
mut payment_data: PaymentData<F>,
|
||||||
_customer: Option<storage::Customer>,
|
_customer: Option<storage::Customer>,
|
||||||
_storage_scheme: enums::MerchantStorageScheme,
|
storage_scheme: enums::MerchantStorageScheme,
|
||||||
) -> RouterResult<(
|
) -> RouterResult<(
|
||||||
BoxedOperation<'b, F, api::PaymentsSessionRequest>,
|
BoxedOperation<'b, F, api::PaymentsSessionRequest>,
|
||||||
PaymentData<F>,
|
PaymentData<F>,
|
||||||
@ -169,6 +169,21 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsSessionRequest> for
|
|||||||
where
|
where
|
||||||
F: 'b + Send,
|
F: 'b + Send,
|
||||||
{
|
{
|
||||||
|
let metadata = payment_data.payment_intent.metadata.clone();
|
||||||
|
payment_data.payment_intent = match metadata {
|
||||||
|
Some(metadata) => db
|
||||||
|
.update_payment_intent(
|
||||||
|
payment_data.payment_intent,
|
||||||
|
storage::PaymentIntentUpdate::MetadataUpdate { metadata },
|
||||||
|
storage_scheme,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|error| {
|
||||||
|
error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
|
||||||
|
})?,
|
||||||
|
None => payment_data.payment_intent,
|
||||||
|
};
|
||||||
|
|
||||||
Ok((Box::new(self), payment_data))
|
Ok((Box::new(self), payment_data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -394,6 +394,23 @@ impl<F: Clone> TryFrom<PaymentData<F>> for types::PaymentsAuthorizeData {
|
|||||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||||
field_name: "browser_info",
|
field_name: "browser_info",
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let parsed_metadata: Option<api_models::payments::Metadata> = payment_data
|
||||||
|
.payment_intent
|
||||||
|
.metadata
|
||||||
|
.map(|metadata_value| {
|
||||||
|
metadata_value
|
||||||
|
.parse_value("metadata")
|
||||||
|
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||||
|
field_name: "metadata",
|
||||||
|
})
|
||||||
|
.attach_printable("unable to parse metadata")
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let order_details = parsed_metadata.map(|data| data.order_details);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
payment_method_data: {
|
payment_method_data: {
|
||||||
let payment_method_type = payment_data
|
let payment_method_type = payment_data
|
||||||
@ -418,6 +435,7 @@ impl<F: Clone> TryFrom<PaymentData<F>> for types::PaymentsAuthorizeData {
|
|||||||
amount: payment_data.amount.into(),
|
amount: payment_data.amount.into(),
|
||||||
currency: payment_data.currency,
|
currency: payment_data.currency,
|
||||||
browser_info,
|
browser_info,
|
||||||
|
order_details,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -469,9 +487,25 @@ impl<F: Clone> TryFrom<PaymentData<F>> for types::PaymentsCancelData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<F: Clone> TryFrom<PaymentData<F>> for types::PaymentsSessionData {
|
impl<F: Clone> TryFrom<PaymentData<F>> for types::PaymentsSessionData {
|
||||||
type Error = errors::ApiErrorResponse;
|
type Error = error_stack::Report<errors::ApiErrorResponse>;
|
||||||
|
|
||||||
fn try_from(payment_data: PaymentData<F>) -> Result<Self, Self::Error> {
|
fn try_from(payment_data: PaymentData<F>) -> Result<Self, Self::Error> {
|
||||||
|
let parsed_metadata: Option<api_models::payments::Metadata> = payment_data
|
||||||
|
.payment_intent
|
||||||
|
.metadata
|
||||||
|
.map(|metadata_value| {
|
||||||
|
metadata_value
|
||||||
|
.parse_value("metadata")
|
||||||
|
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||||
|
field_name: "metadata",
|
||||||
|
})
|
||||||
|
.attach_printable("unable to parse metadata")
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let order_details = parsed_metadata.map(|data| data.order_details);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
amount: payment_data.amount.into(),
|
amount: payment_data.amount.into(),
|
||||||
currency: payment_data.currency,
|
currency: payment_data.currency,
|
||||||
@ -480,6 +514,7 @@ impl<F: Clone> TryFrom<PaymentData<F>> for types::PaymentsSessionData {
|
|||||||
.billing
|
.billing
|
||||||
.and_then(|billing_address| billing_address.address.map(|address| address.country))
|
.and_then(|billing_address| billing_address.address.map(|address| address.country))
|
||||||
.flatten(),
|
.flatten(),
|
||||||
|
order_details,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,6 +101,7 @@ pub struct PaymentsAuthorizeData {
|
|||||||
pub off_session: Option<bool>,
|
pub off_session: Option<bool>,
|
||||||
pub setup_mandate_details: Option<payments::MandateData>,
|
pub setup_mandate_details: Option<payments::MandateData>,
|
||||||
pub browser_info: Option<BrowserInformation>,
|
pub browser_info: Option<BrowserInformation>,
|
||||||
|
pub order_details: Option<api_models::payments::OrderDetails>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -127,6 +128,7 @@ pub struct PaymentsSessionData {
|
|||||||
pub amount: i64,
|
pub amount: i64,
|
||||||
pub currency: storage_enums::Currency,
|
pub currency: storage_enums::Currency,
|
||||||
pub country: Option<String>,
|
pub country: Option<String>,
|
||||||
|
pub order_details: Option<api_models::payments::OrderDetails>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|||||||
@ -47,6 +47,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
|
|||||||
setup_mandate_details: None,
|
setup_mandate_details: None,
|
||||||
capture_method: None,
|
capture_method: None,
|
||||||
browser_info: None,
|
browser_info: None,
|
||||||
|
order_details: None,
|
||||||
},
|
},
|
||||||
response: Err(types::ErrorResponse::default()),
|
response: Err(types::ErrorResponse::default()),
|
||||||
payment_method_id: None,
|
payment_method_id: None,
|
||||||
|
|||||||
@ -47,6 +47,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
|
|||||||
setup_mandate_details: None,
|
setup_mandate_details: None,
|
||||||
capture_method: None,
|
capture_method: None,
|
||||||
browser_info: None,
|
browser_info: None,
|
||||||
|
order_details: None,
|
||||||
},
|
},
|
||||||
payment_method_id: None,
|
payment_method_id: None,
|
||||||
response: Err(types::ErrorResponse::default()),
|
response: Err(types::ErrorResponse::default()),
|
||||||
|
|||||||
@ -44,6 +44,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
|
|||||||
setup_mandate_details: None,
|
setup_mandate_details: None,
|
||||||
capture_method: None,
|
capture_method: None,
|
||||||
browser_info: None,
|
browser_info: None,
|
||||||
|
order_details: None,
|
||||||
},
|
},
|
||||||
response: Err(types::ErrorResponse::default()),
|
response: Err(types::ErrorResponse::default()),
|
||||||
payment_method_id: None,
|
payment_method_id: None,
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE payment_intent ALTER COLUMN metadata SET DEFAULT '{}'::JSONB;
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE payment_intent ALTER COLUMN metadata DROP DEFAULT;
|
||||||
Reference in New Issue
Block a user