@infosoftas/s4-orders-js-sdk 0.0.1-alpha2 → 0.0.1-alpha2a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infosoftas/s4-orders-js-sdk",
3
- "version": "0.0.1-alpha2",
3
+ "version": "0.0.1-alpha2a",
4
4
  "scripts": {
5
5
  "build": "rimraf lib && webpack --env mode=production"
6
6
  },
package/src/App.tsx CHANGED
@@ -27,6 +27,7 @@ const App: FC<ConfigType> = ({
27
27
  redirectUrl,
28
28
  showIframe,
29
29
  paymentMethodsOptions,
30
+ invoiceAddressSelection,
30
31
  availablePaymentMethods = [],
31
32
  language = 'en-US',
32
33
  merchantAgreementUrl = '',
@@ -256,6 +257,7 @@ const App: FC<ConfigType> = ({
256
257
  paymentMethodsOptions={paymentMethodsOptions}
257
258
  language={language}
258
259
  merchantAgreementUrl={merchantAgreementUrl}
260
+ invoiceAddressSelection={invoiceAddressSelection}
259
261
  />
260
262
  )}
261
263
  {showIframe && <MainIframe iframeSrc={iframeSrc} />}
@@ -10,6 +10,7 @@ type RequestOrderStartType = {
10
10
  };
11
11
  paymentAgreement: AgreementsType;
12
12
  invoiceContact?: CreateSubscriberRequestType;
13
+ orderReference?: string;
13
14
  };
14
15
 
15
16
  type OrderResponseType = {
@@ -102,4 +102,63 @@
102
102
  }
103
103
  }
104
104
  }
105
+ }
106
+
107
+ .sdk-on-off-label {
108
+ margin-right: 0.5rem;
109
+ }
110
+
111
+ .sdk-on-off {
112
+ position: relative;
113
+ display : inline-block;
114
+ width : 60px;
115
+ height : 28px;
116
+
117
+ input {
118
+ opacity: 0;
119
+ width : 0;
120
+ height : 0;
121
+ }
122
+
123
+ input:checked+.slider:before {
124
+ -webkit-transform: translateX(32px);
125
+ -ms-transform : translateX(32px);
126
+ transform : translateX(32px);
127
+ }
128
+
129
+ input:checked+.slider {
130
+ background-color: $primary-color;
131
+ }
132
+
133
+ .slider {
134
+ position : absolute;
135
+ cursor : pointer;
136
+ top : 0;
137
+ left : 0;
138
+ right : 0;
139
+ bottom : 0;
140
+ background-color : $input-border-disabled-color;
141
+ -webkit-transition: .4s;
142
+ transition : .4s;
143
+
144
+ &:before {
145
+ position : absolute;
146
+ content : "";
147
+ height : 24px;
148
+ width : 24px;
149
+ left : 2px;
150
+ bottom : 2px;
151
+ background-color : white;
152
+ -webkit-transition: .4s;
153
+ transition : .4s;
154
+ }
155
+
156
+ &.round {
157
+ border-radius: 28px;
158
+
159
+ &:before {
160
+ border-radius: 50%;
161
+ }
162
+ }
163
+ }
105
164
  }
@@ -9,6 +9,10 @@
9
9
  justify-content: center;
10
10
  }
11
11
 
12
+ .flex-wrap {
13
+ flex-wrap: wrap;
14
+ }
15
+
12
16
  .align-center {
13
17
  align-items: center;
14
18
  }
