@infosoftas/s4-orders-js-sdk 0.1.20 → 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 (56) hide show
  1. package/README.md +471 -444
  2. package/build/postcss-scope-tailwind.cjs +66 -0
  3. package/components.json +21 -0
  4. package/package.json +59 -50
  5. package/src/App.css +409 -0
  6. package/src/App.tsx +639 -594
  7. package/src/components/Alert/Alert.tsx +23 -22
  8. package/src/components/Button/Button.tsx +18 -22
  9. package/src/components/EHFForm/EHFForm.tsx +253 -235
  10. package/src/components/FormFields/AddressField.tsx +55 -62
  11. package/src/components/FormFields/EmailField.tsx +53 -58
  12. package/src/components/FormFields/InputField.tsx +52 -53
  13. package/src/components/FormFields/NameField.tsx +52 -53
  14. package/src/components/FormFields/PhoneField.tsx +53 -58
  15. package/src/components/FormFields/TermsCheckbox.tsx +70 -58
  16. package/src/components/FormFields/ToggleFiled.tsx +52 -63
  17. package/src/components/FormFields/fieldValidators.ts +71 -0
  18. package/src/components/OIOForm/OIOForm.tsx +130 -69
  19. package/src/components/OrderDenialModal/OrderDenialModal.tsx +102 -89
  20. package/src/components/OrderForm/ContactDetailsSection.tsx +71 -0
  21. package/src/components/OrderForm/OrderForm.tsx +173 -232
  22. package/src/components/OrderForm/PaymentMethodContent.tsx +62 -0
  23. package/src/components/OrderForm/PaymentMethodSection.tsx +79 -0
  24. package/src/components/OrderForm/SubmitConfirmationDialog.tsx +55 -0
  25. package/src/components/OrderForm/orderFormState.ts +171 -0
  26. package/src/components/OrderForm/useSlotElement.tsx +89 -0
  27. package/src/components/ui/alert-dialog.tsx +185 -0
  28. package/src/components/ui/alert.tsx +75 -0
  29. package/src/components/ui/button.tsx +55 -0
  30. package/src/components/ui/card.tsx +99 -0
  31. package/src/components/ui/checkbox.tsx +28 -0
  32. package/src/components/ui/field.tsx +238 -0
  33. package/src/components/ui/input.tsx +19 -0
  34. package/src/components/ui/label.tsx +19 -0
  35. package/src/components/ui/radio-group.tsx +37 -0
  36. package/src/components/ui/separator.tsx +24 -0
  37. package/src/components/ui/skeleton.tsx +13 -0
  38. package/src/components/ui/spinner.tsx +10 -0
  39. package/src/components/ui/switch.tsx +29 -0
  40. package/src/components/ui/textarea.tsx +17 -0
  41. package/src/constants/index.ts +0 -2
  42. package/src/custom.d.ts +4 -4
  43. package/src/index.tsx +2 -0
  44. package/src/lib/utils.ts +1 -0
  45. package/src/types/general.ts +136 -122
  46. package/tsconfig.json +37 -38
  47. package/src/App.scss +0 -23
  48. package/src/assets/styles/forms.scss +0 -164
  49. package/src/assets/styles/general.scss +0 -46
  50. package/src/assets/styles/variables.scss +0 -29
  51. package/src/components/Alert/alert.scss +0 -21
  52. package/src/components/Button/button.scss +0 -48
  53. package/src/components/Loader/Loader.tsx +0 -22
  54. package/src/components/Loader/loader.scss +0 -37
  55. package/src/components/OrderDenialModal/orderDenialModal.scss +0 -42
  56. package/src/components/OrderForm/orderForm.scss +0 -10
