Skip to content

feat(connector): [Checkout] Add Google Pay Predecrypt Flow#9130

Merged
likhinbopanna merged 14 commits intomainfrom
checkout-google-pay
Sep 26, 2025
Merged

feat(connector): [Checkout] Add Google Pay Predecrypt Flow#9130
likhinbopanna merged 14 commits intomainfrom
checkout-google-pay

Conversation

@Anurag-05-prog
Copy link
Contributor

@Anurag-05-prog Anurag-05-prog commented Sep 1, 2025

Type of Change

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

Closes this issue

Description

Added Google Pay Predecrypt Flow

Additional Changes

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

Motivation and Context

How did you test it?

  1. Payments - Create

Request:

curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_lCkY35u2evZqwny1tXdtyZuu7VufIiJjFTJhuI0kMJAP8RL9XmyomvPqQDBzDvo1' \
--data-raw '{
    "amount": 7445,
    "currency": "USD",
    "confirm": true,
    "business_country": "US",
    "business_label": "default",
    "amount_to_capture": 7445,
    "customer_id": "cu_1756821388",
    "capture_method": "automatic",
     "customer_acceptance": {
        "acceptance_type": "offline",
        "accepted_at": "1963-05-03T04:07:52.723Z",
        "online": {
            "ip_address": "in sit",
            "user_agent": "amet irure esse"
        }
    },
    "capture_on": "2022-09-10T10:11:12Z",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
    "email": "something@gmail.com",
    "name": "Joseph Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
    "statement_descriptor_name": "Juspay",
    "statement_descriptor_suffix": "Router",
    "payment_method": "wallet",
    "payment_method_type": "google_pay",
    "billing": {
        "address": {
            "line1": "1467",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "payment_method_data": {
        "wallet": {
            "google_pay": {
                "type": "CARD",
                "description": "SuccessfulAuth: Visa •••• 4242",
                "info": {
                    "assurance_details": {
                        "account_verified": true,
                        "card_holder_authenticated": false
                    },
                    "card_details": "4242",
                    "card_network": "MASTERCARD"
                },
                "tokenization_data": {
                    "application_primary_account_number": "4242424242424242",
                    "card_exp_month": "10",
                    "card_exp_year": "25",
                    "cryptogram": CRYPTOGRAM
                }
            }
        }
    }
}'

Response:

{
    "payment_id": "pay_kZOyskQtl91kB3oiAaFS",
    "merchant_id": "merchant_1756821155",
    "status": "processing",
    "amount": 7445,
    "net_amount": 7445,
    "shipping_cost": null,
    "amount_capturable": 7445,
    "amount_received": null,
    "connector": "checkout",
    "client_secret": "pay_kZOyskQtl91kB3oiAaFS_secret_1VaHJKidL1jl9DftOx0Z",
    "created": "2025-09-02T13:53:01.094Z",
    "currency": "USD",
    "customer_id": "cu_1756821181",
    "customer": {
        "id": "cu_1756821181",
        "name": "Joseph Doe",
        "email": "something@gmail.com",
        "phone": "999999999",
        "phone_country_code": "+65"
    },
    "description": "Its my first payment request",
    "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": "wallet",
    "payment_method_data": {
        "wallet": {
            "google_pay": {
                "last4": "4242",
                "card_network": "MASTERCARD",
                "type": "CARD"
            }
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": null,
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": null,
            "line3": null,
            "zip": "94122",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": null
    },
    "order_details": null,
    "email": "something@gmail.com",
    "name": "Joseph Doe",
    "phone": "999999999",
    "return_url": "https://google.com/",
    "authentication_type": "three_ds",
    "statement_descriptor_name": "Juspay",
    "statement_descriptor_suffix": "Router",
    "next_action": null,
    "cancellation_reason": null,
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": null,
    "payment_experience": null,
    "payment_method_type": "google_pay",
    "connector_label": "checkout_US_default",
    "business_country": "US",
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": {
        "customer_id": "cu_1756821181",
        "created_at": 1756821181,
        "expires": 1756824781,
        "secret": "epk_39573ad4636840e2a30021f811f4a9c4"
    },
    "manual_retry_allowed": false,
    "connector_transaction_id": "pay_ddjiyc5cghrernmk7gqlpnua6e",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "gateway_system": "direct"
    },
    "reference_id": "pay_kZOyskQtl91kB3oiAaFS_1",
    "payment_link": null,
    "profile_id": "pro_YQn8ejoAyLrvevnCJLnZ",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_hkY02nmI5cs4ygBEbIUg",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-09-02T14:08:01.094Z",
    "fingerprint": null,
    "browser_info": null,
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2025-09-02T13:53:02.364Z",
    "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,
    "force_3ds_challenge": false,
    "force_3ds_challenge_trigger": false,
    "issuer_error_code": null,
    "issuer_error_message": null,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null,
    "enable_partial_authorization": null
}
  1. Payments - Retrieve

Request:

curl --location 'http://localhost:8080/payments/pay_kZOyskQtl91kB3oiAaFS?force_sync=true' \
--header 'Accept: application/json' \
--header 'api-key: dev_lCkY35u2evZqwny1tXdtyZuu7VufIiJjFTJhuI0kMJAP8RL9XmyomvPqQDBzDvo1'

Response:

{
    "payment_id": "pay_kZOyskQtl91kB3oiAaFS",
    "merchant_id": "merchant_1756821155",
    "status": "succeeded",
    "amount": 7445,
    "net_amount": 7445,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 7445,
    "connector": "checkout",
    "client_secret": "pay_kZOyskQtl91kB3oiAaFS_secret_1VaHJKidL1jl9DftOx0Z",
    "created": "2025-09-02T13:53:01.094Z",
    "currency": "USD",
    "customer_id": "cu_1756821181",
    "customer": {
        "id": "cu_1756821181",
        "name": "Joseph Doe",
        "email": "something@gmail.com",
        "phone": "999999999",
        "phone_country_code": "+65"
    },
    "description": "Its my first payment request",
    "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": "wallet",
    "payment_method_data": {
        "wallet": {
            "google_pay": {
                "last4": "4242",
                "card_network": "MASTERCARD",
                "type": "CARD"
            }
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": null,
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": null,
            "line3": null,
            "zip": "94122",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": null
    },
    "order_details": null,
    "email": "something@gmail.com",
    "name": "Joseph Doe",
    "phone": "999999999",
    "return_url": "https://google.com/",
    "authentication_type": "three_ds",
    "statement_descriptor_name": "Juspay",
    "statement_descriptor_suffix": "Router",
    "next_action": null,
    "cancellation_reason": null,
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": null,
    "payment_experience": null,
    "payment_method_type": "google_pay",
    "connector_label": "checkout_US_default",
    "business_country": "US",
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": null,
    "manual_retry_allowed": false,
    "connector_transaction_id": "pay_ddjiyc5cghrernmk7gqlpnua6e",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "gateway_system": "direct"
    },
    "reference_id": "pay_kZOyskQtl91kB3oiAaFS_1",
    "payment_link": null,
    "profile_id": "pro_YQn8ejoAyLrvevnCJLnZ",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_hkY02nmI5cs4ygBEbIUg",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-09-02T14:08:01.094Z",
    "fingerprint": null,
    "browser_info": null,
    "payment_channel": null,
    "payment_method_id": "pm_LSM4mcvLDbufpOlCjb1X",
    "network_transaction_id": null,
    "payment_method_status": "inactive",
    "updated": "2025-09-02T13:53:11.202Z",
    "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,
    "force_3ds_challenge": false,
    "force_3ds_challenge_trigger": false,
    "issuer_error_code": null,
    "issuer_error_message": null,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null,
    "enable_partial_authorization": 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

@Anurag-05-prog Anurag-05-prog self-assigned this Sep 1, 2025
@Anurag-05-prog Anurag-05-prog requested a review from a team as a code owner September 1, 2025 11:34
@semanticdiff-com
Copy link

semanticdiff-com bot commented Sep 1, 2025

Review changes with  SemanticDiff

Changed Files
File Status
  crates/hyperswitch_connectors/src/connectors/checkout/transformers.rs  43% smaller
  crates/router/src/core/payments.rs  13% smaller
  config/config.example.toml Unsupported file format
  config/deployments/integration_test.toml Unsupported file format
  config/deployments/production.toml Unsupported file format
  config/deployments/sandbox.toml Unsupported file format
  config/development.toml Unsupported file format
  config/docker_compose.toml Unsupported file format
  crates/router/src/configs/settings.rs  0% smaller
  loadtest/config/development.toml Unsupported file format

@Anurag-05-prog Anurag-05-prog linked an issue Sep 1, 2025 that may be closed by this pull request
2 tasks
@Anurag-05-prog Anurag-05-prog requested review from a team as code owners September 2, 2025 13:52
expiry_month: Secret<String>,
expiry_year: Secret<String>,
eci: Option<String>,
cryptogram: Secret<String>,
Copy link
Contributor

Choose a reason for hiding this comment

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

why cryptogram is mandatory? it might not be present in a decrypted token

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made it an option.

Comment on lines +6913 to +6916
) => !matches!(
google_pay_pre_decrypt_flow_filter,
Some(GooglePayPreDecryptFlow::NetworkTokenization)
),
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of not operation over network tokenization, can we just do a match over connector tokenization. if tomorrow a new variant comes it, that is not needed to be handled in this function

google_pay_pre_decrypt_flow_filter,
Some(GooglePayPreDecryptFlow::NetworkTokenization)
),
_ => true,
Copy link
Contributor

