Skip to content

feat(billing): ship pricing constants contract + refundCharge service#3775

Merged
PierreBrisorgueil merged 3 commits into
masterfrom
feat/billing-pricing-contract-and-refund-service
Jun 2, 2026
Merged

feat(billing): ship pricing constants contract + refundCharge service#3775
PierreBrisorgueil merged 3 commits into
masterfrom
feat/billing-pricing-contract-and-refund-service

Conversation

@PierreBrisorgueil
Copy link
Copy Markdown
Contributor

@PierreBrisorgueil PierreBrisorgueil commented Jun 2, 2026

Summary

Combines T1a + T3a of plan 2026-06-02-trawl-billing-residual-cleanup.md into a single devkit PR to reduce PR count and wall-clock. Both additions are independent (zero call sites changed in devkit — pure new contract + new service). Trawl will absorb both via /update-stack + swap importers in a separate trawl PR.

T1a — Pricing constants contract (config/defaults/)

  • New file: config/defaults/billing.pricing.constants.js — exports the shape contract with safe defaults: PRICING_VERSION='0.0.0', PLAN_QUOTAS={free:0}, RATIOS={}, STRIPE_PRICE_CENTS={}, STRIPE_PACK_CENTS={}. JSDoc per export.
  • Modified: config/defaults/development.config.js — imports the constants and exposes them at config.billing.pricing.* via the existing deep-merge pipeline. Downstream project configs override values (not shape) in their own file; devkit ships the contract only.
  • Contract test: lib/services/tests/billing-pricing-constants.contract.unit.tests.js — 5 shape assertions (types, free key present).
  • Wiring test: modules/billing/tests/billing.pricing.config.wiring.unit.tests.js — 2 assertions confirming config.billing.pricing.* is reachable after full config init.

