hyperswitch
feat(users): refactor `ProdIntent` to support product-type context and merchant-scope
#7638
Merged

feat(users): refactor `ProdIntent` to support product-type context and merchant-scope #7638

likhinbopanna merged 20 commits into main from prod-intent-v2
tsdk02
tsdk0228 days ago (edited 20 days ago)

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

This PR introduces support for associating ProdIntent with specific product_types and transitions the feature from being user-scoped to merchant-scoped, enabling more flexible usage across different products.

Changes Introduced

1. New V2 Routes for Dashboard Metadata

  • Added v2 routes to support ProdIntent creation and retrieval with respect to product_type.

2. Product-Type Context for ProdIntent

  • While inserting the ProdIntent into the dashboard_metadata table (for both v1 and v2 schemas), the product_type is now included in the metadata.

3. Merchant-Scoped ProdIntent

  • Before: ProdIntent was user-scoped, allowing a single request per user.
  • Now: It is merchant-scoped, allowing a user to raise separate ProdIntent requests for different merchant accounts for each product by the same user.

4. Enhanced Email Body

  • The email body sent for ProdIntent now includes the product_type context, helping understand for which product the prod intent was raised.

Additional Changes

  • This PR modifies the API contract
  • This PR modifies the database schema
  • This PR modifies application configuration/environment variables

Motivation and Context

Enable requesting for Production Access for every product type merchant account.

How did you test it?

Hit the curl:

  • Dashboard Metadata - ProdIntent (POST):
curl --location 'http://localhost:8080/user/data' \
--header 'accept: */*' \
--header 'accept-language: en-US,en;q=0.9' \
--header 'api-key: hyperswitch' \
--header 'content-type: application/json' \
--header 'origin: https://app.hyperswitch.io' \
--header 'priority: u=1, i' \
--header 'referer: https://app.hyperswitch.io/dashboard/home' \
--header 'sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"' \
--header 'sec-ch-ua-mobile: ?0' \
--header 'sec-ch-ua-platform: "macOS"' \
--header 'sec-fetch-dest: empty' \
--header 'sec-fetch-mode: cors' \
--header 'sec-fetch-site: same-origin' \
--header 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' \
--header 'x-merchant-id: merchant_1715600622' \
--header 'x-profile-id: pro_CqAdpUQBUUam1n0g56mB' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOWExMGExMTYtZDc1Ny00MjBlLTkzNWQtZWRlZmQ5ZmQ3NTFmIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzQzMDUxMDM3Iiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTc0MzIyMzg0Mywib3JnX2lkIjoib3JnX1h5ZmR4eUttS1RGTEtuNTJ1clBVIiwicHJvZmlsZV9pZCI6InByb190Y3NsZWJDNDhmN1pkNG9mVlRqWCIsInRlbmFudF9pZCI6InB1YmxpYyJ9.NuvkkzrYPcba0JKUUrlM39NfyD2rHZ_jHIkHgPyM39s' \
--header 'Cookie: login_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMzcwZWUwYWUtZDI0ZS00ODJhLTlmNDktZDYzYmZkNGE0ZjVhIiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF9qdmMzb1dXRmxkb0xHNG1GbFc0UCIsInJvbGVfaWQiOiJvcmdfYWRtaW4iLCJleHAiOjE3NDMxMDQxMzcsIm9yZ19pZCI6Im9yZ19hU3NCOWozTWVJcWxVS0YwbDJxRCIsInByb2ZpbGVfaWQiOiJwcm9fcGdrWUkzcnl6TlU2NlBLa3A4ZjciLCJ0ZW5hbnRfaWQiOiJwdWJsaWMifQ.oRERTxTsgrZWNXwSJ5w8CvDHOW1qB2GV3wBQ5Mv-BC8' \
--data-raw '{
    "ProdIntent": {
        "poc_email": "berry@gmail.com",
        "is_completed": true,
        "legal_business_name": "blueberry",
        "business_location": "IN",
        "business_website": "https://google.com",
        "poc_name": "berry",
        "comments": "tx123",
        "product_type": "orchestration"
    }
}'

Dashboard Metadata table should get inserted/updated with the record along with the product_type in the json body.
Sample entry to table:
image

Also, product_type should also be sent in the email body to the configured recepient email-id.
WhatsApp Image 2025-03-28 at 12 58 24 AM

  • Dashboard Metadata - ProdIntent (GET):
