fix(payment_link): added amount conversion to base unit based on currency (#3162)

This commit is contained in:
Sahkal Poddar
2023-12-18 21:32:31 +05:30
committed by GitHub
parent 30fe9d19e4
commit 0fa61a9dd1
5 changed files with 56 additions and 13 deletions

View File

@ -3355,7 +3355,7 @@ pub struct PaymentLinkInitiateRequest {
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
pub struct PaymentLinkDetails { pub struct PaymentLinkDetails {
pub amount: i64, pub amount: String,
pub currency: api_enums::Currency, pub currency: api_enums::Currency,
pub pub_key: String, pub pub_key: String,
pub client_secret: String, pub client_secret: String,
@ -3365,7 +3365,7 @@ pub struct PaymentLinkDetails {
pub merchant_logo: String, pub merchant_logo: String,
pub return_url: String, pub return_url: String,
pub merchant_name: String, pub merchant_name: String,
pub order_details: Option<Vec<OrderDetailsWithAmount>>, pub order_details: Option<Vec<OrderDetailsWithStringAmount>>,
pub max_items_visible_after_collapse: i8, pub max_items_visible_after_collapse: i8,
pub sdk_theme: Option<String>, pub sdk_theme: Option<String>,
} }
@ -3423,3 +3423,17 @@ pub struct PaymentLinkListResponse {
// The list of payment link response objects // The list of payment link response objects
pub data: Vec<PaymentLinkResponse>, pub data: Vec<PaymentLinkResponse>,
} }
#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
pub struct OrderDetailsWithStringAmount {
/// Name of the product that is being purchased
#[schema(max_length = 255, example = "shirt")]
pub product_name: String,
/// The quantity of the product to be purchased
#[schema(example = 1)]
pub quantity: u16,
/// the amount per quantity of product
pub amount: String,
/// Product Image link
pub product_img_link: Option<String>,
}

View File

@ -241,6 +241,8 @@ pub enum StripeErrorCode {
LockTimeout, LockTimeout,
#[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "Merchant connector account is configured with invalid {config}")] #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "Merchant connector account is configured with invalid {config}")]
InvalidConnectorConfiguration { config: String }, InvalidConnectorConfiguration { config: String },
#[error(error_type = StripeErrorType::HyperswitchError, code = "HE_01", message = "Failed to convert currency to minor unit")]
CurrencyConversionFailed,
// [#216]: https://github.com/juspay/hyperswitch/issues/216 // [#216]: https://github.com/juspay/hyperswitch/issues/216
// Implement the remaining stripe error codes // Implement the remaining stripe error codes
@ -595,6 +597,7 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
errors::ApiErrorResponse::InvalidConnectorConfiguration { config } => { errors::ApiErrorResponse::InvalidConnectorConfiguration { config } => {
Self::InvalidConnectorConfiguration { config } Self::InvalidConnectorConfiguration { config }
} }
errors::ApiErrorResponse::CurrencyConversionFailed => Self::CurrencyConversionFailed,
} }
} }
} }
@ -662,7 +665,8 @@ impl actix_web::ResponseError for StripeErrorCode {
| Self::CurrencyNotSupported { .. } | Self::CurrencyNotSupported { .. }
| Self::DuplicateCustomer | Self::DuplicateCustomer
| Self::PaymentMethodUnactivated | Self::PaymentMethodUnactivated
| Self::InvalidConnectorConfiguration { .. } => StatusCode::BAD_REQUEST, | Self::InvalidConnectorConfiguration { .. }
| Self::CurrencyConversionFailed => StatusCode::BAD_REQUEST,
Self::RefundFailed Self::RefundFailed
| Self::PayoutFailed | Self::PayoutFailed
| Self::PaymentLinkNotFound | Self::PaymentLinkNotFound

View File

@ -238,6 +238,8 @@ pub enum ApiErrorResponse {
CurrencyNotSupported { message: String }, CurrencyNotSupported { message: String },
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_24", message = "Merchant connector account is configured with invalid {config}")] #[error(error_type = ErrorType::InvalidRequestError, code = "IR_24", message = "Merchant connector account is configured with invalid {config}")]
InvalidConnectorConfiguration { config: String }, InvalidConnectorConfiguration { config: String },
#[error(error_type = ErrorType::ValidationError, code = "HE_01", message = "Failed to convert currency to minor unit")]
CurrencyConversionFailed,
} }
impl PTError for ApiErrorResponse { impl PTError for ApiErrorResponse {

View File

@ -270,6 +270,9 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon
Self::InvalidConnectorConfiguration {config} => { Self::InvalidConnectorConfiguration {config} => {
AER::BadRequest(ApiError::new("IR", 24, format!("Merchant connector account is configured with invalid {config}"), None)) AER::BadRequest(ApiError::new("IR", 24, format!("Merchant connector account is configured with invalid {config}"), None))
} }
Self::CurrencyConversionFailed => {
AER::Unprocessable(ApiError::new("HE", 2, "Failed to convert currency to minor unit", None))
}
} }
} }
} }

