@akinon/pz-masterpass 1.53.0 → 1.54.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @akinon/pz-masterpass
2
2
 
3
+ ## 1.54.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e9a46ac: ZERO-2738: add CVC input to registered cards in Masterpass
8
+
3
9
  ## 1.53.0
4
10
 
5
11
  ## 1.52.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/pz-masterpass",
3
- "version": "1.53.0",
3
+ "version": "1.54.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.d.ts",
@@ -29,6 +29,7 @@ export interface MasterpassState {
29
29
  additionalParams?: {
30
30
  [key: string]: string;
31
31
  };
32
+ cvcRequired: boolean;
32
33
  }
33
34
 
34
35
  const initialState: MasterpassState = {
@@ -41,7 +42,8 @@ const initialState: MasterpassState = {
41
42
  deletion: {
42
43
  isModalVisible: false
43
44
  },
44
- additionalParams: {}
45
+ additionalParams: {},
46
+ cvcRequired: false
45
47
  };
46
48
 
47
49
  const rootSlice = createSlice({
@@ -111,6 +113,9 @@ const rootSlice = createSlice({
111
113
  { payload }: PayloadAction<{ [key: string]: any }>
112
114
  ) => {
113
115
  state.additionalParams = payload;
116
+ },
117
+ setCvcRequired: (state, { payload }: PayloadAction<boolean>) => {
118
+ state.cvcRequired = payload;
114
119
  }
115
120
  }
116
121
  });
@@ -131,7 +136,8 @@ export const {
131
136
  setOtpResponse,
132
137
  setDeletionModalVisible,
133
138
  setDeletionCardAliasName,
134
- setAdditionalParams
139
+ setAdditionalParams,
140
+ setCvcRequired
135
141
  } = rootSlice.actions;
136
142
 
137
143
  export default rootSlice.reducer;
@@ -80,7 +80,8 @@ export const buildPurchaseForm = async ({
80
80
  amount,
81
81
  additionalParams,
82
82
  language = 'eng',
83
- installmentCount
83
+ installmentCount,
84
+ cardCvc
84
85
  }: {
85
86
  token: string;
86
87
  orderNo: string;
@@ -94,6 +95,7 @@ export const buildPurchaseForm = async ({
94
95
  };
95
96
  language: string;
96
97
  installmentCount: number;
98
+ cardCvc?: string;
97
99
  }) => {
98
100
  const session = await getSession();
99
101
 
@@ -124,6 +126,10 @@ export const buildPurchaseForm = async ({
124
126
  { name: 'cardAliasName', value: selectedCard?.Name ?? '' }
125
127
  ];
126
128
 
129
+ if (cardCvc) {
130
+ fields.push({ name: 'cvc', value: cardCvc });
131
+ }
132
+
127
133
  const form = formCreator({
128
134
  id: 'mp-purchase-form',
129
135
  fields
@@ -15,10 +15,22 @@ import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
15
15
  import { useCards } from '../../hooks/use-cards';
16
16
  import { useDeleteCard } from '../../hooks/use-delete-card';
17
17
  import { useSetBinNumberMutation } from '@akinon/next/data/client/checkout';
18
- import { Button, Image } from '@akinon/next/components';
18
+ import { Button, Icon, Image, Input } from '@akinon/next/components';
19
19
  import { twMerge } from 'tailwind-merge';
20
- import { setIsDirectPurchase, setSelectedCard } from '../../redux/reducer';
20
+ import {
21
+ setCvcRequired,
22
+ setIsDirectPurchase,
23
+ setSelectedCard
24
+ } from '../../redux/reducer';
21
25
  import { setInstallmentOptions } from '@akinon/next/redux/reducers/checkout';
26
+ import clsx from 'clsx';
27
+ import {
28
+ Control,
29
+ FieldError,
30
+ UseFormClearErrors,
31
+ UseFormRegister,
32
+ UseFormSetValue
33
+ } from 'react-hook-form';
22
34
 
23
35
  const cardImages = {
24
36
  amex,
@@ -31,22 +43,31 @@ const cardImages = {
31
43
  const defaultTranslations = {
32
44
  title: 'Select a card to pay with',
33
45
  pay_with_new_card: 'Pay with a new card',
34
- retryFetchCards: 'Retry Fetching Cards'
46
+ retryFetchCards: 'Retry Fetching Cards',
47
+ security_code: 'Security Code',
48
+ security_code_info: 'What’s CVC?'
35
49
  };
36
50
 
37
51
  export interface MasterpassCardListProps {
38
52
  className?: string;
39
53
  translations?: typeof defaultTranslations;
54
+ form?: {
55
+ clearErrors: UseFormClearErrors<any>;
56
+ control: Control<any>;
57
+ errors: Record<string, FieldError>;
58
+ register: UseFormRegister<any>;
59
+ setFormValue: UseFormSetValue<any>;
60
+ };
40
61
  }
41
62
 
42
63
  export const MasterpassCardList = ({
43
64
  className,
44
- translations
65
+ translations,
66
+ form
45
67
  }: MasterpassCardListProps) => {
46
68
  const { preOrder } = useAppSelector((state) => state.checkout);
47
- const { accountStatus, isDirectPurchase, selectedCard } = useAppSelector(
48
- (state) => state.masterpass
49
- );
69
+ const { accountStatus, isDirectPurchase, selectedCard, cvcRequired } =
70
+ useAppSelector((state) => state.masterpass);
50
71
  const [setMasterpassBinNumber] = useSetBinNumberMutation();
51
72
  const { cards, loading, error, refreshCards } = useCards({ skipData: false });
52
73
  const { DeleteButton } = useDeleteCard();
@@ -121,40 +142,90 @@ export const MasterpassCardList = ({
121
142
  return (
122
143
  <li
123
144
  key={card.UniqueId}
124
- className="p-4 mb-2 border-2 border-gray-200 flex justify-between items-center space-x-2 cursor-pointer"
145
+ className="p-4 mb-2 border-2 border-gray-200 flex flex-col space-x-2 cursor-pointer"
125
146
  onClick={async () => {
126
- await setMasterpassBinNumber(
127
- card.Value1.substring(0, 6)
128
- ).unwrap();
129
-
130
- dispatch(setSelectedCard(card));
147
+ if (selectedCard?.UniqueId !== card.UniqueId) {
148
+ await setMasterpassBinNumber(
149
+ card.Value1.substring(0, 6)
150
+ ).unwrap();
151
+
152
+ dispatch(setSelectedCard(card));
153
+ dispatch(setCvcRequired(false));
154
+
155
+ if (form) {
156
+ form.setFormValue('card_cvv', '');
157
+ form.clearErrors('card_cvv');
158
+ }
159
+ }
131
160
  }}
132
161
  >
133
- <input
134
- name="masterpass-card"
135
- type="radio"
136
- checked={selectedCard?.UniqueId === card.UniqueId}
137
- value={card.Name}
138
- id={card.UniqueId}
139
- className="mr-2"
140
- onChange={(e) => e.preventDefault()}
141
- />
142
- <label
143
- htmlFor={card.UniqueId}
144
- className="flex flex-col w-full cursor-pointer md:flex-row md:items-center md:justify-between"
145
- >
146
- <p className="w-full lg:w-1/3">{card.Name}</p>
147
- <p className="w-full text-[10px] lg:w-1/3">{card.Value1}</p>
148
- <Image
149
- className="w-8 h-6 object-contain flex items-center justify-center mr-4"
150
- width={50}
151
- height={50}
152
- src={cardImages[getCreditCardType(card.Value1)].src}
153
- alt={card.Name}
162
+ <div className="flex justify-between items-center">
163
+ <input
164
+ name="masterpass-card"
165
+ type="radio"
166
+ checked={selectedCard?.UniqueId === card.UniqueId}
167
+ value={card.Name}
168
+ id={card.UniqueId}
169
+ className="mr-2"
170
+ onChange={(e) => e.preventDefault()}
154
171
  />
155
- </label>
156
-
157
- <DeleteButton cardAliasName={card.Name} />
172
+ <label
173
+ htmlFor={card.UniqueId}
174
+ className="flex flex-col w-full cursor-pointer md:flex-row md:items-center md:justify-between"
175
+ >
176
+ <p className="w-full lg:w-1/3">{card.Name}</p>
177
+ <p className="w-full text-[10px] lg:w-1/3">{card.Value1}</p>
178
+ <Image
179
+ className="w-8 h-6 object-contain flex items-center justify-center mr-4"
180
+ width={50}
181
+ height={50}
182
+ src={cardImages[getCreditCardType(card.Value1)].src}
183
+ alt={card.Name}
184
+ />
185
+ </label>
186
+
187
+ <DeleteButton cardAliasName={card.Name} />
188
+ </div>
189
+ {selectedCard?.UniqueId === card.UniqueId && form && cvcRequired && (
190
+ <div
191
+ className={twMerge(
192
+ clsx('flex items-center justify-start mt-2', {
193
+ 'items-baseline': form.errors.card_cvv
194
+ })
195
+ )}
196
+ >
197
+ <label
198
+ className="text-xs text-black-400 mr-1.5"
199
+ htmlFor="card_cvv"
200
+ >
201
+ {translations?.security_code ??
202
+ defaultTranslations.security_code}
203
+ </label>
204
+ <Input
205
+ format="###"
206
+ mask="_"
207
+ control={form.control}
208
+ allowEmptyFormatting={true}
209
+ {...form.register('card_cvv')}
210
+ error={form.errors.card_cvv}
211
+ />
212
+ <div className="group relative flex items-center justify-start text-gray-600 cursor-pointer ml-2 transition-all hover:text-secondary">
213
+ <span className="text-xs underline">
214
+ {translations?.security_code_info ??
215
+ defaultTranslations.security_code_info}
216
+ </span>
217
+ <Icon name="cvc" size={16} className="leading-none ml-2" />
218
+ <div className="hidden group-hover:block absolute right-0 bottom-5 w-[11rem] lg:w-[21rem] lg:left-auto lg:right-auto border-2">
219
+ <Image
220
+ src="/cvv.jpg"
221
+ alt="Cvv"
222
+ width={385}
223
+ height={262}
224
+ />
225
+ </div>
226
+ </div>
227
+ </div>
228
+ )}
158
229
  </li>
159
230
  );
160
231
  })}