Skip to content

refactor(customer): refactor customer db with storage utils and move trait to domain_models and impl to storage_model#7538

Merged
Gnanasundari24 merged 29 commits intomainfrom
refactor-customer-db
Apr 15, 2025
Merged

refactor(customer): refactor customer db with storage utils and move trait to domain_models and impl to storage_model#7538
Gnanasundari24 merged 29 commits intomainfrom
refactor-customer-db

Conversation

@jagan-jaya
Copy link
Contributor

@jagan-jaya jagan-jaya commented Mar 17, 2025

Type of Change

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

Description

  1. Move the Customer db to storage_impl and its interface to hyperswitch_domain_model
  2. Added resource handling implementations in KVRouterStore, RouterStore, With this support all the resource handlers can now directly call these generic functions instead of handling the merchant storage scheme manually

Additional Changes

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

Motivation and Context

As part of Payment method crate seperation, customer DB is required in payment methods so moving it out from router

How did you test it?

Start router service with cargo r

With KV disabled for the merchant

  1. Create Merchant Account, Create API Key
2. Create Customer Request
curl --location 'http://localhost:8080/customers' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_gu3raVGqNAtULFRrZ8y503nu1tVIxsZQWTupJcl5xsTLDz37kvLxTfaq6ZCVfb0e' \
--data-raw '{
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "First customer",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}'

Response
{
"customer_id": "cus_LcqiGN9t81YjqIyMgKWt",
"name": "John Doe",
"email": "guest@example.com",
"phone": "999999999",
"phone_country_code": "+65",
"description": "First customer",
"address": null,
"created_at": "2025-03-27T09:06:56.214Z",
"metadata": {
"udf1": "value1",
"new_customer": "true",
"login_date": "2019-09-10T10:11:12Z"
},
"default_payment_method_id": null
}

3. Retrieve Customer

Request

curl --location 'http://localhost:8080/customers/cus_LcqiGN9t81YjqIyMgKWt' \
--header 'Accept: application/json' \
--header 'api-key: dev_gu3raVGqNAtULFRrZ8y503nu1tVIxsZQWTupJcl5xsTLDz37kvLxTfaq6ZCVfb0e'

Response

{
    "customer_id": "cus_LcqiGN9t81YjqIyMgKWt",
    "name": "John Doe",
    "email": "guest@example.com",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "First customer",
    "address": null,
    "created_at": "2025-03-27T09:06:56.214Z",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "default_payment_method_id": null
}
4. Update Customer

Request

curl --location 'http://localhost:8080/customers/cus_LcqiGN9t81YjqIyMgKWt' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_gu3raVGqNAtULFRrZ8y503nu1tVIxsZQWTupJcl5xsTLDz37kvLxTfaq6ZCVfb0e' \
--data-raw '{
    "email": "JohnTest@test.com",
    "name": "John Test",
    "phone_country_code": "+65",
    "phone": "888888888",
    "description": "First customer",
    "metadata": {
        "city": "NY",
        "unit": "245"
    }
}'

Response

{
    "customer_id": "cus_LcqiGN9t81YjqIyMgKWt",
    "name": "John Test",
    "email": "JohnTest@test.com",
    "phone": "888888888",
    "phone_country_code": "+65",
    "description": "First customer",
    "address": null,
    "created_at": "2025-03-27T09:06:56.214Z",
    "metadata": {
        "city": "NY",
        "unit": "245"
    },
    "default_payment_method_id": null
}
5. Delete Customer

Request

curl --location --request DELETE 'http://localhost:8080/customers/cus_LcqiGN9t81YjqIyMgKWt' \
--header 'Accept: application/json' \
--header 'api-key: dev_gu3raVGqNAtULFRrZ8y503nu1tVIxsZQWTupJcl5xsTLDz37kvLxTfaq6ZCVfb0e'

{
    "customer_id": "cus_LcqiGN9t81YjqIyMgKWt",
    "customer_deleted": true,
    "address_deleted": true,
    "payment_methods_deleted": true
}

Response

{
    "customer_id": "cus_LcqiGN9t81YjqIyMgKWt",
    "customer_deleted": true,
    "address_deleted": true,
    "payment_methods_deleted": true
}

With KV enabled for a merchant

  1. Create Merchant Account, Create API Key, Do not start Drainer
2. Enable KV for the merchant

Request

curl --location 'http://localhost:8080/accounts/merchant_1743066953/kv' \
--header 'Content-Type: application/json' \
--header 'api-key: test_admin' \
--data '{
"kv_enabled": true
}'

Response

