@akinon/pz-flow-payment 1.108.0-rc.87 → 1.108.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,16 @@
1
1
  # @akinon/pz-flow-payment
2
2
 
3
- ## 1.108.0-rc.87
3
+ ## 1.108.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
- - 33d4d0c9: ZERO-3615: remove custom not found
7
+ - 21d88c1b: ZERO-3640: Remove redundant API calls
8
+ - d8883ce6: ZERO-3640: Refactor wallet completion handling to accept additional parameters; update related API calls
9
+ - f85464e9: ZERO-3640: Fix API call to use hyphenated key for payment ID in FlowPayment component
10
+ - ce2e9e20: ZERO-3640: Add error handling and WalletCompletePage request to FlowPayment
11
+ - e00b4a82: ZERO-3640: Refactor wallet selection initialization and error handling; streamline payment session management
12
+ - 59ed7a7e: ZERO-3640: Add order number state and update FlowPayment component for wallet payment response handling
13
+ - 31a2d35a: ZERO-3640: Refactor checkout API call to include useFormData option; update FlowPayment component for improved error handling and code readability
11
14
 
12
15
  ## 1.107.0
13
16
 
@@ -48,29 +51,6 @@
48
51
  ### Minor Changes
49
52
 
50
53
  - 69e4cc5: BRDG-14604: Refactor FlowPayment component to optimize flow initialization and cleanup logic
51
- - d8be48fb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
52
- - 8b1d24eb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
53
-
54
- ## 1.96.0-rc.60
55
-
56
- ## 1.96.0-rc.59
57
-
58
- ## 1.96.0-rc.58
59
-
60
- ## 1.96.0-rc.57
61
-
62
- ## 1.96.0-rc.56
63
-
64
- ### Minor Changes
65
-
66
- - 69e4cc5: BRDG-14604: Refactor FlowPayment component to optimize flow initialization and cleanup logic
67
-
68
- ## 1.96.0-rc.55
69
-
70
- ### Minor Changes
71
-
72
- - d8be48fb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
73
- - 8b1d24eb: ZERO-3422: Update fetch method to use dynamic request method in wallet complete redirection middleware
74
54
 
75
55
  ## 1.95.0
76
56
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@akinon/pz-flow-payment",
3
3
  "license": "MIT",
