@akinon/pz-gpay 2.0.0-beta.9 → 2.0.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,45 +1,152 @@
1
1
  # @akinon/pz-gpay
2
2
 
3
- ## 2.0.0-beta.9
3
+ ## 2.0.0
4
+
5
+ ## 2.0.0-beta.27
6
+
7
+ ## 2.0.0-beta.26
8
+
9
+ ## 2.0.0-beta.25
10
+
11
+ ## 2.0.0-beta.24
12
+
13
+ ## 2.0.0-beta.23
14
+
15
+ ## 2.0.0-beta.22
16
+
17
+ ## 2.0.0-beta.21
18
+
19
+ ## 2.0.0-beta.20
20
+
21
+ ## 1.126.0
22
+
23
+ ## 1.125.2
24
+
25
+ ## 1.125.1
26
+
27
+ ## 1.125.0
28
+
29
+ ## 1.124.0
30
+
31
+ ## 1.123.0
32
+
33
+ ## 1.122.0
4
34
 
5
35
  ### Minor Changes
6
36
 
7
- - 0fe7711: ZERO-3387: Upgrade nextjs, eslint-config-next
37
+ - 2e436c13: ZERO-3389 :Update README.md for GPay component: enhance props documentation, add custom UI usage examples
38
+
39
+ ## 1.121.0
40
+
41
+ ## 1.120.0
42
+
43
+ ### Minor Changes
44
+
45
+ - 6ad72e8d: ZERO-4032: Add loading state management for payment submissions across multiple components and add safe guarding
46
+
47
+ ## 1.119.0
48
+
49
+ ## 1.118.0
50
+
51
+ ## 1.117.0
52
+
53
+ ## 1.116.0
54
+
55
+ ## 1.115.0
56
+
57
+ ## 1.114.0
58
+
59
+ ## 1.113.0
60
+
61
+ ## 1.112.0
8
62
 
9
- ## 2.0.0-beta.8
63
+ ## 1.111.0
10
64
 
11
- ## 2.0.0-beta.7
65
+ ## 1.110.0
12
66
 
13
- ## 2.0.0-beta.6
67
+ ## 1.109.0
68
+
69
+ ## 1.108.0
70
+
71
+ ## 1.107.0
72
+
73
+ ## 1.106.0
74
+
75
+ ## 1.105.0
76
+
77
+ ## 1.104.0
78
+
79
+ ## 1.103.0
80
+
81
+ ## 1.102.0
82
+
83
+ ## 1.101.0
84
+
85
+ ## 1.100.0
86
+
87
+ ## 1.99.0
14
88
 
15
89
  ### Minor Changes
16
90
 
17
- - 8f05f9b: ZERO-3250: Beta branch synchronized with Main branch
91
+ - d58538b: ZERO-3638: Enhance RC pipeline: add fetch, merge, and pre-release setup with conditional commit
92
+
93
+ ## 1.98.0
94
+
95
+ ## 1.97.0
96
+
97
+ ## 1.96.0
98
+
99
+ ## 1.95.0
18
100
 
19
- ## 2.0.0-beta.5
101
+ ## 1.94.0
20
102
 
21
- ## 2.0.0-beta.4
103
+ ## 1.93.0
22
104
 
23
- ## 2.0.0-beta.3
105
+ ## 1.92.0
24
106
 
25
- ## 2.0.0-beta.2
107
+ ## 1.91.0
108
+
109
+ ## 1.90.0
26
110
 
27
111
  ### Minor Changes
28
112
 
29
- - a006015: ZERO-3116: Add not-found page and update default middleware.
30
- - 1eeb3d8: ZERO-3116: Add not found page
113
+ - a1463fd: ZERO-3381: Add customUIRender support for flexible UI rendering
114
+
115
+ ## 1.89.0
116
+
117
+ ### Minor Changes
118
+
119
+ - 57a6184: ZERO-3332 :Add data-testid attribute to GPay payment button for testing
120
+
121
+ ## 1.88.0
122
+
123
+ ## 1.87.0
31
124
 
32
- ## 2.0.0-beta.1
125
+ ## 1.86.0
126
+
127
+ ## 1.85.0
128
+
129
+ ## 1.84.0
33
130
 
34
131
  ### Minor Changes
35
132
 
36
- - ZERO-3091: Upgrade Next.js to v15 and React to v19
133
+ - 624a4eb: ZERO-3276: Update installation instructions across multiple README files to standardize format and improve clarity
134
+
135
+ ## 1.83.0
136
+
137
+ ## 1.82.0
138
+
139
+ ## 1.81.0
140
+
141
+ ## 1.80.0
142
+
143
+ ## 1.79.0
37
144
 
