From 5c6635be29def50cd64a40a16d906bc21175a381 Mon Sep 17 00:00:00 2001 From: Nithin N <57832822+Nithin1506200@users.noreply.github.com> Date: Thu, 9 Oct 2025 18:10:06 +0530 Subject: [PATCH] feat(connector): Card non3ds | FINIX (#9680) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference/v1/openapi_spec_v1.json | 2 + api-reference/v2/openapi_spec_v2.json | 2 + config/deployments/integration_test.toml | 5 + config/deployments/production.toml | 5 + config/deployments/sandbox.toml | 5 + config/development.toml | 5 + crates/common_enums/src/connector_enums.rs | 5 + crates/connector_configs/src/connector.rs | 1 + .../connector_configs/toml/development.toml | 25 +- crates/connector_configs/toml/production.toml | 25 +- crates/connector_configs/toml/sandbox.toml | 25 +- .../src/connectors/finix.rs | 491 +++++++++++++++-- .../src/connectors/finix/transformers.rs | 517 +++++++++++------ .../connectors/finix/transformers/request.rs | 213 +++++++ .../connectors/finix/transformers/response.rs | 79 +++ .../src/default_implementations.rs | 1 - .../router/src/core/connector_validation.rs | 4 + .../router/src/types/api/connector_mapping.rs | 3 + crates/router/src/types/api/feature_matrix.rs | 3 + .../src/types/connector_transformers.rs | 1 + .../cypress/e2e/configs/Payment/Finix.js | 520 ++++++++++++++++++ .../cypress/e2e/configs/Payment/Utils.js | 2 + 22 files changed, 1716 insertions(+), 223 deletions(-) create mode 100644 crates/hyperswitch_connectors/src/connectors/finix/transformers/request.rs create mode 100644 crates/hyperswitch_connectors/src/connectors/finix/transformers/response.rs create mode 100644 cypress-tests/cypress/e2e/configs/Payment/Finix.js diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index a5db864f18..7dc338b101 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -12178,6 +12178,7 @@ "ebanx", "elavon", "facilitapay", + "finix", "fiserv", "fiservemea", "fiuu", @@ -30623,6 +30624,7 @@ "ebanx", "elavon", "facilitapay", + "finix", "fiserv", "fiservemea", "fiuu", diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index 1064d6dd3f..0d7cbd6279 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -8718,6 +8718,7 @@ "ebanx", "elavon", "facilitapay", + "finix", "fiserv", "fiservemea", "fiuu", @@ -24107,6 +24108,7 @@ "ebanx", "elavon", "facilitapay", + "finix", "fiserv", "fiservemea", "fiuu", diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 6687210170..b5980eedcf 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -444,6 +444,10 @@ debit = { country = "US, CA", currency = "CAD,USD"} [pm_filters.facilitapay] pix = { country = "BR", currency = "BRL" } +[pm_filters.finix] +credit = { country = "AF,AX,AL,DZ,AS,AD,AO,AI,AQ,AG,AR,AM,AW,AU,AT,AZ,BS,BH,BD,BB,BY,BE,BZ,BJ,BM,BT,BO,BQ,BA,BW,BV,BR,IO,BN,BG,BF,BI,KH,CM,CA,CV,KY,CF,TD,CL,CN,CX,CC,CO,KM,CG,CD,CK,CR,CI,HR,CU,CW,CY,CZ,DK,DJ,DM,DO,EC,EG,SV,GQ,ER,EE,ET,FK,FO,FJ,FI,FR,GF,PF,TF,GA,GM,GE,DE,GH,GI,GR,GL,GD,GP,GU,GT,GG,GN,GW,GY,HT,HM,VA,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IM,IL,IT,JM,JP,JE,JO,KZ,KE,KI,KP,KR,KW,KG,LA,LV,LB,LS,LR,LY,LI,LT,LU,MO,MK,MG,MW,MY,MV,ML,MT,MH,MQ,MR,MU,YT,MX,FM,MD,MC,MN,ME,MS,MA,MZ,MM,NA,NR,NP,NL,NC,NZ,NI,NE,NG,NU,NF,MP,NO,OM,PK,PW,PS,PA,PG,PY,PE,PH,PN,PL,PT,PR,QA,RE,RO,RU,RW,BL,SH,KN,LC,MF,PM,VC,WS,SM,ST,SA,SN,RS,SC,SL,SG,SX,SK,SI,SB,SO,ZA,GS,SS,ES,LK,SD,SR,SJ,SZ,SE,CH,SY,TW,TJ,TZ,TH,TL,TG,TK,TO,TT,TN,TR,TM,TC,TV,UG,UA,AE,GB,UM,UY,UZ,VU,VE,VN,VG,VI,WF,EH,YE,ZM,ZW,US", currency = "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTN,BWP,BYN,BZD,CAD,CDF,CHF,CLF,CLP,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ERN,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK,HTG,HUF,IDR,ILS,INR,IQD,IRR,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KPW,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK,SGD,SHP,SLE,SLL,SOS,SRD,SSP,STD,STN,SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW,ZWL"} +debit = { country = "AF,AX,AL,DZ,AS,AD,AO,AI,AQ,AG,AR,AM,AW,AU,AT,AZ,BS,BH,BD,BB,BY,BE,BZ,BJ,BM,BT,BO,BQ,BA,BW,BV,BR,IO,BN,BG,BF,BI,KH,CM,CA,CV,KY,CF,TD,CL,CN,CX,CC,CO,KM,CG,CD,CK,CR,CI,HR,CU,CW,CY,CZ,DK,DJ,DM,DO,EC,EG,SV,GQ,ER,EE,ET,FK,FO,FJ,FI,FR,GF,PF,TF,GA,GM,GE,DE,GH,GI,GR,GL,GD,GP,GU,GT,GG,GN,GW,GY,HT,HM,VA,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IM,IL,IT,JM,JP,JE,JO,KZ,KE,KI,KP,KR,KW,KG,LA,LV,LB,LS,LR,LY,LI,LT,LU,MO,MK,MG,MW,MY,MV,ML,MT,MH,MQ,MR,MU,YT,MX,FM,MD,MC,MN,ME,MS,MA,MZ,MM,NA,NR,NP,NL,NC,NZ,NI,NE,NG,NU,NF,MP,NO,OM,PK,PW,PS,PA,PG,PY,PE,PH,PN,PL,PT,PR,QA,RE,RO,RU,RW,BL,SH,KN,LC,MF,PM,VC,WS,SM,ST,SA,SN,RS,SC,SL,SG,SX,SK,SI,SB,SO,ZA,GS,SS,ES,LK,SD,SR,SJ,SZ,SE,CH,SY,TW,TJ,TZ,TH,TL,TG,TK,TO,TT,TN,TR,TM,TC,TV,UG,UA,AE,GB,UM,UY,UZ,VU,VE,VN,VG,VI,WF,EH,YE,ZM,ZW,US", currency = "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTN,BWP,BYN,BZD,CAD,CDF,CHF,CLF,CLP,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ERN,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK,HTG,HUF,IDR,ILS,INR,IQD,IRR,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KPW,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK,SGD,SHP,SLE,SLL,SOS,SRD,SSP,STD,STN,SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW,ZWL"} + [pm_filters.helcim] credit = { country = "US, CA", currency = "USD, CAD" } debit = { country = "US, CA", currency = "USD, CAD" } @@ -880,6 +884,7 @@ stripe = { long_lived_token = false, payment_method = "wallet", payment_method_t billwerk = {long_lived_token = false, payment_method = "card"} globalpay = { long_lived_token = false, payment_method = "card", flow = "mandates" } dwolla = { long_lived_token = true, payment_method = "bank_debit" } +finix= { long_lived_token = false, payment_method = "card" } [webhooks] outgoing_enabled = true diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 58e74a7a4b..efb770b10f 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -527,6 +527,10 @@ debit = { country = "US, CA", currency = "CAD,USD"} [pm_filters.facilitapay] pix = { country = "BR", currency = "BRL" } +[pm_filters.finix] +credit = { country = "AF,AX,AL,DZ,AS,AD,AO,AI,AQ,AG,AR,AM,AW,AU,AT,AZ,BS,BH,BD,BB,BY,BE,BZ,BJ,BM,BT,BO,BQ,BA,BW,BV,BR,IO,BN,BG,BF,BI,KH,CM,CA,CV,KY,CF,TD,CL,CN,CX,CC,CO,KM,CG,CD,CK,CR,CI,HR,CU,CW,CY,CZ,DK,DJ,DM,DO,EC,EG,SV,GQ,ER,EE,ET,FK,FO,FJ,FI,FR,GF,PF,TF,GA,GM,GE,DE,GH,GI,GR,GL,GD,GP,GU,GT,GG,GN,GW,GY,HT,HM,VA,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IM,IL,IT,JM,JP,JE,JO,KZ,KE,KI,KP,KR,KW,KG,LA,LV,LB,LS,LR,LY,LI,LT,LU,MO,MK,MG,MW,MY,MV,ML,MT,MH,MQ,MR,MU,YT,MX,FM,MD,MC,MN,ME,MS,MA,MZ,MM,NA,NR,NP,NL,NC,NZ,NI,NE,NG,NU,NF,MP,NO,OM,PK,PW,PS,PA,PG,PY,PE,PH,PN,PL,PT,PR,QA,RE,RO,RU,RW,BL,SH,KN,LC,MF,PM,VC,WS,SM,ST,SA,SN,RS,SC,SL,SG,SX,SK,SI,SB,SO,ZA,GS,SS,ES,LK,SD,SR,SJ,SZ,SE,CH,SY,TW,TJ,TZ,TH,TL,TG,TK,TO,TT,TN,TR,TM,TC,TV,UG,UA,AE,GB,UM,UY,UZ,VU,VE,VN,VG,VI,WF,EH,YE,ZM,ZW,US", currency = "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTN,BWP,BYN,BZD,CAD,CDF,CHF,CLF,CLP,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ERN,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK,HTG,HUF,IDR,ILS,INR,IQD,IRR,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KPW,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK,SGD,SHP,SLE,SLL,SOS,SRD,SSP,STD,STN,SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW,ZWL"} +debit = { country = "AF,AX,AL,DZ,AS,AD,AO,AI,AQ,AG,AR,AM,AW,AU,AT,AZ,BS,BH,BD,BB,BY,BE,BZ,BJ,BM,BT,BO,BQ,BA,BW,BV,BR,IO,BN,BG,BF,BI,KH,CM,CA,CV,KY,CF,TD,CL,CN,CX,CC,CO,KM,CG,CD,CK,CR,CI,HR,CU,CW,CY,CZ,DK,DJ,DM,DO,EC,EG,SV,GQ,ER,EE,ET,FK,FO,FJ,FI,FR,GF,PF,TF,GA,GM,GE,DE,GH,GI,GR,GL,GD,GP,GU,GT,GG,GN,GW,GY,HT,HM,VA,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IM,IL,IT,JM,JP,JE,JO,KZ,KE,KI,KP,KR,KW,KG,LA,LV,LB,LS,LR,LY,LI,LT,LU,MO,MK,MG,MW,MY,MV,ML,MT,MH,MQ,MR,MU,YT,MX,FM,MD,MC,MN,ME,MS,MA,MZ,MM,NA,NR,NP,NL,NC,NZ,NI,NE,NG,NU,NF,MP,NO,OM,PK,PW,PS,PA,PG,PY,PE,PH,PN,PL,PT,PR,QA,RE,RO,RU,RW,BL,SH,KN,LC,MF,PM,VC,WS,SM,ST,SA,SN,RS,SC,SL,SG,SX,SK,SI,SB,SO,ZA,GS,SS,ES,LK,SD,SR,SJ,SZ,SE,CH,SY,TW,TJ,TZ,TH,TL,TG,TK,TO,TT,TN,TR,TM,TC,TV,UG,UA,AE,GB,UM,UY,UZ,VU,VE,VN,VG,VI,WF,EH,YE,ZM,ZW,US", currency = "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTN,BWP,BYN,BZD,CAD,CDF,CHF,CLF,CLP,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ERN,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK,HTG,HUF,IDR,ILS,INR,IQD,IRR,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KPW,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK,SGD,SHP,SLE,SLL,SOS,SRD,SSP,STD,STN,SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW,ZWL"} + [pm_filters.helcim] credit = { country = "US, CA", currency = "USD, CAD" } debit = { country = "US, CA", currency = "USD, CAD" } @@ -889,6 +893,7 @@ stripe = { long_lived_token = false, payment_method = "wallet", payment_method_t billwerk = {long_lived_token = false, payment_method = "card"} globalpay = { long_lived_token = false, payment_method = "card", flow = "mandates" } dwolla = { long_lived_token = true, payment_method = "bank_debit" } +finix= { long_lived_token = false, payment_method = "card" } [webhooks] outgoing_enabled = true diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 9ee0d4fe95..87de6f3e43 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -509,6 +509,10 @@ debit = { country = "US, CA", currency = "CAD,USD"} [pm_filters.facilitapay] pix = { country = "BR", currency = "BRL" } +[pm_filters.finix] +credit = { country = "AF,AX,AL,DZ,AS,AD,AO,AI,AQ,AG,AR,AM,AW,AU,AT,AZ,BS,BH,BD,BB,BY,BE,BZ,BJ,BM,BT,BO,BQ,BA,BW,BV,BR,IO,BN,BG,BF,BI,KH,CM,CA,CV,KY,CF,TD,CL,CN,CX,CC,CO,KM,CG,CD,CK,CR,CI,HR,CU,CW,CY,CZ,DK,DJ,DM,DO,EC,EG,SV,GQ,ER,EE,ET,FK,FO,FJ,FI,FR,GF,PF,TF,GA,GM,GE,DE,GH,GI,GR,GL,GD,GP,GU,GT,GG,GN,GW,GY,HT,HM,VA,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IM,IL,IT,JM,JP,JE,JO,KZ,KE,KI,KP,KR,KW,KG,LA,LV,LB,LS,LR,LY,LI,LT,LU,MO,MK,MG,MW,MY,MV,ML,MT,MH,MQ,MR,MU,YT,MX,FM,MD,MC,MN,ME,MS,MA,MZ,MM,NA,NR,NP,NL,NC,NZ,NI,NE,NG,NU,NF,MP,NO,OM,PK,PW,PS,PA,PG,PY,PE,PH,PN,PL,PT,PR,QA,RE,RO,RU,RW,BL,SH,KN,LC,MF,PM,VC,WS,SM,ST,SA,SN,RS,SC,SL,SG,SX,SK,SI,SB,SO,ZA,GS,SS,ES,LK,SD,SR,SJ,SZ,SE,CH,SY,TW,TJ,TZ,TH,TL,TG,TK,TO,TT,TN,TR,TM,TC,TV,UG,UA,AE,GB,UM,UY,UZ,VU,VE,VN,VG,VI,WF,EH,YE,ZM,ZW,US", currency = "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTN,BWP,BYN,BZD,CAD,CDF,CHF,CLF,CLP,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ERN,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK,HTG,HUF,IDR,ILS,INR,IQD,IRR,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KPW,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK,SGD,SHP,SLE,SLL,SOS,SRD,SSP,STD,STN,SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW,ZWL"} +debit = { country = "AF,AX,AL,DZ,AS,AD,AO,AI,AQ,AG,AR,AM,AW,AU,AT,AZ,BS,BH,BD,BB,BY,BE,BZ,BJ,BM,BT,BO,BQ,BA,BW,BV,BR,IO,BN,BG,BF,BI,KH,CM,CA,CV,KY,CF,TD,CL,CN,CX,CC,CO,KM,CG,CD,CK,CR,CI,HR,CU,CW,CY,CZ,DK,DJ,DM,DO,EC,EG,SV,GQ,ER,EE,ET,FK,FO,FJ,FI,FR,GF,PF,TF,GA,GM,GE,DE,GH,GI,GR,GL,GD,GP,GU,GT,GG,GN,GW,GY,HT,HM,VA,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IM,IL,IT,JM,JP,JE,JO,KZ,KE,KI,KP,KR,KW,KG,LA,LV,LB,LS,LR,LY,LI,LT,LU,MO,MK,MG,MW,MY,MV,ML,MT,MH,MQ,MR,MU,YT,MX,FM,MD,MC,MN,ME,MS,MA,MZ,MM,NA,NR,NP,NL,NC,NZ,NI,NE,NG,NU,NF,MP,NO,OM,PK,PW,PS,PA,PG,PY,PE,PH,PN,PL,PT,PR,QA,RE,RO,RU,RW,BL,SH,KN,LC,MF,PM,VC,WS,SM,ST,SA,SN,RS,SC,SL,SG,SX,SK,SI,SB,SO,ZA,GS,SS,ES,LK,SD,SR,SJ,SZ,SE,CH,SY,TW,TJ,TZ,TH,TL,TG,TK,TO,TT,TN,TR,TM,TC,TV,UG,UA,AE,GB,UM,UY,UZ,VU,VE,VN,VG,VI,WF,EH,YE,ZM,ZW,US", currency = "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTN,BWP,BYN,BZD,CAD,CDF,CHF,CLF,CLP,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ERN,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK,HTG,HUF,IDR,ILS,INR,IQD,IRR,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KPW,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK,SGD,SHP,SLE,SLL,SOS,SRD,SSP,STD,STN,SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW,ZWL"} + [pm_filters.helcim] credit = { country = "US, CA", currency = "USD, CAD" } debit = { country = "US, CA", currency = "USD, CAD" } @@ -895,6 +899,7 @@ stripe = { long_lived_token = false, payment_method = "wallet", payment_method_t billwerk = {long_lived_token = false, payment_method = "card"} globalpay = { long_lived_token = false, payment_method = "card", flow = "mandates" } dwolla = { long_lived_token = true, payment_method = "bank_debit" } +finix= { long_lived_token = false, payment_method = "card" } [webhooks] outgoing_enabled = true diff --git a/config/development.toml b/config/development.toml index 472bba83f2..a0555ec8f3 100644 --- a/config/development.toml +++ b/config/development.toml @@ -711,6 +711,10 @@ debit = { country = "AD,AT,AU,BE,BG,CA,CH,CY,CZ,DE,DK,EE,ES,FI,FR,GB,GG,GI,GR,HK [pm_filters.facilitapay] pix = { country = "BR", currency = "BRL" } +[pm_filters.finix] +credit = { country = "AF,AX,AL,DZ,AS,AD,AO,AI,AQ,AG,AR,AM,AW,AU,AT,AZ,BS,BH,BD,BB,BY,BE,BZ,BJ,BM,BT,BO,BQ,BA,BW,BV,BR,IO,BN,BG,BF,BI,KH,CM,CA,CV,KY,CF,TD,CL,CN,CX,CC,CO,KM,CG,CD,CK,CR,CI,HR,CU,CW,CY,CZ,DK,DJ,DM,DO,EC,EG,SV,GQ,ER,EE,ET,FK,FO,FJ,FI,FR,GF,PF,TF,GA,GM,GE,DE,GH,GI,GR,GL,GD,GP,GU,GT,GG,GN,GW,GY,HT,HM,VA,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IM,IL,IT,JM,JP,JE,JO,KZ,KE,KI,KP,KR,KW,KG,LA,LV,LB,LS,LR,LY,LI,LT,LU,MO,MK,MG,MW,MY,MV,ML,MT,MH,MQ,MR,MU,YT,MX,FM,MD,MC,MN,ME,MS,MA,MZ,MM,NA,NR,NP,NL,NC,NZ,NI,NE,NG,NU,NF,MP,NO,OM,PK,PW,PS,PA,PG,PY,PE,PH,PN,PL,PT,PR,QA,RE,RO,RU,RW,BL,SH,KN,LC,MF,PM,VC,WS,SM,ST,SA,SN,RS,SC,SL,SG,SX,SK,SI,SB,SO,ZA,GS,SS,ES,LK,SD,SR,SJ,SZ,SE,CH,SY,TW,TJ,TZ,TH,TL,TG,TK,TO,TT,TN,TR,TM,TC,TV,UG,UA,AE,GB,UM,UY,UZ,VU,VE,VN,VG,VI,WF,EH,YE,ZM,ZW,US", currency = "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTN,BWP,BYN,BZD,CAD,CDF,CHF,CLF,CLP,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ERN,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK,HTG,HUF,IDR,ILS,INR,IQD,IRR,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KPW,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK,SGD,SHP,SLE,SLL,SOS,SRD,SSP,STD,STN,SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW,ZWL"} +debit = { country = "AF,AX,AL,DZ,AS,AD,AO,AI,AQ,AG,AR,AM,AW,AU,AT,AZ,BS,BH,BD,BB,BY,BE,BZ,BJ,BM,BT,BO,BQ,BA,BW,BV,BR,IO,BN,BG,BF,BI,KH,CM,CA,CV,KY,CF,TD,CL,CN,CX,CC,CO,KM,CG,CD,CK,CR,CI,HR,CU,CW,CY,CZ,DK,DJ,DM,DO,EC,EG,SV,GQ,ER,EE,ET,FK,FO,FJ,FI,FR,GF,PF,TF,GA,GM,GE,DE,GH,GI,GR,GL,GD,GP,GU,GT,GG,GN,GW,GY,HT,HM,VA,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IM,IL,IT,JM,JP,JE,JO,KZ,KE,KI,KP,KR,KW,KG,LA,LV,LB,LS,LR,LY,LI,LT,LU,MO,MK,MG,MW,MY,MV,ML,MT,MH,MQ,MR,MU,YT,MX,FM,MD,MC,MN,ME,MS,MA,MZ,MM,NA,NR,NP,NL,NC,NZ,NI,NE,NG,NU,NF,MP,NO,OM,PK,PW,PS,PA,PG,PY,PE,PH,PN,PL,PT,PR,QA,RE,RO,RU,RW,BL,SH,KN,LC,MF,PM,VC,WS,SM,ST,SA,SN,RS,SC,SL,SG,SX,SK,SI,SB,SO,ZA,GS,SS,ES,LK,SD,SR,SJ,SZ,SE,CH,SY,TW,TJ,TZ,TH,TL,TG,TK,TO,TT,TN,TR,TM,TC,TV,UG,UA,AE,GB,UM,UY,UZ,VU,VE,VN,VG,VI,WF,EH,YE,ZM,ZW,US", currency = "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTN,BWP,BYN,BZD,CAD,CDF,CHF,CLF,CLP,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ERN,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK,HTG,HUF,IDR,ILS,INR,IQD,IRR,ISK,JMD,JOD,JPY,KES,KGS,KHR,KMF,KPW,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK,SGD,SHP,SLE,SLL,SOS,SRD,SSP,STD,STN,SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW,ZWL"} + [pm_filters.helcim] credit = { country = "US, CA", currency = "USD, CAD" } debit = { country = "US, CA", currency = "USD, CAD" } @@ -1022,6 +1026,7 @@ gocardless = { long_lived_token = true, payment_method = "bank_debit" } billwerk = { long_lived_token = false, payment_method = "card" } dwolla = { long_lived_token = true, payment_method = "bank_debit" } globalpay = { long_lived_token = false, payment_method = "card", flow = "mandates" } +finix= { long_lived_token = false, payment_method = "card" } [temp_locker_enable_config] stripe = { payment_method = "bank_transfer" } diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 2b225e09a5..fe11c685ae 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -96,6 +96,7 @@ pub enum RoutableConnectors { Ebanx, Elavon, Facilitapay, + Finix, Fiserv, Fiservemea, Fiuu, @@ -274,6 +275,7 @@ pub enum Connector { Ebanx, Elavon, Facilitapay, + Finix, Fiserv, Fiservemea, Fiuu, @@ -477,6 +479,7 @@ impl Connector { | Self::Ebanx | Self::Elavon | Self::Facilitapay + | Self::Finix | Self::Fiserv | Self::Fiservemea | Self::Fiuu @@ -665,6 +668,7 @@ impl From for Connector { RoutableConnectors::Ebanx => Self::Ebanx, RoutableConnectors::Elavon => Self::Elavon, RoutableConnectors::Facilitapay => Self::Facilitapay, + RoutableConnectors::Finix => Self::Finix, RoutableConnectors::Fiserv => Self::Fiserv, RoutableConnectors::Fiservemea => Self::Fiservemea, RoutableConnectors::Fiuu => Self::Fiuu, @@ -805,6 +809,7 @@ impl TryFrom for RoutableConnectors { Connector::Ebanx => Ok(Self::Ebanx), Connector::Elavon => Ok(Self::Elavon), Connector::Facilitapay => Ok(Self::Facilitapay), + Connector::Finix => Ok(Self::Finix), Connector::Fiserv => Ok(Self::Fiserv), Connector::Fiservemea => Ok(Self::Fiservemea), Connector::Fiuu => Ok(Self::Fiuu), diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index e92311b46c..9ddf6a1846 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -502,6 +502,7 @@ impl ConnectorConfig { Connector::Ebanx => Ok(connector_data.ebanx_payout), Connector::Elavon => Ok(connector_data.elavon), Connector::Facilitapay => Ok(connector_data.facilitapay), + Connector::Finix => Ok(connector_data.finix), Connector::Fiserv => Ok(connector_data.fiserv), Connector::Fiservemea => Ok(connector_data.fiservemea), Connector::Fiuu => Ok(connector_data.fiuu), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 746c5a260d..0bc14f42dc 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -7303,9 +7303,28 @@ required = true type = "Text" [finix] -[finix.connector_auth.HeaderKey] -api_key = "API Key" - +[finix.connector_auth.SignatureKey] +api_key = "Username" +api_secret = "Password" +key1 = "Merchant Id" +[[finix.credit]] +payment_method_type = "Mastercard" +[[finix.credit]] +payment_method_type = "Visa" +[[finix.credit]] +payment_method_type = "AmericanExpress" +[[finix.credit]] +payment_method_type = "Discover" +[[finix.credit]] +payment_method_type = "JCB" +[[finix.credit]] +payment_method_type = "DinersClub" +[[finix.credit]] +payment_method_type = "UnionPay" +[[finix.credit]] +payment_method_type = "Interac" +[[finix.credit]] +payment_method_type = "Maestro" [loonio] [loonio.connector_auth.BodyKey] diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index f612dd67de..99042fae46 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -6040,8 +6040,29 @@ required = true type = "Text" [finix] -[finix.connector_auth.HeaderKey] -api_key = "API Key" +[finix.connector_auth.SignatureKey] +api_key = "Username" +api_secret = "Password" +key1 = "Merchant Id" +[[finix.credit]] +payment_method_type = "Mastercard" +[[finix.credit]] +payment_method_type = "Visa" +[[finix.credit]] +payment_method_type = "AmericanExpress" +[[finix.credit]] +payment_method_type = "Discover" +[[finix.credit]] +payment_method_type = "JCB" +[[finix.credit]] +payment_method_type = "DinersClub" +[[finix.credit]] +payment_method_type = "UnionPay" +[[finix.credit]] +payment_method_type = "Interac" +[[finix.credit]] +payment_method_type = "Maestro" + [loonio] [loonio.connector_auth.BodyKey] diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 2f8b52777d..7aba1e4db0 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -7264,7 +7264,6 @@ label = "Site where transaction is initiated" placeholder = "Enter site where transaction is initiated" required = true type = "Text" - [gigadat_payout] [gigadat_payout.connector_auth.SignatureKey] api_key = "Access Token" @@ -7280,8 +7279,28 @@ required = true type = "Text" [finix] -[finix.connector_auth.HeaderKey] -api_key = "API Key" +[finix.connector_auth.SignatureKey] +api_key = "Username" +api_secret = "Password" +key1 = "Merchant Id" +[[finix.credit]] +payment_method_type = "Mastercard" +[[finix.credit]] +payment_method_type = "Visa" +[[finix.credit]] +payment_method_type = "AmericanExpress" +[[finix.credit]] +payment_method_type = "Discover" +[[finix.credit]] +payment_method_type = "JCB" +[[finix.credit]] +payment_method_type = "DinersClub" +[[finix.credit]] +payment_method_type = "UnionPay" +[[finix.credit]] +payment_method_type = "Interac" +[[finix.credit]] +payment_method_type = "Maestro" [loonio] diff --git a/crates/hyperswitch_connectors/src/connectors/finix.rs b/crates/hyperswitch_connectors/src/connectors/finix.rs index d42f50a153..d47f03adf6 100644 --- a/crates/hyperswitch_connectors/src/connectors/finix.rs +++ b/crates/hyperswitch_connectors/src/connectors/finix.rs @@ -2,7 +2,8 @@ pub mod transformers; use std::sync::LazyLock; -use common_enums::{enums, ConnectorIntegrationStatus}; +use base64::Engine; +use common_enums::{enums, CaptureMethod, ConnectorIntegrationStatus, PaymentMethodType}; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, @@ -17,18 +18,20 @@ use hyperswitch_domain_models::{ access_token_auth::AccessTokenAuth, payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, refunds::{Execute, RSync}, + CreateConnectorCustomer, }, router_request_types::{ - AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, - RefundsData, SetupMandateRequestData, + AccessTokenRequestData, ConnectorCustomerData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, }, router_response_types::{ - ConnectorInfo, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods, + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, }, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -72,11 +75,175 @@ impl api::Refund for Finix {} impl api::RefundExecute for Finix {} impl api::RefundSync for Finix {} impl api::PaymentToken for Finix {} +impl api::ConnectorCustomer for Finix {} + +impl ConnectorIntegration + for Finix +{ + fn get_headers( + &self, + req: &RouterData, + 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: &RouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/identities", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_router_data = finix::FinixRouterData::try_from((MinorUnit::zero(), req))?; + let connector_req = finix::FinixCreateIdentityRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::ConnectorCustomerType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::ConnectorCustomerType::get_headers( + self, req, connectors, + )?) + .set_body(types::ConnectorCustomerType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult< + RouterData, + errors::ConnectorError, + > { + let response: finix::FinixIdentityResponse = res + .response + .parse_struct("Finix IdentityResponse") + .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 Finix { - // Not Implemented (R) + fn get_headers( + &self, + req: &RouterData, + 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: &RouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/payment_instruments", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_router_data = finix::FinixRouterData::try_from((MinorUnit::zero(), req))?; + let connector_req = + finix::FinixCreatePaymentInstrumentRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::TokenizationType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::TokenizationType::get_headers(self, req, connectors)?) + .set_body(types::TokenizationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult< + RouterData, + errors::ConnectorError, + > { + let response: finix::FinixInstrumentResponse = res + .response + .parse_struct("Finix InstrumentResponse") + .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 ConnectorCommonExt for Finix @@ -121,9 +288,19 @@ impl ConnectorCommon for Finix { ) -> CustomResult)>, errors::ConnectorError> { let auth = finix::FinixAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let credentials = format!( + "{}:{}", + auth.finix_user_name.clone().expose(), + auth.finix_password.clone().expose() + ); + let encoded = format!( + "Basic {:}", + base64::engine::general_purpose::STANDARD.encode(credentials.as_bytes()) + ); + Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + encoded.into_masked(), )]) } @@ -142,9 +319,9 @@ impl ConnectorCommon for Finix { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.get_code(), + message: response.get_message(), + reason: None, attempt_status: None, connector_transaction_id: None, network_advice_code: None, @@ -158,7 +335,7 @@ impl ConnectorCommon for Finix { impl ConnectorValidation for Finix { fn validate_mandate_payment( &self, - _pm_type: Option, + _pm_type: Option, pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { match pm_data { @@ -204,10 +381,16 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let flow = match req.request.capture_method.unwrap_or_default() { + CaptureMethod::Automatic | CaptureMethod::SequentialAutomatic => "transfers", + CaptureMethod::Manual | CaptureMethod::ManualMultiple | CaptureMethod::Scheduled => { + "authorizations" + } + }; + Ok(format!("{}/{}", self.base_url(connectors), flow)) } fn get_request_body( @@ -221,7 +404,7 @@ impl ConnectorIntegration for Fin fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_transaction_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + match finix::FinixId::from(connector_transaction_id) { + transformers::FinixId::Auth(id) => Ok(format!( + "{}/authorizations/{}", + self.base_url(connectors), + id + )), + transformers::FinixId::Transfer(id) => { + Ok(format!("{}/transfers/{}", self.base_url(connectors), id)) + } + } } fn build_request( @@ -324,11 +524,18 @@ impl ConnectorIntegration for Fin .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, - }) + let response_id = response.id.clone(); + finix::get_finix_response( + ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }, + match finix::FinixId::from(response_id) { + finix::FinixId::Auth(_) => finix::FinixFlow::Auth, + finix::FinixId::Transfer(_) => finix::FinixFlow::Transfer, + }, + ) } fn get_error_response( @@ -355,18 +562,30 @@ impl ConnectorIntegration fo fn get_url( &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_transaction_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/authorizations/{}", + self.base_url(connectors), + connector_transaction_id + )) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + let connector_router_data = finix::FinixRouterData::try_from((amount, req))?; + let connector_req = finix::FinixCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -376,7 +595,7 @@ impl ConnectorIntegration fo ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() - .method(Method::Post) + .method(Method::Put) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -397,15 +616,18 @@ impl ConnectorIntegration fo ) -> CustomResult { let response: finix::FinixPaymentsResponse = res .response - .parse_struct("Finix PaymentsCaptureResponse") + .parse_struct("Finix 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, - }) + finix::get_finix_response( + ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }, + finix::FinixFlow::Capture, + ) } fn get_error_response( @@ -417,7 +639,89 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Finix {} +impl ConnectorIntegration for Finix { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + 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: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_transaction_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/authorizations/{}", + self.base_url(connectors), + connector_transaction_id + )) + } + + fn get_request_body( + &self, + _req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = finix::FinixCancelRequest { void_me: true }; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Put) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: finix::FinixPaymentsResponse = res + .response + .parse_struct("Finix PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + finix::get_finix_response( + ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }, + finix::FinixFlow::Transfer, + ) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} impl ConnectorIntegration for Finix { fn get_headers( @@ -434,10 +738,14 @@ impl ConnectorIntegration for Finix { fn get_url( &self, - _req: &RefundsRouterData, - _connectors: &Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!( + "{}/transfers/{}/reversals", + self.base_url(connectors), + req.request.connector_transaction_id + )) } fn get_request_body( @@ -451,8 +759,8 @@ impl ConnectorIntegration for Finix { req.request.currency, )?; - let connector_router_data = finix::FinixRouterData::from((refund_amount, req)); - let connector_req = finix::FinixRefundRequest::try_from(&connector_router_data)?; + let connector_router_data = finix::FinixRouterData::try_from((refund_amount, req))?; + let connector_req = finix::FinixCreateRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -481,9 +789,9 @@ impl ConnectorIntegration for Finix { event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult, errors::ConnectorError> { - let response: finix::RefundResponse = res + let response: finix::FinixPaymentsResponse = res .response - .parse_struct("finix RefundResponse") + .parse_struct("FinixPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -518,10 +826,19 @@ impl ConnectorIntegration for Finix { fn get_url( &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let refund_id = req + .request + .connector_refund_id + .clone() + .ok_or(errors::ConnectorError::MissingConnectorRefundID)?; + Ok(format!( + "{}/transfers/{}", + self.base_url(connectors), + refund_id + )) } fn build_request( @@ -548,9 +865,9 @@ impl ConnectorIntegration for Finix { event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: finix::RefundResponse = res + let response: finix::FinixPaymentsResponse = res .response - .parse_struct("finix RefundSyncResponse") + .parse_struct("FinixPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -594,8 +911,61 @@ impl webhooks::IncomingWebhook for Finix { } } -static FINIX_SUPPORTED_PAYMENT_METHODS: LazyLock = - LazyLock::new(SupportedPaymentMethods::new); +static FINIX_SUPPORTED_PAYMENT_METHODS: LazyLock = LazyLock::new(|| { + let default_capture_methods = vec![CaptureMethod::Automatic, CaptureMethod::Manual]; + + let supported_card_network = vec![ + common_enums::CardNetwork::Visa, + common_enums::CardNetwork::Mastercard, + common_enums::CardNetwork::AmericanExpress, + common_enums::CardNetwork::Discover, + common_enums::CardNetwork::JCB, + common_enums::CardNetwork::DinersClub, + common_enums::CardNetwork::UnionPay, + common_enums::CardNetwork::Interac, + common_enums::CardNetwork::Maestro, + ]; + + let mut finix_supported_payment_methods = SupportedPaymentMethods::new(); + + finix_supported_payment_methods.add( + common_enums::PaymentMethod::Card, + PaymentMethodType::Credit, + PaymentMethodDetails { + mandates: common_enums::FeatureStatus::NotSupported, + refunds: common_enums::FeatureStatus::Supported, + supported_capture_methods: default_capture_methods.clone(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card( + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::Supported, + supported_card_networks: supported_card_network.clone(), + }, + ), + ), + }, + ); + finix_supported_payment_methods.add( + common_enums::PaymentMethod::Card, + PaymentMethodType::Debit, + PaymentMethodDetails { + mandates: common_enums::FeatureStatus::NotSupported, + refunds: common_enums::FeatureStatus::Supported, + supported_capture_methods: default_capture_methods.clone(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card( + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::Supported, + supported_card_networks: supported_card_network.clone(), + }, + ), + ), + }, + ); + finix_supported_payment_methods +}); static FINIX_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { display_name: "Finix", @@ -618,4 +988,11 @@ impl ConnectorSpecifications for Finix { fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { Some(&FINIX_SUPPORTED_WEBHOOK_FLOWS) } + + fn should_call_connector_customer( + &self, + _payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, + ) -> bool { + true + } } diff --git a/crates/hyperswitch_connectors/src/connectors/finix/transformers.rs b/crates/hyperswitch_connectors/src/connectors/finix/transformers.rs index b09f6ee47b..f9e76be04a 100644 --- a/crates/hyperswitch_connectors/src/connectors/finix/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/finix/transformers.rs @@ -1,120 +1,337 @@ -use common_enums::enums; +pub mod request; +pub mod response; +use common_enums::{enums, AttemptStatus, CaptureMethod, CountryAlpha2, CountryAlpha3}; use common_utils::types::MinorUnit; 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}, + router_data::{ConnectorAuthType, ErrorResponse, PaymentMethodToken, RouterData}, + router_flow_types::{ + self as flows, + refunds::{Execute, RSync}, + Authorize, Capture, + }, + router_request_types::{ + ConnectorCustomerData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCaptureData, RefundsData, ResponseId, + }, + router_response_types::{ + ConnectorCustomerResponseData, PaymentsResponseData, RefundsResponseData, + }, + types::RefundsRouterData, }; -use hyperswitch_interfaces::errors; +use hyperswitch_interfaces::{consts, errors::ConnectorError}; use masking::Secret; -use serde::{Deserialize, Serialize}; +pub use request::*; +pub use response::*; -use crate::types::{RefundsResponseRouterData, ResponseRouterData}; +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + unimplemented_payment_method, + utils::{ + get_unimplemented_payment_method_error_message, AddressDetailsData, CardData, + RouterData as _, + }, +}; -//TODO: Fill the struct with respective fields -pub struct FinixRouterData { - pub amount: MinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. - pub router_data: T, +pub struct FinixRouterData<'a, Flow, Req, Res> { + pub amount: MinorUnit, + pub router_data: &'a RouterData, + pub merchant_id: Secret, } -impl From<(MinorUnit, T)> for FinixRouterData { - fn from((amount, item): (MinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts - Self { +impl<'a, Flow, Req, Res> TryFrom<(MinorUnit, &'a RouterData)> + for FinixRouterData<'a, Flow, Req, Res> +{ + type Error = error_stack::Report; + + fn try_from(value: (MinorUnit, &'a RouterData)) -> Result { + let (amount, router_data) = value; + let auth = FinixAuthType::try_from(&router_data.connector_auth_type)?; + + Ok(Self { amount, - router_data: item, - } + router_data, + merchant_id: auth.merchant_id, + }) } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] -pub struct FinixPaymentsRequest { - amount: MinorUnit, - card: FinixCard, -} +impl + TryFrom< + &FinixRouterData< + '_, + flows::CreateConnectorCustomer, + ConnectorCustomerData, + PaymentsResponseData, + >, + > for FinixCreateIdentityRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &FinixRouterData< + '_, + flows::CreateConnectorCustomer, + ConnectorCustomerData, + PaymentsResponseData, + >, + ) -> Result { + let customer_data: &ConnectorCustomerData = &item.router_data.request; + let personal_address = item.router_data.get_optional_billing().and_then(|address| { + let billing = address.address.as_ref(); + billing.map(|billing_address| FinixAddress { + line1: billing_address.get_optional_line1(), + line2: billing_address.get_optional_line2(), + city: billing_address.get_optional_city(), + region: billing_address.get_optional_state(), + postal_code: billing_address.get_optional_zip(), + country: billing_address + .get_optional_country() + .map(CountryAlpha2::from_alpha2_to_alpha3), + }) + }); + let entity = FinixIdentityEntity { + phone: customer_data.phone.clone(), + first_name: item.router_data.get_optional_billing_first_name(), + last_name: item.router_data.get_optional_billing_last_name(), + email: item.router_data.get_optional_billing_email(), + personal_address, + }; -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct FinixCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, -} - -impl TryFrom<&FinixRouterData<&PaymentsAuthorizeRouterData>> for FinixPaymentsRequest { - type Error = error_stack::Report; - fn try_from(item: &FinixRouterData<&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()), - } + Ok(Self { + entity, + tags: None, + identity_type: FinixIdentityType::PERSONAL, + }) } } -//TODO: Fill the struct with respective fields -// Auth Struct -pub struct FinixAuthType { - pub(super) api_key: Secret, -} - -impl TryFrom<&ConnectorAuthType> for FinixAuthType { - type Error = error_stack::Report; - fn try_from(auth_type: &ConnectorAuthType) -> Result { - match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), - }), - _ => 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 FinixPaymentStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for common_enums::AttemptStatus { - fn from(item: FinixPaymentStatus) -> Self { - match item { - FinixPaymentStatus::Succeeded => Self::Charged, - FinixPaymentStatus::Failed => Self::Failure, - FinixPaymentStatus::Processing => Self::Authorizing, - } - } -} - -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct FinixPaymentsResponse { - status: FinixPaymentStatus, - id: String, -} - -impl TryFrom> +impl TryFrom> for RouterData { - type Error = error_stack::Report; + type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, + item: ResponseRouterData, ) -> Result { Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), + response: Ok(PaymentsResponseData::ConnectorCustomerResponse( + ConnectorCustomerResponseData::new_with_customer_id(item.response.id), + )), + ..item.data + }) + } +} + +impl TryFrom<&FinixRouterData<'_, Authorize, PaymentsAuthorizeData, PaymentsResponseData>> + for FinixPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &FinixRouterData<'_, Authorize, PaymentsAuthorizeData, PaymentsResponseData>, + ) -> Result { + if matches!( + item.router_data.auth_type, + enums::AuthenticationType::ThreeDs + ) { + return Err(ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("finix"), + ) + .into()); + } + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(_) => { + let source = item.router_data.get_payment_method_token()?; + Ok(Self { + amount: item.amount, + currency: item.router_data.request.currency, + source: match source { + PaymentMethodToken::Token(token) => token, + PaymentMethodToken::ApplePayDecrypt(_) => Err( + unimplemented_payment_method!("Apple Pay", "Simplified", "Stax"), + )?, + PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Stax"))? + } + PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Stax"))? + } + }, + merchant: item.merchant_id.clone(), + tags: None, + three_d_secure: None, + }) + } + _ => Err( + ConnectorError::NotImplemented("Payment method not supported".to_string()).into(), + ), + } + } +} + +impl TryFrom<&FinixRouterData<'_, Capture, PaymentsCaptureData, PaymentsResponseData>> + for FinixCaptureRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &FinixRouterData<'_, Capture, PaymentsCaptureData, PaymentsResponseData>, + ) -> Result { + Ok(Self { + capture_amount: item.router_data.request.minor_amount_to_capture, + }) + } +} + +impl + TryFrom< + &FinixRouterData< + '_, + flows::PaymentMethodToken, + PaymentMethodTokenizationData, + PaymentsResponseData, + >, + > for FinixCreatePaymentInstrumentRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &FinixRouterData< + '_, + flows::PaymentMethodToken, + PaymentMethodTokenizationData, + PaymentsResponseData, + >, + ) -> Result { + let tokenization_data = &item.router_data.request; + + match &tokenization_data.payment_method_data { + PaymentMethodData::Card(card_data) => { + Ok(Self { + instrument_type: FinixPaymentInstrumentType::PaymentCard, + name: card_data.card_holder_name.clone(), + number: Some(Secret::new(card_data.card_number.clone().get_card_no())), + security_code: Some(card_data.card_cvc.clone()), + expiration_month: Some(card_data.get_expiry_month_as_i8()?), + expiration_year: Some(card_data.get_expiry_year_as_4_digit_i32()?), + identity: item.router_data.get_connector_customer_id()?, // This would come from a previously created identity + tags: None, + address: None, + card_brand: None, // Finix determines this from the card number + card_type: None, // Finix determines this from the card number + additional_data: None, + }) + } + _ => Err(ConnectorError::NotImplemented( + "Payment method not supported for tokenization".to_string(), + ) + .into()), + } + } +} + +// Implement response handling for tokenization +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: AttemptStatus::Charged, + response: Ok(PaymentsResponseData::TokenizationResponse { + token: item.response.id, + }), + ..item.data + }) + } +} + +// Auth Struct + +impl TryFrom<&ConnectorAuthType> for FinixAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + finix_user_name: api_key.clone(), + finix_password: api_secret.clone(), + merchant_id: key1.clone(), + }), + _ => Err(ConnectorError::FailedToObtainAuthType.into()), + } + } +} + +fn get_attempt_status(state: FinixState, flow: FinixFlow, is_void: Option) -> AttemptStatus { + if is_void == Some(true) { + return match state { + FinixState::FAILED | FinixState::CANCELED | FinixState::UNKNOWN => { + AttemptStatus::VoidFailed + } + FinixState::PENDING => AttemptStatus::Voided, + FinixState::SUCCEEDED => AttemptStatus::Voided, + }; + } + match (flow, state) { + (FinixFlow::Auth, FinixState::PENDING) => AttemptStatus::AuthenticationPending, + (FinixFlow::Auth, FinixState::SUCCEEDED) => AttemptStatus::Authorized, + (FinixFlow::Auth, FinixState::FAILED) => AttemptStatus::AuthorizationFailed, + (FinixFlow::Auth, FinixState::CANCELED) | (FinixFlow::Auth, FinixState::UNKNOWN) => { + AttemptStatus::AuthorizationFailed + } + (FinixFlow::Transfer, FinixState::PENDING) => AttemptStatus::Pending, + (FinixFlow::Transfer, FinixState::SUCCEEDED) => AttemptStatus::Charged, + (FinixFlow::Transfer, FinixState::FAILED) + | (FinixFlow::Transfer, FinixState::CANCELED) + | (FinixFlow::Transfer, FinixState::UNKNOWN) => AttemptStatus::Failure, + (FinixFlow::Capture, FinixState::PENDING) => AttemptStatus::Pending, + (FinixFlow::Capture, FinixState::SUCCEEDED) => AttemptStatus::Pending, // Psync with Transfer id can determine actuall success + (FinixFlow::Capture, FinixState::FAILED) + | (FinixFlow::Capture, FinixState::CANCELED) + | (FinixFlow::Capture, FinixState::UNKNOWN) => AttemptStatus::Failure, + } +} + +pub(crate) fn get_finix_response( + router_data: ResponseRouterData, + finix_flow: FinixFlow, +) -> Result, error_stack::Report> { + let status = get_attempt_status( + router_data.response.state.clone(), + finix_flow, + router_data.response.is_void, + ); + Ok(RouterData { + status, + response: if router_data.response.state.is_failure() { + Err(ErrorResponse { + code: router_data + .response + .failure_code + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: router_data + .response + .messages + .map_or(consts::NO_ERROR_MESSAGE.to_string(), |msg| msg.join(",")), + reason: router_data.response.failure_message, + status_code: router_data.http_code, + attempt_status: Some(status), + connector_transaction_id: Some(router_data.response.id.clone()), + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + connector_metadata: None, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + router_data + .response + .transfer + .unwrap_or(router_data.response.id), + ), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -122,96 +339,82 @@ impl TryFrom TryFrom<&FinixRouterData<'_, F, RefundsData, RefundsResponseData>> + for FinixCreateRefundRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &FinixRouterData<'_, F, RefundsData, RefundsResponseData>, + ) -> Result { + let refund_amount = item.router_data.request.minor_refund_amount; + Ok(Self::new(refund_amount)) } } -//TODO: Fill the struct with respective fields -// REFUND : -// Type definition for RefundRequest -#[derive(Default, Debug, Serialize)] -pub struct FinixRefundRequest { - pub amount: MinorUnit, -} - -impl TryFrom<&FinixRouterData<&RefundsRouterData>> for FinixRefundRequest { - type Error = error_stack::Report; - fn try_from(item: &FinixRouterData<&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 { +impl From for enums::RefundStatus { + fn from(item: FinixState) -> Self { match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping + FinixState::PENDING => Self::Pending, + FinixState::SUCCEEDED => Self::Success, + FinixState::FAILED | FinixState::CANCELED | FinixState::UNKNOWN => Self::Failure, } } } -//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; +impl TryFrom> + for RefundsRouterData +{ + type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { response: Ok(RefundsResponseData { connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + refund_status: enums::RefundStatus::from(item.response.state), }), ..item.data }) } } -impl TryFrom> for RefundsRouterData { - type Error = error_stack::Report; +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { response: Ok(RefundsResponseData { connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + refund_status: enums::RefundStatus::from(item.response.state), }), ..item.data }) } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct FinixErrorResponse { - 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, +impl FinixErrorResponse { + pub fn get_message(&self) -> String { + self.embedded + .as_ref() + .and_then(|embedded| embedded.errors.as_ref()) + .and_then(|errors| errors.first()) + .and_then(|error| error.message.clone()) + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()) + } + + pub fn get_code(&self) -> String { + self.embedded + .as_ref() + .and_then(|embedded| embedded.errors.as_ref()) + .and_then(|errors| errors.first()) + .and_then(|error| error.code.clone()) + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()) + } } diff --git a/crates/hyperswitch_connectors/src/connectors/finix/transformers/request.rs b/crates/hyperswitch_connectors/src/connectors/finix/transformers/request.rs new file mode 100644 index 0000000000..703c12aa84 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/finix/transformers/request.rs @@ -0,0 +1,213 @@ +use std::collections::HashMap; + +use common_enums::Currency; +use common_utils::{pii::Email, types::MinorUnit}; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use super::*; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FinixPaymentsRequest { + pub amount: MinorUnit, + pub currency: Currency, + pub source: Secret, + pub merchant: Secret, + pub tags: Option, + pub three_d_secure: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FinixCaptureRequest { + pub capture_amount: MinorUnit, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FinixCancelRequest { + pub void_me: bool, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FinixCaptureAuthorizationRequest { + pub amount: Option, + pub tags: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FinixCreateIdentityRequest { + pub entity: FinixIdentityEntity, + pub tags: Option, + #[serde(rename = "type")] + pub identity_type: FinixIdentityType, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FinixIdentityEntity { + pub phone: Option>, + pub first_name: Option>, + pub last_name: Option>, + pub email: Option, + pub personal_address: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FinixCreatePaymentInstrumentRequest { + #[serde(rename = "type")] + pub instrument_type: FinixPaymentInstrumentType, + pub name: Option>, + pub number: Option>, + pub security_code: Option>, + pub expiration_month: Option>, + pub expiration_year: Option>, + pub identity: String, + pub tags: Option, + pub address: Option, + pub card_brand: Option, + pub card_type: Option, + pub additional_data: Option>, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FinixCreateRefundRequest { + pub refund_amount: MinorUnit, +} + +impl FinixCreateRefundRequest { + pub fn new(refund_amount: MinorUnit) -> Self { + Self { refund_amount } + } +} + +// ---------- COMMON ENUMS + +#[derive(Debug, Clone)] +pub enum FinixId { + Auth(String), + Transfer(String), +} + +impl From for FinixId { + fn from(id: String) -> Self { + if id.starts_with("AU") { + Self::Auth(id) + } else if id.starts_with("TR") { + Self::Transfer(id) + } else { + // Default to Auth if the prefix doesn't match + Self::Auth(id) + } + } +} + +impl std::fmt::Display for FinixId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Auth(id) => write!(f, "{}", id), + Self::Transfer(id) => write!(f, "{}", id), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum FinixState { + PENDING, + SUCCEEDED, + FAILED, + CANCELED, + #[serde(other)] + UNKNOWN, + // RETURNED +} +impl FinixState { + pub fn is_failure(&self) -> bool { + match self { + Self::PENDING | Self::SUCCEEDED => false, + Self::FAILED | Self::CANCELED | Self::UNKNOWN => true, + } + } +} +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum FinixPaymentType { + DEBIT, + CREDIT, + REVERSAL, + FEE, + ADJUSTMENT, + DISPUTE, + RESERVE, + SETTLEMENT, + #[serde(other)] + UNKNOWN, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum FinixPaymentInstrumentType { + #[serde(rename = "PAYMENT_CARD")] + PaymentCard, + + #[serde(rename = "BANK_ACCOUNT")] + BankAccount, + #[serde(other)] + Unknown, +} + +/// Represents the type of a payment card. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum FinixCardType { + DEBIT, + CREDIT, + PREPAID, + #[serde(other)] + UNKNOWN, +} + +/// 3D Secure authentication details. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FinixThreeDSecure { + pub authenticated: Option, + pub liability_shift: Option>, + pub version: Option, + pub eci: Option>, + pub cavv: Option>, + pub xid: Option>, +} + +/// Key-value pair tags. +pub type FinixTags = HashMap; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FinixAddress { + pub line1: Option>, + pub line2: Option>, + pub city: Option, + pub region: Option>, + pub postal_code: Option>, + pub country: Option, +} + +/// The type of the business. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum FinixIdentityType { + PERSONAL, +} + +pub enum FinixFlow { + Auth, + Transfer, + Capture, +} + +impl FinixFlow { + pub fn get_flow_for_auth(capture_method: CaptureMethod) -> Self { + match capture_method { + CaptureMethod::SequentialAutomatic | CaptureMethod::Automatic => Self::Transfer, + CaptureMethod::Manual | CaptureMethod::ManualMultiple | CaptureMethod::Scheduled => { + Self::Auth + } + } + } +} +pub struct FinixAuthType { + pub finix_user_name: Secret, + pub finix_password: Secret, + pub merchant_id: Secret, +} diff --git a/crates/hyperswitch_connectors/src/connectors/finix/transformers/response.rs b/crates/hyperswitch_connectors/src/connectors/finix/transformers/response.rs new file mode 100644 index 0000000000..9f29d9a75f --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/finix/transformers/response.rs @@ -0,0 +1,79 @@ +use std::collections::HashMap; + +use common_enums::Currency; +use common_utils::types::MinorUnit; +use serde::{Deserialize, Serialize}; + +use super::*; +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct FinixPaymentsResponse { + pub id: String, + pub created_at: Option, + pub updated_at: Option, + pub application: Option>, + pub amount: MinorUnit, + pub captured_amount: Option, + pub currency: Currency, + pub is_void: Option, + pub source: Option>, + pub state: FinixState, + pub failure_code: Option, + pub messages: Option>, + pub failure_message: Option, + pub transfer: Option, + pub tags: FinixTags, + #[serde(rename = "type")] + pub payment_type: Option, + // pub trace_id: String, + pub three_d_secure: Option, + // Add other fields from the API response as needed. +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct FinixIdentityResponse { + pub id: String, + pub created_at: Option, + pub updated_at: Option, + pub application: Option, + pub entity: Option>, + pub tags: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct FinixInstrumentResponse { + pub id: String, + pub created_at: String, + pub updated_at: String, + pub application: String, + pub identity: Option, + #[serde(rename = "type")] + pub instrument_type: FinixPaymentInstrumentType, + pub tags: Option, + pub card_type: Option, + pub card_brand: Option, + pub fingerprint: Option, + pub address: Option, + pub name: Option>, + pub currency: Option, + pub enabled: bool, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct FinixErrorResponse { + // pub status_code: u16, + pub total: Option, + #[serde(rename = "_embedded")] + pub embedded: Option, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct FinixErrorEmbedded { + pub errors: Option>, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct FinixError { + // pub logref: Option, + pub message: Option, + pub code: Option, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index b39b68de1b..f143073a03 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -1474,7 +1474,6 @@ macro_rules! default_imp_for_create_customer { } default_imp_for_create_customer!( - connectors::Finix, connectors::Vgs, connectors::Aci, connectors::Adyen, diff --git a/crates/router/src/core/connector_validation.rs b/crates/router/src/core/connector_validation.rs index db8f6be6b9..a38e55e420 100644 --- a/crates/router/src/core/connector_validation.rs +++ b/crates/router/src/core/connector_validation.rs @@ -586,6 +586,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { paytm::transformers::PaytmAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Finix => { + finix::transformers::FinixAuthType::try_from(self.auth_type)?; + Ok(()) + } } } } diff --git a/crates/router/src/types/api/connector_mapping.rs b/crates/router/src/types/api/connector_mapping.rs index ba8d741c37..b4de4ff3ab 100644 --- a/crates/router/src/types/api/connector_mapping.rs +++ b/crates/router/src/types/api/connector_mapping.rs @@ -254,6 +254,9 @@ impl ConnectorData { enums::Connector::Facilitapay => { Ok(ConnectorEnum::Old(Box::new(connector::Facilitapay::new()))) } + enums::Connector::Finix => { + Ok(ConnectorEnum::Old(Box::new(connector::Finix::new()))) + } enums::Connector::Fiserv => { Ok(ConnectorEnum::Old(Box::new(connector::Fiserv::new()))) } diff --git a/crates/router/src/types/api/feature_matrix.rs b/crates/router/src/types/api/feature_matrix.rs index 604ffc8b6d..e4ba354aac 100644 --- a/crates/router/src/types/api/feature_matrix.rs +++ b/crates/router/src/types/api/feature_matrix.rs @@ -169,6 +169,9 @@ impl FeatureMatrixConnectorData { enums::Connector::Facilitapay => { Ok(ConnectorEnum::Old(Box::new(connector::Facilitapay::new()))) } + enums::Connector::Finix => { + Ok(ConnectorEnum::Old(Box::new(connector::Finix::new()))) + } enums::Connector::Fiserv => { Ok(ConnectorEnum::Old(Box::new(connector::Fiserv::new()))) } diff --git a/crates/router/src/types/connector_transformers.rs b/crates/router/src/types/connector_transformers.rs index 34895bbdd1..9b22fc9719 100644 --- a/crates/router/src/types/connector_transformers.rs +++ b/crates/router/src/types/connector_transformers.rs @@ -61,6 +61,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Ebanx => Self::Ebanx, api_enums::Connector::Elavon => Self::Elavon, api_enums::Connector::Facilitapay => Self::Facilitapay, + api_enums::Connector::Finix => Self::Finix, api_enums::Connector::Fiserv => Self::Fiserv, api_enums::Connector::Fiservemea => Self::Fiservemea, api_enums::Connector::Fiuu => Self::Fiuu, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Finix.js b/cypress-tests/cypress/e2e/configs/Payment/Finix.js new file mode 100644 index 0000000000..24f5ddd2a7 --- /dev/null +++ b/cypress-tests/cypress/e2e/configs/Payment/Finix.js @@ -0,0 +1,520 @@ +import { customerAcceptance, cardRequiredField } from "./Commons"; +import { getCustomExchange } from "./Modifiers"; + +// Card details for non-3DS payment +const successfulNo3DSCardDetails = { + card_number: "4111111111111111", // Visa test card + card_exp_month: "10", + card_exp_year: "2050", + card_holder_name: "Test User", + card_cvc: "123", +}; +const singleUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + single_use: { + amount: 8000, + currency: "USD", + }, + }, +}; +const multiUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + multi_use: { + amount: 8000, + currency: "USD", + }, + }, +}; +// Finix doesn't support 3DS, but keeping this for common pattern +const failedNo3DSCardDetails = { + card_number: "4000000000000002", // Failed card + card_exp_month: "01", + card_exp_year: "2035", + card_holder_name: "Test User", + card_cvc: "123", +}; + +// Payment method data for non-3DS +const payment_method_data_no3ds = { + card: { + last4: "1111", + card_type: null, + card_network: null, + card_issuer: null, + card_issuing_country: null, + card_isin: "411111", + card_extended_bin: null, + card_exp_month: "10", + card_exp_year: "2050", + card_holder_name: "Test User", + payment_checks: null, + authentication_data: null, + }, + billing: null, +}; + +const requiredFields = { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["finix"], + }, + ], + required_fields: cardRequiredField, + }, + ], + }, + ], +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "on_session", + }, + }, + }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + amount: 11500, + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 11500, + }, + }, + }, + // Payment confirmation with shipping cost + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SessionToken: { + Response: { + status: 200, + body: { + session_token: [], + }, + }, + }, + // Non-3DS Manual Capture - Card is authorized but requires explicit capture + No3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + capture_method: "manual", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + payment_method: "card", + attempt_count: 1, + payment_method_data: payment_method_data_no3ds, + }, + }, + }, + // Non-3DS Auto Capture - Card is authorized and captured automatically + "3DSAutoCapture": { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", // not implemented + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + }, + Response: { + status: 401, + body: { + status: "requires_customer_action", + }, + }, + }, + + MandateSingleUseNo3DSAutoCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support mandates + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 400, + body: {}, + }, + }), + + MandateSingleUseNo3DSManualCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support mandates + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }), + + MandateMultiUseNo3DSAutoCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support mandates + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }), + + MandateMultiUseNo3DSManualCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support mandates + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }), + MITManualCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + currency: "EUR", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MITAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + currency: "EUR", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + + // ===== SAVE CARD SCENARIOS ===== + // Note: Authipay may not support save card, marked as TRIGGER_SKIP + + SaveCardUseNo3DSAutoCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support save card + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 400, + body: { + status: "succeeded", + }, + }, + }), + + SaveCardUseNo3DSManualCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support save card + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }), + ZeroAuthMandate: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip as requested by user + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 401, + body: { + status: "processing", + }, + }, + }), + "3DSManualCapture": { + Configs: { + TRIGGER_SKIP: true, // not implemented + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + }, + Response: { + status: 401, + body: { + status: "requires_customer_action", + }, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + payment_method: "card", + attempt_count: 1, + payment_method_data: payment_method_data_no3ds, + }, + }, + }, + // Failed Non-3DS payment + No3DSFailPayment: { + Request: { + payment_method: "card", + payment_method_data: { + card: failedNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "failed", + error_code: "card_declined", + error_message: "Card was declined", + unified_code: "UE_9000", + unified_message: "Something went wrong", + }, + }, + }, + // Capture a previously authorized payment + Capture: { + Request: { + amount_to_capture: 6000, + }, + Response: { + status: 200, + body: { + status: "processing", // capture takes 1-2 min to process + amount: 6000, + amount_capturable: 6000, + }, + }, + }, + // Partial capture of an authorized payment + PartialCapture: { + Request: { + amount_to_capture: 2000, + }, + Response: { + status: 200, + body: { + status: "processing", + amount: 6000, + }, + }, + }, + // Void (cancel) an authorized payment + Void: { + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + // Full refund of a captured payment + Refund: { + Request: { + amount: 6000, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + // Partial refund of a captured payment + PartialRefund: { + Request: { + amount: 2000, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + manualPaymentRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + amount: 6000, + }, + Response: { + status: 400, + + body: { + type: "invalid_request", + message: + "This Payment could not be refund because it has a status of processing. The expected state is succeeded, partially_captured", + code: "IR_14", + }, + }, + }, + manualPaymentPartialRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + amount: 2000, + }, + Response: { + status: 400, + body: { + type: "invalid_request", + message: + "This Payment could not be refund because it has a status of processing. The expected state is succeeded, partially_captured", + code: "IR_14", + }, + }, + }, + // Sync refund status (check refund status) + SyncRefund: { + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + }, + pm_list: { + PmListResponse: { + PmListNull: { + payment_methods: [], + }, + pmListDynamicFieldWithoutBilling: requiredFields, + pmListDynamicFieldWithBilling: requiredFields, + pmListDynamicFieldWithNames: requiredFields, + pmListDynamicFieldWithEmail: requiredFields, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/configs/Payment/Utils.js b/cypress-tests/cypress/e2e/configs/Payment/Utils.js index a97d878229..9d3cbe06a7 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Utils.js @@ -25,6 +25,7 @@ import { connectorDetails as dlocalConnectorDetails } from "./Dlocal.js"; import { connectorDetails as elavonConnectorDetails } from "./Elavon.js"; import { connectorDetails as facilitapayConnectorDetails } from "./Facilitapay.js"; import { connectorDetails as fiservConnectorDetails } from "./Fiserv.js"; +import { connectorDetails as finixConnectorDetails } from "./Finix.js"; import { connectorDetails as fiservemeaConnectorDetails } from "./Fiservemea.js"; import { connectorDetails as fiuuConnectorDetails } from "./Fiuu.js"; import { connectorDetails as forteConnectorDetails } from "./Forte.js"; @@ -92,6 +93,7 @@ const connectorDetails = { fiserv: fiservConnectorDetails, fiservemea: fiservemeaConnectorDetails, fiuu: fiuuConnectorDetails, + finix: finixConnectorDetails, forte: forteConnectorDetails, getnet: getnetConnectorDetails, globalpay: globalpayConnectorDetails,