feat(stripe): add support for afterpay clearpay through stripe (#441)

This commit is contained in:
Narayan Bhat
2023-01-25 21:13:21 +05:30
committed by GitHub
parent ecd0ca53b9
commit 351087fc7f
6 changed files with 173 additions and 90 deletions

View File

@ -321,14 +321,20 @@ pub struct CCard {
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum KlarnaRedirectIssuer {
Stripe,
pub enum KlarnaIssuer {
Klarna,
}
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum KlarnaSdkIssuer {
Klarna,
pub enum AffirmIssuer {
Affirm,
}
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum AfterpayClearpayIssuer {
AfterpayClearpay,
}
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
@ -337,7 +343,7 @@ pub enum PayLaterData {
/// For KlarnaRedirect as PayLater Option
KlarnaRedirect {
/// The issuer name of the redirect
issuer_name: KlarnaRedirectIssuer,
issuer_name: KlarnaIssuer,
/// The billing email
billing_email: String,
// The billing country code
@ -345,16 +351,27 @@ pub enum PayLaterData {
},
/// For Klarna Sdk as PayLater Option
KlarnaSdk {
/// The issuer name of the redirect
issuer_name: KlarnaSdkIssuer,
/// The issuer name of the sdk
issuer_name: KlarnaIssuer,
/// The token for the sdk workflow
token: String,
},
/// For Affirm redirect flow
/// For Affirm redirect as PayLater Option
AffirmRedirect {
/// The billing email address
/// The issuer name of affirm redirect issuer
issuer_name: AffirmIssuer,
/// The billing email
billing_email: String,
},
/// For AfterpayClearpay redirect as PayLater Option
AfterpayClearpayRedirect {
/// The issuer name of afterpayclearpay redirect issuer
issuer_name: AfterpayClearpayIssuer,
/// The billing email
billing_email: String,
/// The billing name
billing_name: String,
},
}
#[derive(Debug, Clone, Eq, PartialEq, Default, serde::Deserialize, serde::Serialize, ToSchema)]

View File

@ -344,7 +344,8 @@ impl
&self,
req: &types::PaymentsAuthorizeRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let stripe_req = utils::Encode::<stripe::PaymentIntentRequest>::convert_and_url_encode(req)
let req = stripe::PaymentIntentRequest::try_from(req)?;
let stripe_req = utils::Encode::<stripe::PaymentIntentRequest>::encode(&req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(stripe_req))
}

View File

@ -1,6 +1,9 @@
use std::str::FromStr;
use api_models::{self, payments};
use common_utils::fp_utils;
use error_stack::{IntoReport, ResultExt};
use masking::ExposeInterface;
use serde::{Deserialize, Serialize};
use strum::EnumString;
use url::Url;
@ -81,7 +84,7 @@ pub struct PaymentIntentRequest {
pub mandate: Option<String>,
pub description: Option<String>,
#[serde(flatten)]
pub shipping: Address,
pub shipping: StripeShippingAddress,
#[serde(flatten)]
pub payment_data: Option<StripePaymentMethodData>,
pub capture_method: StripeCaptureMethod,
@ -129,6 +132,8 @@ pub struct StripePayLaterData {
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)]
@ -137,6 +142,7 @@ pub enum StripePaymentMethodData {
Card(StripeCardData),
Klarna(StripePayLaterData),
Affirm(StripePayLaterData),
AfterpayClearpay(StripePayLaterData),
Bank,
Wallet,
Paypal,
@ -148,10 +154,46 @@ pub enum StripePaymentMethodType {
Card,
Klarna,
Affirm,
AfterpayClearpay,
}
fn validate_shipping_address_against_payment_method(
shipping_address: &StripeShippingAddress,
payment_method: &payments::PaymentMethod,
) -> Result<(), errors::ConnectorError> {
if let payments::PaymentMethod::PayLater(payments::PayLaterData::AfterpayClearpayRedirect {
..
}) = payment_method
{
fp_utils::when(shipping_address.name.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.first_name".to_string(),
})
})?;
fp_utils::when(shipping_address.line1.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.line1".to_string(),
})
})?;
fp_utils::when(shipping_address.country.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.country".to_string(),
})
})?;
fp_utils::when(shipping_address.zip.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.zip".to_string(),
})
})?;
}
Ok(())
}
impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
type Error = error_stack::Report<errors::ConnectorError>;
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");
@ -166,71 +208,32 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
.clone()
.and_then(|mandate_ids| mandate_ids.connector_mandate_id)
{
None => (
Some(match item.request.payment_method_data {
api::PaymentMethod::Card(ref ccard) => StripePaymentMethodData::Card({
let payment_method_auth_type = match item.auth_type {
enums::AuthenticationType::ThreeDs => Auth3ds::Any,
enums::AuthenticationType::NoThreeDs => Auth3ds::Automatic,
};
StripeCardData {
payment_method_types: StripePaymentMethodType::Card,
payment_method_data_type: StripePaymentMethodType::Card,
payment_method_data_card_number: ccard.card_number.clone(),
payment_method_data_card_exp_month: ccard.card_exp_month.clone(),
payment_method_data_card_exp_year: ccard.card_exp_year.clone(),
payment_method_data_card_cvc: ccard.card_cvc.clone(),
payment_method_auth_type,
None => {
let payment_method: StripePaymentMethodData =
(item.request.payment_method_data.clone(), item.auth_type).try_into()?;
(Some(payment_method), None)
}
}),
api::PaymentMethod::BankTransfer => StripePaymentMethodData::Bank,
api::PaymentMethod::PayLater(ref pay_later_data) => match pay_later_data {
api_models::payments::PayLaterData::KlarnaRedirect {
billing_email,
billing_country,
..
} => StripePaymentMethodData::Klarna(StripePayLaterData {
payment_method_types: StripePaymentMethodType::Klarna,
payment_method_data_type: StripePaymentMethodType::Klarna,
billing_email: billing_email.to_string(),
billing_country: Some(billing_country.to_string()),
}),
api_models::payments::PayLaterData::AffirmRedirect {
billing_email,
} => StripePaymentMethodData::Affirm(StripePayLaterData {
payment_method_types: StripePaymentMethodType::Affirm,
payment_method_data_type: StripePaymentMethodType::Affirm,
billing_email: billing_email.to_string(),
billing_country: None,
}),
_ => Err(error_stack::report!(
errors::ConnectorError::NotImplemented(String::from("Stripe does not support payment through provided payment method"))
))?,
},
api::PaymentMethod::Wallet(_) => StripePaymentMethodData::Wallet,
api::PaymentMethod::Paypal => StripePaymentMethodData::Paypal,
}),
None,
),
Some(mandate_id) => (None, Some(mandate_id)),
}
};
let shipping_address = match item.address.shipping.clone() {
Some(mut shipping) => Address {
Some(mut shipping) => StripeShippingAddress {
city: shipping.address.as_mut().and_then(|a| a.city.take()),
country: shipping.address.as_mut().and_then(|a| a.country.take()),
line1: shipping.address.as_mut().and_then(|a| a.line1.take()),
line2: shipping.address.as_mut().and_then(|a| a.line2.take()),
postal_code: shipping.address.as_mut().and_then(|a| a.zip.take()),
zip: shipping.address.as_mut().and_then(|a| a.zip.take()),
state: shipping.address.as_mut().and_then(|a| a.state.take()),
name: shipping.address.as_mut().map(|a| {
name: shipping.address.as_mut().and_then(|a| {
a.first_name.as_ref().map(|first_name| {
format!(
"{} {}",
a.first_name.clone().expose_option().unwrap_or_default(),
first_name.clone().expose(),
a.last_name.clone().expose_option().unwrap_or_default()
)
.into()
})
}),
phone: shipping.phone.map(|p| {
format!(
@ -241,9 +244,14 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
.into()
}),
},
None => Address::default(),
None => StripeShippingAddress::default(),
};
validate_shipping_address_against_payment_method(
&shipping_address,
&item.request.payment_method_data,
)?;
let off_session = item
.request
.off_session
@ -402,6 +410,7 @@ impl<F, T>
} => mandate_options.map(|mandate_options| mandate_options.reference),
StripePaymentMethodOptions::Klarna {} => None,
StripePaymentMethodOptions::Affirm {} => None,
StripePaymentMethodOptions::AfterpayClearpay {} => None,
});
Ok(Self {
@ -457,6 +466,7 @@ impl<F, T>
} => mandate_options.map(|mandate_option| mandate_option.reference),
StripePaymentMethodOptions::Klarna {} => None,
StripePaymentMethodOptions::Affirm {} => None,
StripePaymentMethodOptions::AfterpayClearpay {} => None,
});
Ok(Self {
@ -620,7 +630,7 @@ pub struct ErrorResponse {
}
#[derive(Debug, Default, Eq, PartialEq, Serialize)]
pub struct Address {
pub struct StripeShippingAddress {
#[serde(rename = "shipping[address][city]")]
pub city: Option<String>,
#[serde(rename = "shipping[address][country]")]
@ -630,7 +640,7 @@ pub struct Address {
#[serde(rename = "shipping[address][line2]")]
pub line2: Option<Secret<String>>,
#[serde(rename = "shipping[address][postal_code]")]
pub postal_code: Option<Secret<String>>,
pub zip: Option<Secret<String>>,
#[serde(rename = "shipping[address][state]")]
pub state: Option<Secret<String>>,
#[serde(rename = "shipping[name]")]
@ -690,6 +700,7 @@ pub enum StripePaymentMethodOptions {
},
Klarna {},
Affirm {},
AfterpayClearpay {},
}
// #[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
// pub struct Card
@ -782,7 +793,7 @@ pub struct StripeWebhookObjectId {
}
impl TryFrom<(api::PaymentMethod, enums::AuthenticationType)> for StripePaymentMethodData {
type Error = error_stack::Report<errors::ConnectorError>;
type Error = errors::ConnectorError;
fn try_from(
(pm_data, auth_type): (api::PaymentMethod, enums::AuthenticationType),
) -> Result<Self, Self::Error> {
@ -813,12 +824,31 @@ impl TryFrom<(api::PaymentMethod, enums::AuthenticationType)> for StripePaymentM
payment_method_data_type: StripePaymentMethodType::Klarna,
billing_email,
billing_country: Some(billing_country),
billing_name: None,
})),
_ => Err(error_stack::report!(
errors::ConnectorError::NotImplemented(String::from(
"Stripe does not support payment through provided payment method"
))
)),
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::Wallet(_) => Ok(Self::Wallet),
api::PaymentMethod::Paypal => Ok(Self::Paypal),

View File

@ -2,7 +2,7 @@ use std::marker::PhantomData;
use async_trait::async_trait;
use common_utils::ext_traits::{AsyncExt, Encode};
use error_stack::ResultExt;
use error_stack::{self, ResultExt};
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};
use uuid::Uuid;

View File

@ -80,8 +80,9 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::payments::NextActionType,
api_models::payments::Metadata,
api_models::payments::WalletData,
api_models::payments::KlarnaRedirectIssuer,
api_models::payments::KlarnaSdkIssuer,
api_models::payments::KlarnaIssuer,
api_models::payments::AffirmIssuer,
api_models::payments::AfterpayClearpayIssuer,
api_models::payments::NextAction,
api_models::payments::PayLaterData,
api_models::payments::MandateData,

View File

@ -203,6 +203,14 @@
}
}
},
"AffirmIssuer": {
"type": "string",
"enum": ["affirm"]
},
"AfterpayClearpayIssuer": {
"type": "string",
"enum": ["afterpay_clearpay"]
},
"AuthenticationType": {
"type": "string",
"enum": ["three_ds", "no_three_ds"]
@ -778,11 +786,7 @@
"requires_capture"
]
},
"KlarnaRedirectIssuer": {
"type": "string",
"enum": ["stripe"]
},
"KlarnaSdkIssuer": {
"KlarnaIssuer": {
"type": "string",
"enum": ["klarna"]
},
@ -1071,7 +1075,7 @@
"required": ["issuer_name", "billing_email", "billing_country"],
"properties": {
"issuer_name": {
"$ref": "#/components/schemas/KlarnaRedirectIssuer"
"$ref": "#/components/schemas/KlarnaIssuer"
},
"billing_email": {
"type": "string",
@ -1094,7 +1098,7 @@
"required": ["issuer_name", "token"],
"properties": {
"issuer_name": {
"$ref": "#/components/schemas/KlarnaSdkIssuer"
"$ref": "#/components/schemas/KlarnaIssuer"
},
"token": {
"type": "string",
@ -1110,12 +1114,39 @@
"properties": {
"affirm_redirect": {
"type": "object",
"description": "For Affirm redirect flow",
"required": ["billing_email"],
"description": "For Affirm redirect as PayLater Option",
"required": ["issuer_name", "billing_email"],
"properties": {
"issuer_name": {
"$ref": "#/components/schemas/AffirmIssuer"
},
"billing_email": {
"type": "string",
"description": "The billing email address"
"description": "The billing email"
}
}
}
}
},
{
"type": "object",
"required": ["afterpay_clearpay_redirect"],
"properties": {
"afterpay_clearpay_redirect": {
"type": "object",
"description": "For AfterpayClearpay redirect as PayLater Option",
"required": ["issuer_name", "billing_email", "billing_name"],
"properties": {
"issuer_name": {
"$ref": "#/components/schemas/AfterpayClearpayIssuer"
},
"billing_email": {
"type": "string",
"description": "The billing email"
},
"billing_name": {
"type": "string",
"description": "The billing name"
}
}
}
@ -2036,6 +2067,9 @@
"error_message": {
"type": "string"
},
"error_code": {
"type": "string"
},
"created_at": {
"type": "string",
"format": "date-time"