@akinon/pz-saved-card 1.62.0-rc.24 → 1.62.0

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -1,81 +1,3 @@
1
1
  # @akinon/pz-saved-card
2
2
 
3
- ## 1.62.0-rc.24
4
-
5
- ### Minor Changes
6
-
7
- - b92001cc: ZERO-2903: fix missing dependency in useEffect hook
8
- - 63597bc6: ZERO-2903: update saved card readme
9
- - 8fb37c4d: ZERO-2903: enchance SavedCardOption component with custom wrapper props and classnames
10
- - ce25dacb: ZERO-2903: optimize saved card
11
-
12
- ## 1.61.0-rc.23
13
-
14
- ## 1.61.0-rc.22
15
-
16
- ### Minor Changes
17
-
18
- - b92001cc: ZERO-2903: fix missing dependency in useEffect hook
19
- - 63597bc6: ZERO-2903: update saved card readme
20
- - 8fb37c4d: ZERO-2903: enchance SavedCardOption component with custom wrapper props and classnames
21
- - ce25dacb: ZERO-2903: optimize saved card
22
-
23
- ## 1.60.0-rc.21
24
-
25
- ## 1.60.0-rc.20
26
-
27
- ## 1.60.0-rc.19
28
-
29
- ## 1.60.0-rc.18
30
-
31
- ## 1.60.0-rc.17
32
-
33
- ## 1.60.0-rc.16
34
-
35
- ## 1.60.0-rc.15
36
-
37
- ## 1.60.0-rc.14
38
-
39
- ## 1.60.0-rc.13
40
-
41
- ## 1.60.0-rc.12
42
-
43
- ## 1.60.0-rc.11
44
-
45
- ## 1.60.0-rc.10
46
-
47
- ## 1.60.0-rc.9
48
-
49
- ## 1.60.0-rc.8
50
-
51
- ## 1.60.0-rc.7
52
-
53
- ### Minor Changes
54
-
55
- - 63597bc: ZERO-2903: update saved card readme
56
- - ce25dac: ZERO-2903: optimize saved card
57
-
58
- ## 1.60.0-rc.6
59
-
60
- ### Minor Changes
61
-
62
- - b92001c: ZERO-2903: fix missing dependency in useEffect hook
63
- - 8fb37c4: ZERO-2903: enchance SavedCardOption component with custom wrapper props and classnames
64
-
65
- ## 1.59.0-rc.5
66
-
67
- ### Minor Changes
68
-
69
- - b92001c: ZERO-2903: fix missing dependency in useEffect hook
70
- - 8fb37c4: ZERO-2903: enchance SavedCardOption component with custom wrapper props and classnames
71
-
72
- ## 1.59.0-rc.4
73
-
74
- ### Minor Changes
75
-
76
- - b92001c: ZERO-2903: fix missing dependency in useEffect hook
77
- - 8fb37c4: ZERO-2903: enchance SavedCardOption component with custom wrapper props and classnames
78
-
79
- ## 1.59.0-rc.3
80
-
81
- ## 1.59.0-rc.2
3
+ ## 1.62.0
package/package.json CHANGED
@@ -1,22 +1,20 @@
1
1
  {
2
2
  "name": "@akinon/pz-saved-card",
3
- "version": "1.62.0-rc.24",
3
+ "version": "1.62.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.tsx",
6
6
  "peerDependencies": {
7
7
  "react": "^18.0.0",
8
8
  "react-dom": "^18.0.0"
9
9
  },
10
- "dependencies": {
11
- "react-redux": "8.1.3"
12
- },
13
10
  "devDependencies": {
14
11
  "@types/node": "^18.7.8",
15
12
  "@types/react": "^18.0.17",
16
13
  "@types/react-dom": "^18.0.6",
17
- "prettier": "^3.0.3",
18
14
  "react": "^18.2.0",
19
15
  "react-dom": "^18.2.0",
20
- "typescript": "^5.2.2"
16
+ "typescript": "^4.7.4",
17
+ "react-hook-form": "7.31.3",
18
+ "@hookform/resolvers": "2.9.0"
21
19
  }
22
20
  }
package/readme.md CHANGED
@@ -1,45 +1,36 @@
1
- # Saved Card Plugin
1
+ # pz-saved-card
2
2
 
3
- ## Installation
4
-
5
- There are two ways to install the Saved Card plugin:
6
-
7
- ### 1. Install the npm package using Yarn
3
+ ### Example Usage
4
+ ##### File Path: src/views/checkout/steps/payment/options/saved-card.tsx
8
5
 