package/src/App.tsx CHANGED
@@ -1,594 +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 (
216
- type: MessageEventTypeEnum,
217
- canceledOrderId?: string
218
- ) => {
219
- if (type === MessageEventTypeEnum.CANCEL) {
220
- try {
221
- const orderIdToCancel = canceledOrderId || orderId;
222
- if (orderIdToCancel) {
223
- await handleOrderDelete(orderIdToCancel);
224
- }
225
- } catch (error) {
226
- console.log(error);
227
- }
228
-
229
- setLoading(false);
230
- setOrderId(null);
231
- setOrderInfo(null);
232
- setIframeSrc(null);
233
- setIsConfirmed(false);
234
- setShowOrderForm(true);
235
- }
236
- };
237
-
238
- useMessageEvent(
239
- messageCallback,
240
- handleMessageEvent,
241
- !!showIframe,
242
- orderInfo
243
- );
244
-
245
- const handleForm = (url: string | null, data?: OrderInfoType | null) => {
246
- setIsConfirmed(false);
247
- setIsFailed(false);
248
- setOrderId(data?.orderId || null);
249
- setOrderInfo(data || null);
250
- if (data) {
251
- setOrderFormValues(data as unknown as OrderFormInputsType);
252
- }
253
- if (window === top) {
254
- top.postMessage(
255
- {
256
- type: MessageEventTypeEnum.ORDER_UPDATE_INFO,
257
- isUpdate: true,
258
- orderInfo: data || null,
259
- },
260
- top?.location?.origin || '*'
261
- );
262
- }
263
- if (
264
- data?.paymentMethod === PaymentMethodEnum.Email ||
265
- data?.paymentMethod === PaymentMethodEnum.Invoice
266
- ) {
267
- messageCallback({
268
- orderId: data.orderId || orderId || '',
269
- agreementId: '',
270
- orderInfo: data || orderInfo || null,
271
- });
272
- } else if (data?.paymentMethod === PaymentMethodEnum.EHF) {
273
- setFormType(FormTypeEnum.EHF);
274
- userActionCallback?.(UserActionEnum.SELECT_FORM, {
275
- form: FormTypeEnum.EHF,
276
- });
277
- } else if (data?.paymentMethod === PaymentMethodEnum.OIO) {
278
- setFormType(FormTypeEnum.OIO);
279
- userActionCallback?.(UserActionEnum.SELECT_FORM, {
280
- form: FormTypeEnum.OIO,
281
- });
282
- } else if (url) {
283
- if (
284
- showIframe &&
285
- data?.paymentMethod === PaymentMethodEnum.SwedbankPay
286
- ) {
287
- setIframeSrc(url);
288
- return;
289
- }
290
-
291
- if (
292
- data?.paymentMethod === PaymentMethodEnum.Mollie &&
293
- data?.orderId
294
- ) {
295
- try {
296
- sessionStorage.setItem(
297
- 'mollieOrderId',
298
- JSON.stringify({ id: data.orderId, ts: Date.now() })
299
- );
300
- } catch (e) {
301
- console.warn(
302
- 'Could not persist mollieOrderId; return leg will rely on the S4OrderId param.',
303
- e
304
- );
305
- }
306
- }
307
-
308
- setTimeout(() => {
309
- window.location.href = url;
310
- }, 300);
311
-
312
- return;
313
- } else if (data?.paymentMethod) {
314
- setIsFailed(true);
315
- setShowOrderForm(true);
316
- setFormType(FormTypeEnum.ORDER);
317
- }
318
-
319
- setIframeSrc(null);
320
- };
321
-
322
- const handleInvoiceBack = () => {
323
- setFormType(FormTypeEnum.ORDER);
324
- userActionCallback?.(UserActionEnum.SELECT_FORM, {
325
- form: FormTypeEnum.ORDER,
326
- });
327
- };
328
-
329
- const updateFormData = (data: OrderFormInputsType) => {
330
- setOrderFormValues(data);
331
- setFormType(data.paymentMethod as unknown as FormTypeEnum);
332
- userActionCallback?.(UserActionEnum.SELECT_FORM, {
333
- form: data.paymentMethod,
334
- });
335
- };
336
-
337
- const handleInvoiceForm = async (
338
- _: string | null,
339
- data?: OrderInfoType | null
340
- ) => {
341
- await messageCallback({
342
- orderId: data?.orderId || orderId || '',
343
- agreementId: '',
344
- orderInfo: data || orderInfo || null,
345
- });
346
- };
347
-
348
- const onSubmitStart = (id: string) => {
349
- setSubscriberSdkId(id);
350
- submitStartCallback?.(id);
351
- };
352
-
353
- if (window !== top) {
354
- return null;
355
- }
356
-
357
- return (
358
- <ErrorBoundary>
359
- <div
360
- className={`sdk-order-container ${
361
- iframeSrc ? 'show-iframe' : ''
362
- }`}
363
- data-testid="sdk-order-module-id"
364
- >
365
- {moduleTitle && <h1 className="text-center">{moduleTitle}</h1>}
366
- {loading && (
367
- <div className="d-flex justify-center">
368
- <Loader size="lg" dark={true} />
369
- </div>
370
- )}
371
- {!iframeSrc && showOrderForm && (
372
- <>
373
- {formType === FormTypeEnum.OIO && (
374
- <OIOForm
375
- onBack={handleInvoiceBack}
376
- callback={handleInvoiceForm}
377
- backButtonText={settings?.backButtonText}
378
- verifyButtonText={settings?.verifyButtonText}
379
- orderDenialCloseButtonText={
380
- settings?.orderDenialCloseButtonText
381
- }
382
- orderDenialContinueButtonText={
383
- settings?.orderDenialContinueButtonText
384
- }
385
- organizationNumberLabel={settings?.cvrLabel}
386
- glnLabel={settings?.glnLabel}
387
- organizationNumber={
388
- orderFormValues?.organizationNumber
389
- }
390
- orderValues={orderFormValues}
391
- templatePackageId={templatePackageId}
392
- subscriberId={subscriberSdkId}
393
- userId={userId}
394
- identityProviderId={identityProviderId}
395
- organizationId={organizationId}
396
- redirectUrl={redirectUrl}
397
- showIframe={showIframe}
398
- paymentMethodsOptions={paymentMethodsOptions}
399
- language={language}
400
- merchantAgreementUrl={merchantAgreementUrl}
401
- invoiceAddressSelection={
402
- invoiceAddressSelection
403
- }
404
- invoiceLookupNotFoundText={
405
- settings?.invoiceLookupNotFoundText
406
- }
407
- userActionCallback={userActionCallback}
408
- setContactCallback={setContactCallback}
409
- errorValidationTitleMsg={
410
- settings?.errorValidationTitleMsg
411
- }
412
- errorValidationDenialOrderBlockingMsg={
413
- settings?.errorValidationDenialOrderBlockingMsg
414
- }
415
- errorValidationBlockingOffersMsg={
416
- settings?.errorValidationBlockingOffersMsg
417
- }
418
- orderDenialOfferBaseText={
419
- settings?.orderDenialOfferBaseText
420
- }
421
- orderDenialOfferWithFallbackText={
422
- settings?.orderDenialOfferWithFallbackText
423
- }
424
- orderDenialAmountText={
425
- settings?.orderDenialAmountText
426
- }
427
- fetchDenialFallbackOffer={
428
- settings?.fetchDenialFallbackOffer
429
- }
430
- />
431
- )}
432
- {formType === FormTypeEnum.EHF && (
433
- <EHFForm
434
- onBack={handleInvoiceBack}
435
- callback={handleInvoiceForm}
436
- backButtonText={settings?.backButtonText}
437
- verifyButtonText={settings?.verifyButtonText}
438
- orderDenialCloseButtonText={
439
- settings?.orderDenialCloseButtonText
440
- }
441
- orderDenialContinueButtonText={
442
- settings?.orderDenialContinueButtonText
443
- }
444
- organizationNumberLabel={
445
- settings?.organizationNumberLabel
446
- }
447
- organizationNumber={
448
- orderFormValues?.organizationNumber
449
- }
450
- orderValues={orderFormValues}
451
- templatePackageId={templatePackageId}
452
- subscriberId={subscriberSdkId}
453
- userId={userId}
454
- identityProviderId={identityProviderId}
455
- organizationId={organizationId}
456
- redirectUrl={redirectUrl}
457
- showIframe={showIframe}
458
- paymentMethodsOptions={paymentMethodsOptions}
459
- language={language}
460
- merchantAgreementUrl={merchantAgreementUrl}
461
- invoiceAddressSelection={
462
- invoiceAddressSelection
463
- }
464
- invoiceLookupNotFoundText={
465
- settings?.invoiceLookupNotFoundText
466
- }
467
- userActionCallback={userActionCallback}
468
- setContactCallback={setContactCallback}
469
- errorValidationTitleMsg={
470
- settings?.errorValidationTitleMsg
471
- }
472
- errorValidationDenialOrderBlockingMsg={
473
- settings?.errorValidationDenialOrderBlockingMsg
474
- }
475
- errorValidationBlockingOffersMsg={
476
- settings?.errorValidationBlockingOffersMsg
477
- }
478
- orderDenialOfferBaseText={
479
- settings?.orderDenialOfferBaseText
480
- }
481
- orderDenialOfferWithFallbackText={
482
- settings?.orderDenialOfferWithFallbackText
483
- }
484
- orderDenialAmountText={
485
- settings?.orderDenialAmountText
486
- }
487
- fetchDenialFallbackOffer={
488
- settings?.fetchDenialFallbackOffer
489
- }
490
- />
491
- )}
492
- {formType !== FormTypeEnum.OIO &&
493
- formType !== FormTypeEnum.EHF &&
494
- templatePackageId && (
495
- <OrderForm
496
- callback={handleForm}
497
- updateFormData={updateFormData}
498
- submitStartCallback={onSubmitStart}
499
- templatePackageId={templatePackageId}
500
- subscriberId={subscriberSdkId}
501
- userId={userId}
502
- identityProviderId={identityProviderId}
503
- organizationId={organizationId}
504
- paymentMethods={availablePaymentMethods}
505
- allowedPaymentMethods={
506
- allowedPaymentMethods
507
- }
508
- submitButtonText={
509
- settings?.submitButtonText
510
- }
511
- orderDenialCloseButtonText={
512
- settings?.orderDenialCloseButtonText
513
- }
514
- orderDenialContinueButtonText={
515
- settings?.orderDenialContinueButtonText
516
- }
517
- paymentMethodLabel={
518
- settings?.paymentMethodLabel
519
- }
520
- paymentMethodNotAllowedMsg={
521
- settings?.paymentMethodNotAllowedMsg
522
- }
523
- errorReqMsg={settings?.errorReqMsg}
524
- errorInvalidEmailMsg={
525
- settings?.errorInvalidEmailMsg
526
- }
527
- errorInvalidPhoneMsg={
528
- settings?.errorInvalidPhoneMsg
529
- }
530
- defaultValues={orderFormValues}
531
- redirectUrl={redirectUrl}
532
- showIframe={showIframe}
533
- paymentMethodsOptions={
534
- paymentMethodsOptions
535
- }
536
- language={language}
537
- merchantAgreementUrl={merchantAgreementUrl}
538
- invoiceAddressSelection={
539
- invoiceAddressSelection
540
- }
541
- userActionCallback={userActionCallback}
542
- setContactCallback={setContactCallback}
543
- errorValidationTitleMsg={
544
- settings?.errorValidationTitleMsg
545
- }
546
- errorValidationDenialOrderBlockingMsg={
547
- settings?.errorValidationDenialOrderBlockingMsg
548
- }
549
- errorValidationBlockingOffersMsg={
550
- settings?.errorValidationBlockingOffersMsg
551
- }
552
- orderDenialOfferBaseText={
553
- settings?.orderDenialOfferBaseText
554
- }
555
- orderDenialOfferWithFallbackText={
556
- settings?.orderDenialOfferWithFallbackText
557
- }
558
- orderDenialAmountText={
559
- settings?.orderDenialAmountText
560
- }
561
- fetchDenialFallbackOffer={
562
- settings?.fetchDenialFallbackOffer
563
- }
564
- termsAndConditionsText={
565
- settings?.termsAndConditionsText
566
- }
567
- requireTermsAcceptance={
568
- requireTermsAcceptance
569
- }
570
- />
571
- )}
572
- </>
573
- )}
574
- {showIframe && <MainIframe iframeSrc={iframeSrc} />}
575
- {isConfirmed && (
576
- <Alert
577
- className="mt-2"
578
- type="success"
579
- msg={settings?.successText}
580
- />
581
- )}
582
-
583
- {isFailed && <Alert className="mt-2" msg={failedMsg} />}
584
- {isFailed &&
585
- errorsMsg?.length > 0 &&
586
- errorsMsg.map((i, index) => (
587
- <Alert key={`${i}-${index}`} className="mt-2" msg={i} />
588
- ))}
589
- </div>
590
- </ErrorBoundary>
591
- );
592
- };
593
-
594
- 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;