4
- "version": "1.108.0-rc.87",
4
+ "version": "1.108.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
@@ -64,12 +77,10 @@ export default function FlowPayment({
64
77
  const flowComponentRef = useRef<any>(null);
65
78
  const lastAmountRef = useRef<string | null>(null);
66
79
  const isInitializingRef = useRef<boolean>(false);
67
- const walletPaymentInitialized = useRef<boolean>(false);
68
-
69
- // RTK Query mutations
70
- const [setPaymentOption] = useSetPaymentOptionMutation();
71
- const [setWalletSelectionPage] = useSetWalletSelectionPageMutation();
72
- const [setWalletPaymentPage] = useSetWalletPaymentPageMutation();
80
+ const [errors, setErrors] = useState<any>(null);
81
+ const [walletSelectionReady, setWalletSelectionReady] =
82
+ useState<boolean>(false);
83
+ const [orderNumber, setOrderNumber] = useState<string | null>(null);
73
84
 
74
85
  // Memoize the amount to track actual changes
75
86
  const currentAmount = useMemo(() => {
@@ -77,100 +88,110 @@ 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
+ setErrors(
104
+ 'Payment method not available. Please try a different payment option.'
105
+ );
106
+ setWalletSelectionReady(false);
107
+ return;
97
108
  }
98
- };
99
109
 
100
- const refreshPaymentSessionAndInitFlow = async ({
101
- publicKey
102
- }: {
103
- publicKey: string;
104
- }) => {
105
- // Prevent multiple simultaneous initializations
106
- if (isInitializingRef.current) {
110
+ // Initialize wallet payment page to get payment session data
111
+ const walletPaymentPageResponse = await dispatch(
112
+ checkoutApi.endpoints.setWalletPaymentPage.initiate({
113
+ payment_method: 'checkout_flow',
114
+ wallet_method: preOrder.wallet_method
115
+ })
116
+ ).unwrap();
117
+
118
+ if (walletPaymentPageResponse.errors) {
119
+ setErrors(walletPaymentPageResponse.errors);
120
+ setWalletSelectionReady(false);
107
121
  return;
108
122
  }
109
123
 
110
- isInitializingRef.current = true;
124
+ // Store the order number from the wallet payment page response
125
+ if (walletPaymentPageResponse.pre_order?.number) {
126
+ setOrderNumber(walletPaymentPageResponse.pre_order.number);
127
+ }
111
128
 
112
- 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
- );
124
-
125
- if (!walletSelectionPageContext) {
126
- console.warn(
127
- 'WalletSelectionPage not found in payment option response context list'
128
- );
129
- return;
130
- }
131
- }
129
+ setWalletSelectionReady(true);
130
+ };
132
131
 
133
- // Step 2: Set wallet selection page
134
- const walletSelectionResponse = await setWalletSelectionPage({
135
- payment_option: preOrder.payment_option?.pk
136
- }).unwrap();
132
+ const handlePaymentSuccess = async (paymentData: any) => {
133
+ setErrors(null);
137
134
 
138
- const walletPaymentPageContext =
139
- walletSelectionResponse.context_list?.find(
140
- (c) => c.page_name === 'WalletPaymentPage'
141
- );
135
+ try {
136
+ const ckoPaymentId = paymentData?.id;
137
+ const isSuccess = paymentData?.status === 'Approved';
138
+
139
+ const paymentCompleteResponse = await dispatch(
140
+ checkoutApi.endpoints.setWalletCompletePage.initiate({
141
+ success: isSuccess,
142
+ 'cko-payment-id': ckoPaymentId,
143
+ oid: orderNumber
144
+ })
145
+ ).unwrap();
142
146
 
143
- if (!walletPaymentPageContext) {
147
+ if (paymentCompleteResponse.errors) {
148
+ setErrors(paymentCompleteResponse.errors);
144
149
  return;
145
150
  }
146
151
 
147
- // Step 3: Set wallet payment page to get updated payment session
148
- const walletPaymentResponse = await setWalletPaymentPage({}).unwrap();
152
+ if (
153
+ paymentCompleteResponse.context_list.find(
154
+ (c) => c.page_name === 'ThankYouPage'
155
+ )
156
+ ) {
157
+ const redirectUrl = paymentCompleteResponse.context_list.find(
158
+ (c) => c.page_name === 'ThankYouPage'
159
+ )?.page_context.context_data.redirect_url;
149
160
 
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
- });
161
+ const redirectUrlWithLocale = `${
162
+ window.location.origin
163
+ }${getUrlPathWithLocale(redirectUrl, getCookie('pz-locale') || 'en')}`;
164
+
165
+ window.location.href = redirectUrlWithLocale;
166
+ }
157
167
  } catch (error) {
158
- console.error(
159
- 'Error refreshing payment session and initializing flow:',
160
- error
161
- );
162
- } finally {
163
- isInitializingRef.current = false;
168
+ console.error('Error processing payment completion:', error);
169
+ setErrors(error);
164
170
  }
165
171
  };
166
172
 
