Skip to content

feat(billing): charge service fees on credit top-ups - #5264

Closed
jeanduplessis wants to merge 2 commits into
transaction-service-fee-3-safetyfrom
transaction-service-fee-4-top-ups
Closed

feat(billing): charge service fees on credit top-ups#5264
jeanduplessis wants to merge 2 commits into
transaction-service-fee-3-safetyfrom
transaction-service-fee-4-top-ups

Conversation

@jeanduplessis

@jeanduplessis jeanduplessis commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Activates the 5% service fee for personal and organization credit top-ups, including subsequent automatic top-up invoices.

Why this change is needed

Top-ups span hosted Checkout and Kilo-owned draft invoices. Both paths must produce the same fee decision without double-attaching a fee, while credits remain equal to purchased principal and fee failures must not block the top-up.

How this is addressed

  • Attach a separate service-fee line to eligible personal and organization Checkout top-ups.
  • Add principal and fee items to Kilo-owned auto-top-up invoices before payment.
  • Skip generic invoice attachment for auto-top-up metadata so only one code path can add the fee.
  • Carry the assessment key through Stripe objects and settle the existing assessment from webhooks.
  • Observe refund and dispute events against service-fee assessments.

Stack: #5236#5262#5263#5264 (this PR) → #5265#5266.

Human Verification

No new product or browser verification was performed while publishing this PR. The completed local validation run covered personal and organization Checkout top-ups, auto-top-up setup, and subsequent off-session invoices; its release-environment gates remain separate.

Reviewer Notes

Human Reviewer Flags

  • Auto-top-up invoices are owned by Kilo with auto_advance=false, so the principal and fee are added synchronously before invoices.pay().
  • The invoice.created handler explicitly skips those invoices to prevent duplicate fee lines.
  • The activation cutoff uses the Stripe billing object's creation time, not webhook delivery time.
  • Fee preparation failures fail open and record a missed assessment rather than retrying collection later.

Code Reviewer Agent

Code Reviewer Notes
  • Verify the two attachment paths are mutually exclusive.
  • Credits must remain the product principal even though Stripe collects principal plus fee.
  • Refund and dispute observers run before unrelated webhook branches can return early.

@kilo-code-bot

kilo-code-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental review of exemption-log wording in the service-fee spec, validation notes, and ADR found no issues; the terminology now matches the unified exemption-log schema.

Files Reviewed (3 files)
  • .plans/service-fees/SPEC.md
  • .plans/service-fees/VALIDATION.md
  • docs/adr/0004-stripe-service-fee-assessment.md
Previous Review Summaries (2 snapshots, latest commit ea2270f)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit ea2270f)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental review of the unified-exemption-log test cleanup found no issues; the removed history-table delete matches the current schema.

Files Reviewed (1 file)
  • apps/web/src/routers/organizations/organization-auto-top-up-router.test.ts

Previous review (commit 7696e84)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • apps/web/src/lib/autoTopUp.ts
  • apps/web/src/lib/autoTopUp.test.ts
  • apps/web/src/lib/organizations/organization-auto-top-up.ts
  • apps/web/src/lib/stripe/index.ts
  • apps/web/src/routers/organizations/organization-auto-top-up-router.test.ts

Reviewed the service-fee wiring for personal/org Checkout top-ups and Kilo-owned auto-top-up invoices against the service-fees library. Verified: the two fee-attachment paths are mutually exclusive (invoice.created skips auto-topup/org-auto-topup invoices while attachPreparedAutoTopUpInvoiceFee handles them synchronously), the assessment key is deterministic (invoice:<id>) so a swallowed invoices.update metadata failure still settles correctly, credits remain the principal (serviceFeePrincipalMinor preserved via mergeServiceFeeCommercialMetadata; settleTrustedAutoTopUpInvoice/settleTrustedTopUpCharge read it), refund/dispute/credit-note observers are placed before early-return branches as documented, and test mocks/cleanup order respect FK dependencies (exemptions before history). No memory-leak vectors introduced (per-call store construction, bounded pagination). The throw-for-retry behavior of the refund/dispute observers on unsettled assessments is a deliberate, documented trade-off with alerting.


Reviewed by grok-4.6 · Input: 31.5K · Output: 4.6K · Cached: 171K

Review guidance: REVIEW.md from base branch transaction-service-fee-3-safety

Attach the 5% fee to personal and organization Checkout top-ups and
auto top-up invoices. Refund and dispute webhooks now observe fee
assessments. Kilo Pass charging is still unwired.
@jeanduplessis
jeanduplessis force-pushed the transaction-service-fee-4-top-ups branch from 199deb1 to 878935f Compare August 19, 2026 14:23
@jeanduplessis
jeanduplessis force-pushed the transaction-service-fee-3-safety branch from c83e1e2 to 484f4d4 Compare August 19, 2026 14:23
@IamCoder18

Copy link
Copy Markdown
Contributor

Hey, quick question: are bug bounty credits exempt from the 5% fee?

@jeanduplessis

Copy link
Copy Markdown
Contributor Author

Bug bounty credits do not incur the 5% service fee. They are internal/free credit grants, not a fee-bearing Stripe purchase, so there is no service-fee assessment to exempt. The fee applies only when a customer buys eligible paid principal through the listed Stripe top-up or Kilo Pass flows.

@jeanduplessis

Copy link
Copy Markdown
Contributor Author

Superseded by #5383, which consolidates the complete service-fee implementation into one PR against main.

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