diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index ccd6d2e11b7..67a9aa9735d 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -11183,6 +11183,7 @@ "cashtocode", "celero", "chargebee", + "checkbook", "checkout", "coinbase", "coingate", @@ -28310,6 +28311,7 @@ "celero", "chargebee", "custombilling", + "checkbook", "checkout", "coinbase", "coingate", diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index 5550c859ca9..23092be8de9 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -8198,6 +8198,7 @@ "cashtocode", "celero", "chargebee", + "checkbook", "checkout", "coinbase", "coingate", @@ -22824,6 +22825,7 @@ "celero", "chargebee", "custombilling", + "checkbook", "checkout", "coinbase", "coingate", diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index ddff65fa9c0..d0a49abaf8d 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -346,6 +346,9 @@ credit = { country = "AT,BE,BG,HR,CY,CZ,DK,EE,FI,FR,DE,GR,HU,IS,IE,IT,LV,LI,LT,L google_pay = { country = "AL, DZ, AS, AO, AG, AR, AU, AT, AZ, BH, BY, BE, BR, CA, BG, CL, CO, HR, DK, DO, EE, EG, FI, FR, DE, GR, HK, HU, IN, ID, IE, IL, IT, JP, JO, KZ, KE, KW, LV, LB, LT, LU, MY, MX, NL, NZ, NO, OM, PK, PA, PE, PH, PL, PT, QA, RO, SA, SG, SK, ZA, ES, LK, SE, CH, TH, TW, TR, UA, AE, US, UY, VN", currency = "AED, ALL, AOA, AUD, AZN, BGN, BHD, BRL, CAD, CHF, CLP, COP, CZK, DKK, DOP, DZD, EGP, EUR, GBP, HKD, HUF, IDR, ILS, INR, JPY, KES, KWD, KZT, LKR, MXN, MYR, NOK, NZD, OMR, PAB, PEN, PHP, PKR, PLN, QAR, RON, SAR, SEK, SGD, THB, TRY, TWD, UAH, USD, UYU, VND, XCD, ZAR" } apple_pay = { country = "AM, AT, AZ, BY, BE, BG, HR, CY, DK, EE, FO, FI, FR, GE, DE, GR, GL, GG, HU, IS, IE, IM, IT, KZ, JE, LV, LI, LT, LU, MT, MD, MC, ME, NL, NO, PL, PT, RO, SM, RS, SK, SI, ES, SE, CH, UA, GB, VA, AU , HK, JP , MY , MN, NZ, SG, TW, VN, EG , MA, ZA, AR, BR, CL, CO, CR, DO, EC, SV, GT, HN, MX, PA, PY, PE, UY, BH, IL, JO, KW, OM,QA, SA, AE, CA", currency = "EGP, MAD, ZAR, AUD, CNY, HKD, JPY, MOP, MYR, MNT, NZD, SGD, KRW, TWD, VND, AMD, EUR, BGN, CZK, DKK, GEL, GBP, HUF, ISK, KZT, CHF, MDL, NOK, PLN, RON, RSD, SEK, UAH, BRL, COP, CRC, DOP, GTQ, HNL, MXN, PAB, PYG, PEN, BSD, UYU, BHD, ILS, JOD, KWD, OMR, QAR, SAR, AED, CAD, USD" } +[pm_filters.checkbook] +ach = { country = "US", currency = "USD" } + [pm_filters.elavon] credit = { country = "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,CLP,CNY,COP,CRC,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,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 = "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,CLP,CNY,COP,CRC,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,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" } diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 11d96d6269f..79de6f569fc 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -546,6 +546,9 @@ eps = { country = "AT" , currency = "EUR" } mb_way = { country = "PT" , currency = "EUR" } sofort = { country = "AT,BE,FR,DE,IT,PL,ES,CH,GB" , currency = "EUR"} +[pm_filters.checkbook] +ach = { country = "US", currency = "USD" } + [pm_filters.cashtocode] classic = { 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" } evoucher = { 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" } diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index a372c873c78..4f95f8a2ed5 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -421,6 +421,9 @@ apple_pay = { currency = "USD" } google_pay = { currency = "USD" } samsung_pay = { currency = "USD" } +[pm_filters.checkbook] +ach = { country = "US", currency = "USD" } + [pm_filters.cybersource] credit = { currency = "USD,GBP,EUR,PLN,SEK" } debit = { currency = "USD,GBP,EUR,PLN,SEK" } diff --git a/config/development.toml b/config/development.toml index 173d9363f2a..3f7ba18a9b9 100644 --- a/config/development.toml +++ b/config/development.toml @@ -620,6 +620,9 @@ credit = { country = "AT,BE,BG,HR,CY,CZ,DK,EE,FI,FR,DE,GR,HU,IS,IE,IT,LV,LI,LT,L google_pay = { country = "AL, DZ, AS, AO, AG, AR, AU, AT, AZ, BH, BY, BE, BR, CA, BG, CL, CO, HR, DK, DO, EE, EG, FI, FR, DE, GR, HK, HU, IN, ID, IE, IL, IT, JP, JO, KZ, KE, KW, LV, LB, LT, LU, MY, MX, NL, NZ, NO, OM, PK, PA, PE, PH, PL, PT, QA, RO, SA, SG, SK, ZA, ES, LK, SE, CH, TH, TW, TR, UA, AE, US, UY, VN", currency = "AED, ALL, AOA, AUD, AZN, BGN, BHD, BRL, CAD, CHF, CLP, COP, CZK, DKK, DOP, DZD, EGP, EUR, GBP, HKD, HUF, IDR, ILS, INR, JPY, KES, KWD, KZT, LKR, MXN, MYR, NOK, NZD, OMR, PAB, PEN, PHP, PKR, PLN, QAR, RON, SAR, SEK, SGD, THB, TRY, TWD, UAH, USD, UYU, VND, XCD, ZAR" } apple_pay = { country = "AM, AT, AZ, BY, BE, BG, HR, CY, DK, EE, FO, FI, FR, GE, DE, GR, GL, GG, HU, IS, IE, IM, IT, KZ, JE, LV, LI, LT, LU, MT, MD, MC, ME, NL, NO, PL, PT, RO, SM, RS, SK, SI, ES, SE, CH, UA, GB, VA, AU , HK, JP , MY , MN, NZ, SG, TW, VN, EG , MA, ZA, AR, BR, CL, CO, CR, DO, EC, SV, GT, HN, MX, PA, PY, PE, UY, BH, IL, JO, KW, OM,QA, SA, AE, CA", currency = "EGP, MAD, ZAR, AUD, CNY, HKD, JPY, MOP, MYR, MNT, NZD, SGD, KRW, TWD, VND, AMD, EUR, BGN, CZK, DKK, GEL, GBP, HUF, ISK, KZT, CHF, MDL, NOK, PLN, RON, RSD, SEK, UAH, BRL, COP, CRC, DOP, GTQ, HNL, MXN, PAB, PYG, PEN, BSD, UYU, BHD, ILS, JOD, KWD, OMR, QAR, SAR, AED, CAD, USD" } +[pm_filters.checkbook] +ach = { country = "US", currency = "USD" } + [pm_filters.nexixpay] credit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU,AU,BR,US", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } debit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU,AU,BR,US", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index cb5195925c5..11aa2e7104e 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -78,7 +78,7 @@ pub enum RoutableConnectors { Celero, Chargebee, Custombilling, - // Checkbook, + Checkbook, Checkout, Coinbase, Coingate, @@ -238,7 +238,7 @@ pub enum Connector { Cashtocode, Celero, Chargebee, - // Checkbook, + Checkbook, Checkout, Coinbase, Coingate, @@ -426,7 +426,7 @@ impl Connector { | Self::Cashtocode | Self::Celero | Self::Chargebee - // | Self::Checkbook + | Self::Checkbook | Self::Coinbase | Self::Coingate | Self::Cryptopay @@ -593,7 +593,7 @@ impl From for Connector { RoutableConnectors::Celero => Self::Celero, RoutableConnectors::Chargebee => Self::Chargebee, RoutableConnectors::Custombilling => Self::Custombilling, - // RoutableConnectors::Checkbook => Self::Checkbook, + RoutableConnectors::Checkbook => Self::Checkbook, RoutableConnectors::Checkout => Self::Checkout, RoutableConnectors::Coinbase => Self::Coinbase, RoutableConnectors::Cryptopay => Self::Cryptopay, @@ -716,7 +716,7 @@ impl TryFrom for RoutableConnectors { Connector::Cashtocode => Ok(Self::Cashtocode), Connector::Celero => Ok(Self::Celero), Connector::Chargebee => Ok(Self::Chargebee), - // Connector::Checkbook => Ok(Self::Checkbook), + Connector::Checkbook => Ok(Self::Checkbook), Connector::Checkout => Ok(Self::Checkout), Connector::Coinbase => Ok(Self::Coinbase), Connector::Coingate => Ok(Self::Coingate), diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 61736a42edf..cb6c9062ca3 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -390,6 +390,7 @@ impl ConnectorConfig { Connector::Cashtocode => Ok(connector_data.cashtocode), Connector::Celero => Ok(connector_data.celero), Connector::Chargebee => Ok(connector_data.chargebee), + Connector::Checkbook => Ok(connector_data.checkbook), Connector::Checkout => Ok(connector_data.checkout), Connector::Coinbase => Ok(connector_data.coinbase), Connector::Coingate => Ok(connector_data.coingate), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 9e493eb6a54..0a3897aedc4 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -1360,6 +1360,17 @@ merchant_id_evoucher="MerchantId Evoucher" [cashtocode.connector_webhook_details] merchant_secret="Source verification key" +[checkbook] +[[checkbook.bank_transfer]] +payment_method_type = "ach" + +[checkbook.connector_auth.BodyKey] +key1 = "Checkbook Publishable key" +api_key = "Checkbook API Secret key" + +[checkbook.connector_webhook_details] +merchant_secret="Source verification key" + [checkout] [[checkout.credit]] payment_method_type = "Mastercard" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index e81c3f6aac9..cebc0aaabec 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -1122,6 +1122,15 @@ key1 = "Secret Key" [cryptopay.connector_webhook_details] merchant_secret = "Source verification key" +[checkbook] +[[checkbook.bank_transfer]] + payment_method_type = "ach" +[checkbook.connector_auth.BodyKey] + key1 = "Checkbook Publishable key" + api_key = "Checkbook API Secret key" +[checkbook.connector_webhook_details] + merchant_secret="Source verification key" + [checkout] [[checkout.credit]] payment_method_type = "Mastercard" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index bdd9cde57f0..d16708ff4f4 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1359,6 +1359,15 @@ merchant_id_evoucher = "MerchantId Evoucher" [cashtocode.connector_webhook_details] merchant_secret = "Source verification key" +[checkbook] +[[checkbook.bank_transfer]] + payment_method_type = "ach" +[checkbook.connector_auth.BodyKey] + key1 = "Checkbook Publishable key" + api_key = "Checkbook API Secret key" +[checkbook.connector_webhook_details] + merchant_secret="Source verification key" + [checkout] [[checkout.credit]] payment_method_type = "Mastercard" diff --git a/crates/hyperswitch_connectors/src/connectors/checkbook.rs b/crates/hyperswitch_connectors/src/connectors/checkbook.rs index e0f31ceb57f..90148c84f88 100644 --- a/crates/hyperswitch_connectors/src/connectors/checkbook.rs +++ b/crates/hyperswitch_connectors/src/connectors/checkbook.rs @@ -1,12 +1,16 @@ pub mod transformers; +use std::sync::LazyLock; + +use api_models::{enums, payments::PaymentIdType}; use common_utils::{ + crypto, errors::CustomResult, - ext_traits::BytesExt, + ext_traits::{ByteSliceExt, BytesExt}, request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector}, }; -use error_stack::{report, ResultExt}; +use error_stack::ResultExt; use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ @@ -19,9 +23,12 @@ use hyperswitch_domain_models::{ PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, }, - router_response_types::{PaymentsResponseData, RefundsResponseData}, + router_response_types::{ + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, + }, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; @@ -39,7 +46,7 @@ use hyperswitch_interfaces::{ use masking::{ExposeInterface, Mask}; use transformers as checkbook; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{constants::headers, types::ResponseRouterData}; #[derive(Clone)] pub struct Checkbook { @@ -115,9 +122,14 @@ impl ConnectorCommon for Checkbook { ) -> CustomResult)>, errors::ConnectorError> { let auth = checkbook::CheckbookAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let auth_key = format!( + "{}:{}", + auth.publishable_key.expose(), + auth.secret_key.expose() + ); Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + auth_key.into_masked(), )]) } @@ -148,9 +160,7 @@ impl ConnectorCommon for Checkbook { } } -impl ConnectorValidation for Checkbook { - //TODO: implement functions when support enabled -} +impl ConnectorValidation for Checkbook {} impl ConnectorIntegration for Checkbook { //TODO: implement sessions flow @@ -179,9 +189,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/v3/invoice", self.base_url(connectors))) } fn get_request_body( @@ -189,14 +199,11 @@ impl ConnectorIntegration CustomResult { - let amount = utils::convert_amount( - self.amount_converter, - req.request.minor_amount, - req.request.currency, - )?; - - let connector_router_data = checkbook::CheckbookRouterData::from((amount, req)); - let connector_req = checkbook::CheckbookPaymentsRequest::try_from(&connector_router_data)?; + let amount = self + .amount_converter + .convert(req.request.minor_amount, req.request.currency) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let connector_req = checkbook::CheckbookPaymentsRequest::try_from((amount, req))?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -265,10 +272,19 @@ impl ConnectorIntegration for Che 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_txn_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}/v3/invoice/{}", + self.base_url(connectors), + connector_txn_id + )) } fn build_request( @@ -314,64 +330,53 @@ impl ConnectorIntegration for Che } } -impl ConnectorIntegration for Checkbook { +impl ConnectorIntegration for Checkbook {} + +impl ConnectorIntegration for Checkbook { fn get_headers( &self, - req: &PaymentsCaptureRouterData, + 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: &PaymentsCaptureRouterData, - _connectors: &Connectors, + req: &PaymentsCancelRouterData, + 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()) + Ok(format!( + "{}v3/invoice/{}", + self.base_url(connectors), + req.request.connector_transaction_id + )) } fn build_request( &self, - req: &PaymentsCaptureRouterData, + req: &PaymentsCancelRouterData, connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() - .method(Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .method(Method::Delete) + .url(&types::PaymentsVoidType::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, - )?) + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &PaymentsCaptureRouterData, + data: &PaymentsCancelRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: checkbook::CheckbookPaymentsResponse = res .response - .parse_struct("Checkbook PaymentsCaptureResponse") + .parse_struct("Checkbook PaymentsCancelResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -391,8 +396,6 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Checkbook {} - impl ConnectorIntegration for Checkbook { fn get_headers( &self, @@ -411,23 +414,23 @@ impl ConnectorIntegration for Checkbo _req: &RefundsRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Err(errors::ConnectorError::NotSupported { + message: "Refunds are not supported".to_string(), + connector: "checkbook", + } + .into()) } fn get_request_body( &self, - req: &RefundsRouterData, + _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 = checkbook::CheckbookRouterData::from((refund_amount, req)); - let connector_req = checkbook::CheckbookRefundRequest::try_from(&connector_router_data)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Err(errors::ConnectorError::NotSupported { + message: "Refunds are not supported".to_string(), + connector: "checkbook", + } + .into()) } fn build_request( @@ -451,21 +454,11 @@ impl ConnectorIntegration for Checkbo fn handle_response( &self, - data: &RefundsRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, + _data: &RefundsRouterData, + _event_builder: Option<&mut ConnectorEvent>, + _res: Response, ) -> CustomResult, errors::ConnectorError> { - let response: checkbook::RefundResponse = res - .response - .parse_struct("checkbook 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, - }) + Err(errors::ConnectorError::NotImplemented("Refunds are not supported".to_string()).into()) } fn get_error_response( @@ -518,21 +511,11 @@ impl ConnectorIntegration for Checkbook fn handle_response( &self, - data: &RefundSyncRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, + _data: &RefundSyncRouterData, + _event_builder: Option<&mut ConnectorEvent>, + _res: Response, ) -> CustomResult { - let response: checkbook::RefundResponse = res - .response - .parse_struct("checkbook 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, - }) + Err(errors::ConnectorError::NotImplemented("Refunds are not supported".to_string()).into()) } fn get_error_response( @@ -548,24 +531,128 @@ impl ConnectorIntegration for Checkbook impl webhooks::IncomingWebhook for Checkbook { fn get_webhook_object_reference_id( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let details: checkbook::CheckbookPaymentsResponse = request + .body + .parse_struct("CheckbookWebhookResponse") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + PaymentIdType::ConnectorTransactionId(details.id), + )) } fn get_webhook_event_type( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let details: checkbook::CheckbookPaymentsResponse = request + .body + .parse_struct("CheckbookWebhookResponse") + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; + Ok(api_models::webhooks::IncomingWebhookEvent::from( + details.status, + )) } fn get_webhook_resource_object( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let details: checkbook::CheckbookPaymentsResponse = request + .body + .parse_struct("CheckbookWebhookResponse") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + Ok(Box::new(details)) + } + + fn get_webhook_source_verification_algorithm( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::HmacSha256)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let header_value = request + .headers + .get("signature") + .ok_or(errors::ConnectorError::WebhookSignatureNotFound) + .attach_printable("Failed to get signature for checkbook")? + .to_str() + .map_err(|_| errors::ConnectorError::WebhookSignatureNotFound) + .attach_printable("Failed to get signature for checkbook")?; + let signature = header_value + .split(',') + .find_map(|s| s.strip_prefix("signature=")) + .ok_or(errors::ConnectorError::WebhookSignatureNotFound)?; + hex::decode(signature) + .change_context(errors::ConnectorError::WebhookSignatureNotFound) + .attach_printable("Failed to decrypt checkbook webhook payload for verification") + } + + fn get_webhook_source_verification_message( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + _merchant_id: &common_utils::id_type::MerchantId, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let header_value = request + .headers + .get("signature") + .ok_or(errors::ConnectorError::WebhookSignatureNotFound)? + .to_str() + .map_err(|_| errors::ConnectorError::WebhookSignatureNotFound)?; + let nonce = header_value + .split(',') + .find_map(|s| s.strip_prefix("nonce=")) + .ok_or(errors::ConnectorError::WebhookSignatureNotFound)?; + let message = format!("{}{}", String::from_utf8_lossy(request.body), nonce); + Ok(message.into_bytes()) } } -impl ConnectorSpecifications for Checkbook {} +static CHECKBOOK_SUPPORTED_PAYMENT_METHODS: LazyLock = + LazyLock::new(|| { + let supported_capture_methods = vec![enums::CaptureMethod::Automatic]; + + let mut checkbook_supported_payment_methods = SupportedPaymentMethods::new(); + + checkbook_supported_payment_methods.add( + enums::PaymentMethod::BankTransfer, + enums::PaymentMethodType::Ach, + PaymentMethodDetails { + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::NotSupported, + supported_capture_methods, + specific_features: None, + }, + ); + checkbook_supported_payment_methods + }); + +static CHECKBOOK_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + display_name: "Checkbook", + description: + "Checkbook is a payment platform that allows users to send and receive digital checks.", + connector_type: enums::PaymentConnectorCategory::PaymentGateway, +}; + +static CHECKBOOK_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 1] = [enums::EventClass::Payments]; + +impl ConnectorSpecifications for Checkbook { + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + Some(&CHECKBOOK_CONNECTOR_INFO) + } + + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + Some(&*CHECKBOOK_SUPPORTED_PAYMENT_METHODS) + } + fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { + Some(&CHECKBOOK_SUPPORTED_WEBHOOK_FLOWS) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/checkbook/transformers.rs b/crates/hyperswitch_connectors/src/connectors/checkbook/transformers.rs index d9f009f78fe..176fe81f962 100644 --- a/crates/hyperswitch_connectors/src/connectors/checkbook/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/checkbook/transformers.rs @@ -1,102 +1,84 @@ -use common_enums::enums; -use common_utils::types::FloatMajorUnit; +use api_models::webhooks::IncomingWebhookEvent; +use common_utils::{pii, types::FloatMajorUnit}; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, + payment_method_data::{BankTransferData, PaymentMethodData}, router_data::{ConnectorAuthType, RouterData}, - router_flow_types::refunds::{Execute, RSync}, router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + router_response_types::PaymentsResponseData, + types::PaymentsAuthorizeRouterData, }; -use hyperswitch_interfaces::errors; +use hyperswitch_interfaces::errors::ConnectorError; use masking::Secret; use serde::{Deserialize, Serialize}; use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + types::ResponseRouterData, + utils::{get_unimplemented_payment_method_error_message, RouterData as _}, }; -//TODO: Fill the struct with respective fields -pub struct CheckbookRouterData { - pub amount: FloatMajorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. - pub router_data: T, -} - -impl From<(FloatMajorUnit, T)> for CheckbookRouterData { - fn from((amount, item): (FloatMajorUnit, 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)] +#[derive(Debug, Serialize)] pub struct CheckbookPaymentsRequest { + name: Secret, + recipient: pii::Email, amount: FloatMajorUnit, - card: CheckbookCard, + description: String, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct CheckbookCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, -} - -impl TryFrom<&CheckbookRouterData<&PaymentsAuthorizeRouterData>> for CheckbookPaymentsRequest { - type Error = error_stack::Report; +impl TryFrom<(FloatMajorUnit, &PaymentsAuthorizeRouterData)> for CheckbookPaymentsRequest { + type Error = error_stack::Report; fn try_from( - item: &CheckbookRouterData<&PaymentsAuthorizeRouterData>, + (amount, item): (FloatMajorUnit, &PaymentsAuthorizeRouterData), ) -> Result { - match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = CheckbookCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; - Ok(Self { - amount: item.amount, - card, - }) - } - _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + match item.request.payment_method_data.clone() { + PaymentMethodData::BankTransfer(bank_transfer_data) => match *bank_transfer_data { + BankTransferData::AchBankTransfer {} => Ok(Self { + name: item.get_billing_full_name()?, + recipient: item.get_billing_email()?, + amount, + description: item.get_description()?, + }), + _ => Err(ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("Checkbook"), + ) + .into()), + }, + _ => Err(ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("Checkbook"), + ) + .into()), } } } -//TODO: Fill the struct with respective fields -// Auth Struct pub struct CheckbookAuthType { - pub(super) api_key: Secret, + pub(super) publishable_key: Secret, + pub(super) secret_key: Secret, } impl TryFrom<&ConnectorAuthType> for CheckbookAuthType { - type Error = error_stack::Report; + type Error = error_stack::Report; fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + ConnectorAuthType::BodyKey { key1, api_key } => Ok(Self { + publishable_key: key1.to_owned(), + secret_key: api_key.to_owned(), }), - _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + _ => Err(ConnectorError::FailedToObtainAuthType.into()), } } } // PaymentsResponse -//TODO: Append the remaining status flags #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum CheckbookPaymentStatus { - Succeeded, + Unpaid, + InProcess, + Paid, + Mailed, + Printed, Failed, + Expired, + Void, #[default] Processing, } @@ -104,24 +86,48 @@ pub enum CheckbookPaymentStatus { impl From for common_enums::AttemptStatus { fn from(item: CheckbookPaymentStatus) -> Self { match item { - CheckbookPaymentStatus::Succeeded => Self::Charged, - CheckbookPaymentStatus::Failed => Self::Failure, - CheckbookPaymentStatus::Processing => Self::Authorizing, + CheckbookPaymentStatus::Paid + | CheckbookPaymentStatus::Mailed + | CheckbookPaymentStatus::Printed => Self::Charged, + CheckbookPaymentStatus::Failed | CheckbookPaymentStatus::Expired => Self::Failure, + CheckbookPaymentStatus::Unpaid => Self::AuthenticationPending, + CheckbookPaymentStatus::InProcess | CheckbookPaymentStatus::Processing => Self::Pending, + CheckbookPaymentStatus::Void => Self::Voided, + } + } +} + +impl From for IncomingWebhookEvent { + fn from(status: CheckbookPaymentStatus) -> Self { + match status { + CheckbookPaymentStatus::Mailed + | CheckbookPaymentStatus::Printed + | CheckbookPaymentStatus::Paid => Self::PaymentIntentSuccess, + CheckbookPaymentStatus::Failed | CheckbookPaymentStatus::Expired => { + Self::PaymentIntentFailure + } + CheckbookPaymentStatus::Unpaid + | CheckbookPaymentStatus::InProcess + | CheckbookPaymentStatus::Processing => Self::PaymentIntentProcessing, + CheckbookPaymentStatus::Void => Self::PaymentIntentCancelled, } } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct CheckbookPaymentsResponse { - status: CheckbookPaymentStatus, - id: String, + pub status: CheckbookPaymentStatus, + pub id: String, + pub amount: Option, + pub description: Option, + pub name: Option, + pub recipient: Option, } impl TryFrom> for RouterData { - type Error = error_stack::Report; + type Error = error_stack::Report; fn try_from( item: ResponseRouterData, ) -> Result { @@ -142,83 +148,6 @@ impl TryFrom TryFrom<&CheckbookRouterData<&RefundsRouterData>> for CheckbookRefundRequest { - type Error = error_stack::Report; - fn try_from(item: &CheckbookRouterData<&RefundsRouterData>) -> Result { - Ok(Self { - amount: item.amount.to_owned(), - }) - } -} - -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, 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 CheckbookErrorResponse { pub status_code: u16, 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 2dd291d16f5..62f4c098992 100644 --- a/crates/payment_methods/src/configs/payment_connector_required_fields.rs +++ b/crates/payment_methods/src/configs/payment_connector_required_fields.rs @@ -199,6 +199,7 @@ enum RequiredField { DcbMsisdn, DcbClientUid, OrderDetailsProductName, + Description, } impl RequiredField { @@ -868,6 +869,15 @@ impl RequiredField { value: None, }, ), + Self::Description => ( + "description".to_string(), + RequiredFieldInfo { + required_field: "description".to_string(), + display_name: "description".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), } } } @@ -3192,8 +3202,17 @@ fn get_bank_transfer_required_fields() -> HashMap { celero::transformers::CeleroAuthType::try_from(self.auth_type)?; Ok(()) } - // api_enums::Connector::Checkbook => { - // checkbook::transformers::CheckbookAuthType::try_from(self.auth_type)?; - // Ok(()) - // }, + api_enums::Connector::Checkbook => { + checkbook::transformers::CheckbookAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Checkout => { checkout::transformers::CheckoutAuthType::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 e80d75a7ec1..5e3649461b2 100644 --- a/crates/router/src/types/api/connector_mapping.rs +++ b/crates/router/src/types/api/connector_mapping.rs @@ -157,9 +157,9 @@ impl ConnectorData { enums::Connector::Chargebee => { Ok(ConnectorEnum::Old(Box::new(connector::Chargebee::new()))) } - // enums::Connector::Checkbook => { - // Ok(ConnectorEnum::Old(Box::new(connector::Checkbook))) - // } + enums::Connector::Checkbook => { + Ok(ConnectorEnum::Old(Box::new(connector::Checkbook::new()))) + } enums::Connector::Checkout => { Ok(ConnectorEnum::Old(Box::new(connector::Checkout::new()))) } diff --git a/crates/router/src/types/connector_transformers.rs b/crates/router/src/types/connector_transformers.rs index 47cdf592aa1..673829a06c7 100644 --- a/crates/router/src/types/connector_transformers.rs +++ b/crates/router/src/types/connector_transformers.rs @@ -27,7 +27,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Cashtocode => Self::Cashtocode, api_enums::Connector::Celero => Self::Celero, api_enums::Connector::Chargebee => Self::Chargebee, - // api_enums::Connector::Checkbook => Self::Checkbook, + api_enums::Connector::Checkbook => Self::Checkbook, api_enums::Connector::Checkout => Self::Checkout, api_enums::Connector::Coinbase => Self::Coinbase, api_enums::Connector::Coingate => Self::Coingate, diff --git a/crates/router/tests/connectors/checkbook.rs b/crates/router/tests/connectors/checkbook.rs index b6b4252f852..a312de7fdfd 100644 --- a/crates/router/tests/connectors/checkbook.rs +++ b/crates/router/tests/connectors/checkbook.rs @@ -1,6 +1,8 @@ +use std::str::FromStr; + +use hyperswitch_domain_models::address::{Address, AddressDetails}; use masking::Secret; -use router::types::{self, api, domain, storage::enums}; -use test_utils::connector_auth; +use router::types::{self, api, storage::enums, Email}; use crate::utils::{self, ConnectorActions}; @@ -12,19 +14,17 @@ impl utils::Connector for CheckbookTest { use router::connector::Checkbook; utils::construct_connector_data_old( Box::new(Checkbook::new()), - types::Connector::DummyConnector1, + types::Connector::Checkbook, api::GetToken::Connector, None, ) } fn get_auth_token(&self) -> types::ConnectorAuthType { - utils::to_connector_auth_type( - connector_auth::ConnectorAuthentication::new() - .checkbook - .expect("Missing connector authentication configuration") - .into(), - ) + types::ConnectorAuthType::BodyKey { + key1: Secret::new("dummy_publishable_key".to_string()), + api_key: Secret::new("dummy_secret_key".to_string()), + } } fn get_name(&self) -> String { @@ -35,52 +35,40 @@ impl utils::Connector for CheckbookTest { static CONNECTOR: CheckbookTest = CheckbookTest {}; fn get_default_payment_info() -> Option { - None + Some(utils::PaymentInfo { + address: Some(types::PaymentAddress::new( + None, + None, + Some(Address { + address: Some(AddressDetails { + first_name: Some(Secret::new("John".to_string())), + last_name: Some(Secret::new("Doe".to_string())), + ..Default::default() + }), + phone: None, + email: Some(Email::from_str("abc@gmail.com").unwrap()), + }), + None, + )), + ..Default::default() + }) } fn payment_method_details() -> Option { None } -// Cards Positive Tests -// Creates a payment using the manual capture flow (Non 3DS). +// Creates a payment. #[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); + assert_eq!(response.status, enums::AttemptStatus::AuthenticationPending); } -// 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). +// Synchronizes a payment. #[actix_web::test] async fn should_sync_authorized_payment() { let authorize_response = CONNECTOR @@ -90,7 +78,7 @@ async fn should_sync_authorized_payment() { let txn_id = utils::get_connector_transaction_id(authorize_response.response); let response = CONNECTOR .psync_retry_till_status_matches( - enums::AttemptStatus::Authorized, + enums::AttemptStatus::AuthenticationPending, Some(types::PaymentsSyncData { connector_transaction_id: types::ResponseId::ConnectorTransactionId( txn_id.unwrap(), @@ -101,320 +89,20 @@ async fn should_sync_authorized_payment() { ) .await .expect("PSync response"); - assert_eq!(response.status, enums::AttemptStatus::Authorized,); + assert_eq!(response.status, enums::AttemptStatus::AuthenticationPending); } -// Voids a payment using the manual capture flow (Non 3DS). +// Voids a payment. #[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()) + .authorize_payment(payment_method_details(), get_default_payment_info()) .await - .unwrap(); - assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + .expect("Authorize payment response"); 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: domain::PaymentMethodData::Card(domain::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: domain::PaymentMethodData::Card(domain::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: domain::PaymentMethodData::Card(domain::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)", - ); + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); } - -// Connector dependent test cases goes here - -// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/cypress-tests/cypress/e2e/configs/Payment/Checkbook.js b/cypress-tests/cypress/e2e/configs/Payment/Checkbook.js new file mode 100644 index 00000000000..c1ab59c1c94 --- /dev/null +++ b/cypress-tests/cypress/e2e/configs/Payment/Checkbook.js @@ -0,0 +1,57 @@ +export const connectorDetails = { + bank_transfer_pm: { + Ach: { + Request: { + amount: 333, + payment_method: "bank_transfer", + payment_method_type: "ach", + billing: { + address: { + zip: "560095", + country: "US", + first_name: "akshakaya N", + last_name: "sss", + line1: "Fasdf", + line2: "Fasdf", + city: "Fasdf", + }, + email: "johndoe@mail.com", + }, + payment_method_data: { + bank_transfer: { + ach_bank_transfer: {}, + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MissingEmail: { + Request: { + payment_method: "bank_transfer", + payment_method_type: "ach", + billing: { + address: { + first_name: "John", + last_name: "Doe", + }, + }, + currency: "USD", + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "email is a required field", + code: "IR_01", + }, + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/configs/Payment/Commons.js b/cypress-tests/cypress/e2e/configs/Payment/Commons.js index 9ad52085367..2fc01c4a388 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Commons.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Commons.js @@ -190,6 +190,12 @@ export const payment_methods_enabled = [ recurring_enabled: false, installment_payment_enabled: true, }, + { + payment_method_type: "ach", + minimum_amount: 0, + maximum_amount: 68607706, + recurring_enabled: false, + }, { payment_method_type: "instant_bank_transfer_finland", minimum_amount: 1, @@ -342,6 +348,7 @@ export const connectorDetails = { }, }, }), + Pix: getCustomExchange({ Request: { payment_method: "bank_transfer", @@ -367,6 +374,31 @@ export const connectorDetails = { currency: "BRL", }, }), + Ach: getCustomExchange({ + Request: { + payment_method: "bank_transfer", + payment_method_type: "ach", + payment_method_data: { + bank_transfer: { + ach_bank_transfer: {}, + }, + }, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "BR", + first_name: "john", + last_name: "doe", + }, + }, + currency: "BRL", + }, + }), InstantBankTransferFinland: getCustomExchange({ Request: { payment_method: "bank_transfer", diff --git a/cypress-tests/cypress/e2e/configs/Payment/Stripe.js b/cypress-tests/cypress/e2e/configs/Payment/Stripe.js index 787534499cd..777a0802bf3 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Stripe.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Stripe.js @@ -789,6 +789,38 @@ export const connectorDetails = { }, }, }, + bank_transfer_pm: { + Ach: { + Request: { + amount: 333, + payment_method: "bank_transfer", + payment_method_type: "ach", + billing: { + address: { + zip: "560095", + country: "US", + first_name: "akshakaya N", + last_name: "sss", + line1: "Fasdf", + line2: "Fasdf", + city: "Fasdf", + }, + email: "johndoe@mail.com", + }, + payment_method_data: { + bank_transfer: { + ach_bank_transfer: {}, + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + }, bank_redirect_pm: { Ideal: { Request: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/Utils.js b/cypress-tests/cypress/e2e/configs/Payment/Utils.js index 5a2bfb43391..3a1d82e6759 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Utils.js @@ -14,6 +14,7 @@ import { connectorDetails as billwerkConnectorDetails } from "./Billwerk.js"; import { connectorDetails as bluesnapConnectorDetails } from "./Bluesnap.js"; import { connectorDetails as braintreeConnectorDetails } from "./Braintree.js"; import { connectorDetails as checkoutConnectorDetails } from "./Checkout.js"; +import { connectorDetails as checkbookConnectorDetails } from "./Checkbook.js"; import { connectorDetails as commonConnectorDetails } from "./Commons.js"; import { connectorDetails as cybersourceConnectorDetails } from "./Cybersource.js"; import { connectorDetails as datatransConnectorDetails } from "./Datatrans.js"; @@ -70,6 +71,7 @@ const connectorDetails = { bluesnap: bluesnapConnectorDetails, braintree: braintreeConnectorDetails, checkout: checkoutConnectorDetails, + checkbook: checkbookConnectorDetails, commons: commonConnectorDetails, cybersource: cybersourceConnectorDetails, dlocal: dlocalConnectorDetails, diff --git a/cypress-tests/cypress/e2e/spec/Payment/00017-BankTransfers.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00017-BankTransfers.cy.js index 46c204d7894..6fb154214c7 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00017-BankTransfers.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00017-BankTransfers.cy.js @@ -182,4 +182,62 @@ describe("Bank Transfers", () => { ); }); }); + + context("Bank transfer - Ach flow", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_transfer_pm" + ]["PaymentIntent"]("Ach"); + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm bank transfer", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_transfer_pm" + ]["Ach"]; + + cy.confirmBankTransferCallTest( + fixtures.confirmBody, + data, + true, + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle bank transfer redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + const payment_method_type = globalState.get("paymentMethodType"); + + if (globalState.get("connectorId") != "checkbook") { + cy.handleBankTransferRedirection( + globalState, + payment_method_type, + expected_redirection + ); + } + }); + }); }); diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index a600bc6890a..cb982c5ed21 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -1892,6 +1892,18 @@ Cypress.Commands.add( ); globalState.set("nextActionType", "image_data_url"); } + break; + case "ach": + if ( + response.body.next_action + ?.bank_transfer_steps_and_charges_details != null + ) { + globalState.set( + "nextActionType", + "bank_transfer_steps_and_charges_details" + ); + } + break; default: expect(response.body) @@ -3231,7 +3243,12 @@ Cypress.Commands.add( const nextActionType = globalState.get("nextActionType"); const expectedUrl = new URL(expectedRedirection); - const redirectionUrl = new URL(nextActionUrl); + let redirectionUrl = null; + try { + redirectionUrl = new URL(nextActionUrl); + } catch { + /* banktransfer may not have redirection url */ + } handleRedirection( "bank_transfer", diff --git a/cypress-tests/cypress/support/redirectionHandler.js b/cypress-tests/cypress/support/redirectionHandler.js index cd1670a9314..4e614f97d85 100644 --- a/cypress-tests/cypress/support/redirectionHandler.js +++ b/cypress-tests/cypress/support/redirectionHandler.js @@ -73,8 +73,10 @@ function bankTransferRedirection( nextActionType ) { let verifyUrl = true; // Default to true, can be set to false based on conditions - switch (nextActionType) { + case "bank_transfer_steps_and_charges_details": + verifyUrl = false; + break; case "qr_code_url": cy.request(redirectionUrl.href).then((response) => { switch (connectorId) {