Skip to content

Add decimal_string support with vendored Decimal type#2617

Merged
jar-stripe merged 16 commits intomasterfrom
jar/decimal-tier2-node
Mar 24, 2026
Merged

Add decimal_string support with vendored Decimal type#2617
jar-stripe merged 16 commits intomasterfrom
jar/decimal-tier2-node

Conversation

@jar-stripe
Copy link
Contributor

@jar-stripe jar-stripe commented Mar 20, 2026

Why?

The Stripe API uses decimal_string fields — high-precision decimal values that travel as JSON strings on the wire to avoid floating-point loss. This PR adds a Stripe.Decimal type and bidirectional coercion so these fields are transparent to SDK users: pass a Stripe.Decimal in, get a Stripe.Decimal back.

Coercion fires for both v1 and v2 methods. The codegen-emitted requestSchema/responseSchema metadata drives which fields get coerced; methods without decimal fields are unaffected. The apiMode === "v2" guard in StripeResource.ts has been removed — coercion now fires whenever a schema is present, matching Tier 1 SDK behavior (Java, Go, .NET).

What?

Runtime (hand-written):

  • src/Decimal.ts: Vendored from the Stripe-authored apps-extensibility-sdk. Uses BigInt coefficient × 10^exponent for arbitrary precision with zero external dependencies. Branded TypeScript type (__decimalBrand). Frozen instances. Full arithmetic (+, -, ×, ÷), comparison, and formatting methods.
  • src/Types.d.ts: Adds {kind: 'decimal_string'} to V2RuntimeSchema discriminated union.
  • src/V2Coercion.ts: Adds decimal_string coercion in coerceV2ResponseData (Decimal.from(value)) and coerceV2RequestData (duck-type detect Decimal → .toString()). Decimal plugs into the existing int64 coercion pipeline.
  • src/StripeResource.ts: Removes apiMode === "v2" guard — coercion fires whenever spec.requestSchema/spec.responseSchema is present.
  • src/stripe.cjs.node.ts / src/stripe.cjs.worker.ts / src/stripe.esm.node.ts / src/stripe.esm.worker.ts: Export Decimal for both CJS and ESM consumers.
  • types/lib.d.ts: Manual Stripe.Decimal interface declaration (instance methods + static from()/zero), RoundDirection, DecimalRoundingOptions, DecimalRoundingPresets, isDecimal(), DEFAULT_DIV_PRECISION.
  • tsconfig.cjs.json: Bumped target from es2017 to es2020 for BigInt literal support.

Generated:

  • All methods with decimal_string fields in request params or response (v1 and v2) now emit requestSchema/responseSchema entries with {kind: 'decimal_string'}.
  • primitiveDecimalType changed from string to Decimal, so TypeScript types use Stripe.Decimal instead of string for these fields.

See Also

Changelog

  • ⚠️ Breaking change: All decimal_string fields changed type from string to Stripe.Decimal in both request params and response objects. Code that reads or writes these fields as string will need to use Stripe.Decimal instead (construct via Decimal.from("1.23"), serialize via .toString()). Affected fields across v1 and v2 APIs:
    • Checkout.Session: currency_conversion.fx_rate
    • Climate.Order: metric_tons; Climate.Product: metric_tons_available
    • CreditNoteLineItem: unit_amount_decimal
    • InvoiceItem: quantity_decimal, unit_amount_decimal
    • InvoiceLineItem: quantity_decimal, unit_amount_decimal
    • Issuing.Authorization / Issuing.Transaction (and TestHelpers): quantity_decimal, unit_cost_decimal, gross_amount_decimal, local_amount_decimal, national_amount_decimal
    • Plan: amount_decimal, flat_amount_decimal, unit_amount_decimal
    • Price: unit_amount_decimal, flat_amount_decimal (including currency_options and tiers)
    • V2.Core.Account / V2.Core.AccountPerson: percent_ownership
    • Request params on Invoice, Product, Quote, Subscription, SubscriptionItem, SubscriptionSchedule, PaymentLink: unit_amount_decimal, flat_amount_decimal, quantity_decimal (where applicable)

jar-stripe and others added 3 commits March 20, 2026 10:45
Sources:
- apps-extensibility-sdk/libs/apps-extensibility-sdk/src/stdlib/decimal.ts
- apps-extensibility-sdk/libs/apps-extensibility-sdk/src/stdlib/decimal.test.ts

Adapts the source for stripe-node:
- Inlines brand symbol declarations (no external brand.ts dependency)
- Ports tests from vitest (expectTypeOf) to Mocha/Chai
- Bumps tsconfig.cjs.json target from es2017 to es2020 to allow BigInt
  literals (Node.js 18+ supports BigInt natively; engines field requires >=18)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
- src/Types.d.ts: Add decimal_string kind to V2RuntimeSchema union,
  alongside the existing int64_string kind.

- src/V2Int64.ts: Add Decimal import and decimal_string cases to both
  coercion functions:
  * coerceV2RequestData: duck-type detects Decimal instances (toFixed +
    isZero methods) and calls .toString() for wire format
  * coerceV2ResponseData: converts string API values to Decimal instances
    using Decimal.from(value)

- test/V2Decimal.spec.ts: Tests covering decimal_string deserialization,
  serialization, nullable/array/object/nested structures, precision
  preservation, full round-trip behavior, and int64_string non-regression.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
- src/stripe.cjs.node.ts, src/stripe.cjs.worker.ts: export Decimal so
  users can do `import { Decimal } from 'stripe'` or reference it as
  `require('stripe').Decimal`

- types/lib.d.ts: declare Stripe.Decimal interface + namespace (with
  static from() / zero) and Stripe.RoundingMode type in the Stripe
  namespace, following the same pattern as Stripe.V2.Amount

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
@jar-stripe jar-stripe requested a review from a team as a code owner March 20, 2026 23:10
@jar-stripe jar-stripe requested review from Copilot and mbroshi-stripe and removed request for a team March 20, 2026 23:10
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class support for Stripe V2 decimal_string fields by introducing a vendored arbitrary-precision Decimal implementation and integrating it into the existing V2 request/response coercion pipeline.

Changes:

  • Adds a new vendored src/Decimal.ts implementation (BigInt coefficient × 10^exponent) plus tests.
  • Extends V2RuntimeSchema with { kind: 'decimal_string' } and coerces request/response values via Decimal.
  • Exposes Decimal from the CommonJS entrypoints and updates TS declarations; bumps CJS TS target to es2020 for BigInt literal support.

Reviewed changes

Copilot reviewed 7 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Decimal.ts Adds vendored arbitrary-precision Decimal implementation using BigInt.
src/Types.d.ts Extends V2 runtime schema union to include decimal_string.
src/V2Int64.ts Adds request/response coercion logic for decimal_string using Decimal.
src/stripe.cjs.node.ts Exports Decimal from the CJS node entrypoint.
src/stripe.cjs.worker.ts Exports Decimal from the CJS worker entrypoint.
types/lib.d.ts Adds Stripe.Decimal / Stripe.RoundingMode type surface and docs.
tsconfig.cjs.json Bumps TS target to es2020 to support BigInt literals in emitted JS.
test/Decimal.spec.ts Adds unit tests for Decimal parsing/arithmetic/formatting.
test/V2Decimal.spec.ts Adds coercion pipeline tests for decimal_string schemas and round-trips.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

jar-stripe and others added 2 commits March 20, 2026 18:31
…urce

Schema coercion now fires for any method that has a requestSchema or
responseSchema, regardless of apiMode. V1 methods without schemas are
unaffected. This matches Tier 1 SDK behavior (Java, Go, .NET) which
coerce decimal fields unconditionally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
…ma for v1 and v2 methods

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Copy link
Member

@xavdid-stripe xavdid-stripe left a comment

Choose a reason for hiding this comment

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

couple of small things but it looks good overall!

jar-stripe and others added 7 commits March 24, 2026 14:48
…, error handling

- Update vendored Decimal.ts with full IEEE 754-2019 rounding directions
  (7 modes), explicit required params on div/toFixed, round() method,
  rounding presets, isDecimal(), and DEFAULT_DIV_PRECISION
- Inline brand symbols (no external brand.ts in stripe-node)
- Add Decimal export to ESM entrypoints for CJS/ESM parity
- Wrap Decimal.from() in V2Int64.ts with contextual error message
- Update types/lib.d.ts to match new Decimal API surface

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
… compat

The stripe-node prettier version doesn't support ES2022 # private class
fields. Convert to TypeScript `private readonly` to unblock formatting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
File now handles both int64_string and decimal_string coercion,
so the name should reflect its broader scope.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@jar-stripe jar-stripe enabled auto-merge (squash) March 24, 2026 22:08
jar-stripe and others added 4 commits March 24, 2026 15:18
- Add eslint-disable for __brand/__stripeType naming convention
- Replace ++ with += 1 and = with /= operator assignment
- Replace nested ternary with if/else

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
- Replace RoundingMode.HALF_UP/HALF_EVEN with 'half-up'/'half-even' strings
- Make div() and toFixed() direction param explicit (no longer optional)
- Update V1 coercion test to expect coercion (v2 guard was removed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
@jar-stripe jar-stripe merged commit af7c994 into master Mar 24, 2026
9 checks passed
@jar-stripe jar-stripe deleted the jar/decimal-tier2-node branch March 24, 2026 23:02
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.

3 participants