refactor: add payment_issuer and payment_experience in pa (#491)

This commit is contained in:
Narayan Bhat
2023-02-19 19:29:22 +05:30
committed by GitHub
parent c47619b5ea
commit 66563595df
54 changed files with 790 additions and 801 deletions

48
Cargo.lock generated
View File

@ -1181,6 +1181,41 @@ dependencies = [
"typenum",
]
[[package]]
name = "darling"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0808e1bd8671fb44a113a14e13497557533369847788fa2ae912b6ebfce9fa8"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "001d80444f28e193f30c2f293455da62dcf9a6b29918a4253152ae2b1de592cb"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b36230598a2d5de7ec1c6f51f72d8a99a9208daff41de2084d06e3fd3ea56685"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "dashmap"
version = "5.4.0"
@ -1893,6 +1928,12 @@ dependencies = [
"tokio-native-tls",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.3.0"
@ -3094,6 +3135,7 @@ dependencies = [
name = "router_derive"
version = "0.1.0"
dependencies = [
"darling",
"diesel",
"proc-macro2",
"quote",
@ -3531,6 +3573,12 @@ dependencies = [
"time",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.24.1"

View File

@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use super::payments::AddressDetails;
use crate::{enums as api_enums, payment_methods};
use crate::enums as api_enums;
#[derive(Clone, Debug, Deserialize, ToSchema)]
#[serde(deny_unknown_fields)]
@ -325,7 +325,7 @@ pub struct PaymentMethods {
pub installment_payment_enabled: bool,
/// Type of payment experience enabled with the connector
#[schema(value_type = Option<Vec<PaymentExperience>>,example = json!(["redirect_to_url"]))]
pub payment_experience: Option<Vec<payment_methods::PaymentExperience>>,
pub payment_experience: Option<Vec<api_enums::PaymentExperience>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]

View File

@ -343,6 +343,66 @@ pub enum PaymentMethodIssuerCode {
JpBacs,
}
#[derive(
Clone,
Copy,
Debug,
Eq,
Hash,
PartialEq,
serde::Deserialize,
serde::Serialize,
frunk::LabelledGeneric,
ToSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum PaymentIssuer {
Klarna,
Affirm,
AfterpayClearpay,
AmericanExpress,
BankOfAmerica,
Barclays,
CapitalOne,
Chase,
Citi,
Discover,
NavyFederalCreditUnion,
PentagonFederalCreditUnion,
SynchronyBank,
WellsFargo,
}
#[derive(
Eq,
PartialEq,
Hash,
Copy,
Clone,
Debug,
serde::Serialize,
serde::Deserialize,
ToSchema,
Default,
frunk::LabelledGeneric,
)]
#[serde(rename_all = "snake_case")]
pub enum PaymentExperience {
/// The URL to which the customer needs to be redirected for completing the payment.
#[default]
RedirectToUrl,
/// Contains the data for invoking the sdk client for completing the payment.
InvokeSdkClient,
/// The QR code data to be displayed to the customer.
DisplayQrCode,
/// Contains data to finish one click payment.
OneClick,
/// Redirect customer to link wallet
LinkWallet,
/// Contains the data for invoking the sdk client for completing the payment.
InvokePaymentApp,
}
#[derive(
Clone,
Copy,

View File

@ -122,7 +122,7 @@ pub struct PaymentMethodResponse {
/// Type of payment experience enabled with the connector
#[schema(value_type = Option<Vec<PaymentExperience>>,example = json!(["redirect_to_url"]))]
pub payment_experience: Option<Vec<PaymentExperience>>,
pub payment_experience: Option<Vec<api_enums::PaymentExperience>>,
/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
#[schema(value_type = Option<Object>,example = json!({ "city": "NY", "unit": "245" }))]
@ -348,8 +348,8 @@ pub struct ListPaymentMethod {
pub installment_payment_enabled: bool,
/// Type of payment experience enabled with the connector
#[schema(example = json!(["redirect_to_url"]))]
pub payment_experience: Option<Vec<PaymentExperience>>,
#[schema(value_type = Option<Vec<PaymentExperience>>, example = json!(["redirect_to_url"]))]
pub payment_experience: Option<Vec<api_enums::PaymentExperience>>,
}
/// We need a custom serializer to only send relevant fields in ListPaymentMethodResponse
@ -447,7 +447,7 @@ pub struct CustomerPaymentMethod {
/// Type of payment experience enabled with the connector
#[schema(value_type = Option<Vec<PaymentExperience>>,example = json!(["redirect_to_url"]))]
pub payment_experience: Option<Vec<PaymentExperience>>,
pub payment_experience: Option<Vec<api_enums::PaymentExperience>>,
/// Card details from card locker
#[schema(example = json!({"last4": "1142","exp_month": "03","exp_year": "2030"}))]
@ -462,26 +462,6 @@ pub struct CustomerPaymentMethod {
#[serde(default, with = "common_utils::custom_serde::iso8601::option")]
pub created: Option<time::PrimitiveDateTime>,
}
#[derive(Eq, PartialEq, Hash, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum PaymentExperience {
/// The URL to which the customer needs to be redirected for completing the payment.The URL to
/// which the customer needs to be redirected for completing the payment.
RedirectToUrl,
/// Contains the data for invoking the sdk client for completing the payment.
InvokeSdkClient,
/// The QR code data to be displayed to the customer.
DisplayQrCode,
/// Contains data to finish one click payment.
OneClick,
/// Redirect customer to link wallet
LinkWallet,
/// Contains the data for invoking the sdk client for completing the payment.
InvokePaymentApp,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct PaymentMethodId {
pub payment_method_id: String,

View File

@ -132,6 +132,12 @@ pub struct PaymentsRequest {
"java_script_enabled":true
}"#)]
pub browser_info: Option<serde_json::Value>,
/// Payment Issuser for the current payment
#[schema(value_type = Option<PaymentIssuer>, example = "klarna")]
pub payment_issuer: Option<api_enums::PaymentIssuer>,
/// Payment Experience, works in tandem with payment_issuer
#[schema(value_type = Option<PaymentExperience>, example = "redirect_to_url")]
pub payment_experience: Option<api_enums::PaymentExperience>,
}
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)]
@ -342,35 +348,27 @@ pub enum AfterpayClearpayIssuer {
pub enum PayLaterData {
/// For KlarnaRedirect as PayLater Option
KlarnaRedirect {
/// The issuer name of the redirect
issuer_name: KlarnaIssuer,
/// The billing email
billing_email: String,
#[schema(value_type = String)]
billing_email: Secret<String, pii::Email>,
// The billing country code
billing_country: String,
},
/// For Klarna Sdk as PayLater Option
KlarnaSdk {
/// The issuer name of the sdk
issuer_name: KlarnaIssuer,
/// The token for the sdk workflow
token: String,
},
/// For Affirm redirect as PayLater Option
AffirmRedirect {
/// The issuer name of affirm redirect issuer
issuer_name: AffirmIssuer,
/// The billing email
billing_email: String,
},
AffirmRedirect {},
/// For AfterpayClearpay redirect as PayLater Option
AfterpayClearpayRedirect {
/// The issuer name of afterpayclearpay redirect issuer
issuer_name: AfterpayClearpayIssuer,
/// The billing email
billing_email: String,
#[schema(value_type = String)]
billing_email: Secret<String, pii::Email>,
/// The billing name
billing_name: String,
#[schema(value_type = String)]
billing_name: Secret<String>,
},
}

View File

@ -86,7 +86,7 @@ pub mod iso8601 {
}
}
/// https://github.com/serde-rs/serde/issues/994#issuecomment-316895860
/// <https://github.com/serde-rs/serde/issues/994#issuecomment-316895860>
pub mod json_string {
use serde::de::{self, Deserialize, DeserializeOwned, Deserializer};

View File

@ -216,7 +216,6 @@ impl<F, T>
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),

View File

@ -672,7 +672,6 @@ impl TryFrom<types::PaymentsCancelResponseRouterData<AdyenCancelResponse>>
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.psp_reference),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),
@ -722,7 +721,6 @@ pub fn get_adyen_response(
let payments_response_data = types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(response.psp_reference),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
};
@ -783,7 +781,6 @@ pub fn get_redirection_response(
let payments_response_data = types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::NoResponseId,
redirection_data: Some(redirection_data),
redirect: true,
mandate_reference: None,
connector_metadata: None,
};
@ -876,7 +873,6 @@ impl TryFrom<types::PaymentsCaptureResponseRouterData<AdyenCaptureResponse>>
status,
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.psp_reference),
redirect: false,
redirection_data: None,
mandate_reference: None,
connector_metadata: None,