curl --location 'http://localhost:8080/user/data?keys=ProdIntent' \
--header 'X-Merchant-Id: ddcd_oBuAVsHl0XMLag9JsHCV' \
--header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiM2RlMDFhYWYtOWVkMi00YTY5LTg3YWItNWU1YmI2ZWFiNGQ5IiwibWVyY2hhbnRfaWQiOiJtZXJjaGFudF8xNzQyMjEyMzQxIiwicm9sZV9pZCI6Im9yZ19hZG1pbiIsImV4cCI6MTc0Mjk4MDgyOCwib3JnX2lkIjoib3JnX3pMQmRNTnpLaDZrNmhiT2tockdJIiwicHJvZmlsZV9pZCI6InByb19LWnhHZ21rUHFkMldQdXBFa2psWCIsInRlbmFudF9pZCI6InB1YmxpYyJ9.aD185AZEOcIjHnLnZZ9GJ2ujSHIdRPbPtrYhzNo0V4M' \
--header 'sec-ch-ua-platform: "macOS"' \
--header 'Referer: https://integ.hyperswitch.io/dashboard/v2/recon' \
--header 'sec-ch-ua: "Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"' \
--header 'X-Profile-Id: pro_UaxaxwTw4A0a9D0QNBrf' \
--header 'sec-ch-ua-mobile: ?0' \
--header 'api-key: hyperswitch' \
--header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36' \
--header 'Content-Type: application/json'

Expected Output:

[
    {
        "ProdIntent": {
            "legal_business_name": "blueberry",
            "business_label": null,
            "business_location": "IN",
            "display_name": null,
            "poc_email": "berry@gmail.com",
            "business_type": null,
            "business_identifier": null,
            "business_website": "https://google.com",
            "poc_name": "berry",
            "poc_contact": null,
            "comments": "tx123",
            "is_completed": true,
            "product_type": "orchestration"
        }
    }
]

Similarly v2 routes:
http://localhost:8080/v2/user/data (POST)
http://localhost:8080/v2/user/data?keys=ProdIntent (GET)

Checklist

  • I formatted the code cargo +nightly fmt --all
  • I addressed lints thrown by cargo clippy
  • I reviewed the submitted code
  • I added unit tests for my changes where possible
tsdk02 feat(users): add v2 routes for dashboard_metadata
cb78db0e
tsdk02 tsdk02 added C-feature
tsdk02 tsdk02 assigned tsdk02 tsdk02 28 days ago
tsdk02 tsdk02 requested a review 28 days ago
semanticdiff-com
semanticdiff-com28 days ago (edited 15 days ago)
tsdk02 feat(users): add product_type context while populating table and whil…
07d3eb72
tsdk02 tsdk02 requested a review 28 days ago
tsdk02 fix clippy errors
74bd7902
tsdk02 tsdk02 changed the title feat(users): add v2 routes for dashboard_metadata feat(users): refactor `ProdIntent` to support product-type context and merchant-scope 28 days ago
tsdk02 minor nits
9353361c
tsdk02 feat(users): make prodIntent merchant-scoped
1df4975a
tsdk02 merged main
9251a082
hyperswitch-bot[bot] chore: run formatter
8d9d3e28
tsdk02 feat(users): extract product_type through merchant_account db call
5d0d135f
tsdk02 merged main
c1981921
hyperswitch-bot[bot] chore: run formatter
dda8479f
tsdk02 minor nits
e52026e8
tsdk02 tsdk02 requested a review from ThisIsMani ThisIsMani 27 days ago
tsdk02 tsdk02 requested a review from racnan racnan 27 days ago
tsdk02 tsdk02 requested a review from SanchithHegde SanchithHegde 27 days ago
ThisIsMani
ThisIsMani commented on 2025-03-27
Conversation is marked as resolved
Show resolved
crates/api_models/src/user/dashboard_metadata.rs
105105 pub is_completed: bool,
106106}
107107
108
pub type ProdIntentWithProductType = (ProdIntent, MerchantProductType);
ThisIsMani27 days ago

Convert this to a struct if possible. (NIT)

