Bug: [FEATURE] add functionality to enable payment link form button only when form is filled



### Feature Description

Payment link renders a submit button for submitting the payment form data. This button is always enabled, and displays any errors in case an incomplete form was tried to submit.

This feature takes care of enabling the button only when the form is complete.

### Possible Implementation

Since payment link already has a defined behavior today - to keep the button enabled, but display validation errors in case an incomplete form was submitted, this new feature can be added behind a payment link config flag.

`enable_button_only_on_form_ready` - default will be false

This will control the behavior of enabling / disabling for incomplete forms.

### Have you spent some time checking if this feature request has been raised before?

- [x] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [x] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json
index d93208fb665..1ffd5a880b5 100644
--- a/api-reference-v2/openapi_spec.json
+++ b/api-reference-v2/openapi_spec.json
@@ -11583,6 +11583,14 @@
             "type": "object",
             "description": "Metadata is useful for storing additional, unstructured information about the merchant account.",
             "nullable": true
+          },
+          "product_type": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/api_enums.MerchantProductType"
+              }
+            ],
+            "nullable": true
           }
         },
         "additionalProperties": false
@@ -11717,6 +11725,14 @@
           },
           "recon_status": {
             "$ref": "#/components/schemas/ReconStatus"
+          },
+          "product_type": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/api_enums.MerchantProductType"
+              }
+            ],
+            "nullable": true
           }
         }
       },
@@ -14036,7 +14052,8 @@
           "display_sdk_only",
           "enabled_saved_payment_method",
           "hide_card_nickname_field",
-          "show_card_form_by_default"
+          "show_card_form_by_default",
+          "enable_button_only_on_form_ready"
         ],
         "properties": {
           "theme": {
@@ -14160,6 +14177,10 @@
               }
             },
             "nullable": true
+          },
+          "enable_button_only_on_form_ready": {
+            "type": "boolean",
+            "description": "Flag to enable the button only when the payment form is ready for submission"
           }
         }
       },
@@ -14297,6 +14318,11 @@
               }
             },
             "nullable": true
+          },
+          "enable_button_only_on_form_ready": {
+            "type": "boolean",
+            "description": "Flag to enable the button only when the payment form is ready for submission",
+            "nullable": true
           }
         }
       },
diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs
index 61bb7c009e3..b6f1e0a8b96 100644
--- a/crates/api_models/src/admin.rs
+++ b/crates/api_models/src/admin.rs
@@ -2839,6 +2839,8 @@ pub struct PaymentLinkConfigRequest {
     pub sdk_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
     /// Payment link configuration rules
     pub payment_link_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
+    /// Flag to enable the button only when the payment form is ready for submission
+    pub enable_button_only_on_form_ready: Option<bool>,
 }
 
 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
@@ -2924,6 +2926,8 @@ pub struct PaymentLinkConfig {
     pub sdk_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
     /// Payment link configuration rules
     pub payment_link_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
+    /// Flag to enable the button only when the payment form is ready for submission
+    pub enable_button_only_on_form_ready: bool,
 }
 
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs
index f9540fd08fc..8f5e5fc8b48 100644
--- a/crates/api_models/src/payments.rs
+++ b/crates/api_models/src/payments.rs
@@ -7883,6 +7883,7 @@ pub struct PaymentLinkDetails {
     pub background_colour: Option<String>,
     pub sdk_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
     pub payment_link_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
+    pub enable_button_only_on_form_ready: bool,
 }
 
 #[derive(Debug, serde::Serialize, Clone)]
@@ -7900,6 +7901,7 @@ pub struct SecurePaymentLinkDetails {
     pub background_colour: Option<String>,
     pub sdk_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
     pub payment_link_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
+    pub enable_button_only_on_form_ready: bool,
 }
 
 #[derive(Debug, serde::Serialize)]
diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs
index 9d47ed7b594..384f7c7df24 100644
--- a/crates/diesel_models/src/business_profile.rs
+++ b/crates/diesel_models/src/business_profile.rs
@@ -653,6 +653,7 @@ pub struct PaymentLinkConfigRequest {
     pub background_colour: Option<String>,
     pub sdk_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
     pub payment_link_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
+    pub enable_button_only_on_form_ready: Option<bool>,
 }
 
 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq)]
diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs
index e51d8daa791..4f2f4c49ea1 100644
--- a/crates/diesel_models/src/payment_intent.rs
+++ b/crates/diesel_models/src/payment_intent.rs
@@ -193,6 +193,8 @@ pub struct PaymentLinkConfigRequestForPayments {
     /// Payment link configuration rules
     pub payment_link_ui_rules:
         Option<std::collections::HashMap<String, std::collections::HashMap<String, String>>>,
+    /// Flag to enable the button only when the payment form is ready for submission
+    pub enable_button_only_on_form_ready: Option<bool>,
 }
 
 common_utils::impl_to_sql_from_sql_json!(PaymentLinkConfigRequestForPayments);
diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs
index fac251d8b68..65372d9f053 100644
--- a/crates/hyperswitch_domain_models/src/lib.rs
+++ b/crates/hyperswitch_domain_models/src/lib.rs
@@ -417,6 +417,7 @@ impl ApiModelToDieselModelConvertor<api_models::admin::PaymentLinkConfigRequest>
             payment_button_text_colour: item.payment_button_text_colour,
             sdk_ui_rules: item.sdk_ui_rules,
             payment_link_ui_rules: item.payment_link_ui_rules,
+            enable_button_only_on_form_ready: item.enable_button_only_on_form_ready,
         }
     }
     fn convert_back(self) -> api_models::admin::PaymentLinkConfigRequest {
@@ -440,6 +441,7 @@ impl ApiModelToDieselModelConvertor<api_models::admin::PaymentLinkConfigRequest>
             payment_button_text_colour,
             sdk_ui_rules,
             payment_link_ui_rules,
+            enable_button_only_on_form_ready,
         } = self;
         api_models::admin::PaymentLinkConfigRequest {
             theme,
@@ -467,6 +469,7 @@ impl ApiModelToDieselModelConvertor<api_models::admin::PaymentLinkConfigRequest>
             payment_button_text_colour,
             sdk_ui_rules,
             payment_link_ui_rules,
+            enable_button_only_on_form_ready,
         }
     }
 }
diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs
index 57ec34227b0..247693864da 100644
--- a/crates/router/src/consts.rs
+++ b/crates/router/src/consts.rs
@@ -158,6 +158,9 @@ pub const DEFAULT_DISPLAY_SDK_ONLY: bool = false;
 /// Default bool to enable saved payment method
 pub const DEFAULT_ENABLE_SAVED_PAYMENT_METHOD: bool = false;
 
+/// [PaymentLink] Default bool for enabling button only when form is ready
+pub const DEFAULT_ENABLE_BUTTON_ONLY_ON_FORM_READY: bool = false;
+
 /// Default Merchant Logo Link
 pub const DEFAULT_MERCHANT_LOGO: &str =
     "https://live.hyperswitch.io/payment-link-assets/Merchant_placeholder.png";
diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs
index 292f582df9a..c13e087947b 100644
--- a/crates/router/src/core/payment_link.rs
+++ b/crates/router/src/core/payment_link.rs
@@ -23,8 +23,9 @@ use super::{
 use crate::{
     consts::{
         self, DEFAULT_ALLOWED_DOMAINS, DEFAULT_BACKGROUND_COLOR, DEFAULT_DISPLAY_SDK_ONLY,
-        DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, DEFAULT_HIDE_CARD_NICKNAME_FIELD,
-        DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, DEFAULT_SHOW_CARD_FORM,
+        DEFAULT_ENABLE_BUTTON_ONLY_ON_FORM_READY, DEFAULT_ENABLE_SAVED_PAYMENT_METHOD,
+        DEFAULT_HIDE_CARD_NICKNAME_FIELD, DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG,
+        DEFAULT_SDK_LAYOUT, DEFAULT_SHOW_CARD_FORM,
     },
     errors::RouterResponse,
     get_payment_link_config_value, get_payment_link_config_value_based_on_priority,
@@ -137,6 +138,7 @@ pub async fn form_payment_link_data(
                 payment_button_text_colour: None,
                 sdk_ui_rules: None,
                 payment_link_ui_rules: None,
+                enable_button_only_on_form_ready: DEFAULT_ENABLE_BUTTON_ONLY_ON_FORM_READY,
             }
         };
 
@@ -290,6 +292,7 @@ pub async fn form_payment_link_data(
         payment_button_text_colour: payment_link_config.payment_button_text_colour.clone(),
         sdk_ui_rules: payment_link_config.sdk_ui_rules.clone(),
         payment_link_ui_rules: payment_link_config.payment_link_ui_rules.clone(),
+        enable_button_only_on_form_ready: payment_link_config.enable_button_only_on_form_ready,
     };
 
     Ok((
@@ -348,6 +351,8 @@ pub async fn initiate_secure_payment_link_flow(
                 payment_button_text_colour: payment_link_config.payment_button_text_colour,
                 sdk_ui_rules: payment_link_config.sdk_ui_rules,
                 payment_link_ui_rules: payment_link_config.payment_link_ui_rules,
+                enable_button_only_on_form_ready: payment_link_config
+                    .enable_button_only_on_form_ready,
             };
             let js_script = format!(
                 "window.__PAYMENT_DETAILS = {}",
@@ -634,6 +639,7 @@ pub fn get_payment_link_config_based_on_priority(
         enabled_saved_payment_method,
         hide_card_nickname_field,
         show_card_form_by_default,
+        enable_button_only_on_form_ready,
     ) = get_payment_link_config_value!(
         payment_create_link_config,
         business_theme_configs,
@@ -647,7 +653,11 @@ pub fn get_payment_link_config_based_on_priority(
             DEFAULT_ENABLE_SAVED_PAYMENT_METHOD
         ),
         (hide_card_nickname_field, DEFAULT_HIDE_CARD_NICKNAME_FIELD),
-        (show_card_form_by_default, DEFAULT_SHOW_CARD_FORM)
+        (show_card_form_by_default, DEFAULT_SHOW_CARD_FORM),
+        (
+            enable_button_only_on_form_ready,
+            DEFAULT_ENABLE_BUTTON_ONLY_ON_FORM_READY
+        )
     );
 
     let (
@@ -702,6 +712,7 @@ pub fn get_payment_link_config_based_on_priority(
             payment_button_text_colour,
             sdk_ui_rules,
             payment_link_ui_rules,
+            enable_button_only_on_form_ready,
         };
 
     Ok((payment_link_config, domain_name))
@@ -812,6 +823,7 @@ pub async fn get_payment_link_status(
             payment_button_text_colour: None,
             sdk_ui_rules: None,
             payment_link_ui_rules: None,
+            enable_button_only_on_form_ready: DEFAULT_ENABLE_BUTTON_ONLY_ON_FORM_READY,
         }
     };
 
diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css
index 7b347306809..f58617261d6 100644
--- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css
+++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css
@@ -652,6 +652,10 @@ body {
   cursor: not-allowed;
 }
 
+#submit.not-ready {
+  background-color: #C2C2C2 !important;
+}
+
 #submit-spinner {
   width: 28px;
   height: 28px;
diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html
index 9abcc440068..ac04479e44c 100644
--- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html
+++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html
@@ -290,9 +290,9 @@
               <div></div>
             </div>
           </div>
-          <form id="payment-form" onclick="handleSubmit(); return false;">
+          <form id="payment-form">
             <div id="unified-checkout"></div>
-            <button id="submit" class="hidden">
+            <button type="submit" id="submit" class="hidden">
               <span id="submit-spinner" class="hidden"></span>
               <span id="submit-button-text"></span>
             </button>
diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js
index 5b8267c57a9..e73e23076a9 100644
--- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js
+++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js
@@ -257,7 +257,7 @@ function boot() {
 
   // Update payment link styles
   var paymentLinkUiRules = paymentDetails.payment_link_ui_rules;
-  if (paymentLinkUiRules !== null && typeof paymentLinkUiRules === "object" && Object.getPrototypeOf(paymentLinkUiRules) === Object.prototype) {
+  if (isObject(paymentLinkUiRules)) {
     updatePaymentLinkUi(paymentLinkUiRules);
   }
 
@@ -279,7 +279,18 @@ function boot() {
 boot();
 
 /**
- * Use - add event listeners for changing UI on screen resize
+ * Use - checks if a given value is an object
+ * @param {any} val 
+ * @returns {boolean}
+ */
+function isObject(val) {
+  return val !== null && typeof val === "object" && Object.getPrototypeOf(val) === Object.prototype
+}
+
+/**
+ * Use - add event listeners for changing UI on
+ *        - Screen resize
+ *        - Form inputs
  * @param {PaymentDetails} paymentDetails
  */
 function initializeEventListeners(paymentDetails) {
@@ -384,17 +395,58 @@ function initializeEventListeners(paymentDetails) {
     // @ts-ignore
     window.state.isMobileView = currentWidth <= 1199;
   });
+
+  var paymentForm = document.getElementById("payment-form");
+  if (paymentForm instanceof HTMLFormElement) {
+    paymentForm.addEventListener("submit", function (event) {
+      event.preventDefault();
+      handleSubmit(event);
+    })
+  }
+
+  if (paymentDetails.enable_button_only_on_form_ready) {
+    handleFormReadyForSubmission();
+  }
+}
+
+function handleFormReadyForSubmission() {
+  window.addEventListener("message", function (event) {
+    // Event listener for updating the button rules
+    if (isObject(event.data) && event.data["isFormReadyForSubmission"] !== null) {
+      let isFormReadyForSubmission = event.data["isFormReadyForSubmission"];
+      var submitButtonNode = document.getElementById("submit");
+      if (submitButtonNode instanceof HTMLButtonElement) {
+        if (isFormReadyForSubmission === false) {
+          submitButtonNode.disabled = true;
+          addClass("#submit", "not-ready");
+          addClass("#submit", "disabled");
+        } else if (isFormReadyForSubmission === true) {
+          submitButtonNode.disabled = false;
+          removeClass("#submit", "not-ready");
+          removeClass("#submit", "disabled");
+        }
+      }
+    }
+  });
 }
 
 /**
  * Trigger - post mounting SDK
  * Use - set relevant classes to elements in the doc for showing SDK
  **/
-function showSDK(display_sdk_only) {
+function showSDK(display_sdk_only, enable_button_only_on_form_ready) {
   if (!display_sdk_only) {
     show("#hyper-checkout-details");
   }
   show("#hyper-checkout-sdk");
+  if (enable_button_only_on_form_ready) {
+    addClass("#submit", "not-ready");
+    addClass("#submit", "disabled");
+    var submitButtonNode = document.getElementById("submit");
+    if (submitButtonNode instanceof HTMLButtonElement) {
+      submitButtonNode.disabled = true;
+    }
+  }
   show("#submit");
   show("#unified-checkout");
   hide("#sdk-spinner");
@@ -426,10 +478,11 @@ function handleSubmit(e) {
   // Update button loader
   hide("#submit-button-text");
   show("#submit-spinner");
+  addClass("#submit", "processing");
+  addClass("#submit", "disabled");
   var submitButtonNode = document.getElementById("submit");
   if (submitButtonNode instanceof HTMLButtonElement) {
     submitButtonNode.disabled = true;
-    submitButtonNode.classList.add("disabled");
   }
 
   hyper
@@ -472,11 +525,12 @@ function handleSubmit(e) {
       console.error("Error confirming payment_intent", error);
     })
     .finally(() => {
+      removeClass("#submit", "processing");
       hide("#submit-spinner");
       show("#submit-button-text");
+      removeClass("#submit", "disabled");
       if (submitButtonNode instanceof HTMLButtonElement) {
         submitButtonNode.disabled = false;
-        submitButtonNode.classList.remove("disabled");
       }
     });
 }
diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js
index ca6a944baaa..f9b0fe892cf 100644
--- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js
+++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js
@@ -75,7 +75,7 @@ function initializeSDK() {
   // @ts-ignore
   mountUnifiedCheckout("#unified-checkout");
   // @ts-ignore
-  showSDK(paymentDetails.display_sdk_only);
+  showSDK(paymentDetails.display_sdk_only, paymentDetails.enable_button_only_on_form_ready);
 
   let shimmer = document.getElementById("payment-details-shimmer");
   shimmer.classList.add("reduce-opacity");
diff --git a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js
index 9b5a144d29a..12ae76a2085 100644
--- a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js
+++ b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js
@@ -98,7 +98,7 @@ if (!isFramed) {
     // @ts-ignore
     mountUnifiedCheckout("#unified-checkout");
     // @ts-ignore
-    showSDK(paymentDetails.display_sdk_only);
+    showSDK(paymentDetails.display_sdk_only, paymentDetails.enable_button_only_on_form_ready);
 
     let shimmer = document.getElementById("payment-details-shimmer");
     shimmer.classList.add("reduce-opacity");
diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs
index a34c137dbda..d6bb8f9bf0c 100644
--- a/crates/router/src/core/payments/transformers.rs
+++ b/crates/router/src/core/payments/transformers.rs
@@ -4492,6 +4492,7 @@ impl ForeignFrom<api_models::admin::PaymentLinkConfigRequest>
             payment_button_text_colour: config.payment_button_text_colour,
             sdk_ui_rules: config.sdk_ui_rules,
             payment_link_ui_rules: config.payment_link_ui_rules,
+            enable_button_only_on_form_ready: config.enable_button_only_on_form_ready,
         }
     }
 }
@@ -4562,6 +4563,7 @@ impl ForeignFrom<diesel_models::PaymentLinkConfigRequestForPayments>
             payment_button_text_colour: config.payment_button_text_colour,
             sdk_ui_rules: config.sdk_ui_rules,
             payment_link_ui_rules: config.payment_link_ui_rules,
+            enable_button_only_on_form_ready: config.enable_button_only_on_form_ready,
         }
     }
 }
diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs
index 5baf491cb33..f2f48a26f19 100644
--- a/crates/router/src/types/transformers.rs
+++ b/crates/router/src/types/transformers.rs
@@ -2174,6 +2174,7 @@ impl ForeignFrom<api_models::admin::PaymentLinkConfigRequest>
             payment_button_text_colour: item.payment_button_text_colour,
             sdk_ui_rules: item.sdk_ui_rules,
             payment_link_ui_rules: item.payment_link_ui_rules,
+            enable_button_only_on_form_ready: item.enable_button_only_on_form_ready,
         }
     }
 }