View File

@ -329,7 +329,6 @@ impl<F, T>
item.response.transaction_response.transaction_id,
),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: metadata,
}),
@ -606,7 +605,6 @@ impl<F, Req>
item.response.transaction.transaction_id,
),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),

View File

@ -219,7 +219,6 @@ impl<F, T>
item.response.transaction.id,
),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),

View File

@ -246,7 +246,6 @@ impl TryFrom<types::PaymentsResponseRouterData<PaymentsResponse>>
)),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
redirect: redirection_data.is_some(),
redirection_data,
mandate_reference: None,
connector_metadata: None,
@ -289,7 +288,6 @@ impl TryFrom<types::PaymentsSyncResponseRouterData<PaymentsResponse>>
)),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
redirect: redirection_data.is_some(),
redirection_data,
mandate_reference: None,
connector_metadata: None,
@ -332,7 +330,6 @@ impl TryFrom<types::PaymentsCancelResponseRouterData<PaymentVoidResponse>>
Ok(Self {
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(response.action_id.clone()),
redirect: false,
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
@ -404,7 +401,6 @@ impl TryFrom<types::PaymentsCaptureResponseRouterData<PaymentCaptureResponse>>
resource_id: types::ResponseId::ConnectorTransactionId(
item.data.request.connector_transaction_id.to_owned(),
),
redirect: false,
redirection_data: None,
mandate_reference: None,
connector_metadata: None,

View File

@ -315,7 +315,6 @@ impl<F, T>
_ => Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),
@ -379,7 +378,6 @@ impl<F, T>
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),

View File

@ -226,7 +226,6 @@ impl<F, T>
gateway_resp.transaction_processing_details.transaction_id,
),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),

View File

@ -165,7 +165,6 @@ fn get_payment_response(
_ => Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(response.id),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),

View File

