Skip to content

Commit 7c2ed11

Browse files
committed
feat(payments): Revise Subscription Management page and Upgrade page to handle credits
- revises Subscription Management page to remove interval and add credit messaging - revises Upgrade page to add credit messaging Closes: FXA-11634
1 parent 846181a commit 7c2ed11

8 files changed

Lines changed: 132 additions & 54 deletions

File tree

libs/payments/ui/src/lib/server/components/UpgradePurchaseDetails/en.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ upgrade-purchase-details-new-plan-weekly = { $productName } (Weekly)
1414
upgrade-purchase-details-new-plan-monthly = { $productName } (Monthly)
1515
upgrade-purchase-details-new-plan-halfyearly = { $productName } (6-month)
1616
upgrade-purchase-details-new-plan-yearly = { $productName } (Yearly)
17+
18+
upgrade-purchase-details-prorated-credits = Negative balance shown will be applied as credits to your account and used towards future invoices.

libs/payments/ui/src/lib/server/components/UpgradePurchaseDetails/index.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,15 @@ export function UpgradePurchaseDetails(props: UpgradePurchaseDetailsProps) {
227227
{l10n.getLocalizedCurrencyString(oneTimeCharge, currency)}
228228
</p>
229229
</div>
230+
231+
{oneTimeCharge < 0 && (
232+
<p className="pb-4">
233+
{l10n.getString(
234+
'upgrade-purchase-details-prorated-credits',
235+
'Negative balance shown will be applied as credits to your account and used towards future invoices.'
236+
)}
237+
</p>
238+
)}
230239
</>
231240
)}
232241
</div>