{
    "merchant_id": "merchant_1743066953",
    "kv_enabled": true
}
3. Create Customer Request
curl --location 'http://localhost:8080/customers' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_f3M5W0ZyHHsCUekQ6z1mPraNzaUzrb73No6SPnlDbFGUygPOJtOOfCIyyHUGXmwL' \
--data-raw '{
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "First customer",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}'

Response
{
"customer_id": "cus_hS8uA0hvtVdbBa8ecYOw",
"name": "John Doe",
"email": "guest@example.com",
"phone": "999999999",
"phone_country_code": "+65",
"description": "First customer",
"address": null,
"created_at": "2025-03-27T09:19:49.092Z",
"metadata": {
"udf1": "value1",
"new_customer": "true",
"login_date": "2019-09-10T10:11:12Z"
},
"default_payment_method_id": null
}

4. Retrieve Customer

Request

curl --location 'http://localhost:8080/customers/cus_hS8uA0hvtVdbBa8ecYOw' \
--header 'Accept: application/json' \
--header 'api-key: dev_f3M5W0ZyHHsCUekQ6z1mPraNzaUzrb73No6SPnlDbFGUygPOJtOOfCIyyHUGXmwL'

Response

{
    "customer_id": "cus_hS8uA0hvtVdbBa8ecYOw",
    "name": "John Doe",
    "email": "guest@example.com",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "First customer",
    "address": null,
    "created_at": "2025-03-27T09:19:49.092Z",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "default_payment_method_id": null
}
5. Update Customer

Request

curl --location 'http://localhost:8080/customers/cus_hS8uA0hvtVdbBa8ecYOw' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_f3M5W0ZyHHsCUekQ6z1mPraNzaUzrb73No6SPnlDbFGUygPOJtOOfCIyyHUGXmwL' \
--data-raw '{
    "email": "JohnTest@test.com",
    "name": "John Test",
    "phone_country_code": "+65",
    "phone": "888888888",
    "description": "First customer",
    "metadata": {
        "city": "NY",
        "unit": "245"
    }
}'

Response

{
    "customer_id": "cus_hS8uA0hvtVdbBa8ecYOw",
    "name": "John Test",
    "email": "JohnTest@test.com",
    "phone": "888888888",
    "phone_country_code": "+65",
    "description": "First customer",
    "address": null,
    "created_at": "2025-03-27T09:19:49.092Z",
    "metadata": {
        "city": "NY",
        "unit": "245"
    },
    "default_payment_method_id": null
}
6. Delete Customer

Request

curl --location --request DELETE 'http://localhost:8080/customers/cus_hS8uA0hvtVdbBa8ecYOw' \
--header 'Accept: application/json' \
--header 'api-key: dev_f3M5W0ZyHHsCUekQ6z1mPraNzaUzrb73No6SPnlDbFGUygPOJtOOfCIyyHUGXmwL'

Response

{
    "customer_id": "cus_hS8uA0hvtVdbBa8ecYOw",
    "customer_deleted": true,
    "address_deleted": true,
    "payment_methods_deleted": true
}
7. Retrieve Customer

Request

curl --location 'http://localhost:8080/customers/cus_hS8uA0hvtVdbBa8ecYOw' \
--header 'Accept: application/json' \
--header 'api-key: dev_f3M5W0ZyHHsCUekQ6z1mPraNzaUzrb73No6SPnlDbFGUygPOJtOOfCIyyHUGXmwL'

Response

{
    "customer_id": "cus_hS8uA0hvtVdbBa8ecYOw",
    "name": "Redacted",
    "email": "Redacted",
    "phone": "Redacted",
    "phone_country_code": "Redacted",
    "description": "Redacted",
    "address": null,
    "created_at": "2025-03-27T09:19:49.092Z",
    "metadata": {
        "city": "NY",
        "unit": "245"
    },
    "default_payment_method_id": null
}

Start Drainer

After adding drainer, the KV created customer will be added into db by drainer, so read, update and delete operation should work fine after starting it.

Apply below changes

# In config/docker_compose.toml
[master_database]
host = "host.docker.internal"
[replica_database]
host = "host.docker.internal"
[redis]
host = "host.docker.internal"

In docker-compose.yml change drainer as below

 hyperswitch-drainer:
    image: juspaydotin/hyperswitch-drainer:standalone
    pull_policy: always
    command: /local/bin/drainer -f /local/config/docker_compose.toml
    deploy:
      replicas: ${DRAINER_INSTANCE_COUNT:-1}
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      - router_net
    profiles:
      - full_kv
    volumes:
      - ./config:/local/config
    restart: unless-stopped
    labels:
      logs: "promtail"

Run the drainer with docker-compose up hyperswitch-drainer

With KV disabled for the same merchant

1. Disable KV for the same merchant

Request

