@akinon/pz-saved-card 1.101.0-snapshot-ZERO-3653-20250925093957 → 1.101.0-snapshot-ZERO-3648-20250925195915

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/CHANGELOG.md CHANGED
@@ -1,12 +1,6 @@
1
1
  # @akinon/pz-saved-card
2
2
 
3
- ## 1.101.0-snapshot-ZERO-3653-20250925093957
4
-
5
- ### Minor Changes
6
-
7
- - 4ca44c7: ZERO-3634: add register_consumer_card
8
- - 5b50079: ZERO-3634: iyzico saved card
9
- - 9442cf0: ZERO-3653: make the register_consumer_card option optional
3
+ ## 1.101.0-snapshot-ZERO-3648-20250925195915
10
4
 
11
5
  ## 1.100.0
12
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/pz-saved-card",
3
- "version": "1.101.0-snapshot-ZERO-3653-20250925093957",
3
+ "version": "1.101.0-snapshot-ZERO-3648-20250925195915",
4
4
  "license": "MIT",
5
5
  "main": "src/index.tsx",
6
6
  "peerDependencies": {
@@ -1,14 +1,12 @@
1
- import { cloneElement } from 'react';
1
+ import React, { cloneElement } from 'react';
2
2
  import { Button, Icon } from '@akinon/next/components';
3
- import { AgreementAndSubmitProps } from '../types';
4
3
 
5
4
  export const AgreementAndSubmit = ({
6
5
  agreementCheckbox,
7
6
  control,
8
7
  errors,
9
- buttonText,
10
- formError
11
- }: AgreementAndSubmitProps) => (
8
+ buttonText
9
+ }) => (
12
10
  <div className="flex flex-col text-xs pb-4 px-4 sm:px-6">
13
11
  {agreementCheckbox &&
14
12
  cloneElement(agreementCheckbox, {
@@ -16,16 +14,6 @@ export const AgreementAndSubmit = ({
16
14
  error: errors.agreement,
17
15
  fieldId: 'agreement'
18
16
  })}
19
- {formError?.non_field_errors && (
20
- <div className="w-full text-xs text-start px-1 mt-3 text-error">
21
- {formError.non_field_errors}
22
- </div>
23
- )}
24
- {formError?.status && (
25
- <div className="w-full text-xs text-start px-1 mt-3 text-error">
26
- {formError.status}
27
- </div>
28
- )}
29
17
  <Button
30
18
  type="submit"
31
19
  className="group uppercase mt-4 inline-flex items-center justify-center"
@@ -1,4 +1,5 @@
1
1
  import { setDeletionModalId, setDeletionModalVisible } from '../redux/reducer';
2
+ import React from 'react';
2
3
  import { ErrorText } from './error-text';
3
4
  import { CardLabel } from './card-label';
4
5
  import { DeleteIcon } from './delete-icon';
@@ -1,17 +1,19 @@
1
1
  import SavedCardInstallments from './installments';
2
- import { InstallmentSectionProps } from '../types';
2
+ import React from 'react';
3
3
 
4
4
  export const InstallmentSection = ({
5
5
  title,
6
+ selectedCard,
6
7
  installmentOptions,
7
8
  translations,
8
9
  errors
9
- }: InstallmentSectionProps) => (
10
+ }) => (
10
11
  <div className="border-solid border-gray-400 bg-white">
11
12
  <div className="px-4 py-2">
12
13
  <span className="text-black-800 text-lg font-medium">{title}</span>
13
14
  </div>
14
15
  <SavedCardInstallments
16
+ selectedCard={selectedCard}
15
17
  installmentOptions={installmentOptions}
16
18
  translations={translations}
17
19
  error={errors}
@@ -12,12 +12,14 @@ const defaultTranslations = {
12
12
  };
13
13
 
14
14
  type SavedCardInstallmentsProps = {
15
+ selectedCard: SavedCard;
15
16
  installmentOptions: any[];
16
17
  translations?: InstallmentTexts;
17
18
  error: any;
18
19
  };
19
20
 
20
21
  const SavedCardInstallments = ({
22
+ selectedCard,
21
23
  installmentOptions = [],
22
24
  translations,
23
25
  error
@@ -28,6 +30,7 @@ const SavedCardInstallments = ({
28
30
 
29
31
  useEffect(() => {
30
32
  if (
33
+ selectedCard &&
31
34
  installmentOptions.length > 0 &&
32
35
  installmentOptions[0]?.pk !== installmentOption
33
36
  ) {
@@ -37,7 +40,7 @@ const SavedCardInstallments = ({
37
40
  });
38
41
  setInstallmentOption(firstOptionPk);
39
42
  }
40
- }, [installmentOptions, installmentOption, setInstallment]);
43
+ }, [installmentOptions, installmentOption, setInstallment, selectedCard]);
41
44
 
42
45
  if (installmentOptions.length === 0) {
43
46
  return (
package/src/index.tsx CHANGED
@@ -1,11 +1,5 @@
1
1
  import savedCardReducer from './redux/reducer';
2
2
  import savedCardMiddleware from './redux/middleware';
3
3
  import SavedCardOption from './views/saved-card-option';
4
- import IyzicoSavedCardOption from './views/iyzico-saved-card-option';
5
4
 
6
- export {
7
- savedCardReducer,
8
- savedCardMiddleware,
9
- SavedCardOption,
10
- IyzicoSavedCardOption
11
- };
5
+ export { savedCardReducer, savedCardMiddleware, SavedCardOption };
package/src/redux/api.ts CHANGED
@@ -21,49 +21,20 @@ interface GetResponse<T> {
21
21
  results: T[];
22
22
  }
23
23
 
24
- type DefaultSetSavedCardRequest = {
24
+ type SetSavedCardRequest = {
25
25
  card: SavedCard;
26
26
  };
27
27
 
28
- type IyzicoSetSavedCardRequest = {
29
- card: string;
30
- };
31
-
32
- type SetSavedCardRequest =
33
- | DefaultSetSavedCardRequest
34
- | IyzicoSetSavedCardRequest;
35
-
36
28
  type DeleteSavedCardRequest = number;
37
29
 
38
30
  type SetInstallmentRequest = {
39
31
  installment: string;
40
32
  };
41
33
 
42
- type DefaultCompleteSavedCardRequest = {
34
+ type CompleteSavedCardRequest = {
43
35
  agreement: boolean;
44
36
  };
45
37
 
46
- type IyzicoCompleteSavedCardRequest =
47
- | {
48
- agreement: boolean;
49
- register_consumer_card: boolean;
50
- consumer_token: string;
51
- card_token: string;
52
- }
53
- | {
54
- agreement: boolean;
55
- card_holder: string;
56
- card_number: string;
57
- card_cvv: string;
58
- card_month: string;
59
- card_year: string;
60
- register_consumer_card: boolean;
61
- };
62
-
63
- type CompleteSavedCardRequest =
64
- | DefaultCompleteSavedCardRequest
65
- | IyzicoCompleteSavedCardRequest;
66
-
67
38
  export const savedCardApi = api.injectEndpoints({
68
39
  endpoints: (build) => ({
69
40
  getSavedCards: build.query<GetResponse<SavedCard>, void>({
@@ -100,7 +71,7 @@ export const savedCardApi = api.injectEndpoints({
100
71
  ),
101
72
  method: 'POST',
102
73
  body: {
103
- card: typeof card === 'string' ? card : card.token
74
+ card: card.token
104
75
  }
105
76
  }),
106
77
  async onQueryStarted({ card }, { dispatch, queryFulfilled }) {
@@ -135,7 +106,7 @@ export const savedCardApi = api.injectEndpoints({
135
106
  CheckoutResponse,
136
107
  CompleteSavedCardRequest
137
108
  >({
138
- query: (body) => ({
109
+ query: ({ agreement }) => ({
139
110
  url: buildClientRequestUrl(
140
111
  `/orders/checkout?page=CompleteSavedCardRequest`,
141
112
  {
@@ -143,7 +114,9 @@ export const savedCardApi = api.injectEndpoints({
143
114
  }
144
115
  ),
145
116
  method: 'POST',
146
- body
117
+ body: {
118
+ agreement
119
+ }
147
120
  }),
148
121
  async onQueryStarted(arg, { dispatch, queryFulfilled }) {
149
122
  dispatch(setPaymentStepBusy(true));
@@ -1,27 +1,22 @@
1
1
  import { Middleware } from '@reduxjs/toolkit';
2
- import { setCards, setUcs } from './reducer';
2
+ import { setCards } from './reducer';
3
3
 
4
4
  const savedCardMiddleware: Middleware = ({ dispatch }) => {
5
5
  return (next) => (action) => {
6
6
  const result = next(action);
7
7
 
8
8
  const savedCardContext = result.payload?.context_list?.find(
9
- (context) => context.page_slug === 'savedcardselectionpage'
9
+ (context) =>
10
+ context.page_slug === 'savedcardselectionpage' &&
11
+ context.page_context.cards
10
12
  );
11
13
 
12
- if (savedCardContext && savedCardContext.page_context?.cards) {
14
+ if (savedCardContext) {
13
15
  const cards = JSON.parse(
14
16
  JSON.stringify(savedCardContext.page_context.cards)
15
17
  );
16
18
 
17
19
  dispatch(setCards(cards));
18
- } else if (savedCardContext && savedCardContext.page_context?.ucs) {
19
- const ucs = JSON.parse(JSON.stringify(savedCardContext.page_context.ucs));
20
-
21
- const match = ucs.script.match(/<script\b[^>]*>([\s\S]*?)<\/script>/);
22
- ucs.script = match ? match[1] : ucs.script;
23
-
24
- dispatch(setUcs(ucs));
25
20
  }
26
21
 
27
22
  return result;
@@ -7,20 +7,12 @@ export type SavedCard = {
7
7
  token: string;
8
8
  };
9
9
 
10
- export type IyzicoUcs = {
11
- maskedGsmNumber?: string;
12
- script?: string;
13
- scriptType?: string;
14
- ucsToken?: string;
15
- };
16
-
17
10
  export interface SavedCardState {
18
11
  cards?: Array<SavedCard>;
19
12
  deletion: {
20
13
  id: number;
21
14
  isModalVisible: boolean;
22
15
  };
23
- ucs?: IyzicoUcs;
24
16
  }
25
17
 
26
18
  const initialState: SavedCardState = {
@@ -43,22 +35,11 @@ const savedCardSlice = createSlice({
43
35
  },
44
36
  setDeletionModalVisible(state, { payload }) {
45
37
  state.deletion.isModalVisible = payload;
46
- },
47
- setUcs(state, action: { payload: IyzicoUcs }) {
48
- state.ucs = action.payload;
49
- },
50
- resetUcs() {
51
- return initialState;
52
38
  }
53
39
  }
54
40
  });
55
41
 
56
- export const {
57
- setCards,
58
- setDeletionModalId,
59
- setDeletionModalVisible,
60
- setUcs,
61
- resetUcs
62
- } = savedCardSlice.actions;
42
+ export const { setCards, setDeletionModalId, setDeletionModalVisible } =
43
+ savedCardSlice.actions;
63
44
 
64
45
  export default savedCardSlice.reducer;
@@ -1,5 +1,4 @@
1
1
  import { ReactElement } from 'react';
2
- import { Control, FieldErrors } from 'react-hook-form';
3
2
 
4
3
  export type InstallmentTexts = {
5
4
  title?: string;
@@ -16,9 +15,6 @@ export type DeletePopupTexts = {
16
15
 
17
16
  export type ErrorTexts = {
18
17
  required?: string;
19
- cardNumberLength?: string;
20
- cvvLength?: string;
21
- cardHolderMatches?: string;
22
18
  };
23
19
 
24
20
  export type SavedCardOptionTexts = {
@@ -29,29 +25,6 @@ export type SavedCardOptionTexts = {
29
25
  errors?: ErrorTexts;
30
26
  };
31
27
 
32
- export type IyzicoSavedCardOptionTexts = {
33
- title?: string;
34
- button?: string;
35
- installment?: InstallmentTexts;
36
- errors?: ErrorTexts & {
37
- saveCardRequired?: string;
38
- };
39
- label?: {
40
- cardHolder?: string;
41
- cardNumber?: string;
42
- expiryDate?: string;
43
- cvv?: string;
44
- saveCardForFuture?: string;
45
- };
46
- placeholder?: {
47
- cardHolder?: string;
48
- cardMonth?: string;
49
- cardYear?: string;
50
- };
51
- savedCardsTitle?: string;
52
- paymentErrorMessage?: string;
53
- };
54
-
55
28
  export type CardSelectionSectionProps = {
56
29
  title: string;
57
30
  cards: any[];
@@ -64,34 +37,15 @@ export type CardSelectionSectionProps = {
64
37
 
65
38
  export type InstallmentSectionProps = {
66
39
  title: string;
40
+ selectedCard: any;
67
41
  installmentOptions: any;
68
42
  translations: InstallmentTexts;
69
43
  errors: any;
70
- setFormValue?: any;
71
44
  };
72
45
 
73
- export interface AgreementAndSubmitProps {
74
- agreementCheckbox?: ReactElement;
75
- control: Control<any>;
76
- errors: FieldErrors;
77
- buttonText: string;
78
- formError?: {
79
- non_field_errors?: string;
80
- status?: string;
81
- [key: string]: any;
82
- };
83
- }
84
-
85
- export type CardFormSectionProps = {
86
- register: any;
46
+ export type AgreementAndSubmitProps = {
47
+ agreementCheckbox: ReactElement | undefined;
87
48
  control: any;
88
49
  errors: any;
89
- months: Array<{ label: string; value: string }>;
90
- years: Array<{ label: string; value: string }>;
91
- translations?: IyzicoSavedCardOptionTexts['label'];
92
- setFormValue?: any;
93
- };
94
-
95
- export type IyzicoSavedCardsProps = {
96
- title?: string;
50
+ buttonText: string;
97
51
  };
@@ -183,6 +183,7 @@ const SavedCardOption = ({
183
183
  {customRender?.installmentSection ? (
184
184
  customRender.installmentSection({
185
185
  title: mergedTexts.installment?.title,
186
+ selectedCard,
186
187
  installmentOptions,
187
188
  translations: mergedTexts.installment,
188
189
  errors: errors.installment
@@ -190,6 +191,7 @@ const SavedCardOption = ({
190
191
  ) : (
191
192
  <InstallmentSection
192
193
  title={mergedTexts.installment?.title}
194
+ selectedCard={selectedCard}
193
195
  installmentOptions={installmentOptions}
194
196
  translations={mergedTexts.installment}
195
197
  errors={errors.installment}
@@ -1,127 +0,0 @@
1
- import React from 'react';
2
- import { Input, Select, Icon, Image } from '@akinon/next/components';
3
- import { Control, UseFormRegister, FieldErrors } from 'react-hook-form';
4
-
5
- export interface CardFormSectionProps {
6
- register: UseFormRegister<any>;
7
- control: Control<any>;
8
- errors: FieldErrors;
9
- months: Array<{ label: string; value: string }>;
10
- years: Array<{ label: string; value: string }>;
11
- translations?: {
12
- cardHolder?: string;
13
- cardNumber?: string;
14
- expiryDate?: string;
15
- cvv?: string;
16
- cvvTooltip?: string;
17
- saveCardForFuture?: string;
18
- };
19
- }
20
-
21
- export const CardFormSection = ({
22
- register,
23
- control,
24
- errors,
25
- months,
26
- years,
27
- translations = {},
28
- }: CardFormSectionProps) => {
29
- return (
30
- <div className="w-full bg-white">
31
- <div className="px-4 my-2 w-full flex justify-between flex-wrap">
32
- <div className="my-2 w-full sm:px-4">
33
- <Input
34
- label={translations.cardHolder}
35
- {...register('card_holder')}
36
- error={errors.card_holder}
37
- />
38
- </div>
39
-
40
- <div className="my-2 w-full flex flex-col sm:px-4">
41
- <div className="text-xs text-gray-800 mb-2 w-full flex justify-between items-center">
42
- <span>{translations.cardNumber}</span>
43
- </div>
44
-
45
- <Input
46
- format="#### #### #### ####"
47
- mask="_"
48
- allowEmptyFormatting={true}
49
- control={control}
50
- {...register('card_number')}
51
- error={errors.card_number}
52
- />
53
- </div>
54
-
55
- <div className="w-full my-2 sm:flex">
56
- <div className="sm:w-2/3 sm:px-4">
57
- <label
58
- className="flex w-full text-xs text-start text-black-400 mb-1.5"
59
- htmlFor="card_month"
60
- >
61
- {translations.expiryDate}
62
- </label>
63
-
64
- <div className="flex w-full h-10 space-x-2.5">
65
- <div className="w-2/4">
66
- <Select
67
- className="w-full text-xs border-gray-400 sm:text-sm"
68
- options={months}
69
- {...register('card_month')}
70
- error={errors.card_month}
71
- />
72
- </div>
73
-
74
- <div className="w-2/4">
75
- <Select
76
- className="w-full text-xs border-gray-400 sm:text-sm"
77
- options={years}
78
- {...register('card_year')}
79
- error={errors.card_year}
80
- />
81
- </div>
82
- </div>
83
- </div>
84
-
85
- <div className="my-2 sm:w-1/3 sm:px-4 sm:my-0">
86
- <label
87
- className="flex w-full text-xs text-start text-black-400 mb-1.5"
88
- htmlFor="card_cvv"
89
- >
90
- {translations.cvv}
91
- </label>
92
- <Input
93
- format="###"
94
- mask="_"
95
- control={control}
96
- allowEmptyFormatting={true}
97
- {...register('card_cvv')}
98
- error={errors.card_cvv}
99
- />
100
- <div className="group relative flex items-center justify-start text-gray-600 cursor-pointer mt-2 transition-all hover:text-secondary">
101
- <span className="text-xs underline">
102
- {translations.cvv}
103
- </span>
104
- <Icon name="cvc" size={16} className="leading-none ml-2" />
105
- <div className="hidden group-hover:block absolute right-0 bottom-5 w-[11rem] lg:w-[21rem] lg:left-auto lg:right-auto border-2">
106
- <Image src="/cvv.jpg" alt="Cvv" width={385} height={264} />
107
- </div>
108
- </div>
109
- </div>
110
- </div>
111
-
112
- <div className="my-2 w-full flex flex-col sm:px-4">
113
- <label className="flex items-center space-x-2">
114
- <input
115
- type="checkbox"
116
- {...register('register_consumer_card')}
117
- className="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded"
118
- />
119
- <span className="text-sm text-gray-700">
120
- {translations.saveCardForFuture}
121
- </span>
122
- </label>
123
- </div>
124
- </div>
125
- </div>
126
- );
127
- };
@@ -1,445 +0,0 @@
1
- 'use client';
2
-
3
- import * as yup from 'yup';
4
- import { ObjectSchema } from 'yup';
5
- import { yupResolver } from '@hookform/resolvers/yup';
6
- import { useForm } from 'react-hook-form';
7
- import {
8
- FormHTMLAttributes,
9
- HTMLAttributes,
10
- ReactElement,
11
- useEffect,
12
- useMemo,
13
- useState
14
- } from 'react';
15
- import { useAppSelector } from '@akinon/next/redux/hooks';
16
- import {
17
- useCompleteSavedCardMutation,
18
- useSetSavedCardMutation
19
- } from '../redux/api';
20
-
21
- import {
22
- AgreementAndSubmitProps,
23
- CardFormSectionProps,
24
- ErrorTexts,
25
- InstallmentSectionProps,
26
- IyzicoSavedCardOptionTexts,
27
- IyzicoSavedCardsProps
28
- } from '../types';
29
- import { InstallmentSection } from '../components/installment-section';
30
- import { AgreementAndSubmit } from '../components/agreement-and-submit';
31
- import { CardFormSection } from '../components/card-form-section';
32
- import Script from 'next/script';
33
-
34
- type IyzicoSavedCardOptionProps = {
35
- texts?: IyzicoSavedCardOptionTexts;
36
- agreementCheckbox?: ReactElement;
37
- customFormSchema?: {
38
- 'new-card'?: ObjectSchema<any>;
39
- 'existing-card'?: ObjectSchema<any>;
40
- };
41
- customRender?: {
42
- cardFormSection?: (props: CardFormSectionProps) => ReactElement;
43
- savedCardsSection?: (props: IyzicoSavedCardsProps) => ReactElement;
44
- installmentSection?: (props: InstallmentSectionProps) => ReactElement;
45
- agreementAndSubmit?: (props: AgreementAndSubmitProps) => ReactElement;
46
- };
47
- formWrapperClassName?: string;
48
- cardSelectionWrapperClassName?: string;
49
- installmentWrapperClassName?: string;
50
- formProps?: FormHTMLAttributes<HTMLFormElement>;
51
- cardSelectionWrapperProps?: HTMLAttributes<HTMLDivElement>;
52
- installmentWrapperProps?: HTMLAttributes<HTMLDivElement>;
53
- };
54
-
55
- const defaultTranslations: IyzicoSavedCardOptionTexts = {
56
- title: 'Pay with Iyzico',
57
- button: 'Complete Payment',
58
- installment: {
59
- title: 'Installment Options'
60
- },
61
- errors: {
62
- required: 'This field is required',
63
- cardNumberLength: 'Card number must be 16 digits long.',
64
- cvvLength: 'CVV must be 3 digits long.',
65
- cardHolderMatches: 'Please enter a valid name',
66
- saveCardRequired: 'Please select the Save My Card option'
67
- },
68
- label: {
69
- cardHolder: 'Cardholder Name',
70
- cardNumber: 'Card Number',
71
- expiryDate: 'Expiration Date',
72
- cvv: 'CVV',
73
- saveCardForFuture: 'Save this card for future payments',
74
- },
75
- placeholder: {
76
- cardHolder: 'Name on Card',
77
- cardMonth: 'Month',
78
- cardYear: 'Year'
79
- },
80
- savedCardsTitle: 'Select from your saved cards:',
81
- paymentErrorMessage: 'An error occurred while processing your payment'
82
- };
83
-
84
- const mergeTranslations = (
85
- customTranslations: IyzicoSavedCardOptionTexts,
86
- defaultTranslations: IyzicoSavedCardOptionTexts
87
- ) => {
88
- return {
89
- ...defaultTranslations,
90
- ...customTranslations,
91
- installment: {
92
- ...defaultTranslations.installment,
93
- ...customTranslations.installment
94
- },
95
- errors: {
96
- ...defaultTranslations.errors,
97
- ...customTranslations.errors
98
- },
99
- label: {
100
- ...defaultTranslations.label,
101
- ...customTranslations.label
102
- },
103
- placeholder: {
104
- ...defaultTranslations.placeholder,
105
- ...customTranslations.placeholder
106
- }
107
- };
108
- };
109
-
110
- const createFormSchema = (
111
- errors: ErrorTexts,
112
- type: 'existing-card' | 'new-card'
113
- ) => {
114
- const baseSchema = {
115
- agreement: yup
116
- .boolean()
117
- .oneOf([true], errors?.required ?? defaultTranslations.errors.required),
118
- register_consumer_card: yup.boolean().default(false)
119
- };
120
-
121
- if (type === 'new-card') {
122
- return yup.object().shape({
123
- ...baseSchema,
124
- card_holder: yup
125
- .string()
126
- .required(errors?.required ?? defaultTranslations.errors.required)
127
- .matches(
128
- /^[a-zA-ZğüşöçıİĞÜŞÖÇ\s]+$/,
129
- errors?.cardHolderMatches ??
130
- defaultTranslations.errors.cardHolderMatches
131
- ),
132
- card_number: yup
133
- .string()
134
- .transform((value: string) => value.replace(/_/g, '').replace(/ /g, ''))
135
- .length(
136
- 16,
137
- errors?.cardNumberLength ??
138
- defaultTranslations.errors.cardNumberLength
139
- )
140
- .required(errors?.required ?? defaultTranslations.errors.required),
141
- card_month: yup
142
- .string()
143
- .required(errors?.required ?? defaultTranslations.errors.required),
144
- card_year: yup
145
- .string()
146
- .required(errors?.required ?? defaultTranslations.errors.required),
147
- card_cvv: yup
148
- .string()
149
- .transform((value: string) => value.replace(/_/g, '').replace(/ /g, ''))
150
- .length(3, errors?.cvvLength ?? defaultTranslations.errors.cvvLength)
151
- .required(errors?.required ?? defaultTranslations.errors.required)
152
- });
153
- }
154
-
155
- return yup.object().shape(baseSchema);
156
- };
157
-
158
- const IyzicoSavedCardOption = ({
159
- texts = defaultTranslations,
160
- agreementCheckbox,
161
- customFormSchema,
162
- customRender,
163
- formWrapperClassName = 'flex flex-wrap w-full',
164
- cardSelectionWrapperClassName = 'w-full flex flex-col xl:w-6/10',
165
- installmentWrapperClassName = 'w-full xl:w-4/10 xl:border-l xl:border-t-0',
166
- formProps = {},
167
- cardSelectionWrapperProps = {},
168
- installmentWrapperProps = {}
169
- }: IyzicoSavedCardOptionProps) => {
170
- const mergedTexts = useMemo(
171
- () => mergeTranslations(texts, defaultTranslations),
172
- [texts]
173
- );
174
- const [completeSavedCard] = useCompleteSavedCardMutation();
175
- const [setSavedCard] = useSetSavedCardMutation();
176
- const installmentOptions = useAppSelector(
177
- (state) => state.checkout.installmentOptions
178
- );
179
- const ucs = useAppSelector((state) => state.savedCard.ucs);
180
- const [type, setType] = useState<'new-card' | 'existing-card'>('new-card');
181
- const [months, setMonths] = useState([]);
182
- const [years, setYears] = useState([]);
183
- const [formError, setFormError] = useState(null);
184
- const [showCardForm, setShowCardForm] = useState(true);
185
-
186
- const getFormSchema = () => {
187
- if (customFormSchema?.[type]) {
188
- return customFormSchema[type];
189
- }
190
- return createFormSchema(mergedTexts.errors, type);
191
- };
192
-
193
- const {
194
- register,
195
- handleSubmit,
196
- control,
197
- formState: { errors },
198
- setValue: setFormValue
199
- } = useForm({
200
- resolver: yupResolver(getFormSchema())
201
- });
202
-
203
- const onSubmit = async (data) => {
204
- if (!window.iyziUcsInit) {
205
- return;
206
- }
207
-
208
- setFormError(null);
209
-
210
- try {
211
- if (type === 'existing-card') {
212
- const res = await completeSavedCard({
213
- agreement: data.agreement,
214
- consumer_token: window.universalCardStorage.consumerToken,
215
- card_token: window.universalCardStorage.cardToken,
216
- register_consumer_card: true
217
- });
218
-
219
- if ('data' in res && res.data?.errors) {
220
- setFormError(res.data.errors);
221
- }
222
- } else {
223
- const res = await completeSavedCard({
224
- agreement: data.agreement,
225
- card_holder: data.card_holder,
226
- card_number: data.card_number.replaceAll(' ', ''),
227
- card_month: data.card_month,
228
- card_year: data.card_year,
229
- card_cvv: data.card_cvv,
230
- register_consumer_card: data.register_consumer_card || false
231
- });
232
-
233
- if ('data' in res && res.data?.errors) {
234
- setFormError(res.data.errors);
235
- }
236
- }
237
- } catch (error) {
238
- console.error('Error completing saved card:', error);
239
- setFormError({
240
- non_field_errors:
241
- mergedTexts.paymentErrorMessage ??
242
- defaultTranslations.paymentErrorMessage
243
- });
244
- }
245
- };
246
-
247
- const ucsScript = ucs?.script && (
248
- <Script
249
- id="saved_card"
250
- strategy="afterInteractive"
251
- dangerouslySetInnerHTML={{ __html: ucs.script }}
252
- />
253
- );
254
-
255
- useEffect(() => {
256
- window.iyziUcsInit?.createTag();
257
-
258
- const monthsList = [
259
- {
260
- label:
261
- mergedTexts.placeholder?.cardMonth ??
262
- defaultTranslations.placeholder.cardMonth,
263
- value: ''
264
- }
265
- ];
266
- const yearsList = [
267
- {
268
- label:
269
- mergedTexts.placeholder?.cardYear ??
270
- defaultTranslations.placeholder.cardYear,
271
- value: ''
272
- }
273
- ];
274
- const date = new Date();
275
- const currentYear = date.getFullYear();
276
-
277
- for (let i = 1; i <= 12; i++) {
278
- monthsList.push({ label: i.toString(), value: i.toString() });
279
- }
280
-
281
- for (let i = currentYear; i < currentYear + 13; i++) {
282
- yearsList.push({ label: i.toString(), value: i.toString() });
283
- }
284
-
285
- setMonths(monthsList);
286
- setYears(yearsList);
287
-
288
- if (window.iyziUcsInit?.ucsToken) {
289
- setSavedCard({
290
- card: window.iyziUcsInit.ucsToken
291
- });
292
- }
293
- }, []);
294
-
295
- useEffect(() => {
296
- if (window.iyziUcsInit?.scriptType === 'CONSUMER_WITH_CARD_EXIST') {
297
- if (
298
- window.universalCardStorage?.cardToken
299
- ) {
300
- setType('existing-card');
301
- } else {
302
- setType('new-card');
303
- }
304
- } else {
305
- setType('new-card');
306
- }
307
-
308
- const checkInterval = setInterval(() => {
309
- if (window.iyziUcsInit?.scriptType === 'CONSUMER_WITH_CARD_EXIST') {
310
- if (
311
- !window.universalCardStorage?.consumerToken ||
312
- window.universalCardStorage?.cardToken
313
- ) {
314
- setType('existing-card');
315
- } else {
316
- setType('new-card');
317
- }
318
- }
319
- }, 500);
320
-
321
- return () => clearInterval(checkInterval);
322
- }, [type]);
323
-
324
- useEffect(() => {
325
- const checkboxInterval = setInterval(() => {
326
- if (window.universalCardStorage?.cardToken !== undefined) {
327
- setShowCardForm(false);
328
- } else {
329
- setShowCardForm(true);
330
- }
331
- }, 100);
332
-
333
- return () => clearInterval(checkboxInterval);
334
- }, [type, setFormValue]);
335
-
336
- return (
337
- <>
338
- <form
339
- className={formWrapperClassName}
340
- onSubmit={handleSubmit(onSubmit)}
341
- {...formProps}
342
- >
343
- <div
344
- className={cardSelectionWrapperClassName}
345
- {...cardSelectionWrapperProps}
346
- >
347
- {mergedTexts.title && (
348
- <div className="border-solid border-gray-400 px-4 py-2">
349
- <span className="text-black-800 text-lg font-medium">
350
- {mergedTexts.title}
351
- </span>
352
- </div>
353
- )}
354
-
355
- {window.iyziUcsInit?.scriptType === 'CONSUMER_WITH_CARD_EXIST' && (
356
- <>
357
- {customRender?.savedCardsSection ? (
358
- customRender.savedCardsSection({
359
- title: mergedTexts.savedCardsTitle
360
- })
361
- ) : (
362
- <div>
363
- <h3 className="text-sm font-medium text-gray-800 mb-3 px-4 sm:px-6">
364
- {mergedTexts.savedCardsTitle}
365
- </h3>
366
- <div id="ucs-cards" className="mb-3 px-4 sm:px-6"></div>
367
- </div>
368
- )}
369
- </>
370
- )}
371
-
372
- {showCardForm && (
373
- <>
374
- {customRender?.cardFormSection ? (
375
- customRender.cardFormSection({
376
- register,
377
- control,
378
- errors,
379
- months,
380
- years,
381
- translations: mergedTexts.label,
382
- setFormValue
383
- })
384
- ) : (
385
- <CardFormSection
386
- register={register}
387
- control={control}
388
- errors={errors}
389
- months={months}
390
- years={years}
391
- translations={mergedTexts.label}
392
- />
393
- )}
394
- </>
395
- )}
396
- </div>
397
-
398
- <div
399
- className={installmentWrapperClassName}
400
- {...installmentWrapperProps}
401
- >
402
- {customRender?.installmentSection ? (
403
- customRender.installmentSection({
404
- title: mergedTexts.installment?.title,
405
- installmentOptions,
406
- translations: mergedTexts.installment,
407
- errors: errors.installment,
408
- setFormValue
409
- })
410
- ) : (
411
- <InstallmentSection
412
- title={mergedTexts.installment?.title}
413
- installmentOptions={installmentOptions}
414
- translations={mergedTexts.installment}
415
- errors={errors.installment}
416
- />
417
- )}
418
-
419
- <div className="flex flex-col text-xs">
420
- {customRender?.agreementAndSubmit ? (
421
- customRender.agreementAndSubmit({
422
- agreementCheckbox,
423
- control,
424
- errors,
425
- buttonText: mergedTexts.button,
426
- formError
427
- })
428
- ) : (
429
- <AgreementAndSubmit
430
- agreementCheckbox={agreementCheckbox}
431
- control={control}
432
- errors={errors}
433
- buttonText={mergedTexts.button}
434
- formError={formError}
435
- />
436
- )}
437
- </div>
438
- </div>
439
- </form>
440
- {ucsScript}
441
- </>
442
- );
443
- };
444
-
445
- export default IyzicoSavedCardOption;