@infosoftas/s4-orders-js-sdk 0.0.1-alpha1

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 (40) hide show
  1. package/.prettierignore +1 -0
  2. package/.prettierrc.json +7 -0
  3. package/package.json +41 -0
  4. package/src/App.scss +24 -0
  5. package/src/App.tsx +184 -0
  6. package/src/ErrorBoundary.tsx +41 -0
  7. package/src/api/OrdersApi.ts +44 -0
  8. package/src/api/SubscribeApi.ts +51 -0
  9. package/src/assets/styles/forms.scss +105 -0
  10. package/src/assets/styles/general.scss +38 -0
  11. package/src/assets/styles/variables.scss +29 -0
  12. package/src/components/Alert/Alert.tsx +22 -0
  13. package/src/components/Alert/alert.scss +21 -0
  14. package/src/components/Button/Button.tsx +43 -0
  15. package/src/components/Button/button.scss +48 -0
  16. package/src/components/FormFields/EmailField.tsx +53 -0
  17. package/src/components/FormFields/FormFields.helper.ts +16 -0
  18. package/src/components/FormFields/FormFieldsMapper.ts +13 -0
  19. package/src/components/FormFields/NameField.tsx +46 -0
  20. package/src/components/FormFields/PhoneField.tsx +55 -0
  21. package/src/components/Loader/Loader.tsx +22 -0
  22. package/src/components/Loader/loader.scss +37 -0
  23. package/src/components/MainIframe/MainIframe.tsx +42 -0
  24. package/src/components/OrderForm/OrderForm.tsx +235 -0
  25. package/src/components/OrderForm/orderForm.scss +10 -0
  26. package/src/constants/index.ts +8 -0
  27. package/src/enums/api.ts +4 -0
  28. package/src/enums/general.ts +12 -0
  29. package/src/hooks/useIframe.tsx +59 -0
  30. package/src/hooks/useMessageEvent.tsx +142 -0
  31. package/src/hooks/useQueryParams.ts +5 -0
  32. package/src/index.tsx +22 -0
  33. package/src/types/api.ts +12 -0
  34. package/src/types/general.ts +50 -0
  35. package/src/types/order.ts +41 -0
  36. package/src/utils/fetcher.ts +89 -0
  37. package/src/utils/helper.ts +18 -0
  38. package/src/utils/order.helper.ts +48 -0
  39. package/tsconfig.json +41 -0
  40. package/webpack.config.js +103 -0
