-
Notifications
You must be signed in to change notification settings - Fork 514
payments: rework refund flow to three-knob API #1429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
ca299a3
payments: rework refund flow to three-knob API (amount, revoke, end-sub)
BilalG1 e255f6c
Merge branch 'dev' into fix/reworked-refunds
BilalG1 a9368aa
payments: address pre-merge review on refund rework
BilalG1 cd3b58e
payments: address second-pass review on refund rework
BilalG1 8fc2027
payments: fix refund dialog defaults, sub-revocation entry index, and…
BilalG1 08ff1bd
payments: address third-pass refund review feedback
BilalG1 165af2e
payments: address fourth-pass refund review feedback
BilalG1 ec9c16e
payments: replace revoke_product+end_subscription with end_action tri…
BilalG1 8c8ffcf
dashboard: refund dialog uses single tri-state lifecycle picker
BilalG1 a6fbe9f
dashboard: await refund directly in dialog onClick
BilalG1 5484ea8
e2e: use decimal money notation in refund tests so values match comments
BilalG1 a253cfa
payments: idempotent-cancel guard on endAtPeriodEnd Stripe update
BilalG1 97cdc11
payments: dedupe sub product-revocation via Subscription.productRevok…
BilalG1 badf6d6
payments: emit sub-end entries on the refund row, drop refund-driven …
BilalG1 cd0e084
merge origin/dev
BilalG1 d21afa6
dashboard: reload transactions grid after a refund
BilalG1 94eb603
payments: reject contradictory end-at-period-end refunds, fix Stripe …
BilalG1 2caf02d
dashboard: drop refund confirmation checkbox
BilalG1 6969588
dashboard: reword refund lifecycle picker as actions
BilalG1 8cb042f
payments: regrant free plan after a refund ends an internal-tenancy sub
BilalG1 5c36839
Merge remote-tracking branch 'origin/dev' into fix/reworked-refunds
BilalG1 37f99bc
payments: surface customer + describe refund rows in transaction list
BilalG1 b7a425f
payments: allow refunding API-granted subscriptions
BilalG1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
2 changes: 2 additions & 0 deletions
2
...ackend/prisma/migrations/20260514000000_add_subscription_product_revoked_at/migration.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| -- AlterTable | ||
| ALTER TABLE "Subscription" ADD COLUMN "productRevokedAt" TIMESTAMP(3); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
apps/backend/src/app/api/latest/internal/payments/transactions/refund/route.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { | ||
| getRefundDrivenImmediateEndedAt, | ||
| shouldRejectSubscriptionProductRevocationReplay, | ||
| } from "./route"; | ||
|
|
||
| describe("subscription refund replay guard", () => { | ||
| it("allows retry repair when subscription marker exists but refund revocation row is missing", () => { | ||
| expect(shouldRejectSubscriptionProductRevocationReplay({ | ||
| endNow: true, | ||
| productRevokedAt: new Date("2026-01-01T00:00:00Z"), | ||
| priorProductRevoked: false, | ||
| })).toBe(false); | ||
| }); | ||
|
|
||
| it("rejects replay only after both subscription marker and refund revocation row exist", () => { | ||
| expect(shouldRejectSubscriptionProductRevocationReplay({ | ||
| endNow: true, | ||
| productRevokedAt: new Date("2026-01-01T00:00:00Z"), | ||
| priorProductRevoked: true, | ||
| })).toBe(true); | ||
| }); | ||
|
|
||
| it("does not reject non-immediate refunds", () => { | ||
| expect(shouldRejectSubscriptionProductRevocationReplay({ | ||
| endNow: false, | ||
| productRevokedAt: new Date("2026-01-01T00:00:00Z"), | ||
| priorProductRevoked: true, | ||
| })).toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| describe("refund-driven immediate end timestamp", () => { | ||
| it("preserves an existing past endedAt", () => { | ||
| const existingEndedAt = new Date("2026-01-01T00:00:00Z"); | ||
| const now = new Date("2026-01-02T00:00:00Z"); | ||
|
|
||
| expect(getRefundDrivenImmediateEndedAt({ existingEndedAt, now })).toBe(existingEndedAt); | ||
| }); | ||
|
|
||
| it("pulls a scheduled future endedAt forward to now", () => { | ||
| const now = new Date("2026-01-02T00:00:00Z"); | ||
| const existingEndedAt = new Date("2026-02-01T00:00:00Z"); | ||
|
|
||
| expect(getRefundDrivenImmediateEndedAt({ existingEndedAt, now })).toBe(now); | ||
| }); | ||
|
|
||
| it("uses now when no endedAt exists", () => { | ||
| const now = new Date("2026-01-02T00:00:00Z"); | ||
|
|
||
| expect(getRefundDrivenImmediateEndedAt({ existingEndedAt: null, now })).toBe(now); | ||
| }); | ||
| }); |
1,446 changes: 1,106 additions & 340 deletions
1,446
apps/backend/src/app/api/latest/internal/payments/transactions/refund/route.tsx
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.