diff --git a/Cargo.lock b/Cargo.lock index 4b51d433be..3e1101fdbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3525,7 +3525,7 @@ dependencies = [ [[package]] name = "grpc-api-types" version = "0.1.0" -source = "git+https://github.com/juspay/connector-service?rev=a9f7cd96693fa034ea69d8e21125ea0f76182fae#a9f7cd96693fa034ea69d8e21125ea0f76182fae" +source = "git+https://github.com/juspay/connector-service?rev=0409f6aa1014dd1b9827fabfa4fa424e16d07ebc#0409f6aa1014dd1b9827fabfa4fa424e16d07ebc" dependencies = [ "axum 0.8.4", "error-stack 0.5.0", @@ -6899,7 +6899,7 @@ dependencies = [ [[package]] name = "rust-grpc-client" version = "0.1.0" -source = "git+https://github.com/juspay/connector-service?rev=a9f7cd96693fa034ea69d8e21125ea0f76182fae#a9f7cd96693fa034ea69d8e21125ea0f76182fae" +source = "git+https://github.com/juspay/connector-service?rev=0409f6aa1014dd1b9827fabfa4fa424e16d07ebc#0409f6aa1014dd1b9827fabfa4fa424e16d07ebc" dependencies = [ "grpc-api-types", ] diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index e38d2cfa96..7591fb65fb 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -11734,7 +11734,9 @@ "payone", "paypal", "paystack", + "paytm", "payu", + "phonepe", "placetopay", "powertranz", "prophetpay", @@ -29246,7 +29248,9 @@ "payone", "paypal", "paystack", + "paytm", "payu", + "phonepe", "placetopay", "powertranz", "prophetpay", diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index 8347355c12..6602d2e281 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -8296,7 +8296,9 @@ "payone", "paypal", "paystack", + "paytm", "payu", + "phonepe", "placetopay", "powertranz", "prophetpay", @@ -23099,7 +23101,9 @@ "payone", "paypal", "paystack", + "paytm", "payu", + "phonepe", "placetopay", "powertranz", "prophetpay", diff --git a/config/config.example.toml b/config/config.example.toml index d4c23a6b52..0ca3cbdc5b 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -276,7 +276,9 @@ payme.base_url = "https://sandbox.payme.io/" payone.base_url = "https://payment.preprod.payone.com/" paypal.base_url = "https://api-m.sandbox.paypal.com/" paystack.base_url = "https://api.paystack.co" +paytm.base_url = "https://securegw-stage.paytm.in/" payu.base_url = "https://secure.snd.payu.com/" +phonepe.base_url = "https://api.phonepe.com/apis/hermes/" placetopay.base_url = "https://test.placetopay.com/rest/gateway" plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" @@ -646,6 +648,14 @@ open_banking_uk = { country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,M [pm_filters.razorpay] upi_collect = { country = "IN", currency = "INR" } +[pm_filters.phonepe] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + +[pm_filters.paytm] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + [pm_filters.plaid] open_banking_pis = {currency = "EUR,GBP"} diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 4bad9058da..453dd68044 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -113,7 +113,9 @@ payme.base_url = "https://sandbox.payme.io/" payone.base_url = "https://payment.preprod.payone.com/" paypal.base_url = "https://api-m.sandbox.paypal.com/" paystack.base_url = "https://api.paystack.co" +paytm.base_url = "https://securegw-stage.paytm.in/" payu.base_url = "https://secure.snd.payu.com/" +phonepe.base_url = "https://api.phonepe.com/apis/hermes/" placetopay.base_url = "https://test.placetopay.com/rest/gateway" plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" @@ -556,6 +558,14 @@ open_banking_uk = {country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,MT [pm_filters.razorpay] upi_collect = {country = "IN", currency = "INR"} +[pm_filters.phonepe] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + +[pm_filters.paytm] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + [pm_filters.redsys] credit = { currency = "AUD,BGN,CAD,CHF,COP,CZK,DKK,EUR,GBP,HRK,HUF,ILS,INR,JPY,MYR,NOK,NZD,PEN,PLN,RUB,SAR,SEK,SGD,THB,USD,ZAR", country="ES" } debit = { currency = "AUD,BGN,CAD,CHF,COP,CZK,DKK,EUR,GBP,HRK,HUF,ILS,INR,JPY,MYR,NOK,NZD,PEN,PLN,RUB,SAR,SEK,SGD,THB,USD,ZAR", country="ES" } diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 90fd7d058b..2a78a2d6f7 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -117,7 +117,9 @@ payme.base_url = "https://live.payme.io/" payone.base_url = "https://payment.payone.com/" paypal.base_url = "https://api-m.paypal.com/" paystack.base_url = "https://api.paystack.co" +paytm.base_url = "https://securegw-stage.paytm.in/" payu.base_url = "https://secure.payu.com/api/" +phonepe.base_url = "https://api.phonepe.com/apis/hermes/" placetopay.base_url = "https://checkout.placetopay.com/rest/gateway" plaid.base_url = "https://production.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" @@ -597,6 +599,14 @@ open_banking_uk = {country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,MT [pm_filters.razorpay] upi_collect = {country = "IN", currency = "INR"} +[pm_filters.phonepe] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + +[pm_filters.paytm] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + [pm_filters.redsys] credit = { currency = "AUD,BGN,CAD,CHF,COP,CZK,DKK,EUR,GBP,HRK,HUF,ILS,INR,JPY,MYR,NOK,NZD,PEN,PLN,RUB,SAR,SEK,SGD,THB,USD,ZAR", country="ES" } debit = { currency = "AUD,BGN,CAD,CHF,COP,CZK,DKK,EUR,GBP,HRK,HUF,ILS,INR,JPY,MYR,NOK,NZD,PEN,PLN,RUB,SAR,SEK,SGD,THB,USD,ZAR", country="ES" } diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 00e387cb0b..71926937b9 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -117,7 +117,9 @@ payme.base_url = "https://sandbox.payme.io/" payone.base_url = "https://payment.preprod.payone.com/" paypal.base_url = "https://api-m.sandbox.paypal.com/" paystack.base_url = "https://api.paystack.co" +paytm.base_url = "https://securegw-stage.paytm.in/" payu.base_url = "https://secure.snd.payu.com/" +phonepe.base_url = "https://api.phonepe.com/apis/hermes/" placetopay.base_url = "https://test.placetopay.com/rest/gateway" plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" @@ -605,6 +607,14 @@ open_banking_uk = { country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,M [pm_filters.razorpay] upi_collect = {country = "IN", currency = "INR"} +[pm_filters.phonepe] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + +[pm_filters.paytm] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + [pm_filters.redsys] credit = { currency = "AUD,BGN,CAD,CHF,COP,CZK,DKK,EUR,GBP,HRK,HUF,ILS,INR,JPY,MYR,NOK,NZD,PEN,PLN,RUB,SAR,SEK,SGD,THB,USD,ZAR", country="ES" } debit = { currency = "AUD,BGN,CAD,CHF,COP,CZK,DKK,EUR,GBP,HRK,HUF,ILS,INR,JPY,MYR,NOK,NZD,PEN,PLN,RUB,SAR,SEK,SGD,THB,USD,ZAR", country="ES" } diff --git a/config/development.toml b/config/development.toml index 047afe1179..04472252af 100644 --- a/config/development.toml +++ b/config/development.toml @@ -315,7 +315,9 @@ payme.base_url = "https://sandbox.payme.io/" payone.base_url = "https://payment.preprod.payone.com/" paypal.base_url = "https://api-m.sandbox.paypal.com/" paystack.base_url = "https://api.paystack.co" +paytm.base_url = "https://securegw-stage.paytm.in/" payu.base_url = "https://secure.snd.payu.com/" +phonepe.base_url = "https://api.phonepe.com/apis/hermes/" placetopay.base_url = "https://test.placetopay.com/rest/gateway" plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" @@ -460,6 +462,14 @@ open_banking_uk = { country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,M [pm_filters.razorpay] upi_collect = { country = "IN", currency = "INR" } +[pm_filters.phonepe] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + +[pm_filters.paytm] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + [pm_filters.plaid] open_banking_pis = { currency = "EUR,GBP" } @@ -1235,6 +1245,12 @@ url = "http://localhost:8080" host = "localhost" port = 8000 connection_timeout = 10 +ucs_only_connectors = [ + "razorpay", + "phonepe", + "paytm", + "cashfree", +] [revenue_recovery] monitoring_threshold_in_seconds = 2592000 diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 770bf0fdea..85208bd157 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -202,7 +202,9 @@ payme.base_url = "https://sandbox.payme.io/" payone.base_url = "https://payment.preprod.payone.com/" paypal.base_url = "https://api-m.sandbox.paypal.com/" paystack.base_url = "https://api.paystack.co" +paytm.base_url = "https://securegw-stage.paytm.in/" payu.base_url = "https://secure.snd.payu.com/" +phonepe.base_url = "https://api.phonepe.com/apis/hermes/" placetopay.base_url = "https://test.placetopay.com/rest/gateway" plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" @@ -561,6 +563,14 @@ open_banking_uk = { country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,M [pm_filters.razorpay] upi_collect = { country = "IN", currency = "INR" } +[pm_filters.phonepe] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + +[pm_filters.paytm] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + [pm_filters.payu] debit = { country = "AE, AF, AL, AM, CW, AO, AR, AU, AW, AZ, BA, BB, BG, BH, BI, BM, BN, BO, BR, BS, BW, BY, BZ, CA, CD, LI, CL, CN, CO, CR, CV, CZ, DJ, DK, DO, DZ, EG, ET, AD, FJ, FK, GG, GE, GH, GI, GM, GN, GT, GY, HK, HN, HR, HT, HU, ID, IL, IQ, IS, JM, JO, JP, KG, KH, KM, KR, KW, KY, KZ, LA, LB, LR, LS, MA, MD, MG, MK, MN, MO, MR, MV, MW, MX, MY, MZ, NA, NG, NI, BV, CK, OM, PA, PE, PG, PL, PY, QA, RO, RS, RW, SA, SB, SC, SE, SG, SH, SO, SR, SV, SZ, TH, TJ, TM, TN, TO, TR, TT, TW, TZ, UG, AS, UY, UZ, VE, VN, VU, WS, CM, AI, BJ, PF, YE, ZA, ZM, ZW", currency = "AED, AFN, ALL, AMD, ANG, AOA, ARS, AUD, AWG, AZN, BAM, BBD, BGN, BHD, BIF, BMD, BND, BOB, BRL, BSD, BWP, BYN, BZD, CAD, CDF, CHF, CLP, CNY, COP, CRC, CVE, CZK, DJF, DKK, DOP, DZD, EGP, ETB, EUR, FJD, FKP, GBP, GEL, GHS, GIP, GMD, GNF, GTQ, GYD, HKD, HNL, HRK, HTG, HUF, IDR, ILS, IQD, ISK, JMD, JOD, JPY, KGS, KHR, KMF, KRW, KWD, KYD, KZT, LAK, LBP, LRD, LSL, MAD, MDL, MGA, MKD, MNT, MOP, MRU, MVR, MWK, MXN, MYR, MZN, NAD, NGN, NIO, NOK, NZD, OMR, PAB, PEN, PGK, PLN, PYG, QAR, RON, RSD, RWF, SAR, SBD, SCR, SEK, SGD, SHP, SOS, SRD, SVC, SZL, THB, TJS, TMT, TND, TOP, TRY, TTD, TWD, TZS, UGX, USD, UYU, UZS, VES, VND, VUV, WST, XAF, XCD, XOF, XPF, YER, ZAR, ZMW, ZWL" } credit = { country = "AE, AF, AL, AM, CW, AO, AR, AU, AW, AZ, BA, BB, BG, BH, BI, BM, BN, BO, BR, BS, BW, BY, BZ, CA, CD, LI, CL, CN, CO, CR, CV, CZ, DJ, DK, DO, DZ, EG, ET, AD, FJ, FK, GG, GE, GH, GI, GM, GN, GT, GY, HK, HN, HR, HT, HU, ID, IL, IQ, IS, JM, JO, JP, KG, KH, KM, KR, KW, KY, KZ, LA, LB, LR, LS, MA, MD, MG, MK, MN, MO, MR, MV, MW, MX, MY, MZ, NA, NG, NI, BV, CK, OM, PA, PE, PG, PL, PY, QA, RO, RS, RW, SA, SB, SC, SE, SG, SH, SO, SR, SV, SZ, TH, TJ, TM, TN, TO, TR, TT, TW, TZ, UG, AS, UY, UZ, VE, VN, VU, WS, CM, AI, BJ, PF, YE, ZA, ZM, ZW", currency = "AED, AFN, ALL, AMD, ANG, AOA, ARS, AUD, AWG, AZN, BAM, BBD, BGN, BHD, BIF, BMD, BND, BOB, BRL, BSD, BWP, BYN, BZD, CAD, CDF, CHF, CLP, CNY, COP, CRC, CVE, CZK, DJF, DKK, DOP, DZD, EGP, ETB, EUR, FJD, FKP, GBP, GEL, GHS, GIP, GMD, GNF, GTQ, GYD, HKD, HNL, HRK, HTG, HUF, IDR, ILS, IQD, ISK, JMD, JOD, JPY, KGS, KHR, KMF, KRW, KWD, KYD, KZT, LAK, LBP, LRD, LSL, MAD, MDL, MGA, MKD, MNT, MOP, MRU, MVR, MWK, MXN, MYR, MZN, NAD, NGN, NIO, NOK, NZD, OMR, PAB, PEN, PGK, PLN, PYG, QAR, RON, RSD, RWF, SAR, SBD, SCR, SEK, SGD, SHP, SOS, SRD, SVC, SZL, THB, TJS, TMT, TND, TOP, TRY, TTD, TWD, TZS, UGX, USD, UYU, UZS, VES, VND, VUV, WST, XAF, XCD, XOF, XPF, YER, ZAR, ZMW, ZWL" } diff --git a/config/payment_required_fields_v2.toml b/config/payment_required_fields_v2.toml index fe1e6e050d..35483873f3 100644 --- a/config/payment_required_fields_v2.toml +++ b/config/payment_required_fields_v2.toml @@ -2114,6 +2114,26 @@ common = [ mandate = [] non_mandate = [] +[required_fields.upi.upi_collect.fields.Phonepe] +common = [ + { required_field = "payment_method_data.upi.vpa_id", display_name = "vpa_id", field_type = "text" }, + { required_field = "payment_method_data.billing.email", display_name = "email", field_type = "user_email_address" }, + { required_field = "payment_method_data.billing.phone.number", display_name = "phone", field_type = "user_phone_number" }, + { required_field = "payment_method_data.billing.phone.country_code", display_name = "dialing_code", field_type = "user_phone_number_country_code" } +] +mandate = [] +non_mandate = [] + +[required_fields.upi.upi_collect.fields.Paytm] +common = [ + { required_field = "payment_method_data.upi.vpa_id", display_name = "vpa_id", field_type = "text" }, + { required_field = "payment_method_data.billing.email", display_name = "email", field_type = "user_email_address" }, + { required_field = "payment_method_data.billing.phone.number", display_name = "phone", field_type = "user_phone_number" }, + { required_field = "payment_method_data.billing.phone.country_code", display_name = "dialing_code", field_type = "user_phone_number_country_code" } +] +mandate = [] +non_mandate = [] + # BankDebit # Payment method type: Ach [required_fields.bank_debit.ach.fields.Stripe] diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 8d5b737b8b..c3741e0f5e 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -131,7 +131,9 @@ pub enum RoutableConnectors { Payone, Paypal, Paystack, + Paytm, Payu, + Phonepe, Placetopay, Powertranz, Prophetpay, @@ -301,7 +303,9 @@ pub enum Connector { Payone, Paypal, Paystack, + Paytm, Payu, + Phonepe, Placetopay, Powertranz, Prophetpay, @@ -523,7 +527,9 @@ impl Connector { | Self::Noon | Self::Tokenio | Self::Stripe - | Self::Datatrans => false, + | Self::Datatrans + | Self::Paytm + | Self::Phonepe => false, Self::Checkout | Self::Nmi |Self::Cybersource | Self::Archipel => true, } } @@ -684,6 +690,8 @@ impl From for Connector { RoutableConnectors::Inespay => Self::Inespay, RoutableConnectors::Coingate => Self::Coingate, RoutableConnectors::Hipay => Self::Hipay, + RoutableConnectors::Paytm => Self::Paytm, + RoutableConnectors::Phonepe => Self::Phonepe, } } } @@ -810,6 +818,8 @@ impl TryFrom for RoutableConnectors { Connector::Hipay => Ok(Self::Hipay), Connector::Inespay => Ok(Self::Inespay), Connector::Redsys => Ok(Self::Redsys), + Connector::Paytm => Ok(Self::Paytm), + Connector::Phonepe => Ok(Self::Phonepe), Connector::CtpMastercard | Connector::Gpayments | Connector::HyperswitchVault diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index f0dc8dbf62..84e7398f61 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -257,7 +257,9 @@ pub struct ConnectorConfig { #[cfg(feature = "payouts")] pub paypal_payout: Option, pub paystack: Option, + pub paytm: Option, pub payu: Option, + pub phonepe: Option, pub placetopay: Option, pub plaid: Option, pub powertranz: Option, @@ -499,6 +501,8 @@ impl ConnectorConfig { Connector::Netcetera => Ok(connector_data.netcetera), Connector::CtpMastercard => Ok(connector_data.ctp_mastercard), Connector::Xendit => Ok(connector_data.xendit), + Connector::Paytm => Ok(connector_data.paytm), + Connector::Phonepe => Ok(connector_data.phonepe), } } } diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 9943475119..238e336c3c 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -6463,6 +6463,25 @@ payment_experience = "redirect_to_url" [mpgs.connector_auth.HeaderKey] api_key = "API Key" +[phonepe] +[[phonepe.upi]] +payment_method_type = "upi_collect" +[[phonepe.upi]] +payment_method_type = "upi_intent" +[phonepe.connector_auth.SignatureKey] +api_key="merchant_id" +api_secret="key_index" +key1="salt_key" + +[paytm] +[[paytm.upi]] +payment_method_type = "upi_collect" +[[paytm.upi]] +payment_method_type = "upi_intent" +[paytm.connector_auth.SignatureKey] +api_key="Signing key" +api_secret="website name" +key1="merchant_id" [bluecode] [bluecode.connector_auth.HeaderKey] api_key = "API Key" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index d4cb52daed..509ea1608a 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -5080,6 +5080,26 @@ key1 = "API Secret" payment_method_type = "breadpay" payment_experience = "redirect_to_url" +[phonepe] +[[phonepe.upi]] +payment_method_type = "upi_collect" +[[phonepe.upi]] +payment_method_type = "upi_intent" +[phonepe.connector_auth.SignatureKey] +api_key="merchant_id" +api_secret="key_index" +key1="salt_key" + +[paytm] +[[paytm.upi]] +payment_method_type = "upi_collect" +[[paytm.upi]] +payment_method_type = "upi_intent" +[paytm.connector_auth.SignatureKey] +api_key="Signing key" +api_secret="website name" +key1="merchant_id" + [mpgs] [mpgs.connector_auth.HeaderKey] api_key = "API Key" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 6ef9c0be1a..ff6a076ae3 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -6444,6 +6444,25 @@ payment_experience = "redirect_to_url" [mpgs.connector_auth.HeaderKey] api_key = "API Key" +[phonepe] +[[phonepe.upi]] +payment_method_type = "upi_collect" +[[phonepe.upi]] +payment_method_type = "upi_intent" +[phonepe.connector_auth.SignatureKey] +api_key="merchant_id" +api_secret="key_index" +key1="salt_key" + +[paytm] +[[paytm.upi]] +payment_method_type = "upi_collect" +[[paytm.upi]] +payment_method_type = "upi_intent" +[paytm.connector_auth.SignatureKey] +api_key="Signing key" +api_secret="website name" +key1="merchant_id" [bluecode] [bluecode.connector_auth.HeaderKey] api_key = "API Key" diff --git a/crates/external_services/Cargo.toml b/crates/external_services/Cargo.toml index d7fa589752..bfa0a8b613 100644 --- a/crates/external_services/Cargo.toml +++ b/crates/external_services/Cargo.toml @@ -53,7 +53,7 @@ reqwest = { version = "0.11.27", features = ["rustls-tls"] } http = "0.2.12" url = { version = "2.5.4", features = ["serde"] } quick-xml = { version = "0.31.0", features = ["serialize"] } -unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "a9f7cd96693fa034ea69d8e21125ea0f76182fae", package = "rust-grpc-client" } +unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "0409f6aa1014dd1b9827fabfa4fa424e16d07ebc", package = "rust-grpc-client" } # First party crates diff --git a/crates/external_services/src/grpc_client/unified_connector_service.rs b/crates/external_services/src/grpc_client/unified_connector_service.rs index bac66a3bb5..7ca74cdda3 100644 --- a/crates/external_services/src/grpc_client/unified_connector_service.rs +++ b/crates/external_services/src/grpc_client/unified_connector_service.rs @@ -118,6 +118,10 @@ pub struct UnifiedConnectorServiceClientConfig { /// Contains the connection timeout duration in seconds pub connection_timeout: u64, + + /// List of connectors to use with the unified connector service + #[serde(default)] + pub ucs_only_connectors: Vec, } /// Contains the Connector Auth Type and related authentication data. diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index acf048d45d..e2b648e75e 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -83,7 +83,9 @@ pub mod payme; pub mod payone; pub mod paypal; pub mod paystack; +pub mod paytm; pub mod payu; +pub mod phonepe; pub mod placetopay; pub mod plaid; pub mod powertranz; @@ -143,13 +145,13 @@ pub use self::{ multisafepay::Multisafepay, netcetera::Netcetera, nexinets::Nexinets, nexixpay::Nexixpay, nmi::Nmi, nomupay::Nomupay, noon::Noon, nordea::Nordea, novalnet::Novalnet, nuvei::Nuvei, opayo::Opayo, opennode::Opennode, paybox::Paybox, payeezy::Payeezy, payload::Payload, - payme::Payme, payone::Payone, paypal::Paypal, paystack::Paystack, payu::Payu, - placetopay::Placetopay, plaid::Plaid, powertranz::Powertranz, prophetpay::Prophetpay, - rapyd::Rapyd, razorpay::Razorpay, recurly::Recurly, redsys::Redsys, riskified::Riskified, - santander::Santander, shift4::Shift4, signifyd::Signifyd, silverflow::Silverflow, - square::Square, stax::Stax, stripe::Stripe, stripebilling::Stripebilling, taxjar::Taxjar, - threedsecureio::Threedsecureio, thunes::Thunes, tokenio::Tokenio, trustpay::Trustpay, - trustpayments::Trustpayments, tsys::Tsys, + payme::Payme, payone::Payone, paypal::Paypal, paystack::Paystack, paytm::Paytm, payu::Payu, + phonepe::Phonepe, placetopay::Placetopay, plaid::Plaid, powertranz::Powertranz, + prophetpay::Prophetpay, rapyd::Rapyd, razorpay::Razorpay, recurly::Recurly, redsys::Redsys, + riskified::Riskified, santander::Santander, shift4::Shift4, signifyd::Signifyd, + silverflow::Silverflow, square::Square, stax::Stax, stripe::Stripe, + stripebilling::Stripebilling, taxjar::Taxjar, threedsecureio::Threedsecureio, thunes::Thunes, + tokenio::Tokenio, trustpay::Trustpay, trustpayments::Trustpayments, tsys::Tsys, unified_authentication_service::UnifiedAuthenticationService, vgs::Vgs, volt::Volt, wellsfargo::Wellsfargo, wellsfargopayout::Wellsfargopayout, wise::Wise, worldline::Worldline, worldpay::Worldpay, worldpayvantiv::Worldpayvantiv, worldpayxml::Worldpayxml, xendit::Xendit, diff --git a/crates/hyperswitch_connectors/src/connectors/paytm.rs b/crates/hyperswitch_connectors/src/connectors/paytm.rs new file mode 100644 index 0000000000..4a72fa8857 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/paytm.rs @@ -0,0 +1,614 @@ +pub mod transformers; + +use std::sync::LazyLock; + +use common_enums::enums; +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{ + ConnectorInfo, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods, + }, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use transformers as paytm; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Paytm { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Paytm { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Paytm {} +impl api::PaymentSession for Paytm {} +impl api::ConnectorAccessToken for Paytm {} +impl api::MandateSetup for Paytm {} +impl api::PaymentAuthorize for Paytm {} +impl api::PaymentSync for Paytm {} +impl api::PaymentCapture for Paytm {} +impl api::PaymentVoid for Paytm {} +impl api::Refund for Paytm {} +impl api::RefundExecute for Paytm {} +impl api::RefundSync for Paytm {} +impl api::PaymentToken for Paytm {} + +impl ConnectorIntegration + for Paytm +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Paytm +where + Self: ConnectorIntegration, +{ + 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); + Ok(header) + } +} + +impl ConnectorCommon for Paytm { + fn id(&self) -> &'static str { + "paytm" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.paytm.base_url.as_ref() + } + + fn get_auth_header( + &self, + _auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + // This method is not implemented for Paytm, as it will always call the UCS service which has the logic to create headers. + Err(errors::ConnectorError::NotImplemented("get_auth_header method".to_string()).into()) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: paytm::PaytmErrorResponse = + res.response + .parse_struct("PaytmErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }) + } +} + +impl ConnectorValidation for Paytm { + fn validate_mandate_payment( + &self, + _pm_type: Option, + pm_data: PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + match pm_data { + PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented( + "validate_mandate_payment does not support cards".to_string(), + ) + .into()), + _ => Ok(()), + } + } + + fn validate_psync_reference_id( + &self, + _data: &PaymentsSyncData, + _is_three_ds: bool, + _status: enums::AttemptStatus, + _connector_meta_data: Option, + ) -> CustomResult<(), errors::ConnectorError> { + Ok(()) + } +} + +impl ConnectorIntegration for Paytm { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Paytm {} + +impl ConnectorIntegration for Paytm {} + +impl ConnectorIntegration for Paytm { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + 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: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = paytm::PaytmRouterData::from((amount, req)); + let connector_req = paytm::PaytmPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: paytm::PaytmPaymentsResponse = res + .response + .parse_struct("Paytm PaymentsAuthorizeResponse") + .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) + } +} + +impl ConnectorIntegration for Paytm { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + 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: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: paytm::PaytmPaymentsResponse = res + .response + .parse_struct("paytm PaymentsSyncResponse") + .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) + } +} + +impl ConnectorIntegration for Paytm { + 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, + ) -> 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: paytm::PaytmPaymentsResponse = res + .response + .parse_struct("Paytm 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) + } +} + +impl ConnectorIntegration for Paytm {} + +impl ConnectorIntegration for Paytm { + fn get_headers( + &self, + req: &RefundsRouterData, + 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: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = paytm::PaytmRouterData::from((refund_amount, req)); + let connector_req = paytm::PaytmRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: paytm::RefundResponse = res + .response + .parse_struct("paytm RefundResponse") + .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) + } +} + +impl ConnectorIntegration for Paytm { + fn get_headers( + &self, + req: &RefundSyncRouterData, + 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: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: paytm::RefundResponse = res + .response + .parse_struct("paytm RefundSyncResponse") + .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) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Paytm { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +static PAYTM_SUPPORTED_PAYMENT_METHODS: LazyLock = + LazyLock::new(SupportedPaymentMethods::new); + +static PAYTM_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + display_name: "Paytm", + description: "Paytm connector", + connector_type: enums::PaymentConnectorCategory::PaymentGateway, +}; + +static PAYTM_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = []; + +impl ConnectorSpecifications for Paytm { + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + Some(&PAYTM_CONNECTOR_INFO) + } + + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + Some(&*PAYTM_SUPPORTED_PAYMENT_METHODS) + } + + fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { + Some(&PAYTM_SUPPORTED_WEBHOOK_FLOWS) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/paytm/transformers.rs b/crates/hyperswitch_connectors/src/connectors/paytm/transformers.rs new file mode 100644 index 0000000000..67fe017e60 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/paytm/transformers.rs @@ -0,0 +1,225 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::types::{RefundsResponseRouterData, ResponseRouterData}; + +//TODO: Fill the struct with respective fields +pub struct PaytmRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for PaytmRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct PaytmPaymentsRequest { + amount: StringMinorUnit, + card: PaytmCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct PaytmCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&PaytmRouterData<&PaymentsAuthorizeRouterData>> for PaytmPaymentsRequest { + type Error = error_stack::Report; + fn try_from(item: &PaytmRouterData<&PaymentsAuthorizeRouterData>) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented( + "Card payment method not implemented".to_string(), + ) + .into()), + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct PaytmAuthType { + pub merchant_id: Secret, + pub merchant_key: Secret, + pub website: Secret, +} + +impl TryFrom<&ConnectorAuthType> for PaytmAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + merchant_id: key1.to_owned(), // merchant_id + merchant_key: api_key.to_owned(), // signing key + website: api_secret.to_owned(), // website name + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum PaytmPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: PaytmPaymentStatus) -> Self { + match item { + PaytmPaymentStatus::Succeeded => Self::Charged, + PaytmPaymentStatus::Failed => Self::Failure, + PaytmPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct PaytmPaymentsResponse { + status: PaytmPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> 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 + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct PaytmRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&PaytmRouterData<&RefundsRouterData>> for PaytmRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &PaytmRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +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 + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + 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 + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + 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 + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct PaytmErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, + pub network_advice_code: Option, + pub network_decline_code: Option, + pub network_error_message: Option, +} diff --git a/crates/hyperswitch_connectors/src/connectors/phonepe.rs b/crates/hyperswitch_connectors/src/connectors/phonepe.rs new file mode 100644 index 0000000000..43ea9538fd --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/phonepe.rs @@ -0,0 +1,614 @@ +pub mod transformers; + +use std::sync::LazyLock; + +use common_enums::enums; +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{ + ConnectorInfo, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods, + }, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use transformers as phonepe; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Phonepe { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Phonepe { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Phonepe {} +impl api::PaymentSession for Phonepe {} +impl api::ConnectorAccessToken for Phonepe {} +impl api::MandateSetup for Phonepe {} +impl api::PaymentAuthorize for Phonepe {} +impl api::PaymentSync for Phonepe {} +impl api::PaymentCapture for Phonepe {} +impl api::PaymentVoid for Phonepe {} +impl api::Refund for Phonepe {} +impl api::RefundExecute for Phonepe {} +impl api::RefundSync for Phonepe {} +impl api::PaymentToken for Phonepe {} + +impl ConnectorIntegration + for Phonepe +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Phonepe +where + Self: ConnectorIntegration, +{ + 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); + Ok(header) + } +} + +impl ConnectorCommon for Phonepe { + fn id(&self) -> &'static str { + "phonepe" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.phonepe.base_url.as_ref() + } + + fn get_auth_header( + &self, + _auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + // This method is not implemented for Phonepe, as it will always call the UCS service which has the logic to create headers. + Err(errors::ConnectorError::NotImplemented("get_auth_header method".to_string()).into()) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: phonepe::PhonepeErrorResponse = res + .response + .parse_struct("PhonepeErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }) + } +} + +impl ConnectorValidation for Phonepe { + fn validate_mandate_payment( + &self, + _pm_type: Option, + pm_data: PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + match pm_data { + PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented( + "validate_mandate_payment does not support cards".to_string(), + ) + .into()), + _ => Ok(()), + } + } + + fn validate_psync_reference_id( + &self, + _data: &PaymentsSyncData, + _is_three_ds: bool, + _status: enums::AttemptStatus, + _connector_meta_data: Option, + ) -> CustomResult<(), errors::ConnectorError> { + Ok(()) + } +} + +impl ConnectorIntegration for Phonepe { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Phonepe {} + +impl ConnectorIntegration for Phonepe {} + +impl ConnectorIntegration for Phonepe { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + 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: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = phonepe::PhonepeRouterData::from((amount, req)); + let connector_req = phonepe::PhonepePaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: phonepe::PhonepePaymentsResponse = res + .response + .parse_struct("Phonepe PaymentsAuthorizeResponse") + .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) + } +} + +impl ConnectorIntegration for Phonepe { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + 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: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: phonepe::PhonepePaymentsResponse = res + .response + .parse_struct("phonepe PaymentsSyncResponse") + .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) + } +} + +impl ConnectorIntegration for Phonepe { + 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, + ) -> 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: phonepe::PhonepePaymentsResponse = res + .response + .parse_struct("Phonepe 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) + } +} + +impl ConnectorIntegration for Phonepe {} + +impl ConnectorIntegration for Phonepe { + fn get_headers( + &self, + req: &RefundsRouterData, + 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: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = phonepe::PhonepeRouterData::from((refund_amount, req)); + let connector_req = phonepe::PhonepeRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: phonepe::RefundResponse = res + .response + .parse_struct("phonepe RefundResponse") + .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) + } +} + +impl ConnectorIntegration for Phonepe { + fn get_headers( + &self, + req: &RefundSyncRouterData, + 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: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: phonepe::RefundResponse = res + .response + .parse_struct("phonepe RefundSyncResponse") + .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) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Phonepe { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +static PHONEPE_SUPPORTED_PAYMENT_METHODS: LazyLock = + LazyLock::new(SupportedPaymentMethods::new); + +static PHONEPE_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + display_name: "Phonepe", + description: "Phonepe connector", + connector_type: enums::PaymentConnectorCategory::PaymentGateway, +}; + +static PHONEPE_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = []; + +impl ConnectorSpecifications for Phonepe { + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + Some(&PHONEPE_CONNECTOR_INFO) + } + + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + Some(&*PHONEPE_SUPPORTED_PAYMENT_METHODS) + } + + fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { + Some(&PHONEPE_SUPPORTED_WEBHOOK_FLOWS) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/phonepe/transformers.rs b/crates/hyperswitch_connectors/src/connectors/phonepe/transformers.rs new file mode 100644 index 0000000000..9741218004 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/phonepe/transformers.rs @@ -0,0 +1,227 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::{PeekInterface, Secret}; +use serde::{Deserialize, Serialize}; + +use crate::types::{RefundsResponseRouterData, ResponseRouterData}; + +//TODO: Fill the struct with respective fields +pub struct PhonepeRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for PhonepeRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct PhonepePaymentsRequest { + amount: StringMinorUnit, + card: PhonepeCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct PhonepeCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&PhonepeRouterData<&PaymentsAuthorizeRouterData>> for PhonepePaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &PhonepeRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented( + "Card payment method not implemented".to_string(), + ) + .into()), + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct PhonepeAuthType { + pub merchant_id: Secret, + pub salt_key: Secret, + pub key_index: String, +} + +impl TryFrom<&ConnectorAuthType> for PhonepeAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + merchant_id: api_key.clone(), + salt_key: key1.clone(), + key_index: api_secret.peek().clone(), // Use api_secret for key index + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum PhonepePaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: PhonepePaymentStatus) -> Self { + match item { + PhonepePaymentStatus::Succeeded => Self::Charged, + PhonepePaymentStatus::Failed => Self::Failure, + PhonepePaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct PhonepePaymentsResponse { + status: PhonepePaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> 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 + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct PhonepeRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&PhonepeRouterData<&RefundsRouterData>> for PhonepeRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &PhonepeRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +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 + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + 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 + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + 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 + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct PhonepeErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, + pub network_advice_code: Option, + pub network_decline_code: Option, + pub network_error_message: Option, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 05fe9d06ad..67821e66d7 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -218,7 +218,9 @@ default_imp_for_authorize_session_token!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -360,7 +362,9 @@ default_imp_for_calculate_tax!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -502,7 +506,9 @@ default_imp_for_session_update!( connectors::Payme, connectors::Payone, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::UnifiedAuthenticationService, @@ -639,7 +645,9 @@ default_imp_for_post_session_tokens!( connectors::Payme, connectors::Payone, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Fiuu, @@ -775,7 +783,9 @@ default_imp_for_create_order!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Fiuu, @@ -910,7 +920,9 @@ default_imp_for_update_metadata!( connectors::Payload, connectors::Payme, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Payone, @@ -1031,7 +1043,9 @@ default_imp_for_complete_authorize!( connectors::Payload, connectors::Payone, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Rapyd, @@ -1156,7 +1170,9 @@ default_imp_for_incremental_authorization!( connectors::Payme, connectors::Payone, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1296,7 +1312,9 @@ default_imp_for_create_customer!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1417,8 +1435,10 @@ default_imp_for_connector_redirect_response!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payone, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1538,8 +1558,10 @@ default_imp_for_pre_processing_steps!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payone, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1672,7 +1694,9 @@ default_imp_for_post_processing_steps!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, @@ -1809,8 +1833,10 @@ default_imp_for_approve!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payone, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1949,7 +1975,9 @@ default_imp_for_reject!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2087,7 +2115,9 @@ default_imp_for_webhook_source_verification!( connectors::Payload, connectors::Payme, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2225,7 +2255,9 @@ default_imp_for_accept_dispute!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2362,7 +2394,9 @@ default_imp_for_submit_evidence!( connectors::Paypal, connectors::Payone, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2498,7 +2532,9 @@ default_imp_for_defend_dispute!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2644,7 +2680,9 @@ default_imp_for_file_upload!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2773,7 +2811,9 @@ default_imp_for_payouts!( connectors::Payload, connectors::Payme, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2902,7 +2942,9 @@ default_imp_for_payouts_create!( connectors::Payme, connectors::Payone, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3038,7 +3080,9 @@ default_imp_for_payouts_retrieve!( connectors::Payload, connectors::Payme, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Payone, connectors::Placetopay, connectors::Plaid, @@ -3176,8 +3220,10 @@ default_imp_for_payouts_eligibility!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payone, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3310,7 +3356,9 @@ default_imp_for_payouts_fulfill!( connectors::Payload, connectors::Payme, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3445,8 +3493,10 @@ default_imp_for_payouts_cancel!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payone, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3583,7 +3633,9 @@ default_imp_for_payouts_quote!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3721,7 +3773,9 @@ default_imp_for_payouts_recipient!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3859,7 +3913,9 @@ default_imp_for_payouts_recipient_account!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3999,7 +4055,9 @@ default_imp_for_frm_sale!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -4138,7 +4196,9 @@ default_imp_for_frm_checkout!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -4277,7 +4337,9 @@ default_imp_for_frm_transaction!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -4416,7 +4478,9 @@ default_imp_for_frm_fulfillment!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -4555,7 +4619,9 @@ default_imp_for_frm_record_return!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -4688,7 +4754,9 @@ default_imp_for_revoking_mandates!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -4824,7 +4892,9 @@ default_imp_for_uas_pre_authentication!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Powertranz, connectors::Prophetpay, connectors::Mifinity, @@ -4960,7 +5030,9 @@ default_imp_for_uas_post_authentication!( connectors::Payone, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Powertranz, connectors::Prophetpay, connectors::Plaid, @@ -5096,7 +5168,9 @@ default_imp_for_uas_authentication_confirmation!( connectors::Payme, connectors::Payone, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Powertranz, connectors::Prophetpay, connectors::Mifinity, @@ -5222,7 +5296,9 @@ default_imp_for_connector_request_id!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Payme, connectors::Payone, connectors::Paypal, @@ -5354,8 +5430,10 @@ default_imp_for_fraud_check!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Paypal, connectors::Payu, + connectors::Phonepe, connectors::Powertranz, connectors::Prophetpay, connectors::Mifinity, @@ -5514,7 +5592,9 @@ default_imp_for_connector_authentication!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Payone, connectors::Powertranz, connectors::Prophetpay, @@ -5647,7 +5727,9 @@ default_imp_for_uas_authentication!( connectors::Payload, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Powertranz, connectors::Prophetpay, connectors::Mifinity, @@ -5779,7 +5861,9 @@ default_imp_for_revenue_recovery!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Paypal, connectors::Powertranz, connectors::Prophetpay, @@ -5919,7 +6003,9 @@ default_imp_for_billing_connector_payment_sync!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Paypal, connectors::Powertranz, connectors::Prophetpay, @@ -6057,7 +6143,9 @@ default_imp_for_revenue_recovery_record_back!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Paypal, connectors::Powertranz, connectors::Prophetpay, @@ -6195,7 +6283,9 @@ default_imp_for_billing_connector_invoice_sync!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Paypal, connectors::Powertranz, connectors::Prophetpay, @@ -6327,7 +6417,9 @@ default_imp_for_external_vault!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Paypal, connectors::Plaid, connectors::Powertranz, @@ -6465,7 +6557,9 @@ default_imp_for_external_vault_insert!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Paypal, connectors::Plaid, connectors::Powertranz, @@ -6603,7 +6697,9 @@ default_imp_for_external_vault_retrieve!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Paypal, connectors::Plaid, connectors::Powertranz, @@ -6741,7 +6837,9 @@ default_imp_for_external_vault_delete!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Paypal, connectors::Plaid, connectors::Powertranz, @@ -6878,7 +6976,9 @@ default_imp_for_external_vault_create!( connectors::Payeezy, connectors::Payload, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Paypal, connectors::Plaid, connectors::Powertranz, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 754d3f72f5..72cb5a3cf3 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -325,7 +325,9 @@ default_imp_for_new_connector_integration_payment!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -464,7 +466,9 @@ default_imp_for_new_connector_integration_refund!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -598,7 +602,9 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -737,7 +743,9 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -875,7 +883,9 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1014,7 +1024,9 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1163,7 +1175,9 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1304,7 +1318,9 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1445,7 +1461,9 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1586,7 +1604,9 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1727,7 +1747,9 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -1868,7 +1890,9 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2009,7 +2033,9 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2150,7 +2176,9 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2291,7 +2319,9 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2430,7 +2460,9 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2571,7 +2603,9 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2712,7 +2746,9 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2853,7 +2889,9 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -2994,7 +3032,9 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3135,7 +3175,9 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3272,7 +3314,9 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Payload, connectors::Payme, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3329,6 +3373,7 @@ macro_rules! default_imp_for_new_connector_integration_frm { default_imp_for_new_connector_integration_frm!( connectors::Trustpayments, connectors::Affirm, + connectors::Paytm, connectors::Vgs, connectors::Airwallex, connectors::Amazonpay, @@ -3386,6 +3431,7 @@ default_imp_for_new_connector_integration_frm!( connectors::Payeezy, connectors::Payload, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3467,6 +3513,7 @@ macro_rules! default_imp_for_new_connector_integration_connector_authentication default_imp_for_new_connector_integration_connector_authentication!( connectors::Trustpayments, connectors::Affirm, + connectors::Paytm, connectors::Vgs, connectors::Airwallex, connectors::Amazonpay, @@ -3522,6 +3569,7 @@ default_imp_for_new_connector_integration_connector_authentication!( connectors::Payeezy, connectors::Payload, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3594,6 +3642,7 @@ macro_rules! default_imp_for_new_connector_integration_revenue_recovery { default_imp_for_new_connector_integration_revenue_recovery!( connectors::Trustpayments, connectors::Affirm, + connectors::Paytm, connectors::Vgs, connectors::Airwallex, connectors::Amazonpay, @@ -3651,6 +3700,7 @@ default_imp_for_new_connector_integration_revenue_recovery!( connectors::Payeezy, connectors::Payload, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Plaid, connectors::Powertranz, @@ -3807,7 +3857,9 @@ default_imp_for_new_connector_integration_external_vault!( connectors::Payme, connectors::Paypal, connectors::Paystack, + connectors::Paytm, connectors::Payu, + connectors::Phonepe, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, diff --git a/crates/hyperswitch_domain_models/src/connector_endpoints.rs b/crates/hyperswitch_domain_models/src/connector_endpoints.rs index 779ca1e906..ad80bdb9ea 100644 --- a/crates/hyperswitch_domain_models/src/connector_endpoints.rs +++ b/crates/hyperswitch_domain_models/src/connector_endpoints.rs @@ -98,7 +98,9 @@ pub struct Connectors { pub payone: ConnectorParams, pub paypal: ConnectorParams, pub paystack: ConnectorParams, + pub paytm: ConnectorParams, pub payu: ConnectorParams, + pub phonepe: ConnectorParams, pub placetopay: ConnectorParams, pub plaid: ConnectorParams, pub powertranz: ConnectorParams, 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 801b02c920..469b27baf8 100644 --- a/crates/payment_methods/src/configs/payment_connector_required_fields.rs +++ b/crates/payment_methods/src/configs/payment_connector_required_fields.rs @@ -1071,19 +1071,47 @@ impl Default for RequiredFields { enums::PaymentMethod::Upi, PaymentMethodType(HashMap::from([( enums::PaymentMethodType::UpiCollect, - connectors(vec![( - Connector::Razorpay, - fields( - vec![], - vec![], - vec![ - RequiredField::UpiCollectVpaId, - RequiredField::BillingEmail, - RequiredField::BillingPhone, - RequiredField::BillingPhoneCountryCode, - ], + connectors(vec![ + ( + Connector::Razorpay, + fields( + vec![], + vec![], + vec![ + RequiredField::UpiCollectVpaId, + RequiredField::BillingEmail, + RequiredField::BillingPhone, + RequiredField::BillingPhoneCountryCode, + ], + ), ), - )]), + ( + Connector::Phonepe, + fields( + vec![], + vec![], + vec![ + RequiredField::UpiCollectVpaId, + RequiredField::BillingEmail, + RequiredField::BillingPhone, + RequiredField::BillingPhoneCountryCode, + ], + ), + ), + ( + Connector::Paytm, + fields( + vec![], + vec![], + vec![ + RequiredField::UpiCollectVpaId, + RequiredField::BillingEmail, + RequiredField::BillingPhone, + RequiredField::BillingPhoneCountryCode, + ], + ), + ), + ]), )])), ), ( diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 9399c0d3b3..6f70c428d8 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -88,7 +88,7 @@ reqwest = { version = "0.11.27", features = ["json", "rustls-tls", "gzip", "mult ring = "0.17.14" rust_decimal = { version = "1.37.1", features = ["serde-with-float", "serde-with-str"] } rust-i18n = { git = "https://github.com/kashif-m/rust-i18n", rev = "f2d8096aaaff7a87a847c35a5394c269f75e077a" } -unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "a9f7cd96693fa034ea69d8e21125ea0f76182fae", package = "rust-grpc-client" } +unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "0409f6aa1014dd1b9827fabfa4fa424e16d07ebc", package = "rust-grpc-client" } rustc-hash = "1.1.0" rustls = "0.22" rustls-pemfile = "2" diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index 2e7303ba93..a54c1e4840 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -32,18 +32,18 @@ pub use hyperswitch_connectors::connectors::{ novalnet::Novalnet, nuvei, nuvei::Nuvei, opayo, opayo::Opayo, opennode, opennode::Opennode, paybox, paybox::Paybox, payeezy, payeezy::Payeezy, payload, payload::Payload, payme, payme::Payme, payone, payone::Payone, paypal, paypal::Paypal, paystack, paystack::Paystack, - payu, payu::Payu, placetopay, placetopay::Placetopay, plaid, plaid::Plaid, powertranz, - powertranz::Powertranz, prophetpay, prophetpay::Prophetpay, rapyd, rapyd::Rapyd, razorpay, - razorpay::Razorpay, recurly, recurly::Recurly, redsys, redsys::Redsys, riskified, - riskified::Riskified, santander, santander::Santander, shift4, shift4::Shift4, signifyd, - signifyd::Signifyd, silverflow, silverflow::Silverflow, square, square::Square, stax, - stax::Stax, stripe, stripe::Stripe, stripebilling, stripebilling::Stripebilling, taxjar, - taxjar::Taxjar, threedsecureio, threedsecureio::Threedsecureio, thunes, thunes::Thunes, - tokenio, tokenio::Tokenio, trustpay, trustpay::Trustpay, trustpayments, - trustpayments::Trustpayments, tsys, tsys::Tsys, unified_authentication_service, - unified_authentication_service::UnifiedAuthenticationService, vgs, vgs::Vgs, volt, volt::Volt, - wellsfargo, wellsfargo::Wellsfargo, wellsfargopayout, wellsfargopayout::Wellsfargopayout, wise, - wise::Wise, worldline, worldline::Worldline, worldpay, worldpay::Worldpay, worldpayvantiv, - worldpayvantiv::Worldpayvantiv, worldpayxml, worldpayxml::Worldpayxml, xendit, xendit::Xendit, - zen, zen::Zen, zsl, zsl::Zsl, + paytm, paytm::Paytm, payu, payu::Payu, phonepe, phonepe::Phonepe, placetopay, + placetopay::Placetopay, plaid, plaid::Plaid, powertranz, powertranz::Powertranz, prophetpay, + prophetpay::Prophetpay, rapyd, rapyd::Rapyd, razorpay, razorpay::Razorpay, recurly, + recurly::Recurly, redsys, redsys::Redsys, riskified, riskified::Riskified, santander, + santander::Santander, shift4, shift4::Shift4, signifyd, signifyd::Signifyd, silverflow, + silverflow::Silverflow, square, square::Square, stax, stax::Stax, stripe, stripe::Stripe, + stripebilling, stripebilling::Stripebilling, taxjar, taxjar::Taxjar, threedsecureio, + threedsecureio::Threedsecureio, thunes, thunes::Thunes, tokenio, tokenio::Tokenio, trustpay, + trustpay::Trustpay, trustpayments, trustpayments::Trustpayments, tsys, tsys::Tsys, + unified_authentication_service, unified_authentication_service::UnifiedAuthenticationService, + vgs, vgs::Vgs, volt, volt::Volt, wellsfargo, wellsfargo::Wellsfargo, wellsfargopayout, + wellsfargopayout::Wellsfargopayout, wise, wise::Wise, worldline, worldline::Worldline, + worldpay, worldpay::Worldpay, worldpayvantiv, worldpayvantiv::Worldpayvantiv, worldpayxml, + worldpayxml::Worldpayxml, xendit, xendit::Xendit, zen, zen::Zen, zsl, zsl::Zsl, }; diff --git a/crates/router/src/core/connector_validation.rs b/crates/router/src/core/connector_validation.rs index ccf9ee5e9d..99b873af1d 100644 --- a/crates/router/src/core/connector_validation.rs +++ b/crates/router/src/core/connector_validation.rs @@ -522,6 +522,14 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { threedsecureio::transformers::ThreedsecureioAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Phonepe => { + phonepe::transformers::PhonepeAuthType::try_from(self.auth_type)?; + Ok(()) + } + api_enums::Connector::Paytm => { + paytm::transformers::PaytmAuthType::try_from(self.auth_type)?; + Ok(()) + } } } } diff --git a/crates/router/src/core/unified_connector_service.rs b/crates/router/src/core/unified_connector_service.rs index 7d19975052..79dd1cc701 100644 --- a/crates/router/src/core/unified_connector_service.rs +++ b/crates/router/src/core/unified_connector_service.rs @@ -57,6 +57,16 @@ pub async fn should_call_unified_connector_service( let payment_method = router_data.payment_method.to_string(); let flow_name = get_flow_name::()?; + let is_ucs_only_connector = state + .conf + .grpc_client + .unified_connector_service + .as_ref() + .is_some_and(|config| config.ucs_only_connectors.contains(&connector_name)); + + if is_ucs_only_connector { + return Ok(true); + } let config_key = format!( "{}_{}_{}_{}_{}", consts::UCS_ROLLOUT_PERCENT_CONFIG_PREFIX, @@ -135,11 +145,9 @@ pub fn build_unified_connector_service_payment_method( let upi_details = payments_grpc::UpiCollect { vpa_id }; PaymentMethod::UpiCollect(upi_details) } - _ => { - return Err(UnifiedConnectorServiceError::NotImplemented(format!( - "Unimplemented payment method subtype: {payment_method_type:?}" - )) - .into()); + hyperswitch_domain_models::payment_method_data::UpiData::UpiIntent(_) => { + let upi_details = payments_grpc::UpiIntent {}; + PaymentMethod::UpiIntent(upi_details) } }; @@ -238,6 +246,112 @@ pub fn handle_unified_connector_service_response_for_payment_authorize( > { let status = AttemptStatus::foreign_try_from(response.status())?; + // <<<<<<< HEAD + // let connector_response_reference_id = + // response.response_ref_id.as_ref().and_then(|identifier| { + // identifier + // .id_type + // .clone() + // .and_then(|id_type| match id_type { + // payments_grpc::identifier::IdType::Id(id) => Some(id), + // payments_grpc::identifier::IdType::EncodedData(encoded_data) => { + // Some(encoded_data) + // } + // payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, + // }) + // }); + + // let transaction_id = response.transaction_id.as_ref().and_then(|id| { + // id.id_type.clone().and_then(|id_type| match id_type { + // payments_grpc::identifier::IdType::Id(id) => Some(id), + // payments_grpc::identifier::IdType::EncodedData(encoded_data) => Some(encoded_data), + // payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, + // }) + // }); + + // let (connector_metadata, redirection_data) = match response.redirection_data.clone() { + // Some(redirection_data) => match redirection_data.form_type { + // Some(ref form_type) => match form_type { + // payments_grpc::redirect_form::FormType::Uri(uri) => { + // let image_data = QrImage::new_from_data(uri.uri.clone()) + // .change_context(UnifiedConnectorServiceError::ParsingFailed)?; + // let image_data_url = Url::parse(image_data.data.clone().as_str()) + // .change_context(UnifiedConnectorServiceError::ParsingFailed)?; + // let qr_code_info = QrCodeInformation::QrDataUrl { + // image_data_url, + // display_to_timestamp: None, + // }; + // ( + // Some(qr_code_info.encode_to_value()) + // .transpose() + // .change_context(UnifiedConnectorServiceError::ParsingFailed)?, + // None, + // ) + // } + // _ => ( + // None, + // Some(RedirectForm::foreign_try_from(redirection_data)).transpose()?, + // ), + // }, + // None => (None, None), + // }, + // None => (None, None), + // }; + + // let router_data_response = match status { + // AttemptStatus::Charged | + // AttemptStatus::Authorized | + // AttemptStatus::AuthenticationPending | + // AttemptStatus::DeviceDataCollectionPending | + // AttemptStatus::Started | + // AttemptStatus::AuthenticationSuccessful | + // AttemptStatus::Authorizing | + // AttemptStatus::ConfirmationAwaited | + // AttemptStatus::Pending => Ok(PaymentsResponseData::TransactionResponse { + // resource_id: match transaction_id.as_ref() { + // Some(transaction_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(transaction_id.clone()), + // None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId, + // }, + // redirection_data: Box::new( + // redirection_data + // ), + // mandate_reference: Box::new(None), + // connector_metadata, + // network_txn_id: response.network_txn_id.clone(), + // connector_response_reference_id, + // incremental_authorization_allowed: response.incremental_authorization_allowed, + // charges: None, + // }), + // AttemptStatus::AuthenticationFailed + // | AttemptStatus::AuthorizationFailed + // | AttemptStatus::Unresolved + // | AttemptStatus::Failure => Err(ErrorResponse { + // code: response.error_code().to_owned(), + // message: response.error_message().to_owned(), + // reason: Some(response.error_message().to_owned()), + // status_code: 500, + // attempt_status: Some(status), + // connector_transaction_id: connector_response_reference_id, + // network_decline_code: None, + // network_advice_code: None, + // network_error_message: None, + // }), + // AttemptStatus::RouterDeclined | + // AttemptStatus::CodInitiated | + // AttemptStatus::Voided | + // AttemptStatus::VoidInitiated | + // AttemptStatus::CaptureInitiated | + // AttemptStatus::VoidFailed | + // AttemptStatus::AutoRefunded | + // AttemptStatus::PartialCharged | + // AttemptStatus::PartialChargedAndChargeable | + // AttemptStatus::PaymentMethodAwaited | + // AttemptStatus::CaptureFailed | + // AttemptStatus::IntegrityFailure => return Err(UnifiedConnectorServiceError::NotImplemented(format!( + // "AttemptStatus {status:?} is not implemented for Unified Connector Service" + // )).into()), + // }; + // ======= let router_data_response = Result::::foreign_try_from(response)?; diff --git a/crates/router/src/core/unified_connector_service/transformers.rs b/crates/router/src/core/unified_connector_service/transformers.rs index 719b82ffd1..267896ce8e 100644 --- a/crates/router/src/core/unified_connector_service/transformers.rs +++ b/crates/router/src/core/unified_connector_service/transformers.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; +use api_models::payments::QrCodeInformation; use common_enums::{AttemptStatus, AuthenticationType}; -use common_utils::request::Method; +use common_utils::{ext_traits::Encode, request::Method}; use diesel_models::enums as storage_enums; use error_stack::ResultExt; use external_services::grpc_client::unified_connector_service::UnifiedConnectorServiceError; +use hyperswitch_connectors::utils::QrImage; use hyperswitch_domain_models::{ router_data::{ErrorResponse, RouterData}, router_flow_types::payments::{Authorize, PSync, SetupMandate}, @@ -14,7 +16,9 @@ use hyperswitch_domain_models::{ router_response_types::{PaymentsResponseData, RedirectForm}, }; use masking::{ExposeInterface, PeekInterface}; +use router_env::tracing; use unified_connector_service_client::payments::{self as payments_grpc, Identifier}; +use url::Url; use crate::{ core::unified_connector_service::build_unified_connector_service_payment_method, @@ -364,6 +368,35 @@ impl ForeignTryFrom }) }); + let (connector_metadata, redirection_data) = match response.redirection_data.clone() { + Some(redirection_data) => match redirection_data.form_type { + Some(ref form_type) => match form_type { + payments_grpc::redirect_form::FormType::Uri(uri) => { + let image_data = QrImage::new_from_data(uri.uri.clone()) + .change_context(UnifiedConnectorServiceError::ParsingFailed)?; + let image_data_url = Url::parse(image_data.data.clone().as_str()) + .change_context(UnifiedConnectorServiceError::ParsingFailed)?; + let qr_code_info = QrCodeInformation::QrDataUrl { + image_data_url, + display_to_timestamp: None, + }; + ( + Some(qr_code_info.encode_to_value()) + .transpose() + .change_context(UnifiedConnectorServiceError::ParsingFailed)?, + None, + ) + } + _ => ( + None, + Some(RedirectForm::foreign_try_from(redirection_data)).transpose()?, + ), + }, + None => (None, None), + }, + None => (None, None), + }; + let response = if response.error_code.is_some() { Err(ErrorResponse { code: response.error_code().to_owned(), @@ -383,14 +416,10 @@ impl ForeignTryFrom None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId, }, redirection_data: Box::new( - response - .redirection_data - .clone() - .map(RedirectForm::foreign_try_from) - .transpose()? + redirection_data ), mandate_reference: Box::new(None), - connector_metadata: None, + connector_metadata, network_txn_id: response.network_txn_id.clone(), connector_response_reference_id, incremental_authorization_allowed: response.incremental_authorization_allowed, @@ -907,6 +936,7 @@ impl ForeignTryFrom for Method { type Error = error_stack::Report; fn foreign_try_from(value: payments_grpc::HttpMethod) -> Result { + tracing::debug!("Converting gRPC HttpMethod: {:?}", value); match value { payments_grpc::HttpMethod::Get => Ok(Self::Get), payments_grpc::HttpMethod::Post => Ok(Self::Post), diff --git a/crates/router/src/types/api/connector_mapping.rs b/crates/router/src/types/api/connector_mapping.rs index 06094e6d86..c4a1f187dd 100644 --- a/crates/router/src/types/api/connector_mapping.rs +++ b/crates/router/src/types/api/connector_mapping.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use error_stack::{report, ResultExt}; +use hyperswitch_connectors::connectors::{Paytm, Phonepe}; use crate::{ configs::settings::Connectors, @@ -442,6 +443,8 @@ impl ConnectorData { .attach_printable(format!("invalid connector name: {connector_name}"))) .change_context(errors::ApiErrorResponse::InternalServerError) } + enums::Connector::Phonepe => Ok(ConnectorEnum::Old(Box::new(Phonepe::new()))), + enums::Connector::Paytm => Ok(ConnectorEnum::Old(Box::new(Paytm::new()))), }, Err(_) => Err(report!(errors::ConnectorError::InvalidConnectorName) .attach_printable(format!("invalid connector name: {connector_name}"))) diff --git a/crates/router/src/types/connector_transformers.rs b/crates/router/src/types/connector_transformers.rs index 52479a4f03..2563f87191 100644 --- a/crates/router/src/types/connector_transformers.rs +++ b/crates/router/src/types/connector_transformers.rs @@ -187,6 +187,8 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { message: "Taxjar is not a routable connector".to_string(), })? } + api_enums::Connector::Phonepe => Self::Phonepe, + api_enums::Connector::Paytm => Self::Paytm, }) } } diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index f8a19cad0a..5189e4cc01 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -87,7 +87,9 @@ mod payme; mod payone; mod paypal; mod paystack; +mod paytm; mod payu; +mod phonepe; mod placetopay; mod plaid; mod powertranz; diff --git a/crates/router/tests/connectors/paytm.rs b/crates/router/tests/connectors/paytm.rs new file mode 100644 index 0000000000..9dc1798a03 --- /dev/null +++ b/crates/router/tests/connectors/paytm.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct PaytmTest; +impl ConnectorActions for PaytmTest {} +impl utils::Connector for PaytmTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Paytm; + utils::construct_connector_data_old( + Box::new(Paytm::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .paytm + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "paytm".to_string() + } +} + +static CONNECTOR: PaytmTest = PaytmTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/phonepe.rs b/crates/router/tests/connectors/phonepe.rs new file mode 100644 index 0000000000..9a4c7f28f1 --- /dev/null +++ b/crates/router/tests/connectors/phonepe.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct PhonepeTest; +impl ConnectorActions for PhonepeTest {} +impl utils::Connector for PhonepeTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Phonepe; + utils::construct_connector_data_old( + Box::new(Phonepe::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .phonepe + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "phonepe".to_string() + } +} + +static CONNECTOR: PhonepeTest = PhonepeTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index c8a2dffbe6..b14a190bd6 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -94,7 +94,9 @@ pub struct ConnectorAuthentication { pub payone: Option, pub paypal: Option, pub paystack: Option, + pub paytm: Option, pub payu: Option, + pub phonepe: Option, pub placetopay: Option, pub plaid: Option, pub powertranz: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 62959cf3af..197469b68f 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -169,7 +169,9 @@ payme.base_url = "https://sandbox.payme.io/" payone.base_url = "https://payment.preprod.payone.com/" paypal.base_url = "https://api-m.sandbox.paypal.com/" paystack.base_url = "https://api.paystack.co" +paytm.base_url = "https://securegw-stage.paytm.in/" payu.base_url = "https://secure.snd.payu.com/" +phonepe.base_url = "https://api.phonepe.com/apis/hermes/" placetopay.base_url = "https://test.placetopay.com/rest/gateway" plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" @@ -397,6 +399,14 @@ sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT,GB", currency = "E [pm_filters.razorpay] upi_collect = { country = "IN", currency = "INR" } +[pm_filters.phonepe] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + +[pm_filters.paytm] +upi_collect = { country = "IN", currency = "INR" } +upi_intent = { country = "IN", currency = "INR" } + [pm_filters.adyen] boleto = { country = "BR", currency = "BRL" } sofort = { country = "AT,BE,DE,ES,CH,NL", currency = "CHF,EUR" } diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index eef377ec11..2f56916090 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen adyenplatform affirm airwallex amazonpay applepay archipel authipay authorizedotnet bambora bamboraapac bankofamerica barclaycard billwerk bitpay blackhawknetwork bluecode bluesnap boku braintree breadpay cashtocode celero chargebee checkbook checkout coinbase cryptopay ctp_visa custombilling cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector dwolla ebanx elavon facilitapay fiserv fiservemea fiuu flexiti forte getnet globalpay globepay gocardless gpayments helcim hipay hyperswitch_vault iatapay inespay itaubank jpmorgan juspaythreedsserver katapult klarna mifinity mollie moneris mpgs multisafepay netcetera nexinets nexixpay nomupay noon nordea novalnet nuvei opayo opennode paybox payeezy payload payme payone paypal paystack payu placetopay plaid powertranz prophetpay rapyd razorpay recurly redsys santander shift4 silverflow square stax stripe stripebilling taxjar threedsecureio thunes tokenio trustpay trustpayments tsys unified_authentication_service vgs volt wellsfargo wellsfargopayout wise worldline worldpay worldpayvantiv worldpayxml xendit zsl "$1") + connectors=(aci adyen adyenplatform affirm airwallex amazonpay applepay archipel authipay authorizedotnet bambora bamboraapac bankofamerica barclaycard billwerk bitpay blackhawknetwork bluecode bluesnap boku braintree breadpay cashtocode celero chargebee checkbook checkout coinbase cryptopay ctp_visa custombilling cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector dwolla ebanx elavon facilitapay fiserv fiservemea fiuu flexiti forte getnet globalpay globepay gocardless gpayments helcim hipay hyperswitch_vault iatapay inespay itaubank jpmorgan juspaythreedsserver katapult klarna mifinity mollie moneris mpgs multisafepay netcetera nexinets nexixpay nomupay noon nordea novalnet nuvei opayo opennode paybox payeezy payload payme payone paypal paystack paytm payu phonepe placetopay plaid powertranz prophetpay rapyd razorpay recurly redsys santander shift4 silverflow square stax stripe stripebilling taxjar threedsecureio thunes tokenio trustpay trustpayments tsys unified_authentication_service vgs volt wellsfargo wellsfargopayout wise worldline worldpay worldpayvantiv worldpayxml xendit zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS