@infosoftas/s4-orders-js-sdk 0.1.12 → 0.1.13

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/README.md CHANGED
@@ -39,17 +39,19 @@ enum PaymentMethodEnum {
39
39
  OIO = 'OIO',
40
40
  }
41
41
 
42
- type OrderFormFiledType = {
42
+ type OrderFormFieldType = {
43
43
  name: string;
44
44
  required?: boolean;
45
+ readOnly?: boolean;
46
+ label?: string;
45
47
  };
46
48
 
47
49
  type PaymentMethodOptionsType = {
48
50
  [key in PaymentMethodEnum]: {
49
51
  generateSubscriberContact?: boolean;
50
52
  accountId?: string;
51
- orderFormFields?: OrderFormFiledType[] | never[];
52
- paymentInvoiceFields?: OrderFormFiledType[] | never[] | null;
53
+ orderFormFields: OrderFormFieldType[];
54
+ paymentInvoiceFields?: OrderFormFieldType[] | never[] | null;
53
55
  };
54
56
  }
55
57
 
@@ -75,9 +77,10 @@ type ConfigType = {
75
77
  moduleTitle?: string;
76
78
  submitStartCallback?: (subscriberId: string) => void;
77
79
  userActionCallback?: (action: UserActionEnum, args: object | null | undefined) => void;
78
- setContactCallback?: (contactInfo: contactRequestType) => void;
79
- apiKey: string;
80
- apiUrl: string;
80
+ setContactCallback?: (contactInfo: ContactRequestType) => void;
81
+ cancelVippsCallback?: () => void;
82
+ apiKey?: string;
83
+ apiUrl?: string;
81
84
  templatePackageId: string;
82
85
  subscriberId: string;
83
86
  userId: string;
@@ -85,14 +88,31 @@ type ConfigType = {
85
88
  organizationId: string;
86
89
  redirectUrl?: string;
87
90
  showIframe?: boolean;
91
+ /**
92
+ * The full list of payment methods displayed in the UI, each with a display label and value.
93
+ * Controls what the user sees in the payment method selector.
94
+ * @example [{ label: 'Credit Card', value: 'SwedbankPay' }, { label: 'Invoice', value: 'Invoice' }]
95
+ */
88
96
  availablePaymentMethods?: { label: string; value: PaymentMethodEnum }[];
97
+ /**
98
+ * Whitelist of payment methods the subscriber is permitted to use.
99
+ * If a method is in availablePaymentMethods but not here, it will be shown but blocked.
100
+ * @example ['SwedbankPay', 'Invoice']
101
+ */
102
+ allowedPaymentMethods?: PaymentMethodEnum[];
103
+ /**
104
+ * Per-payment-method form field configuration.
105
+ * Controls which input fields are shown in the order form for each payment method.
106
+ * Use accountId on SwedbankPay to set your Swedbank Pay account ID.
107
+ */
89
108
  paymentMethodsOptions?: PaymentMethodOptionsType;
109
+ requireTermsAcceptance?: boolean;
90
110
  language?: string;
91
111
  merchantAgreementUrl?: string;
92
112
  invoiceAddressSelection?: {
93
113
  enabled?: boolean;
94
114
  label?: string;
95
- fields?: OrderFormFiledType[] | never[];
115
+ fields?: OrderFormFieldType[];
96
116
  };
97
117
  settings?: {
98
118
  successText?: string;
@@ -101,6 +121,7 @@ type ConfigType = {
101
121
  backButtonText?: string;
102
122
  verifyButtonText?: string;
103
123
  organizationNumberLabel?: string;
124
+ cvrLabel?: string;
104
125
  glnLabel?: string;
105
126
  orderDefaultValues?: OrderFormInputsType;
106
127
  paymentMethodLabel?: string;
@@ -110,6 +131,9 @@ type ConfigType = {
110
131
  errorValidationTitleMsg?: string;
111
132
  errorValidationDenialOrderBlockingMsg?: string;
112
133
  errorValidationBlockingOffersMsg?: string;
134
+ paymentMethodNotAllowedMsg?: string;
135
+ invoiceLookupNotFoundText?: string;
136
+ termsAndConditionsText?: string;
113
137
  };
114
138
  }
115
139
  ```
@@ -388,8 +412,11 @@ export default OrderPlace;
388
412
  | redirectUrl | window.location.href | redirect url |
389
413
  | showIframe | false | show credit card in iframe |
390
414
  | userActionCallback | undefined | function for tracking user actions |
391
- | paymentMethodsOptions | orderFormFields: [{name: 'phone, required: false, readOnly: false, label: ''}] | default form fields |
392
- | availablePaymentMethods | [] | available payment methods |
415
+ | cancelVippsCallback | undefined | callback invoked when a Vipps/MobilePay payment is cancelled |
416
+ | paymentMethodsOptions | orderFormFields: [{name: 'phoneNumber', required: false, readOnly: false, label: ''}] | default form fields |
417
+ | availablePaymentMethods | [] | available payment methods to display |
418
+ | allowedPaymentMethods | [] | list of payment methods the subscriber is allowed to use |
419
+ | requireTermsAcceptance | false | when true, the user must accept terms and conditions before submitting |
393
420
  | language | 'en-US' | language |
394
421
  | merchantAgreementUrl | '' | vipps and MobilePay property |
395
- | settings | {successText: 'Order successful completed!',failureText: 'Something went wrong!',submitButtonText: 'Start',backButtonText: 'Back',verifyButtonText: 'Verify',organizationNumberLabel: 'CVR',glnLabel: 'GLN',paymentMethodLabel:'Select Payment Method', orderDefaultValues: 'Default order form values', paymentMethodNotAllowedMsg: 'This payment method not allowed!', invoiceLookupNotFoundText: 'There was no recipient found for the given information' } | label and text properties |
422
+ | settings | {successText: 'Order completed successfully!', failureText: 'Something went wrong!', submitButtonText: 'Start', backButtonText: 'Back', verifyButtonText: 'Verify', organizationNumberLabel: 'Organization Number', cvrLabel: 'CVR', glnLabel: 'GLN', paymentMethodLabel: 'Select Payment Method', orderDefaultValues: 'Default order form values', errorReqMsg: '', errorInvalidEmailMsg: '', errorInvalidPhoneMsg: '', errorValidationTitleMsg: 'One or more validation errors occurred.', errorValidationDenialOrderBlockingMsg: '...', errorValidationBlockingOffersMsg: '...', paymentMethodNotAllowedMsg: 'This payment method not allowed!', invoiceLookupNotFoundText: 'There was no recipient found for the given information', termsAndConditionsText: '' } | label and text properties |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infosoftas/s4-orders-js-sdk",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "scripts": {
5
5
  "build": "rimraf lib && webpack --mode production",
6
6
  "release": "release-it"
@@ -27,13 +27,13 @@
27
27
  "copy-webpack-plugin": "^14.0.0",
28
28
  "css-loader": "^7.1.2",
29
29
  "mini-css-extract-plugin": "^2.9.4",
30
- "release-it": "^19.0.6",
30
+ "release-it": "^20.0.0",
31
31
  "rimraf": "^6.1.2",
32
32
  "sass": "^1.94.2",
33
33
  "sass-loader": "^16.0.6",
34
34
  "style-loader": "^4.0.0",
35
35
  "ts-loader": "^9.5.4",
36
- "typescript": "^5.9.3",
36
+ "typescript": "^6.0.2",
37
37
  "webpack": "^5.105.2",
38
38
  "webpack-cli": "^6.0.1"
39
39
  },
package/src/App.tsx CHANGED
@@ -45,6 +45,7 @@ const App: FC<ConfigType> = ({
45
45
  invoiceAddressSelection,
46
46
  availablePaymentMethods = [],
47
47
  allowedPaymentMethods,
48
+ requireTermsAcceptance,
48
49
  language = 'en-US',
49
50
  merchantAgreementUrl = '',
50
51
  settings = {
@@ -67,9 +68,17 @@ const App: FC<ConfigType> = ({
67
68
  'The order/subscription will not be created because the subscriber has a denial order blocking all.',
68
69
  errorValidationBlockingOffersMsg:
69
70
  'The order/subscription will not be created because the subscriber has a denial order blocking offers.',
71
+ orderDenialOfferBaseText:
72
+ 'Hello again!\n\nLooks like you already tried this offer.',
73
+ orderDenialOfferWithFallbackText:
74
+ 'Hello again!\n\nLooks like you already tried this offer, but we would like to keep you with us.\n\nTherefore we got a new great offer for you:',
75
+ orderDenialAmountText:
76
+ 'We are sorry, but you do not meet the requirements for this offer. Please contact customer service for more information.',
77
+ orderDenialFallbackOffer: undefined,
70
78
  paymentMethodNotAllowedMsg: '',
71
79
  invoiceLookupNotFoundText:
72
80
  'There was no recipient found for the given information',
81
+ termsAndConditionsText: '',
73
82
  },
74
83
  }) => {
75
84
  const [loading, setLoading] = useState<boolean>(false);
@@ -342,6 +351,12 @@ const App: FC<ConfigType> = ({
342
351
  callback={handleInvoiceForm}
343
352
  backButtonText={settings?.backButtonText}
344
353
  verifyButtonText={settings?.verifyButtonText}
354
+ orderDenialCloseButtonText={
355
+ settings?.orderDenialCloseButtonText
356
+ }
357
+ orderDenialContinueButtonText={
358
+ settings?.orderDenialContinueButtonText
359
+ }
345
360
  organizationNumberLabel={settings?.cvrLabel}
346
361
  glnLabel={settings?.glnLabel}
347
362
  organizationNumber={
@@ -375,6 +390,18 @@ const App: FC<ConfigType> = ({
375
390
  errorValidationBlockingOffersMsg={
376
391
  settings?.errorValidationBlockingOffersMsg
377
392
  }
393
+ orderDenialOfferBaseText={
394
+ settings?.orderDenialOfferBaseText
395
+ }
396
+ orderDenialOfferWithFallbackText={
397
+ settings?.orderDenialOfferWithFallbackText
398
+ }
399
+ orderDenialAmountText={
400
+ settings?.orderDenialAmountText
401
+ }
402
+ orderDenialFallbackOffer={
403
+ settings?.orderDenialFallbackOffer
404
+ }
378
405
  />
379
406
  )}
380
407
  {formType === FormTypeEnum.EHF && (
@@ -383,6 +410,12 @@ const App: FC<ConfigType> = ({
383
410
  callback={handleInvoiceForm}
384
411
  backButtonText={settings?.backButtonText}
385
412
  verifyButtonText={settings?.verifyButtonText}
413
+ orderDenialCloseButtonText={
414
+ settings?.orderDenialCloseButtonText
415
+ }
416
+ orderDenialContinueButtonText={
417
+ settings?.orderDenialContinueButtonText
418
+ }
386
419
  organizationNumberLabel={
387
420
  settings?.organizationNumberLabel
388
421
  }
@@ -417,6 +450,18 @@ const App: FC<ConfigType> = ({
417
450
  errorValidationBlockingOffersMsg={
418
451
  settings?.errorValidationBlockingOffersMsg
419
452
  }
453
+ orderDenialOfferBaseText={
454
+ settings?.orderDenialOfferBaseText
455
+ }
456
+ orderDenialOfferWithFallbackText={
457
+ settings?.orderDenialOfferWithFallbackText
458
+ }
459
+ orderDenialAmountText={
460
+ settings?.orderDenialAmountText
461
+ }
462
+ orderDenialFallbackOffer={
463
+ settings?.orderDenialFallbackOffer
464
+ }
420
465
  />
421
466
  )}
422
467
  {formType !== FormTypeEnum.OIO &&
@@ -438,6 +483,12 @@ const App: FC<ConfigType> = ({
438
483
  submitButtonText={
439
484
  settings?.submitButtonText
440
485
  }
486
+ orderDenialCloseButtonText={
487
+ settings?.orderDenialCloseButtonText
488
+ }
489
+ orderDenialContinueButtonText={
490
+ settings?.orderDenialContinueButtonText
491
+ }
441
492
  paymentMethodLabel={
442
493
  settings?.paymentMethodLabel
443
494
  }
@@ -473,6 +524,24 @@ const App: FC<ConfigType> = ({
473
524
  errorValidationBlockingOffersMsg={
474
525
  settings?.errorValidationBlockingOffersMsg
475
526
  }
527
+ orderDenialOfferBaseText={
528
+ settings?.orderDenialOfferBaseText
529
+ }
530
+ orderDenialOfferWithFallbackText={
531
+ settings?.orderDenialOfferWithFallbackText
532
+ }
533
+ orderDenialAmountText={
534
+ settings?.orderDenialAmountText
535
+ }
536
+ orderDenialFallbackOffer={
537
+ settings?.orderDenialFallbackOffer
538
+ }
539
+ termsAndConditionsText={
540
+ settings?.termsAndConditionsText
541
+ }
542
+ requireTermsAcceptance={
543
+ requireTermsAcceptance
544
+ }
476
545
  />
477
546
  )}
478
547
  </>
@@ -489,7 +558,9 @@ const App: FC<ConfigType> = ({
489
558
  {isFailed && <Alert className="mt-2" msg={failedMsg} />}
490
559
  {isFailed &&
491
560
  errorsMsg?.length > 0 &&
492
- errorsMsg.map((i) => <Alert className="mt-2" msg={i} />)}
561
+ errorsMsg.map((i, index) => (
562
+ <Alert key={`${i}-${index}`} className="mt-2" msg={i} />
563
+ ))}
493
564
  </div>
494
565
  </ErrorBoundary>
495
566
  );
@@ -3,12 +3,14 @@ import { FormProvider, useForm, SubmitHandler } from 'react-hook-form';
3
3
 
4
4
  import Alert from '../Alert/Alert';
5
5
  import Button from '../Button/Button';
6
+ import OrderDenialModal from '../OrderDenialModal/OrderDenialModal';
6
7
  import InputField from '../FormFields/InputField';
7
8
  import { PaymentMethodEnum, UserActionEnum } from '../../enums/general';
8
9
  import {
9
- OrderFormFiledType,
10
+ OrderFormFieldType,
10
11
  PaymentMethodOptionsType,
11
12
  ContactRequestType,
13
+ OrderDenialFallbackOfferType,
12
14
  } from '../../types/general';
13
15
  import { OrderFormInputsType, OrderInfoType } from '../../types/order';
14
16
  import { orderInvoiceContactFields } from '../../utils/order.helper';
@@ -27,6 +29,8 @@ type Props = {
27
29
  className?: string;
28
30
  backButtonText?: string;
29
31
  verifyButtonText?: string;
32
+ orderDenialCloseButtonText?: string;
33
+ orderDenialContinueButtonText?: string;
30
34
  organizationNumberLabel?: string;
31
35
  organizationNumber?: string;
32
36
  glnLabel?: string;
@@ -44,13 +48,17 @@ type Props = {
44
48
  invoiceAddressSelection?: {
45
49
  enabled?: boolean;
46
50
  label?: string;
47
- fields?: OrderFormFiledType[];
51
+ fields?: OrderFormFieldType[];
48
52
  paymentMethods?: PaymentMethodEnum[];
49
53
  };
50
54
  invoiceLookupNotFoundText?: string;
51
55
  errorValidationTitleMsg?: string;
52
56
  errorValidationDenialOrderBlockingMsg?: string;
53
57
  errorValidationBlockingOffersMsg?: string;
58
+ orderDenialOfferBaseText?: string;
59
+ orderDenialOfferWithFallbackText?: string;
60
+ orderDenialAmountText?: string;
61
+ orderDenialFallbackOffer?: OrderDenialFallbackOfferType;
54
62
  };
55
63
 
56
64
  type EHFFormInputsType = {
@@ -70,6 +78,8 @@ const EHFForm: FC<Props> = ({
70
78
  className = '',
71
79
  backButtonText = '',
72
80
  verifyButtonText = '',
81
+ orderDenialCloseButtonText = 'Close',
82
+ orderDenialContinueButtonText = 'Continue',
73
83
  organizationNumberLabel = '',
74
84
  organizationNumber = '',
75
85
  templatePackageId,
@@ -88,6 +98,10 @@ const EHFForm: FC<Props> = ({
88
98
  errorValidationTitleMsg,
89
99
  errorValidationDenialOrderBlockingMsg,
90
100
  errorValidationBlockingOffersMsg,
101
+ orderDenialOfferBaseText,
102
+ orderDenialOfferWithFallbackText,
103
+ orderDenialAmountText,
104
+ orderDenialFallbackOffer,
91
105
  }) => {
92
106
  const methods = useForm<EHFFormInputsType>({
93
107
  defaultValues: {
@@ -108,7 +122,16 @@ const EHFForm: FC<Props> = ({
108
122
  paymentMethodsOptions?.[orderValues?.paymentMethod as PaymentMethodEnum]
109
123
  ?.orderFormFields ?? DEFAULT_ORDER_FORM_FIELDS;
110
124
 
111
- const { orderSubmit, loading, apiErrorMsg, errorsMsg } = useOrderForm({
125
+ const {
126
+ orderSubmit,
127
+ continueWithFallbackOffer,
128
+ loading,
129
+ apiErrorMsg,
130
+ errorsMsg,
131
+ orderDenialType,
132
+ orderDenialMessage,
133
+ dismissOrderDenial,
134
+ } = useOrderForm({
112
135
  callback,
113
136
  submitStartCallback,
114
137
  userActionCallback,
@@ -130,6 +153,10 @@ const EHFForm: FC<Props> = ({
130
153
  errorValidationTitleMsg,
131
154
  errorValidationDenialOrderBlockingMsg,
132
155
  errorValidationBlockingOffersMsg,
156
+ orderDenialOfferBaseText,
157
+ orderDenialOfferWithFallbackText,
158
+ orderDenialAmountText,
159
+ orderDenialFallbackOffer,
133
160
  });
134
161
 
135
162
  const onSubmit: SubmitHandler<EHFFormInputsType> = async (
@@ -178,7 +205,27 @@ const EHFForm: FC<Props> = ({
178
205
 
179
206
  {apiErrorMsg && <Alert className="mt-2" msg={apiErrorMsg} />}
180
207
  {errorsMsg?.length > 0 &&
181
- errorsMsg.map((i) => <Alert className="mt-2" msg={i} />)}
208
+ errorsMsg.map((i, index) => (
209
+ <Alert key={index} className="mt-2" msg={i} />
210
+ ))}
211
+ <OrderDenialModal
212
+ isOpen={!!orderDenialType}
213
+ message={orderDenialMessage}
214
+ closeButtonText={orderDenialCloseButtonText}
215
+ continueButtonText={orderDenialContinueButtonText}
216
+ canContinue={
217
+ orderDenialType === 'offer' &&
218
+ !!orderDenialFallbackOffer?.templatePackageId
219
+ }
220
+ offer={
221
+ orderDenialType === 'offer' &&
222
+ !!orderDenialFallbackOffer?.templatePackageId
223
+ ? orderDenialFallbackOffer
224
+ : undefined
225
+ }
226
+ onClose={dismissOrderDenial}
227
+ onContinue={continueWithFallbackOffer}
228
+ />
182
229
  </form>
183
230
  </FormProvider>
184
231
  );
@@ -1,4 +1,4 @@
1
- import { OrderFormFiledType } from '../../types/general';
1
+ import { OrderFormFieldType } from '../../types/general';
2
2
 
3
3
  export const FIELD_TYPES = {
4
4
  PHONE: 'phoneNumber',
@@ -21,9 +21,10 @@ export const FIELD_TYPES = {
21
21
  INVOICE_EMAIL: 'invoiceEmail',
22
22
  INVOICE_CARE_OF: 'invoiceCareOf',
23
23
  INVOICE_BUYER_REFERENCE: 'invoiceBuyerReference',
24
+ TERMS_CHECKBOX: 'termsCheckbox',
24
25
  };
25
26
 
26
- export const DEFAULT_ORDER_FORM_FIELDS: OrderFormFiledType[] = [
27
+ export const DEFAULT_ORDER_FORM_FIELDS: OrderFormFieldType[] = [
27
28
  {
28
29
  name: FIELD_TYPES.PHONE,
29
30
  required: false,
@@ -3,6 +3,7 @@ import PhoneField from './PhoneField';
3
3
  import NameField from './NameField';
4
4
  import AddressField from './AddressField';
5
5
  import InputField from './InputField';
6
+ import TermsCheckbox from './TermsCheckbox';
6
7
 
7
8
  import { FIELD_TYPES } from './FormFields.helper';
8
9
 
@@ -27,6 +28,7 @@ const formFieldsMapper = {
27
28
  [FIELD_TYPES.INVOICE_ZIP]: InputField,
28
29
  [FIELD_TYPES.INVOICE_CARE_OF]: InputField,
29
30
  [FIELD_TYPES.INVOICE_BUYER_REFERENCE]: InputField,
31
+ [FIELD_TYPES.TERMS_CHECKBOX]: TermsCheckbox,
30
32
  };
31
33
 
32
34
  export default formFieldsMapper;
@@ -0,0 +1,58 @@
1
+ import { FC, ReactNode } from 'react';
2
+ import { useFormContext } from 'react-hook-form';
3
+
4
+ import { uuidv4 } from '../../utils/helper';
5
+
6
+ type Props = {
7
+ name: string;
8
+ required: boolean;
9
+ disabled?: boolean;
10
+ label?: string | ReactNode;
11
+ errorReqMsg?: string;
12
+ errors?: {
13
+ [key: string]: {
14
+ message?: string;
15
+ };
16
+ };
17
+ };
18
+
19
+ const TermsCheckbox: FC<Props> = ({
20
+ name,
21
+ required = true,
22
+ disabled = false,
23
+ label = 'I accept the terms and conditions',
24
+ errorReqMsg = 'You must accept the terms and conditions to proceed.',
25
+ errors,
26
+ }) => {
27
+ const { register } = useFormContext();
28
+ const id = uuidv4();
29
+
30
+ return (
31
+ <div className="checkbox-wrapper" data-testid={`sdk-${name}-field-id`}>
32
+ <>
33
+ <input
34
+ id={id}
35
+ type="checkbox"
36
+ disabled={disabled}
37
+ className={`checkbox-control ${disabled ? 'disabled' : ''}`}
38
+ {...register(name, {
39
+ required: required ? errorReqMsg : false,
40
+ })}
41
+ />
42
+ <label
43
+ className="checkbox-label"
44
+ htmlFor={id}
45
+ style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
46
+ >
47
+ {label} {required && <span className="text-error">*</span>}
48
+ </label>
49
+ </>
50
+
51
+ {errors && errors[name] && (
52
+ <div className="text-error caption">{errors[name].message}</div>
53
+ )}
54
+ </div>
55
+ );
56
+ };
57
+
58
+ export default TermsCheckbox;
@@ -3,14 +3,16 @@ import { FormProvider, useForm, SubmitHandler } from 'react-hook-form';
3
3
 
4
4
  import Alert from '../Alert/Alert';
5
5
  import Button from '../Button/Button';
6
+ import OrderDenialModal from '../OrderDenialModal/OrderDenialModal';
6
7
  import InputField from '../FormFields/InputField';
7
8
  import { DEFAULT_ORDER_FORM_FIELDS } from '../FormFields/FormFields.helper';
8
9
 
9
10
  import { PaymentMethodEnum, UserActionEnum } from '../../enums/general';
10
11
  import {
11
- OrderFormFiledType,
12
+ OrderFormFieldType,
12
13
  PaymentMethodOptionsType,
13
14
  ContactRequestType,
15
+ OrderDenialFallbackOfferType,
14
16
  } from '../../types/general';
15
17
  import useOrderForm from '../../hooks/useOrderForm';
16
18
  import { orderInvoiceContactFields } from '../../utils/order.helper';
@@ -28,6 +30,8 @@ type Props = {
28
30
  className?: string;
29
31
  backButtonText?: string;
30
32
  verifyButtonText?: string;
33
+ orderDenialCloseButtonText?: string;
34
+ orderDenialContinueButtonText?: string;
31
35
  organizationNumberLabel?: string;
32
36
  organizationNumber?: string;
33
37
  glnLabel?: string;
@@ -45,13 +49,17 @@ type Props = {
45
49
  invoiceAddressSelection?: {
46
50
  enabled?: boolean;
47
51
  label?: string;
48
- fields?: OrderFormFiledType[];
52
+ fields?: OrderFormFieldType[];
49
53
  paymentMethods?: PaymentMethodEnum[];
50
54
  };
51
55
  invoiceLookupNotFoundText?: string;
52
56
  errorValidationTitleMsg?: string;
53
57
  errorValidationDenialOrderBlockingMsg?: string;
54
58
  errorValidationBlockingOffersMsg?: string;
59
+ orderDenialOfferBaseText?: string;
60
+ orderDenialOfferWithFallbackText?: string;
61
+ orderDenialAmountText?: string;
62
+ orderDenialFallbackOffer?: OrderDenialFallbackOfferType;
55
63
  };
56
64
 
57
65
  type OIOFormInputsType = {
@@ -64,7 +72,7 @@ const initialData = {
64
72
  gln: '',
65
73
  };
66
74
 
67
- const EHFForm: FC<Props> = ({
75
+ const OIOForm: FC<Props> = ({
68
76
  callback,
69
77
  onBack,
70
78
  submitStartCallback,
@@ -73,6 +81,8 @@ const EHFForm: FC<Props> = ({
73
81
  className = '',
74
82
  backButtonText = '',
75
83
  verifyButtonText = '',
84
+ orderDenialCloseButtonText = 'Close',
85
+ orderDenialContinueButtonText = 'Continue',
76
86
  organizationNumberLabel = '',
77
87
  organizationNumber = '',
78
88
  glnLabel = '',
@@ -92,6 +102,10 @@ const EHFForm: FC<Props> = ({
92
102
  errorValidationTitleMsg,
93
103
  errorValidationDenialOrderBlockingMsg,
94
104
  errorValidationBlockingOffersMsg,
105
+ orderDenialOfferBaseText,
106
+ orderDenialOfferWithFallbackText,
107
+ orderDenialAmountText,
108
+ orderDenialFallbackOffer,
95
109
  }) => {
96
110
  const methods = useForm<OIOFormInputsType>({
97
111
  defaultValues: {
@@ -116,7 +130,16 @@ const EHFForm: FC<Props> = ({
116
130
  paymentMethodsOptions?.[orderValues?.paymentMethod as PaymentMethodEnum]
117
131
  ?.orderFormFields ?? DEFAULT_ORDER_FORM_FIELDS;
118
132
 
119
- const { orderSubmit, loading, apiErrorMsg, errorsMsg } = useOrderForm({
133
+ const {
134
+ orderSubmit,
135
+ continueWithFallbackOffer,
136
+ loading,
137
+ apiErrorMsg,
138
+ errorsMsg,
139
+ orderDenialType,
140
+ orderDenialMessage,
141
+ dismissOrderDenial,
142
+ } = useOrderForm({
120
143
  callback,
121
144
  submitStartCallback,
122
145
  userActionCallback,
@@ -138,6 +161,10 @@ const EHFForm: FC<Props> = ({
138
161
  errorValidationTitleMsg,
139
162
  errorValidationDenialOrderBlockingMsg,
140
163
  errorValidationBlockingOffersMsg,
164
+ orderDenialOfferBaseText,
165
+ orderDenialOfferWithFallbackText,
166
+ orderDenialAmountText,
167
+ orderDenialFallbackOffer,
141
168
  });
142
169
 
143
170
  const onSubmit: SubmitHandler<OIOFormInputsType> = async (
@@ -189,10 +216,30 @@ const EHFForm: FC<Props> = ({
189
216
  </div>
190
217
  {apiErrorMsg && <Alert className="mt-2" msg={apiErrorMsg} />}
191
218
  {errorsMsg?.length > 0 &&
192
- errorsMsg.map((i) => <Alert className="mt-2" msg={i} />)}
219
+ errorsMsg.map((i, index) => (
220
+ <Alert key={`${i}-${index}`} className="mt-2" msg={i} />
221
+ ))}
222
+ <OrderDenialModal
223
+ isOpen={!!orderDenialType}
224
+ message={orderDenialMessage}
225
+ closeButtonText={orderDenialCloseButtonText}
226
+ continueButtonText={orderDenialContinueButtonText}
227
+ canContinue={
228
+ orderDenialType === 'offer' &&
229
+ !!orderDenialFallbackOffer?.templatePackageId
230
+ }
231
+ offer={
232
+ orderDenialType === 'offer' &&
233
+ !!orderDenialFallbackOffer?.templatePackageId
234
+ ? orderDenialFallbackOffer
235
+ : undefined
236
+ }
237
+ onClose={dismissOrderDenial}
238
+ onContinue={continueWithFallbackOffer}
239
+ />
193
240
  </form>
194
241
  </FormProvider>
195
242
  );
196
243
  };
197
244
 
198
- export default EHFForm;
245
+ export default OIOForm;
@@ -0,0 +1,91 @@
1
+ import { FC } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+
4
+ import { OrderDenialFallbackOfferType } from '../../types/general';
5
+ import Button from '../Button/Button';
6
+
7
+ import './orderDenialModal.scss';
8
+
9
+ type Props = {
10
+ isOpen: boolean;
11
+ message?: string;
12
+ offer?: OrderDenialFallbackOfferType;
13
+ canContinue?: boolean;
14
+ closeButtonText?: string;
15
+ continueButtonText?: string;
16
+ onContinue: () => void;
17
+ onClose: () => void;
18
+ };
19
+
20
+ const OrderDenialModal: FC<Props> = ({
21
+ isOpen,
22
+ message,
23
+ offer,
24
+ canContinue = true,
25
+ closeButtonText = 'Close',
26
+ continueButtonText = 'Continue',
27
+ onContinue,
28
+ onClose,
29
+ }) => {
30
+ if (!isOpen || typeof document === 'undefined') {
31
+ return null;
32
+ }
33
+
34
+ return createPortal(
35
+ <div
36
+ className="sdk-order-denial-modal-overlay"
37
+ data-testid="sdk-order-denial-modal-overlay"
38
+ >
39
+ <div
40
+ className="sdk-order-denial-modal"
41
+ role="dialog"
42
+ aria-modal="true"
43
+ aria-label="Order denial modal"
44
+ >
45
+ <p className="sdk-order-denial-modal-text">
46
+ {message?.split('\\n').map((line, i, arr) => (
47
+ <span key={i}>
48
+ {line}
49
+ {i < arr.length - 1 && <br />}
50
+ </span>
51
+ ))}
52
+ </p>
53
+ {offer?.title && offer?.templatePackageId && (
54
+ <div className="sdk-order-denial-offer-box">
55
+ <h4 className="sdk-order-denial-offer-title">
56
+ {offer.title}
57
+ </h4>
58
+ {offer.description && (
59
+ <p className="sdk-order-denial-offer-description">
60
+ {offer.description}
61
+ </p>
62
+ )}
63
+ {offer.price && (
64
+ <p className="sdk-order-denial-offer-meta">
65
+ {offer.price}
66
+ </p>
67
+ )}
68
+ </div>
69
+ )}
70
+ <div className="sdk-order-denial-modal-actions">
71
+ <Button
72
+ type="button"
73
+ btnType="default"
74
+ buttonText={closeButtonText}
75
+ onClick={onClose}
76
+ />
77
+ {canContinue && (
78
+ <Button
79
+ type="button"
80
+ buttonText={continueButtonText}
81
+ onClick={onContinue}
82
+ />
83
+ )}
84
+ </div>
85
+ </div>
86
+ </div>,
87
+ document.body
88
+ );
89
+ };
90
+
91
+ export default OrderDenialModal;
@@ -0,0 +1,42 @@
1
+ .sdk-order-denial-modal-overlay {
2
+ position: fixed;
3
+ inset: 0;
4
+ z-index: 9999;
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ padding: 16px;
9
+ }
10
+
11
+ .sdk-order-denial-modal {
12
+ width: min(560px, 100%);
13
+ padding: 24px;
14
+ }
15
+
16
+ .sdk-order-denial-modal-text {
17
+ margin: 0;
18
+ }
19
+
20
+ .sdk-order-denial-offer-box {
21
+ margin-top: 16px;
22
+ padding: 14px;
23
+ }
24
+
25
+ .sdk-order-denial-offer-title {
26
+ margin: 0;
27
+ }
28
+
29
+ .sdk-order-denial-offer-description {
30
+ margin: 8px 0 0;
31
+ }
32
+
33
+ .sdk-order-denial-offer-meta {
34
+ margin: 8px 0 0;
35
+ }
36
+
37
+ .sdk-order-denial-modal-actions {
38
+ margin-top: 20px;
39
+ display: flex;
40
+ justify-content: flex-end;
41
+ gap: 10px;
42
+ }
@@ -1,4 +1,12 @@
1
- import { FC, ChangeEvent, Suspense, useState, useMemo, useEffect } from 'react';
1
+ import {
2
+ FC,
3
+ ChangeEvent,
4
+ ReactNode,
5
+ Suspense,
6
+ useState,
7
+ useMemo,
8
+ useEffect,
9
+ } from 'react';
2
10
  import { FormProvider, useForm, SubmitHandler } from 'react-hook-form';
3
11
 
4
12
  import { PaymentMethodEnum, UserActionEnum } from '../../enums/general';
@@ -8,19 +16,22 @@ import {
8
16
  } from '../../constants/index';
9
17
  import Alert from '../Alert/Alert';
10
18
  import Button from '../Button/Button';
19
+ import OrderDenialModal from '../OrderDenialModal/OrderDenialModal';
11
20
  import ToggleField from '../FormFields/ToggleFiled';
12
21
  import formFieldsMapper from '../FormFields/FormFieldsMapper';
13
22
  import { DEFAULT_ORDER_FORM_FIELDS } from '../FormFields/FormFields.helper';
14
23
  import { OrderFormInputsType, OrderInfoType } from '../../types/order';
15
24
  import {
16
25
  PaymentMethodOptionsType,
17
- OrderFormFiledType,
26
+ OrderFormFieldType,
18
27
  ContactRequestType,
28
+ OrderDenialFallbackOfferType,
19
29
  } from '../../types/general';
20
30
  import { orderInvoiceContactFields } from '../../utils/order.helper';
21
31
  import useOrderForm from '../../hooks/useOrderForm';
22
32
 
23
33
  import './orderForm.scss';
34
+ import TermsCheckbox from '../FormFields/TermsCheckbox';
24
35
 
25
36
  type Props = {
26
37
  callback: (url: string | null, orderInfo?: OrderInfoType | null) => void;
@@ -45,6 +56,8 @@ type Props = {
45
56
  paymentMethods?: { label: string; value: PaymentMethodEnum }[];
46
57
  allowedPaymentMethods?: PaymentMethodEnum[];
47
58
  submitButtonText?: string;
59
+ orderDenialCloseButtonText?: string;
60
+ orderDenialContinueButtonText?: string;
48
61
  paymentMethodLabel?: string;
49
62
  paymentMethodNotAllowedMsg?: string;
50
63
  errorReqMsg?: string;
@@ -53,12 +66,18 @@ type Props = {
53
66
  invoiceAddressSelection?: {
54
67
  enabled?: boolean;
55
68
  label?: string;
56
- fields?: OrderFormFiledType[];
69
+ fields?: OrderFormFieldType[];
57
70
  paymentMethods?: PaymentMethodEnum[];
58
71
  };
72
+ requireTermsAcceptance?: boolean;
59
73
  errorValidationTitleMsg?: string;
60
74
  errorValidationDenialOrderBlockingMsg?: string;
61
75
  errorValidationBlockingOffersMsg?: string;
76
+ orderDenialOfferBaseText?: string;
77
+ orderDenialOfferWithFallbackText?: string;
78
+ orderDenialAmountText?: string;
79
+ orderDenialFallbackOffer?: OrderDenialFallbackOfferType;
80
+ termsAndConditionsText?: string | ReactNode;
62
81
  };
63
82
 
64
83
  const initialData = {
@@ -95,6 +114,8 @@ const OrderForm: FC<Props> = ({
95
114
  allowedPaymentMethods,
96
115
  invoiceAddressSelection,
97
116
  submitButtonText = 'Start',
117
+ orderDenialCloseButtonText = 'Close',
118
+ orderDenialContinueButtonText = 'Continue',
98
119
  paymentMethodLabel = 'Select Payment Method',
99
120
  paymentMethodNotAllowedMsg = 'This payment method not allowed!',
100
121
  errorReqMsg = '',
@@ -103,6 +124,12 @@ const OrderForm: FC<Props> = ({
103
124
  errorValidationTitleMsg,
104
125
  errorValidationDenialOrderBlockingMsg,
105
126
  errorValidationBlockingOffersMsg,
127
+ orderDenialOfferBaseText,
128
+ orderDenialOfferWithFallbackText,
129
+ orderDenialAmountText,
130
+ orderDenialFallbackOffer,
131
+ requireTermsAcceptance,
132
+ termsAndConditionsText,
106
133
  }) => {
107
134
  if (!templatePackageId) {
108
135
  console.error('"templatePackageId" should be set');
@@ -127,7 +154,7 @@ const OrderForm: FC<Props> = ({
127
154
  console.error('"identityProviderId" should be set');
128
155
  }
129
156
 
130
- const [orderFields, setOrderFields] = useState<OrderFormFiledType[]>(
157
+ const [orderFields, setOrderFields] = useState<OrderFormFieldType[]>(
131
158
  paymentMethodsOptions?.[
132
159
  defaultValues?.paymentMethod || PAYMENT_METHOD_DEFAULT
133
160
  ]?.orderFormFields ?? DEFAULT_ORDER_FORM_FIELDS
@@ -158,6 +185,7 @@ const OrderForm: FC<Props> = ({
158
185
 
159
186
  const invoiceAddressToggle = watch('invoiceAddressSelection');
160
187
  const paymentMethodInput = watch('paymentMethod');
188
+ const isTermsAccepted = watch('termsAccept', false);
161
189
 
162
190
  const invoiceOrderFields =
163
191
  paymentMethodsOptions?.[paymentMethodInput || PAYMENT_METHOD_DEFAULT]
@@ -169,7 +197,16 @@ const OrderForm: FC<Props> = ({
169
197
  invoiceAddressSelection?.paymentMethods ||
170
198
  INVOICE_ALLOWED_PAYMENT_METHODS;
171
199
 
172
- const { orderSubmit, loading, apiErrorMsg, errorsMsg } = useOrderForm({
200
+ const {
201
+ orderSubmit,
202
+ continueWithFallbackOffer,
203
+ loading,
204
+ apiErrorMsg,
205
+ errorsMsg,
206
+ orderDenialType,
207
+ orderDenialMessage,
208
+ dismissOrderDenial,
209
+ } = useOrderForm({
173
210
  callback,
174
211
  submitStartCallback,
175
212
  userActionCallback,
@@ -190,6 +227,10 @@ const OrderForm: FC<Props> = ({
190
227
  errorValidationTitleMsg,
191
228
  errorValidationDenialOrderBlockingMsg,
192
229
  errorValidationBlockingOffersMsg,
230
+ orderDenialOfferBaseText,
231
+ orderDenialOfferWithFallbackText,
232
+ orderDenialAmountText,
233
+ orderDenialFallbackOffer,
193
234
  });
194
235
 
195
236
  const onSubmit: SubmitHandler<OrderFormInputsType> = async (
@@ -248,6 +289,10 @@ const OrderForm: FC<Props> = ({
248
289
  allowPaymentMethod &&
249
290
  invoicePaymentMethods.includes(paymentMethodInput as PaymentMethodEnum);
250
291
 
292
+ const isSubmitButtonDisabled = requireTermsAcceptance
293
+ ? !isTermsAccepted
294
+ : false;
295
+
251
296
  return (
252
297
  <FormProvider {...methods}>
253
298
  <form
@@ -344,11 +389,20 @@ const OrderForm: FC<Props> = ({
344
389
  })}
345
390
  </Suspense>
346
391
  )}
392
+ {requireTermsAcceptance && (
393
+ <TermsCheckbox
394
+ name="termsAccept"
395
+ label={termsAndConditionsText}
396
+ required
397
+ errors={errors}
398
+ />
399
+ )}
347
400
  {allowPaymentMethod && (
348
401
  <Button
349
402
  type="submit"
350
403
  loading={loading}
351
404
  buttonText={submitButtonText}
405
+ disable={isSubmitButtonDisabled}
352
406
  />
353
407
  )}
354
408
  <Alert
@@ -361,7 +415,27 @@ const OrderForm: FC<Props> = ({
361
415
  />
362
416
  {errorsMsg?.length > 0 &&
363
417
  allowPaymentMethod &&
364
- errorsMsg.map((i) => <Alert className="mt-2" msg={i} />)}
418
+ errorsMsg.map((i, index) => (
419
+ <Alert key={`${i}-${index}`} className="mt-2" msg={i} />
420
+ ))}
421
+ <OrderDenialModal
422
+ isOpen={!!orderDenialType}
423
+ message={orderDenialMessage}
424
+ closeButtonText={orderDenialCloseButtonText}
425
+ continueButtonText={orderDenialContinueButtonText}
426
+ canContinue={
427
+ orderDenialType === 'offer' &&
428
+ !!orderDenialFallbackOffer?.templatePackageId
429
+ }
430
+ offer={
431
+ orderDenialType === 'offer' &&
432
+ !!orderDenialFallbackOffer?.templatePackageId
433
+ ? orderDenialFallbackOffer
434
+ : undefined
435
+ }
436
+ onClose={dismissOrderDenial}
437
+ onContinue={continueWithFallbackOffer}
438
+ />
365
439
  </form>
366
440
  </FormProvider>
367
441
  );
@@ -0,0 +1,4 @@
1
+ declare module '*.scss' {
2
+ const content: { [className: string]: string };
3
+ export default content;
4
+ }
@@ -32,6 +32,7 @@ export enum FormTypeEnum {
32
32
  export enum UserActionEnum {
33
33
  SELECT_FORM = 'selectForm',
34
34
  SELECT_PAYMENT_METHOD = 'selectPaymentMethod',
35
+ SELECT_FALLBACK_OFFER = 'selectFallbackOffer',
35
36
  RETURN_TO_MAIN = 'returnToMain',
36
37
  TOGGLE_INVOICE_ADDRESS = 'toggleInvoiceAddress',
37
38
  SEARCH_ORGANIZATION_NUMBER = 'searchOrganizationNumber',
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useRef, useState } from 'react';
2
2
  import { SubmitHandler } from 'react-hook-form';
3
3
 
4
4
  import { createSubscriber } from '../api/SubscribeApi';
@@ -13,7 +13,8 @@ import { WRONG_MSG, PAYMENT_METHOD_DEFAULT } from '../constants/index';
13
13
  import { OrderFormInputsType, OrderInfoType } from '../types/order';
14
14
  import {
15
15
  ErrorsMsg,
16
- OrderFormFiledType,
16
+ OrderFormFieldType,
17
+ OrderDenialFallbackOfferType,
17
18
  PaymentMethodOptionsType,
18
19
  ContactRequestType,
19
20
  } from '../types/general';
@@ -35,9 +36,7 @@ type Props = {
35
36
  action: UserActionEnum,
36
37
  args: object | null | undefined
37
38
  ) => void;
38
- setContactCallback?: (
39
- contactInfo: ContactRequestType
40
- ) => void;
39
+ setContactCallback?: (contactInfo: ContactRequestType) => void;
41
40
  templatePackageId: string;
42
41
  subscriberId?: string;
43
42
  userId?: string;
@@ -48,13 +47,59 @@ type Props = {
48
47
  language: string;
49
48
  merchantAgreementUrl: string;
50
49
  paymentMethodsOptions?: PaymentMethodOptionsType;
51
- orderFields: OrderFormFiledType[];
50
+ orderFields: OrderFormFieldType[];
52
51
  invoiceAddressToggle?: boolean;
53
- invoiceOrderFields: OrderFormFiledType[];
52
+ invoiceOrderFields: OrderFormFieldType[];
54
53
  invoiceLookupNotFoundText?: string;
55
54
  errorValidationTitleMsg?: string;
56
55
  errorValidationDenialOrderBlockingMsg?: string;
57
56
  errorValidationBlockingOffersMsg?: string;
57
+ orderDenialOfferBaseText?: string;
58
+ orderDenialOfferWithFallbackText?: string;
59
+ orderDenialAmountText?: string;
60
+ orderDenialFallbackOffer?: OrderDenialFallbackOfferType;
61
+ };
62
+
63
+ type DenialErrorType = {
64
+ status?: number;
65
+ errors?: Record<string, string[] | undefined>;
66
+ };
67
+
68
+ const OFFER_DENIAL_BASE_TEXT =
69
+ 'Hello again!\n\nLooks like you already tried this offer.';
70
+ const OFFER_DENIAL_WITH_FALLBACK_TEXT =
71
+ 'Hello again!\n\nLooks like you already tried this offer, but we would like to keep you with us.\n\nTherefore we got a new great offer for you:';
72
+
73
+ const getDenialErrorType = (
74
+ error: DenialErrorType
75
+ ): 'offer' | 'amount' | null => {
76
+ if (error?.status !== 400 || !error?.errors) {
77
+ return null;
78
+ }
79
+
80
+ const normalizedEntries = Object.entries(error.errors).map(
81
+ ([key, value]) => [key.toLowerCase(), value] as const
82
+ );
83
+
84
+ const hasOffer = normalizedEntries.some(
85
+ ([key, value]) =>
86
+ key === 'offer' && Array.isArray(value) && value.length > 0
87
+ );
88
+
89
+ if (hasOffer) {
90
+ return 'offer';
91
+ }
92
+
93
+ const hasAmount = normalizedEntries.some(
94
+ ([key, value]) =>
95
+ key === 'amount' && Array.isArray(value) && value.length > 0
96
+ );
97
+
98
+ if (hasAmount) {
99
+ return 'amount';
100
+ }
101
+
102
+ return null;
58
103
  };
59
104
 
60
105
  const useOrderForm = ({
@@ -79,17 +124,56 @@ const useOrderForm = ({
79
124
  errorValidationTitleMsg,
80
125
  errorValidationDenialOrderBlockingMsg,
81
126
  errorValidationBlockingOffersMsg,
127
+ orderDenialOfferBaseText,
128
+ orderDenialOfferWithFallbackText,
129
+ orderDenialAmountText,
130
+ orderDenialFallbackOffer,
82
131
  }: Props) => {
83
132
  const [loading, setLoading] = useState<boolean>(false);
84
133
  const [apiErrorMsg, setApiErrorMsg] = useState<string>('');
85
134
  const [errorsMsg, setErrorsMsg] = useState<string[]>([]);
135
+ const [orderDenialType, setOrderDenialType] = useState<
136
+ 'offer' | 'amount' | null
137
+ >(null);
138
+ const [submittedOrderData, setSubmittedOrderData] =
139
+ useState<OrderFormInputsType | null>(null);
140
+ const fallbackOfferTemplatePackageIdRef = useRef<string | null>(null);
141
+
142
+ const dismissOrderDenial = () => {
143
+ setOrderDenialType(null);
144
+ };
145
+
146
+ const getOrderDenialMessage = () => {
147
+ if (orderDenialType === 'offer') {
148
+ if (orderDenialFallbackOffer?.templatePackageId) {
149
+ return (
150
+ orderDenialOfferWithFallbackText ||
151
+ OFFER_DENIAL_WITH_FALLBACK_TEXT
152
+ );
153
+ }
154
+
155
+ return orderDenialOfferBaseText || OFFER_DENIAL_BASE_TEXT;
156
+ }
157
+
158
+ if (orderDenialType === 'amount') {
159
+ return (
160
+ orderDenialAmountText ||
161
+ errorValidationDenialOrderBlockingMsg ||
162
+ WRONG_MSG
163
+ );
164
+ }
165
+
166
+ return '';
167
+ };
86
168
 
87
169
  const orderSubmit: SubmitHandler<OrderFormInputsType> = async (
88
170
  data
89
171
  ): Promise<void> => {
90
172
  setApiErrorMsg('');
91
173
  setErrorsMsg([]);
174
+ setOrderDenialType(null);
92
175
  setLoading(true);
176
+ setSubmittedOrderData(data);
93
177
  try {
94
178
  let id: string | undefined = subscriberId;
95
179
  let invoiceResponse = null;
@@ -152,8 +236,6 @@ const useOrderForm = ({
152
236
  sessionStorage.setItem('subscriberId', id); // For avoiding attempt of creating subscriber again
153
237
  }
154
238
 
155
- console.log('Dette er en ny testversjon av OSDK');
156
-
157
239
  if (setContactCallback) {
158
240
  setContactCallback(contactModel);
159
241
  }
@@ -163,6 +245,10 @@ const useOrderForm = ({
163
245
  if (id) {
164
246
  const paymentMethod =
165
247
  data.paymentMethod || PAYMENT_METHOD_DEFAULT;
248
+ const selectedTemplateSubscriptionPlanId =
249
+ fallbackOfferTemplatePackageIdRef.current ||
250
+ templatePackageId;
251
+ fallbackOfferTemplatePackageIdRef.current = null;
166
252
 
167
253
  const agreements = prepareAgreementModel({
168
254
  paymentMethod,
@@ -188,7 +274,8 @@ const useOrderForm = ({
188
274
  subscriberId: id,
189
275
  subscriptionPlan: {
190
276
  organizationId,
191
- templateSubscriptionPlanId: templatePackageId,
277
+ templateSubscriptionPlanId:
278
+ selectedTemplateSubscriptionPlanId,
192
279
  },
193
280
  paymentAgreement: { ...agreements },
194
281
  invoiceContact: invoiceAddressToggle
@@ -218,6 +305,17 @@ const useOrderForm = ({
218
305
  errorValidationDenialOrderBlockingMsg,
219
306
  } as ErrorMessages;
220
307
 
308
+ const detectedDenialType = getDenialErrorType(
309
+ error as DenialErrorType
310
+ );
311
+ if (detectedDenialType) {
312
+ setApiErrorMsg('');
313
+ setErrorsMsg([]);
314
+ setOrderDenialType(detectedDenialType);
315
+ callback(null);
316
+ return;
317
+ }
318
+
221
319
  console.error(error);
222
320
  setApiErrorMsg(
223
321
  prepareErrorMessage(error as Error, undefined, translations)
@@ -238,11 +336,36 @@ const useOrderForm = ({
238
336
  }
239
337
  };
240
338
 
339
+ const continueWithFallbackOffer = async () => {
340
+ if (orderDenialType !== 'offer' || !submittedOrderData) {
341
+ return;
342
+ }
343
+
344
+ const fallbackTemplatePackageId =
345
+ orderDenialFallbackOffer?.templatePackageId;
346
+
347
+ if (!fallbackTemplatePackageId) {
348
+ dismissOrderDenial();
349
+ return;
350
+ }
351
+
352
+ fallbackOfferTemplatePackageIdRef.current = fallbackTemplatePackageId;
353
+ userActionCallback?.(UserActionEnum.SELECT_FALLBACK_OFFER, {
354
+ templatePackageId: fallbackTemplatePackageId,
355
+ });
356
+ dismissOrderDenial();
357
+ await orderSubmit(submittedOrderData);
358
+ };
359
+
241
360
  return {
242
361
  orderSubmit,
362
+ continueWithFallbackOffer,
243
363
  loading,
244
364
  apiErrorMsg,
245
365
  errorsMsg,
366
+ orderDenialType,
367
+ orderDenialMessage: getOrderDenialMessage(),
368
+ dismissOrderDenial,
246
369
  };
247
370
  };
248
371
 
@@ -1,3 +1,5 @@
1
+ import { ReactNode } from 'react';
2
+
1
3
  import { PaymentMethodEnum, UserActionEnum } from '../enums/general';
2
4
  import { OrderFormInputsType } from './order';
3
5
 
@@ -7,7 +9,7 @@ declare global {
7
9
  }
8
10
  }
9
11
 
10
- export type OrderFormFiledType = {
12
+ export type OrderFormFieldType = {
11
13
  name: string;
12
14
  required?: boolean;
13
15
  readOnly?: boolean;
@@ -18,8 +20,8 @@ export type PaymentMethodOptionsType = {
18
20
  [key in PaymentMethodEnum]: {
19
21
  generateSubscriberContact?: boolean;
20
22
  accountId?: string;
21
- orderFormFields: OrderFormFiledType[];
22
- paymentInvoiceFields?: OrderFormFiledType[] | never[] | null;
23
+ orderFormFields?: OrderFormFieldType[];
24
+ paymentInvoiceFields?: OrderFormFieldType[] | never[] | null;
23
25
  };
24
26
  };
25
27
 
@@ -34,6 +36,13 @@ export type ContactRequestType = {
34
36
  careOf?: string;
35
37
  };
36
38
 
39
+ export type OrderDenialFallbackOfferType = {
40
+ title?: string;
41
+ description?: string;
42
+ price?: string;
43
+ templatePackageId?: string;
44
+ };
45
+
37
46
  export type ConfigType = {
38
47
  submitStartCallback?: (subscriberId: string) => void;
39
48
  userActionCallback?: (
@@ -56,12 +65,13 @@ export type ConfigType = {
56
65
  availablePaymentMethods?: { label: string; value: PaymentMethodEnum }[];
57
66
  allowedPaymentMethods?: PaymentMethodEnum[];
58
67
  paymentMethodsOptions?: PaymentMethodOptionsType;
68
+ requireTermsAcceptance?: boolean;
59
69
  language?: string;
60
70
  merchantAgreementUrl?: string;
61
71
  invoiceAddressSelection?: {
62
72
  enabled?: boolean;
63
73
  label?: string;
64
- fields?: OrderFormFiledType[];
74
+ fields?: OrderFormFieldType[];
65
75
  };
66
76
  settings?: {
67
77
  successText?: string;
@@ -69,6 +79,8 @@ export type ConfigType = {
69
79
  submitButtonText?: string;
70
80
  backButtonText?: string;
71
81
  verifyButtonText?: string;
82
+ orderDenialCloseButtonText?: string;
83
+ orderDenialContinueButtonText?: string;
72
84
  organizationNumberLabel?: string;
73
85
  cvrLabel?: string;
74
86
  glnLabel?: string;
@@ -82,6 +94,11 @@ export type ConfigType = {
82
94
  errorValidationTitleMsg?: string;
83
95
  errorValidationDenialOrderBlockingMsg?: string;
84
96
  errorValidationBlockingOffersMsg?: string;
97
+ orderDenialOfferBaseText?: string;
98
+ orderDenialOfferWithFallbackText?: string;
99
+ orderDenialAmountText?: string;
100
+ orderDenialFallbackOffer?: OrderDenialFallbackOfferType;
101
+ termsAndConditionsText?: string | ReactNode;
85
102
  };
86
103
  };
87
104
 
@@ -24,6 +24,7 @@ export type OrderFormInputsType = {
24
24
  invoiceBuyerReference?: string;
25
25
  gln?: string;
26
26
  cvr?: string;
27
+ termsAccept?: boolean;
27
28
  };
28
29
 
29
30
  export type VippsMobilePay = {
@@ -1,6 +1,6 @@
1
1
  import { MessageEventTypeEnum, PaymentMethodEnum } from '../enums/general';
2
2
  import { AgreementsType, OrderFormInputsType } from '../types/order';
3
- import { OrderFormFiledType } from '../types/general';
3
+ import { OrderFormFieldType } from '../types/general';
4
4
  import { FIELD_TYPES } from '../components/FormFields/FormFields.helper';
5
5
 
6
6
  type AgreementModelType = {
@@ -105,7 +105,7 @@ export const prepareAgreementModel = ({
105
105
 
106
106
  type ContactModelType = {
107
107
  data: OrderFormInputsType;
108
- orderFields: OrderFormFiledType[];
108
+ orderFields: OrderFormFieldType[];
109
109
  };
110
110
 
111
111
  export const prepareContactModel = ({
package/tsconfig.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "es5",
3
+ "target": "es2020",
4
4
  "lib": ["dom", "dom.iterable", "esnext"],
5
- "downlevelIteration": true,
6
5
  "allowJs": true,
7
6
  "checkJs": false,
8
7
  "skipLibCheck": true,
@@ -12,28 +11,26 @@
12
11
  "forceConsistentCasingInFileNames": true,
13
12
  "noFallthroughCasesInSwitch": true,
14
13
  "module": "esnext",
15
- "moduleResolution": "node",
16
14
  "resolveJsonModule": true,
17
15
  "isolatedModules": false,
18
16
  "noEmit": false,
19
17
  "noImplicitAny": true,
20
18
  "jsx": "react-jsx",
21
- "baseUrl": "src",
22
19
  "sourceMap": true,
23
20
  "inlineSources": true,
24
21
  "sourceRoot": "/",
25
22
  "paths": {
26
- "*": ["*"],
27
- "API": ["api"],
28
- "API/*": ["api/*"],
29
- "Component/*": ["components/*"],
30
- "Utils/*": ["utils/*"],
31
- "Hooks/*": ["hooks/*"],
32
- "Models/*": ["models/*"],
33
- "Types/*": ["types/*"],
34
- "Enums/*": ["enums/*"],
35
- "Constants/*": ["constants/*"],
36
- "Assets/*": ["assets/*"]
23
+ "*": ["./src/*"],
24
+ "API": ["./src/api"],
25
+ "API/*": ["./src/api/*"],
26
+ "Component/*": ["./src/components/*"],
27
+ "Utils/*": ["./src/utils/*"],
28
+ "Hooks/*": ["./src/hooks/*"],
29
+ "Models/*": ["./src/models/*"],
30
+ "Types/*": ["./src/types/*"],
31
+ "Enums/*": ["./src/enums/*"],
32
+ "Constants/*": ["./src/constants/*"],
33
+ "Assets/*": ["./src/assets/*"]
37
34
  }
38
35
  },
39
36
  "include": ["src"],