@akinon/pz-flow-payment 1.107.0-rc.86 → 1.108.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,13 +1,12 @@
1
1
  # @akinon/pz-flow-payment
2
2
 
3
- ## 1.107.0-rc.86
3
+ ## 1.108.0-rc.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - 1b9e9bee: BRDG-14604: Refactor FlowPayment component to utilize RTK Query mutations for payment options and wallet selection, enhancing session management and initialization logic
8
- - d8be48fb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
9
- - 8b1d24eb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
10
- - 33d4d0c: ZERO-3615: remove custom not found
7
+ - ce2e9e20: ZERO-3640: Add error handling and WalletCompletePage request to FlowPayment
8
+
9
+ ## 1.107.0
11
10
 
12
11
  ## 1.106.0
13
12
 
@@ -46,29 +45,6 @@
46
45
  ### Minor Changes
47
46
 
48
47
  - 69e4cc5: BRDG-14604: Refactor FlowPayment component to optimize flow initialization and cleanup logic
49
- - d8be48fb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
50
- - 8b1d24eb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
51
-
52
- ## 1.96.0-rc.60
53
-
54
- ## 1.96.0-rc.59
55
-
56
- ## 1.96.0-rc.58
57
-
58
- ## 1.96.0-rc.57
59
-
60
- ## 1.96.0-rc.56
61
-
62
- ### Minor Changes
63
-
64
- - 69e4cc5: BRDG-14604: Refactor FlowPayment component to optimize flow initialization and cleanup logic
65
-
66
- ## 1.96.0-rc.55
67
-
68
- ### Minor Changes
69
-
70
- - d8be48fb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
71
- - 8b1d24eb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
72
48
 
73
49
  ## 1.95.0
74
50
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@akinon/pz-flow-payment",
3
3
  "license": "MIT",
4
- "version": "1.107.0-rc.86",
4
+ "version": "1.108.0-rc.0",
5
5
  "main": "src/index.tsx",
