Bug: [BUG] card payouts using payout_method_id



### Bug Description

Payout method data for card is not being fetched in payouts flow.

This bug was introduced in https://github.com/juspay/hyperswitch/pull/7786/files where `get_pm_list_context` was refactored to return back only the additional details by removing the call to card vault.

### Expected Behavior

`payout_method_data` should be fetched from card vault in payouts flow.

### Actual Behavior

`payout_method_data` is not being fetched in payouts flow.

### Steps To Reproduce
1. Create a card payout with `recurring: true`
2. Obtain the `payout_method_id`
3. Create a payout using this `payout_method_id` - throws an error saying card details not found

### Have you spent some time checking if this bug 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/crates/payment_methods/src/controller.rs b/crates/payment_methods/src/controller.rs
index bd5a9f316ef..095788fa368 100644
--- a/crates/payment_methods/src/controller.rs
+++ b/crates/payment_methods/src/controller.rs
@@ -229,6 +229,12 @@ pub trait PaymentMethodsController {
         merchant_id: &id_type::MerchantId,
         card_network: Option<common_enums::CardNetwork>,
     ) -> errors::PmResult<()>;
+
+    #[cfg(feature = "v1")]
+    async fn get_card_details_from_locker(
+        &self,
+        pm: &payment_methods::PaymentMethod,
+    ) -> errors::PmResult<api::CardDetailFromLocker>;
 }
 
 pub async fn create_encrypted_data<T>(
diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs
index 8f29e05e154..7ba707316c5 100644
--- a/crates/router/src/core/payment_methods/cards.rs
+++ b/crates/router/src/core/payment_methods/cards.rs
@@ -890,6 +890,14 @@ impl PaymentMethodsController for PmCards<'_> {
         .await
     }
 