@@ -2204,6 +2205,7 @@ impl ForeignFrom<diesel_models::business_profile::PaymentLinkConfigRequest>
             payment_button_text_colour: item.payment_button_text_colour,
             sdk_ui_rules: item.sdk_ui_rules,
             payment_link_ui_rules: item.payment_link_ui_rules,
+            enable_button_only_on_form_ready: item.enable_button_only_on_form_ready,
         }
     }
 }

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

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

## Description
This PR adds a payment link config for controlling the behavior or Submit button on payment links (`enable_button_only_on_form_ready`)

- If `enable_button_only_on_form_ready` is true - disallow incomplete form submission
- [DEFAULT and CURRENT] If false - allow incomplete form submission and throw validation errors

### 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`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
This helps add more granular control of the payment links UX.

## How did you test it?
Locally using local SDK changes


<details>
    <summary>1. Update business profile to enable confirm button only when form is ready for submission</summary>

cURL

    curl --location --request POST 'http://localhost:8080/account/merchant_1741692513/business_profile/pro_0pSrloAVzi0xbnpnP0mY' \
        --header 'Content-Type: application/json' \
        --header 'api-key: test_admin' \
        --data '{"is_connector_agnostic_mit_enabled":true,"is_network_tokenization_enabled":true,"payment_link_config":{"allowed_domains":["*"],"custom_message_for_card_terms":"Hello","enabled_saved_payment_method":true,"theme":"#1A1A1A","logo":"https://hyperswitch.io/favicon.ico","payment_button_text":"Proceed to Payment!","sdk_ui_rules":{".Label":{"fontWeight":"700 !important","fontSize":"13px !important","color":"#003264 !important","backgroundColor":"red !important","opacity":"1 !important"}},"branding_visibility":false,"business_specific_configs":{"style1":{"enabled_saved_payment_method":true,"theme":"#003264","logo":"https://hyperswitch.io/favicon.ico","display_sdk_only":true,"payment_button_text":"Confirmer","enable_button_only_on_form_ready":true,"custom_message_for_card_terms":"Pour vérifier votre compte, un montant de 0,00 € sera débité lorsque vous aurez cliqué sur le bouton « Confirmer »","sdk_ui_rules":{".Label":{"fontWeight":"600 !important","fontSize":"14px !important","color":"#003264 !important","opacity":"1 !important"},".Input":{"border":"1px solid #003264"},".Input::placeholder":{"fontWeight":"400","fontSize":"16px","color":"#00326480"},".TermsTextLabel":{"color":"#003264 !important","opacity":"1 !important"},".SaveWalletDetailsLabel":{"fontSize":"13px !important","color":"#003264 !important","opacity":"1 !important"},":has(> .TermsTextLabel)":{"opacity":"1 !important"},".SaveWalletDetailsLabel em":{"fontStyle":"normal"}},"payment_link_ui_rules":{"#submit":{"color":"#003264","fontWeight":"700","fontSize":"19px","padding":"25px 0","borderRadius":"5px","backgroundColor":"#FFA445"},"#hyper-checkout-sdk":{"backgroundColor":"#FFFFFF"}}},"style2":{"enabled_saved_payment_method":true,"theme":"#1A1A1A","logo":"https://hyperswitch.io/favicon.ico"},"au":{"theme":"#2167AE","enable_button_only_on_form_ready":true,"payment_button_text":"Pay now","display_sdk_only":true,"payment_link_ui_rules":{"#submit":{"height":"48px","width":"max-content","borderRadius":"50px","padding":"0 20px","marginTop":"24px"},"#submit.processing":{"paddingLeft":"50px !important","paddingRight":"50px !important"}},"sdk_ui_rules":{".Tab":{"display":"none"},".Input, .Input:focus, .Input--invalid, .Input--empty":{"border":"none","boxShadow":"none","borderRadius":"0","padding":"0 !important","paddingBottom":"4px !important"},".Input, .Input:focus, .Input--empty":{"borderBottom":"1px solid #23366F !important","color":"#23366F"},".Input--invalid":{"borderBottom":"1px solid #CB4B40 !important"},".Label":{"marginLeft":"0","color":"#2167AE","opacity":"1 !important","fontSize":"12px !important"},".Label--invalid, .Input--invalid, .Error":{"color":"#CB4B40 !important","opacity":"1 !important"},".InputLogo--empty":{"color":"#979797"},".InputLogo":{"color":"#23366F","margin":"-30px"},".InputLogo--invalid":{"color":"#CB4B40"},".OrPayUsingLabel, .SaveWalletDetailsLabel, .CheckboxLabel, .TermsTextLabel, .InfoElement":{"color":"#003264 !important","opacity":"1 !important"},".OrPayUsingLine":{"backgroundColor":"#003264"}}}}}}'

Response

    {"merchant_id":"merchant_1741692513","profile_id":"pro_0pSrloAVzi0xbnpnP0mY","profile_name":"IN_default","return_url":"https://google.com/success","enable_payment_response_hash":true,"payment_response_hash_key":"kFqRl8UUlHycFDeV4xy9ibEW1bDKgNa7vfd2iYfzgfzc6x3grTHhCb2V2IoMc9uL","redirect_to_merchant_with_http_post":false,"webhook_details":{"webhook_version":"1.0.1","webhook_username":"random","webhook_password":"pass","webhook_url":"https://webhook.site/0728473e-e0aa-4bb6-9eea-85435cf54380","payment_created_enabled":true,"payment_succeeded_enabled":true,"payment_failed_enabled":true},"metadata":null,"routing_algorithm":null,"intent_fulfillment_time":900,"frm_routing_algorithm":null,"payout_routing_algorithm":null,"applepay_verified_domains":null,"session_expiry":900,"payment_link_config":{"domain_name":null,"theme":"#1A1A1A","logo":"https://hyperswitch.io/favicon.ico","seller_name":null,"sdk_layout":null,"display_sdk_only":null,"enabled_saved_payment_method":true,"hide_card_nickname_field":null,"show_card_form_by_default":null,"transaction_details":null,"background_image":null,"details_layout":null,"payment_button_text":"Proceed to Payment!","custom_message_for_card_terms":"Hello","payment_button_colour":null,"skip_status_screen":null,"payment_button_text_colour":null,"background_colour":null,"sdk_ui_rules":{".Label":{"opacity":"1 !important","fontSize":"13px !important","backgroundColor":"red !important","color":"#003264 !important","fontWeight":"700 !important"}},"payment_link_ui_rules":null,"enable_button_only_on_form_ready":null,"business_specific_configs":{"style2":{"theme":"#1A1A1A","logo":"https://hyperswitch.io/favicon.ico","seller_name":null,"sdk_layout":null,"display_sdk_only":null,"enabled_saved_payment_method":true,"hide_card_nickname_field":null,"show_card_form_by_default":null,"transaction_details":null,"background_image":null,"details_layout":null,"payment_button_text":null,"custom_message_for_card_terms":null,"payment_button_colour":null,"skip_status_screen":null,"payment_button_text_colour":null,"background_colour":null,"sdk_ui_rules":null,"payment_link_ui_rules":null,"enable_button_only_on_form_ready":null},"au":{"theme":"#2167AE","logo":null,"seller_name":null,"sdk_layout":null,"display_sdk_only":true,"enabled_saved_payment_method":null,"hide_card_nickname_field":null,"show_card_form_by_default":null,"transaction_details":null,"background_image":null,"details_layout":null,"payment_button_text":"Pay now","custom_message_for_card_terms":null,"payment_button_colour":null,"skip_status_screen":null,"payment_button_text_colour":null,"background_colour":null,"sdk_ui_rules":{".Input, .Input:focus, .Input--invalid, .Input--empty":{"border":"none","padding":"0 !important","boxShadow":"none","borderRadius":"0","paddingBottom":"4px !important"},".Input--invalid":{"borderBottom":"1px solid #CB4B40 !important"},".Tab":{"display":"none"},".Input, .Input:focus, .Input--empty":{"color":"#23366F","borderBottom":"1px solid #23366F !important"},".OrPayUsingLabel, .SaveWalletDetailsLabel, .CheckboxLabel, .TermsTextLabel, .InfoElement":{"opacity":"1 !important","color":"#003264 !important"},".OrPayUsingLine":{"backgroundColor":"#003264"},".Label--invalid::after":{"color":"#CB4B40","content":"' *'"},".Label":{"opacity":"1 !important","color":"#2167AE","marginLeft":"0","fontSize":"12px !important"},".InputLogo":{"color":"#23366F","margin":"-30px"},".Label::after":{"content":"' *'","color":"#23366F"},".InputLogo--empty":{"color":"#979797"},".InputLogo--invalid":{"color":"#CB4B40"},".Label--invalid, .Input--invalid, .Error":{"opacity":"1 !important","color":"#CB4B40 !important"}},"payment_link_ui_rules":{"#submit":{"borderRadius":"50px","height":"48px","width":"max-content","padding":"0 20px","marginTop":"24px"},"#submit.processing":{"paddingLeft":"50px !important","paddingRight":"50px !important"}},"enable_button_only_on_form_ready":true},"style1":{"theme":"#003264","logo":"https://hyperswitch.io/favicon.ico","seller_name":null,"sdk_layout":null,"display_sdk_only":true,"enabled_saved_payment_method":true,"hide_card_nickname_field":null,"show_card_form_by_default":null,"transaction_details":null,"background_image":null,"details_layout":null,"payment_button_text":"Confirmer","custom_message_for_card_terms":"Pour vérifier votre compte, un montant de 0,00 € sera débité lorsque vous aurez cliqué sur le bouton « Confirmer »","payment_button_colour":null,"skip_status_screen":null,"payment_button_text_colour":null,"background_colour":null,"sdk_ui_rules":{".TermsTextLabel":{"opacity":"1 !important","color":"#003264 !important"},".Input":{"border":"1px solid #003264"},".Label":{"opacity":"1 !important","color":"#003264 !important","fontSize":"14px !important","fontWeight":"600 !important"},".Input::placeholder":{"color":"#00326480","fontSize":"16px","fontWeight":"400"},".SaveWalletDetailsLabel":{"fontSize":"13px !important","color":"#003264 !important","opacity":"1 !important"},":has(> .TermsTextLabel)":{"opacity":"1 !important"},".SaveWalletDetailsLabel em":{"fontStyle":"normal"}},"payment_link_ui_rules":{"#submit":{"color":"#003264","padding":"25px 0","fontSize":"19px","fontWeight":"700","borderRadius":"5px","backgroundColor":"#FFA445"},"#hyper-checkout-sdk":{"backgroundColor":"#FFFFFF"}},"enable_button_only_on_form_ready":true}},"allowed_domains":["*"],"branding_visibility":false},"authentication_connector_details":null,"use_billing_as_payment_method_billing":true,"extended_card_info_config":null,"collect_shipping_details_from_wallet_connector":false,"collect_billing_details_from_wallet_connector":false,"always_collect_shipping_details_from_wallet_connector":false,"always_collect_billing_details_from_wallet_connector":false,"is_connector_agnostic_mit_enabled":true,"payout_link_config":null,"outgoing_webhook_custom_http_headers":null,"tax_connector_id":null,"is_tax_connector_enabled":false,"is_network_tokenization_enabled":true,"is_auto_retries_enabled":false,"max_auto_retries_enabled":null,"always_request_extended_authorization":null,"is_click_to_pay_enabled":false,"authentication_product_ids":null,"card_testing_guard_config":{"card_ip_blocking_status":"disabled","card_ip_blocking_threshold":3,"guest_user_card_blocking_status":"disabled","guest_user_card_blocking_threshold":10,"customer_id_blocking_status":"disabled","customer_id_blocking_threshold":5,"card_testing_guard_expiry":3600},"is_clear_pan_retries_enabled":false,"force_3ds_challenge":false}