@ -13,6 +13,7 @@ use crate::{
types::{
self,
api::{self, ConnectorCommon},
storage::enums as storage_enums,
},
utils::{self, BytesExt},
};
@ -235,12 +236,27 @@ impl
match payment_method_data {
api_payments::PaymentMethod::PayLater(api_payments::PayLaterData::KlarnaSdk {
token,
..
}) => Ok(format!(
"{}payments/v1/authorizations/{}/order",
self.base_url(connectors),
token
)),
}) => match (
req.request.payment_issuer.as_ref(),
req.request.payment_experience.as_ref(),
) {
(
Some(storage_enums::PaymentIssuer::Klarna),
Some(storage_enums::PaymentExperience::InvokeSdkClient),
) => Ok(format!(
"{}payments/v1/authorizations/{}/order",
self.base_url(connectors),
token
)),
(None, _) | (_, None) => Err(error_stack::report!(
errors::ConnectorError::MissingRequiredField {
field_name: "payment_issuer and payment_experience"
}
)),
_ => Err(error_stack::report!(
errors::ConnectorError::MismatchedPaymentData
)),
},
_ => Err(error_stack::report!(
errors::ConnectorError::NotImplemented(
"We only support wallet payments through klarna".to_string(),

View File

@ -114,7 +114,6 @@ impl TryFrom<types::PaymentsResponseRouterData<KlarnaPaymentsResponse>>
Ok(Self {
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.order_id),
redirect: false,
redirection_data: None,
mandate_reference: None,
connector_metadata: None,

View File

@ -207,7 +207,6 @@ impl<F, T>
status: enums::AttemptStatus::from(item.response.status.status_code),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.order_id),
redirect: false,
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
@ -258,7 +257,6 @@ impl<F, T>
status: enums::AttemptStatus::from(item.response.status.status_code.clone()),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::NoResponseId,
redirect: false,
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
@ -337,7 +335,6 @@ impl<F, T>
status: enums::AttemptStatus::from(item.response.status.status_code.clone()),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.order_id),
redirect: false,
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
@ -466,7 +463,6 @@ impl<F, T>
status: enums::AttemptStatus::from(order.status.clone()),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(order.order_id.clone()),
redirect: false,
redirection_data: None,
mandate_reference: None,
connector_metadata: None,

View File

@ -414,7 +414,6 @@ impl<F, T>
resource_id: types::ResponseId::ConnectorTransactionId(
data.id.to_owned(),
), //transaction_id is also the field but this id is used to initiate a refund
redirect: redirection_data.is_some(),
redirection_data,
mandate_reference: None,
connector_metadata: None,

View File

@ -154,7 +154,6 @@ impl<F, T>
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),

View File

@ -1,7 +1,7 @@
use std::str::FromStr;
use api_models::{self, payments};
use common_utils::fp_utils;
use common_utils::{fp_utils, pii::Email};
use error_stack::{IntoReport, ResultExt};
use masking::ExposeInterface;
use serde::{Deserialize, Serialize};
@ -80,12 +80,13 @@ pub struct PaymentIntentRequest {
pub metadata_txn_uuid: String,
pub return_url: String,
pub confirm: bool,
pub off_session: Option<bool>,
pub mandate: Option<String>,
pub description: Option<String>,
#[serde(flatten)]
pub shipping: StripeShippingAddress,
#[serde(flatten)]
pub billing: StripeBillingAddress,
#[serde(flatten)]
pub payment_data: Option<StripePaymentMethodData>,
pub capture_method: StripeCaptureMethod,
}
@ -128,27 +129,19 @@ pub struct StripePayLaterData {
pub payment_method_types: StripePaymentMethodType,
#[serde(rename = "payment_method_data[type]")]
pub payment_method_data_type: StripePaymentMethodType,
#[serde(rename = "payment_method_data[billing_details][email]")]
pub billing_email: String,
#[serde(rename = "payment_method_data[billing_details][address][country]")]
pub billing_country: Option<String>,
#[serde(rename = "payment_method_data[billing_details][name]")]
pub billing_name: Option<String>,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum StripePaymentMethodData {
Card(StripeCardData),
Klarna(StripePayLaterData),
Affirm(StripePayLaterData),
AfterpayClearpay(StripePayLaterData),
PayLater(StripePayLaterData),
Bank,
Wallet,
Paypal,
}
#[derive(Debug, Eq, PartialEq, Serialize)]
#[derive(Debug, Eq, PartialEq, Serialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum StripePaymentMethodType {
Card,
@ -159,63 +152,165 @@ pub enum StripePaymentMethodType {
fn validate_shipping_address_against_payment_method(
shipping_address: &StripeShippingAddress,
payment_method: &payments::PaymentMethod,
payment_method: &StripePaymentMethodType,
) -> Result<(), errors::ConnectorError> {
if let payments::PaymentMethod::PayLater(payments::PayLaterData::AfterpayClearpayRedirect {
..
}) = payment_method
{
if let StripePaymentMethodType::AfterpayClearpay = payment_method {
fp_utils::when(shipping_address.name.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.first_name",
field_name: "shipping.address.first_name",
})
})?;
fp_utils::when(shipping_address.line1.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.line1",
field_name: "shipping.address.line1",
})
})?;
fp_utils::when(shipping_address.country.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.country",
field_name: "shipping.address.country",
})
})?;
fp_utils::when(shipping_address.zip.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.zip",
field_name: "shipping.address.zip",
})
})?;
}
Ok(())
}
fn infer_stripe_pay_later_issuer(
issuer: &enums::PaymentIssuer,
experience: &enums::PaymentExperience,
) -> Result<StripePaymentMethodType, errors::ConnectorError> {
if &enums::PaymentExperience::RedirectToUrl == experience {
match issuer {
enums::PaymentIssuer::Klarna => Ok(StripePaymentMethodType::Klarna),
enums::PaymentIssuer::Affirm => Ok(StripePaymentMethodType::Affirm),
enums::PaymentIssuer::AfterpayClearpay => Ok(StripePaymentMethodType::AfterpayClearpay),
_ => Err(errors::ConnectorError::NotSupported {
payment_method: format!("{issuer} payments by {experience}"),
connector: "stripe",
}),
}
} else {
Err(errors::ConnectorError::NotSupported {
payment_method: format!("{issuer} payments by {experience}"),
connector: "stripe",
})
}
}
impl TryFrom<(&api_models::payments::PayLaterData, StripePaymentMethodType)>
for StripeBillingAddress
{
type Error = errors::ConnectorError;
fn try_from(
(pay_later_data, pm_type): (&api_models::payments::PayLaterData, StripePaymentMethodType),
) -> Result<Self, Self::Error> {
match (pay_later_data, pm_type) {
(
payments::PayLaterData::KlarnaRedirect {
billing_email,
billing_country,
},
StripePaymentMethodType::Klarna,
) => Ok(Self {
email: Some(billing_email.to_owned()),
country: Some(billing_country.to_owned()),
..Self::default()
}),
(payments::PayLaterData::AffirmRedirect {}, StripePaymentMethodType::Affirm) => {
Ok(Self::default())
}
(
payments::PayLaterData::AfterpayClearpayRedirect {
billing_email,
billing_name,
},
StripePaymentMethodType::AfterpayClearpay,
) => Ok(Self {
email: Some(billing_email.to_owned()),
name: Some(billing_name.to_owned()),
..Self::default()
}),
_ => Err(errors::ConnectorError::MismatchedPaymentData),
}
}
}
fn create_stripe_payment_method(
issuer: Option<&enums::PaymentIssuer>,
experience: Option<&enums::PaymentExperience>,
payment_method: &api_models::payments::PaymentMethod,
auth_type: enums::AuthenticationType,
) -> Result<
(
StripePaymentMethodData,
StripePaymentMethodType,
StripeBillingAddress,
),
errors::ConnectorError,
> {
match payment_method {
payments::PaymentMethod::Card(card_details) => {
let payment_method_auth_type = match auth_type {
enums::AuthenticationType::ThreeDs => Auth3ds::Any,
enums::AuthenticationType::NoThreeDs => Auth3ds::Automatic,
};
Ok((
StripePaymentMethodData::Card(StripeCardData {
payment_method_types: StripePaymentMethodType::Card,
payment_method_data_type: StripePaymentMethodType::Card,
payment_method_data_card_number: card_details.card_number.clone(),
payment_method_data_card_exp_month: card_details.card_exp_month.clone(),
payment_method_data_card_exp_year: card_details.card_exp_year.clone(),
payment_method_data_card_cvc: card_details.card_cvc.clone(),
payment_method_auth_type,
}),
StripePaymentMethodType::Card,
StripeBillingAddress::default(),
))
}
payments::PaymentMethod::PayLater(pay_later_data) => {
let pm_issuer = issuer.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "payment_issuer",
})?;
let pm_experience = experience.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "payment_experience",
})?;
let pm_type = infer_stripe_pay_later_issuer(pm_issuer, pm_experience)?;
let billing_address =
StripeBillingAddress::try_from((pay_later_data, pm_type.clone()))?;
Ok((
StripePaymentMethodData::PayLater(StripePayLaterData {
payment_method_types: pm_type.clone(),
payment_method_data_type: pm_type.clone(),
}),
pm_type,
billing_address,
))
}
_ => Err(errors::ConnectorError::NotImplemented(
"stripe does not support this payment method".to_string(),
)),
}
}
impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
type Error = errors::ConnectorError;
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
let metadata_order_id = item.payment_id.to_string();
let metadata_txn_id = format!("{}_{}_{}", item.merchant_id, item.payment_id, "1");
let metadata_txn_uuid = Uuid::new_v4().to_string(); //Fetch autogenerated txn_uuid from Database.
// let api::PaymentMethod::Card(a) = item.payment_method_data;
// let api::PaymentMethod::Card(a) = item.payment_method_data;
let (payment_data, mandate) = {
match item
.request
.mandate_id
.clone()
.and_then(|mandate_ids| mandate_ids.connector_mandate_id)
{
None => {
let payment_method: StripePaymentMethodData =
(item.request.payment_method_data.clone(), item.auth_type).try_into()?;
(Some(payment_method), None)
}
Some(mandate_id) => (None, Some(mandate_id)),
}
};
let shipping_address = match item.address.shipping.clone() {
Some(mut shipping) => StripeShippingAddress {
@ -247,15 +342,32 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
None => StripeShippingAddress::default(),
};
validate_shipping_address_against_payment_method(
&shipping_address,
&item.request.payment_method_data,
)?;
let (payment_data, mandate, billing_address) = {
match item
.request
.mandate_id
.clone()
.and_then(|mandate_ids| mandate_ids.connector_mandate_id)
{
None => {
let (payment_method_data, payment_method_type, billing_address) =
create_stripe_payment_method(
item.request.payment_issuer.as_ref(),
item.request.payment_experience.as_ref(),
&item.request.payment_method_data,
item.auth_type,
)?;
let off_session = item
.request
.off_session
.and_then(|value| mandate.as_ref().map(|_| value));
validate_shipping_address_against_payment_method(
&shipping_address,
&payment_method_type,
)?;
(Some(payment_method_data), None, billing_address)
}
Some(mandate_id) => (None, Some(mandate_id), StripeBillingAddress::default()),
}
};
Ok(Self {
amount: item.request.amount, //hopefully we don't loose some cents here
@ -272,9 +384,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
description: item.description.clone(),
shipping: shipping_address,
billing: billing_address,
capture_method: StripeCaptureMethod::from(item.request.capture_method),
payment_data,
off_session,
mandate,
})
}
@ -287,8 +399,13 @@ impl TryFrom<&types::VerifyRouterData> for SetupIntentRequest {
let metadata_txn_id = format!("{}_{}_{}", item.merchant_id, item.payment_id, "1");
let metadata_txn_uuid = Uuid::new_v4().to_string();
let payment_data: StripePaymentMethodData =
(item.request.payment_method_data.clone(), item.auth_type).try_into()?;
//Only cards supported for mandates
let pm_type = StripePaymentMethodType::Card;
let payment_data = StripePaymentMethodData::try_from((
item.request.payment_method_data.clone(),
item.auth_type,
pm_type,
))?;
Ok(Self {
confirm: true,
@ -421,7 +538,6 @@ impl<F, T>
// three_ds_form,
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
redirect: redirection_data.is_some(),
redirection_data,
mandate_reference,
connector_metadata: None,
@ -473,7 +589,6 @@ impl<F, T>
status: enums::AttemptStatus::from(item.response.status),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
redirect: redirection_data.is_some(),
redirection_data,
mandate_reference,
connector_metadata: None,
@ -649,13 +764,23 @@ pub struct StripeShippingAddress {
pub phone: Option<Secret<String>>,
}
#[derive(Debug, Default, Eq, PartialEq, Serialize)]
pub struct StripeBillingAddress {
#[serde(rename = "payment_method_data[billing_details][email]")]
pub email: Option<Secret<String, Email>>,
#[serde(rename = "payment_method_data[billing_details][address][country]")]
pub country: Option<String>,
#[serde(rename = "payment_method_data[billing_details][name]")]
pub name: Option<Secret<String>>,
}
#[derive(Debug, Clone, serde::Deserialize, Eq, PartialEq)]
pub struct StripeRedirectResponse {
pub payment_intent: String,
pub payment_intent_client_secret: String,
pub source_redirect_slug: Option<String>,
pub redirect_status: Option<StripePaymentStatus>,
pub source_type: Option<String>,
pub source_type: Option<Secret<String>>,
}
#[derive(Debug, Serialize, Clone, Copy)]
@ -792,10 +917,20 @@ pub struct StripeWebhookObjectId {
pub data: StripeWebhookDataId,
}
impl TryFrom<(api::PaymentMethod, enums::AuthenticationType)> for StripePaymentMethodData {
impl
TryFrom<(
api::PaymentMethod,
enums::AuthenticationType,
StripePaymentMethodType,
)> for StripePaymentMethodData
{
type Error = errors::ConnectorError;
fn try_from(
(pm_data, auth_type): (api::PaymentMethod, enums::AuthenticationType),
(pm_data, auth_type, pm_type): (
api::PaymentMethod,
enums::AuthenticationType,
StripePaymentMethodType,
),
) -> Result<Self, Self::Error> {
match pm_data {
api::PaymentMethod::Card(ref ccard) => Ok(Self::Card({
@ -814,42 +949,10 @@ impl TryFrom<(api::PaymentMethod, enums::AuthenticationType)> for StripePaymentM
}
})),
api::PaymentMethod::BankTransfer => Ok(Self::Bank),
api::PaymentMethod::PayLater(pay_later_data) => match pay_later_data {
api_models::payments::PayLaterData::KlarnaRedirect {
billing_email,
billing_country,
..
} => Ok(Self::Klarna(StripePayLaterData {
payment_method_types: StripePaymentMethodType::Klarna,
payment_method_data_type: StripePaymentMethodType::Klarna,
billing_email,
billing_country: Some(billing_country),
billing_name: None,
})),
api_models::payments::PayLaterData::AffirmRedirect { billing_email, .. } => {
Ok(Self::Affirm(StripePayLaterData {
payment_method_types: StripePaymentMethodType::Affirm,
payment_method_data_type: StripePaymentMethodType::Affirm,
billing_email,
billing_country: None,
billing_name: None,
}))
}
api_models::payments::PayLaterData::AfterpayClearpayRedirect {
billing_email,
billing_name,
..
} => Ok(Self::AfterpayClearpay(StripePayLaterData {
payment_method_types: StripePaymentMethodType::AfterpayClearpay,
payment_method_data_type: StripePaymentMethodType::AfterpayClearpay,
billing_email,
billing_country: None,
billing_name: Some(billing_name),
})),
_ => Err(errors::ConnectorError::NotImplemented(String::from(
"Stripe does not support payment through provided payment method",
))),
},
api::PaymentMethod::PayLater(_) => Ok(Self::PayLater(StripePayLaterData {
payment_method_types: pm_type.clone(),
payment_method_data_type: pm_type,
})),
api::PaymentMethod::Wallet(_) => Ok(Self::Wallet),
api::PaymentMethod::Paypal => Ok(Self::Paypal),
}

View File

@ -357,7 +357,6 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, Payment, T, types::PaymentsRespo
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),
@ -386,7 +385,6 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, PaymentResponse, T, types::Payme
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.payment.id),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),

View File

@ -160,7 +160,6 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::try_from(response.links)?,
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),
@ -256,7 +255,6 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: data.request.connector_transaction_id.clone(),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),
@ -314,7 +312,6 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::try_from(response.links)?,
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),