173
+ const handlePaymentError = async (error: any) => {
174
+ console.error('Payment error occurred:', error);
175
+ setErrors(error);
176
+ };
177
+
167
178
  const initFlowPaymentWebComponents = async ({
168
- publicKey,
169
- paymentSession
179
+ publicKey
170
180
  }: {
171
181
  publicKey: string;
172
- paymentSession?: any;
173
182
  }) => {
183
+ // Prevent multiple simultaneous initializations
184
+ if (isInitializingRef.current) {
185
+ return;
186
+ }
187
+
188
+ // Check if amount has actually changed
189
+ if (lastAmountRef.current === currentAmount && flowComponentRef.current) {
190
+ return; // Don't recreate component if amount hasn't changed
191
+ }
192
+
193
+ isInitializingRef.current = true;
194
+
174
195
  try {
175
196
  // Destroy existing flow component if it exists
176
197
  if (flowComponentRef.current) {
@@ -188,7 +209,18 @@ export default function FlowPayment({
188
209
  container.innerHTML = '';
189
210
  }
190
211
 
191
- // Update the last amount reference
212
+ // FIX: Use existing payment session from preOrder.context_extras
213
+ let paymentSession = preOrder.context_extras;
214
+
215
+ // If we don't have payment session data, we need to wait for it
216
+ if (!paymentSession) {
217
+ console.warn(
218
+ 'FlowPayment: Payment session not available yet, component will retry when data is ready'
219
+ );
220
+ return;
221
+ }
222
+
223
+ // Update the last amount reference after getting fresh data
192
224
  lastAmountRef.current = currentAmount;
193
225
 
194
226
  const checkout = await loadCheckoutWebComponents({
@@ -196,15 +228,21 @@ export default function FlowPayment({
196
228
  environment: options?.environment || 'production',
197
229
  locale: options?.locale || 'en',
198
230
  translations: options?.translations,
199
- paymentSession:
200
- paymentSession || (preOrder.context_extras as PaymentSessionResponse),
231
+ paymentSession: paymentSession as PaymentSessionResponse,
201
232
  appearance: options?.appearance,
202
233
  componentOptions: options?.componentOptions
203
234
  ? { flow: options.componentOptions }
204
235
  : undefined
205
236
  });
206
237
 
207
- const flowComponent = checkout.create('flow');
238
+ const flowComponent = checkout.create('flow', {
239
+ onPaymentCompleted: async (_self, paymentResponse) => {
240
+ handlePaymentSuccess(paymentResponse);
241
+ },
242
+ onError: async (_self, error) => {
243
+ handlePaymentError(error);
244
+ }
245
+ });
208
246
  flowComponentRef.current = flowComponent;
209
247
 
210
248
  if (container) {
@@ -212,6 +250,8 @@ export default function FlowPayment({
212
250
  }
213
251
  } catch (error) {
214
252
  console.error('Error initializing flow payment components:', error);
253
+ } finally {
254
+ isInitializingRef.current = false;
215
255
  }
216
256
  };
217
257
 
@@ -219,48 +259,29 @@ export default function FlowPayment({
219
259
  initWalletSelection();
220
260
  }, []);
221
261
 
222
- // Initial flow component initialization
223
262
  useEffect(() => {
224
263
  if (
225
264
  !preOrder.wallet_method ||
226
- !preOrder.token ||
227
265
  !walletPaymentData?.data?.public_key ||
228
- preOrder.wallet_method !== 'checkout_flow'
266
+ preOrder.wallet_method !== 'checkout_flow' ||
267
+ !walletSelectionReady ||
268
+ !preOrder.context_extras
229
269
  ) {
230
270
  return;
231
271
  }
232
272
 
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
- }
273
+ initFlowPaymentWebComponents({
274
+ publicKey: walletPaymentData.data.public_key
275
+ });
239
276
  }, [
240
277
  preOrder.wallet_method,
241
278
  preOrder.token,
242
- walletPaymentData?.data?.public_key
279
+ currentAmount, // Use memoized amount instead of preOrder.total_amount
280
+ walletPaymentData?.data?.public_key,
281
+ walletSelectionReady,
282
+ preOrder.context_extras // Add payment session dependency
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 () => {
@@ -271,10 +292,17 @@ export default function FlowPayment({
271
292
  console.warn('Error destroying flow component on unmount:', error);
272
293
  }
273
294
  }
274
- // Reset initialization flag on unmount
275
- walletPaymentInitialized.current = false;
276
295
  };
277
296
  }, []);
278
297
 
279
- return <div id="flow-container" className="flow-payment-container"></div>;
298
+ return (
299
+ <div className="flow-payment-container">
300
+ <div id="flow-container"></div>
301
+ {errors && (
302
+ <div className="text-error-100 text-xs mt-5">
303
+ {formatErrorMessage(errors)}
304
+ </div>
305
+ )}
306
+ </div>
307
+ );
280
308
  }