Skip to content

Commit 5a98c57

Browse files
committed
feat(clerk-js,localizations,shared,ui): Add support for rendering discounts on subscription items
1 parent 38f347c commit 5a98c57

57 files changed

Lines changed: 526 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
'@clerk/localizations': patch
4+
'@clerk/shared': patch
5+
'@clerk/ui': patch
6+
---
7+
8+
Show applied discounts in the subscription overview.

‎packages/clerk-js/src/core/resources/BillingSubscription.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {
22
BillingCredits,
3+
BillingDiscountRedemption,
34
BillingMoneyAmount,
45
BillingSubscriptionItemJSON,
56
BillingSubscriptionItemNextPayment,
@@ -18,6 +19,7 @@ import { unixEpochToDate } from '@/utils/date';
1819

1920
import {
2021
billingCreditsFromJSON,
22+
billingDiscountRedemptionFromJSON,
2123
billingMoneyAmountFromJSON,
2224
billingPerUnitTotalTierFromJSON,
2325
billingSubscriptionItemNextPaymentFromJSON,
@@ -85,6 +87,7 @@ export class BillingSubscriptionItem extends BaseResource implements BillingSubs
8587
};
8688
seats?: BillingSubscriptionItemSeats;
8789
credits?: BillingCredits;
90+
appliedDiscount?: BillingDiscountRedemption;
8891
nextPayment?: BillingSubscriptionItemNextPayment | null;
8992
isFreeTrial!: boolean;
9093

@@ -122,6 +125,7 @@ export class BillingSubscriptionItem extends BaseResource implements BillingSubs
122125
: undefined;
123126

124127
this.credits = data.credits ? billingCreditsFromJSON(data.credits) : undefined;
128+
this.appliedDiscount = data.applied_discount ? billingDiscountRedemptionFromJSON(data.applied_discount) : undefined;
125129
this.nextPayment =
126130
data.next_payment === undefined
127131
? undefined

‎packages/clerk-js/src/utils/__tests__/billing.test.ts‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
import { describe, expect, it } from 'vitest';
99

1010
import {
11+
billingDiscountRedemptionFromJSON,
1112
billingPaymentTotalsFromJSON,
1213
billingSubscriptionItemNextPaymentFromJSON,
1314
billingSubscriptionNextPaymentFromJSON,
@@ -40,6 +41,15 @@ const nextPaymentTotalsJSON = (): BillingTotalsJSON => ({
4041
cycle_days_total: 30,
4142
cycle_passed_percent: 50,
4243
},
44+
discount: {
45+
amount: moneyJSON(1000),
46+
discount_id: 'discount_123',
47+
name: 'Welcome',
48+
effect: 'percentage',
49+
percent_off: 20,
50+
promo_code: 'WELCOME20',
51+
cycles_remaining: 2,
52+
},
4353
total: moneyJSON(500),
4454
},
4555
total_due_after_free_trial: null,
@@ -109,6 +119,14 @@ describe('billingPaymentTotalsFromJSON', () => {
109119
cycle_days_total: 30,
110120
cycle_passed_percent: 3,
111121
},
122+
discount: {
123+
amount: moneyJSON(100),
124+
discount_id: 'discount_123',
125+
name: 'Fixed discount',
126+
effect: 'fixed_amount',
127+
amount_off: moneyJSON(100),
128+
cycles_remaining: null,
129+
},
112130
total: moneyJSON(16),
113131
},
114132
};
@@ -117,6 +135,14 @@ describe('billingPaymentTotalsFromJSON', () => {
117135

118136
expect(totals.discounts?.proration?.amount.amount).toBe(16);
119137
expect(totals.discounts?.proration?.cycleDaysPassed).toBe(1);
138+
expect(totals.discounts?.discount).toMatchObject({
139+
amount: { amount: 100 },
140+
discountId: 'discount_123',
141+
name: 'Fixed discount',
142+
effect: 'fixed_amount',
143+
amountOff: { amount: 100, amountFormatted: '1.00', currency: 'USD', currencySymbol: '$' },
144+
cyclesRemaining: null,
145+
});
120146
expect(totals.discounts?.total.amount).toBe(16);
121147
});
122148

@@ -209,6 +235,15 @@ describe('billingSubscriptionNextPaymentFromJSON', () => {
209235
cycleDaysTotal: 30,
210236
cyclePassedPercent: 50,
211237
},
238+
discount: {
239+
amount: { amount: 1000 },
240+
discountId: 'discount_123',
241+
name: 'Welcome',
242+
effect: 'percentage',
243+
percentOff: 20,
244+
promoCode: 'WELCOME20',
245+
cyclesRemaining: 2,
246+
},
212247
total: { amount: 500 },
213248
},
214249
perUnitTotals: [{ name: 'seats', blockSize: 1 }],
@@ -223,6 +258,55 @@ describe('billingSubscriptionNextPaymentFromJSON', () => {
223258
});
224259
});
225260