View File

@ -167,7 +167,6 @@ impl TryFrom<types::PaymentsResponseRouterData<WorldpayPaymentsResponse>>
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::try_from(item.response.links)?,
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
}),

View File

@ -248,6 +248,11 @@ pub enum ConnectorError {
FailedToObtainCertificateKey,
#[error("This step has not been implemented for: {0}")]
NotImplemented(String),
#[error("{payment_method} is not supported by {connector}")]
NotSupported {
payment_method: String,
connector: &'static str,
},
#[error("Missing connector transaction ID")]
MissingConnectorTransactionID,
#[error("Missing connector refund ID")]
@ -270,6 +275,8 @@ pub enum ConnectorError {
WebhookResourceObjectNotFound,
#[error("Invalid Date/time format")]
InvalidDateFormat,
#[error("Payment Issuer does not match the Payment Data provided")]
MismatchedPaymentData,
}
#[derive(Debug, thiserror::Error)]

View File

@ -101,6 +101,12 @@ impl ConnectorErrorExt for error_stack::Report<errors::ConnectorError> {
),
}
}
errors::ConnectorError::MismatchedPaymentData => {
errors::ApiErrorResponse::InvalidDataValue {
field_name:
"payment_method_data and payment_issuer, payment_experience does not match",
}
}
_ => errors::ApiErrorResponse::InternalServerError,
};
self.change_context(error)

