diff --git a/Cargo.lock b/Cargo.lock index 9dbbad0532..282ea2fc8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1895,6 +1895,7 @@ dependencies = [ "nutype", "once_cell", "openssl", + "pem 2.0.1", "phonenumber", "proptest", "quick-xml", @@ -2247,6 +2248,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84494160481771c27fca091fb9b41c0dfa588e3a3b8156e797b3703b778459d" + [[package]] name = "crypto-bigint" version = "0.4.9" @@ -4037,6 +4044,7 @@ dependencies = [ "common_types", "common_utils", "crc", + "crypto", "encoding_rs", "error-stack 0.4.1", "hex", @@ -4054,7 +4062,7 @@ dependencies = [ "nanoid", "num-traits", "openssl", - "pem", + "pem 3.0.5", "qrcode", "quick-xml", "rand 0.8.5", @@ -4070,6 +4078,7 @@ dependencies = [ "serde_urlencoded", "serde_with", "sha1", + "sha2", "strum 0.26.3", "time", "unicode-normalization", @@ -4578,7 +4587,7 @@ checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ "base64 0.22.1", "js-sys", - "pem", + "pem 3.0.5", "ring 0.17.14", "serde", "serde_json", @@ -5732,6 +5741,16 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "pem" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b13fe415cdf3c8e44518e18a7c95a13431d9bdf6d15367d82b23c377fdd441a" +dependencies = [ + "base64 0.21.7", + "serde", +] + [[package]] name = "pem" version = "3.0.5" diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index 2182ecbef4..cc7e27495d 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -7464,9 +7464,162 @@ "AliPayRedirection": { "type": "object" }, + "AmazonPayDeliveryOptions": { + "type": "object", + "required": [ + "id", + "price", + "shipping_method", + "is_default" + ], + "properties": { + "id": { + "type": "string", + "description": "Delivery Option identifier" + }, + "price": { + "$ref": "#/components/schemas/AmazonPayDeliveryPrice" + }, + "shipping_method": { + "$ref": "#/components/schemas/AmazonPayShippingMethod" + }, + "is_default": { + "type": "boolean", + "description": "Specifies if this delivery option is the default" + } + } + }, + "AmazonPayDeliveryPrice": { + "type": "object", + "required": [ + "amount", + "currency_code" + ], + "properties": { + "amount": { + "$ref": "#/components/schemas/MinorUnit" + }, + "currency_code": { + "$ref": "#/components/schemas/Currency" + } + } + }, + "AmazonPayMerchantCredentials": { + "type": "object", + "required": [ + "merchant_id", + "store_id" + ], + "properties": { + "merchant_id": { + "type": "string", + "description": "Amazon Pay merchant account identifier" + }, + "store_id": { + "type": "string", + "description": "Amazon Pay store ID" + } + } + }, + "AmazonPayPaymentIntent": { + "type": "string", + "enum": [ + "Confirm", + "Authorize", + "AuthorizeWithCapture" + ] + }, "AmazonPayRedirectData": { "type": "object" }, + "AmazonPaySessionTokenData": { + "type": "object", + "required": [ + "amazon_pay" + ], + "properties": { + "amazon_pay": { + "$ref": "#/components/schemas/AmazonPayMerchantCredentials" + } + } + }, + "AmazonPaySessionTokenResponse": { + "type": "object", + "required": [ + "merchant_id", + "ledger_currency", + "store_id", + "payment_intent", + "total_shipping_amount", + "total_tax_amount", + "total_base_amount", + "delivery_options" + ], + "properties": { + "merchant_id": { + "type": "string", + "description": "Amazon Pay merchant account identifier" + }, + "ledger_currency": { + "$ref": "#/components/schemas/Currency" + }, + "store_id": { + "type": "string", + "description": "Amazon Pay store ID" + }, + "payment_intent": { + "$ref": "#/components/schemas/AmazonPayPaymentIntent" + }, + "total_shipping_amount": { + "type": "string", + "description": "The total shipping costs" + }, + "total_tax_amount": { + "type": "string", + "description": "The total tax amount for the order" + }, + "total_base_amount": { + "type": "string", + "description": "The total amount for items in the cart" + }, + "delivery_options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AmazonPayDeliveryOptions" + }, + "description": "The delivery options available for the provided address" + } + } + }, + "AmazonPayShippingMethod": { + "type": "object", + "required": [ + "shipping_method_name", + "shipping_method_code" + ], + "properties": { + "shipping_method_name": { + "type": "string", + "description": "Name of the shipping method" + }, + "shipping_method_code": { + "type": "string", + "description": "Code of the shipping method" + } + } + }, + "AmazonPayWalletData": { + "type": "object", + "required": [ + "checkout_session_id" + ], + "properties": { + "checkout_session_id": { + "type": "string", + "description": "Checkout Session identifier" + } + } + }, "AmountFilter": { "type": "object", "properties": { @@ -11833,6 +11986,7 @@ "adyen", "affirm", "airwallex", + "amazonpay", "archipel", "authorizedotnet", "bambora", @@ -12189,6 +12343,11 @@ "description": "This field is for our legacy Apple Pay flow that contains the Apple Pay certificates and credentials for only iOS Apple Pay flow", "nullable": true }, + "amazon_pay": { + "type": "object", + "description": "This field contains the Amazon Pay certificates and credentials", + "nullable": true + }, "samsung_pay": { "type": "object", "description": "This field contains the Samsung Pay certificates and credentials", @@ -29842,6 +30001,7 @@ "adyen", "affirm", "airwallex", + "amazonpay", "archipel", "authorizedotnet", "bankofamerica", @@ -30866,6 +31026,27 @@ } ] }, + { + "allOf": [ + { + "$ref": "#/components/schemas/AmazonPaySessionTokenResponse" + }, + { + "type": "object", + "required": [ + "wallet_name" + ], + "properties": { + "wallet_name": { + "type": "string", + "enum": [ + "amazon_pay" + ] + } + } + } + ] + }, { "type": "object", "required": [ @@ -32617,6 +32798,17 @@ } } }, + { + "type": "object", + "required": [ + "amazon_pay" + ], + "properties": { + "amazon_pay": { + "$ref": "#/components/schemas/AmazonPayWalletData" + } + } + }, { "type": "object", "required": [ diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index bfa65cddc9..02fd922221 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -4270,9 +4270,162 @@ "AliPayRedirection": { "type": "object" }, + "AmazonPayDeliveryOptions": { + "type": "object", + "required": [ + "id", + "price", + "shipping_method", + "is_default" + ], + "properties": { + "id": { + "type": "string", + "description": "Delivery Option identifier" + }, + "price": { + "$ref": "#/components/schemas/AmazonPayDeliveryPrice" + }, + "shipping_method": { + "$ref": "#/components/schemas/AmazonPayShippingMethod" + }, + "is_default": { + "type": "boolean", + "description": "Specifies if this delivery option is the default" + } + } + }, + "AmazonPayDeliveryPrice": { + "type": "object", + "required": [ + "amount", + "currency_code" + ], + "properties": { + "amount": { + "$ref": "#/components/schemas/MinorUnit" + }, + "currency_code": { + "$ref": "#/components/schemas/Currency" + } + } + }, + "AmazonPayMerchantCredentials": { + "type": "object", + "required": [ + "merchant_id", + "store_id" + ], + "properties": { + "merchant_id": { + "type": "string", + "description": "Amazon Pay merchant account identifier" + }, + "store_id": { + "type": "string", + "description": "Amazon Pay store ID" + } + } + }, + "AmazonPayPaymentIntent": { + "type": "string", + "enum": [ + "Confirm", + "Authorize", + "AuthorizeWithCapture" + ] + }, "AmazonPayRedirectData": { "type": "object" }, + "AmazonPaySessionTokenData": { + "type": "object", + "required": [ + "amazon_pay" + ], + "properties": { + "amazon_pay": { + "$ref": "#/components/schemas/AmazonPayMerchantCredentials" + } + } + }, + "AmazonPaySessionTokenResponse": { + "type": "object", + "required": [ + "merchant_id", + "ledger_currency", + "store_id", + "payment_intent", + "total_shipping_amount", + "total_tax_amount", + "total_base_amount", + "delivery_options" + ], + "properties": { + "merchant_id": { + "type": "string", + "description": "Amazon Pay merchant account identifier" + }, + "ledger_currency": { + "$ref": "#/components/schemas/Currency" + }, + "store_id": { + "type": "string", + "description": "Amazon Pay store ID" + }, + "payment_intent": { + "$ref": "#/components/schemas/AmazonPayPaymentIntent" + }, + "total_shipping_amount": { + "type": "string", + "description": "The total shipping costs" + }, + "total_tax_amount": { + "type": "string", + "description": "The total tax amount for the order" + }, + "total_base_amount": { + "type": "string", + "description": "The total amount for items in the cart" + }, + "delivery_options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AmazonPayDeliveryOptions" + }, + "description": "The delivery options available for the provided address" + } + } + }, + "AmazonPayShippingMethod": { + "type": "object", + "required": [ + "shipping_method_name", + "shipping_method_code" + ], + "properties": { + "shipping_method_name": { + "type": "string", + "description": "Name of the shipping method" + }, + "shipping_method_code": { + "type": "string", + "description": "Code of the shipping method" + } + } + }, + "AmazonPayWalletData": { + "type": "object", + "required": [ + "checkout_session_id" + ], + "properties": { + "checkout_session_id": { + "type": "string", + "description": "Checkout Session identifier" + } + } + }, "AmountDetails": { "type": "object", "required": [ @@ -8377,6 +8530,7 @@ "adyen", "affirm", "airwallex", + "amazonpay", "archipel", "authorizedotnet", "bambora", @@ -8752,6 +8906,11 @@ "description": "This field is for our legacy Apple Pay flow that contains the Apple Pay certificates and credentials for only iOS Apple Pay flow", "nullable": true }, + "amazon_pay": { + "type": "object", + "description": "This field contains the Amazon Pay certificates and credentials", + "nullable": true + }, "samsung_pay": { "type": "object", "description": "This field contains the Samsung Pay certificates and credentials", @@ -23500,6 +23659,7 @@ "adyen", "affirm", "airwallex", + "amazonpay", "archipel", "authorizedotnet", "bankofamerica", @@ -24462,6 +24622,27 @@ } ] }, + { + "allOf": [ + { + "$ref": "#/components/schemas/AmazonPaySessionTokenResponse" + }, + { + "type": "object", + "required": [ + "wallet_name" + ], + "properties": { + "wallet_name": { + "type": "string", + "enum": [ + "amazon_pay" + ] + } + } + } + ] + }, { "type": "object", "required": [ @@ -26159,6 +26340,17 @@ } } }, + { + "type": "object", + "required": [ + "amazon_pay" + ], + "properties": { + "amazon_pay": { + "$ref": "#/components/schemas/AmazonPayWalletData" + } + } + }, { "type": "object", "required": [ diff --git a/config/config.example.toml b/config/config.example.toml index 5fe3dbd05f..e6bbef7fdd 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -187,7 +187,7 @@ adyen.dispute_base_url = "https://ca-test.adyen.com/" adyenplatform.base_url = "https://balanceplatform-api-test.adyen.com/" affirm.base_url = "https://sandbox.affirm.com/api" airwallex.base_url = "https://api-demo.airwallex.com/" -amazonpay.base_url = "https://pay-api.amazon.com/v2" +amazonpay.base_url = "https://pay-api.amazon.com/sandbox/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2" @@ -842,6 +842,9 @@ eps = { country = "AT", currency = "EUR" } ideal = { country = "NL", currency = "EUR" } przelewy24 = { country = "PL", currency = "PLN,EUR" } +[pm_filters.amazonpay] +amazon_pay = { country = "US", currency = "USD" } + [pm_filters.rapyd] apple_pay = { country = "BR, CA, CL, CO, DO, SV, MX, PE, PT, US, AT, BE, BG, HR, CY, CZ, DO, DK, EE, FI, FR, GE, DE, GR, GL, HU, IS, IE, IL, IT, LV, LI, LT, LU, MT, MD, MC, ME, NL, NO, PL, RO, SM, SK, SI, ZA, ES, SE, CH, GB, VA, AU, HK, JP, MY, NZ, SG, KR, TW, VN", currency = "AMD, AUD, BGN, BRL, BYN, CAD, CHF, CLP, CNY, COP, CRC, CZK, DKK, DOP, EUR, GBP, GEL, GTQ, HUF, ISK, JPY, KRW, MDL, MXN, MYR, NOK, PAB, PEN, PLN, PYG, RON, RSD, SEK, SGD, TWD, UAH, USD, UYU, VND, ZAR" } google_pay = { country = "BR, CA, CL, CO, DO, MX, PE, PT, US, AT, BE, BG, HR, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IL, IT, LV, LT, LU, NZ, NO, GB, PL, RO, RU, SK, ZA, ES, SE, CH, TR, AU, HK, IN, ID, JP, MY, PH, SG, TW, TH, VN", currency = "AUD, BGN, BRL, BYN, CAD, CHF, CLP, COP, CZK, DKK, DOP, EUR, GBP, HUF, IDR, JPY, KES, MXN, MYR, NOK, PAB, PEN, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, TWD, UAH, USD, UYU, VND, ZAR" } diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 21ac732394..7875c078a4 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -26,7 +26,7 @@ adyen.dispute_base_url = "https://ca-test.adyen.com/" adyenplatform.base_url = "https://balanceplatform-api-test.adyen.com/" affirm.base_url = "https://sandbox.affirm.com/api" airwallex.base_url = "https://api-demo.airwallex.com/" -amazonpay.base_url = "https://pay-api.amazon.com/v2" +amazonpay.base_url = "https://pay-api.amazon.com/sandbox/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2/" @@ -694,6 +694,9 @@ supported_connectors = "adyen" [debit_routing_config.connector_supported_debit_networks] adyen = "Star,Pulse,Accel,Nyce" +[pm_filters.amazonpay] +amazon_pay = { country = "US", currency = "USD" } + [pm_filters.rapyd] apple_pay = { country = "BR, CA, CL, CO, DO, SV, MX, PE, PT, US, AT, BE, BG, HR, CY, CZ, DO, DK, EE, FI, FR, GE, DE, GR, GL, HU, IS, IE, IL, IT, LV, LI, LT, LU, MT, MD, MC, ME, NL, NO, PL, RO, SM, SK, SI, ZA, ES, SE, CH, GB, VA, AU, HK, JP, MY, NZ, SG, KR, TW, VN", currency = "AMD, AUD, BGN, BRL, BYN, CAD, CHF, CLP, CNY, COP, CRC, CZK, DKK, DOP, EUR, GBP, GEL, GTQ, HUF, ISK, JPY, KRW, MDL, MXN, MYR, NOK, PAB, PEN, PLN, PYG, RON, RSD, SEK, SGD, TWD, UAH, USD, UYU, VND, ZAR" } google_pay = { country = "BR, CA, CL, CO, DO, MX, PE, PT, US, AT, BE, BG, HR, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IL, IT, LV, LT, LU, NZ, NO, GB, PL, RO, RU, SK, ZA, ES, SE, CH, TR, AU, HK, IN, ID, JP, MY, PH, SG, TW, TH, VN", currency = "AUD, BGN, BRL, BYN, CAD, CHF, CLP, COP, CZK, DKK, DOP, EUR, GBP, HUF, IDR, JPY, KES, MXN, MYR, NOK, PAB, PEN, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, TWD, UAH, USD, UYU, VND, ZAR" } diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 853ec279f6..f11642602d 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -30,7 +30,7 @@ adyen.dispute_base_url = "https://{{merchant_endpoint_prefix}}-ca-live.adyen.com adyenplatform.base_url = "https://balanceplatform-api-live.adyen.com/" affirm.base_url = "https://api.affirm.com/api" airwallex.base_url = "https://api.airwallex.com/" -amazonpay.base_url = "https://pay-api.amazon.com/v2" +amazonpay.base_url = "https://pay-api.amazon.com/live/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" authipay.base_url = "https://prod.emea.api.fiservapps.com/ipp/payments-gateway/v2/" @@ -645,6 +645,9 @@ debit = { currency = "AUD,BGN,CAD,CHF,COP,CZK,DKK,EUR,GBP,HRK,HUF,ILS,INR,JPY,MY [pm_filters.plaid] open_banking_pis = {currency = "EUR,GBP"} +[pm_filters.amazonpay] +amazon_pay = { country = "US", currency = "USD" } + [pm_filters.worldpay] debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index dbd5e87e52..b3e3e9f6a8 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -30,7 +30,7 @@ adyen.dispute_base_url = "https://ca-test.adyen.com/" adyenplatform.base_url = "https://balanceplatform-api-test.adyen.com/" affirm.base_url = "https://sandbox.affirm.com/api" airwallex.base_url = "https://api-demo.airwallex.com/" -amazonpay.base_url = "https://pay-api.amazon.com/v2" +amazonpay.base_url = "https://pay-api.amazon.com/sandbox/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2" @@ -710,6 +710,9 @@ eps = { country = "AT", currency = "EUR" } ideal = { country = "NL", currency = "EUR" } przelewy24 = { country = "PL", currency = "PLN,EUR" } +[pm_filters.amazonpay] +amazon_pay = { country = "US", currency = "USD" } + [pm_filters.bluesnap] credit = { country = "AD,AE,AG,AL,AM,AO,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BJ,BN,BO,BR,BS,BT,BW,BY,BZ,CA,CD,CF,CG,CH,CI,CL,CM,CN,CO,CR,CV,CY,CZ,DE,DK,DJ,DM,DO,DZ,EC,EE,EG,ER,ES,ET,FI,FJ,FM,FR,GA,GB,GD,GE,GG,GH,GM,GN,GQ,GR,GT,GW,GY,HN,HR,HT,HU,ID,IE,IL,IN,IS,IT,JM,JP,JO,KE,KG,KH,KI,KM,KN,KR,KW,KZ,LA,LB,LC,LI,LK,LR,LS,LT,LU,LV,MA,MC,MD,ME,MG,MH,MK,ML,MM,MN,MR,MT,MU,MV,MW,MX,MY,MZ,NA,NE,NG,NI,NL,NO,NP,NR,NZ,OM,PA,PE,PG,PH,PK,PL,PS,PT,PW,PY,QA,RO,RS,RW,SA,SB,SC,SE,SG,SI,SK,SL,SM,SN,SO,SR,SS,ST,SV,SZ,TD,TG,TH,TJ,TL,TM,TN,TO,TR,TT,TV,TZ,UA,UG,US,UY,UZ,VA,VC,VE,VN,VU,WS,ZA,ZM,ZW", currency = "AED,AFN,ALL,AMD,ANG,ARS,AUD,AWG,BAM,BBD,BGN,BHD,BMD,BND,BOB,BRL,BSD,BWP,CAD,CHF,CLP,CNY,COP,CRC,CZK,DKK,DOP,DZD,EGP,EUR,FJD,GBP,GEL,GIP,GTQ,HKD,HUF,IDR,ILS,INR,ISK,JMD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,MAD,MDL,MKD,MUR,MWK,MXN,MYR,NAD,NGN,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PLN,PKR,QAR,RON,RSD,RUB,SAR,SCR,SDG,SEK,SGD,THB,TND,TRY,TTD,TWD,TZS,UAH,USD,UYU,UZS,VND,XAF,XCD,XOF,ZAR"} google_pay = { country = "AL, DZ, AS, AO, AG, AR, AU, AT, AZ, BH, BY, BE, BR, BG, CL, CO, HR, CZ, DK, DO, EG, EE, FI, FR, DE, GR, HK, HU, IN, ID, IE, IL, IT, JP, JO, KZ, KE, KW, LV, LB, LT, LU, MY, MX, NL, NZ, NO, OM, PK, PA, PE, PH, PL, PT, QA, RO, RU, SA, SG, SK, ZA, ES, LK, SE, CH, TW, TH, TR, UA, AE, GB, US, UY, VN", currency = "ALL, DZD, USD, XCD, ARS, AUD, EUR, BHD, BRL, BGN, CAD, CLP, COP, CZK, DKK, DOP, EGP, HKD, HUF, INR, IDR, ILS, JPY, KZT, KES, KWD, LBP, MYR, MXN, NZD, NOK, OMR, PKR, PAB, PEN, PHP, PLN, QAR, RON, RUB, SAR, SGD, ZAR, LKR, SEK, CHF, TWD, THB, TRY, UAH, AED, GBP, UYU, VND"} diff --git a/config/development.toml b/config/development.toml index 1215c32362..7db6e74568 100644 --- a/config/development.toml +++ b/config/development.toml @@ -90,14 +90,13 @@ vault_private_key = "" tunnel_private_key = "" [connectors.supported] -wallets = ["klarna", "mifinity", "braintree", "applepay", "adyen"] +wallets = ["klarna", "mifinity", "braintree", "applepay", "adyen", "amazonpay"] rewards = ["cashtocode", "zen"] cards = [ "aci", "adyen", "adyenplatform", "airwallex", - "amazonpay", "archipel", "authipay", "authorizedotnet", @@ -226,7 +225,7 @@ affirm.base_url = "https://sandbox.affirm.com/api" adyen.payout_base_url = "https://pal-test.adyen.com/" adyen.dispute_base_url = "https://ca-test.adyen.com/" airwallex.base_url = "https://api-demo.airwallex.com/" -amazonpay.base_url = "https://pay-api.amazon.com/v2" +amazonpay.base_url = "https://pay-api.amazon.com/sandbox/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2/" @@ -895,6 +894,10 @@ paypal = { currency = "AUD,EUR,BRL,CAD,CNY,EUR,EUR,EUR,GBP,HKD,INR,EUR,JPY,MYR,E google_pay = { country = "AU,AT,BE,BR,CA,CN,HK,MY,NZ,SG,US", currency = "AUD,EUR,EUR,BRL,CAD,CNY,HKD,MYR,NZD,SGD,USD" } apple_pay = { country = "AU,NZ,CN,HK,JP,SG,MY,KR,TW,VN,GB,IE,FR,DE,IT,ES,PT,NL,BE,LU,AT,CH,SE,FI,DK,NO,PL,CZ,SK,HU,LT,LV,EE,GR,RO,BG,HR,SI,MT,CY,IS,LI,MC,SM,VA,US,CA,MX,BR,AR,CL,CO,PE,UY,CR,PA,DO,EC,SV,GT,HN,BS,PR,AE,SA,QA,KW,BH,OM,IL,JO,PS,EG,MA,ZA,GE,AM,AZ,MD,ME,MK,AL,BA,RS,UA", currency = "AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,HUF,ILS,JPY,MXN,NOK,NZD,PHP,PLN,SEK,SGD,THB,TWD,USD" } + +[pm_filters.amazonpay] +amazon_pay = { country = "US", currency = "USD" } + [pm_filters.rapyd] apple_pay = { country = "BR, CA, CL, CO, DO, SV, MX, PE, PT, US, AT, BE, BG, HR, CY, CZ, DO, DK, EE, FI, FR, GE, DE, GR, GL, HU, IS, IE, IL, IT, LV, LI, LT, LU, MT, MD, MC, ME, NL, NO, PL, RO, SM, SK, SI, ZA, ES, SE, CH, GB, VA, AU, HK, JP, MY, NZ, SG, KR, TW, VN", currency = "AMD, AUD, BGN, BRL, BYN, CAD, CHF, CLP, CNY, COP, CRC, CZK, DKK, DOP, EUR, GBP, GEL, GTQ, HUF, ISK, JPY, KRW, MDL, MXN, MYR, NOK, PAB, PEN, PLN, PYG, RON, RSD, SEK, SGD, TWD, UAH, USD, UYU, VND, ZAR" } google_pay = { country = "BR, CA, CL, CO, DO, MX, PE, PT, US, AT, BE, BG, HR, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IL, IT, LV, LT, LU, NZ, NO, GB, PL, RO, RU, SK, ZA, ES, SE, CH, TR, AU, HK, IN, ID, JP, MY, PH, SG, TW, TH, VN", currency = "AUD, BGN, BRL, BYN, CAD, CHF, CLP, COP, CZK, DKK, DOP, EUR, GBP, HUF, IDR, JPY, KES, MXN, MYR, NOK, PAB, PEN, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, TWD, UAH, USD, UYU, VND, ZAR" } diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 7530523427..fd5a6a9a0c 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -113,7 +113,7 @@ affirm.base_url = "https://sandbox.affirm.com/api" adyen.payout_base_url = "https://pal-test.adyen.com/" adyen.dispute_base_url = "https://ca-test.adyen.com/" airwallex.base_url = "https://api-demo.airwallex.com/" -amazonpay.base_url = "https://pay-api.amazon.com/v2" +amazonpay.base_url = "https://pay-api.amazon.com/sandbox/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2/" @@ -257,14 +257,13 @@ zsl.base_url = "https://api.sitoffalb.net/" apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } [connectors.supported] -wallets = ["klarna", "mifinity", "braintree", "applepay"] +wallets = ["klarna", "mifinity", "braintree", "applepay", "amazonpay"] rewards = ["cashtocode", "zen"] cards = [ "aci", "adyen", "adyenplatform", "airwallex", - "amazonpay", "archipel", "authipay", "authorizedotnet", @@ -829,6 +828,9 @@ eps = { country = "AT", currency = "EUR" } ideal = { country = "NL", currency = "EUR" } przelewy24 = { country = "PL", currency = "PLN,EUR" } +[pm_filters.amazonpay] +amazon_pay = { country = "US", currency = "USD" } + [pm_filters.rapyd] apple_pay = { country = "BR, CA, CL, CO, DO, SV, MX, PE, PT, US, AT, BE, BG, HR, CY, CZ, DO, DK, EE, FI, FR, GE, DE, GR, GL, HU, IS, IE, IL, IT, LV, LI, LT, LU, MT, MD, MC, ME, NL, NO, PL, RO, SM, SK, SI, ZA, ES, SE, CH, GB, VA, AU, HK, JP, MY, NZ, SG, KR, TW, VN", currency = "AMD, AUD, BGN, BRL, BYN, CAD, CHF, CLP, CNY, COP, CRC, CZK, DKK, DOP, EUR, GBP, GEL, GTQ, HUF, ISK, JPY, KRW, MDL, MXN, MYR, NOK, PAB, PEN, PLN, PYG, RON, RSD, SEK, SGD, TWD, UAH, USD, UYU, VND, ZAR" } google_pay = { country = "BR, CA, CL, CO, DO, MX, PE, PT, US, AT, BE, BG, HR, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IL, IT, LV, LT, LU, NZ, NO, GB, PL, RO, RU, SK, ZA, ES, SE, CH, TR, AU, HK, IN, ID, JP, MY, PH, SG, TW, TH, VN", currency = "AUD, BGN, BRL, BYN, CAD, CHF, CLP, COP, CZK, DKK, DOP, EUR, GBP, HUF, IDR, JPY, KES, MXN, MYR, NOK, PAB, PEN, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, TWD, UAH, USD, UYU, VND, ZAR" } diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 2e70e1a6c9..5425e7c7b4 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -1716,6 +1716,10 @@ pub struct ConnectorWalletDetails { #[serde(skip_serializing_if = "Option::is_none")] #[schema(value_type = Option)] pub apple_pay: Option, + /// This field contains the Amazon Pay certificates and credentials + #[serde(skip_serializing_if = "Option::is_none")] + #[schema(value_type = Option)] + pub amazon_pay: Option, /// This field contains the Samsung Pay certificates and credentials #[serde(skip_serializing_if = "Option::is_none")] #[schema(value_type = Option)] diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 7ab18f3274..eabca23ed0 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -24,7 +24,7 @@ use common_utils::{ id_type, new_type::MaskedBankAccount, pii::{self, Email}, - types::{MinorUnit, StringMajorUnit}, + types::{AmountConvertor, MinorUnit, StringMajorUnit}, }; use error_stack::ResultExt; @@ -2856,7 +2856,9 @@ impl GetPaymentMethodType for WalletData { Self::BluecodeRedirect {} => api_enums::PaymentMethodType::Bluecode, Self::AliPayQr(_) | Self::AliPayRedirect(_) => api_enums::PaymentMethodType::AliPay, Self::AliPayHkRedirect(_) => api_enums::PaymentMethodType::AliPayHk, - Self::AmazonPayRedirect(_) => api_enums::PaymentMethodType::AmazonPay, + Self::AmazonPay(_) | Self::AmazonPayRedirect(_) => { + api_enums::PaymentMethodType::AmazonPay + } Self::Skrill(_) => api_enums::PaymentMethodType::Skrill, Self::Paysera(_) => api_enums::PaymentMethodType::Paysera, Self::MomoRedirect(_) => api_enums::PaymentMethodType::Momo, @@ -3816,6 +3818,8 @@ pub enum WalletData { AliPayRedirect(AliPayRedirection), /// The wallet data for Ali Pay HK redirect AliPayHkRedirect(AliPayHkRedirection), + /// The wallet data for Amazon Pay + AmazonPay(AmazonPayWalletData), /// The wallet data for Amazon Pay redirect AmazonPayRedirect(AmazonPayRedirectData), /// The wallet data for Bluecode QR Code Redirect @@ -3909,6 +3913,7 @@ impl GetAddressFromPaymentMethodData for WalletData { | Self::KakaoPayRedirect(_) | Self::GoPayRedirect(_) | Self::GcashRedirect(_) + | Self::AmazonPay(_) | Self::AmazonPayRedirect(_) | Self::Skrill(_) | Self::Paysera(_) @@ -4069,6 +4074,20 @@ pub struct GooglePayWalletData { pub tokenization_data: common_types::payments::GpayTokenizationData, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct AmazonPaySessionTokenData { + #[serde(rename = "amazon_pay")] + pub data: AmazonPayMerchantCredentials, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct AmazonPayMerchantCredentials { + /// Amazon Pay merchant account identifier + pub merchant_id: String, + /// Amazon Pay store ID + pub store_id: String, +} + #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct ApplePayRedirectData {} @@ -4185,6 +4204,12 @@ pub struct MifinityData { pub language_preference: Option, } +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct AmazonPayWalletData { + /// Checkout Session identifier + pub checkout_session_id: String, +} + #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct ApplePayWalletData { /// The payment data of Apple pay @@ -7445,6 +7470,8 @@ pub enum SessionToken { Paze(Box), /// The sessions response structure for ClickToPay ClickToPay(Box), + /// The session response structure for Amazon Pay + AmazonPay(Box), /// Whenever there is no session token response or an error in session response NoSessionTokenReceived, } @@ -7848,6 +7875,135 @@ pub struct ApplepayErrorResponse { pub status_message: String, } +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +pub struct AmazonPaySessionTokenResponse { + /// Amazon Pay merchant account identifier + pub merchant_id: String, + /// Ledger currency provided during registration for the given merchant identifier + #[schema(example = "USD", value_type = Currency)] + pub ledger_currency: common_enums::Currency, + /// Amazon Pay store ID + pub store_id: String, + /// Payment flow for charging the buyer + pub payment_intent: AmazonPayPaymentIntent, + /// The total shipping costs + #[schema(value_type = String)] + pub total_shipping_amount: StringMajorUnit, + /// The total tax amount for the order + #[schema(value_type = String)] + pub total_tax_amount: StringMajorUnit, + /// The total amount for items in the cart + #[schema(value_type = String)] + pub total_base_amount: StringMajorUnit, + /// The delivery options available for the provided address + pub delivery_options: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +pub enum AmazonPayPaymentIntent { + /// Create a Charge Permission to authorize and capture funds at a later time + Confirm, + /// Authorize funds immediately and capture at a later time + Authorize, + /// Authorize and capture funds immediately + AuthorizeWithCapture, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct AmazonPayDeliveryOptions { + /// Delivery Option identifier + pub id: String, + /// Total delivery cost + pub price: AmazonPayDeliveryPrice, + /// Shipping method details + pub shipping_method: AmazonPayShippingMethod, + /// Specifies if this delivery option is the default + pub is_default: bool, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct AmazonPayDeliveryPrice { + /// Transaction amount in MinorUnit + pub amount: MinorUnit, + #[serde(skip_deserializing)] + /// Transaction amount in StringMajorUnit + #[schema(value_type = String)] + pub display_amount: StringMajorUnit, + /// Transaction currency code in ISO 4217 format + #[schema(example = "USD", value_type = Currency)] + pub currency_code: common_enums::Currency, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct AmazonPayShippingMethod { + /// Name of the shipping method + pub shipping_method_name: String, + /// Code of the shipping method + pub shipping_method_code: String, +} + +impl AmazonPayDeliveryOptions { + pub fn parse_delivery_options_request( + delivery_options_request: &[serde_json::Value], + ) -> Result, common_utils::errors::ParsingError> { + delivery_options_request + .iter() + .map(|option| { + serde_json::from_value(option.clone()).map_err(|_| { + common_utils::errors::ParsingError::StructParseFailure( + "AmazonPayDeliveryOptions", + ) + }) + }) + .collect() + } + + pub fn get_default_delivery_amount( + delivery_options: Vec, + ) -> Result> { + let mut default_options = delivery_options + .into_iter() + .filter(|delivery_option| delivery_option.is_default); + + match (default_options.next(), default_options.next()) { + (Some(default_option), None) => Ok(default_option.price.amount), + _ => Err(ValidationError::InvalidValue { + message: "Amazon Pay Delivery Option".to_string(), + }) + .attach_printable("Expected exactly one default Amazon Pay Delivery Option"), + } + } + + pub fn validate_currency( + currency_code: common_enums::Currency, + amazonpay_supported_currencies: HashSet, + ) -> Result<(), ValidationError> { + if !amazonpay_supported_currencies.contains(¤cy_code) { + return Err(ValidationError::InvalidValue { + message: format!("{currency_code:?} is not a supported currency."), + }); + } + + Ok(()) + } + + pub fn insert_display_amount( + delivery_options: &mut Vec, + currency_code: common_enums::Currency, + ) -> Result<(), error_stack::Report> { + let required_amount_type = common_utils::types::StringMajorUnitForCore; + for option in delivery_options { + let display_amount = required_amount_type + .convert(option.price.amount, currency_code) + .change_context(common_utils::errors::ParsingError::I64ToStringConversionFailure)?; + + option.price.display_amount = display_amount; + } + + Ok(()) + } +} + #[cfg(feature = "v1")] #[derive(Default, Debug, serde::Serialize, Clone, ToSchema)] pub struct PaymentsSessionResponse { diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index c34d0cdf31..8b1c9e2f9e 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -63,7 +63,7 @@ pub enum RoutableConnectors { Adyen, Affirm, Airwallex, - // Amazonpay, + Amazonpay, Archipel, Authorizedotnet, Bankofamerica, @@ -232,7 +232,7 @@ pub enum Connector { Adyen, Affirm, Airwallex, - // Amazonpay, + Amazonpay, Archipel, Authorizedotnet, Bambora, @@ -432,7 +432,7 @@ impl Connector { | Self::Affirm | Self::Adyenplatform | Self::Airwallex - // | Self::Amazonpay + | Self::Amazonpay | Self::Authorizedotnet | Self::Bambora | Self::Bamboraapac @@ -606,6 +606,7 @@ impl From for Connector { RoutableConnectors::Adyen => Self::Adyen, RoutableConnectors::Affirm => Self::Affirm, RoutableConnectors::Airwallex => Self::Airwallex, + RoutableConnectors::Amazonpay => Self::Amazonpay, RoutableConnectors::Archipel => Self::Archipel, RoutableConnectors::Authorizedotnet => Self::Authorizedotnet, RoutableConnectors::Bankofamerica => Self::Bankofamerica, @@ -738,6 +739,7 @@ impl TryFrom for RoutableConnectors { Connector::Adyen => Ok(Self::Adyen), Connector::Affirm => Ok(Self::Affirm), Connector::Airwallex => Ok(Self::Airwallex), + Connector::Amazonpay => Ok(Self::Amazonpay), Connector::Archipel => Ok(Self::Archipel), Connector::Authorizedotnet => Ok(Self::Authorizedotnet), Connector::Bankofamerica => Ok(Self::Bankofamerica), diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index 8d81b5b367..75fc14b01f 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -40,6 +40,7 @@ nanoid = "0.4.0" nutype = { version = "0.4.3", features = ["serde"] } once_cell = "1.21.3" openssl = {version = "0.10.72", optional = true} +pem = "2.0.0" phonenumber = "0.3.7" quick-xml = { version = "0.31.0", features = ["serialize"] } rand = "0.8.5" diff --git a/crates/common_utils/src/crypto.rs b/crates/common_utils/src/crypto.rs index 9c1af493e2..2672fcd1c7 100644 --- a/crates/common_utils/src/crypto.rs +++ b/crates/common_utils/src/crypto.rs @@ -4,9 +4,11 @@ use std::ops::Deref; use error_stack::ResultExt; use masking::{ExposeInterface, Secret}; use md5; +use pem; use ring::{ aead::{self, BoundKey, OpeningKey, SealingKey, UnboundKey}, - hmac, + hmac, rand as ring_rand, + signature::{RsaKeyPair, RSA_PSS_SHA256}, }; #[cfg(feature = "logs")] use router_env::logger; @@ -680,6 +682,42 @@ pub type EncryptableName = Encryptable>; /// Type alias for `Encryptable>` used for `email` field pub type EncryptableEmail = Encryptable>; +/// Represents the RSA-PSS-SHA256 signing algorithm +#[derive(Debug)] +pub struct RsaPssSha256; + +impl SignMessage for RsaPssSha256 { + fn sign_message( + &self, + private_key_pem_bytes: &[u8], + msg_to_sign: &[u8], + ) -> CustomResult, errors::CryptoError> { + let parsed_pem = pem::parse(private_key_pem_bytes) + .change_context(errors::CryptoError::EncodingFailed) + .attach_printable("Failed to parse PEM string")?; + let key_pair = match parsed_pem.tag() { + "PRIVATE KEY" => RsaKeyPair::from_pkcs8(parsed_pem.contents()) + .change_context(errors::CryptoError::InvalidKeyLength) + .attach_printable("Failed to parse PKCS#8 DER with ring"), + "RSA PRIVATE KEY" => RsaKeyPair::from_der(parsed_pem.contents()) + .change_context(errors::CryptoError::InvalidKeyLength) + .attach_printable("Failed to parse PKCS#1 DER (using from_der) with ring"), + tag => Err(errors::CryptoError::InvalidKeyLength).attach_printable(format!( + "Unexpected PEM tag: {tag}. Expected 'PRIVATE KEY' or 'RSA PRIVATE KEY'", + )), + }?; + let rng = ring_rand::SystemRandom::new(); + let signature_len = key_pair.public().modulus_len(); + let mut signature_bytes = vec![0; signature_len]; + key_pair + .sign(&RSA_PSS_SHA256, &rng, msg_to_sign, &mut signature_bytes) + .change_context(errors::CryptoError::EncodingFailed) + .attach_printable("Failed to sign data with ring")?; + + Ok(signature_bytes) + } +} + #[cfg(test)] mod crypto_tests { #![allow(clippy::expect_used)] @@ -876,4 +914,41 @@ mod crypto_tests { assert!(!wrong_verified); } + + use ring::signature::{UnparsedPublicKey, RSA_PSS_2048_8192_SHA256}; + + #[test] + fn test_rsa_pss_sha256_verify_signature() { + let signer = crate::crypto::RsaPssSha256; + let message = b"abcdefghijklmnopqrstuvwxyz"; + + let private_key_pem_bytes = + std::fs::read("../../private_key.pem").expect("Failed to read private key"); + let parsed_pem = pem::parse(&private_key_pem_bytes).expect("Failed to parse PEM"); + let private_key_der = parsed_pem.contents(); + + let signature = signer + .sign_message(&private_key_pem_bytes, message) + .expect("Signing failed"); + + let key_pair = crate::crypto::RsaKeyPair::from_pkcs8(private_key_der) + .expect("Failed to parse DER key"); + let public_key_der = key_pair.public().as_ref().to_vec(); + + let public_key = UnparsedPublicKey::new(&RSA_PSS_2048_8192_SHA256, &public_key_der); + assert!( + public_key.verify(message, &signature).is_ok(), + "Right signature should verify" + ); + + let mut wrong_signature = signature.clone(); + if let Some(byte) = wrong_signature.first_mut() { + *byte ^= 0xFF; + } + + assert!( + public_key.verify(message, &wrong_signature).is_err(), + "Wrong signature should not verify" + ); + } } diff --git a/crates/common_utils/src/errors.rs b/crates/common_utils/src/errors.rs index 8dd7ac43a8..8202807696 100644 --- a/crates/common_utils/src/errors.rs +++ b/crates/common_utils/src/errors.rs @@ -46,6 +46,9 @@ pub enum ParsingError { /// Failed to parse i64 value for f64 value conversion #[error("Failed to parse i64 value for f64 value conversion")] I64ToDecimalConversionFailure, + /// Failed to parse i64 value for String value conversion + #[error("Failed to parse i64 value for String value conversion")] + I64ToStringConversionFailure, /// Failed to parse String value to Decimal value conversion because `error` #[error("Failed to parse String value to Decimal value conversion because {error}")] StringToDecimalConversionFailure { error: String }, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index bc6c2d717e..e67a813668 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -157,6 +157,7 @@ pub struct ConnectorWalletDetailsConfig { pub samsung_pay: Option>, pub paze: Option>, pub google_pay: Option>, + pub amazon_pay: Option>, } #[serde_with::skip_serializing_none] @@ -198,6 +199,7 @@ pub struct ConnectorConfig { #[cfg(feature = "payouts")] pub adyenplatform_payout: Option, pub airwallex: Option, + pub amazonpay: Option, pub archipel: Option, pub authorizedotnet: Option, pub bamboraapac: Option, @@ -405,6 +407,7 @@ impl ConnectorConfig { Connector::Affirm => Ok(connector_data.affirm), Connector::Adyenplatform => Err("Use get_payout_connector_config".to_string()), Connector::Airwallex => Ok(connector_data.airwallex), + Connector::Amazonpay => Ok(connector_data.amazonpay), Connector::Archipel => Ok(connector_data.archipel), Connector::Authorizedotnet => Ok(connector_data.authorizedotnet), Connector::Bamboraapac => Ok(connector_data.bamboraapac), diff --git a/crates/connector_configs/src/transformer.rs b/crates/connector_configs/src/transformer.rs index 164a0c0cf7..dce521efa2 100644 --- a/crates/connector_configs/src/transformer.rs +++ b/crates/connector_configs/src/transformer.rs @@ -60,7 +60,8 @@ impl DashboardRequestPayload { (_, GooglePay) | (_, ApplePay) | (_, PaymentMethodType::SamsungPay) - | (_, PaymentMethodType::Paze) => { + | (_, PaymentMethodType::Paze) + | (_, PaymentMethodType::AmazonPay) => { Some(api_models::enums::PaymentExperience::InvokeSdkClient) } (_, PaymentMethodType::DirectCarrierBilling) => { diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 68ca7de442..19e2154345 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -532,6 +532,26 @@ key1="Merchant ID" [authipay.connector_webhook_details] merchant_secret="Source verification key" +[amazonpay] +[[amazonpay.wallet]] +payment_method_type = "amazon_pay" +[amazonpay.connector_auth.BodyKey] +api_key="Public Key" +key1="Private Key" + +[[amazonpay.connector_wallets_details.amazon_pay]] +name="merchant_id" +label="Merchant ID" +placeholder="Enter Merchant ID" +required=true +type="Text" +[[amazonpay.connector_wallets_details.amazon_pay]] +name="store_id" +label="Store ID" +placeholder="Enter Store ID" +required=true +type="Text" + [authorizedotnet] [[authorizedotnet.credit]] payment_method_type = "Mastercard" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 1e411ccc6b..f0ceafae5d 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -339,6 +339,26 @@ key1 = "Merchant ID" [authipay.connector_webhook_details] merchant_secret = "Source verification key" +[amazonpay] +[[amazonpay.wallet]] +payment_method_type = "amazon_pay" +[amazonpay.connector_auth.BodyKey] +api_key="Public Key" +key1="Private Key" + +[[amazonpay.connector_wallets_details.amazon_pay]] +name="merchant_id" +label="Merchant ID" +placeholder="Enter Merchant ID" +required=true +type="Text" +[[amazonpay.connector_wallets_details.amazon_pay]] +name="store_id" +label="Store ID" +placeholder="Enter Store ID" +required=true +type="Text" + [authorizedotnet] [[authorizedotnet.credit]] payment_method_type = "Mastercard" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 8fa7cbff6f..23db2c0de6 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -533,6 +533,26 @@ key1 = "Merchant ID" [authipay.connector_webhook_details] merchant_secret = "Source verification key" +[amazonpay] +[[amazonpay.wallet]] +payment_method_type = "amazon_pay" +[amazonpay.connector_auth.BodyKey] +api_key="Public Key" +key1="Private Key" + +[[amazonpay.connector_wallets_details.amazon_pay]] +name="merchant_id" +label="Merchant ID" +placeholder="Enter Merchant ID" +required=true +type="Text" +[[amazonpay.connector_wallets_details.amazon_pay]] +name="store_id" +label="Store ID" +placeholder="Enter Store ID" +required=true +type="Text" + [authorizedotnet] [[authorizedotnet.credit]] payment_method_type = "Mastercard" diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index 3f4653b02a..ed65b54b66 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -18,12 +18,13 @@ dummy_connector = [ ] [dependencies] -chrono = "0.4" -crc = "3" actix-web = "4.11.0" async-trait = "0.1.88" base64 = "0.22.1" bytes = "1.10.1" +chrono = "0.4" +crc = "3" +crypto = "0.2" encoding_rs = "0.8.35" error-stack = "0.4.1" hex = "0.4.3" @@ -54,6 +55,7 @@ serde_urlencoded = "0.7.1" unidecode = "0.3.0" serde_with = "3.12.0" sha1 = { version = "0.10.6" } +sha2 = "0.10" strum = { version = "0.26", features = ["derive"] } time = "0.3.41" unicode-normalization = "0.1.24" diff --git a/crates/hyperswitch_connectors/src/connectors/aci/transformers.rs b/crates/hyperswitch_connectors/src/connectors/aci/transformers.rs index 887daeee98..3edd04f830 100644 --- a/crates/hyperswitch_connectors/src/connectors/aci/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/aci/transformers.rs @@ -149,6 +149,7 @@ impl TryFrom<(&WalletData, &PaymentsAuthorizeRouterData)> for PaymentDetails { | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) | WalletData::GcashRedirect(_) + | WalletData::AmazonPay(_) | WalletData::ApplePay(_) | WalletData::ApplePayThirdPartySdk(_) | WalletData::DanaRedirect { .. } diff --git a/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs index db58519908..8abe4beb98 100644 --- a/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs @@ -2358,6 +2358,7 @@ impl TryFrom<(&WalletData, &PaymentsAuthorizeRouterData)> for AdyenPaymentMethod | WalletData::GooglePayRedirect(_) | WalletData::GooglePayThirdPartySdk(_) | WalletData::BluecodeRedirect {} + | WalletData::AmazonPay(_) | WalletData::PaypalSdk(_) | WalletData::WeChatPayQr(_) | WalletData::CashappQr(_) diff --git a/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs index 9d3029b6db..f8161c2763 100644 --- a/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs @@ -809,6 +809,7 @@ fn get_wallet_details( | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) | WalletData::GcashRedirect(_) + | WalletData::AmazonPay(_) | WalletData::ApplePay(_) | WalletData::BluecodeRedirect {} | WalletData::ApplePayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/amazonpay.rs b/crates/hyperswitch_connectors/src/connectors/amazonpay.rs index 3c38065ed5..c5a698b92b 100644 --- a/crates/hyperswitch_connectors/src/connectors/amazonpay.rs +++ b/crates/hyperswitch_connectors/src/connectors/amazonpay.rs @@ -1,14 +1,22 @@ pub mod transformers; +use std::sync::LazyLock; + +use base64::{engine::general_purpose::STANDARD, Engine}; +use chrono::Utc; +use common_enums::enums; use common_utils::{ + crypto::{RsaPssSha256, SignMessage}, errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{report, ResultExt}; +use hex; use hyperswitch_domain_models::{ - router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + payment_method_data::{PaymentMethodData, WalletData as WalletDataPaymentMethod}, + router_data::{AccessToken, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, @@ -19,10 +27,13 @@ use hyperswitch_domain_models::{ PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, }, - router_response_types::{PaymentsResponseData, RefundsResponseData}, + router_response_types::{ + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, + }, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -36,22 +47,143 @@ use hyperswitch_interfaces::{ types::{self, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{ExposeInterface, Mask, Maskable, PeekInterface, Secret}; +use sha2::{Digest, Sha256}; use transformers as amazonpay; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{self, PaymentsSyncRequestData}, +}; + +const SIGNING_ALGO: &str = "AMZN-PAY-RSASSA-PSS-V2"; +const HEADER_ACCEPT: &str = "accept"; +const HEADER_CONTENT_TYPE: &str = "content-type"; +const HEADER_DATE: &str = "x-amz-pay-date"; +const HEADER_HOST: &str = "x-amz-pay-host"; +const HEADER_IDEMPOTENCY_KEY: &str = "x-amz-pay-idempotency-key"; +const HEADER_REGION: &str = "x-amz-pay-region"; +const FINALIZE_SEGMENT: &str = "finalize"; +const AMAZON_PAY_API_BASE_URL: &str = "https://pay-api.amazon.com"; +const AMAZON_PAY_HOST: &str = "pay-api.amazon.com"; #[derive(Clone)] pub struct Amazonpay { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Amazonpay { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &StringMajorUnitForConnector, } } + + fn get_last_segment(canonical_uri: &str) -> String { + canonical_uri + .chars() + .rev() + .take_while(|&c| c != '/') + .collect::>() + .into_iter() + .rev() + .collect() + } + + pub fn create_authorization_header( + &self, + auth: amazonpay::AmazonpayAuthType, + canonical_uri: &str, + http_method: &Method, + hashed_payload: &str, + header: &[(String, Maskable)], + ) -> String { + let amazonpay::AmazonpayAuthType { + public_key, + private_key, + } = auth; + + let mut signed_headers = + format!("{HEADER_ACCEPT};{HEADER_CONTENT_TYPE};{HEADER_DATE};{HEADER_HOST};",); + if *http_method == Method::Post + && Self::get_last_segment(canonical_uri) != *FINALIZE_SEGMENT.to_string() + { + signed_headers.push_str(HEADER_IDEMPOTENCY_KEY); + signed_headers.push(';'); + } + signed_headers.push_str(HEADER_REGION); + + format!( + "{} PublicKeyId={}, SignedHeaders={}, Signature={}", + SIGNING_ALGO, + public_key.expose().clone(), + signed_headers, + Self::create_signature( + &private_key, + *http_method, + canonical_uri, + &signed_headers, + hashed_payload, + header + ) + .unwrap_or_else(|_| "Invalid signature".to_string()) + ) + } + + fn create_signature( + private_key: &Secret, + http_method: Method, + canonical_uri: &str, + signed_headers: &str, + hashed_payload: &str, + header: &[(String, Maskable)], + ) -> Result { + let mut canonical_request = http_method.to_string() + "\n" + canonical_uri + "\n\n"; + + let mut lowercase_sorted_header_keys: Vec = + header.iter().map(|(key, _)| key.to_lowercase()).collect(); + + lowercase_sorted_header_keys.sort(); + + for key in lowercase_sorted_header_keys { + if let Some((_, maskable_value)) = header.iter().find(|(k, _)| k.to_lowercase() == key) + { + let value: String = match maskable_value { + Maskable::Normal(v) => v.clone(), + Maskable::Masked(secret) => secret.clone().expose(), + }; + canonical_request.push_str(&format!("{key}:{value}\n")); + } + } + + canonical_request.push_str(&("\n".to_owned() + signed_headers + "\n" + hashed_payload)); + + let string_to_sign = format!( + "{}\n{}", + SIGNING_ALGO, + hex::encode(Sha256::digest(canonical_request.as_bytes())) + ); + + Self::sign(private_key, &string_to_sign) + .map_err(|e| format!("Failed to create signature: {e}")) + } + + fn sign( + private_key_pem_str: &Secret, + string_to_sign: &String, + ) -> Result { + let rsa_pss_sha256_signer = RsaPssSha256; + let signature_bytes = rsa_pss_sha256_signer + .sign_message( + private_key_pem_str.peek().as_bytes(), + string_to_sign.as_bytes(), + ) + .change_context(errors::ConnectorError::RequestEncodingFailed) + .map_err(|e| format!("Crypto operation failed: {e:?}"))?; + + Ok(STANDARD.encode(signature_bytes)) + } } impl api::Payment for Amazonpay {} @@ -70,7 +202,6 @@ impl api::PaymentToken for Amazonpay {} impl ConnectorIntegration for Amazonpay { - // Not Implemented (R) } impl ConnectorCommonExt for Amazonpay @@ -80,14 +211,70 @@ where fn build_headers( &self, req: &RouterData, - _connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - let mut header = vec![( - headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), - )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - header.append(&mut api_key); + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let http_method = self.get_http_method(); + + let canonical_uri: String = + self.get_url(req, connectors)? + .replacen(AMAZON_PAY_API_BASE_URL, "", 1); + + let mut header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + headers::ACCEPT.to_string(), + "application/json".to_string().into(), + ), + ( + HEADER_DATE.to_string(), + Utc::now() + .format("%Y-%m-%dT%H:%M:%SZ") + .to_string() + .into_masked(), + ), + ( + HEADER_HOST.to_string(), + AMAZON_PAY_HOST.to_string().into_masked(), + ), + (HEADER_REGION.to_string(), "na".to_string().into_masked()), + ]; + + if http_method == Method::Post + && Self::get_last_segment(&canonical_uri) != *FINALIZE_SEGMENT.to_string() + { + header.push(( + HEADER_IDEMPOTENCY_KEY.to_string(), + req.connector_request_reference_id.clone().into_masked(), + )); + } + + let hashed_payload = if http_method == Method::Get { + hex::encode(Sha256::digest("".as_bytes())) + } else { + hex::encode(Sha256::digest( + self.get_request_body(req, connectors)? + .get_inner_value() + .expose() + .as_bytes(), + )) + }; + + let authorization = self.create_authorization_header( + amazonpay::AmazonpayAuthType::try_from(&req.connector_auth_type)?, + &canonical_uri, + &http_method, + &hashed_payload, + &header, + ); + + header.push(( + headers::AUTHORIZATION.to_string(), + authorization.clone().into_masked(), + )); + Ok(header) } } @@ -99,10 +286,6 @@ impl ConnectorCommon for Amazonpay { fn get_currency_unit(&self) -> api::CurrencyUnit { api::CurrencyUnit::Base - // todo!() - // TODO! Check connector documentation, on which unit they are processing the currency. - // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, - // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base } fn common_get_content_type(&self) -> &'static str { @@ -113,18 +296,6 @@ impl ConnectorCommon for Amazonpay { connectors.amazonpay.base_url.as_ref() } - fn get_auth_header( - &self, - auth_type: &ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { - let auth = amazonpay::AmazonpayAuthType::try_from(auth_type) - .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - Ok(vec![( - headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), - )]) - } - fn build_error_response( &self, res: Response, @@ -140,11 +311,11 @@ impl ConnectorCommon for Amazonpay { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.reason_code.clone(), + message: response.message.clone(), attempt_status: None, connector_transaction_id: None, + reason: None, network_advice_code: None, network_decline_code: None, network_error_message: None, @@ -153,13 +324,9 @@ impl ConnectorCommon for Amazonpay { } } -impl ConnectorValidation for Amazonpay { - //TODO: implement functions when support enabled -} +impl ConnectorValidation for Amazonpay {} -impl ConnectorIntegration for Amazonpay { - //TODO: implement sessions flow -} +impl ConnectorIntegration for Amazonpay {} impl ConnectorIntegration for Amazonpay {} @@ -173,7 +340,7 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -183,10 +350,57 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + match req.request.payment_method_data.clone() { + PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { + WalletDataPaymentMethod::AmazonPay(ref req_wallet) => Ok(format!( + "{}/checkoutSessions/{}/finalize", + self.base_url(connectors), + req_wallet.checkout_session_id.clone() + )), + WalletDataPaymentMethod::AliPayQr(_) + | WalletDataPaymentMethod::AliPayRedirect(_) + | WalletDataPaymentMethod::AliPayHkRedirect(_) + | WalletDataPaymentMethod::AmazonPayRedirect(_) + | WalletDataPaymentMethod::MomoRedirect(_) + | WalletDataPaymentMethod::KakaoPayRedirect(_) + | WalletDataPaymentMethod::GoPayRedirect(_) + | WalletDataPaymentMethod::GcashRedirect(_) + | WalletDataPaymentMethod::ApplePay(_) + | WalletDataPaymentMethod::ApplePayRedirect(_) + | WalletDataPaymentMethod::ApplePayThirdPartySdk(_) + | WalletDataPaymentMethod::DanaRedirect {} + | WalletDataPaymentMethod::GooglePay(_) + | WalletDataPaymentMethod::GooglePayRedirect(_) + | WalletDataPaymentMethod::GooglePayThirdPartySdk(_) + | WalletDataPaymentMethod::MbWayRedirect(_) + | WalletDataPaymentMethod::MobilePayRedirect(_) + | WalletDataPaymentMethod::PaypalRedirect(_) + | WalletDataPaymentMethod::PaypalSdk(_) + | WalletDataPaymentMethod::Paze(_) + | WalletDataPaymentMethod::SamsungPay(_) + | WalletDataPaymentMethod::TwintRedirect {} + | WalletDataPaymentMethod::VippsRedirect {} + | WalletDataPaymentMethod::BluecodeRedirect {} + | WalletDataPaymentMethod::TouchNGoRedirect(_) + | WalletDataPaymentMethod::WeChatPayRedirect(_) + | WalletDataPaymentMethod::WeChatPayQr(_) + | WalletDataPaymentMethod::CashappQr(_) + | WalletDataPaymentMethod::SwishQr(_) + | WalletDataPaymentMethod::RevolutPay(_) + | WalletDataPaymentMethod::Paysera(_) + | WalletDataPaymentMethod::Skrill(_) + | WalletDataPaymentMethod::Mifinity(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("amazonpay"), + ) + .into()) + } + }, + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } } fn get_request_body( @@ -201,7 +415,7 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult { - let response: amazonpay::AmazonpayPaymentsResponse = res + let response: amazonpay::AmazonpayFinalizeResponse = res .response .parse_struct("Amazonpay PaymentsAuthorizeResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -260,7 +474,7 @@ impl ConnectorIntegration for Ama &self, req: &PaymentsSyncRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -270,10 +484,18 @@ impl ConnectorIntegration for Ama fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!( + "{}/charges/{}", + self.base_url(connectors), + req.request.get_connector_transaction_id()? + )) + } + + fn get_http_method(&self) -> Method { + Method::Get } fn build_request( @@ -299,7 +521,7 @@ impl ConnectorIntegration for Ama ) -> CustomResult { let response: amazonpay::AmazonpayPaymentsResponse = res .response - .parse_struct("amazonpay PaymentsSyncResponse") + .parse_struct("Amazonpay PaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -320,90 +542,31 @@ impl ConnectorIntegration for Ama } impl ConnectorIntegration for Amazonpay { - fn get_headers( - &self, - req: &PaymentsCaptureRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn get_request_body( - &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) - } - fn build_request( &self, - req: &PaymentsCaptureRouterData, - connectors: &Connectors, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - RequestBuilder::new() - .method(Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsCaptureType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &PaymentsCaptureRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: amazonpay::AmazonpayPaymentsResponse = res - .response - .parse_struct("Amazonpay PaymentsCaptureResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) + Err(errors::ConnectorError::NotImplemented("Capture".to_string()).into()) } } -impl ConnectorIntegration for Amazonpay {} +impl ConnectorIntegration for Amazonpay { + fn build_request( + &self, + _req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("Void".to_string()).into()) + } +} impl ConnectorIntegration for Amazonpay { fn get_headers( &self, req: &RefundsRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -414,9 +577,9 @@ impl ConnectorIntegration for Amazonp fn get_url( &self, _req: &RefundsRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/refunds", self.base_url(connectors))) } fn get_request_body( @@ -487,7 +650,7 @@ impl ConnectorIntegration for Amazonpay &self, req: &RefundSyncRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -497,10 +660,18 @@ impl ConnectorIntegration for Amazonpay fn get_url( &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!( + "{}/refunds/{}", + self.base_url(connectors), + req.request.connector_refund_id.clone().unwrap_or_default() + )) + } + + fn get_http_method(&self) -> Method { + Method::Get } fn build_request( @@ -573,4 +744,45 @@ impl webhooks::IncomingWebhook for Amazonpay { } } -impl ConnectorSpecifications for Amazonpay {} +static AMAZONPAY_SUPPORTED_PAYMENT_METHODS: LazyLock = + LazyLock::new(|| { + let supported_capture_methods = vec![enums::CaptureMethod::Automatic]; + + let mut amazonpay_supported_payment_methods = SupportedPaymentMethods::new(); + + amazonpay_supported_payment_methods.add( + enums::PaymentMethod::Wallet, + enums::PaymentMethodType::AmazonPay, + PaymentMethodDetails { + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::Supported, + supported_capture_methods: supported_capture_methods.clone(), + specific_features: None, + }, + ); + + amazonpay_supported_payment_methods + }); + +static AMAZONPAY_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + display_name: "Amazon Pay", + description: "Amazon Pay is an Alternative Payment Method (APM) connector that allows merchants to accept payments using customers' stored Amazon account details, providing a seamless checkout experience.", + connector_type: enums::HyperswitchConnectorCategory::AlternativePaymentMethod, + integration_status: enums::ConnectorIntegrationStatus::Alpha, +}; + +static AMAZONPAY_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = []; + +impl ConnectorSpecifications for Amazonpay { + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + Some(&AMAZONPAY_CONNECTOR_INFO) + } + + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + Some(&AMAZONPAY_SUPPORTED_PAYMENT_METHODS) + } + + fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { + Some(&AMAZONPAY_SUPPORTED_WEBHOOK_FLOWS) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/amazonpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/amazonpay/transformers.rs index 5b11d9e9d4..53cdc5c109 100644 --- a/crates/hyperswitch_connectors/src/connectors/amazonpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/amazonpay/transformers.rs @@ -1,31 +1,30 @@ -use common_enums::enums; -use common_utils::types::StringMinorUnit; +use std::collections::HashMap; + +use common_enums::{enums, CaptureMethod}; +use common_utils::{errors::CustomResult, pii, types::StringMajorUnit}; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, - router_data::{ConnectorAuthType, RouterData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::ResponseId, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{PaymentsAuthorizeRouterData, RefundsRouterData}, }; -use hyperswitch_interfaces::errors; +use hyperswitch_interfaces::{consts, errors}; use masking::Secret; use serde::{Deserialize, Serialize}; use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + utils::{is_refund_failure, RouterData as _}, }; -//TODO: Fill the struct with respective fields pub struct AmazonpayRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: StringMajorUnit, pub router_data: T, } -impl From<(StringMinorUnit, T)> for AmazonpayRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(StringMajorUnit, T)> for AmazonpayRouterData { + fn from((amount, item): (StringMajorUnit, T)) -> Self { Self { amount, router_data: item, @@ -33,89 +32,385 @@ impl From<(StringMinorUnit, T)> for AmazonpayRouterData { } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] -pub struct AmazonpayPaymentsRequest { - amount: StringMinorUnit, - card: AmazonpayCard, +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AmazonpayFinalizeRequest { + charge_amount: ChargeAmount, + shipping_address: AddressDetails, + payment_intent: PaymentIntent, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct AmazonpayCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ChargeAmount { + amount: StringMajorUnit, + currency_code: common_enums::Currency, } -impl TryFrom<&AmazonpayRouterData<&PaymentsAuthorizeRouterData>> for AmazonpayPaymentsRequest { +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AddressDetails { + name: Secret, + address_line_1: Secret, + address_line_2: Option>, + address_line_3: Option>, + city: String, + state_or_region: Secret, + postal_code: Secret, + country_code: Option, + phone_number: Secret, +} + +#[derive(Debug, Serialize, PartialEq)] +pub enum PaymentIntent { + AuthorizeWithCapture, +} + +fn get_amazonpay_capture_type( + item: Option, +) -> CustomResult { + match item { + Some(CaptureMethod::Automatic) | None => Ok(PaymentIntent::AuthorizeWithCapture), + Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()), + } +} + +impl TryFrom<&AmazonpayRouterData<&PaymentsAuthorizeRouterData>> for AmazonpayFinalizeRequest { type Error = error_stack::Report; fn try_from( item: &AmazonpayRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { - match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = AmazonpayCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; - Ok(Self { - amount: item.amount.clone(), - card, - }) - } - _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + let charge_amount = ChargeAmount { + amount: item.amount.clone(), + currency_code: item.router_data.request.currency, + }; + let shipping_address = AddressDetails { + name: item.router_data.get_required_shipping_full_name()?, + address_line_1: item.router_data.get_required_shipping_line1()?, + address_line_2: item.router_data.get_optional_shipping_line2(), + address_line_3: item.router_data.get_optional_shipping_line3(), + city: item.router_data.get_required_shipping_city()?, + state_or_region: item.router_data.get_required_shipping_state()?, + postal_code: item.router_data.get_required_shipping_zip()?, + country_code: item.router_data.get_optional_shipping_country(), + phone_number: item.router_data.get_required_shipping_phone_number()?, + }; + let payment_intent = get_amazonpay_capture_type(item.router_data.request.capture_method)?; + Ok(Self { + charge_amount, + shipping_address, + payment_intent, + }) + } +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AmazonpayFinalizeResponse { + checkout_session_id: String, + web_checkout_details: WebCheckoutDetails, + product_type: Option, + payment_details: Option, + cart_details: CartDetails, + charge_permission_type: String, + order_type: Option, + recurring_metadata: Option, + payment_method_on_file_metadata: Option, + processor_specifications: Option, + merchant_details: Option, + merchant_metadata: Option, + supplementary_data: Option, + buyer: Option, + billing_address: Option, + payment_preferences: Option, + status_details: FinalizeStatusDetails, + shipping_address: Option, + platform_id: Option, + charge_permission_id: String, + charge_id: String, + constraints: Option, + creation_timestamp: String, + expiration_timestamp: Option, + store_id: Option, + provider_metadata: Option, + release_environment: Option, + checkout_button_text: Option, + delivery_specifications: Option, + tokens: Option, + disbursement_details: Option, + channel_type: Option, + payment_processing_meta_data: PaymentProcessingMetaData, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct WebCheckoutDetails { + checkout_review_return_url: Option, + checkout_result_return_url: Option, + amazon_pay_redirect_url: Option, + authorize_result_return_url: Option, + sign_in_return_url: Option, + sign_in_cancel_url: Option, + checkout_error_url: Option, + sign_in_error_url: Option, + amazon_pay_decline_url: Option, + checkout_cancel_url: Option, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentDetails { + payment_intent: String, + can_handle_pending_authorization: bool, + charge_amount: ChargeAmount, + total_order_amount: ChargeAmount, + presentment_currency: String, + soft_descriptor: String, + allow_overcharge: bool, + extend_expiration: bool, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct CartDetails { + line_items: Vec, + delivery_options: Vec, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct DeliveryOptions { + id: String, + price: ChargeAmount, + shipping_method: ShippingMethod, + is_default: bool, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ShippingMethod { + shipping_method_name: String, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct RecurringMetadata { + frequency: Frequency, + amount: ChargeAmount, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Frequency { + unit: String, + value: String, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct BuyerDetails { + buyer_id: Secret, + name: Secret, + email: pii::Email, + phone_number: Secret, + prime_membership_types: Vec, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct FinalizeStatusDetails { + state: FinalizeState, + reason_code: Option, + reason_description: Option, + last_updated_timestamp: String, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub enum FinalizeState { + Open, + Completed, + Canceled, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct DeliverySpecifications { + special_restrictions: Vec, + address_restrictions: AddressRestrictions, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AddressRestrictions { + r#type: String, + restrictions: HashMap, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Restriction { + pub states_or_regions: Vec>, + pub zip_codes: Vec>, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentProcessingMetaData { + payment_processing_model: String, +} + +impl From for common_enums::AttemptStatus { + fn from(item: FinalizeState) -> Self { + match item { + FinalizeState::Open => Self::Pending, + FinalizeState::Completed => Self::Charged, + FinalizeState::Canceled => Self::Failure, + } + } +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + match item.response.status_details.state { + FinalizeState::Canceled => { + Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(ErrorResponse { + code: consts::NO_ERROR_CODE.to_owned(), + message: "Checkout was not successfully completed".to_owned(), + reason: Some("Checkout was not successfully completed due to buyer abandoment, payment decline, or because checkout wasn't confirmed with Finalize Checkout Session.".to_owned()), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(item.response.checkout_session_id), + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + connector_metadata: None, + }), + ..item.data + }) + } + FinalizeState::Open + | FinalizeState::Completed => { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status_details.state), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.charge_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.checkout_session_id), + incremental_authorization_allowed: None, + charges: None, + }), + ..item.data + }) + } } } } -//TODO: Fill the struct with respective fields -// Auth Struct pub struct AmazonpayAuthType { - pub(super) api_key: Secret, + pub(super) public_key: Secret, + pub(super) private_key: Secret, } impl TryFrom<&ConnectorAuthType> for AmazonpayAuthType { type Error = error_stack::Report; fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + public_key: api_key.to_owned(), + private_key: key1.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "PascalCase")] pub enum AmazonpayPaymentStatus { - Succeeded, - Failed, - #[default] - Processing, + AuthorizationInitiated, + Authorized, + Canceled, + Captured, + CaptureInitiated, + Declined, } impl From for common_enums::AttemptStatus { fn from(item: AmazonpayPaymentStatus) -> Self { match item { - AmazonpayPaymentStatus::Succeeded => Self::Charged, - AmazonpayPaymentStatus::Failed => Self::Failure, - AmazonpayPaymentStatus::Processing => Self::Authorizing, + AmazonpayPaymentStatus::AuthorizationInitiated => Self::Pending, + AmazonpayPaymentStatus::Authorized => Self::Authorized, + AmazonpayPaymentStatus::Canceled => Self::Voided, + AmazonpayPaymentStatus::Captured => Self::Charged, + AmazonpayPaymentStatus::CaptureInitiated => Self::CaptureInitiated, + AmazonpayPaymentStatus::Declined => Self::CaptureFailed, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct AmazonpayPaymentsResponse { - status: AmazonpayPaymentStatus, - id: String, + charge_id: String, + charge_amount: ChargeAmount, + charge_permission_id: String, + capture_amount: Option, + refunded_amount: Option, + soft_descriptor: Option, + provider_metadata: Option, + converted_amount: Option, + conversion_rate: Option, + channel: Option, + charge_initiator: Option, + status_details: PaymentsStatusDetails, + creation_timestamp: String, + expiration_timestamp: String, + release_environment: Option, + merchant_metadata: Option, + platform_id: Option, + web_checkout_details: Option, + disbursement_details: Option, + payment_method: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ProviderMetadata { + provider_reference_id: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentsStatusDetails { + state: AmazonpayPaymentStatus, + reason_code: Option, + reason_description: Option, + last_updated_timestamp: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum ReleaseEnvironment { + Sandbox, + Live, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct MerchantMetadata { + merchant_reference_id: Option, + merchant_store_name: Option, + note_to_buyer: Option, + custom_information: Option, } impl TryFrom> @@ -125,67 +420,122 @@ impl TryFrom, ) -> Result { - Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: Box::new(None), - mandate_reference: Box::new(None), - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charges: None, - }), - ..item.data - }) + match item.response.status_details.state { + AmazonpayPaymentStatus::Canceled => { + Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(ErrorResponse { + code: consts::NO_ERROR_CODE.to_owned(), + message: "Charge was canceled by Amazon or by the merchant".to_owned(), + reason: Some("Charge was canceled due to expiration, Amazon, buyer, merchant action, or charge permission cancellation.".to_owned()), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(item.response.charge_id), + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + connector_metadata: None, + }), + ..item.data + }) + } + AmazonpayPaymentStatus::Declined => { + Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(ErrorResponse { + code: consts::NO_ERROR_CODE.to_owned(), + message: "The authorization or capture was declined".to_owned(), + reason: Some("Charge was declined due to soft/hard decline, Amazon rejection, or internal processing failure.".to_owned()), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(item.response.charge_id), + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + connector_metadata: None, + }), + ..item.data + }) + } + _ => { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status_details.state), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.charge_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charges: None, + }), + ..item.data + }) + } + } } } -//TODO: Fill the struct with respective fields -// REFUND : -// Type definition for RefundRequest -#[derive(Default, Debug, Serialize)] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct AmazonpayRefundRequest { - pub amount: StringMinorUnit, + pub refund_amount: ChargeAmount, + pub charge_id: String, } impl TryFrom<&AmazonpayRouterData<&RefundsRouterData>> for AmazonpayRefundRequest { type Error = error_stack::Report; fn try_from(item: &AmazonpayRouterData<&RefundsRouterData>) -> Result { + let refund_amount = ChargeAmount { + amount: item.amount.clone(), + currency_code: item.router_data.request.currency, + }; + let charge_id = item.router_data.request.connector_transaction_id.clone(); Ok(Self { - amount: item.amount.to_owned(), + refund_amount, + charge_id, }) } } -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, + RefundInitiated, + Refunded, + Declined, } impl From for enums::RefundStatus { fn from(item: RefundStatus) -> Self { match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping + RefundStatus::RefundInitiated => Self::Pending, + RefundStatus::Refunded => Self::Success, + RefundStatus::Declined => Self::Failure, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct RefundResponse { - id: String, - status: RefundStatus, + refund_id: String, + charge_id: String, + creation_timestamp: String, + refund_amount: ChargeAmount, + status_details: RefundStatusDetails, + soft_descriptor: String, + release_environment: ReleaseEnvironment, + disbursement_details: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RefundStatusDetails { + state: RefundStatus, + reason_code: Option, + reason_description: Option, + last_updated_timestamp: String, } impl TryFrom> for RefundsRouterData { @@ -193,13 +543,35 @@ impl TryFrom> for RefundsRout fn try_from( item: RefundsResponseRouterData, ) -> Result { - Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), - ..item.data - }) + match item.response.status_details.state { + RefundStatus::Declined => { + Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(ErrorResponse { + code: consts::NO_ERROR_CODE.to_owned(), + message: "Amazon has declined the refund.".to_owned(), + reason: Some("Amazon has declined the refund because maximum amount has been refunded or there was some other issue.".to_owned()), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(item.response.charge_id), + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + connector_metadata: None, + }), + ..item.data + }) + } + _ => { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.refund_id, + refund_status: enums::RefundStatus::from(item.response.status_details.state), + }), + ..item.data + }) + } + } } } @@ -208,21 +580,36 @@ impl TryFrom> for RefundsRouter fn try_from( item: RefundsResponseRouterData, ) -> Result { + let refund_status = enums::RefundStatus::from(item.response.status_details.state); + let response = if is_refund_failure(refund_status) { + Err(ErrorResponse { + code: consts::NO_ERROR_CODE.to_owned(), + message: "Amazon has declined the refund.".to_owned(), + reason: Some("Amazon has declined the refund because maximum amount has been refunded or there was some other issue.".to_owned()), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(item.response.refund_id.clone()), + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + connector_metadata: None, + }) + } else { + Ok(RefundsResponseData { + connector_refund_id: item.response.refund_id.to_string(), + refund_status, + }) + }; Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), + response, ..item.data }) } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct AmazonpayErrorResponse { - pub status_code: u16, - pub code: String, + pub reason_code: String, pub message: String, - pub reason: Option, } diff --git a/crates/hyperswitch_connectors/src/connectors/authorizedotnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/authorizedotnet/transformers.rs index b820e30a1d..c523b08b5a 100644 --- a/crates/hyperswitch_connectors/src/connectors/authorizedotnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/authorizedotnet/transformers.rs @@ -527,6 +527,7 @@ impl TryFrom<&SetupMandateRouterData> for CreateCustomerPaymentProfileRequest { | WalletData::MbWayRedirect(_) | WalletData::MobilePayRedirect(_) | WalletData::PaypalRedirect(_) + | WalletData::AmazonPay(_) | WalletData::PaypalSdk(_) | WalletData::Paze(_) | WalletData::SamsungPay(_) @@ -2169,6 +2170,7 @@ fn get_wallet_data( WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs index 2f8f942ac7..30454d3aae 100644 --- a/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs @@ -314,6 +314,7 @@ impl TryFrom<&SetupMandateRouterData> for BankOfAmericaPaymentsRequest { | WalletData::PaypalSdk(_) | WalletData::Paze(_) | WalletData::SamsungPay(_) + | WalletData::AmazonPay(_) | WalletData::TwintRedirect {} | WalletData::VippsRedirect {} | WalletData::TouchNGoRedirect(_) @@ -1100,6 +1101,7 @@ impl TryFrom<&BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>> | WalletData::PaypalRedirect(_) | WalletData::PaypalSdk(_) | WalletData::Paze(_) + | WalletData::AmazonPay(_) | WalletData::TwintRedirect {} | WalletData::VippsRedirect {} | WalletData::TouchNGoRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/barclaycard/transformers.rs b/crates/hyperswitch_connectors/src/connectors/barclaycard/transformers.rs index 9d78d9de9a..acbf757369 100644 --- a/crates/hyperswitch_connectors/src/connectors/barclaycard/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/barclaycard/transformers.rs @@ -1318,6 +1318,7 @@ impl TryFrom<&BarclaycardRouterData<&PaymentsAuthorizeRouterData>> for Barclayca | WalletData::Paysera(_) | WalletData::Skrill(_) | WalletData::BluecodeRedirect {} + | WalletData::AmazonPay(_) | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Barclaycard"), ) diff --git a/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs index 301393b2aa..cf79581ce6 100644 --- a/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs @@ -373,6 +373,7 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/boku/transformers.rs b/crates/hyperswitch_connectors/src/connectors/boku/transformers.rs index 2763e5a5e4..1e89a151e1 100644 --- a/crates/hyperswitch_connectors/src/connectors/boku/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/boku/transformers.rs @@ -180,6 +180,7 @@ fn get_wallet_type(wallet_data: &WalletData) -> Result for TokenRequest { WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) @@ -370,6 +371,7 @@ impl TryFrom<&CheckoutRouterData<&PaymentsAuthorizeRouterData>> for PaymentsRequ WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs index 72b596f1d4..e39bc64c3f 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs @@ -304,6 +304,7 @@ impl TryFrom<&SetupMandateRouterData> for CybersourceZeroMandateRequest { | WalletData::PaypalRedirect(_) | WalletData::PaypalSdk(_) | WalletData::Paze(_) + | WalletData::AmazonPay(_) | WalletData::TwintRedirect {} | WalletData::VippsRedirect {} | WalletData::TouchNGoRedirect(_) @@ -2491,6 +2492,7 @@ impl TryFrom<&CybersourceRouterData<&PaymentsAuthorizeRouterData>> for Cybersour | WalletData::WeChatPayQr(_) | WalletData::CashappQr(_) | WalletData::SwishQr(_) + | WalletData::AmazonPay(_) | WalletData::Mifinity(_) | WalletData::RevolutPay(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 992d5415c3..1879485290 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -576,6 +576,7 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque | WalletData::MbWayRedirect(_) | WalletData::MobilePayRedirect(_) | WalletData::PaypalRedirect(_) + | WalletData::AmazonPay(_) | WalletData::PaypalSdk(_) | WalletData::Paze(_) | WalletData::SamsungPay(_) diff --git a/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs index af27e27ba4..6467a5bd99 100644 --- a/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs @@ -59,6 +59,7 @@ impl TryFrom<&GlobepayRouterData<&types::PaymentsAuthorizeRouterData>> for Globe WalletData::WeChatPayQr(_) => GlobepayChannel::Wechat, WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs b/crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs index bd951cc472..105772073f 100644 --- a/crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs @@ -161,6 +161,7 @@ impl TryFrom<&MifinityRouterData<&types::PaymentsAuthorizeRouterData>> for Mifin WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs index f20c7a2254..e39c57d5ca 100644 --- a/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs @@ -525,6 +525,7 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> WalletData::MbWayRedirect(_) => Type::Redirect, WalletData::AliPayQr(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) @@ -595,6 +596,7 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> WalletData::MbWayRedirect(_) => Gateway::MbWay, WalletData::AliPayQr(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) @@ -772,6 +774,7 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> } WalletData::AliPayQr(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs index deb9cffd10..cc8ada03d0 100644 --- a/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs @@ -706,6 +706,7 @@ fn get_wallet_details( WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/nmi/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nmi/transformers.rs index aafcb54103..6f9fdc6d3c 100644 --- a/crates/hyperswitch_connectors/src/connectors/nmi/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/nmi/transformers.rs @@ -640,6 +640,7 @@ impl TryFrom<(&PaymentMethodData, Option<&PaymentsAuthorizeRouterData>)> for Pay | WalletData::PaypalSdk(_) | WalletData::Paze(_) | WalletData::SamsungPay(_) + | WalletData::AmazonPay(_) | WalletData::TwintRedirect {} | WalletData::VippsRedirect {} | WalletData::TouchNGoRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/noon/transformers.rs b/crates/hyperswitch_connectors/src/connectors/noon/transformers.rs index 53edf82252..127027bfcb 100644 --- a/crates/hyperswitch_connectors/src/connectors/noon/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/noon/transformers.rs @@ -323,6 +323,7 @@ impl TryFrom<&NoonRouterData<&PaymentsAuthorizeRouterData>> for NoonPaymentsRequ WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index 33f207f894..c3fa01c74c 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -355,6 +355,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym WalletDataPaymentMethod::AliPayQr(_) | WalletDataPaymentMethod::AliPayRedirect(_) | WalletDataPaymentMethod::AliPayHkRedirect(_) + | WalletDataPaymentMethod::AmazonPay(_) | WalletDataPaymentMethod::AmazonPayRedirect(_) | WalletDataPaymentMethod::Paysera(_) | WalletDataPaymentMethod::Skrill(_) @@ -1619,6 +1620,7 @@ impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { WalletDataPaymentMethod::AliPayQr(_) | WalletDataPaymentMethod::AliPayRedirect(_) | WalletDataPaymentMethod::AliPayHkRedirect(_) + | WalletDataPaymentMethod::AmazonPay(_) | WalletDataPaymentMethod::AmazonPayRedirect(_) | WalletDataPaymentMethod::Paysera(_) | WalletDataPaymentMethod::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs index fa9c3e4d49..268c4f3e15 100644 --- a/crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs @@ -1359,6 +1359,7 @@ where WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/payme/transformers.rs b/crates/hyperswitch_connectors/src/connectors/payme/transformers.rs index 958c728c5e..8a4361ff92 100644 --- a/crates/hyperswitch_connectors/src/connectors/payme/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/payme/transformers.rs @@ -404,6 +404,7 @@ impl TryFrom<&PaymentMethodData> for SalePaymentMethod { | WalletData::AliPayRedirect(_) | WalletData::BluecodeRedirect {} | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs b/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs index 88fa681934..7be4065bcc 100644 --- a/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs @@ -1076,6 +1076,7 @@ impl TryFrom<&PaypalRouterData<&PaymentsAuthorizeRouterData>> for PaypalPayments | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) | WalletData::GcashRedirect(_) + | WalletData::AmazonPay(_) | WalletData::ApplePay(_) | WalletData::ApplePayRedirect(_) | WalletData::ApplePayThirdPartySdk(_) diff --git a/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs b/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs index 159ea3ab09..eeaf340e10 100644 --- a/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs @@ -379,6 +379,7 @@ impl TryFrom<&WalletData> for PaymentMethodType { WalletData::Paysera(_) => Ok(Self::Paysera), WalletData::Skrill(_) => Ok(Self::Skrill), WalletData::AliPayQr(_) + | WalletData::AmazonPay(_) | WalletData::AliPayHkRedirect(_) | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs index 335ea265c0..1f899e33ab 100644 --- a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs @@ -105,7 +105,8 @@ impl TryFrom<(&types::TokenizationRouterData, WalletData)> for SquareTokenReques fn try_from(value: (&types::TokenizationRouterData, WalletData)) -> Result { let (_item, wallet_data) = value; match wallet_data { - WalletData::ApplePay(_) + WalletData::AmazonPay(_) + | WalletData::ApplePay(_) | WalletData::GooglePay(_) | WalletData::BluecodeRedirect {} | WalletData::AliPayQr(_) diff --git a/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs b/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs index 8feddc5c2b..00e3647029 100644 --- a/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs @@ -1169,6 +1169,7 @@ fn get_stripe_payment_method_type_from_wallet_data( | WalletData::BluecodeRedirect {} | WalletData::Paysera(_) | WalletData::Skrill(_) + | WalletData::AmazonPay(_) | WalletData::AliPayHkRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) @@ -1602,6 +1603,7 @@ impl TryFrom<(&WalletData, Option)> for StripePaymentMethodD | WalletData::Paysera(_) | WalletData::BluecodeRedirect {} | WalletData::Skrill(_) + | WalletData::AmazonPay(_) | WalletData::AliPayHkRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs index 54be560142..f0c783227c 100644 --- a/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs @@ -228,6 +228,7 @@ impl TryFrom<&SetupMandateRouterData> for WellsfargoZeroMandateRequest { | WalletData::PaypalSdk(_) | WalletData::Paze(_) | WalletData::SamsungPay(_) + | WalletData::AmazonPay(_) | WalletData::TwintRedirect {} | WalletData::VippsRedirect {} | WalletData::TouchNGoRedirect(_) @@ -1316,6 +1317,7 @@ impl TryFrom<&WellsfargoRouterData<&PaymentsAuthorizeRouterData>> for Wellsfargo | WalletData::PaypalSdk(_) | WalletData::Paze(_) | WalletData::SamsungPay(_) + | WalletData::AmazonPay(_) | WalletData::TwintRedirect {} | WalletData::VippsRedirect {} | WalletData::TouchNGoRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs index 8035efa49c..d4ddbaba60 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs @@ -158,6 +158,7 @@ fn fetch_payment_instrument( WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPay(_) | WalletData::AmazonPayRedirect(_) | WalletData::Paysera(_) | WalletData::Skrill(_) diff --git a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs index 3d94da666b..3890ec2f37 100644 --- a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs @@ -486,6 +486,7 @@ impl ), WalletData::WeChatPayRedirect(_) | WalletData::PaypalRedirect(_) + | WalletData::AmazonPay(_) | WalletData::ApplePay(_) | WalletData::GooglePay(_) | WalletData::BluecodeRedirect {} diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index aefb4f53f7..d34cb48080 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -521,8 +521,16 @@ pub trait RouterData { fn get_optional_shipping_last_name(&self) -> Option>; fn get_optional_shipping_full_name(&self) -> Option>; fn get_optional_shipping_phone_number(&self) -> Option>; + fn get_optional_shipping_phone_number_without_country_code(&self) -> Option>; fn get_optional_shipping_email(&self) -> Option; + fn get_required_shipping_full_name(&self) -> Result, Error>; + fn get_required_shipping_line1(&self) -> Result, Error>; + fn get_required_shipping_city(&self) -> Result; + fn get_required_shipping_state(&self) -> Result, Error>; + fn get_required_shipping_zip(&self) -> Result, Error>; + fn get_required_shipping_phone_number(&self) -> Result, Error>; + fn get_optional_billing_full_name(&self) -> Option>; fn get_optional_billing_line1(&self) -> Option>; fn get_optional_billing_line3(&self) -> Option>; @@ -673,6 +681,13 @@ impl RouterData .and_then(|phone_details| phone_details.get_number_with_country_code().ok()) } + fn get_optional_shipping_phone_number_without_country_code(&self) -> Option> { + self.address + .get_shipping() + .and_then(|shipping_address| shipping_address.clone().phone) + .and_then(|phone_details| phone_details.get_number().ok()) + } + fn get_description(&self) -> Result { self.description .clone() @@ -1011,6 +1026,38 @@ impl RouterData .and_then(|billing_address| billing_address.get_optional_full_name()) } + fn get_required_shipping_full_name(&self) -> Result, Error> { + self.get_optional_shipping_full_name() + .ok_or_else(missing_field_err( + "shipping.address.first_name or shipping.address.last_name", + )) + } + + fn get_required_shipping_line1(&self) -> Result, Error> { + self.get_optional_shipping_line1() + .ok_or_else(missing_field_err("shipping.address.line1")) + } + + fn get_required_shipping_city(&self) -> Result { + self.get_optional_shipping_city() + .ok_or_else(missing_field_err("shipping.address.city")) + } + + fn get_required_shipping_state(&self) -> Result, Error> { + self.get_optional_shipping_state() + .ok_or_else(missing_field_err("shipping.address.state")) + } + + fn get_required_shipping_zip(&self) -> Result, Error> { + self.get_optional_shipping_zip() + .ok_or_else(missing_field_err("shipping.address.zip")) + } + + fn get_required_shipping_phone_number(&self) -> Result, Error> { + self.get_optional_shipping_phone_number_without_country_code() + .ok_or_else(missing_field_err("shipping.phone.number")) + } + #[cfg(feature = "payouts")] fn get_payout_method_data(&self) -> Result { self.payout_method_data @@ -5470,6 +5517,7 @@ pub enum PaymentMethodDataType { AliPayQr, AliPayRedirect, AliPayHkRedirect, + AmazonPay, AmazonPayRedirect, Skrill, Paysera, @@ -5608,6 +5656,7 @@ impl From for PaymentMethodDataType { payment_method_data::WalletData::KakaoPayRedirect(_) => Self::KakaoPayRedirect, payment_method_data::WalletData::GoPayRedirect(_) => Self::GoPayRedirect, payment_method_data::WalletData::GcashRedirect(_) => Self::GcashRedirect, + payment_method_data::WalletData::AmazonPay(_) => Self::AmazonPay, payment_method_data::WalletData::ApplePay(_) => Self::ApplePay, payment_method_data::WalletData::ApplePayRedirect(_) => Self::ApplePayRedirect, payment_method_data::WalletData::ApplePayThirdPartySdk(_) => { diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index 37812cf64e..ee48226f4e 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -281,6 +281,7 @@ pub enum WalletData { AliPayQr(Box), AliPayRedirect(AliPayRedirection), AliPayHkRedirect(AliPayHkRedirection), + AmazonPay(AmazonPayWalletData), AmazonPayRedirect(Box), BluecodeRedirect {}, Paysera(Box), @@ -530,6 +531,11 @@ pub struct ApplepayPaymentMethod { pub pm_type: String, } +#[derive(Eq, PartialEq, Clone, Default, Debug, serde::Deserialize, serde::Serialize)] +pub struct AmazonPayWalletData { + pub checkout_session_id: String, +} + #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub enum RealTimePaymentData { DuitNow {}, @@ -1142,6 +1148,9 @@ impl From for WalletData { api_models::payments::WalletData::AliPayHkRedirect(_) => { Self::AliPayHkRedirect(AliPayHkRedirection {}) } + api_models::payments::WalletData::AmazonPay(amazon_pay_data) => { + Self::AmazonPay(AmazonPayWalletData::from(amazon_pay_data)) + } api_models::payments::WalletData::AmazonPayRedirect(_) => { Self::AmazonPayRedirect(Box::new(AmazonPayRedirect {})) } @@ -1261,6 +1270,14 @@ impl From for ApplePayWalletData { } } +impl From for AmazonPayWalletData { + fn from(value: api_models::payments::AmazonPayWalletData) -> Self { + Self { + checkout_session_id: value.checkout_session_id, + } + } +} + impl From for SamsungPayTokenData { fn from(samsung_pay_token_data: api_models::payments::SamsungPayTokenData) -> Self { Self { @@ -1993,6 +2010,7 @@ impl GetPaymentMethodType for WalletData { Self::KakaoPayRedirect(_) => api_enums::PaymentMethodType::KakaoPay, Self::GoPayRedirect(_) => api_enums::PaymentMethodType::GoPay, Self::GcashRedirect(_) => api_enums::PaymentMethodType::Gcash, + Self::AmazonPay(_) => api_enums::PaymentMethodType::AmazonPay, Self::ApplePay(_) | Self::ApplePayRedirect(_) | Self::ApplePayThirdPartySdk(_) => { api_enums::PaymentMethodType::ApplePay } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 327a0137a6..f5175e8410 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -1129,7 +1129,7 @@ impl NetAmount { Self { order_amount, shipping_cost: payments_request.shipping_cost, - order_tax_amount: None, + order_tax_amount: payments_request.order_tax_amount, surcharge_amount, tax_on_surcharge, } diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index f5ec0e7d24..a9a842dcfd 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -1206,6 +1206,9 @@ pub struct PaymentsSessionData { pub minor_amount: MinorUnit, pub apple_pay_recurring_details: Option, pub customer_name: Option>, + pub order_tax_amount: Option, + pub shipping_cost: Option, + pub metadata: Option>, } #[derive(Debug, Clone, Default)] diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index b5486ac78c..58809e0263 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -599,6 +599,14 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PollConfig, api_models::payments::ExternalAuthenticationDetailsResponse, api_models::payments::ExtendedCardInfo, + api_models::payments::AmazonPaySessionTokenData, + api_models::payments::AmazonPayMerchantCredentials, + api_models::payments::AmazonPayWalletData, + api_models::payments::AmazonPaySessionTokenResponse, + api_models::payments::AmazonPayPaymentIntent, + api_models::payments::AmazonPayDeliveryOptions, + api_models::payments::AmazonPayDeliveryPrice, + api_models::payments::AmazonPayShippingMethod, api_models::payment_methods::RequiredFieldInfo, api_models::payment_methods::DefaultPaymentMethod, api_models::payment_methods::MaskedBankDetails, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 1c52daa639..66ab8ead91 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -577,6 +577,14 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payment_methods::CardNetworkTokenizeRequest, api_models::payment_methods::CardNetworkTokenizeResponse, api_models::payment_methods::CardType, + api_models::payments::AmazonPaySessionTokenData, + api_models::payments::AmazonPayMerchantCredentials, + api_models::payments::AmazonPayWalletData, + api_models::payments::AmazonPaySessionTokenResponse, + api_models::payments::AmazonPayPaymentIntent, + api_models::payments::AmazonPayDeliveryOptions, + api_models::payments::AmazonPayDeliveryPrice, + api_models::payments::AmazonPayShippingMethod, api_models::payment_methods::RequiredFieldInfo, api_models::payment_methods::MaskedBankDetails, api_models::payment_methods::SurchargeDetailsResponse, diff --git a/crates/payment_methods/src/configs/payment_connector_required_fields.rs b/crates/payment_methods/src/configs/payment_connector_required_fields.rs index e9ce3fed08..32a1c175ea 100644 --- a/crates/payment_methods/src/configs/payment_connector_required_fields.rs +++ b/crates/payment_methods/src/configs/payment_connector_required_fields.rs @@ -2543,7 +2543,25 @@ fn get_wallet_required_fields() -> HashMap Option<&hyperswitch_domain_models::address::Address>; fn get_optional_shipping_line1(&self) -> Option>; fn get_optional_shipping_line2(&self) -> Option>; + fn get_optional_shipping_line3(&self) -> Option>; fn get_optional_shipping_city(&self) -> Option; fn get_optional_shipping_country(&self) -> Option; fn get_optional_shipping_zip(&self) -> Option>; @@ -364,6 +365,15 @@ impl RouterData for types::RouterData Option> { + 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 { 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 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, diff --git a/crates/router/src/core/connector_validation.rs b/crates/router/src/core/connector_validation.rs index d5c9fbc927..69e8cf9621 100644 --- a/crates/router/src/core/connector_validation.rs +++ b/crates/router/src/core/connector_validation.rs @@ -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)?; diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 1f67fd4c00..11af786d68 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -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 { + let amazon_pay_session_token_data = router_data + .connector_wallets_details + .clone() + .parse_value::("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, } } } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index b3a1c7e52b..2de5d00ba5 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -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()), diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index ba443417cd..913870ef58 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -514,6 +514,7 @@ impl From 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, } } diff --git a/crates/router/src/core/payments/operations/payment_session_intent.rs b/crates/router/src/core/payments/operations/payment_session_intent.rs index d50c5da81c..2376dd93a9 100644 --- a/crates/router/src/core/payments/operations/payment_session_intent.rs +++ b/crates/router/src/core/payments/operations/payment_session_intent.rs @@ -395,6 +395,7 @@ impl From 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, } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 50be78e992..9f7dd008d4 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -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 TryFrom> 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 TryFrom> 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 TryFrom> 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 TryFrom> for types::PaymentsSessionD surcharge_details: payment_data.surcharge_details, apple_pay_recurring_details, customer_name: None, + order_tax_amount, + shipping_cost, + metadata, }) } } diff --git a/crates/router/src/types/api/connector_mapping.rs b/crates/router/src/types/api/connector_mapping.rs index 2d8ae49d58..2f93feba33 100644 --- a/crates/router/src/types/api/connector_mapping.rs +++ b/crates/router/src/types/api/connector_mapping.rs @@ -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()))) } diff --git a/crates/router/src/types/api/feature_matrix.rs b/crates/router/src/types/api/feature_matrix.rs index 6388442b0b..77286011ad 100644 --- a/crates/router/src/types/api/feature_matrix.rs +++ b/crates/router/src/types/api/feature_matrix.rs @@ -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()))) } diff --git a/crates/router/src/types/connector_transformers.rs b/crates/router/src/types/connector_transformers.rs index 05321d29c2..0688b46655 100644 --- a/crates/router/src/types/connector_transformers.rs +++ b/crates/router/src/types/connector_transformers.rs @@ -12,7 +12,7 @@ impl ForeignTryFrom 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, diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index 00784b49c1..5faf7bbafb 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -11,7 +11,8 @@ key1 = "MerchantId" api_secret = "Secondary key" [amazonpay] -api_key="API Key" +api_key="Private Key" +key1="Public Key" [archipel] api_key="CA Certificate PEM" diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 720bdb32a6..1bf84848e4 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -18,7 +18,7 @@ pub struct ConnectorAuthentication { #[cfg(feature = "payouts")] pub adyen_uk: Option, pub airwallex: Option, - pub amazonpay: Option, + pub amazonpay: Option, pub archipel: Option, pub authipay: Option, pub authorizedotnet: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 374119bebb..1d0900def9 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -80,7 +80,7 @@ affirm.base_url = "https://sandbox.affirm.com/api" adyen.payout_base_url = "https://pal-test.adyen.com/" adyen.dispute_base_url = "https://ca-test.adyen.com/" airwallex.base_url = "https://api-demo.airwallex.com/" -amazonpay.base_url = "https://pay-api.amazon.com/v2" +amazonpay.base_url = "https://pay-api.amazon.com/sandbox/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2/" @@ -429,6 +429,9 @@ ideal = { country = "NL", currency = "EUR" } [pm_filters.affirm] affirm = { country = "CA,US", currency = "CAD,USD" } +[pm_filters.amazonpay] +amazon_pay = { country = "US", currency = "USD" } + [pm_filters.bambora] credit = { country = "US,CA", currency = "USD" } debit = { country = "US,CA", currency = "USD" }