Skip to content

Accept Google Pay amounts in minor units - #52

Merged
Max Harrison (maxharrison) merged 4 commits into
mainfrom
android-amount-minor-units
Aug 19, 2026
Merged

Accept Google Pay amounts in minor units#52
Max Harrison (maxharrison) merged 4 commits into
mainfrom
android-amount-minor-units

Conversation

@maxharrison

@maxharrison Max Harrison (maxharrison) commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

The Android Google Pay SDK can now take an amount as a whole number of a currency's smallest unit, the way our web SDKs already do. It also rejects an amount Google Pay cannot render, instead of letting it fail inside the payment sheet.

// Before: the merchant did the conversion
total = Amount("54.99")

// Now: either form works
total = Amount("54.99")
total = Amount.ofMinorUnits(5499)   // 54.99 EUR, or 5499 JPY

Part of CARD-1163. Android only; iOS and Apple Pay are untouched.

Context

evervault-pay holds the Android and iOS wallet SDKs. A merchant building a Google Pay sheet on Android gives us a Transaction, and every price in it (the total and each line item) is an Amount.

Most currencies have a minor unit 1/100th the size of the major unit: 5499 cents is 54.99 EUR. Not all do. JPY has no minor unit at all, so 5499 JPY is just 5499. KWD has three digits, so 5499 fils is 5.499 KWD. The number of digits is the currency's exponent.

Until now Amount took only a major-unit decimal string, so the merchant converted from minor units themselves and had to know each currency's exponent to do it. Our web SDKs take minor-unit integers, so a merchant on both web and Android wrote the same charge two different ways. Nothing validated the string either, so a malformed amount reached Google's client and failed there.

Source level change

Amount shipped as data class Amount(val amount: String) in android-v0.0.30. It is now a sealed class with two cases: a decimal string and a count of minor units. Constructing an Amount from Kotlin is unchanged, which is what merchant code overwhelmingly does. Reading one back is not.

Before Now Why
transaction.total.amount no replacement The formatted string depends on the transaction's currency, so nothing can expose it off the Amount alone.
amount.copy(...), destructuring, component1() no replacement Data-class members go with the data class.
new Amount("54.99") (Java) Amount.Companion.invoke("54.99") No Java consumers in this repo.

Stricter at runtime too. Amount("54.999"), Amount("-1.00") and Amount("ten") used to construct fine and then fail inside Google's sheet. They now throw IllegalArgumentException at construction, with a message naming the problem.

Ships in android-v0.0.31. Version bump still to do, per android/README.md.

How it works

  • Amount.ofMinorUnits(5499) stores the integer. The currency is applied later, when the request JSON is built, so the amount is always resolved against the transaction's own currency and there is no way to pass a second, conflicting one.
  • The exponent comes from java.util.Currency.defaultFractionDigits. BigDecimal does the shift, so the conversion is exact.
  • Amount("54.99") still constructs the same way, but now rejects anything Google's client will not accept.
  • Unknown currency codes and pseudo-currencies with no minor unit (XAU) are rejected.

Three-decimal currencies reach hundredths, not thousandths

Google Pay carries at most two fraction digits. KWD, BHD, OMR, JOD and TND have three, so they work down to hundredths of a major unit and no further:

ofMinorUnits in KWD sent to Google
1000 (1.000 KWD) "1.00" fine
1250 (1.250 KWD) "1.25" fine
1005 (1.005 KWD) throws

An amount needing the third digit is rejected rather than rounded, because rounding would charge something other than what was asked for.

Existing integrations are unaffected. The currency is only consulted for the minor-units case, so Amount("1.00") with KWD behaves exactly as before. The limit only applies to ofMinorUnits, which is new API.

Establishing the real two-digit limit (device and browser testing)

Google's docs give totalPrice as ^[0-9]+(\.[0-9][0-9])?$. That is not the rule the client applies. Play Services returns the real one in the error for an over-precise display item:

PaymentDataRequest.transactionInfo.displayItems[0].price
  should match regex "-?[0-9]*(\.[0-9][0-9]?)?"

So one fraction digit is accepted, and negatives are accepted on display items. The two-digit ceiling is real.

Tested on a real device and against pay.js in Chrome:

currency 3 digits 2 digits
USD 10.123 fails 10.12 completes
KWD 1.000 fails 1.00 completes
BHD 1.500 fails 1.50 completes
OMR 2.345 fails 2.34 completes
TND 9.750 fails 9.75 completes

Each currency completing at two digits rules out the currency itself being unsupported. 10.5 completes, confirming one digit is fine. A legal total with an over-precise line item also fails, so displayItems[].price is validated the same way.

An over-precise price is not rejected when the request is built. It opens the sheet and fails there: statusCode 10 (DEVELOPER_ERROR) with an empty message on Android, and an OR_BIBED_06 dialog on web reading "This merchant is having trouble accepting your payment right now". Validating in the SDK turns that into an error an integrator can act on.

@ana-maksimovskikh

Copy link
Copy Markdown
Contributor

Google might not accept newly formatted strings, it required exactly 0 or 2 digits after decimal point

worth doublechecking

https://developers.google.com/pay/api/android/reference/request-objects#TransactionInfo

@maxharrison
Max Harrison (maxharrison) force-pushed the android-amount-minor-units branch 2 times, most recently from 7663bd7 to 4b601f2 Compare August 18, 2026 09:33
@maxharrison
Max Harrison (maxharrison) marked this pull request as ready for review August 18, 2026 13:03
@maxharrison
Max Harrison (maxharrison) requested a review from a team as a code owner August 18, 2026 13:03
Comment thread android/googlepay/build.gradle.kts Outdated

@ana-maksimovskikh ana-maksimovskikh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

.

Comment thread android/googlepay/src/main/java/com/evervault/googlepay/Amount.kt

@ana-maksimovskikh ana-maksimovskikh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A few nits, but overall looks good. Thanks Max 👍

@maxharrison
Max Harrison (maxharrison) merged commit cd52b06 into main Aug 19, 2026
4 checks passed
@maxharrison
Max Harrison (maxharrison) deleted the android-amount-minor-units branch August 19, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants