Skip to content

feat(v2): only functional nonthrowing - #40

Merged
ChristoPy merged 6 commits into
mainfrom
feat/v2-only-functional-nonthrowing
Jul 27, 2026
Merged

feat(v2): only functional nonthrowing#40
ChristoPy merged 6 commits into
mainfrom
feat/v2-only-functional-nonthrowing

Conversation

@ChristoPy

Copy link
Copy Markdown
Contributor

Summary

Brings the ecosystem into line with docs.abacatepay.com's v2 API reference and two standing engineering rules: no classes/OOP, and no thrown exceptions — every call resolves to the exact { data, error, success } shape the API itself returns.

  • @abacatepay/types, @abacatepay/rest, @abacatepay/sdk: added the domains missing from the docs (payment links, webhooks CRUD, outbound PIX transfers, Boleto, refund/cancel/change-plan/record-usage endpoints). REST is now a createREST() factory instead of a class; nothing throws. Fixed customers.delete/coupons.delete/coupons.toggleStatus, which were using DELETE/PATCH against routes that are actually POST-only — v2 never uses DELETE or PATCH anywhere. v1 is frozen and now warns once on use.
  • @abacatepay/zod, @abacatepay/typebox: mirrored the same domain/event-taxonomy fixes into both schema libraries. Fixed a zod-only bug where APISubscription.frequency.dayOfProcessing was a sibling field instead of nested. Fixed a circular-import bug this surfaced (rest.ts needing webhook.ts before it was evaluated).
  • @abacatepay/adapters: WebhookOptions/dispatch() updated to the corrected v2 event taxonomy (onPayoutDoneonPayoutCompleted, onBillingPaid split into onCheckoutCompleted/onTransparentCompleted, plus handlers for every other documented event).
  • express, fastify, hono, elysia, supabase: Webhooks() no longer throws on a missing secret — it returns { ok, handler, error }. Removed the last classes in the repo (AbacatePayXError). Fixed a latent bug in fastify/hono where a malformed JSON body would throw uncaught inside the request handler.
  • Declined: better-auth is an unimplemented stub, out of scope. eslint-plugin lints customer code, not ours, so no-throw/no-classes weren't added there — wrong audience for internal style conventions.

Test plan

  • bunx tsc --noEmit clean in every touched package
  • bun run build clean in every touched package
  • bun test passing in every touched package (all were empty stubs before this — added real coverage)
  • bunx biome check clean across all touched packages
  • Live smoke test: AbacatePay({ secret: 'bad' }) from both @abacatepay/sdk and @abacatepay/sdk/v1 against the real API — neither throws, both resolve { data: null, error, success: false }

ChristoPy and others added 4 commits July 22, 2026 18:26
Brings @abacatepay/types, @abacatepay/rest, and @abacatepay/sdk in line with
docs.abacatepay.com's v2 reference and two standing engineering rules: no
classes/OOP, and no thrown exceptions.

- rest: class REST -> createREST() factory of closures. Nothing throws;
  network errors, exhausted retries, and missing secrets all resolve into
  the same { data, error, success } shape a real API error has.
- types: every RESTxxxData type is now the full API envelope
  (APIResponse/APIResponseWithPagination/APIResponseWithCursorBasedPagination)
  instead of an unwrapped entity, which is what lets rest stop unwrapping
  and throwing. Added the domains missing from the docs: payment links,
  webhooks CRUD, outbound PIX transfers, Boleto, and refund/cancel/
  change-plan/record-usage/delete endpoints. Fixed the webhook event
  taxonomy to match what's actually documented.
- sdk: v2 gained webhooks, paymentLinks, transfers, boleto, checkouts.refund,
  pix.list/refund, subscriptions.cancel/changePlan/recordUsage, and
  products.delete, all following the existing "reads like English" style.
  v1 is frozen and now warns once on use, pointing at v2. Fixed
  customers.delete/coupons.delete/coupons.toggleStatus, which were using
  DELETE/PATCH against routes the docs say are POST -- the v2 API never
  actually uses DELETE or PATCH, only GET and POST.
- Bumped sdk's stale ^0.0.1 dependency on @abacatepay/rest (didn't satisfy
  the workspace's 0.0.3, so it was silently resolving to an old published
  copy instead of the local package).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
Both packages mirrored the pre-update @abacatepay/types/v2 structure: no
payment-links or webhooks resource schemas, no outbound PIX transfers, no
Boleto, no subscription lifecycle (cancel/change-plan/record-usage), and the
same stale webhook event names (payout.done, billing.paid) fixed in types
last commit. This brings both in line with what that commit already did.

- Added paymentLink, webhook (APIWebhook resource), transfers, and Boleto
  schemas to both packages, plus refund endpoints and subscription lifecycle
  bodies/responses -- mirroring @abacatepay/types/v2 in each library's idiom.
- Fixed checkout create body (methods as an array, frequency/upSellProductId/
  interest/fine), the transparents/create wire envelope ({method, data}), and
  renamed RESTPatchToggleCouponStatus* to RESTPostToggleCouponStatus* to
  match the real HTTP method.
- Fixed a zod-only bug where APISubscription.frequency.dayOfProcessing was a
  sibling field instead of nested inside frequency (typebox already had this
  right).
- rest.ts in both packages now imports WebhookEventType/APIWebhook directly
  from ./webhook instead of the barrel (./.), since rest.ts is exported
  before webhook.ts in index.ts and biome's organize-imports keeps
  re-alphabetizing that order -- the barrel import worked by accident and
  broke (TDZ error) as soon as rest.ts needed something from webhook.ts.
- Added smoke tests for both packages (previously empty stubs) covering the
  new schemas.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
The dispatch layer (and its WebhookOptions handler names) was still built
around the old event names payout.done/billing.paid, which the zod package
fix in the previous commit renamed. This wires up the full v2 event list:

- onPayoutDone -> onPayoutCompleted, onBillingPaid split into
  onCheckoutCompleted and onTransparentCompleted (checkout and transparent
  charges have distinct completion payloads in v2).
- Added handlers for every other documented v2 event (checkout.refunded/
  disputed/lost, transparent.refunded/disputed/lost, subscription.*,
  transfer.*) so nothing silently falls through to onPayload only. Their
  payload type is tagged @unstable since AbacatePay doesn't document the
  `data` shape for these yet -- same caveat the zod/typebox packages already
  carry for this event set.
- Fixed a bug in the README example: dispatch() was called with the whole
  `parse()` result object instead of `parsed.data`.
- Added test coverage for dispatch() (previously an empty stub): specific
  handler routing, onPayload fallback, and an undocumented-event handler.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
Per-request handling in all 5 framework packages already returned HTTP error
responses instead of throwing -- the one holdout was Webhooks(options)
itself, which threw an AbacatePayXError class synchronously if called
without a secret. Confirmed with the user this should follow the same
never-throw rule as everything else, even though it's a setup-time check
rather than a request/response.

- Webhooks() now returns { ok: true, handler, error: null } or
  { ok: false, handler: null, error: string } instead of returning the
  handler directly or throwing. Callers destructure and check `ok`.
- Removed the AbacatePayXError classes (5 files) -- nothing throws them
  anymore, and they were the last classes left in these packages.
- Fixed a latent bug in fastify and hono: they called JSON.parse(body)
  directly in the request path with no try/catch, so a malformed JSON body
  would throw uncaught inside the handler. express and supabase already
  wrapped this; fastify and hono now match.
- Updated all 5 READMEs: the Webhooks() call site, event handler names
  (onBillingPaid/onPayoutDone -> onCheckoutCompleted/onPayoutCompleted, per
  the earlier adapters commit), and supabase's `export const POST = ...`
  example.
- Added test coverage for all 5 packages (previously empty stubs): the
  non-throwing missing-secret case, a rejected/malformed request, and a
  validly signed end-to-end dispatch.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
@ChristoPy ChristoPy self-assigned this Jul 22, 2026
Every package changed in this PR has a breaking API change (thrown errors
replaced with return values, classes replaced with factories, renamed
fields/handlers, changed request/response shapes), so each gets a major
version bump:

  rest      0.0.3 -> 1.0.0   (also gets it off 0.0.x, whose strict caret
                               semantics caused the stale-dependency bug
                               fixed in the first commit)
  types     2.0.3 -> 3.0.0
  sdk       1.2.0 -> 2.0.0
  zod       1.0.1 -> 2.0.0
  typebox   1.0.1 -> 2.0.0
  adapters  1.0.2 -> 2.0.0
  express   1.0.2 -> 2.0.0
  fastify   1.0.3 -> 2.0.0
  hono      1.0.1 -> 2.0.0
  elysia    1.0.2 -> 2.0.0
  supabase  1.0.3 -> 2.0.0

Internal dependency ranges updated to match: sdk -> rest ^1.0.0/types
^3.0.0, adapters -> zod ^2.0.0, express/fastify/hono/elysia/supabase ->
adapters ^2.0.0. Re-ran install, build, typecheck, and test across every
package afterward -- all clean.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
@ChristoPy
ChristoPy force-pushed the feat/v2-only-functional-nonthrowing branch from 133eb0a to 881f9cb Compare July 22, 2026 22:55
sdk, adapters, express, fastify, hono, elysia, and supabase all override
"paths": {} in their tsconfig, which disables the source-level path mapping
every other package inherits from tsconfig.base.json. Without that mapping,
tsc falls back to Node-style resolution requiring each dependency's built
dist/ to exist -- but CI ran `tsc -b --noEmit` right after `bun install`
with no build in between (the build step existed in this file but was
commented out with "Skip this for now", placed after the test step where it
wouldn't have helped anyway).

Confirmed by simulating a clean checkout locally: `rm -rf packages/*/dist`
then `bunx tsc -b --noEmit` produces 17 "Cannot find module" errors across
those 7 packages. `bun run build` (already an existing root script) builds
every package in correct dependency-graph order via bun's workspace filter,
which fixes it. Moved that step before "Type check"; `bun test` doesn't
need this (Bun's runtime resolver doesn't require dist), so it's unaffected.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
@ChristoPy
ChristoPy merged commit 2dc44ef into main Jul 27, 2026
2 of 3 checks passed
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.

2 participants