38
- ## 2.0.0-beta.0
145
+ ## 1.78.0
39
146
 
40
- ### Major Changes
147
+ ## 1.77.0
41
148
 
42
- - be6c09d: ZERO-3114: Create beta version.
149
+ ## 1.76.0
43
150
 
44
151
  ## 1.75.0
45
152
 
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@akinon/pz-gpay",
3
- "version": "2.0.0-beta.9",
3
+ "version": "2.0.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.tsx",
6
6
  "peerDependencies": {
7
- "react": "^19.0.0",
8
- "react-dom": "^19.0.0"
7
+ "react": "^18.0.0 || ^19.0.0",
8
+ "react-dom": "^18.0.0 || ^19.0.0"
9
9
  },
10
10
  "devDependencies": {
11
- "@types/node": "^22.10.2",
12
- "@types/react": "^19.0.2",
13
- "@types/react-dom": "^19.0.2",
14
- "react": "^19.0.0",
15
- "react-dom": "^19.0.0",
16
- "typescript": "^5.7.2",
11
+ "@types/node": "^18.7.8",
12
+ "@types/react": "^18.0.17",
13
+ "@types/react-dom": "^18.0.6",
14
+ "react": "19.2.5",
15
+ "react-dom": "19.2.5",
16
+ "typescript": "^4.7.4",
17
17
  "react-hook-form": "7.31.3",
18
18
  "@hookform/resolvers": "2.9.0"
19
19
  }
package/readme.md CHANGED
@@ -10,9 +10,55 @@ npx @akinon/projectzero@latest --plugins
10
10
 
11
11
  ```
12
12
 
13
- ### Example Usage
13
+ ## GPAY
14
14
 
15
- ##### File Path: src/views/checkout/steps/payment/options/gpay.tsx
15
+ The `GPayOption` component provides GarantiPay (GPay) payment integration within the checkout process. It supports both default UI rendering and a fully customized UI via the `customUIRender` prop.
16
+
17
+ ### Props
18
+
19
+ | Prop | Type | Required | Description |
20
+ | --- | --- | --- | --- |
21
+ | `containerClassName` | `string` | Optional | Custom class for the root `<form>` container |
22
+ | `titleClassName` | `string` | Optional | Class for the title (`<h1>` element) |
23
+ | `descriptionClassName` | `string` | Optional | Class for the description container |
24
+ | `buttonClassName` | `string` | Optional | Class for the submit button |
25
+ | `translations` | `Partial<GPayTranslations>` | Optional | Override default title, description, button and error texts |
26
+ | `customUIRender` | `function` | Optional | Function to override the internal rendering logic and provide custom layout |
27
+
28
+ ---
29
+
30
+ ### `customUIRender` Parameters
31
+
32
+ | Parameter | Type | Description |
33
+ | --- | --- | --- |
34
+ | `handleSubmit` | `UseFormHandleSubmit<GPayForm>` | Wrapper for your `onSubmit` function with validation |
35
+ | `onSubmit` | `SubmitHandler<GPayForm>` | Submit logic that handles payment integration internally |
36
+ | `control` | `Control<GPayForm>` | React Hook Form control object, useful for controlled inputs |
37
+ | `errors` | `FieldErrors<GPayForm>` | Validation errors, helpful for form field error handling |
38
+ | `translations` | `GPayTranslations` | Final translation object with merged defaults and overrides |
39
+
40
+ ### Default Translations
41
+
42
+ ```javascript
43
+ {
44
+ title: 'Pay with GarantiPay',
45
+ description: [
46
+ 'Click on the "GarantiPay" button. Guarantee for payment you will be redirected to the payment page.',
47
+ 'Log in to your account with your username and password on the Garanti Pay page. If you do not have a Garanti Pay membership, create a new membership you can create.',
48
+ 'Complete the payment process by selecting your card and payment type.'
49
+ ],
50
+ button: 'Pay with GarantiPay',
51
+ error: 'An error occurred during payment'
52
+ }
53
+ ```
54
+
55
+ ### Usage Examples
56
+
57
+ ```bash
58
+ /src/views/checkout/steps/payment/options/gpay.tsx
59
+ ```
60
+
61
+ ## Default Usage
16
62
 
17
63
  ```javascript
18
64
  import PluginModule, { Component } from '@akinon/next/components/plugin-module';
