Skip to content

feat(payment-methods): create payment_token in vault confirm / do payment-confirm with temp token from session #8525

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 7, 2025

Conversation

Sakilmostak
Copy link
Contributor

@Sakilmostak Sakilmostak commented Jul 2, 2025

Type of Change

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

Description

  • Save the context of payment_method_id in the session
  • Create temporary token for payment_method_id to pass to payment confirm
  • Save the cvc as a temporary data against the payment_token to be accessed by payment
  • Evict the cvc after first use
  • Encrypt/Decrypt the payload for cvc to store in redis
  • do confirm with payment_token in payment confirm call
  • the relative data are stored temporarily (15 mins) or till the first use
  • payment_method_data and cvc are fetch against the payment_token for the payment

Additional Changes

  • This PR modifies the API contract
    payment token are present in payment method session response
"associated_payment_methods": [
        "token_Dafmj9an9QORtSR2ZUlt"
    ],
  • This PR modifies the database schema
  • This PR modifies application configuration/environment variables

Motivation and Context

How did you test it?

Tested through Postman (Hyperswitch v2):

Create a customer:

  • curl for customer create
curl --location '{{baseUrl}}/v2/customers' \
--header 'x-profile-id:{{profile_id}}' \
--header 'Content-Type: application/json' \
--header 'Authorization: api-key={{api_key}}' \
--data-raw '{   
    "name": "John Doe",
    "email": "[email protected]"
}'
  • example response:
{
    "id": "12345_cus_0197c9ed96757970805bab9ee33c297c",
    "merchant_reference_id": null,
    "connector_customer_ids": null,
    "name": "John Doe",
    "email": "[email protected]",
    "phone": null,
    "phone_country_code": null,
    "description": null,
    "default_billing_address": null,
    "default_shipping_address": null,
    "created_at": "2025-07-02T06:58:00.438Z",
    "metadata": null,
    "default_payment_method_id": null
}

Create an Payment Intent:

  • curl for create payment intent
curl --location '{{baseUrl}}/v2/payments/create-intent' \
--header 'Content-Type: application/json' \
--header 'x-profile-id:{{profile_id}}' \
--header 'Authorization: api-key={{api_key}}' \
--data '{
    "amount_details": {
        "order_amount": 100,
        "currency": "EUR"
    },
    "customer_id": "{{customer_id}}",
    "capture_method":"automatic",
    "authentication_type": "no_three_ds"
}'
  • example response
{
    "id": "12345_pay_0197ca1bd1b77843abb91962582f7e4b",
    "status": "requires_payment_method",
    "amount_details": {
        "order_amount": 100,
        "currency": "EUR",
        "shipping_cost": null,
        "order_tax_amount": null,
        "external_tax_calculation": "skip",
        "surcharge_calculation": "skip",
        "surcharge_amount": null,
        "tax_on_surcharge": null
    },
    "client_secret": "cs_0197ca1bd1d07612a7b320f752ac4488",
    "profile_id": "pro_SZTOpU280AYkfajAgo5e",
    "merchant_reference_id": null,
    "routing_algorithm_id": null,
    "capture_method": "automatic",
    "authentication_type": "no_three_ds",
    "billing": null,
    "shipping": null,
    "customer_id": "12345_cus_0197ca1b7f117c20b6e75853d71550af",
    "customer_present": "present",
    "description": null,
    "return_url": null,
    "setup_future_usage": "on_session",
    "apply_mit_exemption": "Skip",
    "statement_descriptor": null,
    "order_details": null,
    "allowed_payment_method_types": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": null,
    "payment_link_enabled": "Skip",
    "payment_link_config": null,
    "request_incremental_authorization": "default",
    "expires_on": "2025-07-02T08:03:30.279Z",
    "frm_metadata": null,
    "request_external_three_ds_authentication": "Skip"
}

Create a Payment Method Session:

  • curl for payment_method_session create
