  - Mandates for alternate payment methods via Stripe ([#1041](https://github.com/juspay/hyperswitch/pull/1041)) ([`64721b8`](https://github.com/juspay/hyperswitch/commit/64721b80ae0d276820404ff1208af91303cf1473))
- **errors:** Add reverse errorswitch trait for foreign errors ([#909](https://github.com/juspay/hyperswitch/pull/909)) ([`ab55d21`](https://github.com/juspay/hyperswitch/commit/ab55d21013a279568379b97821da98457a10754a))

### Bug Fixes

- **common_utils:** Manually implement diesel queryable for email newtype ([#1072](https://github.com/juspay/hyperswitch/pull/1072)) ([`3519649`](https://github.com/juspay/hyperswitch/commit/35196493c4509a6f9f1c202bf8b8a6aa7605346b))
- **connector:**
  - [worldline] fix worldline unit test ([#1054](https://github.com/juspay/hyperswitch/pull/1054)) ([`3131bc8`](https://github.com/juspay/hyperswitch/commit/3131bc84af008f05508aab9049f6ee492ca89460))
  - [ACI] Add amount currency conversion and update error codes ([#1065](https://github.com/juspay/hyperswitch/pull/1065)) ([`b760cba`](https://github.com/juspay/hyperswitch/commit/b760cba5460395487c63ea4363665b0d7e5a6118))
- **mandate:**
  - Make payment_method_data optional for mandate scenario ([#1032](https://github.com/juspay/hyperswitch/pull/1032)) ([`9cb3fa2`](https://github.com/juspay/hyperswitch/commit/9cb3fa216ce490d62f99525b23430809b4943dcb))
  - Fix payment_method_data becoming empty when mandate_id is not present ([#1077](https://github.com/juspay/hyperswitch/pull/1077)) ([`5c5c3ef`](https://github.com/juspay/hyperswitch/commit/5c5c3ef3831991ccfefd9b0561f5eac976ed2191))
- **redis:** Fix recreation on redis connection pool ([#1063](https://github.com/juspay/hyperswitch/pull/1063)) ([`982c27f`](https://github.com/juspay/hyperswitch/commit/982c27fce72074d2644c0a9f229b201b927c55da))
- Impl `Drop` for `RedisConnectionPool` ([#1051](https://github.com/juspay/hyperswitch/pull/1051)) ([`3d05e50`](https://github.com/juspay/hyperswitch/commit/3d05e50abcb92fe7e6c4472faafc03fb70920048))
- Throw PreconditionFailed error when routing_algorithm is not configured ([#1017](https://github.com/juspay/hyperswitch/pull/1017)) ([`8853702`](https://github.com/juspay/hyperswitch/commit/8853702f4b98c72655d6e36ed6acc13b7c261ad5))

### Refactors

- **compatibility:** Refactor stripe compatibility routes using `web::resource` ([#1022](https://github.com/juspay/hyperswitch/pull/1022)) ([`92ae2d9`](https://github.com/juspay/hyperswitch/commit/92ae2d92f18577d5cc88805340fa63c5e50dbc37))
- **router:**
  - Nest the straight through algorithm column in payment attempt ([#1040](https://github.com/juspay/hyperswitch/pull/1040)) ([`64fa21e`](https://github.com/juspay/hyperswitch/commit/64fa21eb4fb265e122f97aaae7445fabd571be23))
  - Add the `connector_label` field to `DuplicateMerchantConnectorAccount` error message ([#1044](https://github.com/juspay/hyperswitch/pull/1044)) ([`b3772f8`](https://github.com/juspay/hyperswitch/commit/b3772f8ef13a565730ec229b612c10ed68bb3c4b))
  - Include payment method type in connector choice for session flow ([#1036](https://github.com/juspay/hyperswitch/pull/1036)) ([`73b8988`](https://github.com/juspay/hyperswitch/commit/73b8988322e3d15f90b2c4ca776d135d23e97710))
- Use newtype pattern for email addresses ([#819](https://github.com/juspay/hyperswitch/pull/819)) ([`b8e2b1c`](https://github.com/juspay/hyperswitch/commit/b8e2b1c5f42dcd41a3d02e0d2422e1407b6a41de))

- - -

## 0.5.9 (2023-05-04)

### Features

- **api_models:** Derive `Serialize`, `Eq`, `PartialEq`, `strum::Display` on `RefundStatus` ([#989](https://github.com/juspay/hyperswitch/pull/989)) ([`22a5372`](https://github.com/juspay/hyperswitch/commit/22a5372481bbf854cffb8b683606cdf0644a5f54))
- **cards:** Validate card security code and expiration ([#874](https://github.com/juspay/hyperswitch/pull/874)) ([`0b7bc7b`](https://github.com/juspay/hyperswitch/commit/0b7bc7bcd23498485c831d1c78187c433b8bb3c7))
- **connector:**
  - [ACI] Add banking redirect support for EPS, Giropay, iDEAL, and Sofortueberweisung ([#890](https://github.com/juspay/hyperswitch/pull/890)) ([`c86f2c0`](https://github.com/juspay/hyperswitch/commit/c86f2c045e3cc614e5f68d84b5055a1b0e222f67))
  - Add dispute webhooks for Stripe ([#918](https://github.com/juspay/hyperswitch/pull/918)) ([`0df2244`](https://github.com/juspay/hyperswitch/commit/0df224479416533579dd6d96e7f0dd9c246b739c))
  - Add Cards(3ds & non3ds),bank_redirects ,wallets(Paypal,Applepay) and Mandates support to nexinets ([#898](https://github.com/juspay/hyperswitch/pull/898)) ([`eea05f5`](https://github.com/juspay/hyperswitch/commit/eea05f5c3196d68cf9cd306419ac55003cebf002))
- **pm_list:** Add available capture methods filter ([#999](https://github.com/juspay/hyperswitch/pull/999)) ([`36cc13d`](https://github.com/juspay/hyperswitch/commit/36cc13d44bb61b840195e1a24f1bebdb0115d13b))
- **router:** Added support for optional defend dispute api call and added evidence submission flow for checkout connector ([#979](https://github.com/juspay/hyperswitch/pull/979)) ([`4728d94`](https://github.com/juspay/hyperswitch/commit/4728d946e24c2c548e7cdc23c34238ff028f1076))
- PG Agnostic mandate using network_txns_id (Adyen, Authorizedotnet, Stripe) ([#855](https://github.com/juspay/hyperswitch/pull/855)) ([`ed99655`](https://github.com/juspay/hyperswitch/commit/ed99655ebc11d53f4b2ffcb8c0eb9ef6b56f32c4))
- Expire client secret after a merchant configurable intent fufliment time ([#956](https://github.com/juspay/hyperswitch/pull/956)) ([`03a9643`](https://github.com/juspay/hyperswitch/commit/03a96432a9d9874d2232d75206f7bc605f1170f3))

### Bug Fixes

- **refund_list:** Updated refund list response status code when no refunds found. ([#974](https://github.com/juspay/hyperswitch/pull/974)) ([`4e0489c`](https://github.com/juspay/hyperswitch/commit/4e0489cf1cb7c17e55cffabeb0067c380ba41ff4))
- **refund_sync:** Add validation for missing `connector_refund_id` ([#1013](https://github.com/juspay/hyperswitch/pull/1013)) ([`4397c8e`](https://github.com/juspay/hyperswitch/commit/4397c8e19977974510f7c24daa8c3ef7f2ab907b))
- **storage_models:** Fix incorrect field order in `MerchantConnectorAccount` ([#976](https://github.com/juspay/hyperswitch/pull/976)) ([`c9e8a9b`](https://github.com/juspay/hyperswitch/commit/c9e8a9b4b721612ff2c771f4849fbad0c18bb7f2))
- Fix internal server errors on merchant connector account creation ([#1026](https://github.com/juspay/hyperswitch/pull/1026)) ([`c31b4b4`](https://github.com/juspay/hyperswitch/commit/c31b4b41c22c9c622d75f0f8421ec67a416d5b70))
- Remove old data while deserialization error from cache ([#1010](https://github.com/juspay/hyperswitch/pull/1010)) ([`23b5647`](https://github.com/juspay/hyperswitch/commit/23b5647290a7baa12107abd88359507aa3c31444))
- Passing connector_name instead of ConnectorCallType ([#1050](https://github.com/juspay/hyperswitch/pull/1050)) ([`c888635`](https://github.com/juspay/hyperswitch/commit/c888635166be08e826f8a21f5c0c3262cc0918f9))

### Refactors

- **config:** Add independent toggles for enabling traces and metrics ([#1020](https://github.com/juspay/hyperswitch/pull/1020)) ([`af71828`](https://github.com/juspay/hyperswitch/commit/af71828e351918fe6a97b52969db4abd331f6e5b))
- **stripe:** Return all the missing fields in a request ([#935](https://github.com/juspay/hyperswitch/pull/935)) ([`e9fc34f`](https://github.com/juspay/hyperswitch/commit/e9fc34ff626c13ec117f4ec9b091a69892bddf4f))
- Use `CountryAlpha2` instead of `CountryCode` for country codes ([#904](https://github.com/juspay/hyperswitch/pull/904)) ([`2cff019`](https://github.com/juspay/hyperswitch/commit/2cff019a1be669e5b1cd44d5513463671f386f4c))

### Documentation

- **README:** Remove redundant "more" in FAQs ([#1031](https://github.com/juspay/hyperswitch/pull/1031)) ([`9cbda83`](https://github.com/juspay/hyperswitch/commit/9cbda838171331598018a640551495014bc364a2))

### Miscellaneous Tasks

- Add `git-cliff` configs for generating changelogs and release notes ([#1047](https://github.com/juspay/hyperswitch/pull/1047)) ([`68360d4`](https://github.com/juspay/hyperswitch/commit/68360d4d6a31d8d7361c83021ca3049780d6d0a3))

### Build System / Dependencies

- **deps:** Make AWS dependencies optional ([#1030](https://github.com/juspay/hyperswitch/pull/1030)) ([`a4f6f3f`](https://github.com/juspay/hyperswitch/commit/a4f6f3fdaa23f7bd849eb44971de8311f9363ac3))

- - -

## 0.5.8 (2023-04-25)

### Chores

*  fix error message for deserialization ([#885](https://github.com/juspay/orca/pull/885)) ([e4d0dd0a](https://github.com/juspay/orca/commit/e4d0dd0a3885151a8e28a0246e67523f90f53076))

### Continuous Integration

* **connector-sanity-tests:**  run tests on being queued for merge ([#960](https://github.com/juspay/orca/pull/960)) ([067dc709](https://github.com/juspay/orca/commit/067dc709360394b062c217ca3a27e011bfbac215))
* **manual-release:**  fix `EXTRA_FEATURES` not being passed correctly ([#912](https://github.com/juspay/orca/pull/912)) ([9c9c52f9](https://github.com/juspay/orca/commit/9c9c52f9af74ebc7e835a5750dd05967b39a0ade))

### Documentation Changes

* **dashboard:**  add button that links to dashboard ([#934](https://github.com/juspay/orca/pull/934)) ([96f9e806](https://github.com/juspay/orca/commit/96f9e8068bfae0dd8479f69d4add675f1aaad991))

### New Features

* **connector:**
  *  add 3ds for Bambora and Support Html 3ds response ([#817](https://github.com/juspay/orca/pull/817)) ([20bea23b](https://github.com/juspay/orca/commit/20bea23b75c30b27f5beda78ac2ffa8302c6e6a8))
  *  [Nuvei] add support for bank redirect Eps, Sofort, Giropay, Ideal ([#870](https://github.com/juspay/orca/pull/870)) ([c1a25b30](https://github.com/juspay/orca/commit/c1a25b30bd88ab4ad4f40866a16ba5651d711ee3))
  *  [Checkout] add GooglePay, ApplePay and Webhooks support  ([#875](https://github.com/juspay/orca/pull/875)) ([3fce1407](https://github.com/juspay/orca/commit/3fce1407039c060712465cf4a696f8ed23f3bffb))
* **router:**
  *  added dispute accept api, file module apis and dispute evidence submission api  ([#900](https://github.com/juspay/orca/pull/900)) ([bdf1e514](https://github.com/juspay/orca/commit/bdf1e5147e710876a62c7377471144175e6c823d))
  *  add new payment methods for Bank redirects, BNPL and wallet ([#864](https://github.com/juspay/orca/pull/864)) ([304081cb](https://github.com/juspay/orca/commit/304081cbadf86bbd5a20d69b96a79d6cd647024c))
* **compatibility:**  add refund retrieve endpoint which accepts gateway creds ([#958](https://github.com/juspay/orca/pull/958)) ([bcbf4c88](https://github.com/juspay/orca/commit/bcbf4c882c248d08d3d0733299c7220597d669e3))
* **Core:**  gracefully shutdown router/scheduler if Redis is unavailable ([#891](https://github.com/juspay/orca/pull/891)) ([13185999](https://github.com/juspay/orca/commit/13185999d5c03dfa9c1f9d72bff6b798c4b80be5))
* **core:**  [Stripe] add bank debits payment method to stripe ([#906](https://github.com/juspay/orca/pull/906)) ([f624eb52](https://github.com/juspay/orca/commit/f624eb52d61561c365cce21e58b08281d096d904))
*  support gpay and applepay session response for all connectors ([#839](https://github.com/juspay/orca/pull/839)) ([d23e14c5](https://github.com/juspay/orca/commit/d23e14c57a1defe46416130bda4845973b62a54d))
*  add relevant ids for payment calls & make json logs  ([#908](https://github.com/juspay/orca/pull/908)) ([93b69e74](https://github.com/juspay/orca/commit/93b69e74b40592b241c6ade1b51e2dd49b25a45d))
*  [Bluesnap] add GooglePay, ApplePay support (#985) (897250e)
*  [Zen] add Cards 3DS, Non-3DS, GooglePay, ApplePay and Webhooks support (#962) (71c39b)

### Bug Fixes

*  different parent payment method token for different payment me… ([#982](https://github.com/juspay/orca/pull/982)) ([2f378345](https://github.com/juspay/orca/commit/2f378345aab58113620c11a18455f118e136a0c1))
* **config:**  fix Tempo config for Tempo 2.0 ([#959](https://github.com/juspay/orca/pull/959)) ([811cd523](https://github.com/juspay/orca/commit/811cd523c20343761ee5b420d0fcab59be39c56d))
* **stripe:**  add setup intent sync for stripe ([#953](https://github.com/juspay/orca/pull/953)) ([ab7fc23a](https://github.com/juspay/orca/commit/ab7fc23a7b7a2453ac41466f428d9c0df504968b))
* **connector:**
  *  fix adyen unit test ([#957](https://github.com/juspay/orca/pull/957)) ([85c76290](https://github.com/juspay/orca/commit/85c7629061ebbe5c9e0393f138af9b8876c3643d))
  *  [coinbase] update cancel status on user cancelling the payment ([#922](https://github.com/juspay/orca/pull/922)) ([22cee8cd](https://github.com/juspay/orca/commit/22cee8cdd9567545cd61587a8158aca754d77e0a))
  *  fix adyen unit test ([#931](https://github.com/juspay/orca/pull/931)) ([afeb8319](https://github.com/juspay/orca/commit/afeb83194f0772e7550c5d4a6ed4ba16216d2a28))
* **connector-template:**  Address unused import and mismatched types in connector-template ([#910](https://github.com/juspay/orca/pull/910)) ([891683e0](https://github.com/juspay/orca/commit/891683e060d1fdda32405cfd06d737b2416acdcc))

### Other Changes

* **try_local_system:**  replace Postman collection links with development collection ([#937](https://github.com/juspay/orca/pull/937)) ([ccc0c3f9](https://github.com/juspay/orca/commit/ccc0c3f96021b25ce5de700cf584d688096a9bca))
* **pr-template:**  add API contract changes and update contributing docs with recent labels ([#936](https://github.com/juspay/orca/pull/936)) ([3e2a7eae](https://github.com/juspay/orca/commit/3e2a7eaed2e830b419964e486757c022a0ebca63))
* **errors:**  make StorageErrorExt generic on errors ([#928](https://github.com/juspay/orca/pull/928)) ([e161d92c](https://github.com/juspay/orca/commit/e161d92c58c85127c73fc150f88d1f58b2275da5))

### Refactors

* **db:**  remove `connector_transaction_id` from PaymentAttemptNew ([#949](https://github.com/juspay/orca/pull/949)) ([57327b82](https://github.com/juspay/orca/commit/57327b829776c58fa6c3569c5546c4706d2c66af))
* **api_keys:**  use `merchant_id` and `key_id` to query the table ([#939](https://github.com/juspay/orca/pull/939)) ([40898c0a](https://github.com/juspay/orca/commit/40898c0ac9199258fbc6e8e12950d4fa54ec3339))

- - -

## 0.5.7 (2023-04-18)

### New Features

* **connector:**
  *  [Shift4] add support for card 3DS payment (#828) (29999fe5)
  *  [Nuvei] add support for card mandates (#818) (298a0a49)
* **bank_redirects:**  modify api contract for sofort (#880) (fc2e4514)
  *  add template code for connector forte (#854) (7a581a6)
  *  add template code for connector nexinets (#852) (dee5f61)

### Bug Fixes

* **connector:**  [coinbase] make metadata as option parameter (#887) (f5728955)
*  Update events table after notifying merchant (#871) (013026)
* **stripe:**  remove cancel reason validation for stripe (#876) (fa44c1f6)

### Enhancement

* **payments:**  make TokenizationAction clonable (#895)

### Integration

*  Frm integration with hyperswitch (#857)

### Refactors

*  use lowercase names for run environment and config files (#801) (ffaa8da0)
*  derive `Serialize` and `Deserialize` to `Country` enum (#882) (456c16fb)
* **storage_models, errors:**  impl StorageErrorExt for error_stack::Result<T, errors::StorageError> (#886) (b4020294)
* **router:**  KMS decrypt secrets when kms feature is enabled  (#868) (8905e663)

- - -

## 0.5.6 2023-04-14

### Build System / Dependencies

* **deps:**  bump `fred` from `5.2.0` to `6.0.0` (#869) (01bc162d)

### Continuous Integration

* **manual_release:**  add `multiple_mca` feature in ci (#872) (aebb4dca)

### New Features

* **core:**  add backwards compatibility for multiple mca (#866) (cf902f19)
* **router:**
  *  added dispute retrieve and dispute list apis (#842) (acab7671)
  *  separate straight through algorithm in separate column in payment attempt (#863) (01f86c49)
* **connector:**
  *  [Airwallex] add multiple redirect support for 3DS (#811) (d1d58e33)
  *  [Worldpay] add support for webhook (#820) (23511166)
  *  [Coinbase] [Opennode] Add support for crypto payments via PG redirection (#834) (b3d14737)
*  multiple connector account support for the same `country` (#816) (6188d515)
*  connector tokenization flow (#750) (29da1dfa)
* **process_tracker:**  changing runner selection to dyn dispatch (#853) (18b84c42)

### Bug Fixes

* **merchant_account:**  change `primary_business_details` to vec in update (#877) (396d24fe)
*  redis deserialization issue in tokenization call (#878) (5e9d7d6b)
*  duplication check fix in basilisk-hs (#881) (b12762e7)

### Refactors

* **Tokenization:**  remove ConnectorCallType from tokenization call (#862) (0d047e08)
* **router_env:**  improve logging setup (#847) (1b94d25f)
* **refund_type:** Feat/add copy derive (#849) (ccf03273)

- - -

## 0.5.5 (2023-04-10)

### New Features

* **api_models:**  derive `strum::Display` for `RefundStatus` (#846) (4524d4f5)
*  allow (de)serializing countries to/from alpha-2, alpha-3 and numeric country codes (#836) (899767cf)
* **connector:**  add authorize, capture, void, psync, refund, rsync for PayPal connector (#747) (36049c13)

### Bug Fixes

*  Add locker sign keyid in env (#844) (70dff140)

### Other Changes

* **common_utils:**  put the async ext trait behind a feature (#835) (de29eb68)
*  update ci workflows for common_enums crate (#843) (45111337)

### Refactors

* **scheduler:**  remove scheduler options & adding graceful shutdown to producer (#840) (11df8436)
* **router:**  refactor amount in PaymentsCaptureData from Option<i64> to i64 (#821) (b8bcba4e)

- - -

## 0.5.4 (2023-04-04)

### New Features

* **request:**  add `RequestBuilder` method to attach default request headers (#826) (6f61f830)
* **middleware:**  add middleware to attach default response headers (#824) (6d7b11a0)
* **core:**  added multiple payment_attempt support for payment_intent (#439) (35d3e277)
* **router:**  added incoming dispute webhooks flow (#769) (a733eafb)

### Bug Fixes

* **cards_info:**  add extra columns to cards_info struct (#813) (442bed0f)
* **connector:**  [Mollie] remove unsupported implementation of Void flow from mollie connector (#808) (eee8304b)

### Other Changes

* **common_utils:**  put the signals module behind a feature flag (#814) (fb4ec431)
* **core:**  replace string with enum for country (#735) (e18bfb2a)
* **api_models:**  put the errors module behind a feature flag (#815) (f14f87a1)
* **storage_models:**  delete client secret when status is succeeded, failed, cancelled (#724) (a05059b7)
### Refactors

* **drainer, router:**  KMS decrypt database password when `kms` feature is enabled (#733) (9d6e4ee3)

- - -

## 0.5.3 (2023-03-29)

### Documentation Changes

* **rfc:**  add rfc template & first RFC (#806) (01a5e0a0)

### New Features

*  cards info api (#749) (b15b8f7b)
* **connector:**  [Nuvei] add webhook support (#795) (20b4372b)

### Bug Fixes

* **compatibility:**  add last_payment_error in stripe payment response (#803) (97b95f0e)

### Refactors

* **api_models:**  enhance accepted countries/currencies types (#807) (f9ef3135)
* **services:**   make AppState impl generic using AppStateInfo (#805) (642c3f3a)

- - -

## 0.5.2 (2023-03-24)

### Chores

*  prepare for building production Docker images (#794) (6ddc30eb)

### Bug Fixes

* **connector:**  [Airwallex] Change Session Token to Init Payment (#798) (a3c00339)

### Other Changes


impl api::PayoutRecipient for Wise {}


pub struct PayboxPSyncRequest {
    #[serde(rename = "DATEQ")]
    pub date: String,

    #[serde(rename = "TYPE")]
    pub transaction_type: String,

    #[serde(rename = "NUMQUESTION")]
    pub paybox_request_number: String,

    #[serde(rename = "VERSION")]
    pub version: String,

    #[serde(rename = "SITE")]
    pub site: Secret<String>,

    #[serde(rename = "RANG")]
    pub rank: Secret<String>,

    #[serde(rename = "CLE")]
    pub key: Secret<String>,

    #[serde(rename = "NUMTRANS")]
    pub transaction_number: String,

    #[serde(rename = "NUMAPPEL")]
    pub paybox_order_id: String,
}


pub struct DisputeResponse {
    /// The identifier for dispute
    pub dispute_id: String,
    /// The identifier for payment_intent
    #[schema(value_type = String)]
    pub payment_id: common_utils::id_type::PaymentId,
    /// The identifier for payment_attempt
    pub attempt_id: String,
    /// The dispute amount
    pub amount: StringMinorUnit,
    /// The three-letter ISO currency code
    #[schema(value_type = Currency)]
    pub currency: Currency,
    /// Stage of the dispute
    pub dispute_stage: DisputeStage,
    /// Status of the dispute
    pub dispute_status: DisputeStatus,
    /// connector to which dispute is associated with
    pub connector: String,
    /// Status of the dispute sent by connector
    pub connector_status: String,
    /// Dispute id sent by connector
    pub connector_dispute_id: String,
    /// Reason of dispute sent by connector
    pub connector_reason: Option<String>,
    /// Reason code of dispute sent by connector
    pub connector_reason_code: Option<String>,
    /// Evidence deadline of dispute sent by connector
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub challenge_required_by: Option<PrimitiveDateTime>,
    /// Dispute created time sent by connector
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub connector_created_at: Option<PrimitiveDateTime>,
    /// Dispute updated time sent by connector
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub connector_updated_at: Option<PrimitiveDateTime>,
    /// Time at which dispute is received
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,
    /// The `profile_id` associated with the dispute
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<common_utils::id_type::ProfileId>,
    /// The `merchant_connector_id` of the connector / processor through which the dispute was processed
    #[schema(value_type = Option<String>)]
    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
}


OpenAPI Block Path: components.schemas.AcceptedCountries
{
  "oneOf": [
    {
      "type": "object",
      "required": [
        "type",
        "list"
      ],
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "enable_only"
          ]
        },
        "list": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/CountryAlpha2"
          }
        }
      }
    },
    {
      "type": "object",
      "required": [
        "type",
        "list"
      ],
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "disable_only"
          ]
        },
        "list": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/CountryAlpha2"
          }
        }
      }
    },
    {
      "type": "object",
      "required": [
        "type"
      ],
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "all_accepted"
          ]
        }
      }
    }
  ],
  "description": "Object to filter the customer countries for which the payment method is displayed",
  "discriminator": {
    "propertyName": "type"
  }
}


File: crates/hyperswitch_domain_models/src/tokenization.rs
 Public functions: 1
 Public structs: 3

use common_enums;
use common_utils::{
    self, date_time,
    errors::{CustomResult, ValidationError},
    types::keymanager,
};
use masking::Secret;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;

#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Tokenization {
    pub id: common_utils::id_type::GlobalTokenId,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub customer_id: common_utils::id_type::GlobalCustomerId,
    pub locker_id: String,
    pub created_at: PrimitiveDateTime,
    pub updated_at: PrimitiveDateTime,
    pub flag: common_enums::TokenizationFlag,
    pub version: common_enums::ApiVersion,
}

impl Tokenization {
    pub fn is_disabled(&self) -> bool {
        self.flag == common_enums::TokenizationFlag::Disabled
    }
}

#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TokenizationNew {
    pub id: common_utils::id_type::GlobalTokenId,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub customer_id: common_utils::id_type::GlobalCustomerId,
    pub locker_id: String,
    pub flag: common_enums::TokenizationFlag,
    pub version: common_enums::ApiVersion,
}

#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TokenizationResponse {
    pub token: String,
    pub created_at: PrimitiveDateTime,
    pub flag: common_enums::TokenizationFlag,
}

impl From<Tokenization> for TokenizationResponse {
    fn from(value: Tokenization) -> Self {
        Self {
            token: value.id.get_string_repr().to_string(),
            created_at: value.created_at,
            flag: value.flag,
        }
    }
}

#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum TokenizationUpdate {
    DeleteTokenizationRecordUpdate {
        flag: Option<common_enums::enums::TokenizationFlag>,
    },
}

#[async_trait::async_trait]
impl super::behaviour::Conversion for Tokenization {
    type DstType = diesel_models::tokenization::Tokenization;
    type NewDstType = diesel_models::tokenization::Tokenization;

    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        Ok(diesel_models::tokenization::Tokenization {
            id: self.id,
            merchant_id: self.merchant_id,
            customer_id: self.customer_id,
            locker_id: self.locker_id,
            created_at: self.created_at,
            updated_at: self.updated_at,
            version: self.version,
            flag: self.flag,
        })
    }

    async fn convert_back(
        _state: &keymanager::KeyManagerState,
        item: Self::DstType,
        _key: &Secret<Vec<u8>>,
        _key_manager_identifier: keymanager::Identifier,
    ) -> CustomResult<Self, ValidationError> {
        Ok(Self {
            id: item.id,
            merchant_id: item.merchant_id,
            customer_id: item.customer_id,
            locker_id: item.locker_id,
            created_at: item.created_at,
            updated_at: item.updated_at,
            flag: item.flag,
            version: item.version,
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        Ok(diesel_models::tokenization::Tokenization {
            id: self.id,
            merchant_id: self.merchant_id,
            customer_id: self.customer_id,
            locker_id: self.locker_id,
            created_at: self.created_at,
            updated_at: self.updated_at,
            version: self.version,
            flag: self.flag,
        })
    }
}

impl From<TokenizationUpdate> for diesel_models::tokenization::TokenizationUpdateInternal {
    fn from(value: TokenizationUpdate) -> Self {
        let now = date_time::now();
        match value {
            TokenizationUpdate::DeleteTokenizationRecordUpdate { flag } => Self {
                updated_at: now,
                flag,
            },
        }
    }
}


pub async fn totp_verify(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<user_api::VerifyTotpRequest>,
) -> HttpResponse {
    let flow = Flow::TotpVerify;
    Box::pin(api::server_wrap(
        flow,
        state.clone(),
        &req,
        json_payload.into_inner(),
        |state, user, req_body, _| user_core::verify_totp(state, user, req_body),
        &auth::SinglePurposeOrLoginTokenAuth(TokenPurpose::TOTP),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


impl api::PaymentVoid for Taxjar {}


pub struct ProphetpayCompleteAuthResponse {
    pub success: bool,
    pub response_text: String,
    #[serde(rename = "transactionID")]
    pub transaction_id: String,
    pub response_code: String,
}


File: crates/test_utils/tests/connectors/paypal_ui.rs

use serial_test::serial;
use thirtyfour::{prelude::*, WebDriver};

use crate::{selenium::*, tester};

struct PaypalSeleniumTest;

impl SeleniumTest for PaypalSeleniumTest {
    fn get_connector_name(&self) -> String {
        "paypal".to_string()
    }
}

async fn should_make_paypal_paypal_wallet_payment(
    web_driver: WebDriver,
) -> Result<(), WebDriverError> {
    let conn = PaypalSeleniumTest {};
    conn.make_paypal_payment(
        web_driver,
        &format!("{CHECKOUT_BASE_URL}/saved/21"),
        vec![
            Event::Trigger(Trigger::Click(By::Css("#payment-submit-btn"))),
            Event::Assert(Assert::IsPresent("Google")),
            Event::Assert(Assert::ContainsAny(
                Selector::QueryParamStr,
                vec!["status=processing"],
            )),
        ],
    )
    .await?;
    Ok(())
}

async fn should_make_paypal_ideal_payment(web_driver: WebDriver) -> Result<(), WebDriverError> {
    let conn = PaypalSeleniumTest {};
    conn.make_redirection_payment(
        web_driver,
        vec![
            Event::Trigger(Trigger::Goto(&format!("{CHECKOUT_BASE_URL}/saved/181"))),
            Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
            Event::Trigger(Trigger::Click(By::Name("Successful"))),
            Event::Assert(Assert::IsPresent("processing")),
        ],
    )
    .await?;
    Ok(())
}

async fn should_make_paypal_giropay_payment(web_driver: WebDriver) -> Result<(), WebDriverError> {
    let conn = PaypalSeleniumTest {};
    conn.make_redirection_payment(
        web_driver,
        vec![
            Event::Trigger(Trigger::Goto(&format!("{CHECKOUT_BASE_URL}/saved/233"))),
            Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
            Event::Trigger(Trigger::Click(By::Name("Successful"))),
            Event::Assert(Assert::IsPresent("processing")),
        ],
    )
    .await?;
    Ok(())
}

async fn should_make_paypal_eps_payment(web_driver: WebDriver) -> Result<(), WebDriverError> {
    let conn = PaypalSeleniumTest {};
    conn.make_redirection_payment(
        web_driver,
        vec![
            Event::Trigger(Trigger::Goto(&format!("{CHECKOUT_BASE_URL}/saved/234"))),
            Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
            Event::Trigger(Trigger::Click(By::Name("Successful"))),
            Event::Assert(Assert::IsPresent("processing")),
        ],
    )
    .await?;
    Ok(())
}

async fn should_make_paypal_sofort_payment(web_driver: WebDriver) -> Result<(), WebDriverError> {
    let conn = PaypalSeleniumTest {};
    conn.make_redirection_payment(
        web_driver,
        vec![
            Event::Trigger(Trigger::Goto(&format!("{CHECKOUT_BASE_URL}/saved/235"))),
            Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
            Event::Trigger(Trigger::Click(By::Name("Successful"))),
            Event::Assert(Assert::IsPresent("processing")),
        ],
    )
    .await?;
    Ok(())
}

#[test]
#[serial]
fn should_make_paypal_paypal_wallet_payment_test() {
    tester!(should_make_paypal_paypal_wallet_payment);
}

#[test]
#[serial]
fn should_make_paypal_ideal_payment_test() {
    tester!(should_make_paypal_ideal_payment);
}

#[test]
#[serial]
fn should_make_paypal_giropay_payment_test() {
    tester!(should_make_paypal_giropay_payment);
}

#[test]
#[serial]
fn should_make_paypal_eps_payment_test() {
    tester!(should_make_paypal_eps_payment);
}

#[test]
#[serial]
fn should_make_paypal_sofort_payment_test() {
    tester!(should_make_paypal_sofort_payment);
}


pub struct NovalnetSyncTransaction {
    tid: String,
}


pub struct SurchargeMetadata {
    surcharge_results: HashMap<SurchargeKey, SurchargeDetails>,
    pub payment_attempt_id: String,
}


pub struct Order {
    order_id: String,
    amount: StringMinorUnit,
    currency: Currency,
    description: Option<String>,
    customer_info: CustomerInfo,
}


impl common_utils::events::ApiEventMetric for ProfileAcquirerUpdate {}

impl common_utils::events::ApiEventMetric for for ProfileAcquirerUpdate


impl ApiEventMetric for AuthenticationSyncResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Authentication {
            authentication_id: self.authentication_id.clone(),
        })
    }
}

impl ApiEventMetric for for AuthenticationSyncResponse


pub fn get_optional_customer_id(
        &self,
    ) -> CustomResult<Option<id_type::CustomerId>, common_utils::errors::ValidationError> {
        self.customer_id
            .as_ref()
            .map(|customer_id| id_type::CustomerId::try_from(customer_id.clone()))
            .transpose()
    }


pub(crate) fn get_webhook_object_from_body(
    body: &[u8],
) -> CustomResult<BluecodeSyncResponse, common_utils::errors::ParsingError> {
    let webhook: BluecodeSyncResponse = body.parse_struct("BluecodeIncomingWebhook")?;

    Ok(webhook)
}


File: crates/router/src/core/currency.rs
 Public functions: 3

use analytics::errors::AnalyticsError;
use common_utils::errors::CustomResult;
use currency_conversion::types::ExchangeRates;
use error_stack::ResultExt;
use router_env::logger;

use crate::{
    consts::DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS,
    core::errors::ApiErrorResponse,
    services::ApplicationResponse,
    utils::currency::{self, convert_currency, get_forex_rates, ForexError as ForexCacheError},
    SessionState,
};

pub async fn retrieve_forex(
    state: SessionState,
) -> CustomResult<ApplicationResponse<currency::FxExchangeRatesCacheEntry>, ApiErrorResponse> {
    let forex_api = state.conf.forex_api.get_inner();
    Ok(ApplicationResponse::Json(
        get_forex_rates(&state, forex_api.data_expiration_delay_in_seconds)
            .await
            .change_context(ApiErrorResponse::GenericNotFoundError {
                message: "Unable to fetch forex rates".to_string(),
            })?,
    ))
}

pub async fn convert_forex(
    state: SessionState,
    amount: i64,
    to_currency: String,
    from_currency: String,
) -> CustomResult<
    ApplicationResponse<api_models::currency::CurrencyConversionResponse>,
    ApiErrorResponse,
> {
    Ok(ApplicationResponse::Json(
        Box::pin(convert_currency(
            state.clone(),
            amount,
            to_currency,
            from_currency,
        ))
        .await
        .change_context(ApiErrorResponse::InternalServerError)?,
    ))
}

pub async fn get_forex_exchange_rates(
    state: SessionState,
) -> CustomResult<ExchangeRates, AnalyticsError> {
    let forex_api = state.conf.forex_api.get_inner();
    let mut attempt = 1;

    logger::info!("Starting forex exchange rates fetch");
    loop {
        logger::info!("Attempting to fetch forex rates - Attempt {attempt} of {DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS}");

        match get_forex_rates(&state, forex_api.data_expiration_delay_in_seconds).await {
            Ok(rates) => {
                logger::info!("Successfully fetched forex rates");
                return Ok((*rates.data).clone());
            }
            Err(error) => {
                let is_retryable = matches!(
                    error.current_context(),
                    ForexCacheError::CouldNotAcquireLock
                        | ForexCacheError::EntryNotFound
                        | ForexCacheError::ForexDataUnavailable
                        | ForexCacheError::LocalReadError
                        | ForexCacheError::LocalWriteError
                        | ForexCacheError::RedisConnectionError
                        | ForexCacheError::RedisLockReleaseFailed
                        | ForexCacheError::RedisWriteError
                        | ForexCacheError::WriteLockNotAcquired
                );

                if !is_retryable {
                    return Err(error.change_context(AnalyticsError::ForexFetchFailed));
                }

                if attempt >= DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS {
                    logger::error!("Failed to fetch forex rates after {DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS} attempts");
                    return Err(error.change_context(AnalyticsError::ForexFetchFailed));
                }
                logger::warn!(
                    "Forex rates fetch failed with retryable error, retrying in {attempt} seconds"
                );
                tokio::time::sleep(std::time::Duration::from_secs(attempt * 2)).await;
                attempt += 1;
            }
        }
    }
}


File: crates/router/src/services/connector_integration_interface.rs

pub use hyperswitch_interfaces::{
    authentication::ExternalAuthenticationPayload, connector_integration_interface::*,
    connector_integration_v2::ConnectorIntegrationV2, webhooks::IncomingWebhookFlowError,
};


OpenAPI Block Path: components.schemas.BusinessGenericLinkConfig
{
  "allOf": [
    {
      "$ref": "#/components/schemas/GenericLinkUiConfig"
    },
    {
      "type": "object",
      "required": [
        "allowed_domains"
      ],
      "properties": {
        "domain_name": {
          "type": "string",
          "description": "Custom domain name to be used for hosting the link",
          "nullable": true
        },
        "allowed_domains": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "A list of allowed domains (glob patterns) where this link can be embedded / opened from",
          "uniqueItems": true
        }
      }
    }
  ]
}


pub struct VoltPaymentsResponse {
    checkout_url: String,
    id: String,
}


pub async fn unlink_routing_config_under_profile(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: common_utils::id_type::ProfileId,
    transaction_type: &enums::TransactionType,
) -> RouterResponse<routing_types::RoutingDictionaryRecord> {
    metrics::ROUTING_UNLINK_CONFIG.add(1, &[]);
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let business_profile = core_utils::validate_and_get_business_profile(
        db,
        key_manager_state,
        merchant_context.get_merchant_key_store(),
        Some(&profile_id),
        merchant_context.get_merchant_account().get_id(),
    )
    .await?
    .get_required_value("Profile")?;

    let routing_algo_id = match transaction_type {
        enums::TransactionType::Payment => business_profile.routing_algorithm_id.clone(),
        #[cfg(feature = "payouts")]
        enums::TransactionType::Payout => business_profile.payout_routing_algorithm_id.clone(),
        // TODO: Handle ThreeDsAuthentication Transaction Type for Three DS Decision Rule Algorithm configuration
        enums::TransactionType::ThreeDsAuthentication => todo!(),
    };

    if let Some(algorithm_id) = routing_algo_id {
        let record = RoutingAlgorithmUpdate::fetch_routing_algo(
            merchant_context.get_merchant_account().get_id(),
            &algorithm_id,
            db,
        )
        .await?;
        let response = record.0.foreign_into();
        admin::ProfileWrapper::new(business_profile)
            .update_profile_and_invalidate_routing_config_for_active_algorithm_id_update(
                db,
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                algorithm_id,
                transaction_type,
            )
            .await?;
        metrics::ROUTING_UNLINK_CONFIG_SUCCESS_RESPONSE.add(1, &[]);
        Ok(service_api::ApplicationResponse::Json(response))
    } else {
        Err(errors::ApiErrorResponse::PreconditionFailed {
            message: "Algorithm is already inactive".to_string(),
        })?
    }
}


impl api::PaymentCapture for Bambora {}

impl api::PaymentCapture for for Bambora


pub struct RefundResponse {
    pub transaction_id: String,
    pub original_transaction_id: String,
    pub action: String,
    pub authorization_amount: Option<FloatMajorUnit>,
    pub authorization_code: String,
    pub response: ResponseStatus,
}


impl Multitenancy {
    pub fn get_tenants(&self) -> &HashMap<id_type::TenantId, Tenant> {
        &self.tenants.0
    }
    pub fn get_tenant_ids(&self) -> Vec<id_type::TenantId> {
        self.tenants
            .0
            .values()
            .map(|tenant| tenant.tenant_id.clone())
            .collect()
    }
    pub fn get_tenant(&self, tenant_id: &id_type::TenantId) -> Option<&Tenant> {
        self.tenants.0.get(tenant_id)
    }
}


pub fn new(
        currency: Option<Currency>,
        status: Option<AttemptStatus>,
        connector: Option<String>,
        auth_type: Option<AuthenticationType>,
        payment_method: Option<String>,
        payment_method_type: Option<String>,
        client_source: Option<String>,
        client_version: Option<String>,
        profile_id: Option<String>,
        card_network: Option<String>,
        merchant_id: Option<String>,
        card_last_4: Option<String>,
        card_issuer: Option<String>,
        error_reason: Option<String>,
        routing_approach: Option<RoutingApproach>,
        signature_network: Option<String>,
        is_issuer_regulated: Option<bool>,
        is_debit_routed: Option<bool>,
        normalized_time_range: TimeRange,
    ) -> Self {
        Self {
            currency,
            status,
            connector,
            auth_type,
            payment_method,
            payment_method_type,
            client_source,
            client_version,
            profile_id,
            card_network,
            merchant_id,
            card_last_4,
            card_issuer,
            error_reason,
            routing_approach,
            signature_network,
            is_issuer_regulated,
            is_debit_routed,
            time_bucket: normalized_time_range,
            start_time: normalized_time_range.start_time,
        }
    }


pub struct WellsfargoErrorInformationResponse {
    id: String,
    error_information: WellsfargoErrorInformation,
}


pub async fn retrieve_default_routing_config_for_profiles(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    transaction_type: &enums::TransactionType,
) -> RouterResponse<Vec<routing_types::ProfileDefaultRoutingConfig>> {
    metrics::ROUTING_RETRIEVE_CONFIG_FOR_PROFILE.add(1, &[]);
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let all_profiles = db
        .list_profile_by_merchant_id(
            key_manager_state,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().get_id(),
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)
        .attach_printable("error retrieving all business profiles for merchant")?;

    let retrieve_config_futures = all_profiles
        .iter()
        .map(|prof| {
            helpers::get_merchant_default_config(
                db,
                prof.get_id().get_string_repr(),
                transaction_type,
            )
        })
        .collect::<Vec<_>>();

    let configs = futures::future::join_all(retrieve_config_futures)
        .await
        .into_iter()
        .collect::<Result<Vec<_>, _>>()?;

    let default_configs = configs
        .into_iter()
        .zip(all_profiles.iter().map(|prof| prof.get_id().to_owned()))
        .map(
            |(config, profile_id)| routing_types::ProfileDefaultRoutingConfig {
                profile_id,
                connectors: config,
            },
        )
        .collect::<Vec<_>>();

    metrics::ROUTING_RETRIEVE_CONFIG_FOR_PROFILE_SUCCESS_RESPONSE.add(1, &[]);
    Ok(service_api::ApplicationResponse::Json(default_configs))
}


pub async fn payments_list_attempts_using_payment_intent_id<F, Res, Req, Op, D>(
    state: SessionState,
    req_state: ReqState,
    merchant_context: domain::MerchantContext,
    profile: domain::Profile,
    operation: Op,
    req: Req,
    payment_id: id_type::GlobalPaymentId,
    header_payload: HeaderPayload,
) -> RouterResponse<Res>
where
    F: Send + Clone + Sync,
    Op: Operation<F, Req, Data = D> + Send + Sync + Clone,
    Req: Debug + Clone,
    D: OperationSessionGetters<F> + Send + Sync + Clone,
    Res: transformers::ToResponse<F, D, Op>,
{
    let (payment_data, _req, customer) = payments_attempt_operation_core::<_, _, _, _>(
        &state,
        req_state,
        merchant_context.clone(),
        profile,
        operation.clone(),
        req,
        payment_id,
        header_payload.clone(),
    )
    .await?;

    Res::generate_response(
        payment_data,
        customer,
        &state.base_url,
        operation,
        &state.conf.connector_request_reference_id_config,
        None,
        None,
        header_payload.x_hs_latency,
        &merchant_context,
    )
}


pub fn get_output(&self) -> &O {
        &self.connector_selection
    }


pub async fn find_by_merchant_id_subscription_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        subscription_id: String,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::subscription_id.eq(subscription_id.to_owned())),
        )
        .await
    }


impl StripebillingWebhookBody {
    pub fn get_webhook_object_from_body(body: &[u8]) -> CustomResult<Self, errors::ConnectorError> {
        let webhook_body: Self = body
            .parse_struct::<Self>("StripebillingWebhookBody")
            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;

        Ok(webhook_body)
    }
}


pub fn get_merchant_reference_id(&self) -> Option<id_type::CustomerId> {
        Some(
            self.customer_id
                .to_owned()
                .unwrap_or_else(common_utils::generate_customer_id_of_default_length),
        )
    }


pub fn get_card_detail(
    _pm: &domain::PaymentMethod,
    response: Card,
) -> CustomResult<api::CardDetailFromLocker, errors::VaultError> {
    let card_number = response.card_number;
    let last4_digits = card_number.clone().get_last4();
    //fetch form card bin

    let card_detail = api::CardDetailFromLocker {
        issuer_country: None,
        last4_digits: Some(last4_digits),
        card_number: Some(card_number),
        expiry_month: Some(response.card_exp_month),
        expiry_year: Some(response.card_exp_year),
        card_fingerprint: None,
        card_holder_name: response.name_on_card,
        nick_name: response.nick_name.map(Secret::new),
        card_isin: None,
        card_issuer: None,
        card_network: None,
        card_type: None,
        saved_to_locker: true,
    };
    Ok(card_detail)
}


impl api::PaymentUpdateMetadata for Stripe {}

impl api::PaymentUpdateMetadata for for Stripe


/// establish connection with the server
    pub fn get_dynamic_routing_connection(
        self,
        client: Client,
    ) -> Result<Option<RoutingStrategy>, Box<dyn std::error::Error>> {
        match self {
            Self::Enabled { host, port, .. } => {
                let uri = format!("http://{host}:{port}").parse::<tonic::transport::Uri>()?;
                logger::info!("Connection established with dynamic routing gRPC Server");

                let (success_rate_client, contract_based_client, elimination_based_client) = (
                    SuccessRateCalculatorClient::with_origin(client.clone(), uri.clone()),
                    ContractScoreCalculatorClient::with_origin(client.clone(), uri.clone()),
                    EliminationAnalyserClient::with_origin(client, uri),
                );

                Ok(Some(RoutingStrategy {
                    success_rate_client,
                    contract_based_client,
                    elimination_based_client,
                }))
            }
            Self::Disabled => Ok(None),
        }
    }


impl RedisSettings {
    /// Validates the Redis configuration provided.
    pub fn validate(&self) -> CustomResult<(), errors::RedisError> {
        use common_utils::{ext_traits::ConfigExt, fp_utils::when};

        when(self.host.is_default_or_empty(), || {
            Err(errors::RedisError::InvalidConfiguration(
                "Redis `host` must be specified".into(),
            ))
        })?;

        when(self.cluster_enabled && self.cluster_urls.is_empty(), || {
            Err(errors::RedisError::InvalidConfiguration(
                "Redis `cluster_urls` must be specified if `cluster_enabled` is `true`".into(),
            ))
        })?;

        when(
            self.default_command_timeout < self.unresponsive_timeout,
            || {
                Err(errors::RedisError::InvalidConfiguration(
                    "Unresponsive timeout cannot be greater than the command timeout".into(),
                )
                .into())
            },
        )
    }
}


impl api::PaymentCapture for Authipay {}

impl api::PaymentCapture for for Authipay


impl std::fmt::Debug for RedisStore {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("CacheStore")
            .field("redis_conn", &"Redis conn doesn't implement debug")
            .finish()
    }
}


pub struct HeaderPayload {
    pub payment_confirm_source: Option<common_enums::PaymentSource>,
    pub client_source: Option<String>,
    pub client_version: Option<String>,
    pub x_hs_latency: Option<bool>,
    pub browser_name: Option<common_enums::BrowserName>,
    pub x_client_platform: Option<common_enums::ClientPlatform>,
    pub x_merchant_domain: Option<String>,
    pub locale: Option<String>,
    pub x_app_id: Option<String>,
    pub x_redirect_uri: Option<String>,
}


}

#[derive(Debug, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct NuveiVoidRequest {
    pub merchant_id: Secret<String>,
    pub merchant_site_id: Secret<String>,
    pub client_unique_id: String,
    pub related_transaction_id: String,
    pub time_stamp: String,
    pub checksum: Secret<String>,
    pub client_request_id: String,
}

impl TryFrom<&types::PaymentsCancelPostCaptureRouterData> for NuveiVoidRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsCancelPostCaptureRouterData) -> Result<Self, Self::Error> {
        let connector_meta: NuveiAuthType = NuveiAuthType::try_from(&item.connector_auth_type)?;
        let merchant_id = connector_meta.merchant_id.clone();
        let merchant_site_id = connector_meta.merchant_site_id.clone();
        let merchant_secret = connector_meta.merchant_secret.clone();
        let client_unique_id = item.connector_request_reference_id.clone();
        let related_transaction_id = item.request.connector_transaction_id.clone();
        let client_request_id = item.connector_request_reference_id.clone();
        let time_stamp =
            date_time::format_date(date_time::now(), date_time::DateFormat::YYYYMMDDHHmmss)
                .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        let checksum = Secret::new(encode_payload(&[
            merchant_id.peek(),
            merchant_site_id.peek(),
            &client_request_id,
            &client_unique_id,
            "", // amount (empty for void)
            "", // currency (empty for void)
            &related_transaction_id,
            "", // authCode (empty)
            "", // comment (empty)
            &time_stamp,
            merchant_secret.peek(),
        ])?);

        Ok(Self {
            merchant_id,
            merchant_site_id,
            client_unique_id,
            related_transaction_id,
            time_stamp,
            checksum,
            client_request_id,
        })
    }
}

impl TryFrom<&types::PaymentsCancelRouterData> for NuveiPaymentFlowRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        Self::try_from(NuveiPaymentRequestData {
            client_request_id: item.connector_request_reference_id.clone(),
            connector_auth_type: item.connector_auth_type.clone(),
            amount: convert_amount(
                NUVEI_AMOUNT_CONVERTOR,
                item.request
                    .minor_amount
                    .ok_or_else(missing_field_err("amount"))?,
                item.request.get_currency()?,
            )?,
            currency: item.request.get_currency()?,
            related_transaction_id: Some(item.request.connector_transaction_id.clone()),
            ..Default::default()
        })
    }
}

// Auth Struct
pub struct NuveiAuthType {
    pub(super) merchant_id: Secret<String>,
    pub(super) merchant_site_id: Secret<String>,
    pub(super) merchant_secret: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for NuveiAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        if let ConnectorAuthType::SignatureKey {
            api_key,
            key1,
            api_secret,
        } = auth_type
        {
            Ok(Self {
                merchant_id: api_key.to_owned(),
                merchant_site_id: key1.to_owned(),
                merchant_secret: api_secret.to_owned(),
            })
        } else {
            Err(errors::ConnectorError::FailedToObtainAuthType)?
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "UPPERCASE")]
pub enum NuveiPaymentStatus {
    Success,
    Failed,
    Error,
    #[default]
    Processing,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "UPPERCASE")]
pub enum NuveiTransactionStatus {
    Approved,
    Declined,
    Error,
    Redirect,
    Pending,
    #[default]
    Processing,
}

impl From<NuveiTransactionStatus> for enums::AttemptStatus {
    fn from(item: NuveiTransactionStatus) -> Self {
        match item {
            NuveiTransactionStatus::Approved => Self::Charged,
            NuveiTransactionStatus::Declined | NuveiTransactionStatus::Error => Self::Failure,
            _ => Self::Pending,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiPartialApproval {
    pub requested_amount: StringMajorUnit,
    pub requested_currency: enums::Currency,
    pub processed_amount: StringMajorUnit,
    pub processed_currency: enums::Currency,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiPaymentsResponse {
    pub order_id: Option<String>,
    pub user_token_id: Option<Secret<String>>,
    pub payment_option: Option<PaymentOption>,
    pub transaction_status: Option<NuveiTransactionStatus>,
    pub gw_error_code: Option<i64>,
    pub gw_error_reason: Option<String>,
    pub gw_extended_error_code: Option<i64>,
    pub issuer_decline_code: Option<String>,
    pub issuer_decline_reason: Option<String>,
    pub transaction_type: Option<NuveiTransactionType>,
    pub transaction_id: Option<String>,
    pub external_transaction_id: Option<String>,
    pub auth_code: Option<String>,
    pub custom_data: Option<String>,
    pub fraud_details: Option<FraudDetails>,
    // NTID
    pub external_scheme_transaction_id: Option<Secret<String>>,
    pub session_token: Option<Secret<String>>,
    pub partial_approval: Option<NuveiPartialApproval>,
    //The ID of the transaction in the merchant’s system.
    pub client_unique_id: Option<String>,
    pub internal_request_id: Option<i64>,
    pub status: NuveiPaymentStatus,
    pub err_code: Option<i64>,
    pub reason: Option<String>,
    pub merchant_id: Option<Secret<String>>,
    pub merchant_site_id: Option<Secret<String>>,
    pub version: Option<String>,
    pub client_request_id: Option<String>,
    pub merchant_advice_code: Option<String>,
}
impl NuveiPaymentsResponse {
    /// returns amount_captured and minor_amount_capturable
    pub fn get_amount_captured(
        &self,
    ) -> Result<(Option<i64>, Option<MinorUnit>), error_stack::Report<errors::ConnectorError>> {
        match &self.partial_approval {
            Some(partial_approval) => {
                let amount = utils::convert_back_amount_to_minor_units(
                    NUVEI_AMOUNT_CONVERTOR,
                    partial_approval.processed_amount.clone(),
                    partial_approval.processed_currency,
                )?;
                match self.transaction_type {
                    None => Ok((None, None)),
                    Some(NuveiTransactionType::Sale) => {
                        Ok((Some(MinorUnit::get_amount_as_i64(amount)), None))
                    }
                    Some(NuveiTransactionType::Auth) => Ok((None, Some(amount))),
                    Some(NuveiTransactionType::Auth3D) => {
                        Ok((Some(MinorUnit::get_amount_as_i64(amount)), None))
                    }
                    Some(NuveiTransactionType::InitAuth3D) => Ok((None, Some(amount))),
                    Some(NuveiTransactionType::Credit) => Ok((None, None)),
                    Some(NuveiTransactionType::Void) => Ok((None, None)),
                    Some(NuveiTransactionType::Settle) => Ok((None, None)),
                }
            }
            None => Ok((None, None)),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum NuveiTransactionType {
    Auth,
    Sale,
    Credit,
    Auth3D,
    InitAuth3D,
    Settle,
    Void,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FraudDetails {
    pub final_decision: String,
}

fn get_payment_status(
    response: &NuveiPaymentsResponse,
    amount: Option<i64>,
    is_post_capture_void: bool,
) -> enums::AttemptStatus {
    // ZERO dollar authorization
    if amount == Some(0) && response.transaction_type.clone() == Some(NuveiTransactionType::Auth) {
        return match response.transaction_status.clone() {
            Some(NuveiTransactionStatus::Approved) => enums::AttemptStatus::Charged,
            Some(NuveiTransactionStatus::Declined) | Some(NuveiTransactionStatus::Error) => {
                enums::AttemptStatus::AuthorizationFailed
            }
            Some(NuveiTransactionStatus::Pending) | Some(NuveiTransactionStatus::Processing) => {
                enums::AttemptStatus::Pending
            }
            Some(NuveiTransactionStatus::Redirect) => enums::AttemptStatus::AuthenticationPending,
            None => match response.status {
                NuveiPaymentStatus::Failed | NuveiPaymentStatus::Error => {
                    enums::AttemptStatus::Failure
                }
                _ => enums::AttemptStatus::Pending,
            },
        };
    }

    match response.transaction_status.clone() {
        Some(status) => match status {
            NuveiTransactionStatus::Approved => match response.transaction_type {
                Some(NuveiTransactionType::Auth) => enums::AttemptStatus::Authorized,
                Some(NuveiTransactionType::Sale) | Some(NuveiTransactionType::Settle) => {
                    enums::AttemptStatus::Charged
                }
                Some(NuveiTransactionType::Void) if is_post_capture_void => {
                    enums::AttemptStatus::VoidedPostCharge
                }
                Some(NuveiTransactionType::Void) => enums::AttemptStatus::Voided,

                _ => enums::AttemptStatus::Pending,
            },
            NuveiTransactionStatus::Declined | NuveiTransactionStatus::Error => {
                match response.transaction_type {
                    Some(NuveiTransactionType::Auth) => enums::AttemptStatus::AuthorizationFailed,
                    Some(NuveiTransactionType::Void) => enums::AttemptStatus::VoidFailed,
                    Some(NuveiTransactionType::Auth3D) => {
                        enums::AttemptStatus::AuthenticationFailed
                    }
                    _ => enums::AttemptStatus::Failure,
                }
            }
            NuveiTransactionStatus::Processing | NuveiTransactionStatus::Pending => {
                enums::AttemptStatus::Pending
            }
            NuveiTransactionStatus::Redirect => enums::AttemptStatus::AuthenticationPending,
        },
        None => match response.status {
            NuveiPaymentStatus::Failed | NuveiPaymentStatus::Error => enums::AttemptStatus::Failure,
            _ => enums::AttemptStatus::Pending,
        },
    }
}

fn build_error_response(response: &NuveiPaymentsResponse, http_code: u16) -> Option<ErrorResponse> {
    match response.status {
        NuveiPaymentStatus::Error => Some(get_error_response(
            response.err_code,
            &response.reason,
            http_code,
            &response.merchant_advice_code,
            &response.gw_error_code.map(|e| e.to_string()),
            &response.gw_error_reason,
        )),

        _ => {
            let err = Some(get_error_response(
                response.gw_error_code,
                &response.gw_error_reason,
                http_code,
                &response.merchant_advice_code,
                &response.gw_error_code.map(|e| e.to_string()),
                &response.gw_error_reason,
            ));

            match response.transaction_status {
                Some(NuveiTransactionStatus::Error) | Some(NuveiTransactionStatus::Declined) => err,
                _ => match response
                    .gw_error_reason
                    .as_ref()
                    .map(|r| r.eq("Missing argument"))
                {
                    Some(true) => err,
                    _ => None,
                },
            }
        }
    }
}

pub trait NuveiPaymentsGenericResponse {
    fn is_post_capture_void() -> bool {
        false
    }
}

impl NuveiPaymentsGenericResponse for CompleteAuthorize {}
impl NuveiPaymentsGenericResponse for Void {}
impl NuveiPaymentsGenericResponse for PSync {}
impl NuveiPaymentsGenericResponse for Capture {}
impl NuveiPaymentsGenericResponse for PostCaptureVoid {
    fn is_post_capture_void() -> bool {
        true
    }
}

impl
    TryFrom<
        ResponseRouterData<
            SetupMandate,
            NuveiPaymentsResponse,
            SetupMandateRequestData,
            PaymentsResponseData,
        >,
    > for RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            SetupMandate,
            NuveiPaymentsResponse,
            SetupMandateRequestData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let amount = item.data.request.amount;

        let (status, redirection_data, connector_response_data) =
            process_nuvei_payment_response(&item, amount, false)?;

        let (amount_captured, minor_amount_capturable) = item.response.get_amount_captured()?;

        let ip_address = item
            .data
            .request
            .browser_info
            .as_ref()
            .ok_or_else(|| errors::ConnectorError::MissingRequiredField {
                field_name: "browser_info",
            })?
            .ip_address
            .as_ref()
            .ok_or_else(|| errors::ConnectorError::MissingRequiredField {
                field_name: "browser_info.ip_address",
            })?
            .to_string();

        Ok(Self {
            status,
            response: if let Some(err) = build_error_response(&item.response, item.http_code) {
                Err(err)
            } else {
                Ok(create_transaction_response(
                    &item.response,
                    redirection_data,
                    Some(ip_address),
                )?)
            },
            amount_captured,
            minor_amount_capturable,
            connector_response: connector_response_data,
            ..item.data
        })
    }
}

// Helper function to process Nuvei payment response

fn process_nuvei_payment_response<F, T>(
    item: &ResponseRouterData<F, NuveiPaymentsResponse, T, PaymentsResponseData>,
    amount: Option<i64>,
    is_post_capture_void: bool,
) -> Result<
    (
        enums::AttemptStatus,
        Option<RedirectForm>,
        Option<ConnectorResponseData>,
    ),
    error_stack::Report<errors::ConnectorError>,
>
where
    F: std::fmt::Debug,
    T: std::fmt::Debug,
{
    let redirection_data = match item.data.payment_method {
        enums::PaymentMethod::Wallet | enums::PaymentMethod::BankRedirect => item
            .response
            .payment_option
            .as_ref()
            .and_then(|po| po.redirect_url.clone())
            .map(|base_url| RedirectForm::from((base_url, Method::Get))),
        _ => item
            .response
            .payment_option
            .as_ref()
            .and_then(|o| o.card.clone())
            .and_then(|card| card.three_d)
            .and_then(|three_ds| three_ds.acs_url.zip(three_ds.c_req))
            .map(|(base_url, creq)| RedirectForm::Form {
                endpoint: base_url,
                method: Method::Post,
                form_fields: std::collections::HashMap::from([("creq".to_string(), creq.expose())]),
            }),
    };
    let connector_response_data =
        convert_to_additional_payment_method_connector_response(&item.response)
            .map(ConnectorResponseData::with_additional_payment_method_data);

    let status = get_payment_status(&item.response, amount, is_post_capture_void);

    Ok((status, redirection_data, connector_response_data))
}

// Helper function to create transaction response
fn create_transaction_response(
    response: &NuveiPaymentsResponse,
    redirection_data: Option<RedirectForm>,
    ip_address: Option<String>,
) -> Result<PaymentsResponseData, error_stack::Report<errors::ConnectorError>> {
    Ok(PaymentsResponseData::TransactionResponse {
        resource_id: response
            .transaction_id
            .clone()
            .map_or(response.order_id.clone(), Some) // For paypal there will be no transaction_id, only order_id will be present
            .map(ResponseId::ConnectorTransactionId)
            .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?,
        redirection_data: Box::new(redirection_data),
        mandate_reference: Box::new(
            response
                .payment_option
                .as_ref()
                .and_then(|po| po.user_payment_option_id.clone())
                .map(|id| MandateReference {
                    connector_mandate_id: Some(id),
                    payment_method_id: None,
                    mandate_metadata: ip_address
                        .map(|ip| pii::SecretSerdeValue::new(serde_json::Value::String(ip))),
                    connector_mandate_request_reference_id: None,
                }),
        ),
        // we don't need to save session token for capture, void flow so ignoring if it is not present
        connector_metadata: if let Some(token) = response.session_token.clone() {
            Some(
                serde_json::to_value(NuveiMeta {
                    session_token: token,
                })
                .change_context(errors::ConnectorError::ResponseHandlingFailed)?,
            )
        } else {
            None
        },
        network_txn_id: response
            .external_scheme_transaction_id
            .as_ref()
            .map(|ntid| ntid.clone().expose()),
        connector_response_reference_id: response.order_id.clone(),
        incremental_authorization_allowed: None,
        charges: None,
    })
}

// Specialized implementation for Authorize
impl
    TryFrom<
        ResponseRouterData<
            Authorize,
            NuveiPaymentsResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    > for RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            Authorize,
            NuveiPaymentsResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        // Get amount directly from the authorize data
        let amount = Some(item.data.request.amount);

        let (status, redirection_data, connector_response_data) =
            process_nuvei_payment_response(&item, amount, false)?;

        let (amount_captured, minor_amount_capturable) = item.response.get_amount_captured()?;

        let ip_address = item
            .data
            .request
            .browser_info
            .clone()
            .and_then(|browser_info| browser_info.ip_address.map(|ip| ip.to_string()));

        Ok(Self {
            status,
            response: if let Some(err) = build_error_response(&item.response, item.http_code) {
                Err(err)
            } else {
                Ok(create_transaction_response(
                    &item.response,
                    redirection_data,
                    ip_address,
                )?)
            },
            amount_captured,
            minor_amount_capturable,
            connector_response: connector_response_data,
            ..item.data
        })
    }
}

// Generic implementation for other flow types
impl<F, T> TryFrom<ResponseRouterData<F, NuveiPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
where
    F: NuveiPaymentsGenericResponse + std::fmt::Debug,
    T: std::fmt::Debug,
    F: std::any::Any,
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, NuveiPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let amount = item
            .data
            .minor_amount_capturable
            .map(|amount| amount.get_amount_as_i64());
        let (status, redirection_data, connector_response_data) =
            process_nuvei_payment_response(&item, amount, F::is_post_capture_void())?;

        let (amount_captured, minor_amount_capturable) = item.response.get_amount_captured()?;
        Ok(Self {
            status,
            response: if let Some(err) = build_error_response(&item.response, item.http_code) {
                Err(err)
            } else {
                Ok(create_transaction_response(
                    &item.response,
                    redirection_data,
                    None,
                )?)
            },
            amount_captured,
            minor_amount_capturable,
            connector_response: connector_response_data,
            ..item.data
        })
    }
}

impl TryFrom<PaymentsPreprocessingResponseRouterData<NuveiPaymentsResponse>>
    for types::PaymentsPreProcessingRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsPreprocessingResponseRouterData<NuveiPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        let response = item.response;
        let is_enrolled_for_3ds = response
            .clone()
            .payment_option
            .and_then(|po| po.card)
            .and_then(|c| c.three_d)
            .and_then(|t| t.v2supported)
            .map(to_boolean)
            .unwrap_or_default();
        Ok(Self {
            status: get_payment_status(&response, item.data.request.amount, false),
            response: Ok(PaymentsResponseData::ThreeDSEnrollmentResponse {
                enrolled_v2: is_enrolled_for_3ds,
                related_transaction_id: response.transaction_id,
            }),

            ..item.data
        })
    }
}

impl From<NuveiTransactionStatus> for enums::RefundStatus {
    fn from(item: NuveiTransactionStatus) -> Self {
        match item {
            NuveiTransactionStatus::Approved => Self::Success,
            NuveiTransactionStatus::Declined | NuveiTransactionStatus::Error => Self::Failure,
            NuveiTransactionStatus::Processing
            | NuveiTransactionStatus::Pending
            | NuveiTransactionStatus::Redirect => Self::Pending,
        }
    }
}

impl TryFrom<RefundsResponseRouterData<Execute, NuveiPaymentsResponse>>
    for types::RefundsRouterData<Execute>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, NuveiPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        let transaction_id = item
            .response
            .transaction_id
            .clone()
            .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?;

        let refund_response =
            get_refund_response(item.response.clone(), item.http_code, transaction_id);

        Ok(Self {
            response: refund_response.map_err(|err| *err),
            ..item.data
        })
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, NuveiPaymentsResponse>>
    for types::RefundsRouterData<RSync>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, NuveiPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        let transaction_id = item
            .response
            .transaction_id
            .clone()
            .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?;

        let refund_response =
            get_refund_response(item.response.clone(), item.http_code, transaction_id);

        Ok(Self {
            response: refund_response.map_err(|err| *err),
            ..item.data
        })
    }
}

impl<F, Req> TryFrom<&RouterData<F, Req, PaymentsResponseData>> for NuveiPaymentsRequest
where
    Req: NuveiAuthorizePreprocessingCommon,
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(data: &RouterData<F, Req, PaymentsResponseData>) -> Result<Self, Self::Error> {
        {
            let item = data;
            let connector_mandate_id = &item.request.get_connector_mandate_id();
            let customer_id = item
                .request
                .get_customer_id_required()
                .ok_or(missing_field_err("customer_id")())?;
            let related_transaction_id = if item.is_three_ds() {
                item.request.get_related_transaction_id().clone()
            } else {
                None
            };

            let ip_address = data
                .recurring_mandate_payment_data
                .as_ref()
                .and_then(|r| r.mandate_metadata.as_ref())
                .ok_or(errors::ConnectorError::MissingRequiredField {
                    field_name: "browser_info.ip_address",
                })?
                .clone()
                .expose()
                .as_str()
                .ok_or(errors::ConnectorError::MissingRequiredField {
                    field_name: "browser_info.ip_address",
                })?
                .to_owned();

            Ok(Self {
                related_transaction_id,
                device_details: DeviceDetails {
                    ip_address: Secret::new(ip_address),
                },
                is_rebilling: Some("1".to_string()), // In case of second installment, rebilling should be 1
                user_token_id: Some(customer_id),
                payment_option: PaymentOption {
                    user_payment_option_id: connector_mandate_id.clone(),
                    ..Default::default()
                },
                ..Default::default()
            })
        }
    }
}

impl ForeignTryFrom<&Option<BrowserInformation>> for DeviceDetails {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn foreign_try_from(browser_info: &Option<BrowserInformation>) -> Result<Self, Self::Error> {
        let browser_info = browser_info
            .as_ref()
            .ok_or_else(missing_field_err("browser_info"))?;
        Ok(Self {
            ip_address: browser_info.get_ip_address()?,
        })
    }
}

fn get_refund_response(
    response: NuveiPaymentsResponse,
    http_code: u16,
    txn_id: String,
) -> Result<RefundsResponseData, Box<ErrorResponse>> {
    let refund_status = response
        .transaction_status
        .clone()
        .map(enums::RefundStatus::from)
        .unwrap_or(enums::RefundStatus::Failure);
    match response.status {
        NuveiPaymentStatus::Error => Err(Box::new(get_error_response(
            response.err_code,
            &response.reason,
            http_code,
            &response.merchant_advice_code,
            &response.gw_error_code.map(|e| e.to_string()),
            &response.gw_error_reason,
        ))),
        _ => match response.transaction_status {
            Some(NuveiTransactionStatus::Error) => Err(Box::new(get_error_response(
                response.err_code,
                &response.reason,
                http_code,
                &response.merchant_advice_code,
                &response.gw_error_code.map(|e| e.to_string()),
                &response.gw_error_reason,
            ))),
            _ => Ok(RefundsResponseData {
                connector_refund_id: txn_id,
                refund_status,
            }),
        },
    }
}

fn get_error_response(
    error_code: Option<i64>,
    error_msg: &Option<String>,
    http_code: u16,
    network_advice_code: &Option<String>,
    network_decline_code: &Option<String>,
    network_error_message: &Option<String>,
) -> ErrorResponse {
    ErrorResponse {
        code: error_code
            .map(|c| c.to_string())
            .unwrap_or_else(|| NO_ERROR_CODE.to_string()),
        message: error_msg
            .clone()
            .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
        reason: None,
        status_code: http_code,
        attempt_status: None,
        connector_transaction_id: None,
        network_advice_code: network_advice_code.clone(),
        network_decline_code: network_decline_code.clone(),
        network_error_message: network_error_message.clone(),
        connector_metadata: None,
    }
}

/// Represents any possible webhook notification from Nuvei.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum NuveiWebhook {
    PaymentDmn(PaymentDmnNotification),
    Chargeback(ChargebackNotification),
}

/// Represents the status of a chargeback event.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum ChargebackStatus {
    RetrievalRequest,
    Chargeback,
    Representment,
    SecondChargeback,
    Arbitration,
    #[serde(other)]
    Unknown,
}

/// Represents a Chargeback webhook notification from the Nuvei Control Panel.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChargebackNotification {
    #[serde(rename = "ppp_TransactionID")]
    pub ppp_transaction_id: Option<String>,
    pub merchant_unique_id: Option<String>,
    pub merchant_id: Option<String>,
    pub merchant_site_id: Option<String>,
    pub request_version: Option<String>,
    pub message: Option<String>,
    pub status: Option<ChargebackStatus>,
    pub reason: Option<String>,
    pub case_id: Option<String>,
    pub processor_case_id: Option<String>,
    pub arn: Option<String>,
    pub retrieval_request_date: Option<String>,
    pub chargeback_date: Option<String>,
    pub chargeback_amount: Option<String>,
    pub chargeback_currency: Option<String>,
    pub original_amount: Option<String>,
    pub original_currency: Option<String>,
    #[serde(rename = "transactionID")]
    pub transaction_id: Option<String>,
    pub user_token_id: Option<String>,
}

/// Represents the overall status of the DMN.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum DmnStatus {
    Success,
    Error,
    Pending,
}

/// Represents the status of the transaction itself.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum TransactionStatus {
    Approved,
    Declined,
    Error,
    Cancelled,
    Pending,
    #[serde(rename = "Settle")]
    Settled,
}

/// Represents the type of transaction.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "PascalCase")]
pub enum PaymentTransactionType {
    Auth,
    Sale,
    Settle,
    Credit,
    Void,
    Auth3D,
    Sale3D,
    Verif,
}

/// Represents a Payment Direct Merchant Notification (DMN) webhook.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentDmnNotification {
    #[serde(rename = "PPP_TransactionID")]
    pub ppp_transaction_id: Option<String>,
    #[serde(rename = "TransactionID")]
    pub transaction_id: Option<String>,
    pub status: Option<DmnStatus>,
    #[serde(rename = "ErrCode")]
    pub err_code: Option<String>,
    #[serde(rename = "ExErrCode")]
    pub ex_err_code: Option<String>,
    pub desc: Option<String>,
    pub merchant_unique_id: Option<String>,
    pub custom_data: Option<String>,
    pub product_id: Option<String>,
    pub first_name: Option<String>,
    pub last_name: Option<String>,
    pub email: Option<String>,
    pub total_amount: Option<String>,
    pub currency: Option<String>,
    pub fee: Option<String>,
    #[serde(rename = "AuthCode")]
    pub auth_code: Option<String>,
    pub transaction_status: Option<TransactionStatus>,
    pub transaction_type: Option<PaymentTransactionType>,
    #[serde(rename = "user_token_id")]
    pub user_token_id: Option<String>,
    #[serde(rename = "payment_method")]
    pub payment_method: Option<String>,
    #[serde(rename = "responseTimeStamp")]
    pub response_time_stamp: Option<String>,
    #[serde(rename = "invoice_id")]
    pub invoice_id: Option<String>,
    #[serde(rename = "merchant_id")]
    pub merchant_id: Option<String>,
    #[serde(rename = "merchant_site_id")]
    pub merchant_site_id: Option<String>,
    #[serde(rename = "responsechecksum")]
    pub response_checksum: Option<String>,
    #[serde(rename = "advanceResponseChecksum")]
    pub advance_response_checksum: Option<String>,
}

// For backward compatibility with existing code
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct NuveiWebhookTransactionId {
    #[serde(rename = "ppp_TransactionID")]
    pub ppp_transaction_id: String,
}

// Helper struct to extract transaction ID from either webhook type
impl From<&NuveiWebhook> for NuveiWebhookTransactionId {
    fn from(webhook: &NuveiWebhook) -> Self {
        match webhook {
            NuveiWebhook::Chargeback(notification) => Self {
                ppp_transaction_id: notification.ppp_transaction_id.clone().unwrap_or_default(),
            },
            NuveiWebhook::PaymentDmn(notification) => Self {
                ppp_transaction_id: notification.ppp_transaction_id.clone().unwrap_or_default(),
            },
        }
    }
}

// Convert webhook to payments response for further processing
impl From<NuveiWebhook> for NuveiPaymentsResponse {
    fn from(webhook: NuveiWebhook) -> Self {
        match webhook {
            NuveiWebhook::Chargeback(notification) => Self {
                transaction_status: Some(NuveiTransactionStatus::Processing),
                transaction_id: notification.transaction_id,
                transaction_type: Some(NuveiTransactionType::Credit), // Using Credit as placeholder for chargeback
                ..Default::default()
            },
            NuveiWebhook::PaymentDmn(notification) => {
                let transaction_type = notification.transaction_type.map(|tt| match tt {
                    PaymentTransactionType::Auth => NuveiTransactionType::Auth,
                    PaymentTransactionType::Sale => NuveiTransactionType::Sale,
                    PaymentTransactionType::Settle => NuveiTransactionType::Settle,
                    PaymentTransactionType::Credit => NuveiTransactionType::Credit,
                    PaymentTransactionType::Void => NuveiTransactionType::Void,
                    PaymentTransactionType::Auth3D => NuveiTransactionType::Auth3D,
                    PaymentTransactionType::Sale3D => NuveiTransactionType::Auth3D, // Map to closest equivalent
                    PaymentTransactionType::Verif => NuveiTransactionType::Auth, // Map to closest equivalent
                });

                Self {
                    transaction_status: notification.transaction_status.map(|ts| match ts {
                        TransactionStatus::Approved => NuveiTransactionStatus::Approved,
                        TransactionStatus::Declined => NuveiTransactionStatus::Declined,
                        TransactionStatus::Error => NuveiTransactionStatus::Error,
                        TransactionStatus::Settled => NuveiTransactionStatus::Approved,
                        _ => NuveiTransactionStatus::Processing,
                    }),
                    transaction_id: notification.transaction_id,
                    transaction_type,
                    ..Default::default()
                }
            }
        }
    }
}

fn get_cvv2_response_description(code: &str) -> Option<&str> {
    match code {
        "M" => Some("CVV2 Match"),
        "N" => Some("CVV2 No Match"),
        "P" => Some("Not Processed. For EU card-on-file (COF) and ecommerce (ECOM) network token transactions, Visa removes any CVV and sends P. If you have fraud or security concerns, Visa recommends using 3DS."),
        "U" => Some("Issuer is not certified and/or has not provided Visa the encryption keys"),
        "S" => Some("CVV2 processor is unavailable."),
        _=> None,
    }
}

fn get_avs_response_description(code: &str) -> Option<&str> {
    match code {
        "A" => Some("The street address matches, the ZIP code does not."),
        "W" => Some("Postal code matches, the street address does not."),
        "Y" => Some("Postal code and the street address match."),
        "X" => Some("An exact match of both the 9-digit ZIP code and the street address."),
        "Z" => Some("Postal code matches, the street code does not."),
        "U" => Some("Issuer is unavailable."),
        "S" => Some("AVS not supported by issuer."),
        "R" => Some("Retry."),
        "B" => Some("Not authorized (declined)."),
        "N" => Some("Both the street address and postal code do not match."),
        _ => None,
    }
}

fn get_merchant_advice_code_description(code: &str) -> Option<&str> {
    match code {
        "01" => Some("New Account Information Available"),
        "02" => Some("Cannot approve at this time, try again later"),
        "03" => Some("Do Not Try Again"),
        "04" => Some("Token requirements not fulfilled for this token type"),
        "21" => Some("Payment Cancellation, do not try again"),
        "24" => Some("Retry after 1 hour"),
        "25" => Some("Retry after 24 hours"),
        "26" => Some("Retry after 2 days"),
        "27" => Some("Retry after 4 days"),
        "28" => Some("Retry after 6 days"),
        "29" => Some("Retry after 8 days"),
        "30" => Some("Retry after 10 days"),
        "40" => Some("Card is a consumer non-reloadable prepaid card"),
        "41" => Some("Card is a consumer single-use virtual card number"),
        "42" => Some("Transaction type exceeds issuer's risk threshold. Please retry with another payment account."),
        "43" => Some("Card is a consumer multi-use virtual card number"),
        _ => None,
    }
}

/// Concatenates a vector of strings without any separator
/// This is useful for creating verification messages for webhooks
pub fn concat_strings(strings: &[String]) -> String {
    strings.join("")
}

fn convert_to_additional_payment_method_connector_response(
    transaction_response: &NuveiPaymentsResponse,


pub async fn update_merchant_account(
    state: web::Data<AppState>,
    req: HttpRequest,
    mid: web::Path<common_utils::id_type::MerchantId>,
    json_payload: web::Json<admin::MerchantAccountUpdate>,
) -> HttpResponse {
    let flow = Flow::MerchantsAccountUpdate;
    let merchant_id = mid.into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, _, req, _| merchant_account_update(state, &merchant_id, None, req),
        auth::auth_type(
            &auth::PlatformOrgAdminAuthWithMerchantIdFromRoute {
                merchant_id_from_route: merchant_id.clone(),
                is_admin_auth_allowed: true,
            },
            &auth::JWTAuthMerchantFromRoute {
                merchant_id: merchant_id.clone(),
                required_permission: Permission::MerchantAccountWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


impl api::PaymentVoid for Worldpayxml {}

impl api::PaymentVoid for for Worldpayxml


pub struct PriorityLogicData {
    pub name: Option<String>,
    pub status: Option<String>,
    pub failure_reason: Option<String>,
}


pub fn requires_defend_dispute(self) -> bool {
        matches!(self, Self::Checkout)
    }


impl webhooks::IncomingWebhook for Flexiti {
    fn get_webhook_object_reference_id(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_event_type(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_resource_object(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }
}

impl webhooks::IncomingWebhook for for Flexiti


pub struct PaymentValue {
    pub amount: i64,
    pub currency: api_models::enums::Currency,
}


impl api::Payment for Stripebilling {}


pub async fn fetch_payout_method_data(
    state: &SessionState,
    payout_data: &mut PayoutData,
    connector_data: &api::ConnectorData,
    merchant_context: &domain::MerchantContext,
) -> RouterResult<()> {
    let connector_transfer_method_id =
        should_create_connector_transfer_method(payout_data, connector_data)?;

    if connector_transfer_method_id.is_some() {
        logger::info!("Using stored transfer_method_id, skipping payout_method_data fetch");
    } else {
        let customer_id = payout_data
            .payouts
            .customer_id
            .clone()
            .get_required_value("customer_id")?;

        let payout_method_data_clone = payout_data.payout_method_data.clone();
        let payout_token = payout_data.payout_attempt.payout_token.clone();
        let merchant_id = payout_data.payout_attempt.merchant_id.clone();
        let payout_type = payout_data.payouts.payout_type;

        let payout_method_data = make_payout_method_data(
            state,
            payout_method_data_clone.as_ref(),
            payout_token.as_deref(),
            &customer_id,
            &merchant_id,
            payout_type,
            merchant_context.get_merchant_key_store(),
            Some(payout_data),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await?
        .get_required_value("payout_method_data")?;

        payout_data.payout_method_data = Some(payout_method_data);
    }

    Ok(())
}