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
4 changes: 3 additions & 1 deletion apps/playground/src/TestHarness.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,9 @@ const allOffersSteps: Step[] = [
amountMinor: 500,
currency: 'USD',
amountPaidMinor: 10890,
netAfterRebateMinor: 10390,
// Net reflects the full refund (rebate + tax): $5.00 rebate + $0.50
// tax → $5.50 back, so the "(incl. $0.50 tax)" note shows.
netAfterRebateMinor: 10340,
paymentMethodBrand: 'visa',
paymentMethodLast4: '4242',
},
Expand Down
6 changes: 6 additions & 0 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Expect breaking changes in minor versions while we're pre-1.0.

## 0.4.2 — 2026-06-01

### Changed

- `DefaultRebateOffer` shows the actual cash refunded. On a taxed invoice the rebate comes back with the tax paid on it, so the middle line reads "Money back" with the full refund and an "(incl. $X tax)" note, and the net reflects it. No-tax invoices are unchanged — the note is hidden and the refund equals the rebate.

## 0.4.1 — 2026-05-30

### Changed
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@churnkey/react",
"version": "0.4.1",
"version": "0.4.2",
"description": "Production-ready cancel flow for React. Drop-in component, headless hook, or full customization. Works standalone or with Churnkey for AI-powered retention.",
"license": "MIT",
"repository": {
Expand Down
19 changes: 13 additions & 6 deletions packages/react/src/components/steps/offer/default-rebate-offer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,19 @@ export function DefaultRebateOffer({
const body = description ?? offer.copy.body
const currency = o.currency ?? 'usd'
const amount = o.amountMinor ?? 0
// refund = paid - net; tax = refund - rebate. The server's net already
// accounts for tax refunded on the rebate, so no tax means refund == rebate.
const refund =
o.amountPaidMinor != null && o.netAfterRebateMinor != null ? o.amountPaidMinor - o.netAfterRebateMinor : amount
const taxRefunded = refund - amount

return (
<div className={cn('ck-step ck-step-offer', classNames?.root)}>
{headline && <h2 className={cn('ck-step-title', classNames?.title)}>{headline}</h2>}
{body && <RichText html={body} className={cn('ck-step-description', classNames?.description)} />}

<div className={cn('ck-offer-card', classNames?.card)}>
{/* Itemized like an invoice: what they already paid this period, the
rebate we credit (the accented line), and the net after the refund.
Paid and net are server-resolved in token mode, so each renders
only when present. */}
{/* paid / money back / net, like an invoice. Paid and net only exist in token mode. */}
<div className="ck-offer-rebate">
{o.amountPaidMinor != null && (
<div className="ck-offer-rebate-row">
Expand All @@ -43,8 +45,13 @@ export function DefaultRebateOffer({
</div>
)}
<div className="ck-offer-rebate-row ck-offer-rebate-credit">
<span>Cancellation rebate</span>
<span>−{formatPriceFromMinor(amount, currency)}</span>
<span>
Money back
{taxRefunded > 0 && (
<span className="ck-offer-rebate-tax"> (incl. {formatPriceFromMinor(taxRefunded, currency)} tax)</span>
)}
</span>
<span>−{formatPriceFromMinor(refund, currency)}</span>
</div>
{o.netAfterRebateMinor != null && (
<div className="ck-offer-rebate-row ck-offer-rebate-total">
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/core/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,12 @@ export interface SdkContactOffer extends SdkOfferBase {

export interface SdkRebateOffer extends SdkOfferBase {
type: 'rebate'
/** Cash refunded to the card, smallest currency unit. */
/** The rebate amount (pre-tax). The card is refunded this plus any tax charged on it. */
amountMinor: number
currency: string
/** Gross amount paid on the target invoice — the "you paid" row. */
amountPaidMinor: number
/** amountPaidMinor − amountMinor — the "your net" row. */
/** amountPaidMinor minus the full refund (rebate plus its tax) — the "your net" row. */
netAfterRebateMinor: number
paymentMethodBrand?: string
paymentMethodLast4?: string
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,12 @@ export interface RedirectOffer {

export interface RebateOffer {
type: 'rebate'
/** Cash refunded to the card, smallest currency unit. */
/** The rebate amount (pre-tax), smallest currency unit. The card is refunded this plus any tax charged on it. */
amountMinor: number
currency: string
/** Gross paid on the target invoice. Server-resolved in token mode. */
amountPaidMinor?: number
/** amountPaidMinor − amountMinor. Server-resolved in token mode. */
/** amountPaidMinor minus the full refund (the rebate plus its tax). Server-resolved in token mode. */
netAfterRebateMinor?: number
paymentMethodBrand?: string
paymentMethodLast4?: string
Expand Down
9 changes: 8 additions & 1 deletion packages/react/src/styles/cancel-flow.css
Original file line number Diff line number Diff line change
Expand Up @@ -482,13 +482,20 @@
color: var(--ck-color-text-muted);
}

/* The rebate line — the figure we want the eye to land on. */
/* The money-back amount — the accented line. */
.ck-cancel-flow .ck-offer-rebate-credit {
font-size: 14px;
font-weight: 600;
color: var(--ck-color-primary);
}

/* The "(incl. tax)" note, kept quieter than the amount. */
.ck-cancel-flow .ck-offer-rebate-tax {
font-size: 13px;
font-weight: 400;
color: var(--ck-color-text-muted);
}

.ck-cancel-flow .ck-offer-rebate-total {
margin-top: 6px;
padding-top: 14px;
Expand Down
Loading