Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,70 @@

## Unreleased

- The billing family is now full inventory (`39 / 39` OpenAPI operations):
all four P0 families (webhooks, checkout, billing, billing_portal) are at
100%.
- `POST /v1/invoices/{id}/lines/{line_item_id}` updates one draft-invoice line
(`amount`, `description`, metadata) with the same totals recomputation as
`update_lines`.
- `POST /v1/invoices/{id}/attach_payment` records an attached PaymentIntent
(customer-matched) or payment record on a draft invoice as evidence;
collection still runs through `finalize`/`pay`.
- `POST /v1/subscriptions/{id}/migrate` records
`billing_mode[type]=flexible` (plus optional `proration_discounts`) as
subscription metadata evidence; flexible-billing proration recalculation is
not modeled.
- `POST /v1/payment_intents/{id}` updates intent metadata and evidenced
description; amount, currency, and status stay immutable.
- `GET /v1/credit_notes/{id}/lines` returns one line derived from the stored
credit-note amount and memo/reason.
- The OpenAPI inventory moves from `195 / 587` (`33.2%`) to `200 / 587`
(`34.1%`): billing `39 / 39`, payments `23 / 41`, payment_history `14 / 30`.
- `GET /v1/checkout/sessions/{id}/line_items` returns the session's line items
as expanded Stripe `item` objects (pre-discount amounts; per-line
discount/tax splits are not modeled).
- `POST /v1/checkout/sessions/{id}` updates an open session: metadata merge
plus `line_items[N][quantity]` overrides with immutable prices. Non-open
sessions, out-of-range indexes, and non-positive quantities are rejected.
Checkout sessions are now full inventory: `6 / 6` operations.
- `POST /v1/invoices/{id}` updates draft invoices (description, `days_until_due`,
`default_payment_method`, metadata merge) and `DELETE /v1/invoices/{id}`
deletes a draft together with its attached lines and timeline evidence.
- `POST /v1/invoices/{id}/add_lines`, `update_lines`, and `remove_lines` mutate
draft-invoice lines through the invoice-item path, recomputing
`subtotal`/`total`/`amount_due` per change.
- `DELETE /v1/products/{id}` removes local product evidence; existing prices
keep referencing the deleted product id.
- The OpenAPI inventory moves from `187 / 587` (`31.9%`) to `195 / 587`
(`33.2%`): checkout closes to `6 / 6`, billing moves to `36 / 39`, and
catalog to `28 / 54`.
- Added billing portal configurations: `GET/POST
/v1/billing_portal/configurations` and `GET/POST
/v1/billing_portal/configurations/{id}` store local portal-configuration
evidence with Stripe-shaped `business_profile`, `default_return_url`,
`login_page`, `features[...]`, and `metadata` fields, list filters for
`active`/`is_default`, and the first configuration in a run becoming the
default. The hosted portal does not render configuration features.
- `GET /v1/subscription_items` lists resolved items across subscriptions with
the `subscription` filter and the standard list envelope, and
`GET /v1/subscription_items/{id}` retrieves one item.
- `POST /v1/subscription_items/{id}` updates an item's `price` and `quantity`
(plus metadata and evidence-only `tax_rates`), routing
`proration_behavior=always_invoice/create_prorations` through the same
proration path as item create so a quantity bump with `always_invoice`
issues the prorated `subscription_update` invoice.
- `GET/DELETE /v1/customers/{id}/subscriptions/{id}/discount` now mirror the
top-level subscription discount routes and are customer-scoped: a mismatched
customer gets `404`.
- Fixed subscription discount deletion: `DELETE .../subscriptions/{id}/discount`
built a metadata copy with the discount keys removed, but subscription
metadata patches merge, so the stored discount survived its own deletion and
a following `GET` still returned it. The patch now zeroes the discount keys,
which is how merge-mode patches delete.
- Registered compat claims for already-tested routes: invoice `void`,
invoice `mark_uncollectible`, and checkout `expire`. The OpenAPI inventory
moves from `175 / 587` (`29.8%`) to `187 / 587` (`31.9%`): billing
`31 / 39`, billing_portal `5 / 5`, checkout `4 / 6`.
- Local evidence objects — coupons, promotion codes, subscription schedules,
disputes, tax rates, tax IDs and customer cash balances — are now stored in
the run's own database instead of process memory. They were the only objects
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Billtap

[![CI](https://github.com/midagedev/billtap/actions/workflows/ci.yml/badge.svg)](https://github.com/midagedev/billtap/actions/workflows/ci.yml)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
[![GHCR image](https://img.shields.io/badge/ghcr-billtap-2496ED?logo=docker&logoColor=white)](https://github.com/midagedev/billtap/pkgs/container/billtap)

Full-stack Stripe-style billing sandbox for local development, CI scenarios,
and controlled staging checks.

Expand All @@ -19,7 +23,7 @@ behavior outside the published contract.

| Surface | What it is for |
| --- | --- |
| Stripe-like API | Local customers, products, prices, coupons, promotion codes, checkout sessions, subscriptions, schedules, invoices, payment intents, cash balance, refunds, credit notes, disputes, test clocks, webhook endpoints, and events for supported billing flows. |
| Stripe-like API | Local customers, products, prices, coupons, promotion codes, tax rates, checkout sessions, subscriptions, schedules, invoices, payment intents, cash balance, refunds, credit notes, disputes, test clocks, webhook endpoints, and events for supported billing flows. |
| Hosted checkout | Browser-visible sandbox checkout for exercising app integration and deterministic payment outcomes. |
| Billing portal | Local customer portal for plan changes, seats, cancellation, resume, and payment-method update flows. |
| Developer dashboard | Billing objects, timeline, webhook delivery attempts, app responses, and debug bundle export in one place. |
Expand Down Expand Up @@ -327,13 +331,13 @@ curl -fsS "http://localhost:8080/api/diagnostics?limit=100" \
| --- | --- | --- |
| Runtime | Go server with SQLite local default | In-memory storage exists for tests |
| Frontend | React checkout, portal, and dashboard apps | Built with Vite into `dist/app` |
| Stripe-like API | Practical local subset | Customers, catalog, checkout, portal sessions, subscriptions, schedules, invoices, payment intents, cash balance, refunds, credit notes, disputes, test clocks, webhook endpoints, events, search/list projections used by tests |
| Stripe-like API | Practical local subset | Customers, catalog, checkout, portal sessions, subscriptions, schedules, invoices, payment intents, cash balance, refunds, credit notes, disputes, tax rates, test clocks, webhook endpoints, events, search/list projections used by tests |
| Webhooks | Signed delivery with reliability controls | Retry, duplicate, delay, out-of-order, grouped replay, endpoint attempts, delivery evidence, redaction |
| Scenarios | YAML runner | Local clock, app assertions, JSON/Markdown reports, exit-code policy |
| Fixtures | Apply/snapshot/assert APIs | JSON/YAML input, fixture metadata isolation, structured pass/fail reports |
| SaaS profile | Generic workspace billing profile | Plans, seats, members, export quota, extra export, payment history, support bundle, platform/connect-style webhook evidence |
| Release state | Source plus GHCR image | Local Docker image builds and GHCR image workflow; no package/Homebrew/signed binary yet |
| Stripe API inventory | `160 / 587` operations, `27.3%` L1+ | OpenAPI route inventory is schema-visible for all `587` operations; implemented coverage is tracked in `docs/STRIPE_COMPATIBILITY_90_TARGET.md` |
| Stripe API inventory | `200 / 587` operations, `34.1%` L1+ | OpenAPI route inventory is schema-visible for all `587` operations; implemented coverage is tracked in `docs/STRIPE_COMPATIBILITY_90_TARGET.md` |

Detailed compatibility matrix: `docs/COMPATIBILITY.md`.

Expand Down
2 changes: 1 addition & 1 deletion docs/API_VALIDATION_AND_ERROR_SIMULATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ Current lane:
- `go run ./cmd/billtap compatibility scorecard --output-dir dist/compatibility`
runs the offline Billtap-owned corpus and writes JSON, Markdown, and replay
bundle artifacts without requiring external Stripe services.
- `l3-public-readiness-v7` currently has 49 release-blocking cases covering the
- `l3-public-readiness-v7` currently has 50 release-blocking cases covering the
public subset's request validation, protocol parameter acceptance,
OpenAPI-backed fallback validation, idempotency mismatch, deterministic
checkout payment-error aliases, and direct PaymentIntent/SetupIntent state
Expand Down
Loading
Loading