Skip to content

fix: add user_authorization to IntentMandate and intent binding to PaymentMandate#189

Open
ayushozha wants to merge 1 commit intogoogle-agentic-commerce:mainfrom
ayushozha:fix/mandate-security-add-intent-auth-and-payment-binding
Open

fix: add user_authorization to IntentMandate and intent binding to PaymentMandate#189
ayushozha wants to merge 1 commit intogoogle-agentic-commerce:mainfrom
ayushozha:fix/mandate-security-add-intent-auth-and-payment-binding

Conversation

@ayushozha
Copy link

Summary

  • Add user_authorization (JWT signature) field to IntentMandate for non-repudiable proof of user intent in Human Not Present (HNP) scenarios
  • Add intent_mandate_id and transaction_modality fields to PaymentMandateContents so the payment network can verify HNP transactions have proper pre-authorization
  • Update sign_mandates_on_user_device to include intent_mandate_hash when an intent mandate is present, binding the payment authorization to the user-signed intent
  • Update Go type definitions in samples/go/pkg/ap2/types/mandate.go to match

Test plan

  • Verify existing Human Present flow still works (intent_mandate not in state, so behavior unchanged)
  • Verify IntentMandate now accepts optional user_authorization field
  • Verify PaymentMandateContents now accepts optional intent_mandate_id and transaction_modality fields
  • Verify sign_mandates_on_user_device appends intent mandate hash when intent_mandate is present in state
  • Verify Go struct definitions match Python definitions

Fixes #150

🤖 Generated with Claude Code

…ymentMandate

- Add user_authorization (JWT signature) field to IntentMandate for
  non-repudiable proof of user intent in HNP scenarios
- Add intent_mandate_id and transaction_modality to PaymentMandateContents
  for payment network verification
- Update sign_mandates_on_user_device to include intent_mandate_hash
  when intent mandate is present

Fixes google-agentic-commerce#150

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ayushozha ayushozha requested a review from a team as a code owner March 18, 2026 08:29
@google-cla
Copy link

google-cla bot commented Mar 18, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the system's capability to handle Human Not Present (HNP) transactions securely. It establishes a robust framework for users to pre-authorize transactions through digitally signed intent mandates, allowing the payment network to verify these pre-authorizations for HNP payments. This ensures non-repudiable proof of user intent and tightly binds payment authorizations to specific user-signed intents, thereby increasing trust and security in automated transaction flows.

Highlights

  • Enhanced IntentMandate with User Authorization: Added a user_authorization field (JWT signature) to the IntentMandate structure, providing non-repudiable proof of user intent for Human Not Present (HNP) scenarios.
  • PaymentMandateContents for HNP Verification: Introduced intent_mandate_id and transaction_modality fields to PaymentMandateContents, enabling the payment network to verify proper pre-authorization for HNP transactions.
  • Binding Payment Authorization to Intent Mandate: Updated the sign_mandates_on_user_device function to include the intent_mandate_hash in the payment authorization when an intent mandate is present, securely binding the payment to the user-signed intent.
  • Go Type Definition Updates: Synchronized Go type definitions in samples/go/pkg/ap2/types/mandate.go to reflect the new fields added to IntentMandate and PaymentMandateContents.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces fields to support Human Not Present (HNP) transaction flows. The changes add user_authorization to IntentMandate and intent_mandate_id and transaction_modality to PaymentMandateContents. The logic for signing mandates is updated to include an intent mandate hash when available. The corresponding Go type definitions are also updated. My review focuses on improving data integrity and validation in the Pydantic models by suggesting the use of validators and enums to enforce business rules described in the field descriptions.

Comment on lines +77 to +95
user_authorization: Optional[str] = Field(
None,
description=(
"""
A base64url-encoded JSON Web Token (JWT) that digitally signs the
intent mandate contents by the user's private key. This provides
non-repudiable proof of the user's intent and prevents tampering
by the shopping agent.

If this field is present, user_cart_confirmation_required can be
set to false, allowing the agent to execute purchases in the
user's absence.

If this field is None, user_cart_confirmation_required must be true,
requiring the user to confirm each specific purchase.
"""
),
example="eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZXhhbXBsZ...",
)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The description for user_authorization states an important business rule: "If this field is None, user_cart_confirmation_required must be true". To ensure data integrity and prevent invalid model states, this rule should be enforced with a Pydantic validator.

Here is an example of how you could implement this with a model_validator:

from pydantic import model_validator

class IntentMandate(BaseModel):
    # ... existing fields ...

    @model_validator(mode='after')
    def check_user_authorization_logic(self) -> 'IntentMandate':
        if self.user_authorization is None and not self.user_cart_confirmation_required:
            raise ValueError(
                'user_cart_confirmation_required must be True if user_authorization is not provided'
            )
        return self

This will make your model more robust.

Comment on lines +176 to +185
intent_mandate_id: Optional[str] = Field(
None,
description=(
"Reference to the user-signed Intent Mandate that authorizes "
"this transaction in Human Not Present scenarios. This allows "
"the payment network to verify that the 'human not present' "
"transaction has pre-authorization support from a 'human present' "
"intent mandate. Required for HNP transactions."
),
)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The description for intent_mandate_id mentions it is "Required for HNP transactions." This is a critical validation rule that should be enforced within the model to maintain data consistency. You can use a Pydantic model_validator to ensure intent_mandate_id is provided when transaction_modality is set for a Human Not Present transaction.

Here's how you could implement this check:

from pydantic import model_validator

class PaymentMandateContents(BaseModel):
    # ... existing fields ...

    @model_validator(mode='after')
    def check_hnp_requirements(self) -> 'PaymentMandateContents':
        if self.transaction_modality == 'human_not_present' and self.intent_mandate_id is None:
            raise ValueError('intent_mandate_id is required for human_not_present transactions')
        return self

(Note: This example assumes transaction_modality is a string. If you adopt the Enum suggestion from another comment, you would compare against the enum member, e.g., TransactionModality.HUMAN_NOT_PRESENT.)

Comment on lines +186 to +193
transaction_modality: Optional[str] = Field(
None,
description=(
"Transaction modality: 'human_present' or 'human_not_present'. "
"This signals to the payment network whether the user was present "
"at the time of payment authorization."
),
)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The transaction_modality field accepts a limited set of string values: 'human_present' or 'human_not_present'. Using a plain string is susceptible to errors from typos or case variations. It's a best practice to use an Enum for such fields to enforce valid values, which improves code clarity and robustness.

You could define an Enum and use it for this field:

from enum import Enum

class TransactionModality(str, Enum):
    HUMAN_PRESENT = "human_present"
    HUMAN_NOT_PRESENT = "human_not_present"

class PaymentMandateContents(BaseModel):
    # ...
    transaction_modality: Optional[TransactionModality] = Field(
        None,
        description=(
            "Transaction modality. This signals to the payment network whether "
            "the user was present at the time of payment authorization."
        ),
    )
    # ...

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.

[Bug]: Security Inconsistencies Between Specification and Implementation in Mandate Design

1 participant