mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +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 {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user