curl --location 'http://localhost:8080/v2/payment-methods-session' \
--header 'X-Profile-Id: pro_SZTOpU280AYkfajAgo5e' \
--header 'x-feature: hyperswitch-custom-v2' \
--header 'Content-Type: application/json' \
--header 'Authorization: api-key=dev_asjzrvIGaJOBC19Hx719NnZBxpydXaERsgKO6ltr9X8Z0sg69XouAfZnRoQB14W4' \
--data '{
    "customer_id": "{{customer_id}}"
}'
  • example response
{
    "id": "12345_pms_0197ca1b96677e10bc58ea9b14097714",
    "customer_id": "12345_cus_0197ca1b7f117c20b6e75853d71550af",
    "billing": null,
    "psp_tokenization": null,
    "network_tokenization": null,
    "tokenization_data": null,
    "expires_at": "2025-07-02T08:03:15.079Z",
    "client_secret": "cs_0197ca1b96677e10bc58eaa6c8632a9d",
    "return_url": null,
    "next_action": null,
    "authentication_details": null,
    "associated_payment_methods": null,
    "associated_token_id": null
}

Confirm the session and collect the payment_method_token from associated_payment_methods:

  • curl for confirming payment_method_session:
curl --location '{{baseUrl}}/v2/payment-methods-session/:session_id' \
--header 'x-profile-id:{{profile_id}}' \
--header 'Content-Type: application/json' \
--header 'Authorization: publishable-key={{publishable_key}},client-secret={{client_secret}}' \
--data '{
    "payment_method_type": "card",
    "payment_method_subtype": "debit",
    "payment_method_data": {
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "11",
            "card_exp_year": "29",
            "card_holder_name": "John Doe",
            "card_cvc": "123"
        }
    }
}'
  • example response
{
    "id": "12345_pms_0197ca1b96677e10bc58ea9b14097714",
    "customer_id": "12345_cus_0197ca1b7f117c20b6e75853d71550af",
    "billing": null,
    "psp_tokenization": null,
    "network_tokenization": null,
    "tokenization_data": null,
    "expires_at": "2025-07-02T08:03:15.079Z",
    "client_secret": "CLIENT_SECRET_REDACTED",
    "return_url": null,
    "next_action": null,
    "authentication_details": null,
    "associated_payment_methods": [
        "token_Dafmj9an9QORtSR2ZUlt"
    ],
    "associated_token_id": null
}

Confirm the payment with the payment_method_token:

  • curl for confirming payment
curl --location '{{baseUrl}}/v2/payments/{{payment_id}}/confirm-intent' \
--header 'x-client-secret:{{client_secret}}' \
--header 'x-profile-id:{{profile_id}}' \
--header 'Content-Type: application/json' \
--header 'Authorization: publishable-key={{publishable_key}},client-secret={{client_secret}}' \
--data '{
    "payment_method_data": {
        "card_token": {
        }
    },
    "payment_method_type": "card",
    "payment_method_subtype": "debit",
    "payment_token": "token_Dafmj9an9QORtSR2ZUlt"
}'
  • example response
{
    "id": "12345_pay_0197ca1bd1b77843abb91962582f7e4b",
    "status": "succeeded",
    "amount": {
        "order_amount": 100,
        "currency": "EUR",
        "shipping_cost": null,
        "order_tax_amount": null,
        "external_tax_calculation": "skip",
        "surcharge_calculation": "skip",
        "surcharge_amount": null,
        "tax_on_surcharge": null,
        "net_amount": 100,
        "amount_to_capture": null,
        "amount_capturable": 0,
        "amount_captured": 100
    },
    "customer_id": "12345_cus_0197ca1b7f117c20b6e75853d71550af",
    "connector": "stripe",
    "created": "2025-07-02T07:48:30.279Z",
    "payment_method_data": {
        "billing": null
    },
    "payment_method_type": "card",
    "payment_method_subtype": "debit",
    "connector_transaction_id": "pi_3RgLPJD5R7gDAGff1vB5q7OL",
    "connector_reference_id": null,
    "merchant_connector_id": "mca_pWR7OWBqRtRvEAr9zgkE",
    "browser_info": null,
    "error": null,
    "shipping": null,
    "billing": null,
    "attempts": null,
    "connector_token_details": {
        "token": "pm_1RgLPJD5R7gDAGff8n5Q2798",
        "connector_token_request_reference_id": "Og4oxt3597iaNjG9wS"
    },
    "payment_method_id": "12345_pm_0197ca1ba6ec7d63a37f47fa75794839",
    "next_action": null,
    "return_url": "https://google.com/success",
    "authentication_type": "no_three_ds",
    "authentication_type_applied": "no_three_ds",
    "is_iframe_redirection_enabled": null,
    "merchant_reference_id": null
}

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