View File

@ -85,9 +85,7 @@ pub async fn add_payment_method(
payment_method_issuer_code: req.payment_method_issuer_code,
recurring_enabled: false, //[#219]
installment_payment_enabled: false, //[#219]
payment_experience: Some(vec![
api_models::payment_methods::PaymentExperience::RedirectToUrl,
]), //[#219]
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), //[#219]
})
}
}
@ -635,9 +633,7 @@ pub async fn list_customer_payment_method(
.map(ForeignInto::foreign_into),
recurring_enabled: false,
installment_payment_enabled: false,
payment_experience: Some(vec![
api_models::payment_methods::PaymentExperience::RedirectToUrl,
]),
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
created: Some(pm.created_at),
};
vec.push(pma);
@ -880,9 +876,7 @@ pub async fn retrieve_payment_method(
.map(ForeignInto::foreign_into),
recurring_enabled: false, //[#219]
installment_payment_enabled: false, //[#219]
payment_experience: Some(vec![
api_models::payment_methods::PaymentExperience::RedirectToUrl,
]), //[#219],
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), //[#219],
},
))
}

View File

@ -127,9 +127,7 @@ pub fn mk_add_card_response(
payment_method_issuer_code: req.payment_method_issuer_code,
recurring_enabled: false, // [#256]
installment_payment_enabled: false, // #[#256]
payment_experience: Some(vec![
api_models::payment_methods::PaymentExperience::RedirectToUrl,
]), // [#256]
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), // [#256]
}
}

View File

@ -433,6 +433,8 @@ impl PaymentCreate {
last_synced,
authentication_type: request.authentication_type.map(ForeignInto::foreign_into),
browser_info,
payment_experience: request.payment_experience.map(ForeignInto::foreign_into),
payment_issuer: request.payment_issuer.map(ForeignInto::foreign_into),
..storage::PaymentAttemptNew::default()
}
}

View File

@ -280,7 +280,6 @@ async fn payment_response_update_tracker<F: Clone, T>(
types::PaymentsResponseData::TransactionResponse {
resource_id,
redirection_data,
redirect,
connector_metadata,
..
} => {
@ -305,7 +304,6 @@ async fn payment_response_update_tracker<F: Clone, T>(
connector_transaction_id: connector_transaction_id.clone(),
authentication_type: None,
payment_method_id: Some(router_data.payment_method_id),
redirect: Some(redirect),
mandate_id: payment_data
.mandate_id
.clone()

View File

@ -65,7 +65,6 @@ where
.map(|id| types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(id.to_string()),
redirection_data: None,
redirect: false,
mandate_reference: None,
connector_metadata: None,
});
@ -430,6 +429,8 @@ impl<F: Clone> TryFrom<PaymentData<F>> for types::PaymentsAuthorizeData {
currency: payment_data.currency,
browser_info,
email: payment_data.email,
payment_experience: payment_data.payment_attempt.payment_experience,
payment_issuer: payment_data.payment_attempt.payment_issuer,
order_details,
})
}

View File

@ -227,8 +227,6 @@ impl PaymentAttemptInterface for MockDb {
tax_amount: payment_attempt.tax_amount,
payment_method_id: payment_attempt.payment_method_id,
payment_method: payment_attempt.payment_method,
payment_flow: payment_attempt.payment_flow,
redirect: payment_attempt.redirect,
connector_transaction_id: payment_attempt.connector_transaction_id,
capture_method: payment_attempt.capture_method,
capture_on: payment_attempt.capture_on,
@ -244,6 +242,8 @@ impl PaymentAttemptInterface for MockDb {
payment_token: None,
error_code: payment_attempt.error_code,
connector_metadata: None,
payment_experience: payment_attempt.payment_experience,
payment_issuer: payment_attempt.payment_issuer,
};
payment_attempts.push(payment_attempt.clone());
Ok(payment_attempt)
@ -366,8 +366,6 @@ mod storage {
tax_amount: payment_attempt.tax_amount,
payment_method_id: payment_attempt.payment_method_id.clone(),
payment_method: payment_attempt.payment_method,
payment_flow: payment_attempt.payment_flow,
redirect: payment_attempt.redirect,
connector_transaction_id: payment_attempt.connector_transaction_id.clone(),
capture_method: payment_attempt.capture_method,
capture_on: payment_attempt.capture_on,
@ -383,6 +381,8 @@ mod storage {
payment_token: payment_attempt.payment_token.clone(),
error_code: payment_attempt.error_code.clone(),
connector_metadata: payment_attempt.connector_metadata.clone(),
payment_experience: payment_attempt.payment_experience.clone(),
payment_issuer: payment_attempt.payment_issuer,
};
let field = format!("pa_{}", created_attempt.attempt_id);

View File

@ -139,6 +139,8 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::enums::SupportedWallets,
api_models::enums::PaymentMethodIssuerCode,
api_models::enums::MandateStatus,
api_models::enums::PaymentExperience,
api_models::enums::PaymentIssuer,
api_models::admin::PaymentConnectorCreate,
api_models::admin::PaymentMethods,
api_models::payments::AddressDetails,
@ -163,7 +165,6 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::payments::CustomerAcceptance,
api_models::payments::PaymentsRequest,
api_models::payments::PaymentsResponse,
api_models::payment_methods::PaymentExperience,
api_models::payments::PaymentsStartRequest,
api_models::payments::PaymentRetrieveBody,
api_models::payments::PaymentsRetrieveRequest,

View File

@ -115,6 +115,8 @@ pub struct PaymentsAuthorizeData {
pub setup_mandate_details: Option<payments::MandateData>,
pub browser_info: Option<BrowserInformation>,
pub order_details: Option<api_models::payments::OrderDetails>,
pub payment_issuer: Option<storage_enums::PaymentIssuer>,
pub payment_experience: Option<storage_enums::PaymentExperience>,
}
#[derive(Debug, Clone)]
@ -181,7 +183,6 @@ pub enum PaymentsResponseData {
TransactionResponse {
resource_id: ResponseId,
redirection_data: Option<services::RedirectForm>,
redirect: bool,
mandate_reference: Option<String>,
connector_metadata: Option<serde_json::Value>,
},

View File

@ -88,18 +88,6 @@ where
}
}
impl From<F<api_enums::RoutingAlgorithm>> for F<storage_enums::RoutingAlgorithm> {
fn from(algo: F<api_enums::RoutingAlgorithm>) -> Self {
Self(frunk::labelled_convert_from(algo.0))
}
}
impl From<F<storage_enums::RoutingAlgorithm>> for F<api_enums::RoutingAlgorithm> {
fn from(algo: F<storage_enums::RoutingAlgorithm>) -> Self {
Self(frunk::labelled_convert_from(algo.0))
}
}
impl From<F<api_enums::ConnectorType>> for F<storage_enums::ConnectorType> {
fn from(conn: F<api_enums::ConnectorType>) -> Self {
Self(frunk::labelled_convert_from(conn.0))
@ -158,6 +146,18 @@ impl From<F<storage_enums::PaymentMethodIssuerCode>> for F<api_enums::PaymentMet
}
}
impl From<F<api_enums::PaymentIssuer>> for F<storage_enums::PaymentIssuer> {
fn from(issuer: F<api_enums::PaymentIssuer>) -> Self {
Self(frunk::labelled_convert_from(issuer.0))
}
}
impl From<F<api_enums::PaymentExperience>> for F<storage_enums::PaymentExperience> {
fn from(experience: F<api_enums::PaymentExperience>) -> Self {
Self(frunk::labelled_convert_from(experience.0))
}
}
impl From<F<storage_enums::IntentStatus>> for F<api_enums::IntentStatus> {
fn from(status: F<storage_enums::IntentStatus>) -> Self {
Self(frunk::labelled_convert_from(status.0))

View File

@ -50,6 +50,8 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
browser_info: None,
order_details: None,
email: None,
payment_experience: None,
payment_issuer: None,
},
response: Err(types::ErrorResponse::default()),
payment_method_id: None,

View File

@ -78,6 +78,8 @@ impl AdyenTest {
browser_info: None,
order_details: None,
email: None,
payment_experience: None,
payment_issuer: None,
})
}
}