curl --location 'http://localhost:8080/accounts/merchant_1743066953/kv' \
--header 'Content-Type: application/json' \
--header 'api-key: test_admin' \
--data '{
"kv_enabled": false
}'

Response

{
    "merchant_id": "merchant_1743066953",
    "kv_enabled": false
}
2. Update Customer details

Request

curl --location 'http://localhost:8080/customers/cus_hS8uA0hvtVdbBa8ecYOw' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_f3M5W0ZyHHsCUekQ6z1mPraNzaUzrb73No6SPnlDbFGUygPOJtOOfCIyyHUGXmwL' \
--data-raw '{
    "email": "JohnTest@test.com",
    "name": "John Test",
    "phone_country_code": "+65",
    "phone": "888888888",
    "description": "First customer",
    "metadata": {
        "city": "NY",
        "unit": "245"
    }
}'

Response

{
    "error": {
        "type": "invalid_request",
        "message": "Customer has already been redacted",
        "code": "IR_11"
    }
}
3. Get Customer

Request

curl --location 'http://localhost:8080/customers/cus_hS8uA0hvtVdbBa8ecYOw'
--header 'Accept: application/json'
--header 'api-key: dev_f3M5W0ZyHHsCUekQ6z1mPraNzaUzrb73No6SPnlDbFGUygPOJtOOfCIyyHUGXmwL'

Response

{
    "customer_id": "cus_hS8uA0hvtVdbBa8ecYOw",
    "name": "Redacted",
    "email": "Redacted",
    "phone": "Redacted",
    "phone_country_code": "Redacted",
    "description": "Redacted",
    "address": null,
    "created_at": "2025-03-27T09:19:49.092Z",
    "metadata": {
        "city": "NY",
        "unit": "245"
    },
    "default_payment_method_id": null
}
4. Delete Customer Request
curl --location --request DELETE 'http://localhost:8080/customers/cus_hS8uA0hvtVdbBa8ecYOw' \
--header 'Accept: application/json' \
--header 'api-key: dev_f3M5W0ZyHHsCUekQ6z1mPraNzaUzrb73No6SPnlDbFGUygPOJtOOfCIyyHUGXmwL'

Response

{
    "error": {
        "type": "invalid_request",
        "message": "Customer has already been redacted",
        "code": "IR_11"
    }
}

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

@semanticdiff-com
Copy link

semanticdiff-com bot commented Mar 17, 2025

Review changes with  SemanticDiff

Changed Files
File Status
  crates/hyperswitch_domain_models/src/customer.rs  3% smaller
  crates/storage_impl/src/customers.rs  1% smaller
  crates/router/src/db/customers.rs  1% smaller
  crates/router/src/db.rs  0% smaller
  crates/router/src/db/kafka_store.rs  0% smaller
  crates/storage_impl/src/lib.rs  0% smaller
  crates/storage_impl/src/mock_db.rs  0% smaller
  crates/storage_impl/src/payment_method.rs  0% smaller

@jagan-jaya jagan-jaya self-assigned this Mar 17, 2025
@jagan-jaya jagan-jaya changed the title Refactor customer db refactor(customer): refactor customer db with storage utils and move trait to domain_models and impl to storage_model Mar 18, 2025
Base automatically changed from refactor-storage-error to main March 21, 2025 14:08
@jagan-jaya jagan-jaya marked this pull request as ready for review March 27, 2025 10:30
@jagan-jaya jagan-jaya requested review from a team as code owners March 27, 2025 10:30
jarnura
jarnura previously approved these changes Apr 1, 2025
@Gnanasundari24 Gnanasundari24 added this pull request to the merge queue Apr 15, 2025
Merged via the queue into main with commit e8e0b5d Apr 15, 2025
16 of 20 checks passed
@Gnanasundari24 Gnanasundari24 deleted the refactor-customer-db branch April 15, 2025 07:54
pixincreate added a commit that referenced this pull request Apr 15, 2025
…acilitapay-pix-pmt

* 'main' of github.com:juspay/hyperswitch:
  feat(docker): add webhook notifiers for installation tracking (#7653)
  refactor(customer): refactor customer db with storage utils and move trait to domain_models and impl to storage_model (#7538)
  feat(core): Add support for updating metadata after payment has been authorized (#7776)
  chore(version): 2025.04.14.0
  fix: script for one click docker setup (#7762)
  fix(payment_link): add validation for return_url during payment link creation (#7802)
  chore: address Rust 1.86.0 clippy lints (#7735)
  fix(connector): Add network error message support for payment connectors (#7760)
  feat(webhook): add filter by event class and type (#7275)
  ci(cypress): verify if card fields are populated on updating card info (#7743)
  chore(version): 2025.04.11.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants