mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +08:00 
			
		
		
		
	fix(payment_link): added amount conversion to base unit based on currency (#3162)
This commit is contained in:
		| @ -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>, | ||||||
|  | } | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
| @ -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 { | ||||||
|  | |||||||
| @ -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)) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 { | ||||||
|  |         Some(mut order_details) => { | ||||||
|  |             let mut order_details_amount_string_array: Vec< | ||||||
|  |                 api_models::payments::OrderDetailsWithStringAmount, | ||||||
|  |             > = Vec::new(); | ||||||
|             for order in order_details.iter_mut() { |             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() { |                 if order.product_img_link.is_none() { | ||||||
|                 order.product_img_link = Some(DEFAULT_PRODUCT_IMG.to_string()); |                     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) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Sahkal Poddar
					Sahkal Poddar