@@ -10,13 +10,14 @@ type Props = {
10
10
  label?: string;
11
11
  errorReqMsg?: string;
12
12
  errors?: {
13
- address?: {
13
+ [key: string]: {
14
14
  message?: string;
15
15
  };
16
16
  };
17
17
  };
18
18
 
19
19
  const AddressField: FC<Props> = ({
20
+ name = 'address',
20
21
  required = false,
21
22
  readOnly = false,
22
23
  label = 'Address',
@@ -33,17 +34,15 @@ const AddressField: FC<Props> = ({
33
34
  placeholder=" "
34
35
  readOnly={readOnly}
35
36
  className={`input-control ${readOnly ? 'read-only' : ''}`}
36
- {...register('address', {
37
+ {...register(name, {
37
38
  required: required ? errorReqMsg : false,
38
39
  })}
39
40
  />
40
41
  <label className="label-control" htmlFor={id}>
41
42
  {label} {required && <span className="text-error">*</span>}
42
43
  </label>
43
- {errors && errors.address && (
44
- <div className="text-error caption">
45
- {errors.address.message}
46
- </div>
44
+ {errors && errors[name] && (
45
+ <div className="text-error caption">{errors[name].message}</div>
47
46
  )}
48
47
  </div>
49
48
  );
@@ -11,13 +11,14 @@ type Props = {
11
11
  errorReqMsg?: string;
12
12
  errorInvalidEmailMsg?: string;
13
13
  errors?: {
14
- email?: {
14
+ [key: string]: {
15
15
  message?: string;
16
16
  };
17
17
  };
18
18
  };
19
19
 
20
20
  const EmailField: FC<Props> = ({
21
+ name = 'email',
21
22
  required = false,
22
23
  readOnly = false,
23
24
  label = 'Email',
@@ -36,7 +37,7 @@ const EmailField: FC<Props> = ({
36
37
  className={`input-control ${readOnly ? 'read-only' : ''}`}
37
38
  readOnly={readOnly}
38
39
  type="email"
39
- {...register('email', {
40
+ {...register(name, {
40
41
  required: required ? errorReqMsg : false,
41
42
  pattern: {
42
43
  value: /^[-!#-'*+/-9=?^-~]+(?:\.[-!#-'*+/-9=?^-~]+)*@[-!#-'*+/-9=?^-~]+(?:\.[-!#-'*+/-9=?^-~]{2,20})+$/i,
@@ -47,8 +48,8 @@ const EmailField: FC<Props> = ({
47
48
  <label className="label-control" htmlFor={id}>
48
49
  {label} {required && <span className="text-error">*</span>}
49
50
  </label>
50
- {errors && errors.email && (
51
- <div className="text-error caption">{errors.email.message}</div>
51
+ {errors && errors[name] && (
52
+ <div className="text-error caption">{errors[name].message}</div>
52
53
  )}
53
54
  </div>
54
55
  );
@@ -12,6 +12,15 @@ export const FIELD_TYPES = {
12
12
  ORGANIZATION_NUMBER: 'organizationNumber',
13
13
  BUYER_REFERENCE: 'buyerReference',
14
14
  ORDER_REFERENCE: 'orderReference',
15
+ INVOICE_NAME: 'invoiceName',
16
+ INVOICE_ADDRESS: 'invoiceAddress',
17
+ INVOICE_CITY: 'invoiceCity',
18
+ INVOICE_ZIP: 'invoiceZip',
19
+ INVOICE_COUNTRY: 'invoiceCountry',
20
+ INVOICE_PHONE: 'invoicePhoneNumber',
21
+ INVOICE_EMAIL: 'invoiceEmail',
22
+ INVOICE_CARE_OF: 'invoiceCareOf',
23
+ INVOICE_BUYER_REFERENCE: 'invoiceBuyerReference',
15
24
  };
16
25
 
17
26
  export const DEFAULT_ORDER_FORM_FIELDS: OrderFormFiledType[] = [
@@ -18,6 +18,15 @@ const formFieldsMapper = {
18
18
  [FIELD_TYPES.ORGANIZATION_NUMBER]: InputField,
19
19
  [FIELD_TYPES.BUYER_REFERENCE]: InputField,
20
20
  [FIELD_TYPES.ORDER_REFERENCE]: InputField,
21
+ [FIELD_TYPES.INVOICE_PHONE]: PhoneField,
22
+ [FIELD_TYPES.INVOICE_NAME]: NameField,
23
+ [FIELD_TYPES.INVOICE_EMAIL]: EmailField,
24
+ [FIELD_TYPES.INVOICE_ADDRESS]: AddressField,
25
+ [FIELD_TYPES.INVOICE_COUNTRY]: InputField,
26
+ [FIELD_TYPES.INVOICE_CITY]: InputField,
27
+ [FIELD_TYPES.INVOICE_ZIP]: InputField,
28
+ [FIELD_TYPES.INVOICE_CARE_OF]: InputField,
29
+ [FIELD_TYPES.INVOICE_BUYER_REFERENCE]: InputField,
21
30
  };
22
31
 
23
32
  export default formFieldsMapper;
@@ -10,13 +10,14 @@ type Props = {
10
10
  label?: string;
11
11
  errorReqMsg?: string;
12
12
  errors?: {
13
- name?: {
13
+ [key: string]: {
14
14
  message?: string;
15
15
  };
16
16
  };
17
17
  };
18
18
 
19
19
  const NameField: FC<Props> = ({
20
+ name = 'name',
20
21
  required = false,
21
22
  readOnly = false,
22
23
  label = 'Name',
@@ -33,15 +34,15 @@ const NameField: FC<Props> = ({
33
34
  placeholder=" "
34
35
  readOnly={readOnly}
35
36
  className={`input-control ${readOnly ? 'read-only' : ''}`}
36
- {...register('name', {
37
+ {...register(name, {
37
38
  required: required ? errorReqMsg : false,
38
39
  })}
39
40
  />
40
41
  <label className="label-control" htmlFor={id}>
41
42
  {label} {required && <span className="text-error">*</span>}
42
43
  </label>
43
- {errors && errors.name && (
44
- <div className="text-error caption">{errors.name.message}</div>
44
+ {errors && errors[name] && (
45
+ <div className="text-error caption">{errors[name].message}</div>
45
46
  )}
46
47
  </div>
47
48
  );
@@ -11,13 +11,14 @@ type Props = {
11
11
  errorReqMsg?: string;
12
12
  errorInvalidPhoneMsg?: string;
13
13
  errors?: {
14
- phoneNumber?: {
14
+ [key: string]: {
15
15
  message?: string;
16
16
  };
17
17
  };
18
18
  };
19
19
 
20
20
  const PhoneFiled: FC<Props> = ({
21
+ name = 'phoneNumber',
21
22
  required = false,
22
23
  readOnly = false,
23
24
  label = 'Phone number',
@@ -36,7 +37,7 @@ const PhoneFiled: FC<Props> = ({
36
37
  placeholder=" "
37
38
  readOnly={readOnly}
38
39
  type="tel"
39
- {...register('phoneNumber', {
40
+ {...register(name, {
40
41
  required: required ? errorReqMsg : false,
41
42
  pattern: {
42
43
  value: /^[+]*[(]{0,1}[0-9]{1,3}[)]{0,1}[-\s\./0-9]{6,14}$/g,
@@ -47,10 +48,8 @@ const PhoneFiled: FC<Props> = ({
47
48
  <label className="label-control" htmlFor={id}>
48
49
  {label} {required && <span className="text-error">*</span>}
49
50
  </label>
50
- {errors && errors.phoneNumber && (
51
- <div className="text-error caption">
52
- {errors.phoneNumber.message}
53
- </div>
51
+ {errors && errors[name] && (
52
+ <div className="text-error caption">{errors[name].message}</div>
54
53
  )}
55
54
  </div>
56
55
  );
@@ -0,0 +1,58 @@
1
+ import { FC } from 'react';
2
+ import { useFormContext } from 'react-hook-form';
3
+
4
+ import { uuidv4 } from '../../utils/helper';
5
+
6
+ type Props = {
7
+ name: string;
8
+ required?: boolean;
9
+ readOnly?: boolean;
10
+ label?: string;
11
+ errorReqMsg?: string;
12
+ errors?: {
13
+ [key: string]: {
14
+ message?: string;
15
+ };
16
+ };
17
+ };
18
+
19
+ const ToggleField: FC<Props> = ({
20
+ name,
21
+ required = false,
22
+ readOnly = false,
23
+ label = '',
24
+ errorReqMsg = 'This field is required!',
25
+ errors,
26
+ }) => {
27
+ const { register } = useFormContext();
28
+ const id = uuidv4();
29
+ return (
30
+ <div className="field-wrapper">
31
+ <div className="d-flex align-center flex-wrap">
32
+ <label className="sdk-on-off-label" htmlFor={id}>
33
+ {label} {required && <span className="text-error">*</span>}
34
+ </label>
35
+ <label
36
+ className="sdk-on-off"
37
+ data-testid={`sdk-${name}-field-id`}
38
+ >
39
+ <input
40
+ id={id}
41
+ type="checkbox"
42
+ readOnly={readOnly}
43
+ className={`${readOnly ? 'read-only' : ''}`}
44
+ {...register(name, {
45
+ required: required ? errorReqMsg : false,
46
+ })}
47
+ />
48
+ <span className="slider round"></span>
49
+ </label>
50
+ </div>
51
+ {errors && errors[name] && (
52
+ <div className="text-error caption">{errors[name].message}</div>
53
+ )}
54
+ </div>
55
+ );
56
+ };
57
+
58
+ export default ToggleField;
@@ -7,6 +7,7 @@ import { PaymentMethodEnum } from '../../enums/general';
7
7
  import { WRONG_MSG, PAYMENT_METHOD_DEFAULT } from '../../constants/index';
8
8
  import Alert from '../Alert/Alert';
9
9
  import Button from '../Button/Button';
10
+ import ToggleField from '../FormFields/ToggleFiled';
10
11
  import formFieldsMapper from '../FormFields/FormFieldsMapper';
11
12
  import { DEFAULT_ORDER_FORM_FIELDS } from '../FormFields/FormFields.helper';
12
13
  import { OrderFormInputsType, OrderInfoType } from '../../types/order';
@@ -16,8 +17,10 @@ import {
16
17
  ErrorsMsg,
17
18
  } from '../../types/general';
18
19
  import {
20
+ orderInvoiceContactFields,
19
21
  prepareAgreementModel,
20
22
  prepareContactModel,
23
+ prepareInvoiceModel,
21
24
  } from '../../utils/order.helper';
22
25
  import {
23
26
  prepareErrorMessage,
@@ -45,9 +48,15 @@ type Props = {
45
48
  errorReqMsg?: string;
46
49
  errorInvalidEmailMsg?: string;
47
50
  errorInvalidPhoneMsg?: string;
51
+ invoiceAddressSelection?: {
52
+ enabled?: boolean;
53
+ label?: string;
54
+ fields?: OrderFormFiledType[];
55
+ };
48
56
  };
49
57
 
50
58
  const initialData = {
59
+ invoiceAddressSelection: false,
51
60
  name: '',
52
61
  email: '',
53
62
  phoneNumber: '',
@@ -56,6 +65,7 @@ const initialData = {
56
65
  address: '',
57
66
  zip: '',
58
67
  paymentMethod: PAYMENT_METHOD_DEFAULT,
68
+ orderReference: '',
59
69
  };
60
70
 
61
71
  const OrderForm: FC<Props> = ({
@@ -72,6 +82,7 @@ const OrderForm: FC<Props> = ({
72
82
  language,
73
83
  merchantAgreementUrl,
74
84
  paymentMethods = [],
85
+ invoiceAddressSelection,
75
86
  buttonText = 'Start',
76
87
  paymentMethodLabel = 'Select Payment Method',
77
88
  errorReqMsg = '',
@@ -95,14 +106,19 @@ const OrderForm: FC<Props> = ({
95
106
  defaultValues?.paymentMethod || PAYMENT_METHOD_DEFAULT,
96
107
  },
97
108
  });
109
+ const invoiceOrderFields =
110
+ invoiceAddressSelection?.fields || orderInvoiceContactFields;
98
111
 
99
112
  const {
100
113
  register,
101
114
  handleSubmit,
102
115
  setValue,
116
+ watch,
103
117
  formState: { errors },
104
118
  } = methods;
105
119
 
120
+ const invoiceAddressToggle = watch('invoiceAddressSelection');
121
+
106
122
  const onSubmit: SubmitHandler<OrderFormInputsType> = async (
107
123
  data
108
124
  ): Promise<void> => {
@@ -158,6 +174,13 @@ const OrderForm: FC<Props> = ({
158
174
  templateSubscriptionPlanId: templatePackageId,
159
175
  },
160
176
  paymentAgreement: { ...agreements },
177
+ invoiceContact: invoiceAddressToggle
178
+ ? prepareInvoiceModel({
179
+ data,
180
+ orderFields: invoiceOrderFields,
181
+ })
182
+ : undefined,
183
+ orderReference: data.orderReference || undefined,
161
184
  });
162
185
  callback(responseOrder.terminalRedirectUrl, {
163
186
  orderId: responseOrder.id,
@@ -256,6 +279,35 @@ const OrderForm: FC<Props> = ({
256
279
  })}
257
280
  </Suspense>
258
281
  )}
282
+ {invoiceAddressSelection?.enabled && (
283
+ <ToggleField
284
+ name="invoiceAddressSelection"
285
+ label={
286
+ invoiceAddressSelection?.label ?? 'Invoice Address'
287
+ }
288
+ />
289
+ )}
290
+ {invoiceAddressToggle && invoiceOrderFields?.length > 0 && (
291
+ <Suspense fallback={null}>
292
+ {invoiceOrderFields.map((field) => {
293
+ const Component = formFieldsMapper[field.name];
294
+
295
+ return Component ? (
296
+ <Component
297
+ key={field.name}
298
+ name={field.name}
299
+ label={field.label}
300
+ required={field.required || false}
301
+ readOnly={field.readOnly || false}
302
+ errors={errors}
303
+ errorReqMsg={errorReqMsg}
304
+ errorInvalidEmailMsg={errorInvalidEmailMsg}
305
+ errorInvalidPhoneMsg={errorInvalidPhoneMsg}
306
+ />
307
+ ) : null;
308
+ })}
309
+ </Suspense>
310
+ )}
259
311
  <Button
260
312
  type="submit"
261
313
  loading={loading}
package/src/types/api.ts CHANGED
@@ -20,4 +20,5 @@ export type CreateSubscriberRequestType = {
20
20
  addressLines?: string[];
21
21
  zip?: string;
22
22
  careOf?: string;
23
+ buyerReference?: string;
23
24
  };
@@ -38,6 +38,11 @@ export type ConfigType = {
38
38
  paymentMethodsOptions?: PaymentMethodOptionsType;
39
39
  language?: string;
40
40
  merchantAgreementUrl?: string;
41
+ invoiceAddressSelection?: {
42
+ enabled?: boolean;
43
+ label?: string;
44
+ fields?: OrderFormFiledType[];
45
+ };
41
46
  settings?: {
42
47
  successText?: string;
43
48
  failureText?: string;
@@ -10,6 +10,17 @@ export type OrderFormInputsType = {
10
10
  address?: string;
11
11
  zip?: string;
12
12
  careOf?: string;
13
+ invoiceAddressSelection?: boolean;
14
+ orderReference?: string;
15
+ invoiceName?: string;
16
+ invoiceEmail?: string;
17
+ invoicePhoneNumber?: string;
18
+ invoiceCountry?: string;
19
+ invoiceCity?: string;
20
+ invoiceAddress?: string;
21
+ invoiceZip?: string;
22
+ invoiceCareOf?: string;
23
+ invoiceBuyerReference?: string;
13
24
  };
14
25
 
15
26
  export type VippsMobilePay = {
@@ -1,6 +1,7 @@
1
1
  import { MessageEventTypeEnum, PaymentMethodEnum } from '../enums/general';
2
2
  import { AgreementsType, OrderFormInputsType } from '../types/order';
3
3
  import { OrderFormFiledType } from '../types/general';
4
+ import { FIELD_TYPES } from '../components/FormFields/FormFields.helper';
4
5
 
5
6
  type AgreementModelType = {
6
7
  paymentMethod: PaymentMethodEnum;
@@ -112,9 +113,111 @@ export const prepareContactModel = ({
112
113
  ? data.zip?.trim() || undefined
113
114
  : undefined,
114
115
  careOf: orderFields.find((i) => i.name === 'careOf')
115
- ? data.zip?.trim() || undefined
116
+ ? data.careOf?.trim() || undefined
116
117
  : undefined,
117
118
  };
118
119
 
119
120
  return model;
120
121
  };
122
+
123
+ export const prepareInvoiceModel = ({
124
+ data,
125
+ orderFields,
126
+ }: ContactModelType) => {
127
+ const model = {
128
+ name: orderFields.find((i) => i.name === 'invoiceName')
129
+ ? data.invoiceName?.trim() || undefined
130
+ : undefined,
131
+ email: orderFields.find((i) => i.name === 'invoiceEmail')
132
+ ? data.invoiceEmail?.trim() || undefined
133
+ : undefined,
134
+ phone: orderFields.find((i) => i.name === 'invoicePhoneNumber')
135
+ ? data.invoicePhoneNumber?.trim() || undefined
136
+ : undefined,
137
+ country: orderFields.find((i) => i.name === 'invoiceCountry')
138
+ ? data.invoiceCountry?.trim() || undefined
139
+ : undefined,
140
+ city: orderFields.find((i) => i.name === 'invoiceCity')
141
+ ? data.invoiceCity?.trim() || undefined
142
+ : undefined,
143
+ addressLines: orderFields.find((i) => i.name === 'invoiceAddress')
144
+ ? data.invoiceAddress?.trim()
145
+ ? data.invoiceAddress
146
+ ?.trim()
147
+ .replace(/\r\n/g, '\n')
148
+ .split('\n')
149
+ .filter((line) => line)
150
+ : undefined
151
+ : undefined,
152
+ zip: orderFields.find((i) => i.name === 'invoiceZip')
153
+ ? data.invoiceZip?.trim() || undefined
154
+ : undefined,
155
+ careOf: orderFields.find((i) => i.name === 'invoiceCareOf')
156
+ ? data.invoiceCareOf?.trim() || undefined
157
+ : undefined,
158
+ buyerReference: orderFields.find(
159
+ (i) => i.name === 'invoiceBuyerReference'
160
+ )
161
+ ? data.invoiceBuyerReference?.trim() || undefined
162
+ : undefined,
163
+ };
164
+
165
+ return model;
166
+ };
167
+
168
+ export const orderInvoiceContactFields = [
169
+ {
170
+ name: FIELD_TYPES.INVOICE_NAME,
171
+ label: 'Name',
172
+ required: false,
173
+ readOnly: false,
174
+ },
175
+ {
176
+ name: FIELD_TYPES.INVOICE_EMAIL,
177
+ label: 'Email',
178
+ required: false,
179
+ readOnly: false,
180
+ },
181
+ {
182
+ name: FIELD_TYPES.INVOICE_PHONE,
183
+ label: 'Phone',
184
+ required: false,
185
+ readOnly: false,
186
+ },
187
+ {
188
+ name: FIELD_TYPES.INVOICE_COUNTRY,
189
+ label: 'Country',
190
+ required: false,
191
+ readOnly: false,
192
+ },
193
+ {
194
+ name: FIELD_TYPES.INVOICE_CITY,
195
+ label: 'City',
196
+ required: false,
197
+ readOnly: false,
198
+ },
199
+ {
200
+ name: FIELD_TYPES.INVOICE_ADDRESS,
201
+ label: 'Address',
202
+ required: false,
203
+ readOnly: false,
204
+ },
205
+ {
206
+ name: FIELD_TYPES.INVOICE_ZIP,
207
+ label: 'Zip',
208
+ required: false,
209
+ readOnly: false,
210
+ },
211
+ {
212
+ name: FIELD_TYPES.INVOICE_CARE_OF,
213
+ label: 'Contact Person',
214
+ required: false,
215
+ readOnly: false,
216
+ },
217
+ {
218
+ name: FIELD_TYPES.INVOICE_BUYER_REFERENCE,
219
+ label: 'Buyer Reference',
220
+ required: false,
221
+ readOnly: false,
222
+ },
223
+ ];