261+
describe('billingDiscountRedemptionFromJSON', () => {
262+
it('maps an applied subscription item discount', () => {
263+
const discount = billingDiscountRedemptionFromJSON({
264+
object: 'commerce_discount_redemption',
265+
id: 'redemption_123',
266+
subscription_item_id: 'sub_item_123',
267+
discount_id: 'discount_123',
268+
name: 'Welcome',
269+
source: 'promo_code',
270+
promo_code: 'WELCOME20',
271+
effect: 'fixed_amount',
272+
amount_off: moneyJSON(500),
273+
amount: moneyJSON(400),
274+
cycles_remaining: 2,
275+
cycles_applied: 1,
276+
status: 'active',
277+
redeemed_at: 1_609_459_200_000,
278+
redeemed_by: 'user_123',
279+
});
280+
281+
expect(discount).toMatchObject({
282+
id: 'redemption_123',
283+
subscriptionItemId: 'sub_item_123',
284+
discountId: 'discount_123',
285+
name: 'Welcome',
286+
source: 'promo_code',
287+
promoCode: 'WELCOME20',
288+
effect: 'fixed_amount',
289+
amountOff: {
290+
amount: 500,
291+
amountFormatted: '5.00',
292+
currency: 'USD',
293+
currencySymbol: '$',
294+
},
295+
amount: {
296+
amount: 400,
297+
amountFormatted: '4.00',
298+
currency: 'USD',
299+
currencySymbol: '$',
300+
},
301+
cyclesRemaining: 2,
302+
cyclesApplied: 1,
303+
status: 'active',
304+
redeemedAt: new Date('2021-01-01T00:00:00.000Z'),
305+
redeemedBy: 'user_123',
306+
});
307+
});
308+
});
309+
226310
describe('billingSubscriptionItemNextPaymentFromJSON', () => {
227311
it('maps amount, date, and per_unit_totals', () => {
228312
const data: BillingSubscriptionItemNextPaymentJSON = {

‎packages/clerk-js/src/utils/billing.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import type {
2+
BillingAppliedDiscount,
3+
BillingAppliedDiscountJSON,
24
BillingCheckoutTotals,
35
BillingCheckoutTotalsJSON,
46
BillingCredits,
57
BillingCreditsJSON,
8+
BillingDiscountRedemption,
9+
BillingDiscountRedemptionJSON,
610
BillingDiscounts,
711
BillingDiscountsJSON,
812
BillingMoneyAmount,
@@ -105,10 +109,40 @@ const billingDiscountsFromJSON = (data: BillingDiscountsJSON): BillingDiscounts
105109
cyclePassedPercent: data.proration.cycle_passed_percent,
106110
}
107111
: null,
112+
discount: data.discount ? billingAppliedDiscountFromJSON(data.discount) : undefined,
108113
total: billingMoneyAmountFromJSON(data.total),
109114
};
110115
};
111116

117+
const billingAppliedDiscountFromJSON = (data: BillingAppliedDiscountJSON): BillingAppliedDiscount => ({
118+
amount: billingMoneyAmountFromJSON(data.amount),
119+
discountId: data.discount_id,
120+
name: data.name,
121+
effect: data.effect,
122+
percentOff: data.percent_off,
123+
amountOff: data.amount_off ? billingMoneyAmountFromJSON(data.amount_off) : undefined,
124+
promoCode: data.promo_code,
125+
cyclesRemaining: data.cycles_remaining,
126+
});
127+
128+
export const billingDiscountRedemptionFromJSON = (data: BillingDiscountRedemptionJSON): BillingDiscountRedemption => ({
129+
id: data.id,
130+
subscriptionItemId: data.subscription_item_id,
131+
discountId: data.discount_id,
132+
name: data.name,
133+
source: data.source,
134+
promoCode: data.promo_code,
135+
effect: data.effect,
136+
percentOff: data.percent_off,
137+
amountOff: data.amount_off ? billingMoneyAmountFromJSON(data.amount_off) : undefined,
138+
amount: data.amount ? billingMoneyAmountFromJSON(data.amount) : undefined,
139+
cyclesRemaining: data.cycles_remaining,
140+
cyclesApplied: data.cycles_applied,
141+
status: data.status,
142+
redeemedAt: unixEpochToDate(data.redeemed_at),
143+
redeemedBy: data.redeemed_by,
144+
});
145+
112146
const billingPeriodTotalsFromJSON = (data: BillingPeriodTotalsJSON): BillingPeriodTotals => {
113147
return {
114148
subtotal: billingMoneyAmountFromJSON(data.subtotal),

‎packages/localizations/src/ar-SA.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ export const arSA: LocalizationResource = {
126126
credit: undefined,
127127
creditRemainder: undefined,
128128
defaultFreePlanActive: undefined,
129+
discountAmount: undefined,
130+
discountCyclesRemaining: undefined,
131+
discountDuration: undefined,
129132
free: undefined,
130133
getStarted: undefined,
131134
highlightedPlanBadge: undefined,
@@ -137,6 +140,7 @@ export const arSA: LocalizationResource = {
137140
monthAbbreviation: undefined,
138141
monthPerUnit: undefined,
139142
monthly: undefined,
143+
months: undefined,
140144
pastDue: undefined,
141145
pay: undefined,
142146
payerCreditRemainder: undefined,
@@ -215,6 +219,7 @@ export const arSA: LocalizationResource = {
215219
year: undefined,
216220
yearAbbreviation: undefined,
217221
yearPerUnit: undefined,
222+
years: undefined,
218223
},
219224
configureSSO: {
220225
activate: {

‎packages/localizations/src/be-BY.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ export const beBY: LocalizationResource = {
126126
credit: undefined,
127127
creditRemainder: undefined,
128128
defaultFreePlanActive: undefined,
129+
discountAmount: undefined,
130+
discountCyclesRemaining: undefined,
131+
discountDuration: undefined,
129132
free: undefined,
130133
getStarted: undefined,
131134
highlightedPlanBadge: undefined,
@@ -137,6 +140,7 @@ export const beBY: LocalizationResource = {
137140
monthAbbreviation: undefined,
138141
monthPerUnit: undefined,
139142
monthly: undefined,
143+
months: undefined,
140144
pastDue: undefined,
141145
pay: undefined,
142146
payerCreditRemainder: undefined,
@@ -215,6 +219,7 @@ export const beBY: LocalizationResource = {
215219
year: undefined,
216220
yearAbbreviation: undefined,
217221
yearPerUnit: undefined,
222+
years: undefined,
218223
},
219224
configureSSO: {
220225
activate: {

‎packages/localizations/src/bg-BG.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ export const bgBG: LocalizationResource = {
127127
credit: undefined,
128128
creditRemainder: undefined,
129129
defaultFreePlanActive: undefined,
130+
discountAmount: undefined,
131+
discountCyclesRemaining: undefined,
132+
discountDuration: undefined,
130133
free: undefined,
131134
getStarted: undefined,
132135
highlightedPlanBadge: undefined,
@@ -138,6 +141,7 @@ export const bgBG: LocalizationResource = {
138141
monthAbbreviation: undefined,
139142
monthPerUnit: undefined,
140143
monthly: undefined,
144+
months: undefined,
141145
pastDue: undefined,
142146
pay: undefined,
143147
payerCreditRemainder: undefined,
@@ -216,6 +220,7 @@ export const bgBG: LocalizationResource = {
216220
year: undefined,
217221
yearAbbreviation: undefined,
218222
yearPerUnit: undefined,
223+
years: undefined,
219224
},
220225
configureSSO: {
221226
activate: {

‎packages/localizations/src/bn-IN.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ export const bnIN: LocalizationResource = {
132132
credit: 'ক্রেডিট',
133133
creditRemainder: 'আপনার বর্তমান সাবস্ক্রিপশনের অবশিষ্ট সময়ের জন্য ক্রেডিট।',
134134
defaultFreePlanActive: 'আপনি বর্তমানে বিনামূল্যের প্ল্যানে আছেন',
135+
discountAmount: undefined,
136+
discountCyclesRemaining: undefined,
137+
discountDuration: undefined,
135138
free: 'বিনামূল্যে',
136139
getStarted: 'শুরু করুন',
137140
highlightedPlanBadge: 'জনপ্রিয়',
@@ -143,6 +146,7 @@ export const bnIN: LocalizationResource = {
143146
monthAbbreviation: undefined,
144147
monthPerUnit: undefined,
145148
monthly: 'মাসিক',
149+
months: undefined,
146150
pastDue: 'বকেয়া',
147151
pay: '{{amount}} পরিশোধ করুন',
148152
payerCreditRemainder: undefined,
@@ -221,6 +225,7 @@ export const bnIN: LocalizationResource = {
221225
year: 'বছর',
222226
yearAbbreviation: undefined,
223227
yearPerUnit: undefined,
228+
years: undefined,
224229
},
225230
configureSSO: {
226231
activate: {

‎packages/localizations/src/ca-ES.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ export const caES: LocalizationResource = {
133133
credit: 'Crèdit',
134134
creditRemainder: 'Crèdit pel temps restant de la teva subscripció actual.',
135135
defaultFreePlanActive: 'Estàs al pla gratuït',
136+
discountAmount: undefined,
137+
discountCyclesRemaining: undefined,
138+
discountDuration: undefined,
136139
free: 'Gratuït',
137140
getStarted: 'Comença',
138141
highlightedPlanBadge: 'Popular',
@@ -144,6 +147,7 @@ export const caES: LocalizationResource = {
144147
monthAbbreviation: undefined,
145148
monthPerUnit: undefined,
146149
monthly: 'Mensual',
150+
months: undefined,
147151
pastDue: 'Pagament pendent',
148152
pay: 'Paga {{amount}}',
149153
payerCreditRemainder: undefined,
@@ -222,6 +226,7 @@ export const caES: LocalizationResource = {
222226
year: 'Any',
223227
yearAbbreviation: undefined,
224228
yearPerUnit: undefined,
229+
years: undefined,
225230
},
226231
configureSSO: {
227232
activate: {

‎packages/localizations/src/cs-CZ.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ export const csCZ: LocalizationResource = {
130130
credit: 'Kredit',
131131
creditRemainder: 'Kredit za zbytek vašeho současného předplatného.',
132132
defaultFreePlanActive: 'Aktuálně používáte bezplatný plán',
133+
discountAmount: undefined,
134+
discountCyclesRemaining: undefined,
135+
discountDuration: undefined,
133136
free: 'Zdarma',
134137
getStarted: 'Začít',
135138
highlightedPlanBadge: 'Populární',
@@ -141,6 +144,7 @@ export const csCZ: LocalizationResource = {
141144
monthAbbreviation: undefined,
142145
monthPerUnit: undefined,
143146
monthly: 'Měsíčně',
147+
months: undefined,
144148
pastDue: 'Po splatnosti',
145149
pay: 'Zaplatit {{amount}}',
146150
payerCreditRemainder: undefined,
@@ -219,6 +223,7 @@ export const csCZ: LocalizationResource = {
219223
year: 'Rok',
220224
yearAbbreviation: undefined,
221225
yearPerUnit: undefined,
226+
years: undefined,
222227
},
223228
configureSSO: {
224229
activate: {

0 commit comments

Comments
 (0)