View File

@ -50,6 +50,8 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
browser_info: None,
order_details: None,
email: None,
payment_experience: None,
payment_issuer: None,
},
payment_method_id: None,
response: Err(types::ErrorResponse::default()),

View File

@ -47,6 +47,8 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
browser_info: None,
order_details: None,
email: None,
payment_experience: None,
payment_issuer: None,
},
response: Err(types::ErrorResponse::default()),
payment_method_id: None,

View File

@ -469,6 +469,8 @@ impl Default for PaymentAuthorizeType {
browser_info: Some(BrowserInfoType::default().0),
order_details: None,
email: None,
payment_experience: None,
payment_issuer: None,
};
Self(data)
}

View File

@ -80,6 +80,8 @@ impl WorldlineTest {
browser_info: None,
order_details: None,
email: None,
payment_experience: None,
payment_issuer: None,
})
}
}

View File

@ -327,6 +327,8 @@ async fn payments_create_core() {
off_session: None,
client_secret: None,
browser_info: None,
payment_experience: None,
payment_issuer: None,
};
let expected_response = api::PaymentsResponse {
@ -482,6 +484,8 @@ async fn payments_create_core_adyen_no_redirect() {
off_session: None,
client_secret: None,
browser_info: None,
payment_experience: None,
payment_issuer: None,
};
let expected_response = services::ApplicationResponse::Json(api::PaymentsResponse {

View File

@ -239,6 +239,8 @@ async fn payments_create_core_adyen_no_redirect() {
mandate_id: None,
client_secret: None,
browser_info: None,
payment_experience: None,
payment_issuer: None,
};
let expected_response = services::ApplicationResponse::Json(api::PaymentsResponse {

View File

@ -12,6 +12,7 @@ proc-macro = true
doctest = false
[dependencies]
darling = "0.14.3"
proc-macro2 = "1.0.50"
quote = "1.0.23"
syn = { version = "1.0.107", features = ["full", "extra-traits"] } # the full feature does not seem to encompass all the features

View File

@ -53,13 +53,13 @@ pub fn debug_as_display_derive(input: proc_macro::TokenStream) -> proc_macro::To
/// # Example
///
/// ```
/// use router_derive::{DieselEnum, diesel_enum};
/// use router_derive::diesel_enum;
///
/// // Deriving `FromStr` and `ToString` using the `strum` crate, you can also implement it
/// // yourself if required.
/// #[derive(strum::Display, strum::EnumString)]
/// #[derive(Debug, DieselEnum)]
/// #[diesel_enum]
/// #[derive(Debug)]
/// #[diesel_enum(storage_type = "pg_enum")]
/// enum Color {
/// Red,
/// Green,
@ -69,32 +69,64 @@ pub fn debug_as_display_derive(input: proc_macro::TokenStream) -> proc_macro::To
#[proc_macro_derive(DieselEnum)]
pub fn diesel_enum_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
let tokens =
macros::diesel_enum_derive_inner(&ast).unwrap_or_else(|error| error.to_compile_error());
tokens.into()
}
/// Similar to [`DieselEnum`] but uses text when storing in the database, this is to avoid
/// making changes to the database when the enum variants are added or modified
///
/// # Example
/// [DieselEnum]: macro@crate::diesel_enum
///
/// ```
/// use router_derive::{diesel_enum};
///
/// // Deriving `FromStr` and `ToString` using the `strum` crate, you can also implement it
/// // yourself if required.
/// #[derive(strum::Display, strum::EnumString)]
/// #[derive(Debug)]
/// #[diesel_enum(storage_type = "text")]
/// enum Color {
/// Red,
/// Green,
/// Blue,
/// }
/// ```
#[proc_macro_derive(DieselEnumText)]
pub fn diesel_enum_derive_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
let tokens = macros::diesel_enum_text_derive_inner(&ast)
.unwrap_or_else(|error| error.to_compile_error());
tokens.into()
}
/// Derives the boilerplate code required for using an enum with `diesel` and a PostgreSQL database.
///
/// Works in tandem with the [`DieselEnum`][DieselEnum] derive macro to achieve the desired results.
/// Storage Type can either be "text" or "pg_enum"
/// Choosing text will store the enum as text in the database, whereas pg_enum will map it to the
/// database enum
///
/// Works in tandem with the [`DieselEnum`][DieselEnum] and [`DieselEnumText`][DieselEnumText] derive macro to achieve the desired results.
/// The enum is required to implement (or derive) the [`ToString`][ToString] and the
/// [`FromStr`][FromStr] traits for the [`DieselEnum`][DieselEnum] derive macro to be used.
///
/// [DieselEnum]: crate::DieselEnum
/// [DieselEnumText]: crate::DieselEnumText
/// [FromStr]: ::core::str::FromStr
/// [ToString]: ::std::string::ToString
///
/// # Example
///
/// ```
/// use router_derive::{DieselEnum, diesel_enum};
/// use router_derive::{diesel_enum};
///
/// // Deriving `FromStr` and `ToString` using the `strum` crate, you can also implement it
/// // yourself if required. (Required by the DieselEnum derive macro.)
/// #[derive(strum::Display, strum::EnumString)]
/// #[derive(Debug, DieselEnum)]
/// #[diesel_enum]
/// #[derive(Debug)]
/// #[diesel_enum(storage_type = "text")]
/// enum Color {
/// Red,
/// Green,

View File

@ -9,7 +9,9 @@ use syn::DeriveInput;
pub(crate) use self::{
api_error::api_error_derive_inner,
diesel::{diesel_enum_attribute_inner, diesel_enum_derive_inner},
diesel::{
diesel_enum_attribute_inner, diesel_enum_derive_inner, diesel_enum_text_derive_inner,
},
operation::operation_derive_inner,
};

View File

@ -1,9 +1,46 @@
use darling::FromMeta;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{AttributeArgs, Data, DeriveInput, ItemEnum};
use crate::macros::helpers::non_enum_error;
pub(crate) fn diesel_enum_text_derive_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
match &ast.data {
Data::Enum(_) => (),
_ => return Err(non_enum_error()),
}
Ok(quote! {
#[automatically_derived]
impl #impl_generics ::diesel::serialize::ToSql<::diesel::sql_types::Text, ::diesel::pg::Pg> for #name #ty_generics
#where_clause
{
fn to_sql<'b>(&'b self, out: &mut ::diesel::serialize::Output<'b, '_, ::diesel::pg::Pg>) -> ::diesel::serialize::Result {
use ::std::io::Write;
out.write_all(self.to_string().as_bytes())?;
Ok(::diesel::serialize::IsNull::No)
}
}
#[automatically_derived]
impl #impl_generics ::diesel::deserialize::FromSql<::diesel::sql_types::Text, ::diesel::pg::Pg> for #name #ty_generics
#where_clause
{
fn from_sql(value: ::diesel::pg::PgValue) -> diesel::deserialize::Result<Self> {
use ::core::str::FromStr;
Self::from_str(::core::str::from_utf8(value.as_bytes())?)
.map_err(|_| "Unrecognized enum variant".into())
}
}
})
}
pub(crate) fn diesel_enum_derive_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
@ -50,18 +87,41 @@ pub(crate) fn diesel_enum_attribute_inner(
args: &AttributeArgs,
item: &ItemEnum,
) -> syn::Result<TokenStream> {
if !args.is_empty() {
return Err(syn::Error::new(
Span::call_site(),
"This attribute macro does not accept any arguments.",
));
#[derive(FromMeta, Debug)]
enum StorageType {
PgEnum,
Text,
}
let name = &item.ident;
let struct_name = format_ident!("Db{name}");
Ok(quote! {
#[derive(diesel::AsExpression, diesel::FromSqlRow)]
#[diesel(sql_type = #struct_name)]
#item
})
#[derive(FromMeta, Debug)]
struct StorageTypeArgs {
storage_type: StorageType,
}
let storage_type_args = match StorageTypeArgs::from_list(args) {
Ok(v) => v,
Err(_) => {
return Err(syn::Error::new(
Span::call_site(),
"Expected storage_type of text or pg_enum",
));
}
};
match storage_type_args.storage_type {
StorageType::PgEnum => {
let name = &item.ident;
let type_name = format_ident!("Db{name}");
Ok(quote! {
#[derive(diesel::AsExpression, diesel::FromSqlRow, router_derive::DieselEnum) ]
#[diesel(sql_type = #type_name)]
#item
})
}
StorageType::Text => Ok(quote! {
#[derive(diesel::AsExpression, diesel::FromSqlRow, router_derive::DieselEnumText) ]
#[diesel(sql_type = ::diesel::sql_types::Text)]
#item
}),
}
}

View File

@ -6,11 +6,11 @@ pub mod diesel_exports {
DbEventClass as EventClass, DbEventObjectType as EventObjectType, DbEventType as EventType,
DbFutureUsage as FutureUsage, DbIntentStatus as IntentStatus,
DbMandateStatus as MandateStatus, DbMandateType as MandateType,
DbMerchantStorageScheme as MerchantStorageScheme, DbPaymentFlow as PaymentFlow,
DbMerchantStorageScheme as MerchantStorageScheme,
DbPaymentMethodIssuerCode as PaymentMethodIssuerCode,
DbPaymentMethodSubType as PaymentMethodSubType, DbPaymentMethodType as PaymentMethodType,
DbProcessTrackerStatus as ProcessTrackerStatus, DbRefundStatus as RefundStatus,
DbRefundType as RefundType, DbRoutingAlgorithm as RoutingAlgorithm,
DbRefundType as RefundType,
};
}
@ -25,9 +25,8 @@ pub mod diesel_exports {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum AttemptStatus {
@ -66,10 +65,9 @@ pub enum AttemptStatus {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum AuthenticationType {
@ -89,10 +87,9 @@ pub enum AuthenticationType {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum CaptureMethod {
@ -113,10 +110,9 @@ pub enum CaptureMethod {
strum::EnumString,
serde::Deserialize,
serde::Serialize,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ConnectorType {
@ -149,10 +145,9 @@ pub enum ConnectorType {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
pub enum Currency {
AED,
ALL,
@ -270,9 +265,8 @@ pub enum Currency {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum EventClass {
@ -289,9 +283,8 @@ pub enum EventClass {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum EventObjectType {
@ -308,10 +301,9 @@ pub enum EventObjectType {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum EventType {
@ -329,10 +321,9 @@ pub enum EventType {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum IntentStatus {
@ -358,10 +349,9 @@ pub enum IntentStatus {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum FutureUsage {
@ -381,9 +371,8 @@ pub enum FutureUsage {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum MerchantStorageScheme {
@ -392,30 +381,6 @@ pub enum MerchantStorageScheme {
RedisKv,
}
#[derive(
Clone,
Copy,
Debug,
Eq,
PartialEq,
strum::Display,
strum::EnumString,
serde::Serialize,
serde::Deserialize,
router_derive::DieselEnum,
)]
#[router_derive::diesel_enum]
#[strum(serialize_all = "snake_case")]
pub enum PaymentFlow {
Vsc,
Emi,
Otp,
UpiIntent,
UpiCollect,
UpiScanAndPay,
Sdk,
}
#[derive(
Clone,
Copy,
@ -427,10 +392,9 @@ pub enum PaymentFlow {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum PaymentMethodIssuerCode {
@ -457,10 +421,9 @@ pub enum PaymentMethodIssuerCode {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum PaymentMethodSubType {
@ -484,10 +447,9 @@ pub enum PaymentMethodSubType {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum PaymentMethodType {
@ -517,9 +479,8 @@ pub enum PaymentMethodType {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum WalletIssuer {
@ -537,9 +498,8 @@ pub enum WalletIssuer {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum ProcessTrackerStatus {
@ -566,10 +526,9 @@ pub enum ProcessTrackerStatus {
serde::Deserialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[strum(serialize_all = "snake_case")]
pub enum RefundStatus {
Failure,
@ -591,9 +550,8 @@ pub enum RefundStatus {
serde::Deserialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[strum(serialize_all = "snake_case")]
pub enum RefundType {
InstantRefund,
@ -602,29 +560,6 @@ pub enum RefundType {
RetryRefund,
}
#[derive(
Clone,
Copy,
Debug,
Eq,
PartialEq,
serde::Deserialize,
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
#[router_derive::diesel_enum]
pub enum RoutingAlgorithm {
RoundRobin,
MaxConversion,
MinCost,
Custom,
}
// Mandate
#[derive(
Clone,
@ -637,9 +572,8 @@ pub enum RoutingAlgorithm {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum MandateType {
@ -659,10 +593,9 @@ pub enum MandateType {
serde::Serialize,
strum::Display,
strum::EnumString,
router_derive::DieselEnum,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum]
#[router_derive::diesel_enum(storage_type = "pg_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum MandateStatus {
@ -672,3 +605,62 @@ pub enum MandateStatus {
Pending,
Revoked,
}
#[derive(
Clone,
Copy,
Debug,
Eq,
Hash,
PartialEq,
serde::Deserialize,
serde::Serialize,
strum::Display,
strum::EnumString,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum(storage_type = "text")]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum PaymentIssuer {
Klarna,
Affirm,
AfterpayClearpay,
AmericanExpress,
BankOfAmerica,
Barclays,
CapitalOne,
Chase,
Citi,
Discover,
NavyFederalCreditUnion,
PentagonFederalCreditUnion,
SynchronyBank,
WellsFargo,
}
#[derive(
Eq,
PartialEq,
Hash,
Clone,
Debug,
serde::Serialize,
serde::Deserialize,
Default,
strum::Display,
strum::EnumString,
frunk::LabelledGeneric,
)]
#[router_derive::diesel_enum(storage_type = "text")]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum PaymentExperience {
#[default]
RedirectToUrl,
InvokeSdkClient,
DisplayQrCode,
OneClick,
LinkWallet,
InvokePaymentApp,
}

View File

@ -22,8 +22,6 @@ pub struct PaymentAttempt {
pub tax_amount: Option<i64>,
pub payment_method_id: Option<String>,
pub payment_method: Option<storage_enums::PaymentMethodType>,
pub payment_flow: Option<storage_enums::PaymentFlow>,
pub redirect: Option<bool>,
pub connector_transaction_id: Option<String>,
pub capture_method: Option<storage_enums::CaptureMethod>,
pub capture_on: Option<PrimitiveDateTime>,
@ -39,6 +37,8 @@ pub struct PaymentAttempt {
pub error_code: Option<String>,
pub payment_token: Option<String>,
pub connector_metadata: Option<serde_json::Value>,
pub payment_issuer: Option<storage_enums::PaymentIssuer>,
pub payment_experience: Option<storage_enums::PaymentExperience>,
}
#[derive(
@ -61,8 +61,6 @@ pub struct PaymentAttemptNew {
pub tax_amount: Option<i64>,
pub payment_method_id: Option<String>,
pub payment_method: Option<storage_enums::PaymentMethodType>,
pub payment_flow: Option<storage_enums::PaymentFlow>,
pub redirect: Option<bool>,
pub connector_transaction_id: Option<String>,
pub capture_method: Option<storage_enums::CaptureMethod>,
pub capture_on: Option<PrimitiveDateTime>,
@ -78,6 +76,8 @@ pub struct PaymentAttemptNew {
pub payment_token: Option<String>,
pub error_code: Option<String>,
pub connector_metadata: Option<serde_json::Value>,
pub payment_issuer: Option<storage_enums::PaymentIssuer>,
pub payment_experience: Option<storage_enums::PaymentExperience>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -117,7 +117,6 @@ pub enum PaymentAttemptUpdate {
connector_transaction_id: Option<String>,
authentication_type: Option<storage_enums::AuthenticationType>,
payment_method_id: Option<Option<String>>,
redirect: Option<bool>,
mandate_id: Option<String>,
connector_metadata: Option<serde_json::Value>,
},
@ -146,7 +145,6 @@ pub struct PaymentAttemptUpdateInternal {
payment_method_id: Option<Option<String>>,
cancellation_reason: Option<String>,
modified_at: Option<PrimitiveDateTime>,
redirect: Option<bool>,
mandate_id: Option<String>,
browser_info: Option<serde_json::Value>,
payment_token: Option<String>,
@ -243,7 +241,6 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
connector_transaction_id,
authentication_type,
payment_method_id,
redirect,
mandate_id,
connector_metadata,
} => Self {
@ -253,7 +250,6 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
authentication_type,
payment_method_id,
modified_at: Some(common_utils::date_time::now()),
redirect,
mandate_id,
connector_metadata,
..Default::default()

View File

@ -219,8 +219,6 @@ diesel::table! {
tax_amount -> Nullable<Int8>,
payment_method_id -> Nullable<Varchar>,
payment_method -> Nullable<PaymentMethodType>,
payment_flow -> Nullable<PaymentFlow>,
redirect -> Nullable<Bool>,
connector_transaction_id -> Nullable<Varchar>,
capture_method -> Nullable<CaptureMethod>,
capture_on -> Nullable<Timestamp>,
@ -236,6 +234,8 @@ diesel::table! {
error_code -> Nullable<Varchar>,
payment_token -> Nullable<Varchar>,
connector_metadata -> Nullable<Jsonb>,
payment_issuer -> Nullable<Varchar>,
payment_experience -> Nullable<Varchar>,
}
}

View File

@ -0,0 +1,3 @@
ALTER TABLE payment_attempt DROP COLUMN IF EXISTS payment_issuer;
ALTER TABLE payment_attempt DROP COLUMN IF EXISTS payment_experience;

View File

@ -0,0 +1,6 @@
-- Your SQL goes here
ALTER TABLE payment_attempt
ADD COLUMN IF NOT EXISTS payment_issuer VARCHAR(50);
ALTER TABLE payment_attempt
ADD COLUMN IF NOT EXISTS payment_experience VARCHAR(50);

View File

@ -0,0 +1,15 @@
CREATE TYPE "PaymentFlow" AS ENUM (
'vsc',
'emi',
'otp',
'upi_intent',
'upi_collect',
'upi_scan_and_pay',
'sdk'
);
ALTER TABLE payment_attempt
ADD COLUMN payment_flow "PaymentFlow";
ALTER TABLE payment_attempt
ADD COLUMN redirect BOOLEAN;

View File

@ -0,0 +1,5 @@
ALTER TABLE payment_attempt DROP COLUMN IF EXISTS redirect;
ALTER TABLE payment_attempt DROP COLUMN IF EXISTS payment_flow;
DROP TYPE IF EXISTS "PaymentFlow";

File diff suppressed because it is too large Load Diff