@@ -24,11 +70,33 @@ const GPay = () => {
24
70
  props={{
25
71
  translations: {
26
72
  title: 'Pay with GarantiPay',
27
- description:
28
- 'Click the button below to pay for your order using GarantiPay.',
73
+ description: [
74
+ 'Click on the "Pay with GarantiPay" button. You will be redirected to the payment page.',
75
+ 'Log in with your credentials. If you don\'t have an account, create one.',
76
+ 'Select your card and complete the payment process.'
77
+ ],
29
78
  button: 'Pay Now'
30
79
  },
31
- titleClassName: 'text-4xl'
80
+ containerClassName: 'bg-gray-50 rounded-md',
81
+ titleClassName: 'text-2xl font-bold',
82
+ descriptionClassName: 'text-sm',
83
+ buttonClassName: 'bg-primary text-white',
84
+ customUIRender: ({ handleSubmit, onSubmit, translations }) => (
85
+ <form onSubmit={handleSubmit(onSubmit)} className="p-6 bg-white">
86
+ <h2 className="text-xl font-bold mb-2">{translations.title}</h2>
87
+ <div className="text-sm mb-4">
88
+ {translations.description.map((item, index) => (
89
+ <li key={index}>{item}</li>
90
+ ))}
91
+ </div>
92
+ <button
93
+ type="submit"
94
+ className="w-full h-9 bg-black-300 text-white"
95
+ >
96
+ {translations.button}
97
+ </button>
98
+ </form>
99
+ )
32
100
  }}
33
101
  />
34
102
  );
