@financial-times/n-conversion-forms 46.0.1 → 47.0.1
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/.toolkitstate/ci.json +3 -3
- package/.toolkitstate/install.json +1 -1
- package/components/__snapshots__/payment-term.spec.js.snap +1 -1031
- package/components/delivery-option.stories.js +2 -2
- package/components/payment-term.jsx +249 -208
- package/components/payment-term.spec.js +675 -317
- package/components/payment-term.stories.js +135 -24
- package/dist/payment-term.jsx +198 -145
- package/helpers/duration-helpers.js +90 -0
- package/helpers/duration-helpers.spec.js +142 -0
- package/helpers/index.js +3 -0
- package/helpers/index.spec.js +8 -0
- package/package.json +2 -2
- package/styles/payment-term.scss +5 -5
- package/styles/payment-type.scss +0 -4
|
@@ -34,12 +34,12 @@ Basic.args = {
|
|
|
34
34
|
],
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
export const
|
|
37
|
+
export const UKPaperVoucherAutoRenewingDeliveryOptions = (args) => (
|
|
38
38
|
<div className="ncf">
|
|
39
39
|
<DeliveryOption {...args} />
|
|
40
40
|
</div>
|
|
41
41
|
);
|
|
42
|
-
|
|
42
|
+
UKPaperVoucherAutoRenewingDeliveryOptions.args = {
|
|
43
43
|
country: 'GBR',
|
|
44
44
|
options: [
|
|
45
45
|
{
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
|
-
import { Period,
|
|
4
|
+
import { Period, Price } from '@financial-times/n-pricing';
|
|
5
|
+
|
|
6
|
+
import { getDurationFromISO8601Value, is52WeeksOrLonger } from '../helpers';
|
|
5
7
|
|
|
6
8
|
export function PaymentTerm({
|
|
7
9
|
fieldId = 'paymentTermField',
|
|
@@ -17,159 +19,40 @@ export function PaymentTerm({
|
|
|
17
19
|
isNonRenewingSubscriptionTermType = false,
|
|
18
20
|
}) {
|
|
19
21
|
/**
|
|
20
|
-
*
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {string} currency country id of the currency
|
|
23
|
-
* @param {string} period (expressed in IS0 8601 duration format): e.g. PxY (yearly) or PxM (montly) where x is the amount of years/months
|
|
22
|
+
* Capitalises a string.
|
|
23
|
+
* @param {string} Input string to be capitalised
|
|
24
24
|
* @returns {string}
|
|
25
25
|
*/
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const monthlyPrice = periodObj.calculatePrice('P1M', amount);
|
|
29
|
-
return new Monthly({ value: monthlyPrice, currency }).getAmount('monthly');
|
|
30
|
-
};
|
|
26
|
+
const capitalise = (string) =>
|
|
27
|
+
Boolean(string) ? string[0].toUpperCase() + string.slice(1) : string;
|
|
31
28
|
|
|
32
29
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* @
|
|
30
|
+
* Creates the JSX for a single payment-term option, including the input,
|
|
31
|
+
* title, discount messaging, and descriptive pricing copy.
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} option - Payment term configuration
|
|
34
|
+
* @param {string} option.value - ISO 8601 duration of the subscription term of the offer
|
|
35
|
+
* @param {string} option.name - term name, e.g. "monthly", "annual", "2 yearly
|
|
36
|
+
* @param {string} option.price - Formatted display price
|
|
37
|
+
* @param {string|number} [option.amount] - Price expressed in numerical terms
|
|
38
|
+
* @param {string} [option.symbol] - Currency symbol, e.g. £
|
|
39
|
+
* @param {string} [option.monthlyPrice] - Precomputed monthly equivalent price (can be with or without currency symbol)
|
|
40
|
+
* @param {boolean} [option.discount] - Whether the option should display discount messaging
|
|
41
|
+
* @param {boolean} [option.bestOffer] - Whether the option should show "Best offer" instead of standard discount copy
|
|
42
|
+
* @param {boolean} [option.selected] - Whether the option is selected by default
|
|
43
|
+
* @param {boolean} [option.isTrial] - Whether the option is a trial offer
|
|
44
|
+
* @param {boolean} [option.subscriptionAutoRenewTerm] - Whether the option is for an auto-renewing subscription
|
|
45
|
+
* @param {number} [option.trialAmount] - Amount used for trial pricing
|
|
46
|
+
* @param {string} [option.trialDuration] - Human-readable trial duration copy
|
|
47
|
+
* @param {string} [option.trialPrice] - Formatted trial price
|
|
48
|
+
* @param {string} [option.displayName] - Override label for the term title
|
|
49
|
+
* @param {string} [option.title] - Fallback title for legacy or non-period terms
|
|
50
|
+
* @param {string} [option.subTitle] - Optional subtitle shown alongside the term title
|
|
51
|
+
* @param {string} [option.chargeOnText] - Optional charge timing copy for non-period offers
|
|
52
|
+
* @param {boolean} [option.b2cPartnership] - Whether the option is part of a B2C partnership offer
|
|
53
|
+
* @param {string} [option.b2cDiscountCopy] - Partnership-specific discount copy
|
|
54
|
+
* @returns {React.ReactElement} A rendered payment term option
|
|
37
55
|
*/
|
|
38
|
-
const getTimeFromPeriod = (period) => {
|
|
39
|
-
const periodUnitCodeToWordMap = {
|
|
40
|
-
Y: 'years',
|
|
41
|
-
M: 'months',
|
|
42
|
-
W: 'weeks',
|
|
43
|
-
D: 'days',
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const periodUnitCode = period.substring(period.length - 1);
|
|
47
|
-
|
|
48
|
-
const freq = periodUnitCodeToWordMap[periodUnitCode] || '';
|
|
49
|
-
|
|
50
|
-
const amount = period.substring(1, period.length - 1);
|
|
51
|
-
|
|
52
|
-
return period ? `${amount} ${freq}` : '';
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const isValidPeriod = (period) => {
|
|
56
|
-
try {
|
|
57
|
-
// Period should throw an error if it is not properly provided
|
|
58
|
-
// in order to validate it, we just send in case type is string
|
|
59
|
-
new Period(typeof period === 'string' ? period : '');
|
|
60
|
-
return true;
|
|
61
|
-
} catch (e) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const nameMap = {
|
|
67
|
-
annual: {
|
|
68
|
-
title: 'Annual',
|
|
69
|
-
price: (price) => (
|
|
70
|
-
<React.Fragment>
|
|
71
|
-
Single{' '}
|
|
72
|
-
<span className="ncf__payment-term__price ncf__strong">{price}</span>{' '}
|
|
73
|
-
payment
|
|
74
|
-
</React.Fragment>
|
|
75
|
-
),
|
|
76
|
-
trialPrice: (price) => (
|
|
77
|
-
<React.Fragment>
|
|
78
|
-
Unless you cancel during your trial you will be billed{' '}
|
|
79
|
-
<span className="ncf__payment-term__price">{price}</span> per year
|
|
80
|
-
after the trial period.
|
|
81
|
-
</React.Fragment>
|
|
82
|
-
),
|
|
83
|
-
monthlyPrice: (price) =>
|
|
84
|
-
price && (
|
|
85
|
-
<span className="ncf__payment-term__equivalent-price">
|
|
86
|
-
That’s equivalent to{' '}
|
|
87
|
-
<span className="ncf__payment-term__monthly-price">{price}</span>{' '}
|
|
88
|
-
per month
|
|
89
|
-
</span>
|
|
90
|
-
),
|
|
91
|
-
renewsText: () => (
|
|
92
|
-
<p className="ncf__payment-term__renews-text">
|
|
93
|
-
Renews annually unless cancelled
|
|
94
|
-
</p>
|
|
95
|
-
),
|
|
96
|
-
},
|
|
97
|
-
quarterly: {
|
|
98
|
-
title: 'Quarterly',
|
|
99
|
-
price: (price) => (
|
|
100
|
-
<React.Fragment>
|
|
101
|
-
<span className="ncf__payment-term__price">{price}</span> per quarter
|
|
102
|
-
</React.Fragment>
|
|
103
|
-
),
|
|
104
|
-
trialPrice: (price) => (
|
|
105
|
-
<React.Fragment>
|
|
106
|
-
Unless you cancel during your trial you will be billed{' '}
|
|
107
|
-
<span className="ncf__payment-term__price">{price}</span> per quarter
|
|
108
|
-
after the trial period.
|
|
109
|
-
</React.Fragment>
|
|
110
|
-
),
|
|
111
|
-
monthlyPrice: () => {},
|
|
112
|
-
renewsText: () => (
|
|
113
|
-
<p className="ncf__payment-term__renews-text">
|
|
114
|
-
Renews quarterly unless cancelled
|
|
115
|
-
</p>
|
|
116
|
-
),
|
|
117
|
-
},
|
|
118
|
-
monthly: {
|
|
119
|
-
title: 'Monthly',
|
|
120
|
-
price: (price) => (
|
|
121
|
-
<React.Fragment>
|
|
122
|
-
<span className="ncf__payment-term__price">{price}</span> per month
|
|
123
|
-
</React.Fragment>
|
|
124
|
-
),
|
|
125
|
-
trialPrice: (price) => (
|
|
126
|
-
<React.Fragment>
|
|
127
|
-
Unless you cancel during your trial you will be billed{' '}
|
|
128
|
-
<span className="ncf__payment-term__price">{price}</span> per month
|
|
129
|
-
after the trial period.
|
|
130
|
-
</React.Fragment>
|
|
131
|
-
),
|
|
132
|
-
monthlyPrice: () => {},
|
|
133
|
-
renewsText: () => (
|
|
134
|
-
<p className="ncf__payment-term__renews-text">
|
|
135
|
-
{'Renews monthly unless cancelled'}
|
|
136
|
-
</p>
|
|
137
|
-
),
|
|
138
|
-
},
|
|
139
|
-
custom: {
|
|
140
|
-
price: (price) => (
|
|
141
|
-
<React.Fragment>
|
|
142
|
-
Single{' '}
|
|
143
|
-
<span className="ncf__payment-term__price ncf__strong">{price}</span>{' '}
|
|
144
|
-
payment
|
|
145
|
-
</React.Fragment>
|
|
146
|
-
),
|
|
147
|
-
trialPrice: (trialPrice, trialPeriod) => (
|
|
148
|
-
<React.Fragment>
|
|
149
|
-
Unless you cancel during your trial you will be billed{' '}
|
|
150
|
-
<span className="ncf__payment-term__price">{trialPrice}</span> per{' '}
|
|
151
|
-
{trialPeriod}
|
|
152
|
-
after the trial period.
|
|
153
|
-
</React.Fragment>
|
|
154
|
-
),
|
|
155
|
-
monthlyPrice: (monthlyPrice) =>
|
|
156
|
-
Boolean(monthlyPrice) && (
|
|
157
|
-
<span className="ncf__payment-term__equivalent-price">
|
|
158
|
-
That’s equivalent to{' '}
|
|
159
|
-
<span className="ncf__payment-term__monthly-price">
|
|
160
|
-
{monthlyPrice}
|
|
161
|
-
</span>{' '}
|
|
162
|
-
per month
|
|
163
|
-
</span>
|
|
164
|
-
),
|
|
165
|
-
renewsText: (renewalPeriod) =>
|
|
166
|
-
Boolean(renewalPeriod) && (
|
|
167
|
-
<p className="ncf__payment-term__renews-text">
|
|
168
|
-
Renews every {renewalPeriod} unless cancelled
|
|
169
|
-
</p>
|
|
170
|
-
),
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
56
|
const createPaymentTerm = (option) => {
|
|
174
57
|
const className = classNames([
|
|
175
58
|
'ncf__payment-term__item',
|
|
@@ -187,6 +70,161 @@ export function PaymentTerm({
|
|
|
187
70
|
...(option.selected && { defaultChecked: true }),
|
|
188
71
|
};
|
|
189
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Determines whether input is a valid ISO 8601 duration value that can be decoded by the Period class.
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
const isValidPeriod = () => {
|
|
78
|
+
try {
|
|
79
|
+
// Period should throw an error if it is not properly provided
|
|
80
|
+
// in order to validate it, we just send in case type is string
|
|
81
|
+
new Period(typeof option.value === 'string' ? option.value : '');
|
|
82
|
+
return true;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Compute monthly price for given term name, if the term if 90 days or longer.
|
|
90
|
+
* @returns {string | undefined}
|
|
91
|
+
*/
|
|
92
|
+
const getCalculatedMonthlyPriceIfEligible = () => {
|
|
93
|
+
const priceObject = new Price(
|
|
94
|
+
{
|
|
95
|
+
value: option.amount,
|
|
96
|
+
symbol: option.symbol,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
period: option.value,
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
return priceObject.getMonthlyEquivalentIf90DaysOrLonger()?.amount.value;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returns elements that include the text describing the price of the offer option.
|
|
107
|
+
* @returns {React.ReactElement}
|
|
108
|
+
*/
|
|
109
|
+
const getPriceText = () => {
|
|
110
|
+
const isExpressedAsSinglePayment =
|
|
111
|
+
!option.subscriptionAutoRenewTerm ||
|
|
112
|
+
// With an auto-renewing annual term there is a high chance
|
|
113
|
+
// it will not be the same price in the second year,
|
|
114
|
+
// so we do not want to imply that the price will remain consistent.
|
|
115
|
+
// For shorter auto-renewing terms there is higher confidence that the price
|
|
116
|
+
// will remain consistent across subsequent terms.
|
|
117
|
+
(option.subscriptionAutoRenewTerm && is52WeeksOrLonger(option.value));
|
|
118
|
+
|
|
119
|
+
const isExpressedAsRecurringPayment = !isExpressedAsSinglePayment;
|
|
120
|
+
|
|
121
|
+
if (isExpressedAsSinglePayment) {
|
|
122
|
+
return (
|
|
123
|
+
<React.Fragment>
|
|
124
|
+
Single{' '}
|
|
125
|
+
<span className="ncf__payment-term__price">{option.price}</span>{' '}
|
|
126
|
+
payment
|
|
127
|
+
</React.Fragment>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (isExpressedAsRecurringPayment) {
|
|
132
|
+
return (
|
|
133
|
+
<React.Fragment>
|
|
134
|
+
<span className="ncf__payment-term__price">{option.price}</span> per{' '}
|
|
135
|
+
{getDurationFromISO8601Value({
|
|
136
|
+
iso8601Value: option.value,
|
|
137
|
+
})}
|
|
138
|
+
</React.Fragment>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Returns elements that include the text describing the price increase following the trial period.
|
|
145
|
+
* @returns {React.ReactElement}
|
|
146
|
+
*/
|
|
147
|
+
const getTrialPriceExplanatoryText = () => {
|
|
148
|
+
return (
|
|
149
|
+
<React.Fragment>
|
|
150
|
+
Unless you cancel during your trial you will be billed{' '}
|
|
151
|
+
<span className="ncf__payment-term__price">{option.price}</span> per{' '}
|
|
152
|
+
{getDurationFromISO8601Value({
|
|
153
|
+
iso8601Value: option.value,
|
|
154
|
+
})}{' '}
|
|
155
|
+
after the trial period.
|
|
156
|
+
</React.Fragment>
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Returns elements that include the text describing how regularly an auto-renewing subscription will renew.
|
|
162
|
+
* @returns {React.ReactElement}
|
|
163
|
+
*/
|
|
164
|
+
const getRenewalPeriodText = () => {
|
|
165
|
+
return (
|
|
166
|
+
<p className="ncf__payment-term__renews-text">
|
|
167
|
+
Renews every{' '}
|
|
168
|
+
{getDurationFromISO8601Value({ iso8601Value: option.value })} unless
|
|
169
|
+
cancelled
|
|
170
|
+
</p>
|
|
171
|
+
);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Returns elements that include the text describing the monthly equivalent of the subscription.
|
|
176
|
+
* Returns null if the term is shorter than 90 days
|
|
177
|
+
* @returns {string | null}
|
|
178
|
+
*/
|
|
179
|
+
const getEquivalentMonthlyPrice = () => {
|
|
180
|
+
const calculatedMonthlyPrice = getCalculatedMonthlyPriceIfEligible();
|
|
181
|
+
|
|
182
|
+
if (!calculatedMonthlyPrice) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const hasValidMonthlyPrice =
|
|
187
|
+
option.monthlyPrice !== null &&
|
|
188
|
+
option.monthlyPrice !== undefined &&
|
|
189
|
+
option.monthlyPrice !== '' &&
|
|
190
|
+
option.monthlyPrice !== '0';
|
|
191
|
+
|
|
192
|
+
if (hasValidMonthlyPrice) {
|
|
193
|
+
return isNaN(option.monthlyPrice)
|
|
194
|
+
? option.monthlyPrice
|
|
195
|
+
: `${option.symbol}${option.monthlyPrice}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return calculatedMonthlyPrice;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Returns elements that include the text describing the monthly equivalent of the subscription, if available.
|
|
203
|
+
* @returns {React.ReactElement | null}
|
|
204
|
+
*/
|
|
205
|
+
const getEquivalentMonthlyPriceText = () => {
|
|
206
|
+
const equivalentMonthlyPrice = getEquivalentMonthlyPrice();
|
|
207
|
+
|
|
208
|
+
if (!equivalentMonthlyPrice) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
return (
|
|
212
|
+
<span className="ncf__payment-term__equivalent-price">
|
|
213
|
+
That’s equivalent to{' '}
|
|
214
|
+
<span className="ncf__payment-term__monthly-price">
|
|
215
|
+
{equivalentMonthlyPrice}
|
|
216
|
+
</span>{' '}
|
|
217
|
+
per month
|
|
218
|
+
</span>
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Creates the standard discount badge for an option.
|
|
224
|
+
* Displays either "Best offer" or "Save X off RRP" when a discount exists.
|
|
225
|
+
*
|
|
226
|
+
* @returns {React.ReactElement} The discount element, or false when no discount should be shown
|
|
227
|
+
*/
|
|
190
228
|
const createDiscount = () => {
|
|
191
229
|
return (
|
|
192
230
|
option.discount && (
|
|
@@ -199,6 +237,11 @@ export function PaymentTerm({
|
|
|
199
237
|
);
|
|
200
238
|
};
|
|
201
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Creates B2C partnership discount copy for eligible annual offers.
|
|
242
|
+
*
|
|
243
|
+
* @returns {React.ReactElement} The B2C discount element, or false when the option is not eligible
|
|
244
|
+
*/
|
|
202
245
|
const createB2cDiscountCopy = () => {
|
|
203
246
|
return (
|
|
204
247
|
option.name === 'annual' &&
|
|
@@ -211,70 +254,63 @@ export function PaymentTerm({
|
|
|
211
254
|
);
|
|
212
255
|
};
|
|
213
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Creates the description shown beneath the term title.
|
|
259
|
+
* This may include trial copy, price-per-period copy, equivalent monthly price,
|
|
260
|
+
* renewal messaging, or fallback non-period pricing text.
|
|
261
|
+
*
|
|
262
|
+
* @returns {React.ReactElement} The description block for the payment term
|
|
263
|
+
*/
|
|
214
264
|
const createDescription = () => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
265
|
+
if (option.isTrial) {
|
|
266
|
+
return (
|
|
267
|
+
<div className="ncf__payment-term__description">
|
|
268
|
+
{option.trialDuration || '4 weeks'} for{' '}
|
|
269
|
+
<span className="ncf__payment-term__trial-price">
|
|
270
|
+
{option.trialPrice}
|
|
271
|
+
</span>
|
|
272
|
+
<br />
|
|
273
|
+
{getTrialPriceExplanatoryText()}
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (isValidPeriod(option.value)) {
|
|
279
|
+
return (
|
|
280
|
+
<div className="ncf__payment-term__description">
|
|
281
|
+
{getPriceText()}
|
|
282
|
+
{getEquivalentMonthlyPriceText()}
|
|
283
|
+
{option.subscriptionAutoRenewTerm && getRenewalPeriodText()}
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<div>
|
|
290
|
+
<span className={largePrice ? 'ncf__payment-term__large-price' : ''}>
|
|
291
|
+
{option.price}
|
|
220
292
|
</span>
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
) : (
|
|
226
|
-
<React.Fragment>
|
|
227
|
-
{nameMap[option.name] ? (
|
|
228
|
-
<div className="ncf__payment-term__description">
|
|
229
|
-
{nameMap[option.name].price(option.price)}
|
|
230
|
-
{nameMap[option.name].monthlyPrice(option.monthlyPrice)}
|
|
231
|
-
{isAutoRenewingSubscriptionTermType &&
|
|
232
|
-
nameMap[option.name].renewsText()}
|
|
233
|
-
{/* Remove this discount text temporarily in favour of monthly price */}
|
|
234
|
-
{/* <br />Save up to 25% when you pay annually */}
|
|
235
|
-
</div>
|
|
236
|
-
) : // this should cover the cases different than annual, quarterly and monthly
|
|
237
|
-
// for those containing period on option.value, render custom template, for the rest keep legacy render
|
|
238
|
-
isValidPeriod(option.value) ? (
|
|
239
|
-
<div className="ncf__payment-term__description">
|
|
240
|
-
{nameMap['custom'].price(option.price)}
|
|
241
|
-
{nameMap['custom'].monthlyPrice(
|
|
242
|
-
option.monthlyPrice && option.monthlyPrice !== '0'
|
|
243
|
-
? Number(option.monthlyPrice)
|
|
244
|
-
: getMonthlyPriceFromPeriod(
|
|
245
|
-
option.amount,
|
|
246
|
-
option.currency,
|
|
247
|
-
option.value
|
|
248
|
-
)
|
|
249
|
-
)}
|
|
250
|
-
{isAutoRenewingSubscriptionTermType &&
|
|
251
|
-
nameMap['custom'].renewsText(getTimeFromPeriod(option.value))}
|
|
252
|
-
</div>
|
|
253
|
-
) : (
|
|
254
|
-
<div>
|
|
255
|
-
<span
|
|
256
|
-
className={largePrice ? 'ncf__payment-term__large-price' : ''}
|
|
257
|
-
>
|
|
258
|
-
{option.price}
|
|
259
|
-
</span>
|
|
260
|
-
{option.chargeOnText && (
|
|
261
|
-
<p className="ncf__payment-term__charge-on-text">
|
|
262
|
-
{option.chargeOnText}
|
|
263
|
-
</p>
|
|
264
|
-
)}
|
|
265
|
-
</div>
|
|
293
|
+
{option.chargeOnText && (
|
|
294
|
+
<p className="ncf__payment-term__charge-on-text">
|
|
295
|
+
{option.chargeOnText}
|
|
296
|
+
</p>
|
|
266
297
|
)}
|
|
267
|
-
</
|
|
298
|
+
</div>
|
|
268
299
|
);
|
|
269
300
|
};
|
|
270
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Builds the display name shown as the payment term title.
|
|
304
|
+
* May prepend trial copy, use the capitalised term name for auto-renewing terms,
|
|
305
|
+
* derive a human-readable period for valid non-renewing terms, or fall back to
|
|
306
|
+
* a legacy title when no valid period is available.
|
|
307
|
+
*
|
|
308
|
+
* @returns {string} The formatted display name for the payment term
|
|
309
|
+
*/
|
|
271
310
|
const getTermDisplayName = () => {
|
|
272
311
|
const showTrialCopyInTitle =
|
|
273
312
|
option.isTrial && !isPrintOrBundle && !isDigitalEdition;
|
|
274
313
|
|
|
275
|
-
const title =
|
|
276
|
-
option.name && nameMap[option.name] ? nameMap[option.name].title : '';
|
|
277
|
-
|
|
278
314
|
let termDisplayName = '';
|
|
279
315
|
if (showTrialCopyInTitle) {
|
|
280
316
|
const termName = option.displayName
|
|
@@ -284,13 +320,16 @@ export function PaymentTerm({
|
|
|
284
320
|
}
|
|
285
321
|
|
|
286
322
|
const getTermPeriod = () => {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return title;
|
|
323
|
+
if (option.subscriptionAutoRenewTerm && option.name) {
|
|
324
|
+
return capitalise(option.name);
|
|
290
325
|
}
|
|
326
|
+
|
|
291
327
|
// custom offer with period provided
|
|
292
|
-
if (isValidPeriod(option.value)) {
|
|
293
|
-
return
|
|
328
|
+
if (!option.subscriptionAutoRenewTerm && isValidPeriod(option.value)) {
|
|
329
|
+
return getDurationFromISO8601Value({
|
|
330
|
+
iso8601Value: option.value,
|
|
331
|
+
excludeAmountWhenSingular: false,
|
|
332
|
+
});
|
|
294
333
|
}
|
|
295
334
|
// custom legacy cases, where period is not provided
|
|
296
335
|
return option.title;
|
|
@@ -399,12 +438,14 @@ PaymentTerm.propTypes = {
|
|
|
399
438
|
isB2cPartnership: PropTypes.bool,
|
|
400
439
|
discount: PropTypes.string,
|
|
401
440
|
isTrial: PropTypes.bool,
|
|
441
|
+
subscriptionAutoRenewTerm: PropTypes.bool,
|
|
402
442
|
name: PropTypes.string.isRequired,
|
|
403
443
|
price: PropTypes.string.isRequired,
|
|
404
444
|
selected: PropTypes.bool,
|
|
405
445
|
trialDuration: PropTypes.string,
|
|
406
446
|
trialPrice: PropTypes.string,
|
|
407
447
|
amount: PropTypes.string,
|
|
448
|
+
symbol: PropTypes.string,
|
|
408
449
|
trialAmount: PropTypes.number,
|
|
409
450
|
value: PropTypes.string.isRequired,
|
|
410
451
|
monthlyPrice: PropTypes.string,
|