Skip to content

Commit 36297b8

Browse files
committed
feat(payments-ui): Display whether coupon was applied to next bill
- adds in `[Promotion name] discount] will be applied` when coupon has been applied to the next invoice Closes: PAY-3279, PAY-3283, PAY-3285
1 parent f442efe commit 36297b8

7 files changed

Lines changed: 71 additions & 26 deletions

File tree

libs/payments/customer/src/lib/invoice.manager.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
StripePriceFactory,
1515
StripePromotionCodeFactory,
1616
StripeResponseFactory,
17+
StripeSubscriptionFactory,
1718
StripeUpcomingInvoiceFactory,
1819
StripeAddressFactory,
1920
StripeSubscriptionItemFactory,
@@ -295,6 +296,31 @@ describe('InvoiceManager', () => {
295296
});
296297
});
297298

299+
describe('previewUpcomingSubscription', () => {
300+
it('returns upcoming invoice for a subscription', async () => {
301+
const mockCustomer = StripeCustomerFactory({ currency: 'usd' });
302+
const mockSubscription = StripeSubscriptionFactory();
303+
const mockUpcomingInvoice = StripeResponseFactory(
304+
StripeUpcomingInvoiceFactory()
305+
);
306+
const mockPreviewUpcomingInvoice = InvoicePreviewFactory();
307+
308+
jest
309+
.spyOn(stripeClient, 'invoicesRetrieveUpcoming')
310+
.mockResolvedValue(mockUpcomingInvoice);
311+
312+
mockedStripeInvoiceToFirstInvoicePreviewDTO.mockReturnValue(
313+
mockPreviewUpcomingInvoice
314+
);
315+
316+
const result = await invoiceManager.previewUpcomingSubscription({
317+
customer: mockCustomer,
318+
subscription: mockSubscription,
319+
});
320+
expect(result).toEqual(mockPreviewUpcomingInvoice);
321+
});
322+
});
323+
298324
describe('retrieve', () => {
299325
it('retrieves an invoice', async () => {
300326
const mockInvoice = StripeResponseFactory(StripeInvoiceFactory());

libs/payments/customer/src/lib/invoice.manager.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
StripeCustomer,
1111
StripeInvoice,
1212
StripePromotionCode,
13+
StripeSubscription,
1314
StripeSubscriptionItem,
1415
} from '@fxa/payments/stripe';
1516
import {
@@ -177,6 +178,21 @@ export class InvoiceManager {
177178
});
178179
}
179180

181+
async previewUpcomingSubscription({
182+
customer,
183+
subscription,
184+
}: {
185+
customer: StripeCustomer;
186+
subscription: StripeSubscription;
187+
}): Promise<InvoicePreview> {
188+
const upcomingInvoice = await this.stripeClient.invoicesRetrieveUpcoming({
189+
customer: customer.id,
190+
subscription: subscription.id,
191+
});
192+
193+
return stripeInvoiceToInvoicePreviewDTO(upcomingInvoice);
194+
}
195+
180196
/**
181197
* Fetch the invoice preview for the latest invoice associated with a cart
182198
*/

libs/payments/management/src/lib/subscriptionManagement.error.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,7 @@ export class SetDefaultPaymentAccountCustomerMissingStripeId extends Subscriptio
7777

7878
export class CreateBillingAgreementActiveBillingAgreement extends SubscriptionManagementError {
7979
constructor(uid: string) {
80-
super(
81-
'Account already has an active paypal billing agreement',
82-
{ uid }
83-
);
80+
super('Account already has an active paypal billing agreement', { uid });
8481
this.name = 'CreateBillingAgreementActiveBillingAgreement';
8582
}
8683
}
@@ -97,20 +94,16 @@ export class CreateBillingAgreementAccountCustomerMissingStripeId extends Subscr
9794

9895
export class CreateBillingAgreementCurrencyNotFound extends SubscriptionManagementError {
9996
constructor(uid: string) {
100-
super(
101-
'Currency could not be found for account',
102-
{ uid }
103-
);
97+
super('Currency could not be found for account', { uid });
10498
this.name = 'CreateBillingAgreementCurrencyNotFound';
10599
}
106100
}
107101

108102
export class CreateBillingAgreementPaypalSubscriptionNotFound extends SubscriptionManagementError {
109103
constructor(uid: string) {
110-
super(
111-
'No PayPal subscription found when creating billing agreement',
112-
{ uid }
113-
);
104+
super('No PayPal subscription found when creating billing agreement', {
105+
uid,
106+
});
114107
this.name = 'CreateBillingAgreementPaypalSubscriptionNotFound';
115108
}
116109
}
@@ -202,16 +195,9 @@ export class SubscriptionContentMissingLatestInvoiceError extends SubscriptionMa
202195
}
203196

204197
export class SubscriptionContentMissingUpcomingInvoicePreviewError extends SubscriptionManagementError {
205-
constructor(
206-
subscriptionId: string,
207-
priceId: string,
208-
currency: string,
209-
customer: StripeCustomer
210-
) {
198+
constructor(subscriptionId: string, customer: StripeCustomer) {
211199
super('Subscription is missing latest invoice preview', {
212200
subscriptionId,
213-
priceId,
214-
currency,
215201
customer,
216202
});
217203
this.name = 'SubscriptionContentMissingUpcomingInvoicePreviewError';

libs/payments/management/src/lib/subscriptionManagement.service.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,6 @@ export class SubscriptionManagementService {
374374
webIcon: string,
375375
supportUrl: string
376376
): Promise<SubscriptionContent> {
377-
const currency = subscription.currency;
378377
const latestInvoiceId = subscription.latest_invoice;
379378

380379
if (!latestInvoiceId) {
@@ -392,11 +391,13 @@ export class SubscriptionManagementService {
392391
}
393392

394393
const subplatInterval = getSubplatInterval(interval, intervalCount);
395-
const priceId = price.id;
396394

397395
const [latestInvoice, upcomingInvoice] = await Promise.all([
398396
this.invoiceManager.preview(latestInvoiceId),
399-
this.invoiceManager.previewUpcoming({ priceId, currency, customer }),
397+
this.invoiceManager.previewUpcomingSubscription({
398+
customer,
399+
subscription,
400+
}),
400401
]);
401402

402403
if (!latestInvoice) {
@@ -409,8 +410,6 @@ export class SubscriptionManagementService {
409410
if (!upcomingInvoice) {
410411
throw new SubscriptionContentMissingUpcomingInvoicePreviewError(
411412
subscription.id,
412-
price.id,
413-
currency,
414413
customer
415414
);
416415
}
@@ -427,6 +426,7 @@ export class SubscriptionManagementService {
427426

428427
const {
429428
nextInvoiceDate,
429+
promotionName: nextPromotionName,
430430
subsequentAmount,
431431
subsequentAmountExcludingTax,
432432
subsequentTax,
@@ -467,6 +467,7 @@ export class SubscriptionManagementService {
467467
nextInvoiceTotalExclusiveTax && nextInvoiceTotalExclusiveTax > 0
468468
? (subsequentAmountExcludingTax ?? subsequentAmount)
469469
: subsequentAmount,
470+
nextPromotionName,
470471
promotionName,
471472
cancelAtPeriodEnd: subscription.cancel_at_period_end,
472473
};

libs/payments/management/src/lib/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,6 @@ export interface SubscriptionContent {
5454
nextInvoiceDate: number;
5555
nextInvoiceTax?: number;
5656
nextInvoiceTotal?: number;
57+
nextPromotionName?: string | null;
5758
promotionName?: string | null;
5859
}

libs/payments/ui/src/lib/client/components/SubscriptionContent/en.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ subscription-content-current-billed-on-tax = <strong>{ $invoiceTotal } + { $taxD
1313
subscription-content-current-billed-on-no-tax = <strong>{ $invoiceTotal }</strong><span> billed on { $billedOnDate }</span>
1414
subscription-content-credit-issued-to-your-account = <strong>{ $creditApplied }</strong> credit issued to your account
1515
subscription-content-coupon-applied = { $promotionName } applied
16+
subscription-content-coupon-will-be-applied = { $promotionName } discount will be applied
1617
subscription-content-next-bill-excl-disc-with-tax = Next bill of <strong>{ $nextInvoiceTotal } + { $taxDue } tax</strong>, excluding discounts, is due on <strong>{ $nextBillDate }</strong>
1718
subscription-content-next-bill-excl-no-tax = Next bill of <strong>{ $nextInvoiceTotal }</strong>, excluding discounts, is due on <strong>{ $nextBillDate }</strong>
1819
subscription-content-heading-cancel-subscription = Cancel Subscription

libs/payments/ui/src/lib/client/components/SubscriptionContent/index.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ interface Subscription {
3636
nextInvoiceDate: number;
3737
nextInvoiceTax?: number;
3838
nextInvoiceTotal?: number;
39+
nextPromotionName?: string | null;
3940
promotionName?: string | null;
4041
}
4142

@@ -62,6 +63,7 @@ export const SubscriptionContent = ({
6263
currentPeriodEnd,
6364
nextInvoiceTax,
6465
nextInvoiceTotal,
66+
nextPromotionName,
6567
productName,
6668
webIcon,
6769
promotionName,
@@ -602,7 +604,7 @@ export const SubscriptionContent = ({
602604
</Localized>
603605
)}
604606
{nextInvoiceTotal !== undefined && nextInvoiceTotal >= 0 ? (
605-
<div className="text-sm">
607+
<div className="mt-2 text-sm">
606608
{nextInvoiceTax ? (
607609
<Localized
608610
id="subscription-content-next-bill-excl-disc-with-tax"
@@ -646,6 +648,18 @@ export const SubscriptionContent = ({
646648
)}
647649
</div>
648650
) : null}
651+
{nextPromotionName && (
652+
<Localized
653+
id="subscription-content-coupon-will-be-applied"
654+
vars={{
655+
promotionName: nextPromotionName,
656+
}}
657+
>
658+
<p className="font-bold text-sm text-violet-700">
659+
{nextPromotionName} discount will be applied
660+
</p>
661+
</Localized>
662+
)}
649663
</div>
650664
)}
651665
<Localized

0 commit comments

Comments
 (0)