@infosoftas/s4-orders-js-sdk 0.1.19 → 0.1.21-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.
Files changed (62) hide show
  1. package/README.md +471 -426
  2. package/build/postcss-scope-tailwind.cjs +66 -0
  3. package/components.json +21 -0
  4. package/package.json +59 -43
  5. package/src/App.css +409 -0
  6. package/src/App.tsx +639 -569
  7. package/src/api/OrdersApi.ts +2 -2
  8. package/src/components/Alert/Alert.tsx +23 -22
  9. package/src/components/Button/Button.tsx +18 -22
  10. package/src/components/EHFForm/EHFForm.tsx +253 -235
  11. package/src/components/FormFields/AddressField.tsx +55 -62
  12. package/src/components/FormFields/EmailField.tsx +53 -58
  13. package/src/components/FormFields/InputField.tsx +52 -53
  14. package/src/components/FormFields/NameField.tsx +52 -53
  15. package/src/components/FormFields/PhoneField.tsx +53 -58
  16. package/src/components/FormFields/TermsCheckbox.tsx +70 -58
  17. package/src/components/FormFields/ToggleFiled.tsx +52 -63
  18. package/src/components/FormFields/fieldValidators.ts +71 -0
  19. package/src/components/OIOForm/OIOForm.tsx +132 -71
  20. package/src/components/OrderDenialModal/OrderDenialModal.tsx +102 -89
  21. package/src/components/OrderForm/ContactDetailsSection.tsx +71 -0
  22. package/src/components/OrderForm/OrderForm.tsx +177 -224
  23. package/src/components/OrderForm/PaymentMethodContent.tsx +62 -0
  24. package/src/components/OrderForm/PaymentMethodSection.tsx +79 -0
  25. package/src/components/OrderForm/SubmitConfirmationDialog.tsx +55 -0
  26. package/src/components/OrderForm/orderFormState.ts +171 -0
  27. package/src/components/OrderForm/useSlotElement.tsx +89 -0
  28. package/src/components/ui/alert-dialog.tsx +185 -0
  29. package/src/components/ui/alert.tsx +75 -0
  30. package/src/components/ui/button.tsx +55 -0
  31. package/src/components/ui/card.tsx +99 -0
  32. package/src/components/ui/checkbox.tsx +28 -0
  33. package/src/components/ui/field.tsx +238 -0
  34. package/src/components/ui/input.tsx +19 -0
  35. package/src/components/ui/label.tsx +19 -0
  36. package/src/components/ui/radio-group.tsx +37 -0
  37. package/src/components/ui/separator.tsx +24 -0
  38. package/src/components/ui/skeleton.tsx +13 -0
  39. package/src/components/ui/spinner.tsx +10 -0
  40. package/src/components/ui/switch.tsx +29 -0
  41. package/src/components/ui/textarea.tsx +17 -0
  42. package/src/constants/index.ts +0 -2
  43. package/src/custom.d.ts +4 -4
  44. package/src/enums/general.ts +1 -0
  45. package/src/hooks/useMessageEvent.tsx +54 -3
  46. package/src/hooks/useOrderForm.tsx +9 -2
  47. package/src/index.tsx +2 -0
  48. package/src/lib/utils.ts +1 -0
  49. package/src/types/general.ts +136 -111
  50. package/src/types/order.ts +8 -0
  51. package/src/utils/order.helper.ts +14 -3
  52. package/tsconfig.json +37 -38
  53. package/src/App.scss +0 -23
  54. package/src/assets/styles/forms.scss +0 -164
  55. package/src/assets/styles/general.scss +0 -46
  56. package/src/assets/styles/variables.scss +0 -29
  57. package/src/components/Alert/alert.scss +0 -21
  58. package/src/components/Button/button.scss +0 -48
  59. package/src/components/Loader/Loader.tsx +0 -22
  60. package/src/components/Loader/loader.scss +0 -37
  61. package/src/components/OrderDenialModal/orderDenialModal.scss +0 -42
  62. package/src/components/OrderForm/orderForm.scss +0 -10