packages/fxa-payments-server/src/routes/Product/SubscriptionUpgrade/PlanUpgradeDetails.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ export const PlanUpgradeDetails = ({
3030
}) => {
3131
const { navigatorLanguages, config } = useContext(AppContext);
3232
const invoiceCurrency = invoicePreview.line_items[0].currency;
33-
const { product_name, amount, interval, interval_count } =
34-
selectedPlan;
33+
const { product_name, amount, interval, interval_count } = selectedPlan;
3534
const formattedInterval = formatPlanInterval({
3635
interval: interval,
3736
intervalCount: 1,
@@ -65,13 +64,21 @@ export const PlanUpgradeDetails = ({
6564
<Localized id="sub-update-current-plan-label">Current plan</Localized>
6665
</p>
6766

68-
<PlanDetailsCard className="from-plan" plan={upgradeFromPlan} currency={invoiceCurrency} />
67+
<PlanDetailsCard
68+
className="from-plan"
69+
plan={upgradeFromPlan}
70+
currency={invoiceCurrency}
71+
/>
6972

7073
<p className="plan-label new-plan-label">
7174
<Localized id="sub-update-new-plan-label">New plan</Localized>
7275
</p>
7376

74-
<PlanDetailsCard className="to-plan" plan={selectedPlan} currency={invoiceCurrency} />
77+
<PlanDetailsCard
78+
className="to-plan"
79+
plan={selectedPlan}
80+
currency={invoiceCurrency}
81+
/>
7582

7683
<div className="py-6 border-t-0">
7784
{showTax && !!subTotal && !!exclusiveTaxRates.length && (
@@ -144,7 +151,7 @@ export const PlanUpgradeDetails = ({
144151
</div>
145152
)}
146153

147-
{oneTimeCharge && (
154+
{!!oneTimeCharge && (
148155
<>
149156
<hr className="m-0 my-5 unit-row-hr" />
150157

@@ -165,6 +172,15 @@ export const PlanUpgradeDetails = ({
165172
dataTestId="prorated-amount"
166173
/>
167174
</div>
175+
176+
{oneTimeCharge < 0 && (
177+
<Localized id="sub-update-prorated-upgrade-credit">
178+
<p className="text-base">
179+
Negative balance shown will be applied as credits to your
180+
account and used towards future invoices.
181+
</p>
182+
</Localized>
183+
)}
168184
</>
169185
)}
170186
</div>
@@ -178,7 +194,7 @@ export const PlanDetailsCard = ({
178194
className = '',
179195
}: {
180196
plan: Plan;
181-
currency: string,
197+
currency: string;
182198
className?: string;
183199
}) => {
184200
const { navigatorLanguages, config } = useContext(AppContext);

packages/fxa-payments-server/src/routes/Product/SubscriptionUpgrade/en.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ sub-update-new-plan-weekly = { $productName } (Weekly)
2222
sub-update-new-plan-monthly = { $productName } (Monthly)
2323
sub-update-new-plan-yearly = { $productName } (Yearly)
2424
25-
##
25+
sub-update-prorated-upgrade-credit = Negative balance shown will be applied as credits to your account and used towards future invoices.

packages/fxa-payments-server/src/routes/Product/SubscriptionUpgrade/index.test.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ async function rendersAsExpected(
9393
// and interval count match or not.
9494
const expectedInvoiceDate =
9595
upgradeFromPlan.interval !== selectedPlan.interval &&
96-
selectedPlan.interval_count === upgradeFromPlan.interval_count
96+
selectedPlan.interval_count === upgradeFromPlan.interval_count
9797
? getLocalizedDateString(invoicePreview.line_items[0].period.end)
9898
: getLocalizedDateString(customerWebSubscription.current_period_end);
9999

@@ -115,13 +115,35 @@ async function rendersAsExpected(
115115
).toBeInTheDocument();
116116
}
117117

118-
if (!!invoicePreview.one_time_charge && invoicePreview.one_time_charge > 0) {
119-
expect(queryByTestId('sub-update-prorated-upgrade')).toBeInTheDocument();
120-
expect(queryByTestId('prorated-amount')).toBeInTheDocument();
118+
if (!!invoicePreview.one_time_charge) {
119+
if (invoicePreview.one_time_charge > 0) {
120+
expect(queryByTestId('sub-update-prorated-upgrade')).toBeInTheDocument();
121+
expect(queryByTestId('prorated-amount')).toBeInTheDocument();
122+
expect(
123+
queryByTestId('sub-update-prorated-upgrade-credit')
124+
).not.toBeInTheDocument();
125+
} else if (invoicePreview.one_time_charge < 0) {
126+
expect(queryByTestId('sub-update-prorated-upgrade')).toBeInTheDocument();
127+
expect(queryByTestId('prorated-amount')).toBeInTheDocument();
128+
expect(
129+
queryByTestId('sub-update-prorated-upgrade-credit')
130+
).toBeInTheDocument();
131+
} else {
132+
expect(
133+
queryByTestId('sub-update-prorated-upgrade')
134+
).not.toBeInTheDocument();
135+
expect(
136+
queryByTestId('sub-update-prorated-upgrade-credit')
137+
).not.toBeInTheDocument();
138+
expect(queryByTestId('prorated-amount')).not.toBeInTheDocument();
139+
}
121140
} else {
122141
expect(
123142
queryByTestId('sub-update-prorated-upgrade')
124143
).not.toBeInTheDocument();
144+
expect(
145+
queryByTestId('sub-update-prorated-upgrade-credit')
146+
).not.toBeInTheDocument();
125147
expect(queryByTestId('prorated-amount')).not.toBeInTheDocument();
126148
}
127149
}

packages/fxa-payments-server/src/routes/Subscriptions/Cancel/CancelSubscriptionPanel.test.tsx

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ describe('CancelSubscriptionPanel', () => {
8989
<CancelSubscriptionPanel {...props} />
9090
);
9191

92-
const planPrice = formatPlanPricing(
92+
const planPrice = formatPriceAmount(
9393
props.invoice.total,
9494
props.plan.currency,
95-
props.plan.interval,
96-
props.plan.interval_count
95+
false,
96+
0
9797
);
9898
const nextBillDate = getLocalizedDateString(
9999
props.subsequentInvoice.period_start,
@@ -305,17 +305,17 @@ describe('CancelSubscriptionPanel', () => {
305305
/>
306306
</LocalizationProvider>
307307
);
308-
expect(queryByText('$20.00 fooly')).toBeInTheDocument();
308+
expect(queryByText('$20.00')).toBeInTheDocument();
309309
expect(queryByText('quuz 13/09/2019')).toBeInTheDocument();
310310
expect(queryByText('blee')).toBeInTheDocument();
311311
});
312312

313313
it('displays the correct pricing info with interval > 1', () => {
314314
const bundle = new FluentBundle('gd', { useIsolating: false });
315315
[
316-
`price-details-no-tax-day = { $intervalCount ->
317-
[one] { $priceAmount } fooly
318-
*[other] { $priceAmount } barly { $intervalCount } 24hrs
316+
`price-details-no-tax = { $intervalCount ->
317+
[one] { $priceAmount }
318+
*[other] { $priceAmount }
319319
}`,
320320
].forEach((x) => bundle.addResource(new FluentResource(x)));
321321
const plan = { ...findMockPlan('plan_daily'), interval_count: 8 };
@@ -325,15 +325,15 @@ describe('CancelSubscriptionPanel', () => {
325325
<CancelSubscriptionPanel {...baseProps} plan={plan} />
326326
</LocalizationProvider>
327327
);
328-
expect(queryByText('$20.00 barly 8 24hrs')).toBeInTheDocument();
328+
expect(queryByText('$20.00')).toBeInTheDocument();
329329
});
330330

331331
it('displays the correct pricing and exclusive tax info with interval of 1', () => {
332332
const bundle = new FluentBundle('gd', { useIsolating: false });
333333
[
334-
`price-details-tax-day = { $intervalCount ->
335-
[one] { $priceAmount } + { $taxAmount } tax fooly
336-
*[other] { $priceAmount } + { $taxAmount } tax barly { $intervalCount } 24hrs
334+
`price-details-tax = { $intervalCount ->
335+
[one] { $priceAmount } + { $taxAmount } tax
336+
*[other] { $priceAmount } + { $taxAmount } tax
337337
}`,
338338
'payment-cancel-btn = blee',
339339
`price-details-tax = { $priceAmount } + { $taxAmount } taxes`,
@@ -354,7 +354,7 @@ describe('CancelSubscriptionPanel', () => {
354354
</LocalizationProvider>
355355
);
356356
expect(queryByTestId('price-details-standalone')).toHaveTextContent(
357-
'$20.00 + $3.00 tax fooly'
357+
'$20.00 + $3.00 tax'
358358
);
359359
expect(queryByTestId('sub-next-bill')).toHaveTextContent(
360360
'Next bill of $5.00 + $1.23 taxes is due 13/09/2019'
@@ -365,9 +365,9 @@ describe('CancelSubscriptionPanel', () => {
365365
it('displays the correct pricing and exclusive tax info with interval > 1', () => {
366366
const bundle = new FluentBundle('gd', { useIsolating: false });
367367
[
368-
`price-details-tax-day = { $intervalCount ->
369-
[one] { $priceAmount } + { $taxAmount } tax fooly
370-
*[other] { $priceAmount } + { $taxAmount } tax barly { $intervalCount } 24hrs
368+
`price-details-tax = { $intervalCount ->
369+
[one] { $priceAmount } + { $taxAmount } tax
370+
*[other] { $priceAmount } + { $taxAmount } tax
371371
}`,
372372
`price-details-tax = { $priceAmount } + { $taxAmount } taxes`,
373373
`sub-next-bill-tax-1 = Next bill of { $priceAmount } + { $taxAmount } taxes is due { $date }`,
@@ -385,7 +385,7 @@ describe('CancelSubscriptionPanel', () => {
385385
</LocalizationProvider>
386386
);
387387
expect(queryByTestId('price-details-standalone')).toHaveTextContent(
388-
'$20.00 + $3.00 tax barly 8 24hrs'
388+
'$20.00 + $3.00 tax'
389389
);
390390
expect(queryByTestId('sub-next-bill')).toHaveTextContent(
391391
'Next bill of $5.00 + $1.23 taxes is due 14/08/2019'
@@ -395,9 +395,9 @@ describe('CancelSubscriptionPanel', () => {
395395
it('displays the correct pricing and exclusive tax with discount info with interval > 1', () => {
396396
const bundle = new FluentBundle('gd', { useIsolating: false });
397397
[
398-
`price-details-tax-day = { $intervalCount ->
399-
[one] { $priceAmount } + { $taxAmount } tax fooly
400-
*[other] { $priceAmount } + { $taxAmount } tax barly { $intervalCount } 24hrs
398+
`price-details-tax = { $intervalCount ->
399+
[one] { $priceAmount } + { $taxAmount } tax
400+
*[other] { $priceAmount } + { $taxAmount } tax
401401
}`,
402402
`sub-next-bill-tax-1 = Next bill of { $priceAmount } + { $taxAmount } taxes is due { $date }`,
403403
].forEach((x) => bundle.addResource(new FluentResource(x)));
@@ -414,7 +414,7 @@ describe('CancelSubscriptionPanel', () => {
414414
</LocalizationProvider>
415415
);
416416
expect(queryByTestId('price-details-standalone')).toHaveTextContent(
417-
'$19.50 + $3.00 tax barly 8 24hrs'
417+
'$19.50 + $3.00 tax'
418418
);
419419
expect(queryByTestId('sub-next-bill')).toHaveTextContent(
420420
'Next bill of $4.50 + $1.23 taxes is due 14/08/2019'
@@ -424,9 +424,9 @@ describe('CancelSubscriptionPanel', () => {
424424
it('displays the correct pricing and hides tax for zero tax amount', () => {
425425
const bundle = new FluentBundle('gd', { useIsolating: false });
426426
[
427-
`price-details-tax-day = { $intervalCount ->
428-
[one] { $priceAmount } + { $taxAmount } tax fooly
429-
*[other] { $priceAmount } + { $taxAmount } tax barly { $intervalCount } 24hrs
427+
`price-details-tax = { $intervalCount ->
428+
[one] { $priceAmount } + { $taxAmount } tax
429+
*[other] { $priceAmount } + { $taxAmount } tax
430430
}`,
431431
'payment-cancel-btn = blee',
432432
`price-details-tax = { $priceAmount } + { $taxAmount } taxes`,
@@ -448,7 +448,7 @@ describe('CancelSubscriptionPanel', () => {
448448
</LocalizationProvider>
449449
);
450450
expect(queryByTestId('price-details-standalone')).toHaveTextContent(
451-
'$20.00 daily'
451+
'$20.00'
452452
);
453453
expect(queryByTestId('sub-next-bill')).toHaveTextContent(
454454
'Next bill of $5.00 prices is due 13/09/2019'
@@ -459,9 +459,9 @@ describe('CancelSubscriptionPanel', () => {
459459
it('displays the correct pricing and inclusive tax info with interval of 1', () => {
460460
const bundle = new FluentBundle('gd', { useIsolating: false });
461461
[
462-
`price-details-no-tax-day = { $intervalCount ->
463-
[one] { $priceAmount } fooly
464-
*[other] { $priceAmount } barly { $intervalCount } 24hrs
462+
`price-details-no-tax = { $intervalCount ->
463+
[one] { $priceAmount }
464+
*[other] { $priceAmount }
465465
}`,
466466
'payment-cancel-btn = blee',
467467
`sub-next-bill-no-tax-1 = Next bill of { $priceAmount } prices is due { $date }`,
@@ -481,7 +481,7 @@ describe('CancelSubscriptionPanel', () => {
481481
</LocalizationProvider>
482482
);
483483
expect(queryByTestId('price-details-standalone')).toHaveTextContent(
484-
'$20.00 fooly'
484+
'$20.00'
485485
);
486486
expect(queryByTestId('sub-next-bill')).toHaveTextContent(
487487
'Next bill of $5.00 prices is due 13/09/2019'
@@ -492,9 +492,9 @@ describe('CancelSubscriptionPanel', () => {
492492
it('displays the correct pricing and inclusive tax info with interval > 1', () => {
493493
const bundle = new FluentBundle('gd', { useIsolating: false });
494494
[
495-
`price-details-no-tax-day = { $intervalCount ->
496-
[one] { $priceAmount } fooly
497-
*[other] { $priceAmount } barly { $intervalCount } 24hrs
495+
`price-details-no-tax = { $intervalCount ->
496+
[one] { $priceAmount }
497+
*[other] { $priceAmount }
498498
}`,
499499
`sub-next-bill-no-tax-1 = Next bill of { $priceAmount } prices is due { $date }`,
500500
].forEach((x) => bundle.addResource(new FluentResource(x)));
@@ -511,7 +511,7 @@ describe('CancelSubscriptionPanel', () => {
511511
</LocalizationProvider>
512512
);
513513
expect(queryByTestId('price-details-standalone')).toHaveTextContent(
514-
'$20.00 barly 8 24hrs'
514+
'$20.00'
515515
);
516516
expect(queryByTestId('sub-next-bill')).toHaveTextContent(
517517
'Next bill of $5.00 prices is due 14/08/2019'
@@ -521,9 +521,9 @@ describe('CancelSubscriptionPanel', () => {
521521
it('displays the correct pricing and inclusive tax with discount info with interval > 1', () => {
522522
const bundle = new FluentBundle('gd', { useIsolating: false });
523523
[
524-
`price-details-no-tax-day = { $intervalCount ->
525-
[one] { $priceAmount } fooly
526-
*[other] { $priceAmount } barly { $intervalCount } 24hrs
524+
`price-details-no-tax = { $intervalCount ->
525+
[one] { $priceAmount }
526+
*[other] { $priceAmount }
527527
}`,
528528
`sub-next-bill-no-tax-1 = Next bill of { $priceAmount } prices is due { $date }`,
529529
].forEach((x) => bundle.addResource(new FluentResource(x)));
@@ -540,7 +540,7 @@ describe('CancelSubscriptionPanel', () => {
540540
</LocalizationProvider>
541541
);
542542
expect(queryByTestId('price-details-standalone')).toHaveTextContent(
543-
'$19.50 barly 8 24hrs'
543+
'$19.50'
544544
);
545545
expect(queryByTestId('sub-next-bill')).toHaveTextContent(
546546
'Next bill of $4.50 prices is due 14/08/2019'

0 commit comments

Comments
 (0)