9
- For the latest version, you can install the package using Yarn:
6
+ ```javascript
7
+ import PluginModule, { Component } from '@akinon/next/components/plugin-module';
8
+ import CheckoutAgreements from '@theme/views/checkout/steps/payment/agreements';
9
+
10
+ export default function SavedCard() {
11
+ return (
12
+ <PluginModule
13
+ component={Component.SavedCard}
14
+ props={{
15
+ agreementCheckbox: (
16
+ <CheckoutAgreements control={null} fieldId="agreement" error={null} />
17
+ )
18
+ }}
19
+ />
20
+ );
21
+ }
10
22
 
11
- ```bash
12
- yarn add @akinon/pz-saved-card
13
23
  ```
14
24
 
15
- ### 2. Preferred installation method
25
+ ##### File Path: src/app/[commerce]/[locale]/[currency]/orders/saved-card-redirect/page.tsx
16
26
 
17
- You can also use the following command to install the extension with the latest plugins:
27
+ ```javascript
28
+ import { SavedCardRedirect } from 'pz-saved-card/src/routes/saved-card-redirect';
18
29
 
19
- ```bash
20
- npx @akinon/projectzero@latest --plugins
30
+ export default SavedCardRedirect;
21
31
  ```
22
32
 
23
- ## Usage
24
-
25
- ##### File Path: src/views/checkout/steps/payment/options/saved-card.tsx
26
-
27
- ```jsx
28
- import PluginModule, { Component } from '@akinon/next/components/plugin-module';
29
-
30
- const SavedCard = () => {
31
- return (
32
- <PluginModule
33
- component={Component.SavedCard}
34
- props={{
35
- texts: {
36
- title: 'Pay with Saved Card',
37
- button: 'Pay Now'
38
- }
39
- }}
40
- />
41
- );
42
- };
43
-
44
- export default SavedCard;
45
- ```
33
+ ### Props
34
+ | Properties | Type | Description |
35
+ |----------------------|--------|--------------------------------------------|
36
+ | texts | object | The translations of the component. |
@@ -2,7 +2,6 @@ import { CheckoutContext, PreOrder } from '@akinon/next/types';
2
2
  import { api } from '@akinon/next/data/client/api';
3
3
  import { buildClientRequestUrl } from '@akinon/next/utils';
4
4
  import { setPaymentStepBusy } from '@akinon/next/redux/reducers/checkout';
5
- import { SavedCard } from './reducer';
6
5
 
