@carlonicora/nextjs-jsonapi 5.5.0 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BlockNoteEditor-THBJH2UR.js → BlockNoteEditor-AE3ZTEEH.js} +19 -19
- package/dist/{BlockNoteEditor-THBJH2UR.js.map → BlockNoteEditor-AE3ZTEEH.js.map} +1 -1
- package/dist/{BlockNoteEditor-GVQCKRMU.mjs → BlockNoteEditor-NEYG5L6W.mjs} +4 -4
- package/dist/{BlockNoteViewer-U6E3DS4F.mjs → BlockNoteViewer-3OUT5UFY.mjs} +2 -2
- package/dist/{BlockNoteViewer-VZOVKTFO.js → BlockNoteViewer-4CFKQM7U.js} +3 -3
- package/dist/{BlockNoteViewer-VZOVKTFO.js.map → BlockNoteViewer-4CFKQM7U.js.map} +1 -1
- package/dist/billing/index.d.mts +186 -25
- package/dist/billing/index.d.ts +186 -25
- package/dist/billing/index.js +1148 -587
- package/dist/billing/index.js.map +1 -1
- package/dist/billing/index.mjs +785 -224
- package/dist/billing/index.mjs.map +1 -1
- package/dist/{chunk-WPYT4UKJ.js → chunk-3I5AAJXV.js} +3 -3
- package/dist/{chunk-WPYT4UKJ.js.map → chunk-3I5AAJXV.js.map} +1 -1
- package/dist/{chunk-YDYVJM4I.js → chunk-53BAULDP.js} +4 -4
- package/dist/{chunk-YDYVJM4I.js.map → chunk-53BAULDP.js.map} +1 -1
- package/dist/{chunk-3CGROPF3.mjs → chunk-BTPUYUQ3.mjs} +2 -2
- package/dist/{chunk-VC5A3AUZ.mjs → chunk-EYA2F6AN.mjs} +696 -4
- package/dist/chunk-EYA2F6AN.mjs.map +1 -0
- package/dist/{chunk-G6UE6MEP.js → chunk-HKZAESS6.js} +896 -204
- package/dist/chunk-HKZAESS6.js.map +1 -0
- package/dist/{chunk-W7F5BDSV.mjs → chunk-KQHBONL4.mjs} +2 -2
- package/dist/{chunk-TY5FYP3G.js → chunk-LQ4DK4JF.js} +1919 -1056
- package/dist/chunk-LQ4DK4JF.js.map +1 -0
- package/dist/{chunk-S4YJ5WCW.js → chunk-RL3PL3KV.js} +8 -8
- package/dist/{chunk-S4YJ5WCW.js.map → chunk-RL3PL3KV.js.map} +1 -1
- package/dist/{chunk-X5WIUUNG.mjs → chunk-SFYPXCK4.mjs} +3237 -2374
- package/dist/chunk-SFYPXCK4.mjs.map +1 -0
- package/dist/{chunk-6SQYXUVP.mjs → chunk-Y7RR57EW.mjs} +2 -2
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.js +4 -4
- package/dist/components/index.mjs +3 -3
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +242 -3
- package/dist/core/index.d.ts +242 -3
- package/dist/core/index.js +34 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +33 -1
- package/dist/email-automations/index.js +92 -92
- package/dist/email-automations/index.mjs +3 -3
- package/dist/features/analytics/index.js +121 -121
- package/dist/features/analytics/index.mjs +4 -4
- package/dist/features/analytics/server.js +4 -4
- package/dist/features/analytics/server.mjs +2 -2
- package/dist/features/blog/index.js +150 -150
- package/dist/features/blog/index.mjs +3 -3
- package/dist/features/blog/server.js +16 -16
- package/dist/features/blog/server.mjs +1 -1
- package/dist/features/help/index.js +37 -37
- package/dist/features/help/index.mjs +3 -3
- package/dist/features/tokenusage/index.js +124 -124
- package/dist/features/tokenusage/index.mjs +4 -4
- package/dist/{stripe-promotion-code.interface-C3qqh3mi.d.ts → format-options-BtX3o0Yu.d.ts} +104 -1
- package/dist/{stripe-promotion-code.interface-ClZ7DxS9.d.mts → format-options-D6c7FNDi.d.mts} +104 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +35 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +34 -2
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +2 -2
- package/messages/en.json +125 -3
- package/messages/it.json +125 -3
- package/package.json +1 -1
- package/scripts/validate-messages.mjs +1 -0
- package/src/billing/index.ts +1 -0
- package/src/client/index.ts +4 -0
- package/src/core/index.ts +1 -0
- package/src/core/registry/ModuleRegistry.ts +3 -0
- package/src/features/billing/components/cards/InvoicesSummaryCard.tsx +3 -2
- package/src/features/billing/components/cards/SubscriptionSummaryCard.tsx +4 -3
- package/src/features/billing/components/containers/BillingDashboardContainer.tsx +22 -5
- package/src/features/billing/components/utils/__tests__/discount.spec.ts +59 -0
- package/src/features/billing/components/utils/__tests__/url.spec.ts +32 -0
- package/src/features/billing/components/utils/discount.ts +38 -0
- package/src/features/billing/components/utils/index.ts +2 -0
- package/src/features/billing/components/utils/url.ts +12 -0
- package/src/features/billing/contexts/CouponContext.tsx +211 -0
- package/src/features/billing/contexts/index.ts +1 -0
- package/src/features/billing/i18n-keys.ts +79 -0
- package/src/features/billing/stripe-coupon/components/containers/CouponContainer.tsx +38 -0
- package/src/features/billing/stripe-coupon/components/containers/index.ts +1 -0
- package/src/features/billing/stripe-coupon/components/details/CouponDetails.tsx +69 -0
- package/src/features/billing/stripe-coupon/components/details/index.ts +1 -0
- package/src/features/billing/stripe-coupon/components/forms/CouponCodeEditor.tsx +79 -0
- package/src/features/billing/stripe-coupon/components/forms/CouponCodeFields.tsx +192 -0
- package/src/features/billing/stripe-coupon/components/forms/CouponEditor.tsx +333 -0
- package/src/features/billing/stripe-coupon/components/forms/CouponRefresher.tsx +45 -0
- package/src/features/billing/stripe-coupon/components/forms/index.ts +6 -0
- package/src/features/billing/stripe-coupon/components/index.ts +5 -0
- package/src/features/billing/stripe-coupon/components/lists/CouponCodesList.tsx +61 -0
- package/src/features/billing/stripe-coupon/components/lists/CouponsList.tsx +67 -0
- package/src/features/billing/stripe-coupon/components/lists/index.ts +2 -0
- package/src/features/billing/stripe-coupon/components/widgets/CouponCodeActions.tsx +80 -0
- package/src/features/billing/stripe-coupon/components/widgets/index.ts +1 -0
- package/src/features/billing/stripe-coupon/data/__tests__/stripe-coupon-models.spec.ts +114 -0
- package/src/features/billing/stripe-coupon/data/index.ts +10 -0
- package/src/features/billing/stripe-coupon/data/stripe-coupon-code.fields.ts +8 -0
- package/src/features/billing/stripe-coupon/data/stripe-coupon-code.interface.ts +44 -0
- package/src/features/billing/stripe-coupon/data/stripe-coupon-code.service.ts +92 -0
- package/src/features/billing/stripe-coupon/data/stripe-coupon-code.ts +132 -0
- package/src/features/billing/stripe-coupon/data/stripe-coupon-offer.interface.ts +17 -0
- package/src/features/billing/stripe-coupon/data/stripe-coupon-offer.ts +70 -0
- package/src/features/billing/stripe-coupon/data/stripe-coupon.fields.ts +7 -0
- package/src/features/billing/stripe-coupon/data/stripe-coupon.interface.ts +51 -0
- package/src/features/billing/stripe-coupon/data/stripe-coupon.service.ts +93 -0
- package/src/features/billing/stripe-coupon/data/stripe-coupon.ts +165 -0
- package/src/features/billing/stripe-coupon/hooks/useStripeCouponCodeTableStructure.tsx +154 -0
- package/src/features/billing/stripe-coupon/hooks/useStripeCouponTableStructure.tsx +113 -0
- package/src/features/billing/stripe-coupon/index.ts +5 -0
- package/src/features/billing/stripe-coupon/stripe-coupon-code.module.ts +14 -0
- package/src/features/billing/stripe-coupon/stripe-coupon-offer.module.ts +10 -0
- package/src/features/billing/stripe-coupon/stripe-coupon.module.ts +18 -0
- package/src/features/billing/stripe-coupon/utils/__tests__/coupon-places.spec.ts +19 -0
- package/src/features/billing/stripe-coupon/utils/__tests__/to-promotion-code-result.spec.ts +41 -0
- package/src/features/billing/stripe-coupon/utils/coupon-places.ts +23 -0
- package/src/features/billing/stripe-coupon/utils/format-coupon-discount.ts +36 -0
- package/src/features/billing/stripe-coupon/utils/index.ts +3 -0
- package/src/features/billing/stripe-coupon/utils/to-promotion-code-result.ts +23 -0
- package/src/features/billing/stripe-customer/components/forms/PaymentMethodForm.tsx +24 -8
- package/src/features/billing/stripe-product/components/containers/ProductsListContainer.tsx +21 -6
- package/src/features/billing/stripe-promotion-code/components/PromoCodeInput.tsx +60 -19
- package/src/features/billing/stripe-promotion-code/data/stripe-promotion-code.interface.ts +2 -0
- package/src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx +24 -5
- package/src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx +21 -2
- package/src/features/billing/stripe-subscription/components/wizards/SubscriptionWizard.tsx +1 -1
- package/src/features/billing/stripe-subscription/components/wizards/WizardStepPaymentMethod.tsx +1 -1
- package/src/features/billing/stripe-subscription/components/wizards/WizardStepReview.tsx +60 -49
- package/src/features/billing/stripe-subscription/components/wizards/__tests__/review-card.spec.ts +34 -0
- package/src/features/billing/stripe-subscription/components/wizards/review-card.ts +24 -0
- package/src/features/billing/stripe-subscription/data/__tests__/stripe-subscription.discount.spec.ts +67 -0
- package/src/features/billing/stripe-subscription/data/stripe-subscription.interface.ts +14 -0
- package/src/features/billing/stripe-subscription/data/stripe-subscription.service.ts +39 -0
- package/src/features/billing/stripe-subscription/data/stripe-subscription.ts +21 -0
- package/src/features/billing/stripe-subscription/hooks/__tests__/wizard-payment.spec.ts +48 -0
- package/src/features/billing/stripe-subscription/hooks/__tests__/wizard-promotion.spec.ts +132 -0
- package/src/features/billing/stripe-subscription/hooks/useSubscriptionWizard.ts +138 -12
- package/src/features/billing/stripe-subscription/hooks/wizard-payment.ts +30 -0
- package/src/features/billing/stripe-subscription/hooks/wizard-promotion.ts +54 -0
- package/src/features/user/contexts/CurrentUserContext.tsx +25 -0
- package/src/features/user/contexts/__tests__/CurrentUserContext.spec.tsx +121 -2
- package/dist/chunk-G6UE6MEP.js.map +0 -1
- package/dist/chunk-TY5FYP3G.js.map +0 -1
- package/dist/chunk-VC5A3AUZ.mjs.map +0 -1
- package/dist/chunk-X5WIUUNG.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-GVQCKRMU.mjs.map → BlockNoteEditor-NEYG5L6W.mjs.map} +0 -0
- /package/dist/{BlockNoteViewer-U6E3DS4F.mjs.map → BlockNoteViewer-3OUT5UFY.mjs.map} +0 -0
- /package/dist/{chunk-3CGROPF3.mjs.map → chunk-BTPUYUQ3.mjs.map} +0 -0
- /package/dist/{chunk-W7F5BDSV.mjs.map → chunk-KQHBONL4.mjs.map} +0 -0
- /package/dist/{chunk-6SQYXUVP.mjs.map → chunk-Y7RR57EW.mjs.map} +0 -0
package/dist/server/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunk53BAULDPjs = require('../chunk-53BAULDP.js');
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
var _chunkIK65TDQHjs = require('../chunk-IK65TDQH.js');
|
|
@@ -35,7 +35,7 @@ require('../chunk-G4JR3SFV.js');
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
var
|
|
38
|
+
var _chunkHKZAESS6js = require('../chunk-HKZAESS6.js');
|
|
39
39
|
require('../chunk-GHE4VT2R.js');
|
|
40
40
|
require('../chunk-LXKSUWAV.js');
|
|
41
41
|
require('../chunk-JTPRJNT2.js');
|
|
@@ -66,5 +66,5 @@ require('../chunk-7QVYU63E.js');
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
exports.CACHE_PROFILES = _chunkIK65TDQHjs.CACHE_PROFILES; exports.ServerAuthService =
|
|
69
|
+
exports.CACHE_PROFILES = _chunkIK65TDQHjs.CACHE_PROFILES; exports.ServerAuthService = _chunkHKZAESS6js.AuthService; exports.ServerCompanyService = _chunkHKZAESS6js.CompanyService; exports.ServerContentService = _chunkHKZAESS6js.ContentService; exports.ServerFeatureService = _chunkHKZAESS6js.FeatureService; exports.ServerJsonApiDelete = _chunkKSYQEKJXjs.ServerJsonApiDelete; exports.ServerJsonApiGet = _chunkKSYQEKJXjs.ServerJsonApiGet; exports.ServerJsonApiPatch = _chunkKSYQEKJXjs.ServerJsonApiPatch; exports.ServerJsonApiPost = _chunkKSYQEKJXjs.ServerJsonApiPost; exports.ServerJsonApiPut = _chunkKSYQEKJXjs.ServerJsonApiPut; exports.ServerNotificationService = _chunkHKZAESS6js.NotificationService; exports.ServerPushService = _chunkHKZAESS6js.PushService; exports.ServerRoleService = _chunkHKZAESS6js.RoleService; exports.ServerS3Service = _chunkHKZAESS6js.S3Service; exports.ServerSession = _chunk53BAULDPjs.ServerSession; exports.ServerUserService = _chunkHKZAESS6js.UserService; exports.configureServerJsonApi = _chunkKSYQEKJXjs.configureServerJsonApi; exports.getServerApiUrl = _chunkKSYQEKJXjs.getServerApiUrl; exports.getServerAppUrl = _chunkKSYQEKJXjs.getServerAppUrl; exports.getServerToken = _chunkYUO55Q5Ajs.getServerToken; exports.getServerTrackablePages = _chunkKSYQEKJXjs.getServerTrackablePages; exports.invalidateCacheTag = _chunk53BAULDPjs.invalidateCacheTag; exports.invalidateCacheTags = _chunk53BAULDPjs.invalidateCacheTags; exports.serverRequest = _chunk6TUB7Y6Tjs.serverRequest; exports.updateCacheTag = _chunk53BAULDPjs.updateCacheTag;
|
|
70
70
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
invalidateCacheTag,
|
|
4
4
|
invalidateCacheTags,
|
|
5
5
|
updateCacheTag
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-BTPUYUQ3.mjs";
|
|
7
7
|
import {
|
|
8
8
|
CACHE_PROFILES
|
|
9
9
|
} from "../chunk-T7SAW3UW.mjs";
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
RoleService,
|
|
36
36
|
S3Service,
|
|
37
37
|
UserService
|
|
38
|
-
} from "../chunk-
|
|
38
|
+
} from "../chunk-EYA2F6AN.mjs";
|
|
39
39
|
import "../chunk-V5MI7RNB.mjs";
|
|
40
40
|
import "../chunk-AUXK7QSA.mjs";
|
|
41
41
|
import "../chunk-PI56A4DG.mjs";
|
package/messages/en.json
CHANGED
|
@@ -297,6 +297,118 @@
|
|
|
297
297
|
},
|
|
298
298
|
"billing": {
|
|
299
299
|
"admin": {
|
|
300
|
+
"coupons": {
|
|
301
|
+
"all_products": "All products",
|
|
302
|
+
"codes": {
|
|
303
|
+
"automatic": "Automatic",
|
|
304
|
+
"deactivate": {
|
|
305
|
+
"confirm": "Deactivate",
|
|
306
|
+
"description": "Nobody will be able to use \"{code}\" until you reactivate it. Subscriptions that already have it keep their discount.",
|
|
307
|
+
"title": "Deactivate code"
|
|
308
|
+
},
|
|
309
|
+
"empty": {
|
|
310
|
+
"description": "Add a code customers can use to get this coupon's discount.",
|
|
311
|
+
"title": "No codes yet"
|
|
312
|
+
},
|
|
313
|
+
"entity": "code",
|
|
314
|
+
"never": "Never",
|
|
315
|
+
"reactivate": {
|
|
316
|
+
"confirm": "Reactivate",
|
|
317
|
+
"description": "\"{code}\" can be used again.",
|
|
318
|
+
"title": "Reactivate code"
|
|
319
|
+
},
|
|
320
|
+
"status": {
|
|
321
|
+
"active": "Active",
|
|
322
|
+
"expired": "Expired",
|
|
323
|
+
"inactive": "Inactive",
|
|
324
|
+
"used_up": "Used up"
|
|
325
|
+
},
|
|
326
|
+
"title": "Codes"
|
|
327
|
+
},
|
|
328
|
+
"discount": {
|
|
329
|
+
"amount": "{amount} off",
|
|
330
|
+
"forever": "forever",
|
|
331
|
+
"once": "once",
|
|
332
|
+
"percent": "{value}% off",
|
|
333
|
+
"repeating": "for {months} months"
|
|
334
|
+
},
|
|
335
|
+
"duration": {
|
|
336
|
+
"forever": "Forever",
|
|
337
|
+
"once": "Once",
|
|
338
|
+
"repeating": "For a number of months"
|
|
339
|
+
},
|
|
340
|
+
"empty": {
|
|
341
|
+
"description": "Create a coupon to offer a discount at checkout.",
|
|
342
|
+
"title": "No coupons yet"
|
|
343
|
+
},
|
|
344
|
+
"entity": "coupon",
|
|
345
|
+
"errors": {
|
|
346
|
+
"amountOff": "Enter an amount above zero",
|
|
347
|
+
"durationInMonths": "Enter at least one month",
|
|
348
|
+
"minimumAmount": "Enter an amount above zero, or leave it empty",
|
|
349
|
+
"name": "A coupon name is required",
|
|
350
|
+
"percentOff": "Enter a percentage between 0.01 and 100"
|
|
351
|
+
},
|
|
352
|
+
"fields": {
|
|
353
|
+
"amountOff": "Amount off",
|
|
354
|
+
"automatic": "Apply automatically",
|
|
355
|
+
"code": "Code",
|
|
356
|
+
"codeMaxRedemptions": "Usage limit for this code",
|
|
357
|
+
"codes": "Codes",
|
|
358
|
+
"currency": "Currency",
|
|
359
|
+
"discount": "Discount",
|
|
360
|
+
"discountType": "Type",
|
|
361
|
+
"duration": "Duration",
|
|
362
|
+
"durationInMonths": "Months",
|
|
363
|
+
"expiresAt": "Expires",
|
|
364
|
+
"firstTimeTransaction": "First-time customers only",
|
|
365
|
+
"first_time_warning": "A GM coming from a trial counts as an existing customer, so leave this off for codes meant for trial GMs.",
|
|
366
|
+
"maxRedemptions": "Total usage limit",
|
|
367
|
+
"minimumAmount": "Minimum order amount",
|
|
368
|
+
"name": "Name",
|
|
369
|
+
"percentOff": "Percentage off",
|
|
370
|
+
"places": "Places",
|
|
371
|
+
"products": "Apply to specific products",
|
|
372
|
+
"redeemBy": "Redeem by",
|
|
373
|
+
"status": "Status",
|
|
374
|
+
"stripeCouponId": "ID",
|
|
375
|
+
"stripeCustomerId": "Limit to one customer",
|
|
376
|
+
"stripeId": "Stripe coupon ID"
|
|
377
|
+
},
|
|
378
|
+
"first_code": "First code",
|
|
379
|
+
"fixed_notice": "Stripe doesn't allow changing the discount, duration or limits of a coupon. Make a new coupon instead.",
|
|
380
|
+
"help": {
|
|
381
|
+
"automatic": "Applied by itself when a GM first pays. Only one code can have it: switching it on here switches it off on the code that had it.",
|
|
382
|
+
"products": "Leave empty to apply the coupon to every product."
|
|
383
|
+
},
|
|
384
|
+
"placeholders": {
|
|
385
|
+
"amountOff": "5.00",
|
|
386
|
+
"code": "Leave empty to let Stripe generate one",
|
|
387
|
+
"durationInMonths": "3",
|
|
388
|
+
"maxRedemptions": "No limit",
|
|
389
|
+
"minimumAmount": "0.00",
|
|
390
|
+
"name": "e.g. Founding GM",
|
|
391
|
+
"percentOff": "20",
|
|
392
|
+
"stripeCouponId": "Leave empty to let Stripe generate one"
|
|
393
|
+
},
|
|
394
|
+
"places": {
|
|
395
|
+
"limited": "{used} / {limit}",
|
|
396
|
+
"no_limit": "No limit",
|
|
397
|
+
"unlimited": "{used} / no limit"
|
|
398
|
+
},
|
|
399
|
+
"refresh": {
|
|
400
|
+
"title": "Refresh counts"
|
|
401
|
+
},
|
|
402
|
+
"status": {
|
|
403
|
+
"active": "Active",
|
|
404
|
+
"ended": "Ended"
|
|
405
|
+
},
|
|
406
|
+
"title": "Coupons",
|
|
407
|
+
"type": {
|
|
408
|
+
"amount_off": "Fixed amount off",
|
|
409
|
+
"percent_off": "Percentage off"
|
|
410
|
+
}
|
|
411
|
+
},
|
|
300
412
|
"prices": {
|
|
301
413
|
"actions": {
|
|
302
414
|
"addFeature": "Add feature",
|
|
@@ -430,7 +542,9 @@
|
|
|
430
542
|
"archived": "Archived"
|
|
431
543
|
},
|
|
432
544
|
"tabs": {
|
|
433
|
-
"
|
|
545
|
+
"codes": "Codes",
|
|
546
|
+
"details": "Details",
|
|
547
|
+
"products": "Products"
|
|
434
548
|
},
|
|
435
549
|
"title": "Products"
|
|
436
550
|
}
|
|
@@ -626,10 +740,12 @@
|
|
|
626
740
|
},
|
|
627
741
|
"promo_code": {
|
|
628
742
|
"apply": "Apply",
|
|
743
|
+
"automatic_label": "Applied automatically",
|
|
629
744
|
"duration_forever": "Applied to all payments",
|
|
630
745
|
"duration_once": "Applied to first payment only",
|
|
631
746
|
"duration_repeating": "Applied for {months} months",
|
|
632
|
-
"placeholder": "Enter promo code"
|
|
747
|
+
"placeholder": "Enter promo code",
|
|
748
|
+
"use_different": "Have a different code?"
|
|
633
749
|
},
|
|
634
750
|
"proration": {
|
|
635
751
|
"net_due_today": "Net Due Today",
|
|
@@ -698,8 +814,10 @@
|
|
|
698
814
|
"change_title": "Change Subscription Plan",
|
|
699
815
|
"errors": {
|
|
700
816
|
"already_subscribed": "You already have an active subscription. Please change your existing plan instead.",
|
|
817
|
+
"discount_unavailable": "This discount is no longer available. You have not been charged.",
|
|
701
818
|
"invalid_promo_code": "Invalid promotion code",
|
|
702
819
|
"payment_method_required": "A payment method is required to upgrade from your trial.",
|
|
820
|
+
"payment_not_confirmed": "The payment was not completed. {reason} Nothing was charged: try again, or add another card.",
|
|
703
821
|
"prepare_review": "Failed to prepare review",
|
|
704
822
|
"process_subscription": "Failed to process subscription",
|
|
705
823
|
"validate_promo_code": "Failed to validate promotion code"
|
|
@@ -708,6 +826,7 @@
|
|
|
708
826
|
"payment_description": "Enter your card details to complete your subscription",
|
|
709
827
|
"review": {
|
|
710
828
|
"amount_off": "{amount} off",
|
|
829
|
+
"card_on_file": "{brand} ending in {last4}",
|
|
711
830
|
"charge_now": "Amount to charge now:",
|
|
712
831
|
"confirm_change": "Confirm Plan Change",
|
|
713
832
|
"due_now": "Amount due now:",
|
|
@@ -725,7 +844,10 @@
|
|
|
725
844
|
"selected_plan": "Selected Plan",
|
|
726
845
|
"subscribe_now": "Subscribe Now",
|
|
727
846
|
"trial_upgrade": "Trial Upgrade",
|
|
728
|
-
"trial_upgrade_description": "Your trial will end immediately and you will be charged the full price."
|
|
847
|
+
"trial_upgrade_description": "Your trial will end immediately and you will be charged the full price.",
|
|
848
|
+
"trial_upgrade_description_discounted": "Your trial will end immediately and you will be charged the discounted price.",
|
|
849
|
+
"use_another_card": "Use another card",
|
|
850
|
+
"discount_carries_over": "Your current discount carries over to the new plan."
|
|
729
851
|
},
|
|
730
852
|
"steps": {
|
|
731
853
|
"payment": "Payment",
|
package/messages/it.json
CHANGED
|
@@ -297,6 +297,118 @@
|
|
|
297
297
|
},
|
|
298
298
|
"billing": {
|
|
299
299
|
"admin": {
|
|
300
|
+
"coupons": {
|
|
301
|
+
"all_products": "Tutti i prodotti",
|
|
302
|
+
"codes": {
|
|
303
|
+
"automatic": "Automatico",
|
|
304
|
+
"deactivate": {
|
|
305
|
+
"confirm": "Disattiva",
|
|
306
|
+
"description": "Nessuno potrà usare \"{code}\" finché non lo riattivi. Gli abbonamenti che lo hanno già mantengono lo sconto.",
|
|
307
|
+
"title": "Disattiva codice"
|
|
308
|
+
},
|
|
309
|
+
"empty": {
|
|
310
|
+
"description": "Aggiungi un codice che i clienti possono usare per avere lo sconto di questo coupon.",
|
|
311
|
+
"title": "Ancora nessun codice"
|
|
312
|
+
},
|
|
313
|
+
"entity": "codice",
|
|
314
|
+
"never": "Mai",
|
|
315
|
+
"reactivate": {
|
|
316
|
+
"confirm": "Riattiva",
|
|
317
|
+
"description": "\"{code}\" potrà essere usato di nuovo.",
|
|
318
|
+
"title": "Riattiva codice"
|
|
319
|
+
},
|
|
320
|
+
"status": {
|
|
321
|
+
"active": "Attivo",
|
|
322
|
+
"expired": "Scaduto",
|
|
323
|
+
"inactive": "Inattivo",
|
|
324
|
+
"used_up": "Esaurito"
|
|
325
|
+
},
|
|
326
|
+
"title": "Codici"
|
|
327
|
+
},
|
|
328
|
+
"discount": {
|
|
329
|
+
"amount": "{amount} di sconto",
|
|
330
|
+
"forever": "per sempre",
|
|
331
|
+
"once": "una volta",
|
|
332
|
+
"percent": "{value}% di sconto",
|
|
333
|
+
"repeating": "per {months} mesi"
|
|
334
|
+
},
|
|
335
|
+
"duration": {
|
|
336
|
+
"forever": "Per sempre",
|
|
337
|
+
"once": "Una volta",
|
|
338
|
+
"repeating": "Per un numero di mesi"
|
|
339
|
+
},
|
|
340
|
+
"empty": {
|
|
341
|
+
"description": "Crea un coupon per offrire uno sconto al pagamento.",
|
|
342
|
+
"title": "Ancora nessun coupon"
|
|
343
|
+
},
|
|
344
|
+
"entity": "coupon",
|
|
345
|
+
"errors": {
|
|
346
|
+
"amountOff": "Inserisci un importo maggiore di zero",
|
|
347
|
+
"durationInMonths": "Inserisci almeno un mese",
|
|
348
|
+
"minimumAmount": "Inserisci un importo maggiore di zero, o lascia vuoto",
|
|
349
|
+
"name": "Il nome del coupon è obbligatorio",
|
|
350
|
+
"percentOff": "Inserisci una percentuale tra 0,01 e 100"
|
|
351
|
+
},
|
|
352
|
+
"fields": {
|
|
353
|
+
"amountOff": "Importo dello sconto",
|
|
354
|
+
"automatic": "Applica automaticamente",
|
|
355
|
+
"code": "Codice",
|
|
356
|
+
"codeMaxRedemptions": "Limite di utilizzo per questo codice",
|
|
357
|
+
"codes": "Codici",
|
|
358
|
+
"currency": "Valuta",
|
|
359
|
+
"discount": "Sconto",
|
|
360
|
+
"discountType": "Tipo",
|
|
361
|
+
"duration": "Durata",
|
|
362
|
+
"durationInMonths": "Mesi",
|
|
363
|
+
"expiresAt": "Scadenza",
|
|
364
|
+
"firstTimeTransaction": "Solo nuovi clienti",
|
|
365
|
+
"first_time_warning": "Un GM che arriva da una prova conta come cliente esistente, quindi lascia disattivato per i codici pensati per i GM in prova.",
|
|
366
|
+
"maxRedemptions": "Limite di utilizzo totale",
|
|
367
|
+
"minimumAmount": "Importo minimo dell'ordine",
|
|
368
|
+
"name": "Nome",
|
|
369
|
+
"percentOff": "Percentuale di sconto",
|
|
370
|
+
"places": "Posti",
|
|
371
|
+
"products": "Applica a prodotti specifici",
|
|
372
|
+
"redeemBy": "Utilizzabile fino al",
|
|
373
|
+
"status": "Stato",
|
|
374
|
+
"stripeCouponId": "ID",
|
|
375
|
+
"stripeCustomerId": "Limita a un solo cliente",
|
|
376
|
+
"stripeId": "ID coupon Stripe"
|
|
377
|
+
},
|
|
378
|
+
"first_code": "Primo codice",
|
|
379
|
+
"fixed_notice": "Stripe non permette di cambiare sconto, durata o limiti di un coupon. Crea un nuovo coupon.",
|
|
380
|
+
"help": {
|
|
381
|
+
"automatic": "Si applica da solo quando un GM paga la prima volta. Solo un codice può averlo: attivarlo qui lo disattiva sul codice che lo aveva.",
|
|
382
|
+
"products": "Lascia vuoto per applicare il coupon a tutti i prodotti."
|
|
383
|
+
},
|
|
384
|
+
"placeholders": {
|
|
385
|
+
"amountOff": "5,00",
|
|
386
|
+
"code": "Lascia vuoto per farlo generare a Stripe",
|
|
387
|
+
"durationInMonths": "3",
|
|
388
|
+
"maxRedemptions": "Nessun limite",
|
|
389
|
+
"minimumAmount": "0,00",
|
|
390
|
+
"name": "es. GM fondatore",
|
|
391
|
+
"percentOff": "20",
|
|
392
|
+
"stripeCouponId": "Lascia vuoto per farlo generare a Stripe"
|
|
393
|
+
},
|
|
394
|
+
"places": {
|
|
395
|
+
"limited": "{used} / {limit}",
|
|
396
|
+
"no_limit": "Nessun limite",
|
|
397
|
+
"unlimited": "{used} / nessun limite"
|
|
398
|
+
},
|
|
399
|
+
"refresh": {
|
|
400
|
+
"title": "Aggiorna conteggi"
|
|
401
|
+
},
|
|
402
|
+
"status": {
|
|
403
|
+
"active": "Attivo",
|
|
404
|
+
"ended": "Terminato"
|
|
405
|
+
},
|
|
406
|
+
"title": "Coupon",
|
|
407
|
+
"type": {
|
|
408
|
+
"amount_off": "Importo fisso",
|
|
409
|
+
"percent_off": "Percentuale"
|
|
410
|
+
}
|
|
411
|
+
},
|
|
300
412
|
"prices": {
|
|
301
413
|
"actions": {
|
|
302
414
|
"addFeature": "Aggiungi caratteristica",
|
|
@@ -430,7 +542,9 @@
|
|
|
430
542
|
"archived": "Archiviato"
|
|
431
543
|
},
|
|
432
544
|
"tabs": {
|
|
433
|
-
"
|
|
545
|
+
"codes": "Codici",
|
|
546
|
+
"details": "Dettagli",
|
|
547
|
+
"products": "Prodotti"
|
|
434
548
|
},
|
|
435
549
|
"title": "Prodotti"
|
|
436
550
|
}
|
|
@@ -626,10 +740,12 @@
|
|
|
626
740
|
},
|
|
627
741
|
"promo_code": {
|
|
628
742
|
"apply": "Applica",
|
|
743
|
+
"automatic_label": "Applicato automaticamente",
|
|
629
744
|
"duration_forever": "Applicato a tutti i pagamenti",
|
|
630
745
|
"duration_once": "Applicato solo al primo pagamento",
|
|
631
746
|
"duration_repeating": "Applicato per {months} mesi",
|
|
632
|
-
"placeholder": "Inserisci il codice promozionale"
|
|
747
|
+
"placeholder": "Inserisci il codice promozionale",
|
|
748
|
+
"use_different": "Hai un altro codice?"
|
|
633
749
|
},
|
|
634
750
|
"proration": {
|
|
635
751
|
"net_due_today": "Netto dovuto oggi",
|
|
@@ -698,8 +814,10 @@
|
|
|
698
814
|
"change_title": "Cambia piano di abbonamento",
|
|
699
815
|
"errors": {
|
|
700
816
|
"already_subscribed": "Hai già un abbonamento attivo. Cambia invece il piano attuale.",
|
|
817
|
+
"discount_unavailable": "Questo sconto non è più disponibile. Non ti è stato addebitato nulla.",
|
|
701
818
|
"invalid_promo_code": "Codice promozionale non valido",
|
|
702
819
|
"payment_method_required": "Per passare dalla prova a un piano a pagamento serve un metodo di pagamento.",
|
|
820
|
+
"payment_not_confirmed": "Il pagamento non è stato completato. {reason} Non ti è stato addebitato nulla: riprova o aggiungi un'altra carta.",
|
|
703
821
|
"prepare_review": "Impossibile preparare il riepilogo",
|
|
704
822
|
"process_subscription": "Impossibile elaborare l'abbonamento",
|
|
705
823
|
"validate_promo_code": "Impossibile verificare il codice promozionale"
|
|
@@ -708,6 +826,7 @@
|
|
|
708
826
|
"payment_description": "Inserisci i dati della carta per completare l'abbonamento",
|
|
709
827
|
"review": {
|
|
710
828
|
"amount_off": "{amount} di sconto",
|
|
829
|
+
"card_on_file": "{brand} che termina con {last4}",
|
|
711
830
|
"charge_now": "Importo da addebitare ora:",
|
|
712
831
|
"confirm_change": "Conferma il cambio di piano",
|
|
713
832
|
"due_now": "Importo dovuto ora:",
|
|
@@ -725,7 +844,10 @@
|
|
|
725
844
|
"selected_plan": "Piano selezionato",
|
|
726
845
|
"subscribe_now": "Abbonati ora",
|
|
727
846
|
"trial_upgrade": "Passaggio dalla prova",
|
|
728
|
-
"trial_upgrade_description": "La tua prova terminerà subito e ti verrà addebitato il prezzo pieno."
|
|
847
|
+
"trial_upgrade_description": "La tua prova terminerà subito e ti verrà addebitato il prezzo pieno.",
|
|
848
|
+
"trial_upgrade_description_discounted": "La tua prova terminerà subito e ti verrà addebitato il prezzo scontato.",
|
|
849
|
+
"use_another_card": "Usa un'altra carta",
|
|
850
|
+
"discount_carries_over": "Il tuo sconto attuale resta valido anche sul nuovo piano."
|
|
729
851
|
},
|
|
730
852
|
"steps": {
|
|
731
853
|
"payment": "Pagamento",
|
package/package.json
CHANGED
|
@@ -28,6 +28,7 @@ export const APP_PROVIDED_KEYS = [
|
|
|
28
28
|
/^features\.assistant\.starters\./, // example questions about the app's own domain
|
|
29
29
|
/^features\.assistant\.empty_state\.subtitle$/, // says what the app's assistant knows about
|
|
30
30
|
/^auth\.gdpr\.marketing_consent$/, // a consent must name who sends the news: the app
|
|
31
|
+
/^billing\.ui\.wizard\.errors\.discount_sold_out$/, // names who to contact: the app
|
|
31
32
|
// The app's own entities (CommonDeleter / RecentPagesNavigator render
|
|
32
33
|
// `entities.${type}` for any module). Only the library's own entities ship.
|
|
33
34
|
/^entities\.(?!(?:assistants|companies|howtos|rbac|roles|users)(?:\.|$))/,
|
package/src/billing/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "../features/billing/stripe-customer/components";
|
|
|
5
5
|
export * from "../features/billing/stripe-invoice/components";
|
|
6
6
|
export * from "../features/billing/stripe-price/components";
|
|
7
7
|
export * from "../features/billing/stripe-product/components";
|
|
8
|
+
export * from "../features/billing/stripe-coupon/components";
|
|
8
9
|
export * from "../features/billing/stripe-subscription/components";
|
|
9
10
|
export * from "../features/billing/stripe-usage/components";
|
|
10
11
|
export * from "../features/billing/stripe-promotion-code/components";
|
package/src/client/index.ts
CHANGED
|
@@ -21,6 +21,8 @@ import { useRoleTableStructure } from "../features/role/hooks";
|
|
|
21
21
|
import { useUserTableStructure } from "../features/user/hooks";
|
|
22
22
|
import { useStripePriceTableStructure } from "../features/billing/stripe-price/hooks/useStripePriceTableStructure";
|
|
23
23
|
import { useStripeProductTableStructure } from "../features/billing/stripe-product/hooks/useStripeProductTableStructure";
|
|
24
|
+
import { useStripeCouponTableStructure } from "../features/billing/stripe-coupon/hooks/useStripeCouponTableStructure";
|
|
25
|
+
import { useStripeCouponCodeTableStructure } from "../features/billing/stripe-coupon/hooks/useStripeCouponCodeTableStructure";
|
|
24
26
|
import { useWaitlistTableStructure } from "../features/waitlist/hooks/useWaitlistTableStructure";
|
|
25
27
|
import { registerTableGenerator } from "../hooks";
|
|
26
28
|
|
|
@@ -35,5 +37,7 @@ registerTableGenerator("users", useUserTableStructure);
|
|
|
35
37
|
registerTableGenerator("companies", useCompanyTableStructure);
|
|
36
38
|
registerTableGenerator("stripe-products", useStripeProductTableStructure);
|
|
37
39
|
registerTableGenerator("stripe-prices", useStripePriceTableStructure);
|
|
40
|
+
registerTableGenerator("stripe-coupons", useStripeCouponTableStructure);
|
|
41
|
+
registerTableGenerator("stripe-coupon-codes", useStripeCouponCodeTableStructure);
|
|
38
42
|
registerTableGenerator("waitlists", useWaitlistTableStructure);
|
|
39
43
|
// Note: Content registration moved to app-level to support app-specific cellTopic
|
package/src/core/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ export * from "../features/billing/stripe-product";
|
|
|
56
56
|
export * from "../features/billing/stripe-subscription";
|
|
57
57
|
export * from "../features/billing/stripe-usage";
|
|
58
58
|
export * from "../features/billing/stripe-promotion-code";
|
|
59
|
+
export * from "../features/billing/stripe-coupon";
|
|
59
60
|
// AI connection administration: data + module only, NOT the feature barrel —
|
|
60
61
|
// that one also re-exports the feature's client components, and core must stay
|
|
61
62
|
// importable from the server (same rationale as the tokenusage entries below).
|
|
@@ -34,6 +34,9 @@ export interface FoundationModuleDefinitions {
|
|
|
34
34
|
StripePrice: ModuleWithPermissions;
|
|
35
35
|
StripeUsage: ModuleWithPermissions;
|
|
36
36
|
StripePromotionCode: ModuleWithPermissions;
|
|
37
|
+
StripeCoupon: ModuleWithPermissions;
|
|
38
|
+
StripeCouponCode: ModuleWithPermissions;
|
|
39
|
+
StripeCouponOffer: ModuleWithPermissions;
|
|
37
40
|
// AI connection administration
|
|
38
41
|
AiConnection: ModuleWithPermissions;
|
|
39
42
|
// OAuth modules
|
|
@@ -71,8 +71,9 @@ export function InvoicesSummaryCard({ invoices, loading, error, onViewAllClick }
|
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
//
|
|
75
|
-
|
|
74
|
+
// The most recent invoice that could charge something: a void one is an unpaid first-payment
|
|
75
|
+
// attempt that was replaced, which never charged the GM (fall back to it only when nothing else exists)
|
|
76
|
+
const latestInvoice = invoices.find((inv) => inv.status !== InvoiceStatus.VOID) ?? invoices[0];
|
|
76
77
|
const paidInvoices = invoices.filter((inv) => inv.status === InvoiceStatus.PAID);
|
|
77
78
|
const openInvoices = invoices.filter((inv) => inv.status === InvoiceStatus.OPEN);
|
|
78
79
|
|
|
@@ -91,7 +91,8 @@ export function SubscriptionSummaryCard({
|
|
|
91
91
|
<CreditCard className="h-4 w-4 text-muted-foreground" />
|
|
92
92
|
</CardHeader>
|
|
93
93
|
<CardContent>
|
|
94
|
-
{
|
|
94
|
+
{/* No active or trialing plan (none at all, or only unpaid / ended ones): offer the plans */}
|
|
95
|
+
{!primarySubscription ? (
|
|
95
96
|
<div className="space-y-2">
|
|
96
97
|
<p className="text-xl font-bold text-muted-foreground">{t("billing.ui.cards.subscriptions.empty_title")}</p>
|
|
97
98
|
<p className="text-xs text-muted-foreground">{t("billing.ui.cards.subscriptions.empty_description")}</p>
|
|
@@ -108,7 +109,7 @@ export function SubscriptionSummaryCard({
|
|
|
108
109
|
<ChevronRight className="h-4 w-4 ms-1 rtl:rotate-180" />
|
|
109
110
|
</Button>
|
|
110
111
|
</div>
|
|
111
|
-
) :
|
|
112
|
+
) : (
|
|
112
113
|
<div className="space-y-2">
|
|
113
114
|
<div className="flex items-center gap-2">
|
|
114
115
|
<p className="text-xl font-bold">
|
|
@@ -147,7 +148,7 @@ export function SubscriptionSummaryCard({
|
|
|
147
148
|
</p>
|
|
148
149
|
)}
|
|
149
150
|
</div>
|
|
150
|
-
)
|
|
151
|
+
)}
|
|
151
152
|
</CardContent>
|
|
152
153
|
</Card>
|
|
153
154
|
);
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
SubscriptionSummaryCard,
|
|
23
23
|
} from "../cards";
|
|
24
24
|
import { BillingDetailModal } from "../modals/BillingDetailModal";
|
|
25
|
+
import { urlWithoutParam } from "../utils/url";
|
|
25
26
|
import { BillingAlertBanner } from "../widgets/BillingAlertBanner";
|
|
26
27
|
|
|
27
28
|
type ModalType = "subscriptions" | "payment-methods" | "invoices" | "usage" | null;
|
|
@@ -282,14 +283,27 @@ export function BillingDashboardContainer() {
|
|
|
282
283
|
fetchAllData();
|
|
283
284
|
}, [fetchAllData]);
|
|
284
285
|
|
|
285
|
-
// Handle URL action parameter for deep linking
|
|
286
|
+
// Handle URL action parameter for deep linking. `?action=subscribe` records the
|
|
287
|
+
// intent to subscribe; the wizard itself only renders once a customer exists, so a
|
|
288
|
+
// company without one sees the setup card first and the wizard opens right after
|
|
289
|
+
// "Set Up Billing Account" succeeds.
|
|
286
290
|
useEffect(() => {
|
|
287
291
|
const action = searchParams.get("action");
|
|
288
292
|
if (action === "subscribe") {
|
|
289
|
-
// Open wizard directly (no wrapper modal)
|
|
290
293
|
setShowWizard(true);
|
|
291
|
-
//
|
|
292
|
-
|
|
294
|
+
// Drop only `action`, so a refresh does not re-trigger it. Every other parameter
|
|
295
|
+
// must survive: a host page (the settings hub) selects this dashboard through
|
|
296
|
+
// `?section=`, and losing it switches the page away and unmounts this component.
|
|
297
|
+
window.history.replaceState(
|
|
298
|
+
window.history.state,
|
|
299
|
+
"",
|
|
300
|
+
urlWithoutParam({
|
|
301
|
+
pathname: window.location.pathname,
|
|
302
|
+
search: window.location.search,
|
|
303
|
+
hash: window.location.hash,
|
|
304
|
+
param: "action",
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
293
307
|
}
|
|
294
308
|
}, [searchParams]);
|
|
295
309
|
|
|
@@ -445,9 +459,12 @@ export function BillingDashboardContainer() {
|
|
|
445
459
|
onOpenChange={handleModalClose}
|
|
446
460
|
title={getModalTitle("subscriptions")}
|
|
447
461
|
>
|
|
462
|
+
{/* The dashboard's list, so a wizard success (refreshData) refreshes the dialog too */}
|
|
448
463
|
<SubscriptionsContainer
|
|
449
464
|
onOpenWizard={handleOpenWizard}
|
|
450
465
|
hasActiveRecurringSubscription={hasActiveRecurringSubscription}
|
|
466
|
+
subscriptions={data.subscriptions}
|
|
467
|
+
onSubscriptionsChange={refreshData}
|
|
451
468
|
/>
|
|
452
469
|
</BillingDetailModal>
|
|
453
470
|
|
|
@@ -477,7 +494,7 @@ export function BillingDashboardContainer() {
|
|
|
477
494
|
|
|
478
495
|
{/* Subscription Wizard - rendered at dashboard level to avoid nested dialogs */}
|
|
479
496
|
<SubscriptionWizard
|
|
480
|
-
open={showWizard}
|
|
497
|
+
open={showWizard && !!data.customer}
|
|
481
498
|
onOpenChange={(open) => !open && handleWizardClose()}
|
|
482
499
|
onSuccess={refreshData}
|
|
483
500
|
hasActiveRecurringSubscription={hasActiveRecurringSubscription}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { StripeSubscriptionInterface } from "../../../stripe-subscription/data/stripe-subscription.interface";
|
|
3
|
+
import { applyDiscount, promotionCodeDiscount, subscriptionDiscount } from "../discount";
|
|
4
|
+
|
|
5
|
+
const subscription = (discount: StripeSubscriptionInterface["discount"]) =>
|
|
6
|
+
({ discount }) as unknown as StripeSubscriptionInterface;
|
|
7
|
+
|
|
8
|
+
describe("applyDiscount", () => {
|
|
9
|
+
it("takes a percentage off", () => {
|
|
10
|
+
expect(applyDiscount(999, { discountType: "percent_off", discountValue: 20 })).toBeCloseTo(799.2);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("takes an amount off without going below zero", () => {
|
|
14
|
+
expect(applyDiscount(499, { discountType: "amount_off", discountValue: 100 })).toBe(399);
|
|
15
|
+
expect(applyDiscount(499, { discountType: "amount_off", discountValue: 1000 })).toBe(0);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("returns null without a discount or an amount", () => {
|
|
19
|
+
expect(applyDiscount(999, null)).toBeNull();
|
|
20
|
+
expect(applyDiscount(undefined, { discountType: "percent_off", discountValue: 20 })).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("promotionCodeDiscount", () => {
|
|
25
|
+
it("reads a valid code", () => {
|
|
26
|
+
expect(
|
|
27
|
+
promotionCodeDiscount({ valid: true, code: "FOUNDER", discountType: "percent_off", discountValue: 20 }),
|
|
28
|
+
).toEqual({ discountType: "percent_off", discountValue: 20, currency: undefined });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("ignores an invalid or missing code", () => {
|
|
32
|
+
expect(
|
|
33
|
+
promotionCodeDiscount({ valid: false, code: "X", discountType: "percent_off", discountValue: 20 }),
|
|
34
|
+
).toBeNull();
|
|
35
|
+
expect(promotionCodeDiscount(null)).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("subscriptionDiscount", () => {
|
|
40
|
+
it("reads a percentage discount", () => {
|
|
41
|
+
expect(subscriptionDiscount(subscription({ percentOff: 20, duration: "forever" }))).toEqual({
|
|
42
|
+
discountType: "percent_off",
|
|
43
|
+
discountValue: 20,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("reads an amount discount with its currency", () => {
|
|
48
|
+
expect(subscriptionDiscount(subscription({ amountOff: 200, currency: "eur", duration: "once" }))).toEqual({
|
|
49
|
+
discountType: "amount_off",
|
|
50
|
+
discountValue: 200,
|
|
51
|
+
currency: "eur",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns null when the subscription has no discount", () => {
|
|
56
|
+
expect(subscriptionDiscount(subscription(undefined))).toBeNull();
|
|
57
|
+
expect(subscriptionDiscount(undefined)).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
});
|