@@ -0,0 +1,142 @@
1
+ import { useEffect, useCallback } from 'react';
2
+
3
+ import { MessageEventTypeEnum } from 'Enums/general';
4
+ import { CompleteOrderParamsType, OrderInfoType } from 'Types/order';
5
+ import useQueryParams from './useQueryParams';
6
+
7
+ const useMessageEvent = (
8
+ messageCallback: (data: CompleteOrderParamsType) => Promise<void>,
9
+ handleMessageEvent: (type: MessageEventTypeEnum) => void,
10
+ showIframe: boolean,
11
+ orderInfo: OrderInfoType | null
12
+ ) => {
13
+ const queryParams = useQueryParams();
14
+
15
+ const handleMessage = useCallback(
16
+ async (event: MessageEvent) => {
17
+ // Vipps handler
18
+ if (event.data.type === MessageEventTypeEnum.VIPPS_COMPLETE) {
19
+ await messageCallback({
20
+ orderId: event.data.orderId || '',
21
+ agreementId: event.data.agreementId || '',
22
+ orderInfo: orderInfo || null,
23
+ });
24
+ }
25
+
26
+ // SwedbankPay handler
27
+ if (event.data.type === MessageEventTypeEnum.COMPLETE) {
28
+ await messageCallback({
29
+ orderId: event.data.orderId || '',
30
+ agreementId: event.data.agreementId || '',
31
+ orderInfo: orderInfo || null,
32
+ });
33
+ }
34
+ if (event.data.type === MessageEventTypeEnum.CANCEL) {
35
+ handleMessageEvent(event.data.type);
36
+ }
37
+ },
38
+ [orderInfo]
39
+ );
40
+
41
+ useEffect(() => {
42
+ window.addEventListener('message', handleMessage, false);
43
+
44
+ return () => {
45
+ window.removeEventListener('message', handleMessage, false);
46
+ };
47
+ }, [handleMessage]);
48
+
49
+ // Vipps
50
+ useEffect(() => {
51
+ if (
52
+ queryParams.get('orderId') &&
53
+ queryParams.get('agreementId') &&
54
+ top &&
55
+ window !== top
56
+ ) {
57
+ top?.postMessage(
58
+ {
59
+ type: MessageEventTypeEnum.VIPPS_COMPLETE,
60
+ orderId: queryParams.get('orderId'),
61
+ agreementId: queryParams.get('agreementId'),
62
+ },
63
+ top?.location?.origin || '*'
64
+ );
65
+ }
66
+ }, [queryParams.get('orderId'), queryParams.get('agreementId')]);
67
+
68
+ //Vipps
69
+ useEffect(() => {
70
+ if (
71
+ queryParams.get('orderId') &&
72
+ queryParams.get('agreementId')
73
+ // !showIframe
74
+ ) {
75
+ messageCallback({
76
+ orderId: queryParams.get('orderId') || '',
77
+ agreementId: queryParams.get('agreementId') || '',
78
+ orderInfo: orderInfo || null,
79
+ });
80
+ }
81
+ }, [
82
+ queryParams.get('orderId'),
83
+ queryParams.get('agreementId'),
84
+ // showIframe,
85
+ ]);
86
+
87
+ // SwedbankPay
88
+ useEffect(() => {
89
+ if (
90
+ queryParams.get('action') &&
91
+ queryParams.get('S4OrderId') &&
92
+ queryParams.get('TransactionId') &&
93
+ top &&
94
+ window !== top
95
+ ) {
96
+ top?.postMessage(
97
+ {
98
+ type: queryParams.get('action'),
99
+ orderId: queryParams.get('S4OrderId'),
100
+ agreementId: queryParams.get('TransactionId'),
101
+ },
102
+ top?.location?.origin || '*'
103
+ );
104
+ }
105
+ }, [
106
+ queryParams.get('S4OrderId'),
107
+ queryParams.get('TransactionId'),
108
+ queryParams.get('action'),
109
+ ]);
110
+
111
+ // SwedbankPay
112
+ useEffect(() => {
113
+ if (
114
+ queryParams.get('S4OrderId') &&
115
+ queryParams.get('TransactionId') &&
116
+ queryParams.get('action') === MessageEventTypeEnum.COMPLETE &&
117
+ !showIframe
118
+ ) {
119
+ messageCallback({
120
+ orderId: queryParams.get('S4OrderId') || '',
121
+ agreementId: queryParams.get('TransactionId') || '',
122
+ orderInfo: orderInfo || null,
123
+ });
124
+ }
125
+
126
+ if (
127
+ queryParams.get('action') === MessageEventTypeEnum.CANCEL &&
128
+ !showIframe
129
+ ) {
130
+ handleMessageEvent(
131
+ queryParams.get('action') as MessageEventTypeEnum
132
+ );
133
+ }
134
+ }, [
135
+ queryParams.get('action'),
136
+ queryParams.get('S4OrderId'),
137
+ queryParams.get('TransactionId'),
138
+ showIframe,
139
+ ]);
140
+ };
141
+
142
+ export default useMessageEvent;
@@ -0,0 +1,5 @@
1
+ function useQueryParams(): URLSearchParams {
2
+ return new URLSearchParams(location.search);
3
+ }
4
+
5
+ export default useQueryParams;
package/src/index.tsx ADDED
@@ -0,0 +1,22 @@
1
+ import { createRoot, Root } from 'react-dom/client';
2
+ import { createElement } from 'react';
3
+
4
+ import { ConfigType } from 'Types/general';
5
+ import { createSubscriber, mapSubscriberToUser } from 'API/SubscribeApi';
6
+ import App from './App';
7
+
8
+ let root: Root;
9
+
10
+ export const orderComponent = {
11
+ init: function (config: ConfigType) {
12
+ const domNode = document.getElementById(config.domElementId);
13
+ root = createRoot(domNode || document.createElement('div'));
14
+ const reactElement = createElement(App, config);
15
+ root.render(reactElement);
16
+ },
17
+ remove: function () {
18
+ (root as Root)?.unmount();
19
+ },
20
+ createSubscriber,
21
+ mapSubscriberToUser,
22
+ };
@@ -0,0 +1,12 @@
1
+ import { HttpStatusCode } from 'Enums/api';
2
+
3
+ export interface ApiResponseType<T> {
4
+ status: HttpStatusCode;
5
+ text: string;
6
+ error: string;
7
+ code: HttpStatusCode;
8
+ timestamp: string;
9
+ data: T;
10
+ query: string;
11
+ url: string;
12
+ }
@@ -0,0 +1,50 @@
1
+ import { PaymentMethodEnum } from 'Enums/general';
2
+ import { OrderFormInputsType } from './order';
3
+
4
+ declare global {
5
+ interface Window {
6
+ sdkOrderCallback: () => void;
7
+ }
8
+ }
9
+
10
+ export type OrderFormFiledType = {
11
+ name: string;
12
+ required?: boolean;
13
+ readOnly?: boolean;
14
+ label?: string;
15
+ };
16
+
17
+ export type PaymentMethodOptionsType = {
18
+ [key in PaymentMethodEnum]: {
19
+ generateSubscriberContact?: boolean;
20
+ accountId?: string;
21
+ orderFormFields: OrderFormFiledType[];
22
+ };
23
+ };
24
+
25
+ export type ConfigType = {
26
+ domElementId: string;
27
+ moduleTitle?: string;
28
+ templatePackageId: string;
29
+ subscriberId: string;
30
+ userId: string;
31
+ identityProviderId: string;
32
+ organizationId: string;
33
+ apiKey?: string;
34
+ redirectUrl?: string;
35
+ showIframe?: boolean;
36
+ availablePaymentMethods?: { label: string; value: PaymentMethodEnum }[];
37
+ paymentMethodsOptions?: PaymentMethodOptionsType;
38
+ language?: string;
39
+ merchantAgreementUrl?: string;
40
+ settings?: {
41
+ successText?: string;
42
+ failureText?: string;
43
+ buttonText?: string;
44
+ orderDefaultValues?: OrderFormInputsType;
45
+ paymentMethodLabel?: string;
46
+ errorReqMsg?: string;
47
+ errorInvalidEmailMsg?: string;
48
+ errorInvalidPhoneMsg?: string;
49
+ };
50
+ };
@@ -0,0 +1,41 @@
1
+ import { PaymentMethodEnum } from 'Enums/general';
2
+
3
+ export type OrderFormInputsType = {
4
+ name?: string;
5
+ email?: string;
6
+ phoneNumber?: string;
7
+ paymentMethod?: PaymentMethodEnum;
8
+ };
9
+
10
+ export type VippsMobilePay = {
11
+ phoneNumber?: string;
12
+ merchantRedirectUrl: string;
13
+ merchantAgreementUrl: string;
14
+ generateSubscriberContact?: boolean;
15
+ accountId?: string;
16
+ };
17
+
18
+ export type SwedbankPay = {
19
+ accountId?: string;
20
+ completeUrl: string;
21
+ cancelUrl: string;
22
+ language?: string;
23
+ };
24
+
25
+ export type AgreementsType = {
26
+ paymentProvider: PaymentMethodEnum;
27
+ vippsMobilePay?: VippsMobilePay;
28
+ swedbankPay?: SwedbankPay;
29
+ };
30
+
31
+ export type OrderInfoType = {
32
+ subscriberId: string;
33
+ paymentMethod: string;
34
+ orderId: string;
35
+ };
36
+
37
+ export type CompleteOrderParamsType = {
38
+ orderId: string;
39
+ agreementId: string;
40
+ orderInfo: OrderInfoType | null;
41
+ };
@@ -0,0 +1,89 @@
1
+ import { HttpStatusCode } from 'Enums/api';
2
+ import { ApiResponseType } from 'Types/api';
3
+ import { BASE_API } from 'Constants/index';
4
+
5
+ export type Method =
6
+ | 'GET'
7
+ | 'POST'
8
+ | 'PUT'
9
+ | 'DELETE'
10
+ | 'HEAD'
11
+ | 'OPTIONS'
12
+ | 'PATCH';
13
+
14
+ type Fetcher<T> = {
15
+ url: string;
16
+ params?: Record<string, string | string[]>;
17
+ method?: Method;
18
+ headers?: HeadersInit;
19
+ body?: T;
20
+ };
21
+
22
+ function createHeader<T>(options: Fetcher<T>): Fetcher<T> {
23
+ const accessToken = localStorage.getItem('authTokenKey') || '';
24
+
25
+ const headers = new Headers({
26
+ accept: 'application/json',
27
+ ...(accessToken && {
28
+ Authorization: `Bearer ${accessToken}`,
29
+ }),
30
+ ...options.headers,
31
+ });
32
+
33
+ if (!(options.body instanceof FormData)) {
34
+ headers.append('Content-Type', 'application/json');
35
+ }
36
+
37
+ return { method: 'GET', ...options, headers };
38
+ }
39
+
40
+ async function fetcher<ResponseType, BodyType = void>(
41
+ options: Fetcher<BodyType>
42
+ ): Promise<ApiResponseType<ResponseType> | ResponseType> {
43
+ const { url, method, headers, body, params } = createHeader(options);
44
+
45
+ const endpoint = new URL(`${BASE_API}${url}`);
46
+
47
+ if (params) {
48
+ Object.entries(params).forEach(([key, value]) => {
49
+ if (Array.isArray(value)) {
50
+ value.forEach((v) => endpoint.searchParams.append(key, v));
51
+ return;
52
+ }
53
+ endpoint.searchParams.append(key, value);
54
+ });
55
+ }
56
+
57
+ return new Promise<ApiResponseType<ResponseType> | ResponseType>(
58
+ (resolve, reject) => {
59
+ return fetch(endpoint.toString(), {
60
+ method,
61
+ headers,
62
+ body: body instanceof FormData ? body : JSON.stringify(body),
63
+ })
64
+ .then(async (response) => {
65
+ if (response.status === HttpStatusCode.UNAUTHORIZED) {
66
+ localStorage.removeItem('authTokenKey');
67
+ reject({ message: 'Not Authorized user!' });
68
+ } else if (response.status === HttpStatusCode.FORBIDDEN) {
69
+ reject({ message: 'Something Wrong with permission!' });
70
+ } else {
71
+ const data = await response?.json();
72
+ if (response.ok) {
73
+ resolve(data);
74
+ } else {
75
+ const message =
76
+ data.message || data.details || data.title;
77
+ reject({ ...data, message });
78
+ }
79
+ }
80
+ })
81
+ .catch((error) => {
82
+ console.log(error, 'error');
83
+ reject(error?.response);
84
+ });
85
+ }
86
+ );
87
+ }
88
+
89
+ export default fetcher;
@@ -0,0 +1,18 @@
1
+ import { WRONG_MSG } from 'Constants/index';
2
+
3
+ export const prepareErrorMessage = (error: Error, defaultMsg?: string) => {
4
+ if (typeof error === 'string') {
5
+ try {
6
+ const parsedError = JSON.parse(error);
7
+ return (parsedError as { message: string }).message;
8
+ } catch (e) {
9
+ return error;
10
+ }
11
+ } else {
12
+ return (
13
+ ((error as { message: string })?.message as string) ||
14
+ defaultMsg ||
15
+ WRONG_MSG
16
+ );
17
+ }
18
+ };
@@ -0,0 +1,48 @@
1
+ import { MessageEventTypeEnum, PaymentMethodEnum } from 'Enums/general';
2
+ import { OrderFormInputsType, AgreementsType } from 'Types/order';
3
+
4
+ type AgreementModelType = {
5
+ paymentMethod: PaymentMethodEnum;
6
+ redirectUrl: string;
7
+ data: OrderFormInputsType;
8
+ generateSubscriberContact: boolean;
9
+ language: string;
10
+ merchantAgreementUrl: string;
11
+ accountId?: string;
12
+ };
13
+
14
+ export const prepareAgreementModel = ({
15
+ paymentMethod,
16
+ redirectUrl,
17
+ data,
18
+ generateSubscriberContact,
19
+ language,
20
+ merchantAgreementUrl,
21
+ accountId,
22
+ }: AgreementModelType): AgreementsType => {
23
+ const model: AgreementsType = {
24
+ paymentProvider: paymentMethod,
25
+ };
26
+
27
+ if (paymentMethod === PaymentMethodEnum.SwedbankPay) {
28
+ model.swedbankPay = {
29
+ cancelUrl: `${redirectUrl}?action=${MessageEventTypeEnum.CANCEL}`,
30
+ completeUrl: `${redirectUrl}?action=${MessageEventTypeEnum.COMPLETE}`,
31
+ language,
32
+ accountId,
33
+ };
34
+
35
+ return model;
36
+ }
37
+
38
+ return {
39
+ ...model,
40
+ vippsMobilePay: {
41
+ generateSubscriberContact,
42
+ merchantAgreementUrl,
43
+ accountId,
44
+ phoneNumber: data.phoneNumber,
45
+ merchantRedirectUrl: redirectUrl,
46
+ },
47
+ };
48
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "downlevelIteration": true,
6
+ "allowJs": true,
7
+ "checkJs": false,
8
+ "skipLibCheck": true,
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "strict": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "noFallthroughCasesInSwitch": true,
14
+ "module": "esnext",
15
+ "moduleResolution": "node",
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": false,
18
+ "noEmit": false,
19
+ "noImplicitAny": true,
20
+ "jsx": "react-jsx",
21
+ "baseUrl": "src",
22
+ "sourceMap": true,
23
+ "inlineSources": true,
24
+ "sourceRoot": "/",
25
+ "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/*"]
37
+ }
38
+ },
39
+ "include": ["src"],
40
+ "exclude": ["node_modules"]
41
+ }
@@ -0,0 +1,103 @@
1
+ const path = require('path');
2
+ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
3
+ const Dotenv = require('dotenv-webpack');
4
+
5
+ const envEnum = {
6
+ qa: 'qa',
7
+ dev: 'dev',
8
+ prod: 'production',
9
+ };
10
+
11
+ module.exports = (env) => {
12
+ const enableSourceMap = env.mode === 'dev' ? true : false;
13
+
14
+ return {
15
+ mode: env.mode,
16
+ target: 'web',
17
+ entry: './src/index.tsx',
18
+ devtool: enableSourceMap ? 'inline-source-map' : false,
19
+ experiments: {
20
+ outputModule: true,
21
+ },
22
+ resolve: {
23
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
24
+ alias: {
25
+ Src: path.resolve(__dirname, 'src'),
26
+ API: path.resolve(__dirname, 'src/api'),
27
+ Component: path.resolve(__dirname, 'src/components'),
28
+ Utils: path.resolve(__dirname, 'src/utils'),
29
+ Hooks: path.resolve(__dirname, 'src/hooks'),
30
+ Models: path.resolve(__dirname, 'src/models'),
31
+ Types: path.resolve(__dirname, 'src/types'),
32
+ Enums: path.resolve(__dirname, 'src/enums'),
33
+ Constants: path.resolve(__dirname, 'src/constants'),
34
+ Assets: path.resolve(__dirname, 'src/assets'),
35
+ },
36
+ },
37
+ output: {
38
+ filename: 'script.js',
39
+ path: path.resolve(__dirname, 'dist'),
40
+ publicPath: '/',
41
+ library: {
42
+ type: 'module',
43
+ },
44
+ clean: true,
45
+ asyncChunks: false,
46
+ },
47
+ plugins: [
48
+ new MiniCssExtractPlugin({
49
+ filename: 'css/style.css',
50
+ }),
51
+ new Dotenv({
52
+ path: `./.env.${
53
+ env.mode === envEnum.prod ? 'prod' : env.mode || 'prod'
54
+ }`, // Path to .env file (this is the default)
55
+ safe: false, // load .env.example (defaults to "false" which does not use dotenv-safe)
56
+ }),
57
+ ],
58
+ module: {
59
+ rules: [
60
+ {
61
+ test: /\.(js|jsx)$/,
62
+ exclude: /node_modules/,
63
+ use: {
64
+ loader: 'babel-loader',
65
+ options: {
66
+ presets: [
67
+ '@babel/preset-env',
68
+ '@babel/preset-react',
69
+ '@babel/preset-typescript',
70
+ ],
71
+ },
72
+ },
73
+ },
74
+ {
75
+ test: /\.(ts|tsx)$/,
76
+ exclude: /node_modules/,
77
+ use: ['ts-loader'],
78
+ },
79
+ {
80
+ test: /\.scss$/,
81
+ use: [
82
+ {
83
+ loader: 'style-loader',
84
+ },
85
+ {
86
+ loader: 'css-loader', // translates CSS into CommonJS
87
+ options: { sourceMap: enableSourceMap },
88
+ },
89
+ {
90
+ loader: 'sass-loader', // compiles Scss to CSS
91
+ options: {
92
+ sourceMap: enableSourceMap,
93
+ sassOptions: {
94
+ javascriptEnabled: true,
95
+ },
96
+ },
97
+ },
98
+ ],
99
+ },
100
+ ],
101
+ },
102
+ };
103
+ };