7
6
  interface CheckoutResponse {
8
7
  pre_order?: PreOrder;
@@ -14,55 +13,21 @@ interface CheckoutResponse {
14
13
  redirect_url?: string;
15
14
  }
16
15
 
17
- interface GetResponse<T> {
18
- count: number;
19
- next: null;
20
- previous: null;
21
- results: T[];
22
- }
23
-
24
- type SetSavedCardRequest = {
25
- card: SavedCard;
26
- };
27
-
28
- type DeleteSavedCardRequest = number;
29
-
30
- type SetInstallmentRequest = {
31
- installment: string;
32
- };
33
-
34
- type CompleteSavedCardRequest = {
35
- agreement: boolean;
36
- };
16
+ type CompleteSavedCardRequest =
17
+ | { ucs_token: string; consumer_token: string; card_token: string }
18
+ | {
19
+ agreement: boolean;
20
+ card_number: string;
21
+ card_cvv: string;
22
+ card_month: string;
23
+ card_year: string;
24
+ register_consumer_card: boolean;
25
+ };
37
26
 
38
27
  export const savedCardApi = api.injectEndpoints({
39
28
  endpoints: (build) => ({
40
- getSavedCards: build.query<GetResponse<SavedCard>, void>({
41
- query: () => ({
42
- url: buildClientRequestUrl('/users/saved-cards/')
43
- }),
44
- async onQueryStarted(arg, { dispatch, queryFulfilled }) {
45
- dispatch(setPaymentStepBusy(true));
46
- await queryFulfilled;
47
- dispatch(setPaymentStepBusy(false));
48
- }
49
- }),
50
- deleteSavedCard: build.mutation<
51
- GetResponse<SavedCard>,
52
- DeleteSavedCardRequest
53
- >({
54
- query: (cardId) => ({
55
- url: buildClientRequestUrl(`/users/saved-cards/${cardId}`),
56
- method: 'DELETE'
57
- }),
58
- async onQueryStarted(arg, { dispatch, queryFulfilled }) {
59
- dispatch(setPaymentStepBusy(true));
60
- await queryFulfilled;
61
- dispatch(setPaymentStepBusy(false));
62
- }
63
- }),
64
- setSavedCard: build.mutation<CheckoutResponse, SetSavedCardRequest>({
65
- query: ({ card }) => ({
29
+ setSavedCard: build.mutation<CheckoutResponse, string>({
30
+ query: (ucsToken) => ({
66
31
  url: buildClientRequestUrl(
67
32
  `/orders/checkout?page=SavedCardSelectionPage`,
68
33
  {
@@ -71,20 +36,17 @@ export const savedCardApi = api.injectEndpoints({
71
36
  ),
72
37
  method: 'POST',
73
38
  body: {
74
- card: card.token
39
+ card: ucsToken
75
40
  }
76
41
  }),
77
- async onQueryStarted({ card }, { dispatch, queryFulfilled }) {
42
+ async onQueryStarted(arg, { dispatch, queryFulfilled }) {
78
43
  dispatch(setPaymentStepBusy(true));
79
44
  await queryFulfilled;
80
45
  dispatch(setPaymentStepBusy(false));
81
46
  }
82
47
  }),
83
- setSavedCardInstallmentOption: build.mutation<
84
- CheckoutResponse,
85
- SetInstallmentRequest
86
- >({
87
- query: ({ installment }) => ({
48
+ setInstallment: build.mutation<CheckoutResponse, number>({
49
+ query: (pk) => ({
88
50
  url: buildClientRequestUrl(
89
51
  `/orders/checkout?page=SavedCardInstallmentSelectionPage`,
90
52
  {
@@ -93,7 +55,7 @@ export const savedCardApi = api.injectEndpoints({
93
55
  ),
94
56
  method: 'POST',
95
57
  body: {
96
- installment
58
+ installment: pk
97
59
  }
98
60
  }),
99
61
  async onQueryStarted(arg, { dispatch, queryFulfilled }) {
@@ -102,21 +64,19 @@ export const savedCardApi = api.injectEndpoints({
102
64
  dispatch(setPaymentStepBusy(false));
103
65
  }
104
66
  }),
105
- completeSavedCard: build.mutation<
67
+ setCompleteSavedCard: build.mutation<
106
68
  CheckoutResponse,
107
69
  CompleteSavedCardRequest
108
70
  >({
109
- query: ({ agreement }) => ({
71
+ query: (data) => ({
110
72
  url: buildClientRequestUrl(
111
- `/orders/checkout?page=CompleteSavedCardRequest`,
73
+ `/orders/checkout?page=SavedCardConfirmationPage`,
112
74
  {
113
75
  useFormData: true
114
76
  }
115
77
  ),
116
78
  method: 'POST',
117
- body: {
118
- agreement
119
- }
79
+ body: data
120
80
  }),
121
81
  async onQueryStarted(arg, { dispatch, queryFulfilled }) {
122
82
  dispatch(setPaymentStepBusy(true));
@@ -128,9 +88,7 @@ export const savedCardApi = api.injectEndpoints({
128
88
  });
129
89
 
130
90
  export const {
131
- useGetSavedCardsQuery,
132
91
  useSetSavedCardMutation,
133
- useSetSavedCardInstallmentOptionMutation,
134
- useCompleteSavedCardMutation,
135
- useDeleteSavedCardMutation
92
+ useSetInstallmentMutation,
93
+ useSetCompleteSavedCardMutation
136
94
  } = savedCardApi;
package/src/index.tsx CHANGED
@@ -1,4 +1,6 @@
1
+ export * from './views/option';
2
+
1
3
  import savedCardReducer from './redux/reducer';
2
- import SavedCardOption from './views/saved-card-option';
4
+ import savedCardMiddleware from './redux/middleware';
3
5
 
4
- export { savedCardReducer, SavedCardOption };
6
+ export { savedCardReducer, savedCardMiddleware };
@@ -0,0 +1,25 @@
1
+ import { Middleware } from '@reduxjs/toolkit';
2
+ import { setUcs } from './reducer';
3
+
4
+ const savedCardMiddleware: Middleware = ({ dispatch }) => {
5
+ return (next) => (action) => {
6
+ const result = next(action);
7
+
8
+ const ucsContext = result.payload?.context_list?.find(
9
+ (context) => context.page_context.ucs
10
+ );
11
+
12
+ if (ucsContext) {
13
+ const ucs = JSON.parse(JSON.stringify(ucsContext.page_context.ucs));
14
+
15
+ const match = ucs.script.match(/<script\b[^>]*>([\s\S]*?)<\/script>/);
16
+ ucs.script = match ? match[1] : ucs.script;
17
+
18
+ dispatch(setUcs(ucs));
19
+ }
20
+
21
+ return result;
22
+ };
23
+ };
24
+
25
+ export default savedCardMiddleware;
@@ -1,45 +1,33 @@
1
- import { createSlice } from '@reduxjs/toolkit';
1
+ 'use client';
2
2
 
3
- export type SavedCard = {
4
- id: number;
5
- name: string;
6
- masked_card_number: string;
7
- token: string;
8
- };
3
+ import { createSlice } from '@reduxjs/toolkit';
9
4
 
10
5
  export interface SavedCardState {
11
- cards?: Array<SavedCard>;
12
- deletion: {
13
- id: number;
14
- isModalVisible: boolean;
6
+ ucs: {
7
+ maskedGsmNumber?: string;
8
+ script?: string;
9
+ scriptType?: string;
10
+ ucsToken?: string;
15
11
  };
16
12
  }
17
13
 
18
14
  const initialState: SavedCardState = {
19
- cards: undefined,
20
- deletion: {
21
- id: null,
22
- isModalVisible: false
23
- }
15
+ ucs: {}
24
16
  };
25
17
 
26
18
  const savedCardSlice = createSlice({
27
19
  name: 'savedCard',
28
20
  initialState,
29
21
  reducers: {
30
- setCards(state, { payload }) {
31
- state.cards = payload;
32
- },
33
- setDeletionModalId(state, { payload }) {
34
- state.deletion.id = payload;
22
+ setUcs(state, action) {
23
+ state.ucs = action.payload;
35
24
  },
36
- setDeletionModalVisible(state, { payload }) {
37
- state.deletion.isModalVisible = payload;
25
+ resetUcs() {
26
+ return initialState;
38
27
  }
39
28
  }
40
29
  });
41
30
 
42
- export const { setCards, setDeletionModalId, setDeletionModalVisible } =
43
- savedCardSlice.actions;
31
+ export const { setUcs, resetUcs } = savedCardSlice.actions;
44
32
 
45
33
  export default savedCardSlice.reducer;
@@ -0,0 +1,25 @@
1
+ import { cookies } from 'next/headers';
2
+ import settings from 'settings';
3
+ import { redirect } from 'next/navigation';
4
+ import { ROUTES } from 'routes';
5
+
6
+ export const SavedCardRedirect = async () => {
7
+ const nextCookies = cookies();
8
+ const sessionId = nextCookies.get('osessionid')?.value;
9
+
10
+ if (!sessionId) {
11
+ return redirect(ROUTES.CHECKOUT);
12
+ }
13
+
14
+ const commerceUrl = settings.commerceUrl;
15
+
16
+ const response = await fetch(`${commerceUrl}/orders/saved-card-redirect`, {
17
+ headers: {
18
+ Cookie: nextCookies.toString()
19
+ }
20
+ });
21
+
22
+ const data = await response.text();
23
+
24
+ return <div dangerouslySetInnerHTML={{ __html: data }}></div>;
25
+ };
@@ -1,51 +1,17 @@
1
- import { ReactElement } from 'react';
1
+ export {};
2
2
 
3
- export type InstallmentTexts = {
4
- title?: string;
5
- payments?: string;
6
- per_month?: string;
7
- total?: string;
8
- };
9
-
10
- export type DeletePopupTexts = {
11
- title?: string;
12
- delete_button?: string;
13
- cancel_button?: string;
14
- };
15
-
16
- export type ErrorTexts = {
17
- required?: string;
18
- };
19
-
20
- export type SavedCardOptionTexts = {
21
- title?: string;
22
- button?: string;
23
- installment?: InstallmentTexts;
24
- deletePopup?: DeletePopupTexts;
25
- errors?: ErrorTexts;
26
- };
27
-
28
- export type CardSelectionSectionProps = {
29
- title: string;
30
- cards: any[];
31
- selectedCard: any;
32
- onSelect: (card: any) => void;
33
- register: any;
34
- errors: any;
35
- dispatch: any;
36
- };
37
-
38
- export type InstallmentSectionProps = {
39
- title: string;
40
- selectedCard: any;
41
- installmentOptions: any;
42
- translations: InstallmentTexts;
43
- errors: any;
44
- };
45
-
46
- export type AgreementAndSubmitProps = {
47
- agreementCheckbox: ReactElement | undefined;
48
- control: any;
49
- errors: any;
50
- buttonText: string;
51
- };
3
+ declare global {
4
+ interface Window {
5
+ iyziUcsInit: {
6
+ scriptType: string;
7
+ ucsToken: string;
8
+ createTag: () => void;
9
+ };
10
+ universalCardStorage: {
11
+ binNumber: number;
12
+ cardToken: string;
13
+ consumerToken: string;
14
+ registerConsumerCard: boolean;
15
+ };
16
+ }
17
+ }
@@ -0,0 +1,108 @@
1
+ import { Price, Radio } from '@akinon/next/components';
2
+ import { useLocalization } from '@akinon/next/hooks';
3
+ import { useSetInstallmentMutation } from '../endpoints';
4
+ import { useEffect, useState } from 'react';
5
+
6
+ const defaultTranslations = {
7
+ payments: 'Payments',
8
+ per_month: 'Per Month',
9
+ total: 'Total'
10
+ };
11
+
12
+ const SavedCardInstallments = ({ installmentOptions, translations, error }) => {
13
+ const { t } = useLocalization();
14
+ const [installmentOption, setInstallmentOption] = useState(null);
15
+ const [setInstallment] = useSetInstallmentMutation();
16
+
17
+ const errorMessage = (
18
+ <div className="px-6 mt-4 text-sm text-error">{error?.message}</div>
19
+ );
20
+
21
+ useEffect(() => {
22
+ if (
23
+ installmentOptions[0]?.pk &&
24
+ installmentOption !== installmentOptions[0]?.pk
25
+ ) {
26
+ setInstallment(installmentOptions[0].pk);
27
+ setInstallmentOption(installmentOptions[0].pk);
28
+ }
29
+ }, [installmentOptions, setInstallment]);
30
+
31
+ if (installmentOptions.length === 0) {
32
+ return (
33
+ <>
34
+ <div className="text-xs text-black-800 p-4 sm:p-6">
35
+ {t('checkout.payment.installment_options.description')}
36
+ </div>
37
+ </>
38
+ );
39
+ }
40
+
41
+ return (
42
+ <>
43
+ <div>
44
+ <div className="px-4 mb-4 sm:px-6 sm:mb-6">
45
+ <table className="w-full border-t border-b border-solid border-gray-400">
46
+ <thead>
47
+ <tr>
48
+ <th
49
+ scope="col"
50
+ className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 border-0 text-start"
51
+ >
52
+ {translations?.payments ?? defaultTranslations.payments}
53
+ </th>
54
+ <th
55
+ scope="col"
56
+ className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 border-0 text-right"
57
+ >
58
+ {translations?.per_month ?? defaultTranslations.per_month}
59
+ </th>
60
+ <th
61
+ scope="col"
62
+ className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 border-0 text-right"
63
+ >
64
+ {translations?.total ?? defaultTranslations.total}
65
+ </th>
66
+ </tr>
67
+ </thead>
68
+ <tbody>
69
+ {installmentOptions.map((option) => (
70
+ <tr
71
+ key={`installment-${option.pk}`}
72
+ className="border-t border-solid border-gray-400"
73
+ >
74
+ <td className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 text-left">
75
+ <Radio
76
+ value={option.pk}
77
+ name="installment"
78
+ checked={option.pk === installmentOption}
79
+ onChange={() => {
80
+ setInstallmentOption(option.pk);
81
+ setInstallment(option.pk);
82
+ }}
83
+ >
84
+ <span className="w-full flex items-center justify-start pl-2">
85
+ <span className="text-xs text-black-800 transition-all">
86
+ {option.label}
87
+ </span>
88
+ </span>
89
+ </Radio>
90
+ </td>
91
+ <td className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 text-right">
92
+ <Price value={option.monthly_price_with_accrued_interest} />
93
+ </td>
94
+ <td className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 text-right">
95
+ <Price value={option.price_with_accrued_interest} />
96
+ </td>
97
+ </tr>
98
+ ))}
99
+ </tbody>
100
+ </table>
101
+ </div>
102
+ {error && errorMessage}
103
+ </div>
104
+ </>
105
+ );
106
+ };
107
+
108
+ export default SavedCardInstallments;