6
6
  "dependencies": {
7
7
  "@checkout.com/checkout-web-components": "0.7.0-beta"
@@ -1,10 +1,7 @@
1
1
  import { checkoutApi } from '@akinon/next/data/client/checkout';
2
2
  import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
3
- import {
4
- useSetPaymentOptionMutation,
5
- useSetWalletSelectionPageMutation,
6
- useSetWalletPaymentPageMutation
7
- } from '@akinon/next/data/client/checkout';
3
+ import { getCookie } from '@akinon/next/utils';
4
+ import { getUrlPathWithLocale } from '@akinon/next/utils/localization';
8
5
  import {
9
6
  ComponentOptions,
10
7
  CustomTranslations,
@@ -13,7 +10,23 @@ import {
13
10
  PaymentSessionResponse
14
11
  } from '@checkout.com/checkout-web-components';
15
12
  import { RootState } from '@theme/redux/store';
16
- import { useEffect, useRef, useMemo } from 'react';
13
+ import { useEffect, useRef, useMemo, useState } from 'react';
14
+
15
+ const formatErrorMessage = (errors: any): string => {
16
+ if (typeof errors === 'string') {
17
+ return errors;
18
+ }
19
+
20
+ if (Array.isArray(errors)) {
21
+ return errors.join(', ');
22
+ }
23
+
24
+ if (typeof errors === 'object' && errors !== null) {
25
+ return Object.values(errors).join(', ');
26
+ }
27
+
28
+ return 'An error occurred during payment processing';
29
+ };
17
30
 
18
31
  export default function FlowPayment({
19
32
  options
@@ -65,11 +78,9 @@ export default function FlowPayment({
65
78
  const lastAmountRef = useRef<string | null>(null);
66
79
  const isInitializingRef = useRef<boolean>(false);
67
80
  const walletPaymentInitialized = useRef<boolean>(false);
81
+ const [errors, setErrors] = useState<any>(null);
68
82
 
69
- // RTK Query mutations
70
- const [setPaymentOption] = useSetPaymentOptionMutation();
71
- const [setWalletSelectionPage] = useSetWalletSelectionPageMutation();
72
- const [setWalletPaymentPage] = useSetWalletPaymentPageMutation();
83
+ const environment = options?.environment || 'sandbox';
73
84
 
74
85
  // Memoize the amount to track actual changes
75
86
  const currentAmount = useMemo(() => {
@@ -77,100 +88,104 @@ export default function FlowPayment({
77
88
  }, [preOrder.total_amount]);
78
89
 
79
90
  const initWalletSelection = async () => {
80
- if (!walletPaymentInitialized.current) {
81
- walletPaymentInitialized.current = true;
82
-
83
- const walletSelectionPageResponse = await dispatch(
84
- checkoutApi.endpoints.setWalletSelectionPage.initiate({
85
- payment_option: preOrder.payment_option?.pk
86
- })
87
- ).unwrap();
91
+ const walletSelectionPageResponse = await dispatch(
92
+ checkoutApi.endpoints.setWalletSelectionPage.initiate({
93
+ payment_option: preOrder.payment_option?.pk
94
+ })
95
+ ).unwrap();
88
96
 
89
- const walletPaymentPageContext =
90
- walletSelectionPageResponse.context_list.find(
91
- (c) => c.page_name === 'WalletPaymentPage'
92
- );
97
+ const walletPaymentPageContext =
98
+ walletSelectionPageResponse.context_list.find(
99
+ (c) => c.page_name === 'WalletPaymentPage'
100
+ );
93
101
 
94
- if (walletPaymentPageContext) {
95
- dispatch(checkoutApi.endpoints.setWalletPaymentPage.initiate({}));
96
- }
102
+ if (!walletPaymentPageContext) {
103
+ return;
97
104
  }
98
- };
99
105
 
100
- const refreshPaymentSessionAndInitFlow = async ({
101
- publicKey
102
- }: {
103
- publicKey: string;
104
- }) => {
105
- // Prevent multiple simultaneous initializations
106
- if (isInitializingRef.current) {
107
- return;
106
+ // FIX: Only call setWalletPaymentPage if not already initialized
107
+ if (!walletPaymentInitialized.current) {
108
+ walletPaymentInitialized.current = true;
109
+ dispatch(checkoutApi.endpoints.setWalletPaymentPage.initiate({}));
108
110
  }
111
+ };
109
112
 
110
- isInitializingRef.current = true;
113
+ const handlePaymentSuccess = async (paymentData: any) => {
114
+ setErrors(null);
111
115
 
112
116
  try {
113
- // Step 1: Re-set the current payment option to refresh the session with new amount
114
- if (preOrder.payment_option?.pk) {
115
- const paymentOptionResponse = await setPaymentOption(
116
- preOrder.payment_option.pk
117
- ).unwrap();
118
-
119
- // Check if WalletSelectionPage is available in the context list
120
- const walletSelectionPageContext =
121
- paymentOptionResponse.context_list?.find(
122
- (c) => c.page_name === 'WalletSelectionPage'
123
- );
117
+ const walletPaymentPageResponse = await dispatch(
118
+ checkoutApi.endpoints.setWalletPaymentPage.initiate({
119
+ payment_token: JSON.stringify(paymentData)
120
+ })
121
+ ).unwrap();
124
122
 
125
- if (!walletSelectionPageContext) {
126
- console.warn(
127
- 'WalletSelectionPage not found in payment option response context list'
128
- );
129
- return;
130
- }
123
+ if (walletPaymentPageResponse.errors) {
124
+ setErrors(walletPaymentPageResponse.errors);
125
+ return;
131
126
  }
132
127
 
133
- // Step 2: Set wallet selection page
134
- const walletSelectionResponse = await setWalletSelectionPage({
135
- payment_option: preOrder.payment_option?.pk
136
- }).unwrap();
128
+ if (
129
+ walletPaymentPageResponse.context_list.find(
130
+ (c) => c.page_name === 'WalletCompletePage'
131
+ )
132
+ ) {
133
+ const paymentCompleteResponse = await dispatch(
134
+ checkoutApi.endpoints.setWalletCompletePage.initiate(true)
135
+ ).unwrap();
137
136
 
138
- const walletPaymentPageContext =
139
- walletSelectionResponse.context_list?.find(
140
- (c) => c.page_name === 'WalletPaymentPage'
141
- );
137
+ if (paymentCompleteResponse.errors) {
138
+ setErrors(paymentCompleteResponse.errors);
139
+ return;
140
+ }
142
141
 
143
- if (!walletPaymentPageContext) {
144
- return;
142
+ if (
143
+ paymentCompleteResponse.context_list.find(
144
+ (c) => c.page_name === 'ThankYouPage'
145
+ )
146
+ ) {
147
+ const redirectUrl = paymentCompleteResponse.context_list.find(
148
+ (c) => c.page_name === 'ThankYouPage'
149
+ )?.page_context.context_data.redirect_url;
150
+
151
+ const redirectUrlWithLocale = `${
152
+ window.location.origin
153
+ }${getUrlPathWithLocale(
154
+ redirectUrl,
155
+ getCookie('pz-locale') || 'en'
156
+ )}`;
157
+
158
+ window.location.href = redirectUrlWithLocale;
159
+ }
145
160
  }
146
-
147
- // Step 3: Set wallet payment page to get updated payment session
148
- const walletPaymentResponse = await setWalletPaymentPage({}).unwrap();
149
-
150
- // Step 4: Initialize flow component with fresh payment session
151
- await initFlowPaymentWebComponents({
152
- publicKey,
153
- paymentSession:
154
- walletPaymentResponse?.pre_order?.context_extras ||
155
- preOrder.context_extras
156
- });
157
161
  } catch (error) {
158
- console.error(
159
- 'Error refreshing payment session and initializing flow:',
160
- error
161
- );
162
- } finally {
163
- isInitializingRef.current = false;
162
+ console.error('Error processing payment completion:', error);
163
+ setErrors(error);
164
164
  }
165
165
  };
166
166
 
167
+ const handlePaymentError = async (error: any) => {
168
+ console.error('Payment error occurred:', error);
169
+ setErrors(error);
170
+ };
171
+
167
172
  const initFlowPaymentWebComponents = async ({
168
- publicKey,
169
- paymentSession
173
+ publicKey
170
174
  }: {
171
175
  publicKey: string;
172
- paymentSession?: any;
173
176
  }) => {
177
+ // Prevent multiple simultaneous initializations
178
+ if (isInitializingRef.current) {
179
+ return;
180
+ }
181
+
182
+ // Check if amount has actually changed
183
+ if (lastAmountRef.current === currentAmount && flowComponentRef.current) {
184
+ return; // Don't recreate component if amount hasn't changed
185
+ }
186
+
187
+ isInitializingRef.current = true;
188
+
174
189
  try {
175
190
  // Destroy existing flow component if it exists
176
191
  if (flowComponentRef.current) {
@@ -188,7 +203,27 @@ export default function FlowPayment({
188
203
  container.innerHTML = '';
189
204
  }
190
205
 
191
- // Update the last amount reference
206
+ // FIX: Use existing payment session instead of making another API call
207
+ let paymentSession = preOrder.context_extras;
208
+
209
+ // Only get fresh payment data if we don't have a valid session
210
+ if (!paymentSession && lastAmountRef.current !== currentAmount) {
211
+ // Check if another call is already in progress
212
+ if (!walletPaymentInitialized.current) {
213
+ walletPaymentInitialized.current = true;
214
+ const walletPaymentResponse = await dispatch(
215
+ checkoutApi.endpoints.setWalletPaymentPage.initiate({})
216
+ ).unwrap();
217
+ paymentSession =
218
+ walletPaymentResponse?.pre_order?.context_extras ||
219
+ preOrder.context_extras;
220
+ } else {
221
+ // Use existing context_extras if another call was already made
222
+ paymentSession = preOrder.context_extras;
223
+ }
224
+ }
225
+
226
+ // Update the last amount reference after getting fresh data
192
227
  lastAmountRef.current = currentAmount;
193
228
 
194
229
  const checkout = await loadCheckoutWebComponents({
@@ -196,15 +231,21 @@ export default function FlowPayment({
196
231
  environment: options?.environment || 'production',
197
232
  locale: options?.locale || 'en',
198
233
  translations: options?.translations,
199
- paymentSession:
200
- paymentSession || (preOrder.context_extras as PaymentSessionResponse),
234
+ paymentSession: paymentSession as PaymentSessionResponse,
201
235
  appearance: options?.appearance,
202
236
  componentOptions: options?.componentOptions
203
237
  ? { flow: options.componentOptions }
204
238
  : undefined
205
239
  });
206
240
 
207
- const flowComponent = checkout.create('flow');
241
+ const flowComponent = checkout.create('flow', {
242
+ onPaymentCompleted: async (_self, paymentResponse) => {
243
+ handlePaymentSuccess(paymentResponse);
244
+ },
245
+ onError: async (_self, error) => {
246
+ handlePaymentError(error);
247
+ }
248
+ });
208
249
  flowComponentRef.current = flowComponent;
209
250
 
210
251
  if (container) {
@@ -212,6 +253,8 @@ export default function FlowPayment({
212
253
  }
213
254
  } catch (error) {
214
255
  console.error('Error initializing flow payment components:', error);
256
+ } finally {
257
+ isInitializingRef.current = false;
215
258
  }
216
259
  };
217
260
 
@@ -219,7 +262,6 @@ export default function FlowPayment({
219
262
  initWalletSelection();
220
263
  }, []);
221
264
 
222
- // Initial flow component initialization
223
265
  useEffect(() => {
224
266
  if (
225
267
  !preOrder.wallet_method ||
@@ -230,37 +272,16 @@ export default function FlowPayment({
230
272
  return;
231
273
  }
232
274
 
233
- // Only initialize if component doesn't exist yet (initial mount)
234
- if (!flowComponentRef.current) {
235
- initFlowPaymentWebComponents({
236
- publicKey: walletPaymentData.data.public_key
237
- });
238
- }
275
+ initFlowPaymentWebComponents({
276
+ publicKey: walletPaymentData.data.public_key
277
+ });
239
278
  }, [
240
279
  preOrder.wallet_method,
241
280
  preOrder.token,
281
+ currentAmount, // Use memoized amount instead of preOrder.total_amount
242
282
  walletPaymentData?.data?.public_key
243
283
  ]);
244
284
 
245
- // Handle amount changes by refreshing payment session
246
- useEffect(() => {
247
- if (
248
- !preOrder.wallet_method ||
249
- !preOrder.token ||
250
- !walletPaymentData?.data?.public_key ||
251
- preOrder.wallet_method !== 'checkout_flow'
252
- ) {
253
- return;
254
- }
255
-
256
- // Only refresh if component exists and amount has changed
257
- if (flowComponentRef.current && lastAmountRef.current !== currentAmount) {
258
- refreshPaymentSessionAndInitFlow({
259
- publicKey: walletPaymentData.data.public_key
260
- });
261
- }
262
- }, [currentAmount]);
263
-
264
285
  // Cleanup function when component unmounts
265
286
  useEffect(() => {
266
287
  return () => {
@@ -276,5 +297,14 @@ export default function FlowPayment({
276
297
  };
277
298
  }, []);
278
299
 
279
- return <div id="flow-container" className="flow-payment-container"></div>;
300
+ return (
301
+ <div className="flow-payment-container">
302
+ <div id="flow-container"></div>
303
+ {errors && (
304
+ <div className="text-error-100 text-xs mt-5">
305
+ {formatErrorMessage(errors)}
306
+ </div>
307
+ )}
308
+ </div>
309
+ );
280
310
  }