Conversation is marked as resolved
Show resolved
crates/router/src/core/user/dashboard_metadata.rs
805 state,
806 user.to_owned(),
807 DBEnum::PaypalConnected,
808
types::MetaData::PaypalConnected(api::ProcessorConnected {
809
processor_id: mca.get_id(),
810
processor_name: mca.connector_name,
ThisIsMani27 days ago

Can't you just create this type based on the version. I don't think the insert call needs to be under feature flag.

Conversation is marked as resolved
Show resolved
crates/router/src/core/user/dashboard_metadata.rs
761 state,
762 user.to_owned(),
763 DBEnum::StripeConnected,
764
types::MetaData::StripeConnected(api::ProcessorConnected {
765
processor_id: mca.get_id(),
766
processor_name: mca.connector_name.to_string(),
ThisIsMani27 days ago

Same here.

Conversation is marked as resolved
Show resolved
crates/router/src/core/user/dashboard_metadata.rs
485
486 let product_type = merchant_account.product_type.unwrap_or_default();
487
488
let mut data_value = serde_json::to_value(&data)
489
.change_context(UserErrors::InternalServerError)
490
.attach_printable("Error converting ProdIntent to JSON")?;
491
492
if let serde_json::Value::Object(ref mut map) = data_value {
493
map.insert("product_type".to_string(), serde_json::json!(product_type));
494
} else {
495
return Err(UserErrors::InternalServerError)
496
.attach_printable("Error adding product type to the JSON object");
497
}
ThisIsMani27 days ago👍 1

Can't you construct the new type before and then convert it to json later? You would not need mutations and error handling.

SanchithHegde
SanchithHegde commented on 2025-03-27
Conversation is marked as resolved
Show resolved
crates/router/src/core/user/dashboard_metadata.rs
483 .change_context(UserErrors::InternalServerError)
484 .attach_printable("Failed to retrieve merchant account by merchant_id")?;
485
486
let product_type = merchant_account.product_type.unwrap_or_default();
SanchithHegde27 days ago

Nit: We can add a comment stating why we're using unwrap_or_default().

tsdk0227 days ago

Added a comment stating the reason.

tsdk02 resolve comments
d2455bc6
SanchithHegde
SanchithHegde dismissed these changes on 2025-03-28
tsdk02 feat(users): obtain product_type from the request body
b082834e
tsdk02 tsdk02 dismissed their stale review via b082834e 20 days ago
tsdk02 Merge branch 'main' into prod-intent-v2
4d0b6eac
tsdk02 default product_type if not included in request body
0186b994
SanchithHegde
SanchithHegde commented on 2025-04-03
Conversation is marked as resolved
Show resolved
crates/router/src/core/user/dashboard_metadata.rs
463463 .change_context(UserErrors::EmailParsingError)?;
464464 }
465 let mut metadata = utils::insert_user_scoped_metadata_to_db(
465
let mut data = data;
SanchithHegde20 days ago

I believe you can also do something like:

types::MetaData::ProdIntent(mut data) => { ... }

And then we can avoid the let mut data = data line.

Conversation is marked as resolved
Show resolved
crates/router/src/services/email/types.rs
595600 legal_business_name: self.legal_business_name.clone(),
596601 business_location: self.business_location.clone(),
597602 business_website: self.business_website.clone(),
603
product_type: self.product_type.clone(),
SanchithHegde20 days ago

Derive Copy on MerchantProductType, and avoid the clone here.

tsdk02 resolved comments
64b75fef
SanchithHegde
SanchithHegde dismissed these changes on 2025-04-03
tsdk02 fix errors due to Copy trait derived on MerchantProductType
140bb035
tsdk02 tsdk02 dismissed their stale review via 140bb035 18 days ago
ThisIsMani
ThisIsMani commented on 2025-04-07
Conversation is marked as resolved
Show resolved
crates/api_models/src/user/dashboard_metadata.rs
103103 pub poc_contact: Option<String>,
104104 pub comments: Option<String>,
105105 pub is_completed: bool,
106
pub product_type: Option<MerchantProductType>,
ThisIsMani16 days ago
Suggested change
pub product_type: Option<MerchantProductType>,
#[serde(default)]
pub product_type: MerchantProductType,
tsdk02 Merge branch 'main' into prod-intent-v2
c184d34b
tsdk02 made product type to extract default value and avoided mutating the p…
5960f008
tsdk02 Merge branch 'main' into prod-intent-v2
0bc34463
SanchithHegde
SanchithHegde approved these changes on 2025-04-10
ThisIsMani
ThisIsMani approved these changes on 2025-04-10
likhinbopanna likhinbopanna merged bbd21022 into main 13 days ago
likhinbopanna likhinbopanna deleted the prod-intent-v2 branch 13 days ago
arindam-sahoo arindam-sahoo unassigned tsdk02 tsdk02 8 days ago
tsdk02 tsdk02 assigned tsdk02 tsdk02 6 days ago

Login to write a write a comment.

Login via GitHub

Assignees
Labels
Milestone