+    #[cfg(feature = "v1")]
+    async fn get_card_details_from_locker(
+        &self,
+        pm: &domain::PaymentMethod,
+    ) -> errors::RouterResult<api::CardDetailFromLocker> {
+        get_card_details_from_locker(self.state, pm).await
+    }
+
     #[cfg(feature = "v1")]
     #[instrument(skip_all)]
     async fn retrieve_payment_method(
@@ -4205,6 +4213,7 @@ pub async fn list_customer_payment_method(
             &pm,
             Some(parent_payment_method_token.clone()),
             true,
+            false,
             &merchant_context,
         )
         .await?;
@@ -4349,6 +4358,7 @@ pub async fn list_customer_payment_method(
 }
 
 #[cfg(feature = "v1")]
+#[allow(clippy::too_many_arguments)]
 pub async fn get_pm_list_context(
     state: &routes::SessionState,
     payment_method: &enums::PaymentMethod,
@@ -4358,6 +4368,7 @@ pub async fn get_pm_list_context(
     #[cfg(feature = "payouts")] parent_payment_method_token: Option<String>,
     #[cfg(not(feature = "payouts"))] _parent_payment_method_token: Option<String>,
     is_payment_associated: bool,
+    force_fetch_card_from_vault: bool,
     merchant_context: &domain::MerchantContext,
 ) -> Result<Option<PaymentMethodListContext>, error_stack::Report<errors::ApiErrorResponse>> {
     let cards = PmCards {
@@ -4366,7 +4377,11 @@ pub async fn get_pm_list_context(
     };
     let payment_method_retrieval_context = match payment_method {
         enums::PaymentMethod::Card => {
-            let card_details = cards.get_card_details_with_locker_fallback(pm).await?;
+            let card_details = if force_fetch_card_from_vault {
+                Some(cards.get_card_details_from_locker(pm).await?)
+            } else {
+                cards.get_card_details_with_locker_fallback(pm).await?
+            };
 
             card_details.as_ref().map(|card| PaymentMethodListContext {
                 card_details: Some(card.clone()),
diff --git a/crates/router/src/core/payouts/validator.rs b/crates/router/src/core/payouts/validator.rs
index 3d58d1f367e..198caa4b893 100644
--- a/crates/router/src/core/payouts/validator.rs
+++ b/crates/router/src/core/payouts/validator.rs
@@ -233,6 +233,7 @@ pub async fn validate_create_request(
                 payment_method,
                 None,
                 false,
+                true,
                 merchant_context,
             )
             .await?
@@ -243,7 +244,7 @@ pub async fn validate_create_request(
                             card_number: card.card_number.get_required_value("card_number")?,
                             card_holder_name: card.card_holder_name,
                             expiry_month: card.expiry_month.get_required_value("expiry_month")?,
-                            expiry_year: card.expiry_year.get_required_value("expiry_month")?,
+                            expiry_year: card.expiry_year.get_required_value("expiry_year")?,
                         },
                     ))),
                     (_, Some(bank)) => Ok(Some(payouts::PayoutMethodData::Bank(bank))),

## Type of Change
- [x] Bugfix

## Description
Fixed payout method data not being fetched from card vault when using `payout_method_id` in payouts flow.

**Changes:**
- Added `force_fetch_card_from_vault` parameter to `get_pm_list_context` to enable card vault lookup for payouts
- Added `get_card_details_from_locker` method to PaymentMethodsController trait
- Set `force_fetch_card_from_vault=true` in payouts validator to ensure card details are retrieved from vault
- Fixed typo in error message for `expiry_year` field

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

## Motivation and Context
Fixes regression introduced in [PR #7786](https://github.com/juspay/hyperswitch/pull/7786) where card vault lookup was removed from `get_pm_list_context`. This broke recurring payouts functionality where `payout_method_id` should retrieve stored card details from vault.

## How did you test it?

<details>
    <summary>1. Create a recurring card payout</summary>

cURL

    curl --location --request POST 'http://localhost:8080/payouts/create' \
        --header 'Content-Type: application/json' \
        --header 'api-key: dev_ydIiVWyEGA60P3uQa2IHCKACVSRVHceIJEPrmSsb1TFfpJHfBmYRaMoqrrOiSab1' \
        --data '{"amount":100,"currency":"EUR","profile_id":"pro_pkpPuJzb8FHi5TYDj6ew","customer_id":"cus_UWxTZBStM3s2FzdDGGun","connector":["adyenplatform"],"description":"Its my first payout request","payout_type":"card","payout_method_data":{"card":{"card_number":"4111111111111111","expiry_month":"03","expiry_year":"2030"}},"billing":{"address":{"line1":"1467","line2":"Harrison Street","line3":"Harrison Street","city":"San Fransico","state":"CA","zip":"94122","country":"FR","first_name":"John","last_name":"Doe"}},"entity_type":"Individual","recurring":true,"metadata":{"ref":"123"},"confirm":true,"auto_fulfill":true}'

Response

    {"payout_id":"payout_s9nuyxhtsta0qQSKSaYC","merchant_id":"merchant_1752554602","merchant_order_reference_id":null,"amount":100,"currency":"EUR","connector":"adyenplatform","payout_type":"card","payout_method_data":{"card":{"card_issuer":null,"card_network":null,"card_type":null,"card_issuing_country":null,"bank_code":null,"last4":"1111","card_isin":"411111","card_extended_bin":null,"card_exp_month":"03","card_exp_year":"2030","card_holder_name":null}},"billing":{"address":{"city":"San Fransico","country":"FR","line1":"1467","line2":"Harrison Street","line3":"Harrison Street","zip":"94122","state":"CA","first_name":"John","last_name":"Doe"},"phone":null,"email":null},"auto_fulfill":true,"customer_id":"cus_UWxTZBStM3s2FzdDGGun","customer":{"id":"cus_UWxTZBStM3s2FzdDGGun","name":"John Nether","email":null,"phone":"6168205362","phone_country_code":"+1"},"client_secret":"payout_payout_s9nuyxhtsta0qQSKSaYC_secret_OxIBwSwo8HWWeZat8yYk","return_url":null,"business_country":null,"business_label":null,"description":"Its my first payout request","entity_type":"Individual","recurring":true,"metadata":{"ref":"123"},"merchant_connector_id":"mca_pjCOTcjEbqMrGm7rRLte","status":"initiated","error_message":null,"error_code":null,"profile_id":"pro_pkpPuJzb8FHi5TYDj6ew","created":"2025-07-15T04:44:24.793Z","connector_transaction_id":"38EBID67N4HZE5UD","priority":null,"payout_link":null,"email":null,"name":"John Nether","phone":"6168205362","phone_country_code":"+1","unified_code":null,"unified_message":null,"payout_method_id":"pm_G5g1xumF3AXWDPY8yl30"}

Note down payout method ID

</details>


<details>
    <summary>2. Create payout using payout_method_id</summary>

cURL

    curl --location --request POST 'http://localhost:8080/payouts/create' \
        --header 'Content-Type: application/json' \
        --header 'api-key: dev_ydIiVWyEGA60P3uQa2IHCKACVSRVHceIJEPrmSsb1TFfpJHfBmYRaMoqrrOiSab1' \
        --data-raw '{"payout_method_id":"pm_G5g1xumF3AXWDPY8yl30","amount":1,"currency":"EUR","customer_id":"cus_UWxTZBStM3s2FzdDGGun","email":"payout_customer@example.com","name":"John Doe","phone":"999999999","phone_country_code":"+65","description":"Its my first payout request","connector":["adyenplatform"],"billing":{"address":{"line1":"1467","line2":"Harrison Street","line3":"Harrison Street","city":"San Fransico","state":"CA","zip":"94122","country":"US","first_name":"John","last_name":"Doe"},"phone":{"number":"8056594427","country_code":"+91"}},"entity_type":"Individual","recurring":true,"metadata":{"ref":"123"},"confirm":true,"auto_fulfill":true}'

Response

    {"payout_id":"payout_QfSUC79LP4YDXjKwvn66","merchant_id":"merchant_1752554602","merchant_order_reference_id":null,"amount":1,"currency":"EUR","connector":"adyenplatform","payout_type":"card","payout_method_data":{"card":{"card_issuer":null,"card_network":null,"card_type":null,"card_issuing_country":null,"bank_code":null,"last4":"1111","card_isin":"411111","card_extended_bin":null,"card_exp_month":"03","card_exp_year":"2030","card_holder_name":null}},"billing":{"address":{"city":"San Fransico","country":"US","line1":"1467","line2":"Harrison Street","line3":"Harrison Street","zip":"94122","state":"CA","first_name":"John","last_name":"Doe"},"phone":{"number":"8056594427","country_code":"+91"},"email":null},"auto_fulfill":true,"customer_id":"cus_UWxTZBStM3s2FzdDGGun","customer":{"id":"cus_UWxTZBStM3s2FzdDGGun","name":"John Nether","email":null,"phone":"6168205362","phone_country_code":"+1"},"client_secret":"payout_payout_QfSUC79LP4YDXjKwvn66_secret_ArtMRkkTVFHDFsrTHMbE","return_url":null,"business_country":null,"business_label":null,"description":"Its my first payout request","entity_type":"Individual","recurring":true,"metadata":{"ref":"123"},"merchant_connector_id":"mca_pjCOTcjEbqMrGm7rRLte","status":"initiated","error_message":null,"error_code":null,"profile_id":"pro_pkpPuJzb8FHi5TYDj6ew","created":"2025-07-15T04:45:13.051Z","connector_transaction_id":"38EBHI67N4I9P28N","priority":null,"payout_link":null,"email":null,"name":"John Nether","phone":"6168205362","phone_country_code":"+1","unified_code":null,"unified_message":null,"payout_method_id":"pm_G5g1xumF3AXWDPY8yl30"}

</details>

## Checklist
- [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