    let mut security_header_kvs: HashMap<String, Vec<u8>> = HashMap::with_capacity(props.len());

    for prop_str in &props {
        let (prop_key, prop_value) = prop_str
            .split_once('=')
            .ok_or(ConnectorError::WebhookSourceVerificationFailed)?;

        security_header_kvs.insert(prop_key.to_string(), prop_value.bytes().collect());
    }

    Ok(security_header_kvs)
}

#[async_trait::async_trait]
impl IncomingWebhook for Stripe {
    fn get_webhook_source_verification_algorithm(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, ConnectorError> {
        Ok(Box::new(crypto::HmacSha256))
    }

    fn get_webhook_source_verification_signature(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, ConnectorError> {
        let mut security_header_kvs = get_signature_elements_from_header(request.headers)?;

        let signature = security_header_kvs
            .remove("v1")
            .ok_or(ConnectorError::WebhookSignatureNotFound)?;

        hex::decode(signature).change_context(ConnectorError::WebhookSignatureNotFound)
    }

    fn get_webhook_source_verification_message(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        _merchant_id: &common_utils::id_type::MerchantId,
        _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, ConnectorError> {
        let mut security_header_kvs = get_signature_elements_from_header(request.headers)?;

        let timestamp = security_header_kvs
            .remove("t")
            .ok_or(ConnectorError::WebhookSignatureNotFound)?;

        Ok(format!(
            "{}.{}",
            String::from_utf8_lossy(&timestamp),
            String::from_utf8_lossy(request.body)
        )
        .into_bytes())
    }

    fn get_webhook_object_reference_id(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, ConnectorError> {
        let details: stripe::WebhookEvent = request
            .body
            .parse_struct("WebhookEvent")
            .change_context(ConnectorError::WebhookReferenceIdNotFound)?;

        Ok(match details.event_data.event_object.object {
            stripe::WebhookEventObjectType::PaymentIntent => {
                match details
                    .event_data
                    .event_object
                    .metadata
                    .and_then(|meta_data| meta_data.order_id)
                {
                    // if order_id is present
                    Some(order_id) => api_models::webhooks::ObjectReferenceId::PaymentId(
                        api_models::payments::PaymentIdType::PaymentAttemptId(order_id),
                    ),
                    // else used connector_transaction_id
                    None => api_models::webhooks::ObjectReferenceId::PaymentId(
                        api_models::payments::PaymentIdType::ConnectorTransactionId(
                            details.event_data.event_object.id,
                        ),
                    ),
                }
            }
            stripe::WebhookEventObjectType::Charge => {
                match details
                    .event_data
                    .event_object
                    .metadata
                    .and_then(|meta_data| meta_data.order_id)
                {
                    // if order_id is present
                    Some(order_id) => api_models::webhooks::ObjectReferenceId::PaymentId(
                        api_models::payments::PaymentIdType::PaymentAttemptId(order_id),
                    ),
                    // else used connector_transaction_id
                    None => api_models::webhooks::ObjectReferenceId::PaymentId(
                        api_models::payments::PaymentIdType::ConnectorTransactionId(
                            details
                                .event_data
                                .event_object
                                .payment_intent
                                .ok_or(ConnectorError::WebhookReferenceIdNotFound)?,
                        ),
                    ),
                }
            }
            stripe::WebhookEventObjectType::Dispute => {
                api_models::webhooks::ObjectReferenceId::PaymentId(
                    api_models::payments::PaymentIdType::ConnectorTransactionId(
                        details
                            .event_data
                            .event_object
                            .payment_intent
                            .ok_or(ConnectorError::WebhookReferenceIdNotFound)?,
                    ),
                )
            }
            stripe::WebhookEventObjectType::Source => {
                api_models::webhooks::ObjectReferenceId::PaymentId(
                    api_models::payments::PaymentIdType::PreprocessingId(
                        details.event_data.event_object.id,
                    ),
                )
            }
            stripe::WebhookEventObjectType::Refund => {
                match details
                    .event_data
                    .event_object
                    .metadata
                    .clone()
                    .and_then(|meta_data| meta_data.order_id)
                {
                    // if meta_data is present
                    Some(order_id) => {
                        // Issue: 2076
                        match details
                            .event_data
                            .event_object
                            .metadata
                            .and_then(|meta_data| meta_data.is_refund_id_as_reference)
                        {
                            // if the order_id is refund_id
                            Some(_) => api_models::webhooks::ObjectReferenceId::RefundId(
                                api_models::webhooks::RefundIdType::RefundId(order_id),
                            ),
                            // if the order_id is payment_id
                            // since payment_id was being passed before the deployment of this pr
                            _ => api_models::webhooks::ObjectReferenceId::RefundId(
                                api_models::webhooks::RefundIdType::ConnectorRefundId(
                                    details.event_data.event_object.id,
                                ),
                            ),
                        }
                    }
                    // else use connector_transaction_id
                    None => api_models::webhooks::ObjectReferenceId::RefundId(
                        api_models::webhooks::RefundIdType::ConnectorRefundId(
                            details.event_data.event_object.id,
                        ),
                    ),
                }
            }
        })
    }

    fn get_webhook_event_type(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<IncomingWebhookEvent, ConnectorError> {
        let details: stripe::WebhookEventTypeBody = request
            .body
            .parse_struct("WebhookEventTypeBody")
            .change_context(ConnectorError::WebhookReferenceIdNotFound)?;

        Ok(match details.event_type {
            stripe::WebhookEventType::PaymentIntentFailed => {
                IncomingWebhookEvent::PaymentIntentFailure
            }
            stripe::WebhookEventType::PaymentIntentSucceed => {
                IncomingWebhookEvent::PaymentIntentSuccess
            }
            stripe::WebhookEventType::PaymentIntentCanceled => {
                IncomingWebhookEvent::PaymentIntentCancelled
            }
            stripe::WebhookEventType::PaymentIntentAmountCapturableUpdated => {
                IncomingWebhookEvent::PaymentIntentAuthorizationSuccess
            }
            stripe::WebhookEventType::ChargeSucceeded => {
                if let Some(stripe::WebhookPaymentMethodDetails {
                    payment_method:
                        stripe::WebhookPaymentMethodType::AchCreditTransfer
                        | stripe::WebhookPaymentMethodType::MultibancoBankTransfers,
                }) = details.event_data.event_object.payment_method_details
                {
                    IncomingWebhookEvent::PaymentIntentSuccess
                } else {
                    IncomingWebhookEvent::EventNotSupported
                }
            }
            stripe::WebhookEventType::ChargeRefundUpdated => details
                .event_data
                .event_object
                .status
                .map(|status| match status {
                    stripe::WebhookEventStatus::Succeeded => IncomingWebhookEvent::RefundSuccess,
                    stripe::WebhookEventStatus::Failed => IncomingWebhookEvent::RefundFailure,
                    _ => IncomingWebhookEvent::EventNotSupported,
                })
                .unwrap_or(IncomingWebhookEvent::EventNotSupported),
            stripe::WebhookEventType::SourceChargeable => IncomingWebhookEvent::SourceChargeable,
            stripe::WebhookEventType::DisputeCreated => IncomingWebhookEvent::DisputeOpened,
            stripe::WebhookEventType::DisputeClosed => IncomingWebhookEvent::DisputeCancelled,
            stripe::WebhookEventType::DisputeUpdated => details
                .event_data
                .event_object
                .status
                .map(Into::into)
                .unwrap_or(IncomingWebhookEvent::EventNotSupported),
            stripe::WebhookEventType::PaymentIntentPartiallyFunded => {
                IncomingWebhookEvent::PaymentIntentPartiallyFunded
            }
            stripe::WebhookEventType::PaymentIntentRequiresAction => {
                IncomingWebhookEvent::PaymentActionRequired
            }
            stripe::WebhookEventType::ChargeDisputeFundsWithdrawn => {
                IncomingWebhookEvent::DisputeLost
            }
            stripe::WebhookEventType::ChargeDisputeFundsReinstated => {
                IncomingWebhookEvent::DisputeWon
            }
            stripe::WebhookEventType::Unknown
            | stripe::WebhookEventType::ChargeCaptured
            | stripe::WebhookEventType::ChargeExpired
            | stripe::WebhookEventType::ChargeFailed
            | stripe::WebhookEventType::ChargePending
            | stripe::WebhookEventType::ChargeUpdated
            | stripe::WebhookEventType::ChargeRefunded
            | stripe::WebhookEventType::PaymentIntentCreated
            | stripe::WebhookEventType::PaymentIntentProcessing
            | stripe::WebhookEventType::SourceTransactionCreated => {
                IncomingWebhookEvent::EventNotSupported
            }
        })
    }

    fn get_webhook_resource_object(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, ConnectorError> {
        let details: stripe::WebhookEvent = request
            .body
            .parse_struct("WebhookEvent")
            .change_context(ConnectorError::WebhookBodyDecodingFailed)?;

        Ok(Box::new(details.event_data.event_object))
    }
    fn get_dispute_details(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<DisputePayload, ConnectorError> {
        let details: stripe::WebhookEvent = request
            .body
            .parse_struct("WebhookEvent")
            .change_context(ConnectorError::WebhookBodyDecodingFailed)?;
        let amt = details.event_data.event_object.amount.ok_or_else(|| {
            ConnectorError::MissingRequiredField {
                field_name: "amount",
            }
        })?;

        Ok(DisputePayload {
            amount: utils::convert_amount(
                self.amount_converter_webhooks,
                amt,
                details.event_data.event_object.currency,
            )?,
            currency: details.event_data.event_object.currency,
            dispute_stage: api_models::enums::DisputeStage::Dispute,
            connector_dispute_id: details.event_data.event_object.id,
            connector_reason: details.event_data.event_object.reason,
            connector_reason_code: None,
            challenge_required_by: details
                .event_data
                .event_object
                .evidence_details
                .map(|payload| payload.due_by),
            connector_status: details
                .event_data
                .event_object
                .status
                .ok_or(ConnectorError::WebhookResourceObjectNotFound)?
                .to_string(),
            created_at: Some(details.event_data.event_object.created),
            updated_at: None,
        })
    }
}

impl ConnectorRedirectResponse for Stripe {
    fn get_flow_type(
        &self,
        _query_params: &str,
        _json_payload: Option<serde_json::Value>,
        action: PaymentAction,
    ) -> CustomResult<CallConnectorAction, ConnectorError> {
        match action {
            PaymentAction::PSync
            | PaymentAction::CompleteAuthorize
            | PaymentAction::PaymentAuthenticateCompleteAuthorize => {
                Ok(CallConnectorAction::Trigger)
            }
        }
    }
}

impl api::Payouts for Stripe {}
#[cfg(feature = "payouts")]
impl api::PayoutCancel for Stripe {}
#[cfg(feature = "payouts")]
impl api::PayoutCreate for Stripe {}
#[cfg(feature = "payouts")]
impl api::PayoutFulfill for Stripe {}
#[cfg(feature = "payouts")]
impl api::PayoutRecipient for Stripe {}
#[cfg(feature = "payouts")]
impl api::PayoutRecipientAccount for Stripe {}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoCancel, PayoutsData, PayoutsResponseData> for Stripe {
    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        req: &PayoutsRouterData<PoCancel>,
        connectors: &Connectors,
    ) -> CustomResult<String, ConnectorError> {
        let transfer_id = req.request.get_transfer_id()?;
        Ok(format!(
            "{}v1/transfers/{}/reversals",
            connectors.stripe.base_url, transfer_id
        ))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoCancel>,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        self.build_headers(req, _connectors)
    }

    fn get_request_body(
        &self,
        req: &RouterData<PoCancel, PayoutsData, PayoutsResponseData>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, ConnectorError> {
        let connector_req = stripe::StripeConnectReversalRequest::try_from(req)?;
        Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoCancel>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&PayoutCancelType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(PayoutCancelType::get_headers(self, req, connectors)?)
            .set_body(PayoutCancelType::get_request_body(self, req, connectors)?)
            .build();

        Ok(Some(request))
    }

    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoCancel>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoCancel>, ConnectorError> {
        let response: stripe::StripeConnectReversalResponse = res
            .response
            .parse_struct("StripeConnectReversalResponse")
            .change_context(ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_error_response_body(&response));
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoCreate, PayoutsData, PayoutsResponseData> for Stripe {
    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        _req: &PayoutsRouterData<PoCreate>,
        connectors: &Connectors,
    ) -> CustomResult<String, ConnectorError> {
        Ok(format!("{}v1/transfers", connectors.stripe.base_url))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoCreate>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_request_body(
        &self,
        req: &PayoutsRouterData<PoCreate>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, ConnectorError> {
        let connector_req = stripe::StripeConnectPayoutCreateRequest::try_from(req)?;
        Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoCreate>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&PayoutCreateType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(PayoutCreateType::get_headers(self, req, connectors)?)
            .set_body(PayoutCreateType::get_request_body(self, req, connectors)?)
            .build();

        Ok(Some(request))
    }

    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoCreate>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoCreate>, ConnectorError> {
        let response: stripe::StripeConnectPayoutCreateResponse = res
            .response
            .parse_struct("StripeConnectPayoutCreateResponse")
            .change_context(ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_error_response_body(&response));
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoFulfill, PayoutsData, PayoutsResponseData> for Stripe {
    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        _req: &PayoutsRouterData<PoFulfill>,
        connectors: &Connectors,
    ) -> CustomResult<String, ConnectorError> {
        Ok(format!("{}v1/payouts", connectors.stripe.base_url,))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        let mut headers = self.build_headers(req, connectors)?;
        let customer_account = req.get_connector_customer_id()?;
        let mut customer_account_header = vec![(
            STRIPE_COMPATIBLE_CONNECT_ACCOUNT.to_string(),
            customer_account.into_masked(),
        )];
        headers.append(&mut customer_account_header);
        Ok(headers)
    }

    fn get_request_body(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, ConnectorError> {
        let connector_req = stripe::StripeConnectPayoutFulfillRequest::try_from(req)?;
        Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&PayoutFulfillType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(PayoutFulfillType::get_headers(self, req, connectors)?)
            .set_body(PayoutFulfillType::get_request_body(self, req, connectors)?)
            .build();

        Ok(Some(request))
    }

    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoFulfill>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoFulfill>, ConnectorError> {
        let response: stripe::StripeConnectPayoutFulfillResponse = res
            .response
            .parse_struct("StripeConnectPayoutFulfillResponse")
            .change_context(ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_error_response_body(&response));
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoRecipient, PayoutsData, PayoutsResponseData> for Stripe {
    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        _req: &PayoutsRouterData<PoRecipient>,
        connectors: &Connectors,
    ) -> CustomResult<String, ConnectorError> {
        Ok(format!("{}v1/accounts", connectors.stripe.base_url))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoRecipient>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_request_body(
        &self,
        req: &PayoutsRouterData<PoRecipient>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, ConnectorError> {
        let connector_req = stripe::StripeConnectRecipientCreateRequest::try_from(req)?;
        Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoRecipient>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&PayoutRecipientType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(PayoutRecipientType::get_headers(self, req, connectors)?)
            .set_body(PayoutRecipientType::get_request_body(
                self, req, connectors,
            )?)
            .build();

        Ok(Some(request))
    }

    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoRecipient>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoRecipient>, ConnectorError> {
        let response: stripe::StripeConnectRecipientCreateResponse = res
            .response
            .parse_struct("StripeConnectRecipientCreateResponse")
            .change_context(ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_error_response_body(&response));
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoRecipientAccount, PayoutsData, PayoutsResponseData> for Stripe {
    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        req: &PayoutsRouterData<PoRecipientAccount>,
        connectors: &Connectors,
    ) -> CustomResult<String, ConnectorError> {
        let connector_customer_id = req.get_connector_customer_id()?;
        Ok(format!(
            "{}v1/accounts/{}/external_accounts",
            connectors.stripe.base_url, connector_customer_id
        ))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoRecipientAccount>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_request_body(
        &self,
        req: &PayoutsRouterData<PoRecipientAccount>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, ConnectorError> {
        let connector_req = stripe::StripeConnectRecipientAccountCreateRequest::try_from(req)?;
        Ok(RequestContent::FormUrlEncoded(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoRecipientAccount>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&PayoutRecipientAccountType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(PayoutRecipientAccountType::get_headers(
                self, req, connectors,
            )?)
            .set_body(PayoutRecipientAccountType::get_request_body(
                self, req, connectors,
            )?)
            .build();

        Ok(Some(request))
    }

    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoRecipientAccount>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoRecipientAccount>, ConnectorError> {
        let response: stripe::StripeConnectRecipientAccountCreateResponse = res
            .response
            .parse_struct("StripeConnectRecipientAccountCreateResponse")
            .change_context(ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_error_response_body(&response));
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

static STRIPE_SUPPORTED_PAYMENT_METHODS: LazyLock<SupportedPaymentMethods> = LazyLock::new(|| {
    let default_capture_methods = vec![
        CaptureMethod::Automatic,
        CaptureMethod::Manual,
        CaptureMethod::SequentialAutomatic,
    ];

    let automatic_capture_supported =
        vec![CaptureMethod::Automatic, CaptureMethod::SequentialAutomatic];

    let supported_card_network = vec![
        common_enums::CardNetwork::Visa,
        common_enums::CardNetwork::Mastercard,
        common_enums::CardNetwork::AmericanExpress,
        common_enums::CardNetwork::Discover,
        common_enums::CardNetwork::JCB,
        common_enums::CardNetwork::DinersClub,
        common_enums::CardNetwork::UnionPay,
    ];

    let mut stripe_supported_payment_methods = SupportedPaymentMethods::new();

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::Card,
        PaymentMethodType::Credit,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: Some(
                api_models::feature_matrix::PaymentMethodSpecificFeatures::Card(
                    api_models::feature_matrix::CardSpecificFeatures {
                        three_ds: common_enums::FeatureStatus::Supported,
                        no_three_ds: common_enums::FeatureStatus::Supported,
                        supported_card_networks: supported_card_network.clone(),
                    },
                ),
            ),
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::Card,
        PaymentMethodType::Debit,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: Some(
                api_models::feature_matrix::PaymentMethodSpecificFeatures::Card(
                    api_models::feature_matrix::CardSpecificFeatures {
                        three_ds: common_enums::FeatureStatus::Supported,
                        no_three_ds: common_enums::FeatureStatus::Supported,
                        supported_card_networks: supported_card_network.clone(),
                    },
                ),
            ),
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::PayLater,
        PaymentMethodType::Klarna,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::PayLater,
        PaymentMethodType::Affirm,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::PayLater,
        PaymentMethodType::AfterpayClearpay,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::Wallet,
        PaymentMethodType::AliPay,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::Wallet,
        PaymentMethodType::AmazonPay,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::Wallet,
        PaymentMethodType::ApplePay,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::Wallet,
        PaymentMethodType::GooglePay,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::Wallet,
        PaymentMethodType::WeChatPay,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::Wallet,
        PaymentMethodType::Cashapp,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::Wallet,
        PaymentMethodType::RevolutPay,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankDebit,
        PaymentMethodType::Becs,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankDebit,
        PaymentMethodType::Ach,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankDebit,
        PaymentMethodType::Sepa,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankDebit,
        PaymentMethodType::Bacs,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: default_capture_methods.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankRedirect,
        PaymentMethodType::BancontactCard,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankRedirect,
        PaymentMethodType::Blik,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankTransfer,
        PaymentMethodType::Ach,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankTransfer,
        PaymentMethodType::SepaBankTransfer,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankTransfer,
        PaymentMethodType::Bacs,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankTransfer,
        PaymentMethodType::Multibanco,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankRedirect,
        PaymentMethodType::Giropay,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::NotSupported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankRedirect,
        PaymentMethodType::Ideal,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankRedirect,
        PaymentMethodType::Przelewy24,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankRedirect,
        PaymentMethodType::Eps,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankRedirect,
        PaymentMethodType::OnlineBankingFpx,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::NotSupported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods.add(
        common_enums::PaymentMethod::BankRedirect,
        PaymentMethodType::Sofort,
        PaymentMethodDetails {
            mandates: common_enums::FeatureStatus::Supported,
            refunds: common_enums::FeatureStatus::Supported,
            supported_capture_methods: automatic_capture_supported.clone(),
            specific_features: None,
        },
    );

    stripe_supported_payment_methods
});

static STRIPE_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
    display_name: "Stripe",


File: crates/analytics/src/refunds/metrics/sessionized_metrics/refund_processed_amount.rs

use std::collections::HashSet;

use api_models::analytics::{
    refunds::{RefundDimensions, RefundFilters, RefundMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use super::RefundMetricRow;
use crate::{
    enums::AuthInfo,
    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
};
#[derive(Default)]
pub(crate) struct RefundProcessedAmount {}

#[async_trait::async_trait]
impl<T> super::RefundMetric<T> for RefundProcessedAmount
where
    T: AnalyticsDataSource + super::RefundMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[RefundDimensions],
        auth: &AuthInfo,
        filters: &RefundFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(RefundMetricsBucketIdentifier, RefundMetricRow)>>
    where
        T: AnalyticsDataSource + super::RefundMetricAnalytics,
    {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::RefundSessionized);

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }

        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("count"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Sum {
                field: "refund_amount",
                alias: Some("total"),
            })
            .switch()?;
        query_builder.add_select_column("currency").switch()?;
        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;

        filters.set_filter_clause(&mut query_builder).switch()?;

        auth.set_filter_clause(&mut query_builder).switch()?;

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        for dim in dimensions.iter() {
            query_builder.add_group_by_clause(dim).switch()?;
        }

        query_builder.add_group_by_clause("currency").switch()?;

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .switch()?;
        }

        query_builder
            .add_filter_clause(
                RefundDimensions::RefundStatus,
                storage_enums::RefundStatus::Success,
            )
            .switch()?;

        query_builder
            .execute_query::<RefundMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    RefundMetricsBucketIdentifier::new(
                        i.currency.as_ref().map(|i| i.0),
                        None,
                        i.connector.clone(),
                        i.refund_type.as_ref().map(|i| i.0.to_string()),
                        i.profile_id.clone(),
                        i.refund_reason.clone(),
                        i.refund_error_message.clone(),
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<HashSet<_>, crate::query::PostProcessingError>>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


impl GsmInterface for KafkaStore {
    async fn add_gsm_rule(
        &self,
        rule: hyperswitch_domain_models::gsm::GatewayStatusMap,
    ) -> CustomResult<hyperswitch_domain_models::gsm::GatewayStatusMap, errors::StorageError> {
        self.diesel_store.add_gsm_rule(rule).await
    }

    async fn find_gsm_decision(
        &self,
        connector: String,
        flow: String,
        sub_flow: String,
        code: String,
        message: String,
    ) -> CustomResult<String, errors::StorageError> {
        self.diesel_store
            .find_gsm_decision(connector, flow, sub_flow, code, message)
            .await
    }

    async fn find_gsm_rule(
        &self,
        connector: String,
        flow: String,
        sub_flow: String,
        code: String,
        message: String,
    ) -> CustomResult<hyperswitch_domain_models::gsm::GatewayStatusMap, errors::StorageError> {
        self.diesel_store
            .find_gsm_rule(connector, flow, sub_flow, code, message)
            .await
    }

    async fn update_gsm_rule(
        &self,
        connector: String,
        flow: String,
        sub_flow: String,
        code: String,
        message: String,
        data: hyperswitch_domain_models::gsm::GatewayStatusMappingUpdate,
    ) -> CustomResult<hyperswitch_domain_models::gsm::GatewayStatusMap, errors::StorageError> {
        self.diesel_store
            .update_gsm_rule(connector, flow, sub_flow, code, message, data)
            .await
    }

    async fn delete_gsm_rule(
        &self,
        connector: String,
        flow: String,
        sub_flow: String,
        code: String,
        message: String,
    ) -> CustomResult<bool, errors::StorageError> {
        self.diesel_store
            .delete_gsm_rule(connector, flow, sub_flow, code, message)
            .await
    }
}


File: crates/payment_methods/src/core/migration/payment_methods.rs
 Public functions: 8

use std::str::FromStr;

#[cfg(feature = "v2")]
use api_models::enums as api_enums;
#[cfg(feature = "v1")]
use api_models::enums;
use api_models::payment_methods as pm_api;
#[cfg(feature = "v1")]
use common_utils::{
    consts,
    crypto::Encryptable,
    ext_traits::{AsyncExt, ConfigExt},
    generate_id,
};
use common_utils::{errors::CustomResult, id_type};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    api::ApplicationResponse, errors::api_error_response as errors, merchant_context,
};
#[cfg(feature = "v1")]
use hyperswitch_domain_models::{ext_traits::OptionExt, payment_methods as domain_pm};
use masking::PeekInterface;
#[cfg(feature = "v1")]
use masking::Secret;
#[cfg(feature = "v1")]
use router_env::{instrument, logger, tracing};
#[cfg(feature = "v1")]
use serde_json::json;
use storage_impl::cards_info;

#[cfg(feature = "v1")]
use crate::{
    controller::create_encrypted_data,
    core::migration,
    helpers::{ForeignFrom, StorageErrorExt},
};
use crate::{controller::PaymentMethodsController, helpers::ForeignTryFrom, state};

#[cfg(feature = "v1")]
pub async fn migrate_payment_method(
    state: &state::PaymentMethodsState,
    req: pm_api::PaymentMethodMigrate,
    merchant_id: &id_type::MerchantId,
    merchant_context: &merchant_context::MerchantContext,
    controller: &dyn PaymentMethodsController,
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodMigrateResponse>, errors::ApiErrorResponse>
{
    let mut req = req;
    let card_details = &req.card.get_required_value("card")?;

    let card_number_validation_result =
        cards::CardNumber::from_str(card_details.card_number.peek());

    let card_bin_details = populate_bin_details_for_masked_card(
        card_details,
        &*state.store,
        req.payment_method_type.as_ref(),
    )
    .await?;

    req.card = Some(api_models::payment_methods::MigrateCardDetail {
        card_issuing_country: card_bin_details.issuer_country.clone(),
        card_network: card_bin_details.card_network.clone(),
        card_issuer: card_bin_details.card_issuer.clone(),
        card_type: card_bin_details.card_type.clone(),
        ..card_details.clone()
    });

    if let Some(connector_mandate_details) = &req.connector_mandate_details {
        controller
            .validate_merchant_connector_ids_in_connector_mandate_details(
                merchant_context.get_merchant_key_store(),
                connector_mandate_details,
                merchant_id,
                card_bin_details.card_network.clone(),
            )
            .await?;
    };

    let should_require_connector_mandate_details = req.network_token.is_none();

    let mut migration_status = migration::RecordMigrationStatusBuilder::new();

    let resp = match card_number_validation_result {
        Ok(card_number) => {
            let payment_method_create_request =
                pm_api::PaymentMethodCreate::get_payment_method_create_from_payment_method_migrate(
                    card_number,
                    &req,
                );

            logger::debug!("Storing the card in locker and migrating the payment method");
            get_client_secret_or_add_payment_method_for_migration(
                state,
                payment_method_create_request,
                merchant_context,
                &mut migration_status,
                controller,
            )
            .await?
        }
        Err(card_validation_error) => {
            logger::debug!("Card number to be migrated is invalid, skip saving in locker {card_validation_error}");
            skip_locker_call_and_migrate_payment_method(
                state,
                &req,
                merchant_id.to_owned(),
                merchant_context,
                card_bin_details.clone(),
                should_require_connector_mandate_details,
                &mut migration_status,
                controller,
            )
            .await?
        }
    };
    let payment_method_response = match resp {
        ApplicationResponse::Json(response) => response,
        _ => Err(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to fetch the payment method response")?,
    };

    let pm_id = payment_method_response.payment_method_id.clone();

    let network_token = req.network_token.clone();

    let network_token_migrated = match network_token {
        Some(nt_detail) => {
            logger::debug!("Network token migration");
            let network_token_requestor_ref_id = nt_detail.network_token_requestor_ref_id.clone();
            let network_token_data = &nt_detail.network_token_data;

            Some(
                controller
                    .save_network_token_and_update_payment_method(
                        &req,
                        merchant_context.get_merchant_key_store(),
                        network_token_data,
                        network_token_requestor_ref_id,
                        pm_id,
                    )
                    .await
                    .map_err(|err| logger::error!(?err, "Failed to save network token"))
                    .ok()
                    .unwrap_or_default(),
            )
        }
        None => {
            logger::debug!("Network token data is not available");
            None
        }
    };
    migration_status.network_token_migrated(network_token_migrated);
    let migrate_status = migration_status.build();

    Ok(ApplicationResponse::Json(
        pm_api::PaymentMethodMigrateResponse {
            payment_method_response,
            card_migrated: migrate_status.card_migrated,
            network_token_migrated: migrate_status.network_token_migrated,
            connector_mandate_details_migrated: migrate_status.connector_mandate_details_migrated,
            network_transaction_id_migrated: migrate_status.network_transaction_migrated,
        },
    ))
}

#[cfg(feature = "v2")]
pub async fn migrate_payment_method(
    _state: &state::PaymentMethodsState,
    _req: pm_api::PaymentMethodMigrate,
    _merchant_id: &id_type::MerchantId,
    _merchant_context: &merchant_context::MerchantContext,
    _controller: &dyn PaymentMethodsController,
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodMigrateResponse>, errors::ApiErrorResponse>
{
    todo!()
}

#[cfg(feature = "v1")]
pub async fn populate_bin_details_for_masked_card(
    card_details: &api_models::payment_methods::MigrateCardDetail,
    db: &dyn state::PaymentMethodsStorageInterface,
    payment_method_type: Option<&enums::PaymentMethodType>,
) -> CustomResult<pm_api::CardDetailFromLocker, errors::ApiErrorResponse> {
    if let Some(
            // Cards
            enums::PaymentMethodType::Credit
            | enums::PaymentMethodType::Debit

            // Wallets
            | enums::PaymentMethodType::ApplePay
            | enums::PaymentMethodType::GooglePay,
        ) = payment_method_type {
        migration::validate_card_expiry(
            &card_details.card_exp_month,
            &card_details.card_exp_year,
        )?;
    }

    let card_number = card_details.card_number.clone();

    let (card_isin, _last4_digits) = get_card_bin_and_last4_digits_for_masked_card(
        card_number.peek(),
    )
    .change_context(errors::ApiErrorResponse::InvalidRequestData {
        message: "Invalid masked card number".to_string(),
    })?;

    let card_bin_details = if card_details.card_issuer.is_some()
        && card_details.card_network.is_some()
        && card_details.card_type.is_some()
        && card_details.card_issuing_country.is_some()
    {
        pm_api::CardDetailFromLocker::foreign_try_from((card_details, None))?
    } else {
        let card_info = db
            .get_card_info(&card_isin)
            .await
            .map_err(|error| logger::error!(card_info_error=?error))
            .ok()
            .flatten();

        pm_api::CardDetailFromLocker::foreign_try_from((card_details, card_info))?
    };
    Ok(card_bin_details)
}

#[cfg(feature = "v1")]
impl
    ForeignTryFrom<(
        &api_models::payment_methods::MigrateCardDetail,
        Option<cards_info::CardInfo>,
    )> for pm_api::CardDetailFromLocker
{
    type Error = error_stack::Report<errors::ApiErrorResponse>;
    fn foreign_try_from(
        (card_details, card_info): (
            &api_models::payment_methods::MigrateCardDetail,
            Option<cards_info::CardInfo>,
        ),
    ) -> Result<Self, Self::Error> {
        let (card_isin, last4_digits) =
            get_card_bin_and_last4_digits_for_masked_card(card_details.card_number.peek())
                .change_context(errors::ApiErrorResponse::InvalidRequestData {
                    message: "Invalid masked card number".to_string(),
                })?;
        if let Some(card_bin_info) = card_info {
            Ok(Self {
                scheme: card_details
                    .card_network
                    .clone()
                    .or(card_bin_info.card_network.clone())
                    .map(|card_network| card_network.to_string()),
                last4_digits: Some(last4_digits.clone()),
                issuer_country: card_details
                    .card_issuing_country
                    .clone()
                    .or(card_bin_info.card_issuing_country),
                card_number: None,
                expiry_month: Some(card_details.card_exp_month.clone()),
                expiry_year: Some(card_details.card_exp_year.clone()),
                card_token: None,
                card_fingerprint: None,
                card_holder_name: card_details.card_holder_name.clone(),
                nick_name: card_details.nick_name.clone(),
                card_isin: Some(card_isin.clone()),
                card_issuer: card_details
                    .card_issuer
                    .clone()
                    .or(card_bin_info.card_issuer),
                card_network: card_details
                    .card_network
                    .clone()
                    .or(card_bin_info.card_network),
                card_type: card_details.card_type.clone().or(card_bin_info.card_type),
                saved_to_locker: false,
            })
        } else {
            Ok(Self {
                scheme: card_details
                    .card_network
                    .clone()
                    .map(|card_network| card_network.to_string()),
                last4_digits: Some(last4_digits.clone()),
                issuer_country: card_details.card_issuing_country.clone(),
                card_number: None,
                expiry_month: Some(card_details.card_exp_month.clone()),
                expiry_year: Some(card_details.card_exp_year.clone()),
                card_token: None,
                card_fingerprint: None,
                card_holder_name: card_details.card_holder_name.clone(),
                nick_name: card_details.nick_name.clone(),
                card_isin: Some(card_isin.clone()),
                card_issuer: card_details.card_issuer.clone(),
                card_network: card_details.card_network.clone(),
                card_type: card_details.card_type.clone(),
                saved_to_locker: false,
            })
        }
    }
}

#[cfg(feature = "v2")]
impl
    ForeignTryFrom<(
        &api_models::payment_methods::MigrateCardDetail,
        Option<cards_info::CardInfo>,
    )> for pm_api::CardDetailFromLocker
{
    type Error = error_stack::Report<errors::ApiErrorResponse>;
    fn foreign_try_from(
        (card_details, card_info): (
            &api_models::payment_methods::MigrateCardDetail,
            Option<cards_info::CardInfo>,
        ),
    ) -> Result<Self, Self::Error> {
        let (card_isin, last4_digits) =
            get_card_bin_and_last4_digits_for_masked_card(card_details.card_number.peek())
                .change_context(errors::ApiErrorResponse::InvalidRequestData {
                    message: "Invalid masked card number".to_string(),
                })?;
        if let Some(card_bin_info) = card_info {
            Ok(Self {
                last4_digits: Some(last4_digits.clone()),
                issuer_country: card_details
                    .card_issuing_country
                    .as_ref()
                    .map(|c| api_enums::CountryAlpha2::from_str(c))
                    .transpose()
                    .ok()
                    .flatten()
                    .or(card_bin_info
                        .card_issuing_country
                        .as_ref()
                        .map(|c| api_enums::CountryAlpha2::from_str(c))
                        .transpose()
                        .ok()
                        .flatten()),
                card_number: None,
                expiry_month: Some(card_details.card_exp_month.clone()),
                expiry_year: Some(card_details.card_exp_year.clone()),
                card_fingerprint: None,
                card_holder_name: card_details.card_holder_name.clone(),
                nick_name: card_details.nick_name.clone(),
                card_isin: Some(card_isin.clone()),
                card_issuer: card_details
                    .card_issuer
                    .clone()
                    .or(card_bin_info.card_issuer),
                card_network: card_details
                    .card_network
                    .clone()
                    .or(card_bin_info.card_network),
                card_type: card_details.card_type.clone().or(card_bin_info.card_type),
                saved_to_locker: false,
            })
        } else {
            Ok(Self {
                last4_digits: Some(last4_digits.clone()),
                issuer_country: card_details
                    .card_issuing_country
                    .as_ref()
                    .map(|c| api_enums::CountryAlpha2::from_str(c))
                    .transpose()
                    .ok()
                    .flatten(),
                card_number: None,
                expiry_month: Some(card_details.card_exp_month.clone()),
                expiry_year: Some(card_details.card_exp_year.clone()),
                card_fingerprint: None,
                card_holder_name: card_details.card_holder_name.clone(),
                nick_name: card_details.nick_name.clone(),
                card_isin: Some(card_isin.clone()),
                card_issuer: card_details.card_issuer.clone(),
                card_network: card_details.card_network.clone(),
                card_type: card_details.card_type.clone(),
                saved_to_locker: false,
            })
        }
    }
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn get_client_secret_or_add_payment_method_for_migration(
    state: &state::PaymentMethodsState,
    req: pm_api::PaymentMethodCreate,
    merchant_context: &merchant_context::MerchantContext,
    migration_status: &mut migration::RecordMigrationStatusBuilder,
    controller: &dyn PaymentMethodsController,
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodResponse>, errors::ApiErrorResponse> {
    let merchant_id = merchant_context.get_merchant_account().get_id();
    let customer_id = req.customer_id.clone().get_required_value("customer_id")?;

    #[cfg(not(feature = "payouts"))]
    let condition = req.card.is_some();
    #[cfg(feature = "payouts")]
    let condition = req.card.is_some() || req.bank_transfer.is_some() || req.wallet.is_some();
    let key_manager_state = &state.into();

    let payment_method_billing_address: Option<Encryptable<Secret<serde_json::Value>>> = req
        .billing
        .clone()
        .async_map(|billing| {
            create_encrypted_data(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                billing,
            )
        })
        .await
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encrypt Payment method billing address")?;

    let connector_mandate_details = req
        .connector_mandate_details
        .clone()
        .map(serde_json::to_value)
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    if condition {
        Box::pin(save_migration_payment_method(
            req,
            migration_status,
            controller,
        ))
        .await
    } else {
        let payment_method_id = generate_id(consts::ID_LENGTH, "pm");

        let res = controller
            .create_payment_method(
                &req,
                &customer_id,
                payment_method_id.as_str(),
                None,
                merchant_id,
                None,
                None,
                None,
                connector_mandate_details.clone(),
                Some(enums::PaymentMethodStatus::AwaitingData),
                None,
                payment_method_billing_address,
                None,
                None,
                None,
                None,
            )
            .await?;
        migration_status.connector_mandate_details_migrated(
            connector_mandate_details
                .clone()
                .and_then(|val| (val != json!({})).then_some(true))
                .or_else(|| {
                    req.connector_mandate_details
                        .clone()
                        .and_then(|val| (!val.0.is_empty()).then_some(false))
                }),
        );
        //card is not migrated in this case
        migration_status.card_migrated(false);

        if res.status == enums::PaymentMethodStatus::AwaitingData {
            controller
                .add_payment_method_status_update_task(
                    &res,
                    enums::PaymentMethodStatus::AwaitingData,
                    enums::PaymentMethodStatus::Inactive,
                    merchant_id,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "Failed to add payment method status update task in process tracker",
                )?;
        }

        Ok(ApplicationResponse::Json(
            pm_api::PaymentMethodResponse::foreign_from((None, res)),
        ))
    }
}
#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
pub async fn skip_locker_call_and_migrate_payment_method(
    state: &state::PaymentMethodsState,
    req: &pm_api::PaymentMethodMigrate,
    merchant_id: id_type::MerchantId,
    merchant_context: &merchant_context::MerchantContext,
    card: pm_api::CardDetailFromLocker,
    should_require_connector_mandate_details: bool,
    migration_status: &mut migration::RecordMigrationStatusBuilder,
    controller: &dyn PaymentMethodsController,
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodResponse>, errors::ApiErrorResponse> {
    let db = &*state.store;
    let customer_id = req.customer_id.clone().get_required_value("customer_id")?;

    // In this case, since we do not have valid card details, recurring payments can only be done through connector mandate details.
    //if network token data is present, then connector mandate details are not mandatory

    let connector_mandate_details = if should_require_connector_mandate_details {
        let connector_mandate_details_req = req
            .connector_mandate_details
            .clone()
            .and_then(|c| c.payments)
            .clone()
            .get_required_value("connector mandate details")?;

        Some(
            serde_json::to_value(&connector_mandate_details_req)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to parse connector mandate details")?,
        )
    } else {
        req.connector_mandate_details
            .clone()
            .and_then(|c| c.payments)
            .map(|mandate_details_req| {
                serde_json::to_value(&mandate_details_req)
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed to parse connector mandate details")
            })
            .transpose()?
    };
    let key_manager_state = &state.into();
    let payment_method_billing_address: Option<Encryptable<Secret<serde_json::Value>>> = req
        .billing
        .clone()
        .async_map(|billing| {
            create_encrypted_data(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                billing,
            )
        })
        .await
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encrypt Payment method billing address")?;

    let customer = db
        .find_customer_by_customer_id_merchant_id(
            &state.into(),
            &customer_id,
            &merchant_id,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;

    let payment_method_card_details = pm_api::PaymentMethodsData::Card(
        pm_api::CardDetailsPaymentMethod::from((card.clone(), None)),
    );

    let payment_method_data_encrypted: Option<Encryptable<Secret<serde_json::Value>>> = Some(
        create_encrypted_data(
            &state.into(),
            merchant_context.get_merchant_key_store(),
            payment_method_card_details,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encrypt Payment method card details")?,
    );

    let payment_method_metadata: Option<serde_json::Value> =
        req.metadata.as_ref().map(|data| data.peek()).cloned();

    let network_transaction_id = req.network_transaction_id.clone();

    let payment_method_id = generate_id(consts::ID_LENGTH, "pm");

    let current_time = common_utils::date_time::now();

    let response = db
        .insert_payment_method(
            &state.into(),
            merchant_context.get_merchant_key_store(),
            domain_pm::PaymentMethod {
                customer_id: customer_id.to_owned(),
                merchant_id: merchant_id.to_owned(),
                payment_method_id: payment_method_id.to_string(),
                locker_id: None,
                payment_method: req.payment_method,
                payment_method_type: req.payment_method_type,
                payment_method_issuer: req.payment_method_issuer.clone(),
                scheme: req.card_network.clone().or(card.scheme.clone()),
                metadata: payment_method_metadata.map(Secret::new),
                payment_method_data: payment_method_data_encrypted,
                connector_mandate_details: connector_mandate_details.clone(),
                customer_acceptance: None,
                client_secret: None,
                status: enums::PaymentMethodStatus::Active,
                network_transaction_id: network_transaction_id.clone(),
                payment_method_issuer_code: None,
                accepted_currency: None,
                token: None,
                cardholder_name: None,
                issuer_name: None,
                issuer_country: None,
                payer_country: None,
                is_stored: None,
                swift_code: None,
                direct_debit_token: None,
                created_at: current_time,
                last_modified: current_time,
                last_used_at: current_time,
                payment_method_billing_address,
                updated_by: None,
                version: common_types::consts::API_VERSION,
                network_token_requestor_reference_id: None,
                network_token_locker_id: None,
                network_token_payment_method_data: None,
            },
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to add payment method in db")?;

    logger::debug!("Payment method inserted in db");

    migration_status.network_transaction_id_migrated(
        network_transaction_id.and_then(|val| (!val.is_empty_after_trim()).then_some(true)),
    );

    migration_status.connector_mandate_details_migrated(
        connector_mandate_details
            .clone()
            .and_then(|val| if val == json!({}) { None } else { Some(true) })
            .or_else(|| {
                req.connector_mandate_details.clone().and_then(|val| {
                    val.payments
                        .and_then(|payin_val| (!payin_val.0.is_empty()).then_some(false))
                })
            }),
    );

    if customer.default_payment_method_id.is_none() && req.payment_method.is_some() {
        let _ = controller
            .set_default_payment_method(&merchant_id, &customer_id, payment_method_id.to_owned())
            .await
            .map_err(|error| logger::error!(?error, "Failed to set the payment method as default"));
    }
    Ok(ApplicationResponse::Json(
        pm_api::PaymentMethodResponse::foreign_from((Some(card), response)),
    ))
}

// need to discuss regarding the migration APIs for v2
#[cfg(feature = "v2")]
pub async fn skip_locker_call_and_migrate_payment_method(
    _state: state::PaymentMethodsState,
    _req: &pm_api::PaymentMethodMigrate,
    _merchant_id: id_type::MerchantId,
    _merchant_context: &merchant_context::MerchantContext,
    _card: pm_api::CardDetailFromLocker,
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodResponse>, errors::ApiErrorResponse> {
    todo!()
}
pub fn get_card_bin_and_last4_digits_for_masked_card(
    masked_card_number: &str,
) -> Result<(String, String), cards::CardNumberValidationErr> {
    let last4_digits = masked_card_number
        .chars()
        .rev()
        .take(4)
        .collect::<String>()
        .chars()
        .rev()
        .collect::<String>();

    let card_isin = masked_card_number.chars().take(6).collect::<String>();

    cards::validate::validate_card_number_chars(&card_isin)
        .and_then(|_| cards::validate::validate_card_number_chars(&last4_digits))?;

    Ok((card_isin, last4_digits))
}
#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn save_migration_payment_method(
    req: pm_api::PaymentMethodCreate,
    migration_status: &mut migration::RecordMigrationStatusBuilder,
    controller: &dyn PaymentMethodsController,
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodResponse>, errors::ApiErrorResponse> {
    let connector_mandate_details = req
        .connector_mandate_details
        .clone()
        .map(serde_json::to_value)
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    let network_transaction_id = req.network_transaction_id.clone();

    let res = controller.add_payment_method(&req).await?;

    migration_status.card_migrated(true);
    migration_status.network_transaction_id_migrated(
        network_transaction_id.and_then(|val| (!val.is_empty_after_trim()).then_some(true)),
    );

    migration_status.connector_mandate_details_migrated(
        connector_mandate_details
            .and_then(|val| if val == json!({}) { None } else { Some(true) })
            .or_else(|| {
                req.connector_mandate_details
                    .and_then(|val| (!val.0.is_empty()).then_some(false))
            }),
    );
    Ok(res)
}


pub fn get_id(&self) -> &String {
        &self.payment_method_id
    }


pub fn get_transaction_type(&self) -> api_enums::TransactionType {
        match self.connector_type {
            #[cfg(feature = "payouts")]
            api_enums::ConnectorType::PayoutProcessor => api_enums::TransactionType::Payout,
            _ => api_enums::TransactionType::Payment,
        }
    }


pub struct Keys<K> {
    inner: ops::Range<usize>,
    _marker: PhantomData<K>,
}


pub struct MerchantDetails {
    merchant_id: Secret<String>,
    terminal_id: Option<Secret<String>>,
}


pub trait DisputeMetricAnalytics: LoadRow<DisputeMetricRow> {}


File: crates/payment_methods/src/helpers.rs
 Public functions: 3

use api_models::{enums as api_enums, payment_methods as api};
#[cfg(feature = "v1")]
use common_utils::ext_traits::AsyncExt;
pub use hyperswitch_domain_models::{errors::api_error_response, payment_methods as domain};
#[cfg(feature = "v1")]
use router_env::logger;

use crate::state;
#[cfg(feature = "v1")]
pub async fn populate_bin_details_for_payment_method_create(
    card_details: api_models::payment_methods::CardDetail,
    db: Box<dyn state::PaymentMethodsStorageInterface>,
) -> api_models::payment_methods::CardDetail {
    let card_isin: Option<_> = Some(card_details.card_number.get_card_isin());
    if card_details.card_issuer.is_some()
        && card_details.card_network.is_some()
        && card_details.card_type.is_some()
        && card_details.card_issuing_country.is_some()
    {
        api::CardDetail {
            card_issuer: card_details.card_issuer.to_owned(),
            card_network: card_details.card_network.clone(),
            card_type: card_details.card_type.to_owned(),
            card_issuing_country: card_details.card_issuing_country.to_owned(),
            card_exp_month: card_details.card_exp_month.clone(),
            card_exp_year: card_details.card_exp_year.clone(),
            card_holder_name: card_details.card_holder_name.clone(),
            card_number: card_details.card_number.clone(),
            nick_name: card_details.nick_name.clone(),
        }
    } else {
        let card_info = card_isin
            .clone()
            .async_and_then(|card_isin| async move {
                db.get_card_info(&card_isin)
                    .await
                    .map_err(|error| logger::error!(card_info_error=?error))
                    .ok()
            })
            .await
            .flatten()
            .map(|card_info| api::CardDetail {
                card_issuer: card_info.card_issuer,
                card_network: card_info.card_network.clone(),
                card_type: card_info.card_type,
                card_issuing_country: card_info.card_issuing_country,
                card_exp_month: card_details.card_exp_month.clone(),
                card_exp_year: card_details.card_exp_year.clone(),
                card_holder_name: card_details.card_holder_name.clone(),
                card_number: card_details.card_number.clone(),
                nick_name: card_details.nick_name.clone(),
            });
        card_info.unwrap_or_else(|| api::CardDetail {
            card_issuer: None,
            card_network: None,
            card_type: None,
            card_issuing_country: None,
            card_exp_month: card_details.card_exp_month.clone(),
            card_exp_year: card_details.card_exp_year.clone(),
            card_holder_name: card_details.card_holder_name.clone(),
            card_number: card_details.card_number.clone(),
            nick_name: card_details.nick_name.clone(),
        })
    }
}

#[cfg(feature = "v2")]
pub async fn populate_bin_details_for_payment_method_create(
    _card_details: api_models::payment_methods::CardDetail,
    _db: &dyn state::PaymentMethodsStorageInterface,
) -> api_models::payment_methods::CardDetail {
    todo!()
}

pub fn validate_payment_method_type_against_payment_method(
    payment_method: api_enums::PaymentMethod,
    payment_method_type: api_enums::PaymentMethodType,
) -> bool {
    match payment_method {
        #[cfg(feature = "v1")]
        api_enums::PaymentMethod::Card => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::Credit | api_enums::PaymentMethodType::Debit
        ),
        #[cfg(feature = "v2")]
        api_enums::PaymentMethod::Card => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::Credit
                | api_enums::PaymentMethodType::Debit
                | api_enums::PaymentMethodType::Card
        ),
        api_enums::PaymentMethod::PayLater => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::Affirm
                | api_enums::PaymentMethodType::Alma
                | api_enums::PaymentMethodType::AfterpayClearpay
                | api_enums::PaymentMethodType::Klarna
                | api_enums::PaymentMethodType::PayBright
                | api_enums::PaymentMethodType::Atome
                | api_enums::PaymentMethodType::Walley
                | api_enums::PaymentMethodType::Breadpay
                | api_enums::PaymentMethodType::Flexiti
        ),
        api_enums::PaymentMethod::Wallet => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::AmazonPay
                | api_enums::PaymentMethodType::Bluecode
                | api_enums::PaymentMethodType::Paysera
                | api_enums::PaymentMethodType::Skrill
                | api_enums::PaymentMethodType::ApplePay
                | api_enums::PaymentMethodType::GooglePay
                | api_enums::PaymentMethodType::Paypal
                | api_enums::PaymentMethodType::AliPay
                | api_enums::PaymentMethodType::AliPayHk
                | api_enums::PaymentMethodType::Dana
                | api_enums::PaymentMethodType::MbWay
                | api_enums::PaymentMethodType::MobilePay
                | api_enums::PaymentMethodType::SamsungPay
                | api_enums::PaymentMethodType::Twint
                | api_enums::PaymentMethodType::Vipps
                | api_enums::PaymentMethodType::TouchNGo
                | api_enums::PaymentMethodType::Swish
                | api_enums::PaymentMethodType::WeChatPay
                | api_enums::PaymentMethodType::GoPay
                | api_enums::PaymentMethodType::Gcash
                | api_enums::PaymentMethodType::Momo
                | api_enums::PaymentMethodType::KakaoPay
                | api_enums::PaymentMethodType::Cashapp
                | api_enums::PaymentMethodType::Mifinity
                | api_enums::PaymentMethodType::Paze
                | api_enums::PaymentMethodType::RevolutPay
        ),
        api_enums::PaymentMethod::BankRedirect => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::Giropay
                | api_enums::PaymentMethodType::Ideal
                | api_enums::PaymentMethodType::Sofort
                | api_enums::PaymentMethodType::Eft
                | api_enums::PaymentMethodType::Eps
                | api_enums::PaymentMethodType::BancontactCard
                | api_enums::PaymentMethodType::Blik
                | api_enums::PaymentMethodType::LocalBankRedirect
                | api_enums::PaymentMethodType::OnlineBankingThailand
                | api_enums::PaymentMethodType::OnlineBankingCzechRepublic
                | api_enums::PaymentMethodType::OnlineBankingFinland
                | api_enums::PaymentMethodType::OnlineBankingFpx
                | api_enums::PaymentMethodType::OnlineBankingPoland
                | api_enums::PaymentMethodType::OnlineBankingSlovakia
                | api_enums::PaymentMethodType::Przelewy24
                | api_enums::PaymentMethodType::Trustly
                | api_enums::PaymentMethodType::Bizum
                | api_enums::PaymentMethodType::Interac
                | api_enums::PaymentMethodType::OpenBankingUk
                | api_enums::PaymentMethodType::OpenBankingPIS
        ),
        api_enums::PaymentMethod::BankTransfer => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::Ach
                | api_enums::PaymentMethodType::SepaBankTransfer
                | api_enums::PaymentMethodType::Bacs
                | api_enums::PaymentMethodType::Multibanco
                | api_enums::PaymentMethodType::Pix
                | api_enums::PaymentMethodType::Pse
                | api_enums::PaymentMethodType::PermataBankTransfer
                | api_enums::PaymentMethodType::BcaBankTransfer
                | api_enums::PaymentMethodType::BniVa
                | api_enums::PaymentMethodType::BriVa
                | api_enums::PaymentMethodType::CimbVa
                | api_enums::PaymentMethodType::DanamonVa
                | api_enums::PaymentMethodType::MandiriVa
                | api_enums::PaymentMethodType::LocalBankTransfer
                | api_enums::PaymentMethodType::InstantBankTransfer
                | api_enums::PaymentMethodType::InstantBankTransferFinland
                | api_enums::PaymentMethodType::InstantBankTransferPoland
                | api_enums::PaymentMethodType::IndonesianBankTransfer
        ),
        api_enums::PaymentMethod::BankDebit => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::Ach
                | api_enums::PaymentMethodType::Sepa
                | api_enums::PaymentMethodType::Bacs
                | api_enums::PaymentMethodType::Becs
        ),
        api_enums::PaymentMethod::Crypto => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::CryptoCurrency
        ),
        api_enums::PaymentMethod::Reward => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::Evoucher | api_enums::PaymentMethodType::ClassicReward
        ),
        api_enums::PaymentMethod::RealTimePayment => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::Fps
                | api_enums::PaymentMethodType::DuitNow
                | api_enums::PaymentMethodType::PromptPay
                | api_enums::PaymentMethodType::VietQr
        ),
        api_enums::PaymentMethod::Upi => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::UpiCollect | api_enums::PaymentMethodType::UpiIntent
        ),
        api_enums::PaymentMethod::Voucher => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::Boleto
                | api_enums::PaymentMethodType::Efecty
                | api_enums::PaymentMethodType::PagoEfectivo
                | api_enums::PaymentMethodType::RedCompra
                | api_enums::PaymentMethodType::RedPagos
                | api_enums::PaymentMethodType::Indomaret
                | api_enums::PaymentMethodType::Alfamart
                | api_enums::PaymentMethodType::Oxxo
                | api_enums::PaymentMethodType::SevenEleven
                | api_enums::PaymentMethodType::Lawson
                | api_enums::PaymentMethodType::MiniStop
                | api_enums::PaymentMethodType::FamilyMart
                | api_enums::PaymentMethodType::Seicomart
                | api_enums::PaymentMethodType::PayEasy
        ),
        api_enums::PaymentMethod::GiftCard => {
            matches!(
                payment_method_type,
                api_enums::PaymentMethodType::Givex | api_enums::PaymentMethodType::PaySafeCard
            )
        }
        api_enums::PaymentMethod::CardRedirect => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::Knet
                | api_enums::PaymentMethodType::Benefit
                | api_enums::PaymentMethodType::MomoAtm
                | api_enums::PaymentMethodType::CardRedirect
        ),
        api_enums::PaymentMethod::OpenBanking => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::OpenBankingPIS
        ),
        api_enums::PaymentMethod::MobilePayment => matches!(
            payment_method_type,
            api_enums::PaymentMethodType::DirectCarrierBilling
        ),
    }
}

pub trait ForeignFrom<F> {
    fn foreign_from(from: F) -> Self;
}

/// Trait for converting from one foreign type to another
pub trait ForeignTryFrom<F>: Sized {
    /// Custom error for conversion failure
    type Error;
    /// Convert from a foreign type to the current type and return an error if the conversion fails
    fn foreign_try_from(from: F) -> Result<Self, Self::Error>;
}

#[cfg(feature = "v1")]
impl ForeignFrom<(Option<api::CardDetailFromLocker>, domain::PaymentMethod)>
    for api::PaymentMethodResponse
{
    fn foreign_from(
        (card_details, item): (Option<api::CardDetailFromLocker>, domain::PaymentMethod),
    ) -> Self {
        Self {
            merchant_id: item.merchant_id.to_owned(),
            customer_id: Some(item.customer_id.to_owned()),
            payment_method_id: item.get_id().clone(),
            payment_method: item.get_payment_method_type(),
            payment_method_type: item.get_payment_method_subtype(),
            card: card_details,
            recurring_enabled: Some(false),
            installment_payment_enabled: Some(false),
            payment_experience: None,
            metadata: item.metadata,
            created: Some(item.created_at),
            #[cfg(feature = "payouts")]
            bank_transfer: None,
            last_used_at: None,
            client_secret: item.client_secret,
        }
    }
}

#[cfg(feature = "v2")]
impl ForeignFrom<(Option<api::CardDetailFromLocker>, domain::PaymentMethod)>
    for api::PaymentMethodResponse
{
    fn foreign_from(
        (_card_details, _item): (Option<api::CardDetailFromLocker>, domain::PaymentMethod),
    ) -> Self {
        todo!()
    }
}

pub trait StorageErrorExt<T, E> {
    #[track_caller]
    fn to_not_found_response(self, not_found_response: E) -> error_stack::Result<T, E>;

    #[track_caller]
    fn to_duplicate_response(self, duplicate_response: E) -> error_stack::Result<T, E>;
}

impl<T> StorageErrorExt<T, api_error_response::ApiErrorResponse>
    for error_stack::Result<T, storage_impl::StorageError>
{
    #[track_caller]
    fn to_not_found_response(
        self,
        not_found_response: api_error_response::ApiErrorResponse,
    ) -> error_stack::Result<T, api_error_response::ApiErrorResponse> {
        self.map_err(|err| {
            let new_err = match err.current_context() {
                storage_impl::StorageError::ValueNotFound(_) => not_found_response,
                storage_impl::StorageError::CustomerRedacted => {
                    api_error_response::ApiErrorResponse::CustomerRedacted
                }
                _ => api_error_response::ApiErrorResponse::InternalServerError,
            };
            err.change_context(new_err)
        })
    }

    #[track_caller]
    fn to_duplicate_response(
        self,
        duplicate_response: api_error_response::ApiErrorResponse,
    ) -> error_stack::Result<T, api_error_response::ApiErrorResponse> {
        self.map_err(|err| {
            let new_err = match err.current_context() {
                storage_impl::StorageError::DuplicateValue { .. } => duplicate_response,
                _ => api_error_response::ApiErrorResponse::InternalServerError,
            };
            err.change_context(new_err)
        })
    }
}


pub async fn create_recipient(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    connector_data: &api::ConnectorData,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    let customer_details = payout_data.customer_details.to_owned();
    let connector_name = connector_data.connector_name.to_string();

    // Create the connector label using {profile_id}_{connector_name}
    let connector_label = format!(
        "{}_{}",
        payout_data.profile_id.get_string_repr(),
        connector_name
    );
    let (should_call_connector, _connector_customer_id) =
        helpers::should_call_payout_connector_create_customer(
            state,
            connector_data,
            &customer_details,
            &connector_label,
        );
    if should_call_connector {
        // 1. Form router data
        let router_data = core_utils::construct_payout_router_data(
            state,
            connector_data,
            merchant_context,
            payout_data,
        )
        .await?;

        // 2. Fetch connector integration details
        let connector_integration: services::BoxedPayoutConnectorIntegrationInterface<
            api::PoRecipient,
            types::PayoutsData,
            types::PayoutsResponseData,
        > = connector_data.connector.get_connector_integration();

        // 3. Call connector service
        let router_resp = services::execute_connector_processing_step(
            state,
            connector_integration,
            &router_data,
            payments::CallConnectorAction::Trigger,
            None,
            None,
        )
        .await
        .to_payout_failed_response()?;

        match router_resp.response {
            Ok(recipient_create_data) => {
                let db = &*state.store;
                if let Some(customer) = customer_details {
                    if let Some(updated_customer) =
                        customers::update_connector_customer_in_customers(
                            &connector_label,
                            Some(&customer),
                            recipient_create_data.connector_payout_id.clone(),
                        )
                        .await
                    {
                        #[cfg(feature = "v1")]
                        {
                            let customer_id = customer.customer_id.to_owned();
                            payout_data.customer_details = Some(
                                db.update_customer_by_customer_id_merchant_id(
                                    &state.into(),
                                    customer_id,
                                    merchant_context.get_merchant_account().get_id().to_owned(),
                                    customer,
                                    updated_customer,
                                    merchant_context.get_merchant_key_store(),
                                    merchant_context.get_merchant_account().storage_scheme,
                                )
                                .await
                                .change_context(errors::ApiErrorResponse::InternalServerError)
                                .attach_printable("Error updating customers in db")?,
                            );
                        }

                        #[cfg(feature = "v2")]
                        {
                            let customer_id = customer.get_id().clone();
                            payout_data.customer_details = Some(
                                db.update_customer_by_global_id(
                                    &state.into(),
                                    &customer_id,
                                    customer,
                                    updated_customer,
                                    key_store,
                                    merchant_account.storage_scheme,
                                )
                                .await
                                .change_context(errors::ApiErrorResponse::InternalServerError)
                                .attach_printable("Error updating customers in db")?,
                            );
                        }
                    }
                }

                // Add next step to ProcessTracker
                if recipient_create_data.should_add_next_step_to_process_tracker {
                    add_external_account_addition_task(
                        &*state.store,
                        payout_data,
                        common_utils::date_time::now().saturating_add(Duration::seconds(consts::STRIPE_ACCOUNT_ONBOARDING_DELAY_IN_SECONDS)),
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed while adding attach_payout_account_workflow workflow to process tracker")?;

                    // Update payout status in DB
                    let status = recipient_create_data
                        .status
                        .unwrap_or(api_enums::PayoutStatus::RequiresVendorAccountCreation);
                    let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                        connector_payout_id: payout_data
                            .payout_attempt
                            .connector_payout_id
                            .to_owned(),
                        status,
                        error_code: None,
                        error_message: None,
                        is_eligible: recipient_create_data.payout_eligible,
                        unified_code: None,
                        unified_message: None,
                    };
                    payout_data.payout_attempt = db
                        .update_payout_attempt(
                            &payout_data.payout_attempt,
                            updated_payout_attempt,
                            &payout_data.payouts,
                            merchant_context.get_merchant_account().storage_scheme,
                        )
                        .await
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Error updating payout_attempt in db")?;
                    payout_data.payouts = db
                        .update_payout(
                            &payout_data.payouts,
                            storage::PayoutsUpdate::StatusUpdate { status },
                            &payout_data.payout_attempt,
                            merchant_context.get_merchant_account().storage_scheme,
                        )
                        .await
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Error updating payouts in db")?;

                    // Helps callee functions skip the execution
                    payout_data.should_terminate = true;
                } else if let Some(status) = recipient_create_data.status {
                    let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                        connector_payout_id: payout_data
                            .payout_attempt
                            .connector_payout_id
                            .to_owned(),
                        status,
                        error_code: None,
                        error_message: None,
                        is_eligible: recipient_create_data.payout_eligible,
                        unified_code: None,
                        unified_message: None,
                    };
                    payout_data.payout_attempt = db
                        .update_payout_attempt(
                            &payout_data.payout_attempt,
                            updated_payout_attempt,
                            &payout_data.payouts,
                            merchant_context.get_merchant_account().storage_scheme,
                        )
                        .await
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Error updating payout_attempt in db")?;
                    payout_data.payouts = db
                        .update_payout(
                            &payout_data.payouts,
                            storage::PayoutsUpdate::StatusUpdate { status },
                            &payout_data.payout_attempt,
                            merchant_context.get_merchant_account().storage_scheme,
                        )
                        .await
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Error updating payouts in db")?;
                }
            }
            Err(err) => Err(errors::ApiErrorResponse::PayoutFailed {
                data: serde_json::to_value(err).ok(),
            })?,
        }
    }
    Ok(())
}