@akinon/pz-masterpass 1.45.0 → 1.47.0-rc.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,15 @@
1
1
  # @akinon/pz-masterpass
2
2
 
3
+ ## 1.47.0-rc.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 50b9069: ZERO-2767: update plugin readme files
8
+ - 64699d3: ZERO-2761: Fix invalid import for plugin module
9
+ - e9a46ac: ZERO-2738: add CVC input to registered cards in Masterpass
10
+
11
+ ## 1.46.0
12
+
3
13
  ## 1.45.0
4
14
 
5
15
  ## 1.44.0
package/README.md CHANGED
@@ -1,4 +1,14 @@
1
- # pz-masterpass
1
+ # @akinon/pz-masterpass
2
+
3
+ ### Install the npm package
4
+
5
+ ```bash
6
+ # For latest version
7
+ yarn add @akinon/pz-masterpass
8
+
9
+ # Preferred installation method
10
+ npx @akinon/projectzero@latest --plugins
11
+ ```
2
12
 
3
13
  ## Available Props
4
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/pz-masterpass",
3
- "version": "1.45.0",
3
+ "version": "1.47.0-rc.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
  })}
@@ -41,16 +41,15 @@ export const MasterpassCardRegistration = ({
41
41
  translations?: typeof defaultTranslations;
42
42
  }) => {
43
43
  const { preOrder } = useAppSelector((state: RootState) => state.checkout);
44
- const { msisdn, token, language, otp, isDirectPurchase } = useAppSelector(
45
- (state) => state.masterpass
46
- );
44
+ const { msisdn, token, language, otp, isDirectPurchase, accountStatus } =
45
+ useAppSelector((state) => state.masterpass);
47
46
 
48
47
  const [isInfoModalOpen, setIsInfoModalOpen] = useState(false);
49
48
  const [isAgreementChecked, setIsAgreementChecked] = useState(false);
50
49
  const [accountAliasName, setAccountAliasName] = useState('');
51
50
  const [formError, setFormError] = useState<string | null>(null);
52
51
  const [isBusy, setIsBusy] = useState(false);
53
- const { refreshCards } = useCards();
52
+ const { refreshCards, cards } = useCards();
54
53
 
55
54
  const dispatch = useAppDispatch();
56
55
 
@@ -153,6 +152,14 @@ export const MasterpassCardRegistration = ({
153
152
  }
154
153
  }, [otp.response]);
155
154
 
155
+ useEffect(() => {
156
+ if (cards && cards.length !== 0) {
157
+ dispatch(setAccountStatus(MasterpassStatus.ListCards));
158
+ } else {
159
+ dispatch(setAccountStatus(MasterpassStatus.NoAccount));
160
+ }
161
+ }, [cards]);
162
+
156
163
  if (
157
164
  preOrder.payment_option?.payment_type !== 'masterpass' ||
158
165
  !isDirectPurchase ||
@@ -251,18 +258,22 @@ export const MasterpassCardRegistration = ({
251
258
  </div>
252
259
  </div>
253
260
 
254
- <a
255
- href="#"
256
- className="block text-xs underline mt-5"
257
- onClick={(e) => {
258
- e.preventDefault();
259
- dispatch(setIsDirectPurchase(false));
260
- dispatch(setInstallmentOptions([]));
261
- }}
262
- >
263
- {translations?.pay_with_my_masterpass_card ??
264
- defaultTranslations.pay_with_my_masterpass_card}
265
- </a>
261
+ {accountStatus !== MasterpassStatus.NoAccount &&
262
+ cards &&
263
+ cards.length !== 0 && (
264
+ <a
265
+ href="#"
266
+ className="block text-xs underline mt-5"
267
+ onClick={(e) => {
268
+ e.preventDefault();
269
+ dispatch(setIsDirectPurchase(false));
270
+ dispatch(setInstallmentOptions([]));
271
+ }}
272
+ >
273
+ {translations?.pay_with_my_masterpass_card ??
274
+ defaultTranslations.pay_with_my_masterpass_card}
275
+ </a>
276
+ )}
266
277
  </div>
267
278
  );
268
279
  };