Skip to content

fix(sdk): convert payment.budget max to minor units per ISO 4217 - #377

Open
baaakgun4543 wants to merge 1 commit into
google-agentic-commerce:mainfrom
baaakgun4543:fix/budget-minor-units
Open

baaakgun4543 wants to merge 1 commit into
google-agentic-commerce:mainfrom
baaakgun4543:fix/budget-minor-units

Conversation

@baaakgun4543

Copy link
Copy Markdown

Description

payment.budget is the cumulative cap for recurring agent payments. BudgetEvaluator converts the budget's max to minor units with int(self.constraint.max * 100). The spec example uses a major-unit value ("max": 1000.00), and payment_amount.amount is in integer minor units per ISO 4217. That conversion fails in two ways.

1. The cap is too loose for zero-decimal currencies (fail-open). JPY, KRW and others have no minor unit, so * 100 raises the cap 100x. A Budget(max=1000, currency='JPY') admits a payment of 100,000 JPY.

2. Payments exactly at the cap are rejected (fail-closed).

  • Binary float truncation: int(19.99 * 100) == 1998, so a 19.99 USD budget rejects a 1999-cent payment. Likewise int(0.29 * 100) == 28.
  • Three-decimal currencies: a 1.5 KWD budget becomes 150 instead of 1500, so a 1,500-fils payment is rejected.

Change

  • minor_unit_exponent(currency) returns the ISO 4217 exponent. The default is 2. Exceptions:
    • 0: BIF CLP DJF GNF ISK JPY KMF KRW PYG RWF UGX UYI VND VUV XAF XOF XPF
    • 3: BHD IQD JOD KWD LYD OMR TND
    • 4: CLF UYW
  • major_to_minor_units(value, currency) uses Decimal(str(value)) and the exponent. It floors fractions of a minor unit so rounding can never raise the cap.
  • BudgetEvaluator uses the helper. The violation message is unchanged apart from reporting the correct limit.

Whole-number USD budgets behave exactly as before (e.g. Budget(max=15.0, currency='USD') is 1500 cents), and all existing tests pass.

Tests

New tests in constraints_tests.py:

  • test_payment_budget_decimal_boundary_usd
  • test_payment_budget_iso4217_minor_units, parametrized over JPY (at and over the cap), KRW (at the cap), KWD (at and over the cap) and EUR 0.29.

4 of the 7 new cases fail on main. The other 3 pass before and after, as regression guards.

Full suite in a clean environment with the pinned dependencies (Python 3.12):

Passed Failed
main 186 2
This change 193 2

The 2 failures are kb_sd_jwt_intermediate_tests.py::test_verify_rejects_aud_mismatch and ::test_verify_rejects_nonce_mismatch. They fail identically on main and are unrelated to this change. The change adds no ruff check findings beyond those already on main.

Notes for reviewers

  • The exponent table is inlined to avoid a new dependency. Happy to switch to a maintained source if preferred.

  • payment.amount_range already takes integer minor units (per its schema) and is unaffected.

  • The spec could state explicitly that payment.budget.max is in major units, as its example implies.

  • Follow the CONTRIBUTING Guide.

  • Make your Pull Request title in the https://www.conventionalcommits.org/ specification.

  • Ensure the tests and linter pass (see the note above on the 2 failures already present on main).

  • Appropriate docs were updated (if necessary): no doc change. A spec clarification is suggested above.

BudgetEvaluator converted the major-unit budget with int(max * 100). For zero-decimal currencies this raised the cap 100x (a 1000 JPY budget admitted 100000 JPY), and binary-float truncation rejected payments exactly at the cap (19.99 USD -> 1998 cents). Convert with exact decimal arithmetic and the ISO 4217 minor-unit exponent, flooring fractions so rounding can never raise the cap.
@baaakgun4543

Copy link
Copy Markdown
Author

Note on the failing spellcheck check: every flagged word is already present in the touched files on main. They are PISP, Pisp, pisp, Pisps, otherpisp (the PISP identifiers) and SECP (from SECP256R1). This PR adds none of them. The workflow checks every word in a changed file, and these terms are missing from .cspell/custom-words.txt, so any change to constraints.py or its tests trips it.

#379 adds those terms to the dictionary. Once it is merged, re-running spellcheck here should pass.

@baaakgun4543

Copy link
Copy Markdown
Author

After opening this PR I found that #368 already addresses the same budget conversion bug. I found it independently.

The differences:

  • this PR covers the full set of ISO 4217 zero-decimal currencies (17, including KRW, VND, CLP);
  • it adds boundary tests for a three-decimal currency (KWD 1.5 at and over the cap) and for float truncation (EUR 0.29).

If maintainers prefer #368, I'm happy to close this one and help port those tests over.

This branch has not been deployed

No deployments
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.

1 participant