View File

@ -85,8 +85,6 @@ pub async fn intiate_payment_link_flow(
extract_payment_link_config(merchant_account.payment_link_config.clone())? extract_payment_link_config(merchant_account.payment_link_config.clone())?
}; };
let order_details = validate_order_details(payment_intent.order_details)?;
let return_url = if let Some(payment_create_return_url) = payment_intent.return_url { let return_url = if let Some(payment_create_return_url) = payment_intent.return_url {
payment_create_return_url payment_create_return_url
} else { } else {
@ -102,12 +100,16 @@ pub async fn intiate_payment_link_flow(
payment_intent.currency, payment_intent.currency,
payment_intent.client_secret, payment_intent.client_secret,
)?; )?;
let order_details = validate_order_details(payment_intent.order_details, currency)?;
let (default_sdk_theme, default_background_color) = let (default_sdk_theme, default_background_color) =
(DEFAULT_SDK_THEME, DEFAULT_BACKGROUND_COLOR); (DEFAULT_SDK_THEME, DEFAULT_BACKGROUND_COLOR);
let payment_details = api_models::payments::PaymentLinkDetails { let payment_details = api_models::payments::PaymentLinkDetails {
amount: payment_intent.amount, amount: currency
.to_currency_base_unit(payment_intent.amount)
.into_report()
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?,
currency, currency,
payment_id: payment_intent.payment_id, payment_id: payment_intent.payment_id,
merchant_name: payment_link.custom_merchant_name.unwrap_or( merchant_name: payment_link.custom_merchant_name.unwrap_or(
@ -236,8 +238,9 @@ pub fn check_payment_link_status(fulfillment_time: Option<PrimitiveDateTime>) ->
fn validate_order_details( fn validate_order_details(
order_details: Option<Vec<Secret<serde_json::Value>>>, order_details: Option<Vec<Secret<serde_json::Value>>>,
currency: api_models::enums::Currency,
) -> Result< ) -> Result<
Option<Vec<api_models::payments::OrderDetailsWithAmount>>, Option<Vec<api_models::payments::OrderDetailsWithStringAmount>>,
error_stack::Report<errors::ApiErrorResponse>, error_stack::Report<errors::ApiErrorResponse>,
> { > {
let order_details = order_details let order_details = order_details
@ -256,14 +259,31 @@ fn validate_order_details(
}) })
.transpose()?; .transpose()?;
let updated_order_details = order_details.map(|mut order_details| { let updated_order_details = match order_details {
for order in order_details.iter_mut() { Some(mut order_details) => {
if order.product_img_link.is_none() { let mut order_details_amount_string_array: Vec<
order.product_img_link = Some(DEFAULT_PRODUCT_IMG.to_string()); api_models::payments::OrderDetailsWithStringAmount,
> = Vec::new();
for order in order_details.iter_mut() {
let mut order_details_amount_string : api_models::payments::OrderDetailsWithStringAmount = Default::default();
if order.product_img_link.is_none() {
order_details_amount_string.product_img_link =
Some(DEFAULT_PRODUCT_IMG.to_string())
} else {
order_details_amount_string.product_img_link = order.product_img_link.clone()
};
order_details_amount_string.amount = currency
.to_currency_base_unit(order.amount)
.into_report()
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?;
order_details_amount_string.product_name = order.product_name.clone();
order_details_amount_string.quantity = order.quantity;
order_details_amount_string_array.push(order_details_amount_string)
} }
Some(order_details_amount_string_array)
} }
order_details None => None,
}); };
Ok(updated_order_details) Ok(updated_order_details)
} }