-
Notifications
You must be signed in to change notification settings - Fork 4.6k
feat(connector): [AMAZONPAY] add Payment flows for Amazon Pay Wallet #7062
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
1507918 to
ee8f7ae
Compare
bdb7248 to
5a41be2
Compare
b633ab2 to
e712eb0
Compare
config/deployments/sandbox.toml
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add these configs to hyperswitch/loadtest/config/development.toml
e712eb0 to
db29592
Compare
db43923 to
b261a93
Compare
9f0f89c to
0baee2c
Compare
| pub struct AmazonPaySessionTokenResponse { | ||
| pub merchant_id: String, | ||
| pub ledger_currency: String, | ||
| pub store_id: String, | ||
| pub payment_intent: String, | ||
| pub total_shipping_amount: StringMajorUnit, | ||
| pub total_tax_amount: StringMajorUnit, | ||
| pub total_base_amount: StringMajorUnit, | ||
| pub delivery_options: Vec<AmazonPayDeliveryOption>, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] | ||
| pub struct AmazonPayDeliveryOption { | ||
| pub id: String, | ||
| pub price: AmazonPayDeliveryPrice, | ||
| pub shipping_method: AmazonPayShippingMethod, | ||
| pub is_default: bool, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] | ||
| pub struct AmazonPayDeliveryPrice { | ||
| pub amount: String, | ||
| pub currency_code: String, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] | ||
| pub struct AmazonPayShippingMethod { | ||
| pub shipping_method_name: String, | ||
| pub shipping_method_code: String, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add documentation for every filed in the api_models.
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | ||
| pub struct AmazonPayMerchantCredentials { | ||
| pub merchant_id: String, | ||
| pub store_id: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add documentation for this field as well
crates/api_models/src/payments.rs
Outdated
| #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] | ||
| pub struct AmazonPayDeliveryPrice { | ||
| pub amount: String, | ||
| pub currency_code: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be made as enum
| match value { | ||
| enums::WalletType::GooglePay => Self::GooglePay, | ||
| enums::WalletType::AmazonPay => Self::AmazonPay, | ||
| enums::WalletType::GooglePay => Self::GooglePay, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change required ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reverted the change
| } | ||
| global_enums::PaymentMethodType::AmazonPay => Ok(dirval!(WalletType = AmazonPay)), | ||
| global_enums::PaymentMethodType::GooglePay => Ok(dirval!(WalletType = GooglePay)), | ||
| global_enums::PaymentMethodType::AmazonPay => Ok(dirval!(WalletType = AmazonPay)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change required ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needed to match every PaymentMethodType
crates/api_models/src/payments.rs
Outdated
|
|
||
| #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] | ||
| pub struct AmazonPayDeliveryPrice { | ||
| pub amount: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can amount be of type StringMajorUnit ?
crates/api_models/src/payments.rs
Outdated
| #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] | ||
| pub struct AmazonPaySessionTokenResponse { | ||
| pub merchant_id: String, | ||
| pub ledger_currency: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be enum ?
crates/api_models/src/payments.rs
Outdated
| pub merchant_id: String, | ||
| pub ledger_currency: String, | ||
| pub store_id: String, | ||
| pub payment_intent: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we are hardcoding payment_intent to AuthorizeWithCapture while creating session token, this can be made a enum ?
Or while creating session token we can create a const for AuthorizeWithCapture and add a comment line explaining the reason for hardcoding it.
| .map(|options| { | ||
| options | ||
| .iter() | ||
| .filter_map(|option| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of checking for every required field here, you can create a AmazonPayDeliveryOptions struct that will have all these fields as mandatory and have a try_from for the conversation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using try_from, I created a function to parse AmazonPayDeliveryOptions. If any field is missing, the function returns an error, resulting in an empty array for the session_token in AmazonPay.
|
|
||
| let is_default = option.get("is_default")?.as_bool()?; | ||
|
|
||
| Some(AmazonPayDeliveryOption { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also as per this logic if any one of the required field is not present in the particular delivery option, that element will be considered as None and we will check for new element.
Example:
"metadata": {
"delivery_options": [
{
"id": "standard-delivery",
"price":{
"amount": "20.00",
"currency_code":"USD"
},
"shipping_method":{
"shipping_method_code":"standard-courier"
},
"is_default":true
},
{
"id":"express-delivery",
"price":{
"amount":"50",
"currency_code":"USD"
},
"shipping_method":{
"shipping_method_name":"express-courier",
"shipping_method_code":"express-courier"
},
"is_default":false
}
]
},
In the above shipping_method_name is missing for standard-delivery hence that delivery_options in the session response will contain only express-delivery. Is it expected ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not expected. Changed the code to show the expected behavior.
098597b to
2f6d814
Compare
| ext_traits::ByteSliceExt, | ||
| request::RequestContent, | ||
| types::{AmountConvertor, StringMajorUnitForConnector}, | ||
| // transformers::ForeignTryFrom, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change required ?
| // transformers::ForeignTryFrom, | ||
| types::{AmountConvertor, MinorUnit, StringMajorUnitForConnector}, | ||
| }; | ||
| // use std::convert::TryFrom; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change required ?
| use api_models::{ | ||
| payments as payment_types, | ||
| payments::{ | ||
| AmazonPayDeliveryOptions, AmazonPayDeliveryPrice, AmazonPayPaymentIntent, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of directly importing the type, consider using payment_types::AmazonPayDeliveryOptions wherever required.
Same for other types
| ) -> Result<AmazonPayDeliveryOptions, errors::ApiErrorResponse> { | ||
| let id = value | ||
| .get("id") | ||
| .and_then(|v| v.as_str()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider renaming the variable 'v' to something more descriptive, like 'id_value'
| .ok_or(errors::ApiErrorResponse::MissingRequiredField { field_name: "id" })? | ||
| .to_string(); | ||
|
|
||
| let price = value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that every time you are trying to get a value form metadata which is of type serde_json::Value. I think we can have a AmazonPayDeliveryOption struct that mandates all these fields like id, price etc and the parse metadata to that type.
| nutype = { version = "0.4.3", features = ["serde"] } | ||
| once_cell = "1.21.3" | ||
| openssl = {version = "0.10.72", optional = true} | ||
| pem = "2.0.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is required for writing unit test for signature generation process in Amazon Pay requests.
Type of Change
Description
Added Amazon Pay connector using Amazon Pay Wallet for the US region.
Flow:
In the Hyperswitch dashboard, when configuring the Amazon Pay connector, merchants are required to provide their API keys, Merchant ID, and Store ID. The Merchant ID and Store ID are specifically necessary for rendering the Amazon Pay SDK.
During the
payments-createAPI call, merchants must include the amount and currency (currently, only USD is supported). Additionally, the following fields can be provided:order_tax_amount: Required only if tax is being applied to the bill.shipping_cost: This value must match the amount specified in the default delivery_option. This validation is enforced in the backend.delivery_options: Passed within the metadata field, this should be an array of objects. Each object must include:idprice: An object containingamountandcurrencyshipping_method: An object containingshipping_method_nameandshipping_method_codeis_default: A boolean indicating whether the option is the default. Exactly one delivery option must haveis_defaultset to true, which will determine the applicable shipping cost.In the
session_tokensAPI call, all required information for rendering the Amazon Pay SDK on the frontend is provided.The Amazon Pay SDK is rendered on the frontend. Upon successful checkout, the frontend returns a
checkout_session_idvia theconfirmAPI call. This identifier is essential for making the Authorize call. Currently, only the automatic capture method is supported, meaning the capture process is performed within the same call.Note:
This PR currently supports only the
Automaticcapture method. Therefore, invoking the authorization flow will move the payment to a terminal state.Additional Changes
Motivation and Context
https://developer.amazon.com/docs/amazon-pay-api-v2/introduction.html
How did you test it?
Postman Test
Connector configuration with merchant account
Request:
Response:
Create Payment
Request:
Response:
Session Token
Request:
Response:
If any field within the
metadatais missing, thesession_tokenin the response will be empty.Payments - Create (with missing field in metadata)
Request:
Response:
Session Token (when metadata is passed with missing field in payments-create call)
Request:
Response:
Payments - Confirm
Request:
Response:
Refunds - Create
Request:
Response:
Refunds - Retrieve
Request:
Response:
Feature API
Request:
Response:
Checklist
cargo +nightly fmt --allcargo clippy