</details>

<details>
    <summary>2. Create payment link</summary>

cURL

    curl --location --request POST 'http://localhost:8080/payments' \
        --header 'Content-Type: application/json' \
        --header 'Accept: application/json' \
        --header 'api-key: dev_sI7y2RBalVSE3nYh5k4Gs2WGws277uoKwu3Y7gM9y2U2dmDSVymbHUAqPt3zDsHr' \
        --data-raw '{"customer_id":"cus_t0I7DMr69stOdG3QaLZP","profile_id":"pro_0pSrloAVzi0xbnpnP0mY","customer_acceptance":{"acceptance_type":"online","accepted_at":"1963-05-03T04:07:52.723Z","online":{"ip_address":"127.0.0.1","user_agent":"amet irure esse"}},"amount":100,"currency":"USD","payment_link":true,"capture_method":"automatic","email":"john.doe@example.co.in","session_expiry":100000,"return_url":"https://example.com"}'

Response

    {"payment_id":"pay_ufjEW9U5WAea89stlSED","merchant_id":"merchant_1741692513","status":"requires_payment_method","amount":100,"net_amount":100,"shipping_cost":null,"amount_capturable":100,"amount_received":null,"connector":null,"client_secret":"pay_ufjEW9U5WAea89stlSED_secret_eg6sZLpO9LH60crrsV0I","created":"2025-03-13T08:47:48.496Z","currency":"USD","customer_id":"cus_t0I7DMr69stOdG3QaLZP","customer":{"id":"cus_t0I7DMr69stOdG3QaLZP","name":"John Nether","email":"john.doe@example.co.in","phone":"6168205362","phone_country_code":"+1"},"description":null,"refunds":null,"disputes":null,"mandate_id":null,"mandate_data":null,"setup_future_usage":null,"off_session":null,"capture_on":null,"capture_method":"automatic","payment_method":null,"payment_method_data":null,"payment_token":null,"shipping":null,"billing":null,"order_details":null,"email":"john.doe@example.co.in","name":"John Nether","phone":"6168205362","return_url":"https://example.com/","authentication_type":null,"statement_descriptor_name":null,"statement_descriptor_suffix":null,"next_action":null,"cancellation_reason":null,"error_code":null,"error_message":null,"unified_code":null,"unified_message":null,"payment_experience":null,"payment_method_type":null,"connector_label":null,"business_country":null,"business_label":"default","business_sub_label":null,"allowed_payment_method_types":null,"ephemeral_key":{"customer_id":"cus_t0I7DMr69stOdG3QaLZP","created_at":1741855668,"expires":1741859268,"secret":"epk_3ccf7b98c7c54188ba5d9230c3bd21fe"},"manual_retry_allowed":null,"connector_transaction_id":null,"frm_message":null,"metadata":null,"connector_metadata":null,"feature_metadata":null,"reference_id":null,"payment_link":{"link":"http://localhost:8080/payment_link/merchant_1741692513/pay_ufjEW9U5WAea89stlSED?locale=en","secure_link":"http://localhost:8080/payment_link/s/merchant_1741692513/pay_ufjEW9U5WAea89stlSED?locale=en","payment_link_id":"plink_fW4Nnwl7EcbrmjmOQ1pi"},"profile_id":"pro_0pSrloAVzi0xbnpnP0mY","surcharge_details":null,"attempt_count":1,"merchant_decision":null,"merchant_connector_id":null,"incremental_authorization_allowed":null,"authorization_count":null,"incremental_authorizations":null,"external_authentication_details":null,"external_3ds_authentication_attempted":false,"expires_on":"2025-03-14T12:34:28.493Z","fingerprint":null,"browser_info":null,"payment_method_id":null,"payment_method_status":null,"updated":"2025-03-13T08:47:48.504Z","split_payments":null,"frm_metadata":null,"extended_authorization_applied":null,"capture_before":null,"merchant_order_reference_id":null,"order_tax_amount":null,"connector_mandate_id":null,"card_discovery":null}

#### Things to validate

1. Confirm button is greyed out and disabled if form is incomplete

<img width="611" alt="Screenshot 2025-03-13 at 2 37 23 PM" src="https://github.com/user-attachments/assets/ffb1b4f2-6c78-4f9c-9080-f74e2bf4223d" />


2. Confirm button is enabled once form is completely filled

<img width="627" alt="Screenshot 2025-03-13 at 2 37 36 PM" src="https://github.com/user-attachments/assets/d4312682-0283-481f-9dfa-de6f0b525810" />

</details>

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

- [x] I formatted the code `cargo +nightly fmt --all`
- [ ] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