Choose a reason for hiding this comment

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

dont use a wildcard matching, explicitly put all the cases or you can use a if let statement in this case

Sakilmostak
Sakilmostak previously approved these changes Sep 8, 2025
Comment on lines +342 to +373
PaymentMethodToken::GooglePayDecrypt(google_pay_decrypted_data) => {
let token = google_pay_decrypted_data
.application_primary_account_number
.clone();
let expiry_month = google_pay_decrypted_data
.get_expiry_month()
.change_context(errors::ConnectorError::InvalidDataFormat {
field_name: "payment_method_data.card.card_exp_month",
})?;
let expiry_year = google_pay_decrypted_data
.get_four_digit_expiry_year()
.change_context(errors::ConnectorError::InvalidDataFormat {
field_name: "payment_method_data.card.card_exp_year",
})?;
let cryptogram =
Some(google_pay_decrypted_data.cryptogram.clone().ok_or_else(
|| errors::ConnectorError::MissingRequiredField {
field_name: "cryptogram",
},
)?);
Ok(PaymentSource::GooglePayPredecrypt(Box::new(
GooglePayPredecrypt {
_type: "network_token".to_string(),
token,
token_type: "googlepay".to_string(),
expiry_month,
expiry_year,
eci: Some("06".to_string()),
cryptogram,
},
)))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: you can further break it down to try_from

Some(GooglePayPreDecryptFlow::ConnectorTokenization)
)
} else {
true
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add comment why this should be true

token_type: "googlepay".to_string(),
expiry_month,
expiry_year,
eci: Some("06".to_string()),
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we hardcoding here ?

@hyperswitch-bot hyperswitch-bot bot added M-database-changes Metadata: This PR involves database schema changes and removed M-database-changes Metadata: This PR involves database schema changes labels Sep 15, 2025
awasthi21
awasthi21 previously approved these changes Sep 16, 2025
Sakilmostak
Sakilmostak previously approved these changes Sep 16, 2025
Comment on lines +6977 to +6980
matches!(
google_pay_pre_decrypt_flow_filter,
Some(GooglePayPreDecryptFlow::ConnectorTokenization)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Won't this logic affect other connectors that have a Google Pay decrypt flow?

If the connector and payment method are added under the tokenization environment config, it implies that we should follow connector tokenization for that specific connector and payment method combination.

For example:

checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization" }

This means: follow connector tokenization for all wallet payment methods under the checkout connector.

If the goal is to skip connector tokenization for a specific wallet flow, we explicitly set the value as network_tokenization. If it is not specified, or if it is set as connector_tokenization, we follow the default connector tokenization behavior. We usually don’t explicitly specify connector_tokenization, because the absence of a network_tokenization override already implies it.

But according to the above match statement the logic only triggers the connector tokenization flow if the environment explicitly specifies connector_tokenization. Ideally, this should also include the case when the value is None, i.e., either it's not present or explicitly set as connector_tokenization — both should lead to connector tokenization being followed.

In simpler terms, we should only skip connector tokenization if the env value is explicitly network_tokenization. Otherwise (if it's None or connector_tokenization), we should proceed with connector tokenization.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, reverting the change.

[tokenization]
stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } }
checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization" }
checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization", google_pay_pre_decrypt_flow = "network_tokenization" }
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we move the logic of determining the flow of payment method type for a connector—based on the payment method data—to the ConnectorSpecifications trait?

cc: @jarnura

Copy link
Member

Choose a reason for hiding this comment

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

yes, ConnectorSpecification is the way-forward to go.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, taking this in a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please create an issue for this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ShankarSinghC issue created - #9491

Copy link
Contributor

@ShankarSinghC ShankarSinghC left a comment

Choose a reason for hiding this comment

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

The changes LGTM
As this is the core flow change test google pay payment for few prod connectors as well

awasthi21
awasthi21 previously approved these changes Sep 22, 2025
ShankarSinghC
ShankarSinghC previously approved these changes Sep 22, 2025
Sakilmostak
Sakilmostak previously approved these changes Sep 22, 2025
@Gnanasundari24 Gnanasundari24 added this pull request to the merge queue Sep 23, 2025
github-merge-queue bot pushed a commit that referenced this pull request Sep 23, 2025
Co-authored-by: Anurag Singh <anurag.singh.001@Anurag-Singh-WPMHJ5619X.local>
Co-authored-by: Anurag Singh <anurag.singh.001@MacBookPro.lan>
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Sep 23, 2025
@likhinbopanna likhinbopanna added this pull request to the merge queue Sep 24, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to a conflict with the base branch Sep 24, 2025
awasthi21
awasthi21 previously approved these changes Sep 25, 2025
@likhinbopanna likhinbopanna added this pull request to the merge queue Sep 26, 2025
Merged via the queue into main with commit 96e44a7 Sep 26, 2025
21 of 25 checks passed
@likhinbopanna likhinbopanna deleted the checkout-google-pay branch September 26, 2025 08:09
pixincreate added a commit that referenced this pull request Sep 30, 2025
…pay-new-field

* 'main' of github.com:juspay/hyperswitch: (21 commits)
  feat(payments): add tokenization action handling to payment flow for braintree (#9506)
  feat(connector): [Loonio] Add template code (#9586)
  fix(connector): [paysafe] make `eci_indicator` field optional (#9591)
  fix(core): add should_call_connector_customer function to connector specification (#9569)
  feat(ucs): Add profile ID to lineage tracking in Unified Connector Service (#9559)
  feat(core): Add support for partial auth in proxy payments [V2] (#9503)
  chore(version): 2025.09.30.0
  fix(authorizedotnet): refund via ucs missing connector_metadata (#9581)
  feat(auth): add new authentication to communicate between microservices (#9547)
  Fix: Ideal Giropay Country Currency Config (#9552)
  feat(connector): [ACI] cypress added (#9502)
  feat(connector): Add Peachpayments Cypress (#9573)
  chore(version): 2025.09.29.0
  feat(finix): template code (#9557)
  feat(cypress): add cypress test-cases for manual retry (#9505)
  feat(core): update additional payment method data in psync response (#9519)
  feat(connector): [Checkout] Add Google Pay Predecrypt Flow (#9130)
  feat(framework): Added smithy, smithy-core and smithy-generator crates (#9249)
  fix(core): add request_extended_authorization in the payment attempt and populate it in the payment response (#9492)
  chore(version): 2025.09.26.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.

[FEATURE] Checkout: Add Google Pay Predecrypt Flow

6 participants