@@ -37,7 +105,55 @@ const GPay = () => {
37
105
  export default GPay;
38
106
  ```
39
107
 
40
- ##### File Path: src/app/[commerce]/[locale]/[currency]/orders/garanti-pay-redirect/page.tsx
108
+ ## Custom UI Usage
109
+
110
+ ```javascript
111
+ import PluginModule, { Component } from '@akinon/next/components/plugin-module';
112
+
113
+ const GPay = () => {
114
+ return (
115
+ <PluginModule
116
+ component={Component.GPay}
117
+ props={{
118
+ translations: {
119
+ title: 'Pay with GarantiPay',
120
+ description: [
121
+ 'Click the button below to pay for your order using GarantiPay.'
122
+ ],
123
+ button: 'Pay Now'
124
+ },
125
+ customUIRender: ({ handleSubmit, onSubmit, translations }) => (
126
+ <form
127
+ onSubmit={handleSubmit(onSubmit)}
128
+ className="p-6 bg-white rounded-md space-y-4"
129
+ >
130
+ <h2 className="text-2xl font-semibold">{translations.title}</h2>
131
+ <ul className="text-sm text-gray-700 list-disc pl-4">
132
+ {translations.description.map((item, index) => (
133
+ <li key={index}>{item}</li>
134
+ ))}
135
+ </ul>
136
+ <button
137
+ type="submit"
138
+ className="w-full h-10 bg-black text-white rounded"
139
+ >
140
+ {translations.button}
141
+ </button>
142
+ </form>
143
+ )
144
+ }}
145
+ />
146
+ );
147
+ };
148
+
149
+ export default GPay;
150
+ ```
151
+
152
+ ## Garanti Pay Redirect
153
+
154
+ ```bash
155
+ /src/app/[commerce]/[locale]/[currency]/orders/garanti-pay-redirect/page.tsx
156
+ ```
41
157
 
42
158
  ```javascript
43
159
  import { GarantiPayRedirect } from '@akinon/pz-gpay/src/routes/garanti-pay-redirect';
@@ -47,10 +163,26 @@ export default GarantiPayRedirect;
47
163
 
48
164
  ### Props
49
165
 
50
- | Properties | Type | Description |
51
- | -------------------- | ------ | ------------------------------------------ |
52
- | translations | object | The translations of the component. |
53
- | containerClassName | string | The CSS class to apply to the container. |
54
- | titleClassName | string | The CSS class to apply to the title. |
166
+ | Properties | Type | Description |
167
+ | --- | --- | --- |
168
+ | translations | object | The translations of the component. |
169
+ | containerClassName | string | The CSS class to apply to the container. |
170
+ | titleClassName | string | The CSS class to apply to the title. |
55
171
  | descriptionClassName | string | The CSS class to apply to the description. |
56
- | buttonClassName | string | The CSS class to apply to the button. |
172
+ | buttonClassName | string | The CSS class to apply to the button. |
173
+ | customUIRender | function | Custom render function for the component. Receives form handlers and translations. |
174
+
175
+ ### Default Translations
176
+
177
+ ```javascript
178
+ {
179
+ title: 'Pay with GarantiPay',
180
+ description: [
181
+ 'Click on the "GarantiPay" button. Guarantee for payment you will be redirected to the payment page.',
182
+ 'Log in to your account with your username and password on the Garanti Pay page. If you do not have a Garanti Pay membership, create a new membership you can create.',
183
+ 'Complete the payment process by selecting your card and payment type.'
184
+ ],
185
+ button: 'Pay with GarantiPay',
186
+ error: 'An error occurred during payment'
187
+ }
188
+ ```
@@ -1,9 +1,16 @@
1
1
  'use client';
2
2
 
3
3
  import * as yup from 'yup';
4
- import { ReactNode, useEffect, useState } from 'react';
4
+ import { ReactNode, ReactElement, useEffect, useState } from 'react';
5
+ import {
6
+ SubmitHandler,
7
+ useForm,
8
+ UseFormHandleSubmit,
9
+ Control,
10
+ FieldErrors
11
+ } from 'react-hook-form';
5
12
  import { RootState } from 'redux/store';
6
- import { SubmitHandler, useForm } from 'react-hook-form';
13
+ import { yupResolver } from '@hookform/resolvers/yup';
7
14
  import { useAppSelector } from '@akinon/next/redux/hooks';
8
15
  import { useSetPaymentOptionMutation } from '@akinon/next/data/client/checkout';
9
16
  import {
@@ -11,61 +18,104 @@ import {
11
18
  useSetGPayMethodMutation
12
19
  } from '../endpoints';
13
20
  import { twMerge } from 'tailwind-merge';
14
- import { yupResolver } from '@hookform/resolvers/yup';
15
21
  import { Button } from '@akinon/next/components/button';
16
- import { getPosError } from '@akinon/next/utils';
22
+ import { getPosError, checkPaymentWillRedirect } from '@akinon/next/utils';
23
+
24
+ type GPayForm = Record<string, never>;
17
25
 
18
26
  const gpayFormSchema = yup.object();
19
27
 
20
- enum Elements {
21
- container,
22
- title,
23
- description,
24
- button
25
- }
28
+ type GPayTranslations = {
29
+ title: string;
30
+ description: string[];
31
+ button: string;
32
+ error: string;
33
+ };
26
34
 
27
- type ElementClassKey = `${keyof typeof Elements}ClassName`;
35
+ type GPayOptionProps = {
36
+ containerClassName?: string;
37
+ titleClassName?: string;
38
+ descriptionClassName?: string;
39
+ buttonClassName?: string;
40
+ translations?: Partial<GPayTranslations>;
41
+ customUIRender?: (params: {
42
+ handleSubmit: UseFormHandleSubmit<GPayForm>;
43
+ onSubmit: SubmitHandler<GPayForm>;
44
+ control: Control<GPayForm>;
45
+ errors: FieldErrors<GPayForm>;
46
+ translations: GPayTranslations;
47
+ }) => ReactElement;
48
+ };
28
49
 
29
- type GpayOptionProps = {
30
- [key in ElementClassKey]?: string;
31
- } & {
32
- translations?: {
33
- [element in keyof typeof Elements]?: string | ReactNode;
34
- };
50
+ const defaultTranslations: GPayTranslations = {
51
+ title: 'Pay with GarantiPay',
52
+ description: [
53
+ 'Click on the "Pay with GarantiPay" button. Guarantee for payment you will be redirected to the payment page.',
54
+ 'Log in to your account with your username and password on the Garanti Pay page. If you do not have a Garanti Pay membership, create a new membership you can create.',
55
+ 'Complete the payment process by selecting your card and payment type.'
56
+ ],
57
+ button: 'Pay with GarantiPay',
58
+ error: 'An error occurred during payment'
35
59
  };
36
60
 
37
- export function GPayOption(props: GpayOptionProps) {
38
- const [formError, setFormError] = useState(null);
39
- const [errors, setErrors] = useState([]);
61
+ export function GPayOption(props: GPayOptionProps) {
62
+ const [formError, setFormError] = useState<string | null>(null);
63
+ const [errors, setErrors] = useState<string[]>([]);
64
+ const [isProcessing, setIsProcessing] = useState(false);
65
+
66
+ const translations = { ...defaultTranslations, ...props.translations };
40
67
 
41
68
  const { preOrder } = useAppSelector((state: RootState) => state.checkout);
42
69
  const isPaymentStepBusy = useAppSelector(
43
70
  (state: RootState) => state.checkout.steps.payment.busy
44
71
  );
45
72
 
46
- const { handleSubmit } = useForm({
47
- resolver: yupResolver(gpayFormSchema)
73
+ const {
74
+ handleSubmit,
75
+ control,
76
+ formState: { errors: formErrors, isSubmitting }
77
+ } = useForm<GPayForm>({
78
+ resolver: yupResolver(gpayFormSchema) as any
48
79
  });
49
80
 
50
81
  const [setPaymentOption] = useSetPaymentOptionMutation();
51
- const [setGPayMethod] = useSetGPayMethodMutation();
52
- const [setCompleteGPay] = useSetCompleteGPayMutation();
53
-
54
- const onSubmit: SubmitHandler<null> = async () => {
55
- if (isPaymentStepBusy) return;
56
-
57
- setFormError([]);
58
-
59
- await setGPayMethod();
60
-
61
- const response = await setCompleteGPay().unwrap();
62
-
63
- if (response?.errors?.non_field_errors) {
64
- setFormError(response?.errors?.non_field_errors);
82
+ const [setGPayMethod, { isLoading: isSettingMethod }] =
83
+ useSetGPayMethodMutation();
84
+ const [setCompleteGPay, { isLoading: isCompleting }] =
85
+ useSetCompleteGPayMutation();
86
+
87
+ const isButtonDisabled =
88
+ isSubmitting ||
89
+ isPaymentStepBusy ||
90
+ isSettingMethod ||
91
+ isCompleting ||
92
+ isProcessing;
93
+
94
+ const onSubmit: SubmitHandler<GPayForm> = async () => {
95
+ if (isButtonDisabled) return;
96
+
97
+ setIsProcessing(true);
98
+ setFormError(null);
99
+
100
+ try {
101
+ await setGPayMethod();
102
+
103
+ const response = await setCompleteGPay().unwrap();
104
+
105
+ if (response?.errors?.non_field_errors) {
106
+ setFormError(response.errors.non_field_errors);
107
+ setIsProcessing(false);
108
+ return;
109
+ }
110
+
111
+ if (response?.errors || !checkPaymentWillRedirect(response)) {
112
+ setIsProcessing(false);
113
+ }
114
+ } catch (error) {
115
+ setIsProcessing(false);
65
116
  }
66
117
  };
67
118
 
68
- // this is necessary because we need to set the payment_option after the redirect
69
119
  useEffect(() => {
70
120
  const posErrors = getPosError();
71
121
 
@@ -79,6 +129,16 @@ export function GPayOption(props: GpayOptionProps) {
79
129
  // eslint-disable-next-line react-hooks/exhaustive-deps
80
130
  }, []);
81
131
 
132
+ if (props.customUIRender) {
133
+ return props.customUIRender({
134
+ handleSubmit,
135
+ onSubmit,
136
+ control,
137
+ errors: formErrors,
138
+ translations
139
+ });
140
+ }
141
+
82
142
  return (
83
143
  <form
84
144
  id="gpay_payment"
@@ -86,38 +146,31 @@ export function GPayOption(props: GpayOptionProps) {
86
146
  onSubmit={handleSubmit(onSubmit)}
87
147
  >
88
148
  <h1 className={twMerge('text-2xl font-bold', props.titleClassName)}>
89
- {props.translations?.title ?? 'Pay with GarantiPay'}
149
+ {translations.title}
90
150
  </h1>
151
+
91
152
  <div className={twMerge('text-sm', props.descriptionClassName)}>
92
- {props.translations?.description ? (
93
- props.translations.description
94
- ) : (
95
- <>
96
- <li>
97
- Click on the “Pay with GarantiPay” button. Guarantee for payment
98
- you will be redirected to the payment page.
99
- </li>
100
- <li>
101
- Log in to your account with your username and password on the
102
- Garanti Pay page. If you do not have a Garanti Pay membership,
103
- create a new membership you can create.
104
- </li>
105
- <li>
106
- Complete the payment process by selecting your card and payment
107
- type.
108
- </li>
109
- </>
110
- )}
153
+ {translations.description.map((item, index) => (
154
+ <li key={index}>{item}</li>
155
+ ))}
111
156
  </div>
112
- <Button className={twMerge('w-full mt-3', props.buttonClassName)}>
113
- {props.translations?.button ?? 'Pay with GarantiPay'}
157
+
158
+ <Button
159
+ data-testid="gpay-payment-button"
160
+ className={twMerge('w-full mt-3', props.buttonClassName)}
161
+ type="submit"
162
+ disabled={isButtonDisabled}
163
+ >
164
+ {translations.button}
114
165
  </Button>
166
+
115
167
  {formError && (
116
168
  <div className="w-full text-xs text-start px-1 mt-3 text-error">
117
169
  {formError}
118
170
  </div>
119
171
  )}
120
- {errors && (
172
+
173
+ {errors.length > 0 && (
121
174
  <div className="w-full text-xs text-start px-1 mt-3 text-error">
122
175
  {errors.map((error) => (
123
176
  <div key={error}>{error}</div>