T3a — refundCharge service (modules/billing/services/)

  • New file: modules/billing/services/billing.refund.service.js — ported verbatim from trawl, de-trawlified (stripped issue refs). Wraps stripe.refunds.create with a deterministic idempotency key (refund_{chargeId}_{amount|full}). The actual ledger debit happens via the charge.refunded webhook (single source of truth) — this service only initiates the Stripe refund.
  • Unit test: modules/billing/tests/billing.refund.service.refundCharge.unit.tests.js — 6 cases: full refund, partial amount, deterministic idempotency, reason passthrough, empty chargeId rejection, non-positive amount rejection.
    • Note: filename uses .refundCharge. infix (not the plan's prescribed .unit. suffix) because billing.refund.service.unit.tests.js is already taken by the admin-controller refund tests (inlined logic from a prior simplification). The chosen name is unambiguous.

Test plan

  • Full unit suite green locally: NODE_ENV=test npm run test:unit → 1718 tests pass (0 failures)
  • Contract test verifies export shape: NODE_ENV=test npm run test:unit -- lib/services/tests/billing-pricing-constants.contract
  • Wiring test verifies config.billing.pricing reachable: NODE_ENV=test npm run test:unit -- modules/billing/tests/billing.pricing.config.wiring
  • RefundCharge service tests (6 cases): NODE_ENV=test npm run test:unit -- modules/billing/tests/billing.refund.service.refundCharge
  • CI green on all checks
  • Trawl follow-up: T1b (swap 6 importers to config.billing.pricing + delete trawl-local file) + T3b (/update-stack to absorb) in separate trawl PRs after this merges

Summary by CodeRabbit

  • New Features

    • Added refund processing for Stripe transactions with support for full and partial refunds.
    • Introduced billing and pricing configuration management system.
  • Tests

    • Added comprehensive test coverage for billing refund operations and pricing configuration.

…fig/defaults/

Promote billing.pricing.constants shape to devkit (Task 1a of billing-residual-cleanup plan).
Adds config/defaults/billing.pricing.constants.js with the export contract + safe
defaults (PRICING_VERSION='0.0.0', PLAN_QUOTAS={free:0}, RATIOS={}, STRIPE_PRICE_CENTS={},
STRIPE_PACK_CENTS={}). Wires into development.config.js under billing.pricing so
importers can read via config.billing.pricing.*. Downstream projects override values
(not the shape) in their own project config; devkit ships the contract only.
…y key

Promote billing.refund.service.js from trawl to devkit (Task 3a of billing-residual-cleanup plan).
Wraps stripe.refunds.create with a deterministic idempotency key (refund_{chargeId}_{amount|full})
so retries on network failures never create duplicate refunds. Ledger debit happens via
the charge.refunded webhook (single source of truth) — this service only initiates the
Stripe refund. Full + partial refund, reason passthrough, empty chargeId and non-positive
amount guards all covered by 6 unit test cases.
Copilot AI review requested due to automatic review settings June 2, 2026 09:10
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Warning

Review limit reached

@PierreBrisorgueil, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 51 minutes and 22 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a54bb796-5381-42a1-a301-0451f1a6489e

📥 Commits

Reviewing files that changed from the base of the PR and between 5968959 and f719b3c.

📒 Files selected for processing (3)
  • lib/services/tests/billing-pricing-constants.contract.unit.tests.js
  • modules/billing/services/billing.refund.service.js
  • modules/billing/tests/billing.pricing.config.wiring.unit.tests.js

Walkthrough

This PR adds a billing pricing constants contract with safe defaults wired into development config, alongside a new Stripe refund service that supports full and partial refunds with input validation and idempotency.

Changes

Billing Module Enhancements

Layer / File(s) Summary
Pricing constants contract and configuration
config/defaults/billing.pricing.constants.js, config/defaults/development.config.js, lib/services/tests/billing-pricing-constants.contract.unit.tests.js, modules/billing/tests/billing.pricing.config.wiring.unit.tests.js
New pricing constants contract (PRICING_VERSION, PLAN_QUOTAS, RATIOS, STRIPE_PRICE_CENTS, STRIPE_PACK_CENTS) is defined with safe defaults and wired into development config. Contract shape and default values are validated with unit tests.
Stripe refund service implementation
modules/billing/services/billing.refund.service.js, modules/billing/tests/billing.refund.service.refundCharge.unit.tests.js
New refundCharge service implements full and partial Stripe refunds with input validation, deterministic idempotency keys, customizable reasons, and comprehensive unit test coverage for nominal and error paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

Refactor

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main features added: a pricing constants contract and a refundCharge service, both within the billing module.
Description check ✅ Passed The description is comprehensive, covering summary, scope, validation checklist, guardrails, and notes with detailed technical context and test plan.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/billing-pricing-contract-and-refund-service

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 2, 2026

Codecov Report

❌ Patch coverage is 94.11765% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 90.19%. Comparing base (0a2b3b4) to head (f719b3c).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3775      +/-   ##
==========================================
+ Coverage   90.18%   90.19%   +0.01%     
==========================================
  Files         151      152       +1     
  Lines        5003     5020      +17     
  Branches     1589     1598       +9     
==========================================
+ Hits         4512     4528      +16     
- Misses        386      387       +1     
  Partials      105      105              
Flag Coverage Δ
integration 59.06% <0.00%> (-0.21%) ⬇️
unit 69.34% <94.11%> (+0.08%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0a2b3b4...f719b3c. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

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

This PR adds two new billing building blocks intended to be consumed by downstream projects: (1) a devkit-shipped pricing constants “contract” wired into config.billing.pricing, and (2) a new refundCharge service that initiates Stripe refunds with a deterministic idempotency key. It also adds unit tests to verify the contract shape, config wiring, and refund behavior.

Changes:

  • Add config/defaults/billing.pricing.constants.js contract exports and wire them into config.billing.pricing via development.config.js.
  • Add modules/billing/services/billing.refund.service.js with refundCharge() and unit tests for refund behaviors/idempotency.
  • Add contract + wiring unit tests to ensure the new config surface is reachable and has expected default shape.

Reviewed changes

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

Show a summary per file
File Description
config/defaults/billing.pricing.constants.js Introduces pricing constants export contract with safe defaults.
config/defaults/development.config.js Wires pricing constants into config.billing.pricing through the existing merge pipeline.
lib/services/tests/billing-pricing-constants.contract.unit.tests.js Contract tests validating the exported constants’ shape/types.
modules/billing/services/billing.refund.service.js Adds refundCharge() Stripe refund initiator with deterministic idempotency key.
modules/billing/tests/billing.refund.service.refundCharge.unit.tests.js Unit tests for refundCharge() (full/partial, idempotency, validation).
modules/billing/tests/billing.pricing.config.wiring.unit.tests.js Unit test verifying config.billing.pricing.* is accessible after config init.

Comment thread config/defaults/development.config.js
Comment thread config/defaults/billing.pricing.constants.js
Comment thread modules/billing/services/billing.refund.service.js
Comment thread modules/billing/services/billing.refund.service.js Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 2, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/services/tests/billing-pricing-constants.contract.unit.tests.js`:
- Around line 11-27: Tighten the contract tests to assert the actual safe
defaults instead of broad types: change the assertions for RATIOS,
STRIPE_PRICE_CENTS, and STRIPE_PACK_CENTS to require they are empty objects
(e.g., deep-equal to {} or Object.keys(...).length === 0) so regressions that
populate them by default will fail; keep the existing checks for PRICING_VERSION
and PLAN_QUOTAS but consider also asserting PLAN_QUOTAS.free is a non-negative
number if relevant. Ensure you update the tests referencing the symbols RATIOS,
STRIPE_PRICE_CENTS, and STRIPE_PACK_CENTS only.

In `@modules/billing/services/billing.refund.service.js`:
- Line 21: The function refundCharge currently destructures the third parameter
with ({ reason } = {}) which throws a TypeError if callers pass null; update
refundCharge to guard the options argument before destructuring (e.g., normalize
the incoming param to an object or explicitly check for null/invalid values) and
then extract reason, or throw a clear validation error when a null/invalid
options is passed so callers get a meaningful message; reference the
refundCharge function and adjust its parameter handling or top-of-function
validation accordingly.

In `@modules/billing/tests/billing.pricing.config.wiring.unit.tests.js`:
- Around line 2-17: The test imports the environment-resolving entrypoint
(config/index.js) so it may not verify the default config; change the test to
import the specific defaults module (development.config.js) and run the same
assertions against that imported defaults object (e.g., replace the existing
import of config with a direct import of the development defaults and use
defaults.billing.pricing in the expect checks) so the test targets the new
billing.pricing block deterministically.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f26d6225-88d7-4d0f-a964-eb1e6df5ed68

📥 Commits

Reviewing files that changed from the base of the PR and between 0a2b3b4 and 5968959.

📒 Files selected for processing (6)
  • config/defaults/billing.pricing.constants.js
  • config/defaults/development.config.js
  • lib/services/tests/billing-pricing-constants.contract.unit.tests.js
  • modules/billing/services/billing.refund.service.js
  • modules/billing/tests/billing.pricing.config.wiring.unit.tests.js
  • modules/billing/tests/billing.refund.service.refundCharge.unit.tests.js

Comment thread lib/services/tests/billing-pricing-constants.contract.unit.tests.js Outdated
Comment thread modules/billing/services/billing.refund.service.js Outdated
Comment thread modules/billing/tests/billing.pricing.config.wiring.unit.tests.js Outdated
…und service

CR1: pin exact safe defaults in contract test (toEqual instead of typeof)
so regressions that populate RATIOS/STRIPE_PRICE_CENTS/STRIPE_PACK_CENTS
by default are caught.

CR2: guard options param in refundCharge before destructuring — null arg
caused raw TypeError; now throws a clear validation error.

CR3: wiring test imports development.config.js directly (not config/index.js)
so it deterministically targets the new billing.pricing block in the
defaults file, not whichever env the full pipeline resolves.
@PierreBrisorgueil PierreBrisorgueil dismissed coderabbitai[bot]’s stale review June 2, 2026 09:21

All 3 actionable comments addressed in fix commit f719b3c: CR1 exact-default assertions, CR2 null-options guard, CR3 direct development.config.js import. CodeRabbit check itself is SUCCESS.

@PierreBrisorgueil PierreBrisorgueil merged commit 2e25408 into master Jun 2, 2026
8 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