mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +08:00
feat(connector): [AMAZONPAY] add Payment flows for Amazon Pay Wallet (#7062)
Co-authored-by: Anurag Singh <anurag.singh.001@Anurag-Singh-WPMHJ5619X.local> Co-authored-by: Anurag Singh <anurag.singh.001@AnuragSMHJ5619X.lan> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Anurag Singh <anurag.singh.001@MacBookPro.lan>
This commit is contained in:
@ -115,6 +115,7 @@ pub trait RouterData {
|
||||
fn get_optional_shipping(&self) -> Option<&hyperswitch_domain_models::address::Address>;
|
||||
fn get_optional_shipping_line1(&self) -> Option<Secret<String>>;
|
||||
fn get_optional_shipping_line2(&self) -> Option<Secret<String>>;
|
||||
fn get_optional_shipping_line3(&self) -> Option<Secret<String>>;
|
||||
fn get_optional_shipping_city(&self) -> Option<String>;
|
||||
fn get_optional_shipping_country(&self) -> Option<enums::CountryAlpha2>;
|
||||
fn get_optional_shipping_zip(&self) -> Option<Secret<String>>;
|
||||
@ -364,6 +365,15 @@ impl<Flow, Request, Response> RouterData for types::RouterData<Flow, Request, Re
|
||||
})
|
||||
}
|
||||
|
||||
fn get_optional_shipping_line3(&self) -> Option<Secret<String>> {
|
||||
self.address.get_shipping().and_then(|shipping_address| {
|
||||
shipping_address
|
||||
.clone()
|
||||
.address
|
||||
.and_then(|shipping_details| shipping_details.line3)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_optional_shipping_city(&self) -> Option<String> {
|
||||
self.address.get_shipping().and_then(|shipping_address| {
|
||||
shipping_address
|
||||
@ -2492,6 +2502,7 @@ pub enum PaymentMethodDataType {
|
||||
AliPayQr,
|
||||
AliPayRedirect,
|
||||
AliPayHkRedirect,
|
||||
AmazonPay,
|
||||
AmazonPayRedirect,
|
||||
Paysera,
|
||||
Skrill,
|
||||
@ -2622,6 +2633,7 @@ impl From<domain::payments::PaymentMethodData> for PaymentMethodDataType {
|
||||
domain::payments::WalletData::AliPayQr(_) => Self::AliPayQr,
|
||||
domain::payments::WalletData::AliPayRedirect(_) => Self::AliPayRedirect,
|
||||
domain::payments::WalletData::AliPayHkRedirect(_) => Self::AliPayHkRedirect,
|
||||
domain::payments::WalletData::AmazonPay(_) => Self::AmazonPay,
|
||||
domain::payments::WalletData::AmazonPayRedirect(_) => Self::AmazonPayRedirect,
|
||||
domain::payments::WalletData::Paysera(_) => Self::Paysera,
|
||||
domain::payments::WalletData::Skrill(_) => Self::Skrill,
|
||||
|
||||
@ -88,6 +88,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> {
|
||||
airwallex::transformers::AirwallexAuthType::try_from(self.auth_type)?;
|
||||
Ok(())
|
||||
}
|
||||
api_enums::Connector::Amazonpay => {
|
||||
amazonpay::transformers::AmazonpayAuthType::try_from(self.auth_type)?;
|
||||
Ok(())
|
||||
}
|
||||
api_enums::Connector::Archipel => {
|
||||
archipel::transformers::ArchipelAuthType::try_from(self.auth_type)?;
|
||||
archipel::transformers::ArchipelConfigData::try_from(self.connector_meta_data)?;
|
||||
|
||||
@ -1253,6 +1253,149 @@ fn create_paypal_sdk_session_token(
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_amazon_pay_session_token(
|
||||
router_data: &types::PaymentsSessionRouterData,
|
||||
state: &routes::SessionState,
|
||||
) -> RouterResult<types::PaymentsSessionRouterData> {
|
||||
let amazon_pay_session_token_data = router_data
|
||||
.connector_wallets_details
|
||||
.clone()
|
||||
.parse_value::<payment_types::AmazonPaySessionTokenData>("AmazonPaySessionTokenData")
|
||||
.change_context(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "merchant_id or store_id",
|
||||
})?;
|
||||
let amazon_pay_metadata = amazon_pay_session_token_data.data;
|
||||
let merchant_id = amazon_pay_metadata.merchant_id;
|
||||
let store_id = amazon_pay_metadata.store_id;
|
||||
let amazonpay_supported_currencies =
|
||||
payments::cards::list_countries_currencies_for_connector_payment_method_util(
|
||||
state.conf.pm_filters.clone(),
|
||||
enums::Connector::Amazonpay,
|
||||
enums::PaymentMethodType::AmazonPay,
|
||||
)
|
||||
.await
|
||||
.currencies;
|
||||
// currently supports only the US region hence USD is the only supported currency
|
||||
payment_types::AmazonPayDeliveryOptions::validate_currency(
|
||||
router_data.request.currency,
|
||||
amazonpay_supported_currencies.clone(),
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::CurrencyNotSupported {
|
||||
message: "USD is the only supported currency.".to_string(),
|
||||
})?;
|
||||
let ledger_currency = router_data.request.currency;
|
||||
// currently supports only the 'automatic' capture_method
|
||||
let payment_intent = payment_types::AmazonPayPaymentIntent::AuthorizeWithCapture;
|
||||
let required_amount_type = StringMajorUnitForConnector;
|
||||
let total_tax_amount = required_amount_type
|
||||
.convert(
|
||||
router_data.request.order_tax_amount.unwrap_or_default(),
|
||||
router_data.request.currency,
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::AmountConversionFailed {
|
||||
amount_type: "StringMajorUnit",
|
||||
})?;
|
||||
let total_base_amount = required_amount_type
|
||||
.convert(
|
||||
router_data.request.minor_amount,
|
||||
router_data.request.currency,
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::AmountConversionFailed {
|
||||
amount_type: "StringMajorUnit",
|
||||
})?;
|
||||
|
||||
let delivery_options_request = router_data
|
||||
.request
|
||||
.metadata
|
||||
.clone()
|
||||
.and_then(|metadata| {
|
||||
metadata
|
||||
.expose()
|
||||
.get("delivery_options")
|
||||
.and_then(|value| value.as_array().cloned())
|
||||
})
|
||||
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "metadata.delivery_options",
|
||||
})?;
|
||||
|
||||
let mut delivery_options =
|
||||
payment_types::AmazonPayDeliveryOptions::parse_delivery_options_request(
|
||||
&delivery_options_request,
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
|
||||
field_name: "delivery_options".to_string(),
|
||||
expected_format: r#""delivery_options": [{"id": String, "price": {"amount": Number, "currency_code": String}, "shipping_method":{"shipping_method_name": String, "shipping_method_code": String}, "is_default": Boolean}]"#.to_string(),
|
||||
})?;
|
||||
|
||||
let default_amount = payment_types::AmazonPayDeliveryOptions::get_default_delivery_amount(
|
||||
delivery_options.clone(),
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "is_default",
|
||||
})?;
|
||||
|
||||
for option in &delivery_options {
|
||||
payment_types::AmazonPayDeliveryOptions::validate_currency(
|
||||
option.price.currency_code,
|
||||
amazonpay_supported_currencies.clone(),
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::CurrencyNotSupported {
|
||||
message: "USD is the only supported currency.".to_string(),
|
||||
})?;
|
||||
}
|
||||
|
||||
payment_types::AmazonPayDeliveryOptions::insert_display_amount(
|
||||
&mut delivery_options,
|
||||
router_data.request.currency,
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::AmountConversionFailed {
|
||||
amount_type: "StringMajorUnit",
|
||||
})?;
|
||||
|
||||
let total_shipping_amount = match router_data.request.shipping_cost {
|
||||
Some(shipping_cost) => {
|
||||
if shipping_cost == default_amount {
|
||||
required_amount_type
|
||||
.convert(shipping_cost, router_data.request.currency)
|
||||
.change_context(errors::ApiErrorResponse::AmountConversionFailed {
|
||||
amount_type: "StringMajorUnit",
|
||||
})?
|
||||
} else {
|
||||
return Err(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "shipping_cost",
|
||||
})
|
||||
.attach_printable(format!(
|
||||
"Provided shipping_cost ({shipping_cost}) does not match the default delivery amount ({default_amount})"
|
||||
));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "shipping_cost",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(types::PaymentsSessionRouterData {
|
||||
response: Ok(types::PaymentsResponseData::SessionResponse {
|
||||
session_token: payment_types::SessionToken::AmazonPay(Box::new(
|
||||
payment_types::AmazonPaySessionTokenResponse {
|
||||
merchant_id,
|
||||
ledger_currency,
|
||||
store_id,
|
||||
payment_intent,
|
||||
total_shipping_amount,
|
||||
total_tax_amount,
|
||||
total_base_amount,
|
||||
delivery_options,
|
||||
},
|
||||
)),
|
||||
}),
|
||||
..router_data.clone()
|
||||
})
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RouterDataSession for types::PaymentsSessionRouterData {
|
||||
async fn decide_flow<'a, 'b>(
|
||||
@ -1308,6 +1451,7 @@ impl RouterDataSession for types::PaymentsSessionRouterData {
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
api::GetToken::AmazonPayMetadata => create_amazon_pay_session_token(self, state).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5404,6 +5404,9 @@ async fn get_and_merge_apple_pay_metadata(
|
||||
apple_pay: connector_wallets_details_optional
|
||||
.as_ref()
|
||||
.and_then(|d| d.apple_pay.clone()),
|
||||
amazon_pay: connector_wallets_details_optional
|
||||
.as_ref()
|
||||
.and_then(|d| d.amazon_pay.clone()),
|
||||
samsung_pay: connector_wallets_details_optional
|
||||
.as_ref()
|
||||
.and_then(|d| d.samsung_pay.clone()),
|
||||
@ -5425,6 +5428,9 @@ async fn get_and_merge_apple_pay_metadata(
|
||||
apple_pay_combined: connector_wallets_details_optional
|
||||
.as_ref()
|
||||
.and_then(|d| d.apple_pay_combined.clone()),
|
||||
amazon_pay: connector_wallets_details_optional
|
||||
.as_ref()
|
||||
.and_then(|d| d.amazon_pay.clone()),
|
||||
samsung_pay: connector_wallets_details_optional
|
||||
.as_ref()
|
||||
.and_then(|d| d.samsung_pay.clone()),
|
||||
|
||||
@ -514,6 +514,7 @@ impl From<api_models::enums::PaymentMethodType> for api::GetToken {
|
||||
api_models::enums::PaymentMethodType::SamsungPay => Self::SamsungPayMetadata,
|
||||
api_models::enums::PaymentMethodType::Paypal => Self::PaypalSdkMetadata,
|
||||
api_models::enums::PaymentMethodType::Paze => Self::PazeMetadata,
|
||||
api_models::enums::PaymentMethodType::AmazonPay => Self::AmazonPayMetadata,
|
||||
_ => Self::Connector,
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,6 +395,7 @@ impl From<api_models::enums::PaymentMethodType> for api::GetToken {
|
||||
api_models::enums::PaymentMethodType::SamsungPay => Self::SamsungPayMetadata,
|
||||
api_models::enums::PaymentMethodType::Paypal => Self::PaypalSdkMetadata,
|
||||
api_models::enums::PaymentMethodType::Paze => Self::PazeMetadata,
|
||||
api_models::enums::PaymentMethodType::AmazonPay => Self::AmazonPayMetadata,
|
||||
_ => Self::Connector,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1070,6 +1070,13 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>(
|
||||
ForeignInto::foreign_into((apple_pay_recurring_details, apple_pay_amount))
|
||||
});
|
||||
|
||||
let order_tax_amount = payment_data
|
||||
.payment_intent
|
||||
.amount_details
|
||||
.tax_details
|
||||
.clone()
|
||||
.and_then(|tax| tax.get_default_tax_amount());
|
||||
|
||||
// TODO: few fields are repeated in both routerdata and request
|
||||
let request = types::PaymentsSessionData {
|
||||
amount: payment_data
|
||||
@ -1095,6 +1102,9 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>(
|
||||
minor_amount: payment_data.payment_intent.amount_details.order_amount,
|
||||
apple_pay_recurring_details,
|
||||
customer_name,
|
||||
metadata: payment_data.payment_intent.metadata,
|
||||
order_tax_amount,
|
||||
shipping_cost: payment_data.payment_intent.amount_details.shipping_cost,
|
||||
};
|
||||
|
||||
// TODO: evaluate the fields in router data, if they are required or not
|
||||
@ -4814,6 +4824,13 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSessionD
|
||||
ForeignInto::foreign_into((apple_pay_recurring_details, apple_pay_amount))
|
||||
});
|
||||
|
||||
let order_tax_amount = payment_data
|
||||
.payment_intent
|
||||
.amount_details
|
||||
.tax_details
|
||||
.clone()
|
||||
.and_then(|tax| tax.get_default_tax_amount());
|
||||
|
||||
Ok(Self {
|
||||
amount: amount.get_amount_as_i64(), //need to change once we move to connector module
|
||||
minor_amount: amount,
|
||||
@ -4831,6 +4848,9 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSessionD
|
||||
email: payment_data.email,
|
||||
apple_pay_recurring_details,
|
||||
customer_name: None,
|
||||
metadata: payment_data.payment_intent.metadata,
|
||||
order_tax_amount,
|
||||
shipping_cost: payment_data.payment_intent.amount_details.shipping_cost,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -4900,6 +4920,20 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSessionD
|
||||
ForeignFrom::foreign_from((apple_pay_recurring_details, apple_pay_amount))
|
||||
});
|
||||
|
||||
let order_tax_amount = payment_data
|
||||
.payment_intent
|
||||
.tax_details
|
||||
.clone()
|
||||
.and_then(|tax| tax.get_default_tax_amount());
|
||||
|
||||
let shipping_cost = payment_data.payment_intent.shipping_cost;
|
||||
|
||||
let metadata = payment_data
|
||||
.payment_intent
|
||||
.metadata
|
||||
.clone()
|
||||
.map(Secret::new);
|
||||
|
||||
Ok(Self {
|
||||
amount: net_amount.get_amount_as_i64(), //need to change once we move to connector module
|
||||
minor_amount: amount,
|
||||
@ -4917,6 +4951,9 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSessionD
|
||||
surcharge_details: payment_data.surcharge_details,
|
||||
apple_pay_recurring_details,
|
||||
customer_name: None,
|
||||
order_tax_amount,
|
||||
shipping_cost,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ pub struct ConnectorData {
|
||||
pub enum GetToken {
|
||||
GpayMetadata,
|
||||
SamsungPayMetadata,
|
||||
AmazonPayMetadata,
|
||||
ApplePayMetadata,
|
||||
PaypalSdkMetadata,
|
||||
PazeMetadata,
|
||||
@ -117,9 +118,9 @@ impl ConnectorData {
|
||||
enums::Connector::Airwallex => {
|
||||
Ok(ConnectorEnum::Old(Box::new(connector::Airwallex::new())))
|
||||
}
|
||||
// enums::Connector::Amazonpay => {
|
||||
// Ok(ConnectorEnum::Old(Box::new(connector::Amazonpay)))
|
||||
// }
|
||||
enums::Connector::Amazonpay => {
|
||||
Ok(ConnectorEnum::Old(Box::new(connector::Amazonpay::new())))
|
||||
}
|
||||
enums::Connector::Archipel => {
|
||||
Ok(ConnectorEnum::Old(Box::new(connector::Archipel::new())))
|
||||
}
|
||||
|
||||
@ -31,9 +31,9 @@ impl FeatureMatrixConnectorData {
|
||||
enums::Connector::Airwallex => {
|
||||
Ok(ConnectorEnum::Old(Box::new(connector::Airwallex::new())))
|
||||
}
|
||||
// enums::Connector::Amazonpay => {
|
||||
// Ok(ConnectorEnum::Old(Box::new(connector::Amazonpay)))
|
||||
// }
|
||||
enums::Connector::Amazonpay => {
|
||||
Ok(ConnectorEnum::Old(Box::new(connector::Amazonpay::new())))
|
||||
}
|
||||
enums::Connector::Archipel => {
|
||||
Ok(ConnectorEnum::Old(Box::new(connector::Archipel::new())))
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ impl ForeignTryFrom<api_enums::Connector> for common_enums::RoutableConnectors {
|
||||
api_enums::Connector::Affirm => Self::Affirm,
|
||||
api_enums::Connector::Adyenplatform => Self::Adyenplatform,
|
||||
api_enums::Connector::Airwallex => Self::Airwallex,
|
||||
// api_enums::Connector::Amazonpay => Self::Amazonpay,
|
||||
api_enums::Connector::Amazonpay => Self::Amazonpay,
|
||||
api_enums::Connector::Archipel => Self::Archipel,
|
||||
api_enums::Connector::Authipay => Self::Authipay,
|
||||
api_enums::Connector::Authorizedotnet => Self::Authorizedotnet,
|
||||
|
||||
Reference in New Issue
Block a user