@Sakilmostak Sakilmostak added this to the June 2025 Release milestone Jul 2, 2025
@Sakilmostak Sakilmostak self-assigned this Jul 2, 2025
@Sakilmostak Sakilmostak added A-core Area: Core flows C-feature Category: Feature request or enhancement A-payment-methods Area: Payment Methods labels Jul 2, 2025
Copy link

semanticdiff-com bot commented Jul 2, 2025

@Sakilmostak Sakilmostak marked this pull request as ready for review July 2, 2025 07:57
@Sakilmostak Sakilmostak requested review from a team as code owners July 2, 2025 07:57
jarnura
jarnura previously approved these changes Jul 3, 2025
@@ -12,7 +12,7 @@ pub struct PaymentMethodSession {
pub return_url: Option<common_utils::types::Url>,
#[serde(with = "common_utils::custom_serde::iso8601")]
pub expires_at: time::PrimitiveDateTime,
pub associated_payment_methods: Option<Vec<common_utils::id_type::GlobalPaymentMethodId>>,
pub associated_payment_methods: Option<Vec<String>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can have a type for this instead of string

associated_payment_methods: Some(vec![parent_payment_method_token.clone()])
};

vault::insert_cvc_using_payment_token(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
vault::insert_cvc_using_payment_token(
vault::insert_cvc_using_payment_method_token(

.attach_printable("Failed to get redis connection")?;

let key = format!(
"pm_token_{}_{}_hyperswitch_cvc",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"pm_token_{}_{}_hyperswitch_cvc",
"pm_token_{}_{}_card_cvc",


let payload = payload_to_be_encrypted
.encode_to_string_of_json()
.change_context(errors::ApiErrorResponse::InternalServerError)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add attach printable

Some(fullfillment_time),
)
.await
.change_context(errors::StorageError::KVError)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this change_context needed?

.attach_printable("Failed to get redis connection")?;

let key = format!(
"pm_token_{}_{}_hyperswitch_cvc",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"pm_token_{}_{}_hyperswitch_cvc",
"pm_token_{}_{}_card_cvc",

@Sakilmostak
Copy link
Contributor Author

would be addressing these nits in a separate pr 👍

@Gnanasundari24 Gnanasundari24 added this pull request to the merge queue Jul 7, 2025
Merged via the queue into main with commit 4aca455 Jul 7, 2025
20 of 24 checks passed
@Gnanasundari24 Gnanasundari24 deleted the hs_saas_vault_confirm branch July 7, 2025 11:56
pixincreate added a commit that referenced this pull request Jul 9, 2025
…ayload-webhooks

* 'main' of github.com:juspay/hyperswitch:
  refactor(connector): Move connector mappings and endpoints to dedicated modules (#8562)
  ci(cypress): fix `hipay` test cases (#8563)
  chore(version): 2025.07.09.0
  fix(payment_method): update entity id used for Vault to global customer id (#8380)
  refactor(routing): add conditional check for invoking DE routing flows (#8559)
  feat(connector): [AUTHIPAY] Integrate cards non 3ds payments (#8266)
  ci(cypress): add payu connector (#8567)
  feat(connector): [silverflow] template code (#8553)
  chore(version): 2025.07.08.0
  feat(cypress): [worldpayvantiv] add cypress test (#8234)
  feat(connectors): [worldpayvantiv] add connector mandate support  (#8546)
  feat(connector): [Celero] add Connector Template Code (#8489)
  feat(payment-methods): create payment_token in vault confirm / do payment-confirm with temp token from session (#8525)
  ci(cypress): Add Tsys,Square cypress test (#8543)
  chore(version): 2025.07.07.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-core Area: Core flows A-payment-methods Area: Payment Methods api-v2 C-feature Category: Feature request or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants