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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

- Added a manual invoice-backed one-time payment flow for local SaaS usage
charges, including `POST /v1/invoices`, `POST /v1/invoiceitems`,
`POST /v1/invoices/{id}/finalize`, metadata preservation, expanded
payment-intent evidence, and customer-level default outcomes for invoice pay.
- Accepted Stripe-compatible `proration_date` on subscription updates and
retained subscription update billing/proration parameters as local evidence.
- Added customer-level default PaymentIntent outcomes so fixture-seeded
customers can drive confirmed one-time payment failures without changing
app-created PaymentIntent metadata.
Expand Down
2 changes: 1 addition & 1 deletion cmd/billtap/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestRunCompatibilityWritesScorecard(t *testing.T) {
if !fileContains(t, filepath.Join(dir, "compatibility-scorecard.json"), `"passed": true`) {
t.Fatalf("JSON scorecard missing passed true")
}
if !fileContains(t, filepath.Join(dir, "compatibility-scorecard.json"), `"release_blocking": 49`) {
if !fileContains(t, filepath.Join(dir, "compatibility-scorecard.json"), `"release_blocking": 50`) {
t.Fatalf("JSON scorecard missing expected release-blocking count")
}
if !fileContains(t, filepath.Join(dir, "compatibility-scorecard.md"), "# Compatibility Scorecard") {
Expand Down
13 changes: 7 additions & 6 deletions docs/COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,11 @@ using the unprefixed service URL.
| Checkout sessions | `POST /v1/checkout/sessions`, `GET /v1/checkout/sessions`, `GET /v1/checkout/sessions/{id}` | Supported | Creates subscription-mode sandbox checkout sessions from request line items and hosted Billtap URLs. The Stripe-style session response leaves `line_items` unexpanded. Accepts Stripe SDK form params `allow_promotion_codes`, `discounts[0][coupon]`, `discounts[0][promotion_code]`, and `subscription_data[trial_period_days]`; trial checkout creates local `trialing` subscription evidence. Hosted URLs use the request host by default, or `BILLTAP_PUBLIC_BASE_URL` when configured for container-to-host browser flows. `PUBLIC_BASE_PATH`, `BILLTAP_PUBLIC_BASE_PATH`, and `X-Forwarded-Prefix` are reflected in hosted browser URLs. |
| Checkout completion | `POST /v1/checkout/sessions/{id}/complete`, `POST /api/checkout/sessions/{id}/complete` | Billtap-specific | Completes a sandbox checkout and creates subscription, invoice, payment intent, timeline, and checkout webhook evidence. Supports success plus deterministic failure aliases such as `card_declined`, `insufficient_funds`, `expired_card`, `incorrect_cvc`, `processing_error`, `authentication_required`, `payment_pending`, `canceled`, and documented Stripe test PaymentMethod IDs such as `pm_card_visa_chargeDeclined`. |
| Billing portal sessions | `POST /v1/billing_portal/sessions` | Partial | Returns a Stripe-like `billing_portal.session` object and Billtap portal URL for a known customer, including `flow`, `locale`, `on_behalf_of`, `return_url`, and `livemode` fields. Accepts `customer`, `return_url`, optional `configuration`, `locale`, `on_behalf_of`, and `flow_data`, with enum and required nested-field validation for the modeled portal flows. The hosted portal can simulate saved payment-method updates and subscription cancellation, then redirect to `return_url`; full Stripe-hosted portal configuration rendering is not modeled. |
| Subscriptions | `POST /v1/subscriptions`, `GET /v1/subscriptions`, `GET /v1/subscriptions/search`, `GET /v1/subscriptions/{id}`, `POST /v1/subscriptions/{id}`, `DELETE /v1/subscriptions/{id}`, `POST /v1/subscriptions/{id}/resume`, nested `GET/POST/DELETE /v1/customers/{id}/subscriptions...`, `GET/DELETE /v1/subscriptions/{id}/discount` | Partial | Create/list/retrieve subscriptions through the local checkout-completion state path. Update supports item replacement, metadata merge, single coupon or promotion-code discount application, `test_clock`, `cancel_at_period_end`, and `pause_collection` evidence; resume clears local pause evidence and emits `customer.subscription.updated`. Top-level and nested customer lists support status and metadata equality filters such as `metadata[billtap_fixture_ref]`. Search supports `id`, `customer`, `status`, and metadata equality clauses joined by `AND`. Delete performs immediate sandbox cancellation. Test-clock and scenario clock advances can activate due trials, renew active periods with bounded discount math, fail configured renewals from subscription or customer metadata, and cancel period-end subscriptions in the local billing graph. |
| Subscriptions | `POST /v1/subscriptions`, `GET /v1/subscriptions`, `GET /v1/subscriptions/search`, `GET /v1/subscriptions/{id}`, `POST /v1/subscriptions/{id}`, `DELETE /v1/subscriptions/{id}`, `POST /v1/subscriptions/{id}/resume`, nested `GET/POST/DELETE /v1/customers/{id}/subscriptions...`, `GET/DELETE /v1/subscriptions/{id}/discount` | Partial | Create/list/retrieve subscriptions through the local checkout-completion state path. Update supports item replacement, metadata merge, single coupon or promotion-code discount application, `test_clock`, `cancel_at_period_end`, `pause_collection`, and accepted billing/proration evidence including `proration_date`, `proration_behavior`, `payment_behavior`, `billing_cycle_anchor`, and `trial_end`; resume clears local pause evidence and emits `customer.subscription.updated`. Top-level and nested customer lists support status and metadata equality filters such as `metadata[billtap_fixture_ref]`. Search supports `id`, `customer`, `status`, and metadata equality clauses joined by `AND`. Delete performs immediate sandbox cancellation. Test-clock and scenario clock advances can activate due trials, renew active periods with bounded discount math, fail configured renewals from subscription or customer metadata, and cancel period-end subscriptions in the local billing graph. |
| Subscription schedules | `POST /v1/subscription_schedules`, `GET /v1/subscription_schedules`, `GET /v1/subscription_schedules/{id}`, `POST /v1/subscription_schedules/{id}`, `POST /v1/subscription_schedules/{id}/cancel`, `POST /v1/subscription_schedules/{id}/release` | Partial | Local schedule evidence for one due phase on an existing subscription. Test-clock advance applies a due phase by replacing subscription items and emits `customer.subscription.updated`; complex multi-phase billing, proration, invoices, and schedule lifecycle parity are not modeled. |
| Subscription items | `POST /v1/subscription_items`, `DELETE /v1/subscription_items/{id}` | Partial | Add or remove local subscription items for integration smoke paths. Billing proration and invoice recalculation are not modeled. |
| Invoices | `GET /v1/invoices`, `GET /v1/invoices/search`, `GET /v1/invoices/{id}`, `POST /v1/invoices/{id}/pay`, `POST /v1/invoices/create_preview`, `GET/POST /v1/invoices/upcoming` | Partial | List/retrieve/search invoices created by checkout. Search supports `id`, `customer`, `subscription`, and `status` clauses joined by `AND`. `pay` retries open local invoices with deterministic sandbox `payment_method` or `source` aliases, mutating invoice, subscription, payment-intent, timeline, and webhook evidence. Preview endpoints calculate a Stripe-shaped subscription-update proration line from current period bounds, old/new price totals, `subscription_details[items]`, `subscription_details[proration_date]`, `subscription_details[proration_behavior]`, and `subscription_details[billing_cycle_anchor]`. Invoice and preview responses include the common Stripe Invoice default fields expected by generated SDK models, including status transitions, automatic tax state, tax totals, payment settings, and array-shaped `discounts`. A bounded single-discount subset updates `subtotal`, `total`, and `total_discount_amounts`; taxes, pending invoice items, and full invoice collection lifecycle are outside the modeled subset. |
| Invoices | `POST /v1/invoices`, `GET /v1/invoices`, `GET /v1/invoices/search`, `GET /v1/invoices/{id}`, `POST /v1/invoices/{id}/finalize`, `POST /v1/invoices/{id}/pay`, `GET /v1/invoices/{id}/lines`, `GET /v1/invoices/{id}/payments`, `POST /v1/invoices/create_preview`, `GET/POST /v1/invoices/upcoming` | Partial | List/retrieve/search invoices created by checkout and create a bounded manual one-time invoice flow. Search supports `id`, `customer`, `subscription`, and `status` clauses joined by `AND`. `pay` retries open checkout invoices with deterministic sandbox `payment_method` or `source` aliases, mutating invoice, subscription, payment-intent, timeline, and webhook evidence. Manual invoices accept `customer`, `currency`, `collection_method`, `default_payment_method`, `description`, `auto_advance=false`, `pending_invoice_items_behavior`, `payment_settings[payment_method_types]`, and `metadata[...]`; `finalize` creates local PaymentIntent evidence, and `pay` applies per-intent or customer-level default outcomes including success, card decline, and `requires_action`. Invoice responses include `confirmation_secret`, local hosted/PDF paths, `payments.data.payment.payment_intent`, metadata, status transitions, automatic tax state, tax totals, payment settings, and array-shaped `discounts`. Preview endpoints calculate a Stripe-shaped subscription-update proration line from current period bounds, old/new price totals, `subscription_details[items]`, `subscription_details[proration_date]`, `subscription_details[proration_behavior]`, and `subscription_details[billing_cycle_anchor]`. A bounded single-discount subset updates `subtotal`, `total`, and `total_discount_amounts`; taxes, invoice rendering, automatic collection, and full dunning lifecycle are outside the modeled subset. |
| Invoice items | `POST /v1/invoiceitems`, `GET /v1/invoiceitems`, `GET /v1/invoiceitems/{id}` | Partial | Creates and lists local invoice item evidence for manual one-time invoices. `customer`, `invoice`, `amount`, `currency`, `description`, and `metadata[...]` are accepted. Tax, discount, pending invoice-item collection, and line mutation parity are not modeled. |
| Payment intents | `POST /v1/payment_intents`, `GET /v1/payment_intents`, `GET /v1/payment_intents/search`, `GET /v1/payment_intents/{id}`, `POST /v1/payment_intents/{id}/confirm`, `POST /v1/payment_intents/{id}/capture`, `POST /v1/payment_intents/{id}/cancel` | Partial | Create/list/retrieve/search and mutate local payment intents. Search supports `id`, `customer`, `status`, and metadata equality clauses joined by `AND`. `confirm` supports deterministic sandbox PaymentMethod aliases such as `pm_card_visa`, `pm_card_visa_chargeDeclined`, `pm_card_threeDSecure2Required`, `requires_action`, and local bank-transfer processing. Manual capture moves through `requires_capture` before `capture` succeeds. Direct one-time intents emit `payment_intent.created` and a terminal event when confirmed. Deferred per-intent outcomes and customer defaults are supported; `requires_action` can return either `use_stripe_sdk` or local `redirect_to_url` next-action payloads. |
| Setup intents | `POST /v1/setup_intents`, `GET /v1/setup_intents`, `GET /v1/setup_intents/{id}`, `POST /v1/setup_intents/{id}/confirm`, `POST /v1/setup_intents/{id}/cancel` | Partial | Create/list/retrieve and mutate local setup intents with deterministic success, decline, and authentication-required aliases. Mandates, bank-account verification, and full SCA behavior are not modeled. |
| Payment methods | `GET/POST /v1/payment_methods`, `GET/POST /v1/payment_methods/{id}`, `POST /v1/payment_methods/{id}/attach`, `POST /v1/payment_methods/{id}/detach`, `GET /v1/customers/{id}/payment_methods?type=card`, `GET /v1/customers/{id}/payment_methods/{id}` | Partial | Returns deterministic sandbox card PaymentMethod projections for known customers, including SDK-friendly `billing_details`, `card.checks`, `country`, `fingerprint`, `funding`, `networks`, `three_d_secure_usage`, `metadata`, and `redaction` fields. Query validation covers `type`, `allow_redisplay`, `limit`, and unknown parameters; non-card valid types return an empty local list. Customer fixtures can opt into empty or explicit payment-method lists for no-card and multi-card local scenarios. Create/attach/detach mutate local customer payment-method metadata and reject real card data; general update is accepted as a compatibility no-op for projected local cards. |
Expand Down Expand Up @@ -328,7 +329,7 @@ Fixture packs support repeatable local setup and assertions for:
`payment_methods_fixture: empty` for no-card setup paths
- customer default PaymentIntent outcome fixture controls through
`default_payment_intent_outcome` or `billtap_default_payment_intent_outcome`
metadata
metadata for direct and invoice-backed one-time payments
- test clocks
- subscription graphs created through the normal checkout-completion path
- explicit subscription status/time fields for local lifecycle setup
Expand Down Expand Up @@ -387,9 +388,9 @@ Billtap does not support or claim:
- Provider-specific settlement, risk, tax, invoice rendering, fraud, account,
payout, or dispute behavior.
- Complete webhook event coverage.
- Direct invoice create, finalize, send, void, line mutation, collection, and
full dunning automation. Billtap only supports the local `pay` retry subset
documented above.
- Full invoice rendering, send, void, line mutation, automatic collection, tax,
and dunning automation. Billtap supports the checkout retry subset and the
bounded manual one-time invoice flow documented above.

Use Stripe testmode or the real provider sandbox as the fallback lane for these
behaviors.
Expand Down
Loading
Loading