Bug: fix: reset tracking id api for connector onboarding



## Problem
To support the onboarding flow where merchants can signin/signup directly into the connector's website and give us the credentials of the account we had created onboarding apis (action_url and sync) which uses `tracking_id` to track the onboarding progress of a merchant. And the `tracking_id` we are using currently is `connector_id`.
We are facing an issue in the connector update flow where the merchant tries the onboarding flow again to signin/signup to a new account. If they don't signup/signin and fetches the current status, the sync api will automatically give the progress of the previous account they have signed in in the create flow.

## Possible solution
Instead of using the `connector_id` as the `tracking_id`, we will be use `connector_id_timestamp` as the `tracking_id`. We will create a reset tracking id api which will reset the `tracking_id` by changing the timestamp suffix of the current `tracking_id`. This will be called before the merchant tries to update the mca. As the `tracking_id` is changed in create and update flow, we will no longer get the details of the account which is used in the create flow.
diff --git a/crates/api_models/src/connector_onboarding.rs b/crates/api_models/src/connector_onboarding.rs
index 759d3cb97f1..7e8288d9747 100644
--- a/crates/api_models/src/connector_onboarding.rs
+++ b/crates/api_models/src/connector_onboarding.rs
@@ -52,3 +52,9 @@ pub struct PayPalOnboardingDone {
 pub struct PayPalIntegrationDone {
     pub connector_id: String,
 }
+
+#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
+pub struct ResetTrackingIdRequest {
+    pub connector_id: String,
+    pub connector: enums::Connector,
+}
diff --git a/crates/api_models/src/events/connector_onboarding.rs b/crates/api_models/src/events/connector_onboarding.rs
index 998dc384d62..0da89f61da7 100644
--- a/crates/api_models/src/events/connector_onboarding.rs
+++ b/crates/api_models/src/events/connector_onboarding.rs
@@ -2,11 +2,13 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};
 
 use crate::connector_onboarding::{
     ActionUrlRequest, ActionUrlResponse, OnboardingStatus, OnboardingSyncRequest,
+    ResetTrackingIdRequest,
 };
 
 common_utils::impl_misc_api_event_type!(
     ActionUrlRequest,
     ActionUrlResponse,
     OnboardingSyncRequest,
-    OnboardingStatus
+    OnboardingStatus,
+    ResetTrackingIdRequest
 );
diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs
index afe76184630..7b8d6fa0827 100644
--- a/crates/router/src/consts.rs
+++ b/crates/router/src/consts.rs
@@ -74,6 +74,9 @@ pub const VERIFY_CONNECTOR_ID_PREFIX: &str = "conn_verify";
 #[cfg(feature = "olap")]
 pub const VERIFY_CONNECTOR_MERCHANT_ID: &str = "test_merchant";
 
+#[cfg(feature = "olap")]
+pub const CONNECTOR_ONBOARDING_CONFIG_PREFIX: &str = "onboarding";
+
 /// Max payment session expiry
 pub const MAX_SESSION_EXPIRY: u32 = 7890000;
 
diff --git a/crates/router/src/core/connector_onboarding.rs b/crates/router/src/core/connector_onboarding.rs
index e48026edc2d..e6c1fc9d378 100644
--- a/crates/router/src/core/connector_onboarding.rs
+++ b/crates/router/src/core/connector_onboarding.rs
@@ -1,5 +1,4 @@
 use api_models::{connector_onboarding as api, enums};
-use error_stack::ResultExt;
 use masking::Secret;
 
 use crate::{
@@ -19,16 +18,23 @@ pub trait AccessToken {
 
 pub async fn get_action_url(
     state: AppState,
+    user_from_token: auth::UserFromToken,
     request: api::ActionUrlRequest,
 ) -> RouterResponse<api::ActionUrlResponse> {
+    utils::check_if_connector_exists(&state, &request.connector_id, &user_from_token.merchant_id)
+        .await?;
+
     let connector_onboarding_conf = state.conf.connector_onboarding.clone();
     let is_enabled = utils::is_enabled(request.connector, &connector_onboarding_conf);
+    let tracking_id =
+        utils::get_tracking_id_from_configs(&state, &request.connector_id, request.connector)
+            .await?;
 
     match (is_enabled, request.connector) {
         (Some(true), enums::Connector::Paypal) => {
             let action_url = Box::pin(paypal::get_action_url_from_paypal(
                 state,
-                request.connector_id,
+                tracking_id,
                 request.return_url,
             ))
             .await?;
@@ -49,40 +55,42 @@ pub async fn sync_onboarding_status(
     user_from_token: auth::UserFromToken,
     request: api::OnboardingSyncRequest,
 ) -> RouterResponse<api::OnboardingStatus> {
-    let merchant_account = user_from_token
-        .get_merchant_account(state.clone())
-        .await
-        .change_context(ApiErrorResponse::MerchantAccountNotFound)?;
+    utils::check_if_connector_exists(&state, &request.connector_id, &user_from_token.merchant_id)
+        .await?;
+
     let connector_onboarding_conf = state.conf.connector_onboarding.clone();
     let is_enabled = utils::is_enabled(request.connector, &connector_onboarding_conf);
+    let tracking_id =
+        utils::get_tracking_id_from_configs(&state, &request.connector_id, request.connector)
+            .await?;
 
     match (is_enabled, request.connector) {
         (Some(true), enums::Connector::Paypal) => {
             let status = Box::pin(paypal::sync_merchant_onboarding_status(
                 state.clone(),
-                request.connector_id.clone(),
+                tracking_id,
             ))
             .await?;
             if let api::OnboardingStatus::PayPal(api::PayPalOnboardingStatus::Success(
-                ref inner_data,
+                ref paypal_onboarding_data,
             )) = status
             {
                 let connector_onboarding_conf = state.conf.connector_onboarding.clone();
                 let auth_details = oss_types::ConnectorAuthType::SignatureKey {
                     api_key: connector_onboarding_conf.paypal.client_secret,
                     key1: connector_onboarding_conf.paypal.client_id,
-                    api_secret: Secret::new(inner_data.payer_id.clone()),
+                    api_secret: Secret::new(paypal_onboarding_data.payer_id.clone()),
                 };
-                let some_data = paypal::update_mca(
+                let update_mca_data = paypal::update_mca(
                     &state,
-                    &merchant_account,
+                    user_from_token.merchant_id,
                     request.connector_id.to_owned(),
                     auth_details,
                 )
                 .await?;
 
                 return Ok(ApplicationResponse::Json(api::OnboardingStatus::PayPal(
-                    api::PayPalOnboardingStatus::ConnectorIntegrated(some_data),
+                    api::PayPalOnboardingStatus::ConnectorIntegrated(update_mca_data),
                 )));
             }
             Ok(ApplicationResponse::Json(status))
@@ -94,3 +102,15 @@ pub async fn sync_onboarding_status(
         .into()),
     }
 }
+
+pub async fn reset_tracking_id(
+    state: AppState,
+    user_from_token: auth::UserFromToken,
+    request: api::ResetTrackingIdRequest,
+) -> RouterResponse<()> {
+    utils::check_if_connector_exists(&state, &request.connector_id, &user_from_token.merchant_id)
+        .await?;
+    utils::set_tracking_id_in_configs(&state, &request.connector_id, request.connector).await?;
+
+    Ok(ApplicationResponse::StatusOk)
+}
diff --git a/crates/router/src/core/connector_onboarding/paypal.rs b/crates/router/src/core/connector_onboarding/paypal.rs
index 30aa69067b5..f18681f8cfd 100644
--- a/crates/router/src/core/connector_onboarding/paypal.rs
+++ b/crates/router/src/core/connector_onboarding/paypal.rs
@@ -23,11 +23,11 @@ fn build_referral_url(state: AppState) -> String {
 
 async fn build_referral_request(
     state: AppState,
-    connector_id: String,
+    tracking_id: String,
     return_url: String,
 ) -> RouterResult<Request> {
     let access_token = utils::paypal::generate_access_token(state.clone()).await?;
-    let request_body = types::paypal::PartnerReferralRequest::new(connector_id, return_url);
+    let request_body = types::paypal::PartnerReferralRequest::new(tracking_id, return_url);
 
     utils::paypal::build_paypal_post_request(
         build_referral_url(state),
@@ -38,12 +38,12 @@ async fn build_referral_request(
 
 pub async fn get_action_url_from_paypal(
     state: AppState,
-    connector_id: String,
+    tracking_id: String,
     return_url: String,
 ) -> RouterResult<String> {
     let referral_request = Box::pin(build_referral_request(
         state.clone(),
-        connector_id,
+        tracking_id,
         return_url,
     ))
     .await?;
@@ -137,7 +137,7 @@ async fn find_paypal_merchant_by_tracking_id(
 
 pub async fn update_mca(
     state: &AppState,
-    merchant_account: &oss_types::domain::MerchantAccount,
+    merchant_id: String,
     connector_id: String,
     auth_details: oss_types::ConnectorAuthType,
 ) -> RouterResult<oss_api_types::MerchantConnectorResponse> {
@@ -159,13 +159,9 @@ pub async fn update_mca(
         connector_webhook_details: None,
         pm_auth_config: None,
     };
-    let mca_response = admin::update_payment_connector(
-        state.clone(),
-        &merchant_account.merchant_id,
-        &connector_id,
-        request,
-    )
-    .await?;
+    let mca_response =
+        admin::update_payment_connector(state.clone(), &merchant_id, &connector_id, request)
+            .await?;
 
     match mca_response {
         ApplicationResponse::Json(mca_data) => Ok(mca_data),
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 6625a206be2..03e63553a66 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -942,5 +942,6 @@ impl ConnectorOnboarding {
             .app_data(web::Data::new(state))
             .service(web::resource("/action_url").route(web::post().to(get_action_url)))
             .service(web::resource("/sync").route(web::post().to(sync_onboarding_status)))
+            .service(web::resource("/reset_tracking_id").route(web::post().to(reset_tracking_id)))
     }
 }
diff --git a/crates/router/src/routes/connector_onboarding.rs b/crates/router/src/routes/connector_onboarding.rs
index b7c39b3c1d2..f5555f5bf9b 100644
--- a/crates/router/src/routes/connector_onboarding.rs
+++ b/crates/router/src/routes/connector_onboarding.rs
@@ -20,7 +20,7 @@ pub async fn get_action_url(
         state,
         &http_req,
         req_payload.clone(),
-        |state, _: auth::UserFromToken, req| core::get_action_url(state, req),
+        core::get_action_url,
         &auth::JWTAuth(Permission::MerchantAccountWrite),
         api_locking::LockAction::NotApplicable,
     ))
@@ -45,3 +45,22 @@ pub async fn sync_onboarding_status(
     ))
     .await
 }
+
+pub async fn reset_tracking_id(
+    state: web::Data<AppState>,
+    http_req: HttpRequest,
+    json_payload: web::Json<api_types::ResetTrackingIdRequest>,
+) -> HttpResponse {
+    let flow = Flow::ResetTrackingId;
+    let req_payload = json_payload.into_inner();
+    Box::pin(api::server_wrap(
+        flow.clone(),
+        state,
+        &http_req,
+        req_payload.clone(),
+        core::reset_tracking_id,
+        &auth::JWTAuth(Permission::MerchantAccountWrite),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index 10f408f3d4f..65d65366013 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -178,7 +178,9 @@ impl From<Flow> for ApiIdentifier {
                 Self::UserRole
             }
 
-            Flow::GetActionUrl | Flow::SyncOnboardingStatus => Self::ConnectorOnboarding,
+            Flow::GetActionUrl | Flow::SyncOnboardingStatus | Flow::ResetTrackingId => {
+                Self::ConnectorOnboarding
+            }
         }
     }
 }
diff --git a/crates/router/src/utils/connector_onboarding.rs b/crates/router/src/utils/connector_onboarding.rs
index e8afcd68a46..03735e61cc7 100644
--- a/crates/router/src/utils/connector_onboarding.rs
+++ b/crates/router/src/utils/connector_onboarding.rs
@@ -1,6 +1,11 @@
+use diesel_models::{ConfigNew, ConfigUpdate};
+use error_stack::ResultExt;
+
+use super::errors::StorageErrorExt;
 use crate::{
+    consts,
     core::errors::{api_error_response::NotImplementedMessage, ApiErrorResponse, RouterResult},
-    routes::app::settings,
+    routes::{app::settings, AppState},
     types::{self, api::enums},
 };
 
@@ -34,3 +39,105 @@ pub fn is_enabled(
         _ => None,
     }
 }
+
+pub async fn check_if_connector_exists(
+    state: &AppState,
+    connector_id: &str,
+    merchant_id: &str,
+) -> RouterResult<()> {
+    let key_store = state
+        .store
+        .get_merchant_key_store_by_merchant_id(
+            merchant_id,
+            &state.store.get_master_key().to_vec().into(),
+        )
+        .await
+        .to_not_found_response(ApiErrorResponse::MerchantAccountNotFound)?;
+
+    let _connector = state
+        .store
+        .find_by_merchant_connector_account_merchant_id_merchant_connector_id(
+            merchant_id,
+            connector_id,
+            &key_store,
+        )
+        .await
+        .to_not_found_response(ApiErrorResponse::MerchantConnectorAccountNotFound {
+            id: connector_id.to_string(),
+        })?;
+
+    Ok(())
+}
+
+pub async fn set_tracking_id_in_configs(
+    state: &AppState,
+    connector_id: &str,
+    connector: enums::Connector,
+) -> RouterResult<()> {
+    let timestamp = common_utils::date_time::now_unix_timestamp().to_string();
+    let find_config = state
+        .store
+        .find_config_by_key(&build_key(connector_id, connector))
+        .await;
+
+    if find_config.is_ok() {
+        state
+            .store
+            .update_config_by_key(
+                &build_key(connector_id, connector),
+                ConfigUpdate::Update {
+                    config: Some(timestamp),
+                },
+            )
+            .await
+            .change_context(ApiErrorResponse::InternalServerError)
+            .attach_printable("Error updating data in configs table")?;
+    } else if find_config
+        .as_ref()
+        .map_err(|e| e.current_context().is_db_not_found())
+        .err()
+        .unwrap_or(false)
+    {
+        state
+            .store
+            .insert_config(ConfigNew {
+                key: build_key(connector_id, connector),
+                config: timestamp,
+            })
+            .await
+            .change_context(ApiErrorResponse::InternalServerError)
+            .attach_printable("Error inserting data in configs table")?;
+    } else {
+        find_config.change_context(ApiErrorResponse::InternalServerError)?;
+    }
+
+    Ok(())
+}
+
+pub async fn get_tracking_id_from_configs(
+    state: &AppState,
+    connector_id: &str,
+    connector: enums::Connector,
+) -> RouterResult<String> {
+    let timestamp = state
+        .store
+        .find_config_by_key_unwrap_or(
+            &build_key(connector_id, connector),
+            Some(common_utils::date_time::now_unix_timestamp().to_string()),
+        )
+        .await
+        .change_context(ApiErrorResponse::InternalServerError)
+        .attach_printable("Error getting data from configs table")?
+        .config;
+
+    Ok(format!("{}_{}", connector_id, timestamp))
+}
+
+fn build_key(connector_id: &str, connector: enums::Connector) -> String {
+    format!(
+        "{}_{}_{}",
+        consts::CONNECTOR_ONBOARDING_CONFIG_PREFIX,
+        connector,
+        connector_id,
+    )
+}
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index e37e15443bd..110402444c5 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -311,6 +311,8 @@ pub enum Flow {
     GetActionUrl,
     /// Sync connector onboarding status
     SyncOnboardingStatus,
+    /// Reset tracking id
+    ResetTrackingId,
     /// Verify email Token
     VerifyEmail,
     /// Send verify email

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [x] Bugfix
- [ ] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
1. This PR adds checks in connector_onboarding to only proceed if the connector exists for the given merchant_account.
2. Reset tracking id api has been added to connector onboarding, which will reset the tracking details of the onboarded merchant. This can be used to update the process without any interference of previous account.

### Additional Changes

- [x] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
3. `crates/router/src/configs`
4. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
1. To catch any errors without the connector id check beforehand.
2. To make the update flow of connector onboarding better.

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
Postman.
```
curl --location 'http://localhost:8080/connector_onboarding/action_url' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer JWT' \
--data '{
    "connector": "paypal",
    "connector_id": "",
    "return_url": "https://google.com"
}'
```
```
curl --location 'http://localhost:8080/connector_onboarding/sync' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer JWT' \
--data '{
    "connector": "paypal",
    "profile_id": "",
    "connector_id": ""
}'
```
If the connector_id is invalid, these apis will throw this error
```
{
    "error": {
        "type": "invalid_request",
        "message": "Merchant connector account does not exist in our records",
        "code": "HE_02",
        "reason": " does not exist"
    }
}
```

This api will reset the progress of the onboarding flow. This api will give 200 OK if everything works correctly.
```
curl --location 'http://localhost:8080/connector_onboarding/reset_tracking_id' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer JWT' \
--data '{
    "connector_id": "",
    "connector": "paypal"
}'
```

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