package/src/App.tsx CHANGED
@@ -1,569 +1,639 @@
1
- import { FC, useEffect, useState } from 'react';
2
-
3
- import {
4
- FormTypeEnum,
5
- MessageEventTypeEnum,
6
- PaymentMethodEnum,
7
- UserActionEnum,
8
- } from './enums/general';
9
- import useMessageEvent from './hooks/useMessageEvent';
10
- import OrderForm from './components/OrderForm/OrderForm';
11
- import OIOForm from './components/OIOForm/OIOForm';
12
- import EHFForm from './components/EHFForm/EHFForm';
13
- import Loader from './components/Loader/Loader';
14
- import MainIframe from './components/MainIframe/MainIframe';
15
- import Alert from './components/Alert/Alert';
16
- import { ConfigType, ErrorsMsg } from './types/general';
17
- import {
18
- CompleteOrderParamsType,
19
- OrderInfoType,
20
- OrderFormInputsType,
21
- } from './types/order';
22
- import { orderComplete, orderDelete } from './api/OrdersApi';
23
- import { prepareErrorMessage, prepareErrorsArrayMessage } from './utils/helper';
24
-
25
- import ErrorBoundary from './ErrorBoundary';
26
-
27
- import './App.scss';
28
-
29
- const App: FC<ConfigType> = ({
30
- submitStartCallback,
31
- userActionCallback,
32
- setContactCallback,
33
- cancelVippsCallback,
34
- moduleTitle,
35
- apiKey,
36
- apiUrl,
37
- templatePackageId,
38
- subscriberId,
39
- userId,
40
- identityProviderId,
41
- organizationId,
42
- redirectUrl,
43
- showIframe,
44
- paymentMethodsOptions,
45
- invoiceAddressSelection,
46
- availablePaymentMethods = [],
47
- allowedPaymentMethods,
48
- requireTermsAcceptance,
49
- language = 'en-US',
50
- merchantAgreementUrl = '',
51
- settings = {
52
- successText: 'Order successful completed!',
53
- failureText: 'Something went wrong!',
54
- buttonText: 'Start',
55
- submitButtonText: 'Start',
56
- backButtonText: 'Back',
57
- verifyButtonText: 'Verify',
58
- organizationNumberLabel: 'Organization Number',
59
- paymentMethodLabel: '',
60
- glnLabel: 'GLN',
61
- cvrLabel: 'CVR',
62
- orderDefaultValues: undefined,
63
- errorReqMsg: '',
64
- errorInvalidEmailMsg: '',
65
- errorInvalidPhoneMsg: '',
66
- errorValidationTitleMsg: 'One or more validation errors occurred.',
67
- errorValidationDenialOrderBlockingMsg:
68
- 'The order/subscription will not be created because the subscriber has a denial order blocking all.',
69
- errorValidationBlockingOffersMsg:
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
- paymentMethodNotAllowedMsg: '',
78
- invoiceLookupNotFoundText:
79
- 'There was no recipient found for the given information',
80
- termsAndConditionsText: '',
81
- },
82
- }) => {
83
- const [loading, setLoading] = useState<boolean>(false);
84
- const [showOrderForm, setShowOrderForm] = useState<boolean>(true);
85
- const [iframeSrc, setIframeSrc] = useState<string | null>(null);
86
- const [orderId, setOrderId] = useState<string | null>(null);
87
- const [subscriberSdkId, setSubscriberSdkId] = useState<string | undefined>(
88
- subscriberId || undefined
89
- );
90
- const [isConfirmed, setIsConfirmed] = useState<boolean>(false);
91
- const [isFailed, setIsFailed] = useState<boolean>(false);
92
- const [failedMsg, setFailedMsg] = useState<string>('');
93
- const [errorsMsg, setErrorsMsg] = useState<string[]>([]);
94
- const [orderInfo, setOrderInfo] = useState<OrderInfoType | null>(null);
95
- const [formType, setFormType] = useState(FormTypeEnum.ORDER);
96
- const [orderFormValues, setOrderFormValues] = useState(
97
- settings?.orderDefaultValues
98
- );
99
-
100
- useEffect(() => {
101
- if (apiKey) {
102
- sessionStorage.setItem('sdk_api_key', apiKey);
103
- localStorage.setItem('sdk_api_key', apiKey);
104
- }
105
- }, [apiKey]);
106
-
107
- useEffect(() => {
108
- if (apiUrl) {
109
- sessionStorage.setItem('sdk_api_url', apiUrl);
110
- }
111
- }, [apiUrl]);
112
-
113
- const messageCallback = async (
114
- data: CompleteOrderParamsType
115
- ): Promise<void> => {
116
- setLoading(true);
117
- setIframeSrc(null);
118
- setShowOrderForm(false);
119
- setIsFailed(false);
120
- setFailedMsg('');
121
- setErrorsMsg([]);
122
- try {
123
- // There is no way to detect the difference between "cancelled" and "accepted" payment for Vipps via UI
124
- // Completing an order for cancelled payment is fine. The backend handles it correctly.
125
- await orderComplete(
126
- orderId || data.orderId || '',
127
- userId && identityProviderId
128
- ? {
129
- userId,
130
- identityProviderId,
131
- }
132
- : undefined
133
- );
134
- setIsConfirmed(true);
135
- setLoading(false);
136
- if (window === top) {
137
- top.postMessage(
138
- {
139
- type: MessageEventTypeEnum.ORDER_FLOW_COMPLETE,
140
- isCompleted: true,
141
- orderInfo: orderInfo,
142
- },
143
- top?.location?.origin || '*'
144
- );
145
- }
146
- } catch (error) {
147
- console.log(error);
148
- if (
149
- (error as { status: number }).status === 409 &&
150
- (data.paymentMethod === PaymentMethodEnum.Vipps ||
151
- data.paymentMethod === PaymentMethodEnum.MobilePay)
152
- ) {
153
- cancelVippsCallback?.();
154
- handleOrderDelete(orderId || data.orderId);
155
- return;
156
- }
157
-
158
- setFailedMsg(
159
- prepareErrorMessage(error as Error, settings?.failureText)
160
- );
161
- setErrorsMsg(
162
- prepareErrorsArrayMessage(
163
- (
164
- error as {
165
- errors: ErrorsMsg;
166
- }
167
- )?.errors
168
- )
169
- );
170
- setIsFailed(true);
171
- setLoading(false);
172
- setShowOrderForm(true);
173
- setFormType(FormTypeEnum.ORDER);
174
- userActionCallback?.(UserActionEnum.SELECT_FORM, {
175
- form: FormTypeEnum.ORDER,
176
- });
177
- }
178
- };
179
-
180
- const handleOrderDelete = async (id: string) => {
181
- try {
182
- await orderDelete(id);
183
- if (window === top) {
184
- top.postMessage(
185
- {
186
- type: MessageEventTypeEnum.ORDER_FLOW_CANCEL,
187
- isCanceled: true,
188
- orderInfo: orderInfo,
189
- },
190
- top?.location?.origin || '*'
191
- );
192
- }
193
- setIsFailed(false);
194
- } catch (error) {
195
- console.log(error);
196
- setFailedMsg(
197
- prepareErrorMessage(error as Error, settings?.failureText)
198
- );
199
- setErrorsMsg(
200
- prepareErrorsArrayMessage(
201
- (
202
- error as {
203
- errors: ErrorsMsg;
204
- }
205
- )?.errors
206
- )
207
- );
208
- setIsFailed(true);
209
- }
210
-
211
- setLoading(false);
212
- setShowOrderForm(true);
213
- };
214
-
215
- const handleMessageEvent = async (type: MessageEventTypeEnum) => {
216
- if (type === MessageEventTypeEnum.CANCEL) {
217
- try {
218
- if (orderId) {
219
- await handleOrderDelete(orderId);
220
- }
221
- } catch (error) {
222
- console.log(error);
223
- }
224
-
225
- setLoading(false);
226
- setOrderId(null);
227
- setOrderInfo(null);
228
- setIframeSrc(null);
229
- setIsConfirmed(false);
230
- setShowOrderForm(true);
231
- }
232
- };
233
-
234
- useMessageEvent(
235
- messageCallback,
236
- handleMessageEvent,
237
- !!showIframe,
238
- orderInfo
239
- );
240
-
241
- const handleForm = (url: string | null, data?: OrderInfoType | null) => {
242
- setIsConfirmed(false);
243
- setIsFailed(false);
244
- setOrderId(data?.orderId || null);
245
- setOrderInfo(data || null);
246
- if (data) {
247
- setOrderFormValues(data as unknown as OrderFormInputsType);
248
- }
249
- if (window === top) {
250
- top.postMessage(
251
- {
252
- type: MessageEventTypeEnum.ORDER_UPDATE_INFO,
253
- isUpdate: true,
254
- orderInfo: data || null,
255
- },
256
- top?.location?.origin || '*'
257
- );
258
- }
259
- if (
260
- data?.paymentMethod === PaymentMethodEnum.Email ||
261
- data?.paymentMethod === PaymentMethodEnum.Invoice
262
- ) {
263
- messageCallback({
264
- orderId: data.orderId || orderId || '',
265
- agreementId: '',
266
- orderInfo: data || orderInfo || null,
267
- });
268
- } else if (data?.paymentMethod === PaymentMethodEnum.EHF) {
269
- setFormType(FormTypeEnum.EHF);
270
- userActionCallback?.(UserActionEnum.SELECT_FORM, {
271
- form: FormTypeEnum.EHF,
272
- });
273
- } else if (data?.paymentMethod === PaymentMethodEnum.OIO) {
274
- setFormType(FormTypeEnum.OIO);
275
- userActionCallback?.(UserActionEnum.SELECT_FORM, {
276
- form: FormTypeEnum.OIO,
277
- });
278
- } else if (url) {
279
- if (
280
- showIframe &&
281
- data?.paymentMethod === PaymentMethodEnum.SwedbankPay
282
- ) {
283
- setIframeSrc(url);
284
- return;
285
- }
286
-
287
- setTimeout(() => {
288
- window.location.href = url;
289
- }, 300);
290
-
291
- return;
292
- }
293
-
294
- setIframeSrc(null);
295
- };
296
-
297
- const handleInvoiceBack = () => {
298
- setFormType(FormTypeEnum.ORDER);
299
- userActionCallback?.(UserActionEnum.SELECT_FORM, {
300
- form: FormTypeEnum.ORDER,
301
- });
302
- };
303
-
304
- const updateFormData = (data: OrderFormInputsType) => {
305
- setOrderFormValues(data);
306
- setFormType(data.paymentMethod as unknown as FormTypeEnum);
307
- userActionCallback?.(UserActionEnum.SELECT_FORM, {
308
- form: data.paymentMethod,
309
- });
310
- };
311
-
312
- const handleInvoiceForm = async (
313
- _: string | null,
314
- data?: OrderInfoType | null
315
- ) => {
316
- await messageCallback({
317
- orderId: data?.orderId || orderId || '',
318
- agreementId: '',
319
- orderInfo: data || orderInfo || null,
320
- });
321
- };
322
-
323
- const onSubmitStart = (id: string) => {
324
- setSubscriberSdkId(id);
325
- submitStartCallback?.(id);
326
- };
327
-
328
- if (window !== top) {
329
- return null;
330
- }
331
-
332
- return (
333
- <ErrorBoundary>
334
- <div
335
- className={`sdk-order-container ${
336
- iframeSrc ? 'show-iframe' : ''
337
- }`}
338
- data-testid="sdk-order-module-id"
339
- >
340
- {moduleTitle && <h1 className="text-center">{moduleTitle}</h1>}
341
- {loading && (
342
- <div className="d-flex justify-center">
343
- <Loader size="lg" dark={true} />
344
- </div>
345
- )}
346
- {!iframeSrc && showOrderForm && (
347
- <>
348
- {formType === FormTypeEnum.OIO && (
349
- <OIOForm
350
- onBack={handleInvoiceBack}
351
- callback={handleInvoiceForm}
352
- backButtonText={settings?.backButtonText}
353
- verifyButtonText={settings?.verifyButtonText}
354
- orderDenialCloseButtonText={
355
- settings?.orderDenialCloseButtonText
356
- }
357
- orderDenialContinueButtonText={
358
- settings?.orderDenialContinueButtonText
359
- }
360
- organizationNumberLabel={settings?.cvrLabel}
361
- glnLabel={settings?.glnLabel}
362
- organizationNumber={
363
- orderFormValues?.organizationNumber
364
- }
365
- orderValues={orderFormValues}
366
- templatePackageId={templatePackageId}
367
- subscriberId={subscriberSdkId}
368
- userId={userId}
369
- identityProviderId={identityProviderId}
370
- organizationId={organizationId}
371
- redirectUrl={redirectUrl}
372
- showIframe={showIframe}
373
- paymentMethodsOptions={paymentMethodsOptions}
374
- language={language}
375
- merchantAgreementUrl={merchantAgreementUrl}
376
- invoiceAddressSelection={
377
- invoiceAddressSelection
378
- }
379
- invoiceLookupNotFoundText={
380
- settings?.invoiceLookupNotFoundText
381
- }
382
- userActionCallback={userActionCallback}
383
- setContactCallback={setContactCallback}
384
- errorValidationTitleMsg={
385
- settings?.errorValidationTitleMsg
386
- }
387
- errorValidationDenialOrderBlockingMsg={
388
- settings?.errorValidationDenialOrderBlockingMsg
389
- }
390
- errorValidationBlockingOffersMsg={
391
- settings?.errorValidationBlockingOffersMsg
392
- }
393
- orderDenialOfferBaseText={
394
- settings?.orderDenialOfferBaseText
395
- }
396
- orderDenialOfferWithFallbackText={
397
- settings?.orderDenialOfferWithFallbackText
398
- }
399
- orderDenialAmountText={
400
- settings?.orderDenialAmountText
401
- }
402
- fetchDenialFallbackOffer={
403
- settings?.fetchDenialFallbackOffer
404
- }
405
- />
406
- )}
407
- {formType === FormTypeEnum.EHF && (
408
- <EHFForm
409
- onBack={handleInvoiceBack}
410
- callback={handleInvoiceForm}
411
- backButtonText={settings?.backButtonText}
412
- verifyButtonText={settings?.verifyButtonText}
413
- orderDenialCloseButtonText={
414
- settings?.orderDenialCloseButtonText
415
- }
416
- orderDenialContinueButtonText={
417
- settings?.orderDenialContinueButtonText
418
- }
419
- organizationNumberLabel={
420
- settings?.organizationNumberLabel
421
- }
422
- organizationNumber={
423
- orderFormValues?.organizationNumber
424
- }
425
- orderValues={orderFormValues}
426
- templatePackageId={templatePackageId}
427
- subscriberId={subscriberSdkId}
428
- userId={userId}
429
- identityProviderId={identityProviderId}
430
- organizationId={organizationId}
431
- redirectUrl={redirectUrl}
432
- showIframe={showIframe}
433
- paymentMethodsOptions={paymentMethodsOptions}
434
- language={language}
435
- merchantAgreementUrl={merchantAgreementUrl}
436
- invoiceAddressSelection={
437
- invoiceAddressSelection
438
- }
439
- invoiceLookupNotFoundText={
440
- settings?.invoiceLookupNotFoundText
441
- }
442
- userActionCallback={userActionCallback}
443
- setContactCallback={setContactCallback}
444
- errorValidationTitleMsg={
445
- settings?.errorValidationTitleMsg
446
- }
447
- errorValidationDenialOrderBlockingMsg={
448
- settings?.errorValidationDenialOrderBlockingMsg
449
- }
450
- errorValidationBlockingOffersMsg={
451
- settings?.errorValidationBlockingOffersMsg
452
- }
453
- orderDenialOfferBaseText={
454
- settings?.orderDenialOfferBaseText
455
- }
456
- orderDenialOfferWithFallbackText={
457
- settings?.orderDenialOfferWithFallbackText
458
- }
459
- orderDenialAmountText={
460
- settings?.orderDenialAmountText
461
- }
462
- fetchDenialFallbackOffer={
463
- settings?.fetchDenialFallbackOffer
464
- }
465
- />
466
- )}
467
- {formType !== FormTypeEnum.OIO &&
468
- formType !== FormTypeEnum.EHF &&
469
- templatePackageId && (
470
- <OrderForm
471
- callback={handleForm}
472
- updateFormData={updateFormData}
473
- submitStartCallback={onSubmitStart}
474
- templatePackageId={templatePackageId}
475
- subscriberId={subscriberSdkId}
476
- userId={userId}
477
- identityProviderId={identityProviderId}
478
- organizationId={organizationId}
479
- paymentMethods={availablePaymentMethods}
480
- allowedPaymentMethods={
481
- allowedPaymentMethods
482
- }
483
- submitButtonText={
484
- settings?.submitButtonText
485
- }
486
- orderDenialCloseButtonText={
487
- settings?.orderDenialCloseButtonText
488
- }
489
- orderDenialContinueButtonText={
490
- settings?.orderDenialContinueButtonText
491
- }
492
- paymentMethodLabel={
493
- settings?.paymentMethodLabel
494
- }
495
- paymentMethodNotAllowedMsg={
496
- settings?.paymentMethodNotAllowedMsg
497
- }
498
- errorReqMsg={settings?.errorReqMsg}
499
- errorInvalidEmailMsg={
500
- settings?.errorInvalidEmailMsg
501
- }
502
- errorInvalidPhoneMsg={
503
- settings?.errorInvalidPhoneMsg
504
- }
505
- defaultValues={orderFormValues}
506
- redirectUrl={redirectUrl}
507
- showIframe={showIframe}
508
- paymentMethodsOptions={
509
- paymentMethodsOptions
510
- }
511
- language={language}
512
- merchantAgreementUrl={merchantAgreementUrl}
513
- invoiceAddressSelection={
514
- invoiceAddressSelection
515
- }
516
- userActionCallback={userActionCallback}
517
- setContactCallback={setContactCallback}
518
- errorValidationTitleMsg={
519
- settings?.errorValidationTitleMsg
520
- }
521
- errorValidationDenialOrderBlockingMsg={
522
- settings?.errorValidationDenialOrderBlockingMsg
523
- }
524
- errorValidationBlockingOffersMsg={
525
- settings?.errorValidationBlockingOffersMsg
526
- }
527
- orderDenialOfferBaseText={
528
- settings?.orderDenialOfferBaseText
529
- }
530
- orderDenialOfferWithFallbackText={
531
- settings?.orderDenialOfferWithFallbackText
532
- }
533
- orderDenialAmountText={
534
- settings?.orderDenialAmountText
535
- }
536
- fetchDenialFallbackOffer={
537
- settings?.fetchDenialFallbackOffer
538
- }
539
- termsAndConditionsText={
540
- settings?.termsAndConditionsText
541
- }
542
- requireTermsAcceptance={
543
- requireTermsAcceptance
544
- }
545
- />
546
- )}
547
- </>
548
- )}
549
- {showIframe && <MainIframe iframeSrc={iframeSrc} />}
550
- {isConfirmed && (
551
- <Alert
552
- className="mt-2"
553
- type="success"
554
- msg={settings?.successText}
555
- />
556
- )}
557
-
558
- {isFailed && <Alert className="mt-2" msg={failedMsg} />}
559
- {isFailed &&
560
- errorsMsg?.length > 0 &&
561
- errorsMsg.map((i, index) => (
562
- <Alert key={`${i}-${index}`} className="mt-2" msg={i} />
563
- ))}
564
- </div>
565
- </ErrorBoundary>
566
- );
567
- };
568
-
569
- export default App;
1
+ import { FC, useEffect, useState } from 'react';
2
+
3
+ import {
4
+ FormTypeEnum,
5
+ MessageEventTypeEnum,
6
+ PaymentMethodEnum,
7
+ UserActionEnum,
8
+ } from './enums/general';
9
+ import useMessageEvent from './hooks/useMessageEvent';
10
+ import OrderForm from './components/OrderForm/OrderForm';
11
+ import OIOForm from './components/OIOForm/OIOForm';
12
+ import EHFForm from './components/EHFForm/EHFForm';
13
+ import MainIframe from './components/MainIframe/MainIframe';
14
+ import Alert from './components/Alert/Alert';
15
+ import { Skeleton } from './components/ui/skeleton';
16
+ import { Card, CardContent } from './components/ui/card';
17
+ import { ConfigType, ErrorsMsg } from './types/general';
18
+ import {
19
+ CompleteOrderParamsType,
20
+ OrderInfoType,
21
+ OrderFormInputsType,
22
+ } from './types/order';
23
+ import { orderComplete, orderDelete } from './api/OrdersApi';
24
+ import { prepareErrorMessage, prepareErrorsArrayMessage } from './utils/helper';
25
+
26
+ import ErrorBoundary from './ErrorBoundary';
27
+
28
+ const App: FC<ConfigType> = ({
29
+ submitStartCallback,
30
+ userActionCallback,
31
+ setContactCallback,
32
+ cancelVippsCallback,
33
+ moduleTitle,
34
+ apiKey,
35
+ apiUrl,
36
+ templatePackageId,
37
+ subscriberId,
38
+ userId,
39
+ identityProviderId,
40
+ organizationId,
41
+ redirectUrl,
42
+ showIframe,
43
+ paymentMethodsOptions,
44
+ invoiceAddressSelection,
45
+ availablePaymentMethods = [],
46
+ allowedPaymentMethods,
47
+ requireTermsAcceptance,
48
+ language = 'en-US',
49
+ merchantAgreementUrl = '',
50
+ settings = {
51
+ successText: 'Order successful completed!',
52
+ failureText: 'Something went wrong!',
53
+ buttonText: 'Start',
54
+ submitButtonText: 'Start',
55
+ backButtonText: 'Back',
56
+ verifyButtonText: 'Verify',
57
+ organizationNumberLabel: 'Organization Number',
58
+ paymentMethodLabel: '',
59
+ contactDetailsLabel: '',
60
+ glnLabel: 'GLN',
61
+ cvrLabel: 'CVR',
62
+ orderDefaultValues: undefined,
63
+ errorReqMsg: '',
64
+ errorInvalidEmailMsg: '',
65
+ errorInvalidPhoneMsg: '',
66
+ errorTermsMsg: '',
67
+ errorValidationTitleMsg: 'One or more validation errors occurred.',
68
+ errorValidationDenialOrderBlockingMsg:
69
+ 'The order/subscription will not be created because the subscriber has a denial order blocking all.',
70
+ errorValidationBlockingOffersMsg:
71
+ 'The order/subscription will not be created because the subscriber has a denial order blocking offers.',
72
+ orderDenialOfferBaseText:
73
+ 'Hello again!\n\nLooks like you already tried this offer.',
74
+ orderDenialOfferWithFallbackText:
75
+ '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:',
76
+ orderDenialAmountText:
77
+ 'We are sorry, but you do not meet the requirements for this offer. Please contact customer service for more information.',
78
+ paymentMethodNotAllowedMsg: '',
79
+ invoiceLookupNotFoundText:
80
+ 'There was no recipient found for the given information',
81
+ termsAndConditionsText: '',
82
+ orderDenialCloseButtonText: 'Close',
83
+ orderDenialContinueButtonText: 'Continue',
84
+ },
85
+ }) => {
86
+ const [loading, setLoading] = useState<boolean>(false);
87
+ const [showOrderForm, setShowOrderForm] = useState<boolean>(true);
88
+ const [iframeSrc, setIframeSrc] = useState<string | null>(null);
89
+ const [orderId, setOrderId] = useState<string | null>(null);
90
+ const [subscriberSdkId, setSubscriberSdkId] = useState<string | undefined>(
91
+ subscriberId || undefined
92
+ );
93
+ const [isConfirmed, setIsConfirmed] = useState<boolean>(false);
94
+ const [isFailed, setIsFailed] = useState<boolean>(false);
95
+ const [failedMsg, setFailedMsg] = useState<string>('');
96
+ const [errorsMsg, setErrorsMsg] = useState<string[]>([]);
97
+ const [orderInfo, setOrderInfo] = useState<OrderInfoType | null>(null);
98
+ const [formType, setFormType] = useState(FormTypeEnum.ORDER);
99
+ const [orderFormValues, setOrderFormValues] = useState(
100
+ settings?.orderDefaultValues
101
+ );
102
+
103
+ useEffect(() => {
104
+ if (apiKey) {
105
+ sessionStorage.setItem('sdk_api_key', apiKey);
106
+ localStorage.setItem('sdk_api_key', apiKey);
107
+ }
108
+ }, [apiKey]);
109
+
110
+ useEffect(() => {
111
+ if (apiUrl) {
112
+ sessionStorage.setItem('sdk_api_url', apiUrl);
113
+ }
114
+ }, [apiUrl]);
115
+
116
+ const messageCallback = async (
117
+ data: CompleteOrderParamsType
118
+ ): Promise<void> => {
119
+ setLoading(true);
120
+ setIframeSrc(null);
121
+ setShowOrderForm(false);
122
+ setIsFailed(false);
123
+ setFailedMsg('');
124
+ setErrorsMsg([]);
125
+ try {
126
+ // There is no way to detect the difference between "cancelled" and "accepted" payment for Vipps via UI
127
+ // Completing an order for cancelled payment is fine. The backend handles it correctly.
128
+ await orderComplete(
129
+ orderId || data.orderId || '',
130
+ userId && identityProviderId
131
+ ? {
132
+ userId,
133
+ identityProviderId,
134
+ }
135
+ : undefined
136
+ );
137
+ setIsConfirmed(true);
138
+ setLoading(false);
139
+ if (window === top) {
140
+ top.postMessage(
141
+ {
142
+ type: MessageEventTypeEnum.ORDER_FLOW_COMPLETE,
143
+ isCompleted: true,
144
+ orderInfo: orderInfo,
145
+ },
146
+ top?.location?.origin || '*'
147
+ );
148
+ }
149
+ } catch (error) {
150
+ console.log(error);
151
+ if (
152
+ (error as { status: number }).status === 409 &&
153
+ (data.paymentMethod === PaymentMethodEnum.Vipps ||
154
+ data.paymentMethod === PaymentMethodEnum.MobilePay)
155
+ ) {
156
+ cancelVippsCallback?.();
157
+ handleOrderDelete(orderId || data.orderId);
158
+ return;
159
+ }
160
+
161
+ setFailedMsg(
162
+ prepareErrorMessage(error as Error, settings?.failureText)
163
+ );
164
+ setErrorsMsg(
165
+ prepareErrorsArrayMessage(
166
+ (
167
+ error as {
168
+ errors: ErrorsMsg;
169
+ }
170
+ )?.errors
171
+ )
172
+ );
173
+ setIsFailed(true);
174
+ setLoading(false);
175
+ setShowOrderForm(true);
176
+ setFormType(FormTypeEnum.ORDER);
177
+ userActionCallback?.(UserActionEnum.SELECT_FORM, {
178
+ form: FormTypeEnum.ORDER,
179
+ });
180
+ }
181
+ };
182
+
183
+ const handleOrderDelete = async (id: string) => {
184
+ try {
185
+ await orderDelete(id);
186
+ if (window === top) {
187
+ top.postMessage(
188
+ {
189
+ type: MessageEventTypeEnum.ORDER_FLOW_CANCEL,
190
+ isCanceled: true,
191
+ orderInfo: orderInfo,
192
+ },
193
+ top?.location?.origin || '*'
194
+ );
195
+ }
196
+ setIsFailed(false);
197
+ } catch (error) {
198
+ console.log(error);
199
+ setFailedMsg(
200
+ prepareErrorMessage(error as Error, settings?.failureText)
201
+ );
202
+ setErrorsMsg(
203
+ prepareErrorsArrayMessage(
204
+ (
205
+ error as {
206
+ errors: ErrorsMsg;
207
+ }
208
+ )?.errors
209
+ )
210
+ );
211
+ setIsFailed(true);
212
+ }
213
+
214
+ setLoading(false);
215
+ setShowOrderForm(true);
216
+ };
217
+
218
+ const handleMessageEvent = async (
219
+ type: MessageEventTypeEnum,
220
+ canceledOrderId?: string
221
+ ) => {
222
+ if (type === MessageEventTypeEnum.CANCEL) {
223
+ try {
224
+ const orderIdToCancel = canceledOrderId || orderId;
225
+ if (orderIdToCancel) {
226
+ await handleOrderDelete(orderIdToCancel);
227
+ }
228
+ } catch (error) {
229
+ console.log(error);
230
+ }
231
+
232
+ setLoading(false);
233
+ setOrderId(null);
234
+ setOrderInfo(null);
235
+ setIframeSrc(null);
236
+ setIsConfirmed(false);
237
+ setShowOrderForm(true);
238
+ }
239
+ };
240
+
241
+ useMessageEvent(
242
+ messageCallback,
243
+ handleMessageEvent,
244
+ !!showIframe,
245
+ orderInfo
246
+ );
247
+
248
+ const handleForm = (url: string | null, data?: OrderInfoType | null) => {
249
+ setIsConfirmed(false);
250
+ setIsFailed(false);
251
+ setOrderId(data?.orderId || null);
252
+ setOrderInfo(data || null);
253
+ if (data) {
254
+ setOrderFormValues(data as unknown as OrderFormInputsType);
255
+ }
256
+ if (window === top) {
257
+ top.postMessage(
258
+ {
259
+ type: MessageEventTypeEnum.ORDER_UPDATE_INFO,
260
+ isUpdate: true,
261
+ orderInfo: data || null,
262
+ },
263
+ top?.location?.origin || '*'
264
+ );
265
+ }
266
+ if (
267
+ data?.paymentMethod === PaymentMethodEnum.Email ||
268
+ data?.paymentMethod === PaymentMethodEnum.Invoice
269
+ ) {
270
+ messageCallback({
271
+ orderId: data.orderId || orderId || '',
272
+ agreementId: '',
273
+ orderInfo: data || orderInfo || null,
274
+ });
275
+ } else if (data?.paymentMethod === PaymentMethodEnum.EHF) {
276
+ setFormType(FormTypeEnum.EHF);
277
+ userActionCallback?.(UserActionEnum.SELECT_FORM, {
278
+ form: FormTypeEnum.EHF,
279
+ });
280
+ } else if (data?.paymentMethod === PaymentMethodEnum.OIO) {
281
+ setFormType(FormTypeEnum.OIO);
282
+ userActionCallback?.(UserActionEnum.SELECT_FORM, {
283
+ form: FormTypeEnum.OIO,
284
+ });
285
+ } else if (url) {
286
+ if (
287
+ showIframe &&
288
+ data?.paymentMethod === PaymentMethodEnum.SwedbankPay
289
+ ) {
290
+ setIframeSrc(url);
291
+ return;
292
+ }
293
+
294
+ if (
295
+ data?.paymentMethod === PaymentMethodEnum.Mollie &&
296
+ data?.orderId
297
+ ) {
298
+ try {
299
+ sessionStorage.setItem(
300
+ 'mollieOrderId',
301
+ JSON.stringify({ id: data.orderId, ts: Date.now() })
302
+ );
303
+ } catch (e) {
304
+ console.warn(
305
+ 'Could not persist mollieOrderId; return leg will rely on the S4OrderId param.',
306
+ e
307
+ );
308
+ }
309
+ }
310
+
311
+ setTimeout(() => {
312
+ window.location.href = url;
313
+ }, 300);
314
+
315
+ return;
316
+ } else if (data?.paymentMethod) {
317
+ setIsFailed(true);
318
+ setShowOrderForm(true);
319
+ setFormType(FormTypeEnum.ORDER);
320
+ }
321
+
322
+ setIframeSrc(null);
323
+ };
324
+
325
+ const handleInvoiceBack = () => {
326
+ setFormType(FormTypeEnum.ORDER);
327
+ userActionCallback?.(UserActionEnum.SELECT_FORM, {
328
+ form: FormTypeEnum.ORDER,
329
+ });
330
+ };
331
+
332
+ const updateFormData = (data: OrderFormInputsType) => {
333
+ setOrderFormValues(data);
334
+ setFormType(data.paymentMethod as unknown as FormTypeEnum);
335
+ userActionCallback?.(UserActionEnum.SELECT_FORM, {
336
+ form: data.paymentMethod,
337
+ });
338
+ };
339
+
340
+ const handleInvoiceForm = async (
341
+ _: string | null,
342
+ data?: OrderInfoType | null
343
+ ) => {
344
+ await messageCallback({
345
+ orderId: data?.orderId || orderId || '',
346
+ agreementId: '',
347
+ orderInfo: data || orderInfo || null,
348
+ });
349
+ };
350
+
351
+ const onSubmitStart = (id: string) => {
352
+ setSubscriberSdkId(id);
353
+ submitStartCallback?.(id);
354
+ };
355
+
356
+ if (window !== top) {
357
+ return null;
358
+ }
359
+
360
+ if (loading) {
361
+ return (
362
+ <div
363
+ className="sdk-order-container sdk-scope"
364
+ data-testid="sdk-order-loading-skeleton"
365
+ aria-busy="true"
366
+ >
367
+ <Card>
368
+ <CardContent className="flex flex-col gap-4">
369
+ <div className="flex flex-col gap-1.5">
370
+ <Skeleton className="h-3 w-16" />
371
+ <Skeleton className="h-9 w-full rounded-3xl" />
372
+ </div>
373
+ <div className="flex flex-col gap-1.5">
374
+ <Skeleton className="h-3 w-16" />
375
+ <Skeleton className="h-9 w-full rounded-3xl" />
376
+ </div>
377
+ <div className="flex flex-col gap-1.5">
378
+ <Skeleton className="h-3 w-16" />
379
+ <Skeleton className="h-9 w-full rounded-3xl" />
380
+ </div>
381
+ <div className="flex items-center gap-2">
382
+ <Skeleton className="size-4 shrink-0 rounded-[5px]" />
383
+ <Skeleton className="h-4 w-2/3" />
384
+ </div>
385
+ <Skeleton className="h-9 w-full rounded-4xl" />
386
+ </CardContent>
387
+ </Card>
388
+ </div>
389
+ );
390
+ }
391
+
392
+ return (
393
+ <ErrorBoundary>
394
+ <div
395
+ className={`sdk-order-container sdk-scope ${
396
+ iframeSrc ? 'show-iframe' : ''
397
+ }`}
398
+ data-testid="sdk-order-module-id"
399
+ >
400
+ {moduleTitle && <h1 className="text-center">{moduleTitle}</h1>}
401
+ {!iframeSrc && showOrderForm && (
402
+ <>
403
+ {formType === FormTypeEnum.OIO && (
404
+ <OIOForm
405
+ onBack={handleInvoiceBack}
406
+ callback={handleInvoiceForm}
407
+ backButtonText={settings?.backButtonText}
408
+ verifyButtonText={settings?.verifyButtonText}
409
+ orderDenialCloseButtonText={
410
+ settings?.orderDenialCloseButtonText
411
+ }
412
+ orderDenialContinueButtonText={
413
+ settings?.orderDenialContinueButtonText
414
+ }
415
+ organizationNumberLabel={settings?.cvrLabel}
416
+ glnLabel={settings?.glnLabel}
417
+ organizationNumber={
418
+ orderFormValues?.organizationNumber
419
+ }
420
+ orderValues={orderFormValues}
421
+ templatePackageId={templatePackageId}
422
+ subscriberId={subscriberSdkId}
423
+ userId={userId}
424
+ identityProviderId={identityProviderId}
425
+ organizationId={organizationId}
426
+ redirectUrl={redirectUrl}
427
+ showIframe={showIframe}
428
+ paymentMethodsOptions={paymentMethodsOptions}
429
+ language={language}
430
+ merchantAgreementUrl={merchantAgreementUrl}
431
+ invoiceAddressSelection={
432
+ invoiceAddressSelection
433
+ }
434
+ invoiceLookupNotFoundText={
435
+ settings?.invoiceLookupNotFoundText
436
+ }
437
+ errorReqMsg={settings?.errorReqMsg}
438
+ userActionCallback={userActionCallback}
439
+ setContactCallback={setContactCallback}
440
+ errorValidationTitleMsg={
441
+ settings?.errorValidationTitleMsg
442
+ }
443
+ errorValidationDenialOrderBlockingMsg={
444
+ settings?.errorValidationDenialOrderBlockingMsg
445
+ }
446
+ errorValidationBlockingOffersMsg={
447
+ settings?.errorValidationBlockingOffersMsg
448
+ }
449
+ orderDenialOfferBaseText={
450
+ settings?.orderDenialOfferBaseText
451
+ }
452
+ orderDenialOfferWithFallbackText={
453
+ settings?.orderDenialOfferWithFallbackText
454
+ }
455
+ orderDenialAmountText={
456
+ settings?.orderDenialAmountText
457
+ }
458
+ fetchDenialFallbackOffer={
459
+ settings?.fetchDenialFallbackOffer
460
+ }
461
+ />
462
+ )}
463
+ {formType === FormTypeEnum.EHF && (
464
+ <EHFForm
465
+ onBack={handleInvoiceBack}
466
+ callback={handleInvoiceForm}
467
+ backButtonText={settings?.backButtonText}
468
+ verifyButtonText={settings?.verifyButtonText}
469
+ orderDenialCloseButtonText={
470
+ settings?.orderDenialCloseButtonText
471
+ }
472
+ orderDenialContinueButtonText={
473
+ settings?.orderDenialContinueButtonText
474
+ }
475
+ organizationNumberLabel={
476
+ settings?.organizationNumberLabel
477
+ }
478
+ organizationNumber={
479
+ orderFormValues?.organizationNumber
480
+ }
481
+ orderValues={orderFormValues}
482
+ templatePackageId={templatePackageId}
483
+ subscriberId={subscriberSdkId}
484
+ userId={userId}
485
+ identityProviderId={identityProviderId}
486
+ organizationId={organizationId}
487
+ redirectUrl={redirectUrl}
488
+ showIframe={showIframe}
489
+ paymentMethodsOptions={paymentMethodsOptions}
490
+ language={language}
491
+ merchantAgreementUrl={merchantAgreementUrl}
492
+ invoiceAddressSelection={
493
+ invoiceAddressSelection
494
+ }
495
+ invoiceLookupNotFoundText={
496
+ settings?.invoiceLookupNotFoundText
497
+ }
498
+ errorReqMsg={settings?.errorReqMsg}
499
+ userActionCallback={userActionCallback}
500
+ setContactCallback={setContactCallback}
501
+ errorValidationTitleMsg={
502
+ settings?.errorValidationTitleMsg
503
+ }
504
+ errorValidationDenialOrderBlockingMsg={
505
+ settings?.errorValidationDenialOrderBlockingMsg
506
+ }
507
+ errorValidationBlockingOffersMsg={
508
+ settings?.errorValidationBlockingOffersMsg
509
+ }
510
+ orderDenialOfferBaseText={
511
+ settings?.orderDenialOfferBaseText
512
+ }
513
+ orderDenialOfferWithFallbackText={
514
+ settings?.orderDenialOfferWithFallbackText
515
+ }
516
+ orderDenialAmountText={
517
+ settings?.orderDenialAmountText
518
+ }
519
+ fetchDenialFallbackOffer={
520
+ settings?.fetchDenialFallbackOffer
521
+ }
522
+ />
523
+ )}
524
+ {formType !== FormTypeEnum.OIO &&
525
+ formType !== FormTypeEnum.EHF &&
526
+ templatePackageId && (
527
+ <OrderForm
528
+ callback={handleForm}
529
+ updateFormData={updateFormData}
530
+ submitStartCallback={onSubmitStart}
531
+ templatePackageId={templatePackageId}
532
+ subscriberId={subscriberSdkId}
533
+ userId={userId}
534
+ identityProviderId={identityProviderId}
535
+ organizationId={organizationId}
536
+ paymentMethods={availablePaymentMethods}
537
+ allowedPaymentMethods={
538
+ allowedPaymentMethods
539
+ }
540
+ submitButtonText={
541
+ settings?.submitButtonText
542
+ }
543
+ orderDenialCloseButtonText={
544
+ settings?.orderDenialCloseButtonText
545
+ }
546
+ orderDenialContinueButtonText={
547
+ settings?.orderDenialContinueButtonText
548
+ }
549
+ paymentMethodLabel={
550
+ settings?.paymentMethodLabel
551
+ }
552
+ contactDetailsLabel={
553
+ settings?.contactDetailsLabel
554
+ }
555
+ paymentMethodElementId={
556
+ settings?.paymentMethodElementId
557
+ }
558
+ contactDetailsElementId={
559
+ settings?.contactDetailsElementId
560
+ }
561
+ paymentMethodNotAllowedMsg={
562
+ settings?.paymentMethodNotAllowedMsg
563
+ }
564
+ errorReqMsg={settings?.errorReqMsg}
565
+ errorTermsMsg={settings?.errorTermsMsg}
566
+ errorInvalidEmailMsg={
567
+ settings?.errorInvalidEmailMsg
568
+ }
569
+ errorInvalidPhoneMsg={
570
+ settings?.errorInvalidPhoneMsg
571
+ }
572
+ defaultValues={orderFormValues}
573
+ redirectUrl={redirectUrl}
574
+ showIframe={showIframe}
575
+ paymentMethodsOptions={
576
+ paymentMethodsOptions
577
+ }
578
+ language={language}
579
+ merchantAgreementUrl={merchantAgreementUrl}
580
+ invoiceAddressSelection={
581
+ invoiceAddressSelection
582
+ }
583
+ userActionCallback={userActionCallback}
584
+ setContactCallback={setContactCallback}
585
+ errorValidationTitleMsg={
586
+ settings?.errorValidationTitleMsg
587
+ }
588
+ errorValidationDenialOrderBlockingMsg={
589
+ settings?.errorValidationDenialOrderBlockingMsg
590
+ }
591
+ errorValidationBlockingOffersMsg={
592
+ settings?.errorValidationBlockingOffersMsg
593
+ }
594
+ orderDenialOfferBaseText={
595
+ settings?.orderDenialOfferBaseText
596
+ }
597
+ orderDenialOfferWithFallbackText={
598
+ settings?.orderDenialOfferWithFallbackText
599
+ }
600
+ orderDenialAmountText={
601
+ settings?.orderDenialAmountText
602
+ }
603
+ fetchDenialFallbackOffer={
604
+ settings?.fetchDenialFallbackOffer
605
+ }
606
+ termsAndConditionsText={
607
+ settings?.termsAndConditionsText
608
+ }
609
+ submitConfirmation={
610
+ settings?.submitConfirmation
611
+ }
612
+ requireTermsAcceptance={
613
+ requireTermsAcceptance
614
+ }
615
+ />
616
+ )}
617
+ </>
618
+ )}
619
+ {showIframe && <MainIframe iframeSrc={iframeSrc} />}
620
+ {isConfirmed && (
621
+ <Alert
622
+ className="mt-2"
623
+ type="success"
624
+ msg={settings?.successText}
625
+ />
626
+ )}
627
+
628
+ {isFailed && <Alert className="mt-2" msg={failedMsg} />}
629
+ {isFailed &&
630
+ errorsMsg?.length > 0 &&
631
+ errorsMsg.map((i, index) => (
632
+ <Alert key={`${i}-${index}`} className="mt-2" msg={i} />
633
+ ))}
634
+ </div>
635
+ </ErrorBoundary>
636
+ );
637
+ };
638
+
639
+ export default App;